the dev kit

Self-modification

The agent edits its own source, rebuilds it, and loads the result while it is still answering you. This page covers the tools that do it and the limits drawn around them.

The tools it uses on itself

These appear in the model's tool list whenever the dev kit is available. Whether self-modification is offered at all is a setting, [devkit] in thetis.toml, alongside the list of files guests may never edit.

ToolEffect
new_tool(name, description)Scaffolds a tool crate, builds it, loads it
write_code(target, path, contents)Replaces a file, rebuilds, hot-swaps
patch_code(target, path, old_text, new_text)Exact-match patch, rebuilds, hot-swaps
read_code / list_codeInspection
add_dependency / remove_dependency / list_dependenciesEdits a component's dependencies and rebuilds. Adding one makes cargo reach the network, so the build is killed if it overruns its timeout, and the previous build keeps serving
branch_status / branch_logWhere this conversation's branch stands, and its commit history
reset_branch(rev)Restores the whole branch to how it was at rev, as a new commit — history is preserved, nothing is rewritten
update_from_trunk / complete_merge / abort_mergeBrings the latest trunk into the branch; conflicts come back marked in the working tree, resolved with the ordinary editing tools

target is self, tool:<name>, or gateway:<name>. A tool scaffolded by new_tool starts from templates/tool-template.

The version control underneath is git, and the sandbox is a branch. Every conversation runs on its own branch of the source tree, checked out in its own worktree, and every green build lands there as a commit. One verb is deliberately missing from the contract: merging to trunk. Landing a conversation's changes on trunk is a human decision, made in the UI.

The compiler answers in the tool result

Every mutating call rebuilds its target immediately and returns the compiler's verdict in the tool result. When a build fails, the error text is the string the model reads next, in the same loop iteration, with no human in between to notice it and relay it.

So a broken edit is repaired inside a single turn. The agent writes a file, reads the error, patches the line, and sees the build come back clean, all within one loop the user watches happen. The running revision is untouched the whole time, because a candidate that does not compile never reaches the loader.

When a change starts running

A tool created this way is callable on the very next loop iteration. The agent can decide it needs a capability, build it, and use it before the turn is over.

A change the agent makes to itself takes effect when the current turn ends. That follows from per-call instantiation: the turn in flight finishes on the component it started with, and the next call picks up the rewritten one. The agent never swaps the ground out from under its own running loop.

What it can do to the machine

These are on, and confined. Turn any of them off in thetis.toml.

ToolEffect
read_path / write_path / list_path / delete_pathFiles on the host, confined to filesystem.roots
terminal_open / terminal_run / terminal_read / terminal_close / terminal_listShell sessions that keep their working directory and state between commands
restart_orchestratorReplaces the Thetis process, for changes to the native binary or to startup-only settings

The roots are the boundary. Every path is resolved, symlinks followed, and must land inside filesystem.roots, which defaults to the project root alone. A terminal session starts inside a root for the same reason.

The protected list, holding data, artifacts and .git by default, is a smaller thing. It stops the system deleting its own state by accident. It is not a security control, because a shell can reach those paths regardless.

Be clear-eyed about what enabling terminal access means: the agent can run anything you can. filesystem.enabled, filesystem.allow_delete, terminal.enabled and control.allow_restart each turn a slice of that off.

Restarting is scheduled rather than immediate. The call returns, the turn finishes, and the user reads why before the process goes away. The replacement is spawned before the old process exits, so a failure to start leaves the current one running, and the new process retries binding while the old one releases the port. Restarts are refused before control.min_uptime_secs, so a restart that fixes nothing cannot become a loop.

Turns survive a restart

A turn cut short by restart_orchestrator, a crash, or anything else is picked up again when Thetis comes back. Resuming costs nothing structurally, because the agent is stateless between turns: it rebuilds its context from the session log every time, so carrying on means running a turn again against a log that now records the interruption.

Two things happen before that can work.

restart_orchestrator takes resume, defaulting to true. Setting it false ends the turn at the restart instead, closing it in the log so nothing is left looking like it is still thinking. A turn that keeps dying stops being resumed after a couple of attempts, and the count resets whenever a turn reaches its end.

What it cannot write

Paths cannot escape an aspect's source tree. The dev kit works in terms of a target and a path inside it, not in terms of the filesystem, so an edit aimed at the agent lands in the agent's crate or is refused.

Build-time files are a setting rather than a ban. devkit.protected_files and devkit.protected_dirs list paths a component may never edit in its own tree, and both default to empty: a component that cannot edit its own Cargo.toml cannot add a dependency, and one that cannot edit build.rs cannot generate code. Those are ordinary things for it to need, and the experiment stays on the conversation's branch either way — what reaches trunk is decided by a person.

The grip itself, wit/thetis.wit, is refused for a related reason: changing it rebuilds every guest at once, and it is the file that decides what a guest may reach.