engine
source

04 · extend

Five surfaces. One rule.

Tools, the wire, subagents, transports, models — none of them are closed. Every one is a file: drop it, decorate or register it, restart the session. No registry to edit, no client to rebuild.

Tools — 32 shipped, ten families.

What ships today, not what's possible. A new family shows up the same way as the 32nd tool did.

Navigation3
Tree-sitter5
Language server6
Text editing4
Structural editing3
Execution1
History2
Git2
Web2
Browser4

The wire — 12 commands, 27 events.

A tool is a bet the model takes alone. A command or event is a promise every client depends on — it stays reviewable on purpose.

for the model

Tools

A bet the model takes alone. Drop a file, decorate a function, restart. Cheap to add, cheap to be wrong.

for the client

Commands & events

A promise every client depends on. Reviewed before it ships — the wire only grows by agreement.

StartSessionBind a workspace, resume from SQLite, boot language servers.
ListSessionsStored sessions, newest first.
SubmitUserMessageOne orchestrator turn. Queues if it's already answering.
RequestSnapshotReconnect payload — history, files, tree, git.
RequestOrchContextThe orchestrator's live model context.
OpenFile / CloseFileDrive the file panel. Marks a file read for the funnel.
RequestGitBranch, dirty, staged / unstaged / untracked.
UndoLastEditReverts the last batch. SHA-verified.
AbortAgentCancel the orchestrator's reply, or one subagent by id.
AnswerPromptApprove a command, or settle a worktree.
ShutdownPersist, kill children, drop worktrees, stop servers.

Same rule, in code.

A tool and a command look almost nothing alike on the page — one is a decorator, the other a reviewable dataclass pair. The ceremony to ship either is still one file and a restart.

Surface

tools/echo.py · for the model

Drop a module under tools/. Decorate the function. Restart the session — the engine imports the package tree and registers anything with @tool. No catalog list to edit, no client to rebuild. AgentLoop sends those schemas on each model call and, if the model returns tool_calls, runs them and calls again (capped at 16 turns).

If the agent should read git, add a tool.

from tools.base import ToolContext, tool
 
@tool(description="Echo text back.")
def echo(ctx: ToolContext, text: str) -> str:
return text
 
# name = function name
# schema from type hints, or parameters=
# ctx.workspace is the project root

model --tool calls--> tools/*.py

client --JSON cmd----> protocol/commands.py --> @handles

engine --JSON event--> protocol/events.py

Three more surfaces.

The orchestrator, the transport, and the model behind it all extend the same way.

A subagent

A module in agents/profiles/. The orchestrator sees it as a spawn tool automatically.

# agents/profiles/auditor.py
PROFILE = AgentProfile(
    name="auditor",
    system_prompt=AUDITOR_SYSTEM,
    tool_names=NAV + SITTER,
)

A transport

EngineServer only ever calls three methods on a session. WebSocket or HTTP needs no core changes.

session.subscribe(client)
await session.handle(command)
session.unsubscribe(client)

A model

AgentLoop needs one method. Implement it and swap out OpenRouter.

class MyProvider:
    async def complete(self, messages, tools=None) -> LLMResult:
        ...

The personas themselves — what each one can and can't touch — are on agents.

back to00.overview