Agents¶
How kin's agent system works — both the task tool for delegating work to a focused subagent (foreground or background) and the --agent <name> CLI for running the main session as a profile.
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, parallel 3 — the runaway guards.
- 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. Esc on the parent turn still cancels + closes a running foreground child (and re-raises, so the worker actually cancels).
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)"
bg agent: runs in parallel — tool calls fold into its ambient-strip row (live activity)
completion → subagent_completed event → parent's next user turn gets a reminder
The companion tools¶
Four 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 live + recently-finished agent — fg/bg marker, id, status (+ done_reason when not stop), profile, last output line, tool-call count, prompt-token total |
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?) |
Send the next instruction to an idle subagent — appends message to its transcript and runs it as a new turn (the turn-completion resume model). Foreground returns ONLY the new turn's prose; set run_in_background=true to detach (fires a fresh subagent_completed reminder on the next user turn). Refuses on a running agent (wait for it to finish) or a closed row (transcript stays readable via agent_output) |
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.
Completion reminders¶
The next depth-0 user turn receives a <system-reminder> with metadata-only completion info (agent id, profile, status, duration, tool-call count, prompt tokens). Never the subagent's prose — a bg agent that fetched web content could carry injected text. Three rules:
- Snapshot-and-clear —
_pending_completionsis drained atomically (no awaits between read + clear), so a callback firing mid-downstream can't double-fire. - Per-turn cap — at most 3 reminders per turn; the rest collapse into
(N+ additional — Ctrl+O to inspect). - Defang —
</>in profile names are escaped, so a crafted profile name can't break out of the frame.
A seen-set (_seen_completion_ids) dedups the same agent_id finishing across two turns — its reminder fires exactly once.
The KIN_BG_REMINDERS=0 off-switch¶
Disable reminders in the shell environment (KIN_BG_REMINDERS=0 or false / no / off) or set bg_completion_reminders = false in settings.toml. The reminder text disappears; the panel still shows completions (it's the visual backstop — a quiet reminder is fine, an invisible one is not).
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 | Full detail per agent — buffered output, tool-call count, prompt tokens, plus bg shells and the tasks DAG | Kill a running agent, dismiss a finished row, open its full 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 panes:
| Pane | Contents |
|---|---|
shells |
Live + finished bg shells (mirrors shell_list) |
agents |
Every child agent — foreground + background (the unified AgentRegistry). Each row carries id · fg/bg marker · status (+ done_reason when not stop) · profile · last output · age · closed indicator when set. |
tasks |
The Tasks DAG (see Tasks) |
Keys:
| Key | Action |
|---|---|
| Tab | Cycle the focused pane (shells → agents → tasks → shells) |
| 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) |
| Esc | 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¶
MAX_BG_SUBAGENTS = 3 is enforced at dispatch time on the root session against background agents only (running_bg_count()). A fourth task(run_in_background=true) while three background agents are already running returns:
error: at capacity: 3 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 MAX_PARALLEL + the depth cap, so a session full of running foreground subagents can still dispatch background work. The cap is the runaway guard — a model can't fire 10× task(run_in_background) calls and burn a turn budget invisibly.
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).- Esc 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. turn_cap(hit the profile'smax-turns) — same shape, head carries the cap value (hit its turn cap (N turns)). 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.
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 / security-auditor)
The full schema + a worked example are in Subagents.