Kin / How Kin works
Agents
How kin's agent system works — isolated task delegation, full-context forkagent delegation, and the --agent <name CLI for running the main session as a profile.
Read as MarkdownHow kin’s agent system works — isolated task delegation, full-context
fork_agent delegation, and the --agent <name> CLI for running the main
session as a profile.
Two delegation contexts
task is the fresh-context specialist: its prompt must be self-contained, and
it can choose a named profile or model override. fork_agent is the continuity
path: it inherits the represented conversation through the current user
request and keeps the parent’s identity and model. Use a fork when prior
discussion changes what “correct” means; use a task for an independent view,
specialist scope, or lower prompt cost.
Both can run foreground or background and become rows in the same supervised
registry. The ambient strip, panel, agent_list, and agent_inspect state the
scope rather than leaving it implicit.
Two ways to run a profile
A profile is a named toolset + persona (a markdown file with frontmatter; see Subagents). You can run a profile in two ways:
| Path | What it is |
|---|---|
task(subagent_type=<name>, prompt=…) |
A delegation — the main session stays the main thread; the subagent gets a fresh context with only the prompt, runs to completion, and returns a summary. Foreground by default. |
kin --agent <name> |
A launch — the profile IS the main thread. No parent turn; the CLI walks the same Session factory with the profile’s tools + system prompt. |
task is for fan-out (parallel focused work, isolating a noisy subtask). --agent is for running kin as a narrow specialist (a code reviewer, a research analyst) where you do not want the main session’s tools or identity.
The two paths share the profile loader (subagents.resolve_for(workdir)), the system-prompt composition, and the tool scoping — they cannot drift apart silently.
Foreground subagents
Foreground is the default task path. See Subagents for the full surface — profiles, scope, recursion caps, the 32 KB result cap. The short version:
- The subagent sees only the
prompt, never the parent conversation. - The subagent inherits the parent’s permission mode (destructive calls inside it still prompt).
- The subagent’s tool calls fold into the parent’s
taskrow (routed viaparent_tool_call_id): the row’s title tracks the latest call live plus a running call counter, and expanding the row shows the full per-call activity log — no separate transcript line per child call. A nested subagent’s calls fold into the same line. - Depth 4 and parallel 3 by default; selected MiniMax/Z.ai subscription tiers raise ordinary foreground and background capacity to 4–7 while preserving the depth guard.
- A live-updating timer row. The moment the spawn actually happens, the tool-call row in the transcript flips to a ⬡ glyph with a ticking elapsed timer (
Ns) in place of the plain running glyph — so you can see the subagent is working even if it never calls a tool (a self-contained plan review, for instance, might just think and reply). This coverstask’s foreground path — including when the model dispatches thecriticsubagent itself after you choose Review plan first (adversarial) inpresent_plan’s approval modal. The subagent’s own prose is still never streamed to the transcript (see the critic agent below) — only the timer, the folded activity, and, on finish, the call count + elapsed duration are visible until the result lands. - Registered in the agent registry (shared id space with background). A foreground child gets a stable id (
agent1,agent2, …) and a registry row insess.agents, soagent_list/agent_output/agent_killreach it mid-run and after it finishes. A running foreground child is killable without cancelling the parent turn —agent_kill <fg-id>(or the panel’sx) cancels the child and thetasktool returnserror: subagent <id> was killed before finishing, leaving the parent turn free to continue. ++escape++ on the parent turn still cancels + closes a running foreground child (and re-raises, so the worker actually cancels).
fork_agent uses the same foreground lifecycle. Its child-safe live registry
keeps dynamic/MCP tools and task, while removing interactive/planning controls
and recursive fork_agent.
Background subagents
A backgrounded subagent (task(subagent_type=…, prompt=…, run_in_background=true)) detaches from the parent turn and runs alongside it. The parent turn returns immediately with an agent id (e.g. agent1); the bg subagent runs to completion in its own asyncio task. The parent can list, read, and kill it without waiting.
parent turn: returns "started background subagent agent1 (coder) — isolated prompt"
bg agent: runs in parallel — tool calls fold into its ambient-strip row (live activity)
lifecycle event → the MCA observes and settles it automatically
The companion tools
Seven meta (auto-allowed) tools own the agent lifecycle — for BOTH foreground and background children (they share one registry + id space):
| Tool | What it does |
|---|---|
agent_list |
Snapshot of every agent — fg/bg, lifecycle, outcome, health, mailbox and tool metadata. It never embeds child prose |
agent_output(agent_id, wait?, max_wait_ms?) |
Read a finished agent’s buffered prose (uncapped — bg agents can be large). On a still-running agent with wait=true, blocks up to max_wait_ms for completion |
agent_kill(agent_id) |
Graceful-then-cancel-then-aclose (mirrors shell_kill). Works on a running foreground child too (cancels the child without cancelling the parent). Idempotent on already-finished agents |
agent_message(agent_id, message, run_in_background?) |
Queue FIFO steering for running/pausing/paused children without resuming a pause, or continue an idle retained child in foreground/background |
agent_inspect(agent_id) |
Return structured lifecycle, lineage, liveness, mailbox/tool/usage/output/session metadata; exact diagnostics follow in an untrusted frame |
agent_wait(agent_ids, condition?, max_wait_ms?) |
Block on one or more agents when the model cannot proceed without them (max_wait_ms defaults to 30s, clamped 10–60s), return snapshots, and claim the lifecycle events it reports. A timed-out wait returns compact liveness rows plus a note restating that settlement arrives on its own — and counts consecutive misses, so a poll loop indicts itself |
agent_control(agent_id, action, …) |
Cooperatively pause/resume, promptly interrupt while retaining the session, restart as a linked fresh attempt, or close an idle row |
The run_in_background arg
coerce_bool (not bare bool) — "true", "1", "yes", "on" all enable; everything else falsy. This matches the shell tool’s run_in_background coercion, so a hallucinated string the model sends by accident still detaches correctly.
Automatic supervisor attention
Completion, failure, pause, interruption, and stale-health events automatically regain the main coding agent’s attention. During an active turn they are batched at the next model-round boundary. While idle, Kin starts a serialized internal continuation with no synthetic user bubble; queued human input wins.
The exactly-once reminder carries event/run ids, lifecycle, outcome, health,
duration, usage, tool count, error availability, and outstanding handles.
Terminal settlements are always listed in full; accumulated health/pause
chatter (for example, hours of watchdog stale/healthy cycles) is capped at
the three newest entries with a collapsed-count note. It never carries child
prose or raw exceptions; use agent_output or agent_inspect to retrieve
those in an untrusted frame.
Lifecycle delivery is a correctness property and has no off switch. The
retired KIN_BG_REMINDERS / bg_completion_reminders names have no effect.
Because delivery is guaranteed, polling is explicitly discouraged: the system
prompt, the tool descriptions, and every timed-out agent_wait all tell the
main agent that a healthy running child needs nothing from it — do other work
or end the turn, and act when the event arrives. Supervision belongs to the
main agent, not to you: you never need to watch, relay, or nudge a subagent
yourself.
The ambient strip vs. the agent panel
A bg subagent now has two surfaces, at two different levels of detail:
| Surface | When it’s visible | What it shows | What you can do |
|---|---|---|---|
| The ambient strip (below the composer) | Always mounted; shows itself the moment any bg subagent is dispatched, auto-hides a finished row after ~30s | A compact one-line-per-agent summary — status glyph, profile, and the agent’s live activity (its latest tool call + a call counter, replacing the static prompt preview while it runs; a finished row reverts to the preview + elapsed duration). Capped at 5 rows; a busier session collapses the rest into a +N more — Ctrl+O hint |
Nothing — it’s purely ambient awareness, no kill/dismiss/inspect |
The agent panel (Ctrl+O) |
On demand | One focused view at a time. Agent rows lead with status/profile, then show current activity or task, elapsed time, tool-call count, and prompt tokens; bg shells and the tasks DAG have separate counted views | Kill a running agent, dismiss a finished row, open its transcript or raw buffered output |
The strip exists so a bg subagent isn’t invisible between Ctrl+O checks — before it landed, the only way to notice one had started (or finished) was to open the panel and look. The panel remains the deep-inspection surface; the strip never replaces it.
The agent panel (Ctrl+O)
The panel is the visual discovery path for task(...) — both foreground and background children (DR 0057’s unified registry). Without it a bg subagent is invisible, and a running fg subagent is only as visible as the agent-mode ToolCall row it spawned from. It mounts as a ModalScreen with three counted views, showing only one at a time so empty or secondary sections do not squeeze the useful rows. It opens on the first populated view, with agents first:
| Pane | Contents |
|---|---|
agents |
Every child agent — foreground + background (the unified AgentRegistry). A two-line row leads with profile, human status, placement, elapsed time, and id; the second line shows current activity (or the assigned task) plus available tool-call and prompt-token totals. |
shells |
Live + finished bg shells (mirrors shell_list) |
tasks |
The Tasks DAG (see Tasks) |
Keys:
| Key | Action |
|---|---|
| ++tab++ | Cycle the focused view (agents → shells → tasks → agents) |
| ++shift+tab++ | Cycle backward |
x |
Kill a running row / dismiss a finished one (works for fg rows too — Step 1’s kill path cancels the child WITHOUT cancelling the parent) |
o |
Open the row’s raw buffered output in a ReadOnlyModal |
| ++enter++ | Open the row’s rich transcript in a TranscriptModal (falls through to the read-only output for shell/task rows; Enter is never a no-op) |
| ++escape++ | Close the panel |
Ctrl+O opens it (the same priority binding as shift+tab / ctrl+c, guarded behind modals so it can’t pop on top of an open approval / question / plan prompt).
The BgAgentsBar (the below-composer strip) deliberately stays bg-only — fg activity already renders live in the transcript’s agent-mode ToolCall row, so re-listing it on the bar would double-surface. The panel is the deep-inspection surface for both fg + bg.
Dipping into an agent (Step 7)
Enter on an agent row opens the child’s full transcript in a TranscriptModal — the rich view, not the raw buffered output (o). The modal renders the same widget set /resume paints for a saved journal: a rounded “YOU” border for each user prompt, the flat “▌ KIN” assistant blocks with their Markdown bodies, and the ToolCall rows with their arguments + paired results. The snapshot of child_session.messages is taken synchronously at open — list(child_session.messages) is a copy under the same event loop, so a snapshot in one tick is a consistent point-in-time view of the child’s transcript (no torn reads across user turns). The modal does NOT live-tail; closing-and-reopening refreshes. A running child’s transcript IS readable via this path (the messages list is the same list the running agent is appending to — it survives across tool calls and tool results, and Step 3’s retention keystone keeps it readable for closed children too).
TranscriptionModal lives modal-on-modal on top of the AgentPanel (the same shape as o’s ReadOnlyModal push — a 3-deep screen stack: app → AgentPanel → TranscriptModal). Esc / q dismisses back to the panel; the focus return is automatic. The modal is read-only by design — there is no input path into the child from this view (messaging is the MCA’s, via agent_message; user → agent is deliberately out of scope).
The capacity cap
The root session’s provider-aware capacity is enforced at dispatch time
against background agents only (running_bg_count()). Local/custom
endpoints default to 3; selected MiniMax/Z.ai plans allow 4–7. A spawn at the
active limit returns, for example:
error: at capacity: 5 background agents already running — wait for one to finish or kill one with agent_kill
Foreground children do not count against this cap — they’re bounded by the
same provider-aware semaphore + the depth cap, so a session full of running
foreground subagents can still dispatch background work. The cap remains the
runaway guard; choosing a larger paid plan raises it deliberately through
/providers, never from a project file or model action.
Killing + interrupting
agent_kill/ the panel’sxon a running row → graceful cancel +acloseon the child’s session (kills its bg shells + cancels pending blockers). Idempotent on a finished agent. Works on a running foreground child too: the child is cancelled and the parent’staskcall returnserror: subagent <id> was killed before finishing, leaving the parent turn free to continue (the parent is NOT cancelled).- ++escape++ during the parent turn does not kill background agents — they’re designed to outlive a foreground interrupt. (A running foreground child IS cancelled by Esc, along with the parent turn.) Only
Session.aclose(session-end / app exit) tears bg agents down. - A finished child parks, it isn’t destroyed. When a subagent’s run ends (success or crash), its session stays live on the registry row — the full transcript is retained in
messages, readable via the panel (Ctrl+O→o/ Enter) andagent_output, and resumable later. Closing is deliberate:agent_kill, dismissing a finished row, and app exit (Session.aclose) eachaclosethe child’s session so its lazyrun_codekernel / headless browser / bg shells don’t orphan. A closed row’s transcript stays readable (closing tears down processes, not data). - Each subagent also writes its own journal (hidden from
/resume/--continue— see Sessions).
When a subagent fails
A foreground task call can fail in a few ways, and each one produces a
structured, id-bearing result so the parent can tell what happened and
where the full transcript lives. The result is ALWAYS prefixed with
error: — that prefix is the cross-backend is_error mechanism
(loop._finalize auto-detects it; the Anthropic wire carries
is_error: true, the OpenAI wire has no error field at all, so the text
prefix is the only signal that reaches the model on both wires). Full
shapes are in Subagents — When a subagent fails;
the short version:
error(wire death after retries) —error: subagent <id> failed mid-run — partial output below; transcript retained (agent_output("<id>"))- the partial prose. A mid-run wire death surfaces the partial prose
with a marker — the parent can no longer mistake a half-answer for a
whole one (the silent-partial bug closed by DR 0059). Before DR 0059 a
fg child that died mid-stream had
done_reason=None(the fg path dropped thedoneevent) and the partial prose surfaced as the complete answer with no marker.
- the partial prose. A mid-run wire death surfaces the partial prose
with a marker — the parent can no longer mistake a half-answer for a
whole one (the silent-partial bug closed by DR 0059). Before DR 0059 a
fg child that died mid-stream had
turn_cap(a custom profile hit its positivemax-turns) — same shape, head carries the cap value (hit its turn cap (N turns)). Bundled profiles are unlimited; this outcome exists for an operator-imposed custom bound. Replaces the bare"(the subagent produced no output)"sentinel.loop_detected(doom-loop guard) — same shape, named accordingly.crashed(a raw harness exception) —error: subagent <id> crashed: {exc} — transcript retained (agent_output("<id>")). The child parks, NOT closes (see the “finished child parks” note under Killing + interrupting above), so the transcript hint is true.killed(agent_kill/ panelx) —error: subagent <id> was killed before finishing. The parent turn continues.
The full transcript is always on the row via agent_output("<id>") (or
the panel — Ctrl+O → o / Enter) for every failed row. The 32 KB
cap is signalled by a truncated flag on the child’s emit, not by
string-sniffing — the shaper reads the flag and appends the id-bearing hint
when the cap fires.
The row’s status + done_reason mirror the bg path: a wire death is
status=error + done_reason=error; a turn cap is status=ok +
done_reason=turn_cap. The agent_list tool surfaces done_reason when
it isn’t stop.
Every failure string also carries the resume hint
agent_message("<id>", "<next instruction>") — a parked child has a real
way back in (the turn-completion resume model),
so a failed task call hands the model the recovery path in the same
read as the transcript hint. The stop branch deliberately omits the
resume hint — clean stop is a completion, not a failure to recover from.
Profile-as-session: --agent <name>
kin --agent researcher # main session runs as the researcher profile
kin --agent coder --workdir ~/.. # the main thread = coder, against a path
The CLI walks the same Session factory with the profile’s tools, the profile’s system prompt, and the parent’s KIN_* / settings.toml config (mode, model, base_url, sandbox — all honored). Session.from_profile reuses the same profile loader the task tool uses (subagents.resolve_for(workdir)), so the two paths can’t drift on profile resolution.
What’s stripped
Interactive-only tools (ask, present_plan) are stripped from a profile-as-session — a focused agent is not its own question-asker when it IS the main thread. The same strip applies to bg subagents (see TaskTool.run).
The launch flow
kin --agent researcher→cli.mainreads--agentand resolves the profile.Session.from_profilepopulatesSUBAGENTSagainst the workdir, scopes the registry to the profile’s tools, and composessystem = SYSTEM_PROMPT + profile.system_prompt.- The same
app.bind_client(harness)+app.run()path as a normal launch. - The top bar / banner still show the model + workspace, so you can tell at a glance which agent you’re running.
No-TTY caveat
--agent is a TTY-only path — it launches the Textual UI, which needs a terminal. If stdin.isatty() is false (a non-interactive shell, a piped launch), the CLI refuses with a clear error. If you need a non-interactive --agent run, drive the harness directly via a Python from kin.harness.session import Session + Session.from_profile(...) call (that’s the same factory the CLI uses).
Resuming
--resume <id> + --agent <name> is rejected at the CLI (the two flags are mutually exclusive in cli.py’s arg validation): an agent session is a fresh run, not a replay. If you want to resume a session, run kin --resume <id> without --agent; the saved journal meta determines the model + base_url. To run a profile against a new workdir, use kin --agent <name> standalone.
The planner agent
planner is a bundled, read-only profile that drafts the actual plan content. Rather than researching and writing a plan inline, the top-level model dispatches it — task(subagent_type='planner', prompt=…) — the same way it dispatches any other subagent. It doesn’t see the parent conversation, only the prompt it’s given.
Its toolset is research-only: read_file, glob, grep, ls, web_fetch, web_search, search_workspace, todos, plus write_plan. It researches the codebase (and the web, if useful) until it understands what needs to change — reading every file it will name, tracing call sites, checking the repo’s own guidance docs — then writes a thorough, self-contained plan: goal, context with file:line evidence of how the code works today, the chosen design plus rejected alternatives, files to touch/not touch with exact anchors, constraints and conventions, an ordered task list with per-task checks, risks and edge cases, acceptance criteria, a verify command, and an empty issue log — via write_plan, whose first call auto-enters the planning freeze on the top-level session. The bar is deliberate: on Approve, clear & re-inject the plan becomes the executor’s only context — and the executor may be a smaller model — so it must carry everything execution needs, written for a reader who has never seen the repo. It never calls present_plan itself — only the top-level agent can present a plan to you — so its final message is just the plan’s path plus a short pointer to 2-3 files worth spot-checking, letting the orchestrator validate before presenting. The profile lives at src/kin/harness/defaults/agents/planner.md.
The critic agent
critic is a bundled, read-only profile used by the plan lifecycle. When you pick Review plan first in the present_plan approval modal, the top-level model dispatches critic itself (via task(subagent_type='critic', ...)) against the tracked plan file and returns its critique; you stay in the planning freeze while it applies fixes with write_plan and re-presents (only approve lifts the freeze).
It is built to stress-test, not rubber-stamp:
- Read-only toolset —
read_file,glob,grep,ls,web_fetch,web_search,todos. It can read the codebase the plan targets (and the web) to ground every objection, but it has no edit or shell tools, and it inherits the planning freeze besides — so it cannot act regardless. - Adversarial, attribution-obscured framing — the plan is presented as “a plan under review,” never “your plan” (a model criticizes an external artifact far harder than its own work). It is told to assume at least one critical flaw and find it.
- A six-dimension rubric — it leads with the first step that fails (with evidence), then reviews against SEQUENCING, ASSUMPTIONS, REVERSIBILITY, COMPLETENESS, CODEBASE_FIT, and SELF_CONTAINMENT (could a stranger execute this plan against a cleared context?), and ends with the single highest-priority fix. A single pass.
The critique comes back as prose (not structured JSON) because it is consumed two ways at once — re-injected as context for the model to act on, and shown to you — and prose is the model-agnostic floor across kin’s real fleet. The profile lives at src/kin/harness/defaults/agents/critic.md; override it like any other bundled profile by dropping a critic.md in a higher-priority discovery root.
critic-code is a sibling profile, bundled alongside critic — same
read-only toolset, same adversarial, attribution-obscured framing, but it
reviews finished code or output instead of a plan, against a different
five-dimension rubric: CORRECTNESS, EDGE_CASES, SECURITY, PERFORMANCE, and
MAINTAINABILITY. It isn’t part of the plan lifecycle; the bundled
/critique kind: workflow slash command dispatches three of them in parallel, each owning a
subset of the rubric, and synthesizes their findings by severity; the bundled
/revise command uses it as the rubric grader in its draft → grade → revise
loop (each grader reads the published draft by artifact ref).
explorer is a bundled, read-only profile for codebase orientation — it
answers “how does this codebase work” with a fixed 6-section orientation
(Purpose / Entry points / Key files / Conventions / Answers / Unknowns),
cites path:line evidence, and never pastes file bodies. Its toolset is
local-only — read_file, glob, grep, ls, search_workspace, todos,
no web — which keeps it disjoint from researcher (web) and from coder
(edits). Dispatch it when the parent needs to ground a decision in the
codebase without burning context on broad reads; it’s the canonical
context-isolation exemplar and the fan-out worker the bundled /init
and /research
commands dispatch (/research scales 1–5 explorer lanes to the question’s
complexity and has each lane publish its findings as an artifact handle).
security-auditor is a bundled, read-only profile that stress-tests
finished work against a six-dimension security rubric — INJECTION,
INPUT_HANDLING, AUTH, SECRETS, SUPPLY_CHAIN, CONFIG — and is the
structural sibling of critic-code. Two rules distinguish it from a
generic code review: every finding must name the attack path (where
untrusted data enters → what it reaches; a pathless hardening is at
most low severity), and severity is graded as exploitability × impact,
not how scary the code reads. Dispatch it independently via task when a
parent agent wants a security-focused adversarial review separate from
the broader critic-code rubric.
researcher is the bundled, read-only web-research lane — read_file plus
the web tools and cite_check, no edits, no shell.
Dispatch it for a fast, sourced answer to one question; the bundled
/deep-research kind: workflow command fans
several of them out in parallel (with an adversarial critic review and a
citation-liveness gate) when the question deserves a full multi-source
report.
rig is the bundled, non-interactive excursion lane for Kin’s persistent
computer. It is scoped to rig, rig_shell, rig_memory, and todos; it
returns a distilled answer and records durable machine knowledge before it
finishes. Credentials stay on the human handoff path — if a login is required,
the profile stops and asks the parent to have the operator take control. See
Rig for the computer boundary and control flow.
Discovery
Profiles are loaded from markdown+frontmatter on disk. The five roots, highest priority first:
<workdir>/.kin/agents/<workdir>/.claude/agents/~/.kin/agents/~/.claude/agents/- the bundled defaults at
src/kin/harness/defaults/agents/(general / researcher / coder / planner / critic / critic-code / explorer / rig / security-auditor)
The full schema + a worked example are in Subagents.