The Agent That Builds Itself

Most agent frameworks split decisions into agent-decidable tools and developer-decidable transitions. Collapse that split — make the transition itself a tool — and the agent can design new modes mid-conversation. A 700-line demo with a verification loop, plus an honest accounting of what's still missing.

Most agent frameworks split decisions in two. Tools are agent-decidable: the model picks which one to fire and what to put into it. Transitions between modes — from a "research" node to a "draft" node, from a receptionist mask to a planner mask — are developer-decidable: the engine watches for some signal, and the wiring lives in code only humans edit. This split is load-bearing in ways nobody questions, and the cost only shows up when the visitor walks in with a request that doesn't fit the chain you wired.

Collapse the split and something useful falls out. Make the transition itself a tool. Now the agent can not only pick which mode to enter, it can *design new modes* — write a tool that targets a mode, register it, and use it. The chain becomes data the agent can edit.

This post is about that one refactor and the second-order effects when you take it seriously. I built a tiny TypeScript demo to make the pattern concrete: github.com/ski/mask-chain-toy, about 700 lines.

---

The refactor

In a uniform agent, every tool — including a transition — returns one of two things:

type ToolResult =
  | { kind: 'continue';  observation: unknown }   // engine loops
  | { kind: 'terminate'; observation?: unknown }; // engine stops this step

A regular tool like search_web returns continue. A morph tool returns terminate and mutates state via a ctx the engine handed it:

const MORPH_TO_PLANNER: Tool = {
  name: 'morph_to_planner',
  execute: async (input, ctx) => {
    if (!input.visitor_name) return { kind: 'continue', observation: { error: 'missing' } };
    ctx.manifest.artifacts.visitor_name = input.visitor_name;
    ctx.setNextMask('planner');
    return { kind: 'terminate' };
  },
};

Read it carefully. Nothing in there is engine-special. The morph tool is identifiable as "the one that flips currentMask," but the engine doesn't know that — it just dispatches every action through the same path. Tools are control flow, all the way down.

The closest neighbour to this design is LangGraph, which represents an agent as a state machine of nodes, with the framework routing between them. Worth being precise about the distinction: LangGraph nodes are developer-authored Python functions, and the routing decisions live in conditional edges that the developer also writes. The model's role is to produce outputs that those edges read. In the morph-as-tool design, the routing rides on the tool call itself — a tool is the unit of state change, the model picks tools, the engine doesn't know which ones happen to mutate the chain. It's a smaller framework that cedes more authority to the model.

Where this bites

If your conversation chain is short and known up front — receptionist → planner → done — the split between agent-decidable and developer-decidable doesn't cost you anything. You wire the transitions once and ship.

It bites in three regimes:

Long-tail intents. Visitor walks in with a request your chain doesn't model. In the rigid-chain world the engineer adds a new mask, redeploys, and the visitor who triggered it is long gone. In the morph-as-tool world the agent can *grow* a handler for the unfamiliar shape and route the current visitor into it. Whether you want it to is a different question, but the *capability* exists.

Multi-tenant systems where each tenant has a different conversation shape. A B2B SaaS with 200 customers each running their own variant of the same flow can't ship 200 hardcoded chains. They can ship one chain plus the ability for the agent (or a tenant-admin LLM session) to extend it per-tenant. The chain is per-tenant data, not engine code.

Exploratory domains where the right modes aren't known up front. Anyone building agents in a new vertical knows the second draft of the mask chain looks nothing like the first. If the chain is engine code, every iteration is a redeploy. If the chain is data the agent can shape, iteration happens at conversation time. You discover what masks exist by watching what conversations need.

If your domain doesn't fit any of those, you don't need this pattern. Most don't.

The demo

The toy ships two scenarios. First scenario is the rigid-chain happy path — receptionist captures the visitor's name and intent, morphs to planner, planner picks an option, ends. This is what every agent framework does today.

Second scenario is where the asymmetry breaks. The visitor wants a kitchen renovation quote. The receptionist has no morph_to_estimator because nobody wrote one. The agent does the work itself, in two turns:

[receptionist] Renovations — let me set something up for that.
    [engine] defined tool: estimate_renovation (3 ops)
    [engine] (test:estimate_renovation) looked up 3 options for "kitchen renovation"
    [engine] test_tool estimate_renovation: PASS — verified
[receptionist] Verified the estimator. Routing you to it now.
    [engine] defined mask: estimator (2 tools)
    [engine] defined tool: morph_to_estimator (3 ops)
    [engine] attached tool 'morph_to_estimator' to mask 'receptionist'
    [engine] (agent-defined: morph_to_estimator) handed off to agent-built estimator
↳ cascade-fire into 'estimator'
  [estimator] Composing your kitchen-reno quote from current option data.

Three things to notice. First, estimate_renovation has a body that *composes* the existing lookup_options tool via a call_tool op — it produces real output by delegating to a tool the agent didn't write, not by returning a literal. Second, between defining the tool and using it the agent calls test_tool against a fixture; if the result doesn't match expectations the tool isn't trusted and the agent re-plans. Third, only after PASS does the agent build a mask around the verified tool, define a morph, attach it to itself, and route in. The chain has grown.

What's missing — Voyager and the verification half

The honest comparison is Voyager, the Minecraft agent that writes its own JavaScript skills. Voyager's contribution isn't that it grows skills — that's the easy half. Voyager's contribution is that it *validates* every new skill against environmental feedback. Minecraft tells you whether the agent ate the apple. The skill is added to the library only when the world confirms it works.

The toy's verification step is a sketched-down version of that. The agent supplies its own fixture, the runtime runs the tool against it, asserts the result kind / observation keys / artifact effects, and adds the tool to a verified set on pass. It catches the commonest failure mode (tool doesn't actually do what its describe string claims) but it has three real limits:

  1. Reachability isn't tested. A verified tool can still call_tool into something dangerous; the fixture only exercises one path.
  2. Side effects on real systems aren't sandboxed. A tool that writes to a real API will write during the test run too. You'd want a sandbox manifest scoping capture to non-sensitive fields, or a "safe-to-compose" tag on each tool the agent can call_tool into.
  3. The agent that wrote the tool wrote the fixture. If it can pass its own test, it passes. The actual production verification loop is a separate "auditor" agent writing fixtures for a "builder" agent's tools — Voyager's analogue of a hostile Minecraft.

Decoupling the chain from the engine is half the work. The verification loop is the other half. The toy makes the structural cut and gestures at the verification side; productionising means investing the rest of the way.

What the safety claim actually says

A define_tool that accepts JS strings + uses eval is a remote-code-execution backdoor. The toy avoids this by having the agent emit structured ops — capture, observe, morph, complete, log, terminate, call_tool — and the runtime interprets them. Adversarial input is bounded by the op union; an unrecognised op gets logged and skipped.

That bounds the *vocabulary*. It does not bound the *reachability graph*. A call_tool op chained into send_email is still calling send_email. A capture op writing into a field a downstream trusted tool reads is still mutating that field. If your tool registry contains anything you wouldn't hand the agent a literal pointer to, you need either a "safe-to-compose" allowlist on each tool the agent can call_tool into, or a sandbox manifest that scopes capture to non-sensitive state. Neither is in the toy. Both are in any production deployment.

A persistence stance

Where does the agent's invented tool live? In-memory only? On disk? Committed to git?

The four answers are four different systems:

| Where | Lifetime | What problem it creates |

|---|---|---|

| In-memory only | Until the process restarts | The agent reinvents the same mask 100 times across 100 sessions |

| Per-session disk | Across that visitor's conversations | Tool-library lifecycle: pruning, naming collisions, retrieval |

| Global persistence | Forever, all clients | Multi-tenant contamination, review-authority problem |

| Committed to source | Forever, audited | You've reinvented PR review with an LLM author |

The toy is in-memory only. That's the right scope for what it demonstrates: in-session adaptation. The visitor walks in with an unfamiliar shape, the agent grows a handler for *this conversation*, the session ends, the registry resets. No multi-session compounding, no shared tool library, no contamination.

The pattern *can* extend to longer durabilities, but each step up adds a different problem. In-session-only is what the structural argument is about. Anyone telling you their self-modifying agent persists invented tools globally without addressing the review-authority question is either small enough to handwave it or hasn't hit it yet.

The smaller stuff worth saying

A working agent built around this pattern needs more than the structural primitive. You'd also need:

  • A skill-retrieval step, because a growing tool library blows out context windows. Embed each tool's describe, semantically retrieve the top-K relevant per turn.
  • An immutability barrier so the agent can't redefine define_tool to be a no-op. The toy uses a core set; production wants something stronger (signed registries, capability tokens).
  • A cascade-depth cap, because nothing structural in the engine prevents an agent that defines a mask whose first turn defines another mask whose first turn defines another. The toy's outer loop is implicitly bounded; a real system wants an explicit counter.

None of these are in scope for the structural sketch. All of them are real before you ship.

---

The version of Themis I keep coming back to has a cracked sword. The crack isn't damage — it's evidence the sword has been used enough that it carries the marks. The morph-as-tool refactor is small. The thing it lets you walk toward — agents that shape themselves to the work in front of them, with a verification loop honest about what it does and doesn't catch — is the cracked sword. Not a different instrument. The same one, used hard enough that you can see what it's been doing.

If you want to read the toy, run the toy, or steal the pattern: github.com/ski/mask-chain-toy.

End of Transmission.