Kin / Agents & automation
Headless runs
Run Kin in scripts with defined output, exit codes, permission handling, and budgets.
Read as Markdownkin -p runs one agent turn with no UI at all — the final assistant text goes to stdout, everything else goes to stderr, and the exit code tells a script exactly how the run ended. It’s the shape for cron jobs, systemd timers, launchd, CI steps, and shell pipelines.
To open the TUI with a first turn already supplied, use
kin -- "Review this project" instead. That interactive form requires a TTY,
keeps the session open for approvals and follow-up, and treats the launch text
literally. It does not use this page’s stdout or exit-code contract.
kin -p "Read README.md and summarize it in one sentence."
git diff | kin -p "Write a commit message for this diff."
kin -p "/ship"
kin -p "Audit the TODOs in src/ and write a report" --output ~/.kin/cron/output/todo-audit/$(date +%s).md
Exit codes — the contract
| Code | Meaning | What a script should do |
|---|---|---|
0 |
Clean run — the model finished, nothing needed a human. | Consume stdout. |
1 |
Error — backend failure, context-admission refusal, turn cap, token budget, loop guard, or a crashed run. | Check stderr / the report; retry or alert. |
2 |
Needs-human — the workspace is not persistently trusted, or the run reached an action blocked pending human approval or input. | Establish trust in an interactive launch, or answer: resume in the TUI (kin --resume <id>, printed on stderr), or — for a service-hosted run — attach to the waiting conversation (kin --attach --resume <id>) and answer the live block. |
64 |
Invalid command line (EX_USAGE): unknown flags, missing or invalid arguments, or unsupported combinations such as -p --agent. |
Fix the invocation; waiting for a person to grant trust will not fix it. |
Usage errors use 64 in the terminal and diagnostic subcommands too. Model
configuration failures use 1. Exit 2 is reserved for a human gate; when a
run both fails and needs a person, 1 wins.
Background agents run to quiescence
kin -p does not exit while registered background task agents are still
working. It processes their lifecycle events through hidden MCA continuations,
waits for active children, and captures final text/report/needs-human state only
after no active agent and no unclaimed event remains. These continuations share
the original human run’s token and round limits.
A paused child produces a quiescence_required event: the MCA must resume,
interrupt, restart, or kill it. If the MCA leaves it paused or shared limits
expire, Kin closes active children and exits with the normal error contract.
Two guardrails keep the quiescence phase honest for automation:
- A failed run stays failed. The exit code reflects the main turn: a
supervisor continuation that finishes cleanly after the main turn errored
(or hit a turn/token cap) cannot flip the run back to exit
0. - A hung agent cannot hang the process. If an active agent produces no
lifecycle events and no model/tool activity for about five minutes, the
run ends with exit
1and anagent quiescence stallederror naming the stalled agents, instead of waiting forever. A busy long-running agent never trips this — any tool or model activity counts as progress.
Terminal bell on completion
If you’re running
kin -pinteractively from another shell/tab and watching stderr, a run finishing writes a terminal bell/OSC 9 notification there (a plain bell everywhere, plus a native desktop notification on a capable terminal — Ghostty, iTerm2, WezTerm, kitty). Only fires when stderr is a real terminal (sys.stderr.isatty()) — a redirected/piped run (the normal cron shape) gets no escape bytes in its log.KIN_NOTIFY=0disables it.
The permission envelope — fail-closed, never hanging
Headless runs use the same workspace trust, modes, and permission gate as
the TUI. Before backend or tool work begins, the exact workspace must already
have a valid persistent trust row; a headless or resumed process cannot create
trust and exits 2 when the row is absent or stale. In a trusted workspace,
auto runs edits and shell/code directly; --mode strict keeps the ask-first
posture. The other difference is what happens when a call would prompt:
- In the TUI, an ASK-gated tool call (or the
asktool, orpresent_plan) parks the turn on a modal until you answer. - Headless there is no one to answer, so the call never runs. The model instead receives a structured tool error —
error: needs-human: thewrite_filecall (…) requires human approval, and this is a headless run with no one to ask…— so it can adapt, work with the tools that are allowed, and finish the turn by reporting what’s left for you. The run then exits2.
Nothing is ever silently approved and nothing hangs: the envelope replaces only the human resolution of an ASK. It never widens an Allow, creates workspace trust, or bypasses planning, protected file-tool decisions, or Strict’s shell policy. See Modes & permissions and Auto mode & trusted workspaces.
Strict mode headless = report-only
kin -p --mode strict "…"can read and research, while every edit and every shell/code operation not proven read-only becomes a needs-human error. Useful when you want an unattended run that proposes rather than changes work.
MCP servers are not connected in headless runs (their first-run approval is a human gate), and --agent is refused with -p (the agent profile’s approval/ask modals need a TTY). --resume / --continue are supported headless — see Resuming headless below.
Hosting the run in the session service — kin -p --attach
On a machine that runs the per-user session service
(kin --serve), a headless run can host its conversation in the service
instead of in its own process:
kin -p --attach "Reconcile the ledgers; ask me if anything is ambiguous" \
--idle 14400 --output /tmp/r.md
The service owns the Session, journal, lease, trust check, tools, and permissions exactly as for any other client; the headless process is one writer client. What changes is the needs-human outcome:
- A human gate stays live instead of failing. When a question, approval,
plan, or proposal fires, the model is not handed a needs-human error.
The turn stays parked on the exact pending block inside the service; the
headless client records the block, releases its writer role, detaches, and
exits
2. Stderr, the report, and the sidecar name the conversation (its id is the session id) and each pending block (needs_human[].block). - Anyone local can pick it up. Attach the terminal with
kin --attach --resume <id>(the pending question or approval replays immediately, original id and display signature intact), or let an OS client such as the Kinra OS Front Door observe and claim it. The person’s answer resolves the original block and the original turn continues — no re-prompt, no history rewrite. If the model asks again, the new block parks the same turn for the then-current writer. - The wait window is yours.
--idle SECONDS(default: the service’s 1800, max 86400) is how long the service keeps the conversation alive with no writer attached. Idle expiry ends the conversation exactly like a close — the journal keeps everything, andkin -p --attach --resume <id>or the TUI resumes it. - A completed run behaves as before. Exit
0/1with the same report contract; the client closes its conversation, and the journal remains for resume. - Nothing falls back silently. An absent or unreachable service, a busy
conversation (another writer), or a mid-turn target is a stated exit-
1failure. Flags the service’s own launch decided —--mode, model / provider / preset flags,--no-save,--token-budget,--max-turns— are refused with-p --attach; set budgets where you start the service. A resumed target that is already waiting on a human sends no new prompt: it reports the pending blocks and exits2withturn_sent: falsein the sidecar.
Local clients can also discover waiting conversations: a hello with
conversations: true returns one bounded, content-free listing of the
service’s live conversations — id, workspace, writer, active turn, idle
policy, and pending block ids/kinds — then the connection ends. See
Local clients for the exact exchange.
Resuming headless by continuation
A needs-human run isn’t dead-end headless — you can resume it with a second
kin -p whose prompt is the framed human answer:
# 1. A run that halts on an `ask` (exit 2, sidecar `needs_human`):
kin -p "standardize one env-var name; ask me which" --output /tmp/r.md
# → exit 2; /tmp/r.md.json carries session_id + needs_human[]
# 2. Resume that session with the framed answer as the new prompt:
kin -p --resume <session-id> --output /tmp/r2.md \
-- "[Operator answer to your pending question(s)]
Q1 (question): KIN_FOO
Continue the task; do not re-ask."
# → exit 0; the model uses the answer and finishes
The resumed model sees its original question, the saved needs-human result, and your answer as a new user turn. The journal is continued without rewriting the earlier result.
This continuation path remains the right answer for ordinary questions and approval gates. A current Kin treats any pending pre-DR-0218 sandbox-access record differently: it cancels the obsolete request, records the settlement, and tells you to rerun the intended operation. Neither headless nor interactive resume converts that old capability into workspace trust or retries the old call automatically.
Mechanics:
- The resumed run keeps the same
session_id(continuation, not a fork) — the sidecar’ssession_idfield is the continuation identity, so a scheduler can stamp the resumed run row against the original. --continueresolves to the newest session for the workdir (or exits 1 if there isn’t one);--resume <id>takes an explicit id. A bad id exits 1 with a clean[kin] could not resume session …: …message (NOT a traceback).- The same process-lifetime lease as interactive Kin applies. A scheduler or shell continuation exits 1 while another live Kin owns the conversation; it never becomes a second journal writer.
- The resumed run can halt again (exit 2) if the model re-asks or hits another gate — the needs-human envelope is preserved across resume.
--no-save is ignored on resume; continuation persists to the existing journal.
Prompt input
- Positional:
kin -p "do the thing". A valid user-invocable prompt skill gesture such askin -p "/ship"is resolved from the current workspace and rendered before the session starts. - Piped stdin:
echo "context" | kin -p(10 MB cap).kin -p -forces stdin. - Both: piped stdin is prepended as literal context, the positional is the
instruction —
git diff | kin -p "/review-diff".
Only the positional value uses slash dispatch; stdin is always literal. A
slash-shaped positional name that is unknown, invalid, reserved by a built-in,
or not user-invocable exits 1 before a backend or session is created.
Legacy executable workflow bundles are invalid; the converted /critique,
/security-review, /revise, /research, and /deep-research skills work
through ordinary headless turns. Paths and other
non-gestures such as /tmp/report, /, /Review, and //note remain
ordinary prompt text.
Output
- stdout — the final assistant text, nothing else.
- stderr —
[kin]-prefixed diagnostics: errors, the needs-human list, the resume hint, the report path. --output <file>— a markdown run report: status, session id, model, mode, workdir, duration, the prompt, needs-human items (with the resume command), errors, and the final text. Parent directories are created, so a per-job convention like~/.kin/cron/output/<job>/<timestamp>.mdworks out of the box.<file>.json— a machine-readable sidecar written next to every--outputreport:{version, exit_code, status, done_reason, session_id, model, mode, duration_s, usage: {input, output, total, cached}, needs_human, errors}.usageis the run’s cumulative token spend (best-effort — an endpoint that omits usage counts 0, same as the token budget);cachedis the cache-served share ofinput, which the token budget excludes;needs_humanitems carry the structured question payload (question/header/options/multi_select) so a scheduler or notification wrapper does not parse markdown. A missing or corrupt sidecar degrades to report-only and never fails the run. Two additive fields describe the service-hosted form:service(true when the conversation lives in the session service —session_idis then also the conversation id to attach to, and eachneeds_humanitem carries its liveblockid) andturn_sent(false when the target conversation was already waiting on a human and the prompt was never sent); a waiting service-hosted run reportsdone_reason: "waiting".
Content-free status
Use --status-file FILE when a scheduler must classify the outcome without
keeping conversation content. It atomically replaces an owner-private JSON
file with exactly these fields:
{"version": 1, "exit_code": 2, "reason": "workspace_trust"}
The fixed reasons are ok (0), error (1), usage (64), and, for exit 2,
workspace_trust or human_gate. The latter includes approvals, questions,
plans, and proposals; it carries no details of what was asked. There is no
prompt, reply, tool argument, path, session id, or free-form diagnostic in this
file. It works with --no-save and with -p --attach, and does not enable
session saving.
The parent directory must exist. A write failure exits 1. Choose a fresh
per-invocation path and check the process exit: argument-parsing failures,
help/version, and process termination may leave no status file. Never treat an
old file as the current outcome.
Stdout and stderr are not content-free. A privacy-sensitive caller must
still discard them and avoid --output: both the report and its JSON sidecar
contain conversation material, including the prompt and final text.
Fleet safety — the per-run token budget
Use a per-run token budget to bound unattended work. The round cap is unlimited by default; the loop guard catches identical repetition but cannot bound a run that keeps making different calls.
kin -p "Audit the TODOs in src/ and write a report" --token-budget 150000
- What counts: non-cached prompt + completion tokens, summed across every reported model call in the run — main and subagent rounds, failed and cancelled attempts, isolated vision calls, and compaction summaries included (a fan-out or maintenance call cannot escape the shared leash). Counting prompt tokens (not completion-only) is deliberate: a 40-round loop over a big history is mostly prompt compute — but tokens the endpoint served from its prefix cache are excluded, because charging every cached re-read at full price would make spend grow quadratically with rounds and cut long runs whose real marginal cost is small. An endpoint that omits usage still contributes zero, best-effort; one that omits cached counts charges the full prompt.
- When it stops: cleanly, at the next model-round boundary — never mid-tool-execution. The round that crosses the line finishes its tool calls (history stays valid, the session resumes normally in the TUI). At ~85% spent, one warning naming the remaining tokens enters the conversation so the model can converge; once the budget is crossed, the root agent gets exactly one final wrap-up round with tools withheld to report what it completed, verified, and left undone — then the run ends. Children receive no grace round once the shared run budget is exhausted, so fan-out cannot multiply the allowance. Under budget pressure Kin also reclaims stale oversized tool output automatically (a free prune, no extra model call).
- How it ends: done reason
token_budget→ exit1, with[kin] the run was cut short: token_budgeton stderr and the reason in the--outputreport — the wrap-up answer lands in the report’s Result section, but a budget-cut run is still reported as cut. In the TUI the turn ends with a visible system note instead. - Where it’s set:
--token-budget N(headless flag) >KIN_TOKEN_BUDGETenv > thetoken_budgetsettings key.0/ unset = off. A scheduler passes a per-job budget via env — the subprocess picks it up like any otherKIN_*var. - Not model-writable, on purpose: the budget exists to stop a runaway model, so the model can never raise its own.
Reported usage from a failed attempt is charged before a retry or recovery call. If that usage exhausts the shared budget, the failed-request path stops without another request or wrap-up. Waiting for route capacity does not itself spend tokens.
A round cap is available as a secondary lever for operators who want a hard ceiling independent of tokens: --max-turns N / KIN_MAX_TURNS / the max_turns settings key caps the model↔tool round-trips in one turn (done reason turn_cap). 0 = unlimited (the default). For hours-long large-model runs you’ll usually leave it off and lean on the token budget + the doom-loop guard.
Before the first model request, countable backends also reserve the configured
response budget inside the context window. Kin may summarize only history that
was complete before the new prompt; the submitted prompt and same-turn
reminders stay verbatim. If those still cannot fit, no model call is made and
the run ends with done_reason: context_limit, exit 1, and a measured
admission error on stderr/report. This keeps an oversized scheduled input from
being mistaken for successful work.
The budget is best-effort by construction on endpoints that omit usage from the stream (vLLM and both first-party wires report it). For queue-priority on a shared vLLM (--scheduling-policy priority), see the priority passthrough.
Sessions journal like any other run
A headless run writes the normal session journal (unless --no-save), so you
can pick it up interactively afterwards — kin --resume <session-id> replays
it in the TUI. Ordinary blocked approvals can be re-driven by asking again;
obsolete sandbox-access records settle cancelled and never reopen. All the
usual configuration applies: --provider,
--model, --base-url, --preset, sampling flags, KIN_* env vars, and both
settings.toml layers resolve exactly as for a TUI
launch. See the CLI
reference for the full flag list, including the
-p-only flags (--output, --status-file, --token-budget, --max-turns).
An explicit --preset NAME that is unknown or has an invalid first matching
custom provider row fails with exit 1 before inference; it never falls back
to the daytime provider. The same refusal applies to the winning KIN_PRESET
or provider_preset selection. A custom id is not proof of its endpoint:
review the row’s URL and any explicit URL override. See
preset selection.
Saved user records keep submitted text beside the expanded model
content, including @ spelling.
--no-save writes neither.
Cron example
# Nightly repo digest at 06:30; alert on anything non-zero.
30 6 * * * cd ~/proj && kin -p "Summarize yesterday's git activity" \
--output ~/.kin/cron/output/digest/$(date +\%Y-\%m-\%d).md \
|| notify-send "kin digest needs attention ($?)"