Skip to content

Coding agents

What every coding-agent backend shares. Generated from src/keryx/agents/base.py.

keryx.agents.base

What every coding-agent backend shares.

A backend is an AgentRunner whose open() hands back an AgentSession; everything above that seam — the task manager, the notifier, the voice tools — sees only a RunResult. So the protocol, the result, and the text protocol a subagent speaks back (SPOKEN_SUMMARY: and RESTART_REQUIRED:) live here once, along with the context every subagent is handed whichever agent runs it: the rendered system-prompt suffix, the directory it starts in, and the workspace-mcp server that gives it Gmail and Calendar.

FakeAgentRunner is the scripted stand-in used by tests and by keryx serve --demo.

Every subagent is told (via prompts/subagent_suffix.md) to end its final message with a SPOKEN_SUMMARY: line; extract_spoken_summary turns that into the sentence the voice session reads out, falling back to the last paragraph when the agent forgot.

AgentRunner

Bases: Protocol

Opens subagent conversations.

open async

open(task: Task, *, resume: str | None = None) -> AgentSession

Start (or resume, given a Claude session id) a conversation for task.

AgentSession

Bases: Protocol

One live subagent conversation.

run async

run(prompt: str, *, on_progress: Callable[[str], Any]) -> RunResult

Run one turn to completion. Never raises: failures come back as ok=False.

send async

send(text: str) -> None

Put a follow-up into the turn that is running now.

SteerUnavailable means it was refused and nothing was delivered — the agent has no live steer (Claude), or its turn has just ended — and the task manager re-runs with the text instead. Any other exception is a steer that may have landed.

interrupt async

interrupt() -> None

Ask the agent to stop the current turn.

close async

close() -> None

End the conversation and release the process.

RunResult dataclass

The outcome of one subagent turn.

TokenUsage dataclass

What one or more turns cost in tokens, the same way for every agent.

input_tokens counts every input token, cached ones included, and cached_input_tokens says how many of those were cache reads — Codex's meaning, and Claude's once its uncached, cache-write and cache-read counts are summed.

extract_spoken_summary

extract_spoken_summary(text: str) -> str

The sentence(s) to read out loud for a finished task.

Prefers the text after the last SPOKEN_SUMMARY: marker; falls back to the last paragraph of the report. Always plain, collapsed, at most MAX_SUMMARY_CHARS long.

extract_restart_request

extract_restart_request(text: str) -> str | None

The subagent's RESTART_REQUIRED: reason, or None when it did not ask for one.

A restart takes Keryx off the air and kills the phone call, so this only ever reads an explicit line. Anything that merely looks like one — the words in a report, a changed checkout — is not it.