Kin / Maintainers
Extending kin — tools, kinds, and profiles
How to add a new tool to the kin harness. This page is the user-facing mirror of the canonical walkthrough in src/kin/harness/AGENTS.md — the source-tree version stays the single source of truth; this page exists so a…
Read as MarkdownHow to add a new tool to the kin harness. This page is the user-facing
mirror of the canonical walkthrough in
src/kin/harness/AGENTS.md
— the source-tree version stays the single source of truth; this page
exists so a contributor landing on docs/ doesn’t have to know to
navigate into src/kin/harness/AGENTS.md first.
Adding a tool
Tools live in src/kin/harness/tools/ (kin.harness.tools). The
Tool base class + ToolContext are in tools/registry.py.
To add a tool:
-
Subclass
Tooland set the class attrs:name— what the model calls ("web_fetch","shell", etc.).description— short, what the tool does. The model reads this; keep it tight.parameters— JSON Schema for the args. Strict-decode opt-in viaKIN_STRICT_TOOLS(see Models & providers).kind— the static risk class the gate reads (READ/EDIT/SHELL/META/MCP/NETWORK/RIG/OUTPOST/PUBLISH; see Permissions). This drives the mode → permission decision. A NEW kind must land in both mode-policy dicts + the planning-freeze tuple in one change. An unmapped kind fails closed toASK.
A bounded multi-operation tool may override
permission_kind(args)to resolve the concrete call’s risk. It must fail closed for missing/unknown operations. Four further optional call-level seams exist:approval_scope(args, workdir)— reusable signature + human label;approval_reusable(args)— false removes the Always tier;requires_serial(args)— preserve order for a call that may open a modal or race a sibling effect;snapshots_before_run(args)— request the edit snapshot safety net when the risk kind alone does not imply it.
GitToolis the reference implementation. These methods affect generic loop behavior; do not add a one-off dispatch path for a new tool. -
Implement
async run(args, ctx) → str(or alist[ContentBlock]for multimodal results — seetools/_media.py; the loop normalizes both viacoerce_content). Use thectxhelpers:ctx.emit(event)— post anEventto the UI / journal.ctx.progress(text)— live-tail text in the UI (for streaming).ctx.parent_tool_call_id— MUST be threaded onto anytool_streamevent you emit, or the UI can’t group the stream under the right tool call.
-
Untrusted output (web / file external content) must go through
frame_untrustedintools/_util.pybefore entering history. SeeREFERENCE.md§ “Untrusted content”. -
Numeric args should use
coerce_int(intools/_util.py) — don’t bare-int()model args; junk → default, clamps. Boolean args usecoerce_bool— never raw truthiness: a hallucinated"false"string is Python-truthy but a falsy token under the contract (seeREFERENCE.mdcritical invariant — the contract is load-bearing anywhere permission scope keys off a boolean). -
Register the tool in
tools/__init__.py::default_registry(). Most tools register unconditionally; the default-onsearch_workspacetool keeps a conditionalsearch_enabledkill-switch path. Ordinarygit/githuband Beam’s internal Git trio are deliberately disjoint, and child registries remove publication tools. Theoutpost/outpost-sendpair follows the same default-off-by-absence pattern, registering only once bothoutpost_urlandoutpost_tokenresolve. -
Add coverage under
tests/test_harness/(andtests/test_web.pyif it’s a web tool). The harness suite is the commit gate — a tool without a verify assertion is a silent regression waiting to happen.
Process / subprocess tools (currently just shell.py) need
start_new_session=True for process groups, and cleanup in finally.
See REFERENCE.md § “Process & subprocess”.
Adding a permission kind
The mode → permission gate lives in src/kin/harness/permissions.py
src/kin/harness/modes.py. There are nine built-in kinds:READ,EDIT,SHELL,META,MCP,NETWORK,RIG,OUTPOST,PUBLISH. To add another:
- Add the constant to
permissions.py(e.g.EGRESS = "egress"). - Map it in BOTH mode-policy dicts —
AUTO.policyandSTRICT.policyinmodes.py. The default is fail-closed (policy.get(kind, perm.ASK)inMode.decide), but an omission still changes the intended posture and fails the structural gate. - Add it to
loop._planning_freezesif the kind should be denied while planning (NETWORK,MCP,RIG, andPUBLISHare). All three lists must change together so policy, UX, and planning semantics cannot drift. - Add a kind-row entry to
docs/guide/modes-and-permissions.mdwith a one-line summary of the new risk class + mode behavior. - Add a verify assertion under
tests/test_harness/that asserts the new kind is a key in BOTHAUTO.policyandSTRICT.policy, and in_planning_freezesif applicable. The structural test is the load-bearing backstop.
Adding a subagent profile
Subagent profiles (the .md files behind task <profile> and the
/critique / /security-review / /deep-research bundled
workflows) live in src/kin/harness/defaults/agents/ (bundled) +
.kin/agents/ + .claude/agents/ (user + project, project wins).
-
Create
<name>.mdwith frontmatter:--- name: <name> description: <one-line, model-readable> max-turns: 0 # optional — 0/omitted = unlimited; positive values ≤1000 tools: # optional — omit entirely for "all tools available" - read_file - grep - shell --- <system-prompt body — same shape as the harness `compose_system`>Bundled profiles omit
max-turns: registered children are supervised by the MCA and run until completion unless it pauses, interrupts, restarts, or kills them. Use a positive value only as an intentional operator leash; the root-shared token budget and doom-loop guard are the normal runaway boundaries.tools:is a YAML list of exact tool names (not aread|write|editshorthand) — see the names registered intools/__init__.py::default_registry(). There’s nomodel:frontmatter key: a profile always inherits the parent’s model unless the dispatcher passes a per-callmodelargument totask(...). -
For a profile that ships with kin (an exemplar), drop the file in
src/kin/harness/defaults/agents/. For a user-only profile, drop it in.kin/agents/<name>.mdor.claude/agents/<name>.md. -
For a critic-style profile (read-only, rubric-based), follow the
critic-code.mdtemplate — seesrc/kin/harness/defaults/agents/critic-code.mdfor the rubric + severity-sorted output contract. -
If the profile is dispatched by a bundled command (like
/critique), pair it with adefaults/commands/<name>.mdthat routes the body askind: workflowand dispatches the agent subagent via theagent()primitive. -
Add coverage —
tests/test_harness/test_skills_agents.py/test_subagents.pyfor profile resolution + tool-allow-list enforcement;tests/test_workflow.pyfor the bundled-command wiring.
See src/kin/harness/AGENTS.md agents.py row for the discovery
mechanics (5-root walk + project-shadows-bundled).
Where to read next
src/kin/harness/AGENTS.md— the canonical per-file harness guide (the source of truth for everything on this page).docs/concepts/permissions.md— the conceptual frame for the mode → permission gate.docs/guide/tools.md— the tool catalog (what each existing tool does, the kind each carries).REFERENCE.md— every gotcha, footgun, security invariant, and postmortem. Read before touching the permission gate, the planning freeze, or any tool that touches the filesystem / network.