AGENTS.md · diff
git:20261029.8743913 to git:20261101.afe68ee
88 added, 105 removed. Audit A to A.
# AGENTS.md
- Project context for coding agents working in this repository. This is the
- **contract sheet**: each bullet states what must not regress, in present tense,
- with a pointer to the full mechanics and rationale in
- [docs/invariants.md](docs/invariants.md) (§ numbers below). Keep this file under
- 32 KB. `CLAUDE.md` imports it with `@AGENTS.md`, so there is one source
- and nothing to mirror. Release history lives in
- [CHANGELOG.md](./CHANGELOG.md) and [ROADMAP-TO-GA.md § Post-GA audit log](./ROADMAP-TO-GA.md#post-ga-audit-log), never here.
+ Project context for coding agents. This is the **contract sheet**: standing rules in present tense, with full mechanics and rationale in [docs/invariants.md](docs/invariants.md) and the linked owning docs. Keep this file under 24 KB. `CLAUDE.md` imports it with `@AGENTS.md`, so there is one source and nothing to mirror. Release history lives in [CHANGELOG.md](./CHANGELOG.md) and [ROADMAP-TO-GA.md](./ROADMAP-TO-GA.md), never here.
## Quick reference
- - **Version**: 2.4.4 (kimi-code, subprocess transport, **native agent-core-v2**).
- - **Native-v2 safety** (§1): every executed tool passes managed PreToolUse; the sole chain-breaking plan-file allow is NEVER armed. Preserve all four closures: same-child-home preflight rejects every normalized `default_plan_mode` spelling (before spawn and at spawn, including Stop hook; remedy the config key, not setup); deny Enter/ExitPlanMode for every label; resume requires proven native-v2 lineage and every-agent raw journal scan for plan taint; refuse tower/subagent_fork/master experimental selectors. Exact per-operation `NATIVE_V2_CERTIFIED` rows require source tag scan, plan-ON bypass control and operation smoke; legacy `KIMI_TESTED_MINORS` never gains 0.42. Require the first stream line to be the exact-version marker for v2 and refuse it on v1. Validate Bash.cwd against the trusted root before command policy. Full refusal codes, normalization rules and evidence: [docs/invariants.md §1](docs/invariants.md) and [certification provenance §2](docs/native-v2-certification-provenance.md).
- - **Hook-pin durability** (§1): the canonical hook command never embeds a version-stamped Node token; `preferStableNodePath` prefers `process.argv0` only when absolute and `realpath`-identical; `verifyHookInstalled` runs `access(nodeBin, X_OK)` fail-closed on the token parsed back from the built command (`refusal_kind:"node-bin-not-executable"` is the one refusal whose remedy is NOT /kimi:setup). Re-pin-recoverable refusals carry `retryable_after_setup`; agents re-pin via `${CLAUDE_PLUGIN_ROOT}/scripts/companion.sh setup` and retry ONCE only when it is literally `true` and setup exits 0. **In-verifier auto-repin is REJECTED — do not re-propose.**
- - **Test discipline** (§1): detached-worker hook-enforcement tests MUST run the companion as a real node subprocess through a symlink with NO `KIMI_PLUGIN_CC_NODE_BIN` override and NO `KIMI_PLUGIN_CC_SKIP_HOOK_CHECK`. A real-binary smoke MUST assert its own precondition when the behaviour depends on host state; never write a "plan-file write is denied under plan mode" smoke — that path is never armed, so green would be the vacuous-precondition failure.
- - **Upstream compat** (§2): native-v2 certification is EXACT and per-operation in `runtime/kimi-engine.ts`; all eight operations support 0.42.0, 0.43.0, 0.43.1, 2.0.0–2.0.2, 2.1.0–2.1.1, 2.2.0, 2.3.0–2.3.3 and 2.4.0. Legacy-v1 ends at 0.41.x for an explicitly pinned binary. The sole plan-service allow still bypasses hooks through 2.4.0 (MoonshotAI/kimi-code#3431); retain every no-plan closure. Exact 2.4.0 removes EnterPlanMode and cancels default plan on fresh print; resumed plan journals remain refused. Each audit re-verifies ALL surfaces in [docs/upstream-compat-audit.md](docs/upstream-compat-audit.md) and [docs/invariants.md §2](docs/invariants.md), including engine/session provenance, hook ordering/schema/aggregation, Write/Edit path and Bash command+cwd, bootstrap/config/trust, plan/restore routes, print argv, goal interception/exits, child hooks, finite budgets and exported swarm concurrency. Monitor reports never certify. Exact 2.3.2/2.3.3/2.4.0 swarm activation/deactivation uses authenticated loopback web lifecycles around the single `kimi -p -r` runner; preserve model/auto/swarm profile with plan off, marker/version/session identity, memory-only token, journal proof and `KIMI_CODE_SESSION_IDLE_RECLAIM_MS=0` to disable ambient idle reclamation. `runSwarm` owns deactivation, with remaining budget normally and one separate 2s cancellable recovery attempt; cancellation starts no fresh server. Process ownership/quiescence uncertainty prevents success and preserves diagnostics, raw output and write worktree. Root loss requires prior identity; PGID changes are not exit. Smoke must correlate distinct child journals, assigned Write/Edit paths and toolCallIds with results—not aggregate denials or coordinator-only work. Real rescue and deterministic out-of-root Bash.cwd controls are required per-operation evidence; pursue/swarm-write lineage is not a rescue substitute. Evidence: [docs/native-v2-status.md](docs/native-v2-status.md).
- - **Toolchain**: Node >= 22.5, TypeScript, **bun** for development; npm CLI for registry operations, including package installation
- - **Workflow**: edit `runtime/**/*.ts` → `bun run check` (build + typecheck + test + drift gate)
- - **Installed-state verification**: For questions about what a host is actually running, resolve that host's active version-stamped install cache and run its bundled `scripts/companion.sh setup --check`. The checkout, package metadata, and another host's cache are not proof of active runtime state.
- - **Credential-bearing config** (§3): `~/.kimi-code/config.toml` can hold a BYO-provider `apiKey`, and `~/.kimi-code/credentials/` is the real OAuth-token store (always has been — not a per-version migration). For routine checks never read, print, copy or retain either; use the sanitized `setup --check` or `setup --models [--json]` projection. Model listing internally parses config but emits only routing fields, never secrets or raw parser errors; it never reads the OAuth store or makes provider calls. An explicitly authorized diagnosis of config.toml is restricted to the managed hook block with credentials redacted; `credentials/` is never read or copied.
+ - **Version**: 2.5.1 (kimi-code, subprocess transport, **native agent-core-v2**).
+ - **Native-v2 safety** (§1): every executed tool passes managed PreToolUse; the chain-breaking plan-file allow is NEVER armed. Preserve all four closures: same-child-home preflight rejects every normalized `default_plan_mode` spelling; Enter/ExitPlanMode is denied for every label; resume requires proven native-v2 lineage and every-agent raw journal plan-taint scans; unsafe experimental selectors are refused. Require the exact-version first-line marker, validate Bash.cwd against the trusted root, and require source, plan-ON control, and per-operation smoke evidence for every certification row. Legacy-v1 never gains 0.42. Full rules and evidence: [docs/invariants.md §1](docs/invariants.md) and [certification provenance](docs/native-v2-certification-provenance.md).
+ - **Hook-pin durability** (§1): canonical hook commands avoid version-stamped Node tokens; `process.argv0` is eligible only when absolute and realpath-identical. `verifyHookInstalled` fail-closed checks the parsed-back Node token with `access(X_OK)`. Re-pin recovery happens only when `retryable_after_setup` is literally true and setup exits 0, then retry ONCE. `node-bin-not-executable` has a different remedy; in-verifier auto-repin is rejected.
+ - **Test discipline** (§1): detached-worker enforcement tests use a real node subprocess through a symlink, without `KIMI_PLUGIN_CC_NODE_BIN` or `KIMI_PLUGIN_CC_SKIP_HOOK_CHECK`. Host-dependent smoke asserts its precondition; never test the never-armed plan-file allow as though it were active.
+ - **Upstream compat** (§2): certification is EXACT and per-operation in `runtime/kimi-engine.ts`; legacy-v1 ends at 0.41.x for an explicitly pinned binary. The upstream plan-service allow still bypasses hooks through 2.4.1, so every no-plan closure remains mandatory. Each audit re-verifies all surfaces in [docs/upstream-compat-audit.md](docs/upstream-compat-audit.md) and [docs/invariants.md §2](docs/invariants.md); monitor reports never certify. Swarm activation/deactivation, identity/quiescence, child-journal correlation, and real rescue/Bash.cwd controls follow [docs/native-v2-status.md](docs/native-v2-status.md).
+ - **Toolchain**: Node >=22.5 (>=24.9 for DSH), TypeScript, **bun** for development; npm CLI for registry operations and package installation.
+ - **Workflow**: edit `runtime/**/*.ts` → `bun run check` (build + contracts + surfaces + typecheck + tests + drift gate).
+ - **Installed-state verification**: resolve the host's active version-stamped install cache and run its bundled `scripts/companion.sh setup --check`. A checkout, package metadata, or another host's cache is not proof of active runtime state.
+ - **Credential-bearing config** (§3): never routinely read, print, copy, or retain `~/.kimi-code/config.toml` or `~/.kimi-code/credentials/`; use sanitized `setup --check` or `setup --models [--json]`. Explicit config diagnosis is limited to the managed hook block with credentials redacted; credentials are never read or copied.
## Directory layout
```
- .claude-plugin/ Claude Code plugin manifest (plugin.json, marketplace.json) — id stays as kimi/kimi-marketplace (no rename for v1); v0.4 users opt-in to v1 by updating the same install
- .claude/ Gitignored local audit workspace. Upstream clones/reports live here, including daily monitor reports under kimi-code-research/daily-monitor/
- .agents/ Codex repo marketplace sidecar (plugins/marketplace.json) — generated; source.path points at plugins/kimi-codex
- commands/ Slash command markdown — thin wrappers over companion.sh (Claude Code surface)
- agents/ Claude Code subagent definitions (kimi-rescue, kimi-review, kimi-challenge, kimi-ask, kimi-swarm, kimi-pursue, kimi-swarm-write)
- hooks/ Stop hook for the review gate (kimi-code-side PreToolUse hook lives at runtime/hooks/)
- scripts/ Shell entry points (companion.sh, review-gate-hook.sh) + dev-only surface generator (surface-registry.ts, generate-surfaces.ts)
- .mcp.json Claude Code MCP stdio launch config — hash-pinned
- plugins/kimi-codex/ Self-contained GENERATED Codex root; install copies forbid ../ escapes; never hand-edit:
+ .claude-plugin/ Claude Code plugin manifest/marketplace — id stays kimi/kimi-marketplace
+ .claude/ Gitignored local audit workspace and upstream clones/reports
+ .agents/ Generated Codex marketplace sidecar; source.path points at plugins/kimi-codex
+ commands/ Claude Code slash-command wrappers over companion.sh
+ agents/ Claude Code subagent definitions
+ hooks/ Stop hook for the review gate; Kimi-side PreToolUse lives in runtime/hooks/
+ scripts/ Shell entry points + dev-only surface generator/registry
+ .mcp.json Hash-pinned Claude MCP stdio launch config
+ plugins/kimi-codex/ GENERATED self-contained Codex root; never hand-edit:
├── .codex-plugin/plugin.json Codex manifest (skills + MCP)
├── .mcp.json Generated MCP stdio launch config
- ├── skills/ 12 shell-backed skills, kept out of repo root so Claude Code does not discover them
+ ├── skills/ 12 shell-backed skills, outside Claude discovery
├── scripts/ Byte-mirrored shell entry points (0755)
└── dist/ Byte-mirror of compiled runtime
+ plugins/kimi-dsh/ GENERATED self-contained DeepSeek Harness bundle; never hand-edit:
+ ├── package.json Nested DSH bundle manifest with bounded engines
+ ├── cordis.patch.yml Native seven-operation provider composition
+ ├── scripts/ Byte-mirrored shell entry points (0755)
+ └── dist/ Byte-mirror of compiled runtime
runtime/ TypeScript source — the real runtime
├── background-spawn.ts Shared detached-worker spawn helper (rescue + ask)
├── cli-client.ts Subprocess wrapper around `kimi -p --output-format stream-json`
- ├── swarm-activation.ts Exact-version loopback activation/deactivation for swarm sessions
- ├── stream-json.ts Pure parser for kimi-code's NDJSON output
- ├── cli-cancellation.ts AbortController-based cancellation for long-running commands
- ├── rescue-approval.ts Workspace-bound allowlist (called by the PreToolUse hook)
+ ├── swarm-activation.ts Exact-version loopback swarm activation/deactivation
+ ├── stream-json.ts Pure NDJSON parser
+ ├── cli-cancellation.ts AbortController-based cancellation
+ ├── rescue-approval.ts Workspace-bound allowlist used by the PreToolUse hook
├── commands/ One file per companion subcommand
- ├── mcp/ Typed tools, stdio/protocol, supervised launch, and task ownership
- ├── hooks/ PreToolUse approval hook (entry script + policy + install verifier)
- └── schemas/ Structured output contract for review_gate (review/challenge dropped theirs in v0.2.3)
+ ├── dsh/ Native DSH providers, human commands, and subprocess lifecycle
+ ├── mcp/ Typed tools, stdio/protocol, supervised launch, task ownership
+ ├── hooks/ PreToolUse approval hook, policy, and install verifier
+ └── schemas/ Structured review_gate output contract
dist/ Compiled JS — committed for zero-build install
tests/ bun test suite
```
## Commands
- - `bun run check` — rebuild `dist/`, verify generated surfaces (`check:surfaces`: Claude hash gate + Codex sidecars + the `plugins/kimi-codex` runtime mirror/orphan checks), check the AGENTS.md size limit, typecheck, run full test suite, then drift gate (`git diff --exit-code -- dist plugins/kimi-codex`). If `dist/` or the bundled Codex copy has unstaged changes, check fails — stage them and retry.
- - `bun test <path>` — run a single test file
- - `bun run build` — compile `runtime/**/*.ts` → `dist/**/*.js`
+ - `bun run check` — build, contract-size gate, generated-surface checks, typecheck, full tests, then drift gate for `dist`, `plugins/kimi-codex`, and `plugins/kimi-dsh`. Stage generated changes and retry when the drift gate requires it.
+ - `bun test <path>` — run one test file.
+ - `bun run build` — compile `runtime/**/*.ts` → `dist/**/*.js`.
- The companion runs via `scripts/companion.sh <subcommand>`, which resolves `node` and runs `dist/companion.js`. Subcommands: `setup`, `review`, `task` (`task rescue`, `task challenge`, `task pursue`, `task swarm`), `ask`, `status`, `result`, `cancel`, `replay`, `repair-sessions`, `recover-swarm`, `mcp`.
+ The companion runs via `scripts/companion.sh <subcommand>` → `dist/companion.js`. Subcommands: `setup`, `review`, `task` (`rescue`, `challenge`, `pursue`, `swarm`), `ask`, `status`, `result`, `cancel`, `replay`, `repair-sessions`, `recover-swarm`, `mcp`.
## Architecture
- **Thin plugin, rich runtime** — mirrors [codex-plugin-cc](https://github.com/openai/codex-plugin-cc):
+ **Thin hosts, rich runtime** — mirrors [codex-plugin-cc](https://github.com/openai/codex-plugin-cc):
- - Plugin layer (commands/, agents/, hooks/, .mcp.json) handles routing only
- - Runtime layer (runtime/, scripts/) owns subprocess lifecycle, SQLite job store, hook-side approval policy, and rendering
- - Flow: `slash command → companion.sh → companion.js → kimi -p --output-format stream-json → job store → artifact`
- - MCP: `tool call → companion.sh mcp → supervised mcp-child → the same execution plan, job store, and artifact`
+ - Host surfaces route only; runtime owns subprocesses, SQLite jobs, approval policy, and rendering.
+ - Commands: `slash/skill → companion.sh → companion.js → kimi -p --output-format stream-json → job store → artifact`.
+ - MCP: `tool call → companion.sh mcp → supervised mcp-child → the same execution plan, job store, and artifact`.
+ - DSH: `native provider → managed subprocess → dist/companion.js → the same execution plan, job store, and artifact`; no ACP transport.
- **Key invariants** (each in full in docs/invariants.md §4):
+ **Key invariants** (full statements in docs/invariants.md §4 unless linked otherwise):
- - **Subprocess-first.** One `kimi -p` prompt runner per job; exact 2.3.2/2.3.3/2.4.0 swarm jobs add the bounded web activation lifecycle in §2, never a second prompt transport. Session-id capture is dual-source (stream-json `session.resume_hint` on 0.2.0+; anchored stderr `kimi -r <uuid>` on 0.1.x), first-announce-wins, pinned, idempotent; meta is filtered from `records[]`; the id round-trips verbatim via `kimi -r`.
- - **MCP transport and task ownership** (§4): `companion.sh mcp` exposes seven model tools plus job status/result/cancel over stdio. Claude task handles require per-request tasks capability; Codex legacy gets ordinary results, not native tasks. `mcp-child` fixes the task/job UUID and attaches when cancellation is ready (60s cap), runtime-foreground (`background:false`); `mcp_tasks` holds launch metadata; `jobs` owns outcomes. Host workspace is bounded; bind repo/`operation_kind`, pin the data root, preserve all safety gates, and keep stdout JSON-only. Disconnects never cancel; restarts reattach by ID. MCP-owned jobs revalidate both identities at attach/signals; unconfirmed launches stay working. Failed jobs return completed tasks with `isError:true` and artifact; cancelled maps to `cancelled`; supervisor/protocol failures distinct.
- - **Prompt and metadata separation** (§4): raw task text owns summaries/titles; full prefixed prompts own spawn/digest/recovery. Working-tree review includes untracked files; `--base` never treats dirty files as committed snapshots. Pursue uses `/goal --` and enforces the 4000-character objective budget including hints; `SetGoalBudget` stays denied.
- - **Every model-spawning command is enforced by the PreToolUse hook** in `~/.kimi-code/config.toml` (`kimi -p`'s auto-approve cannot be overridden via argv). On native v2 the hook is load-bearing under the no-plan construction; the preflight refuses `default_plan_mode`, tainted/unknown resume and unsafe experimental selectors before Kimi CLI spawn. Ask/review/challenge/rescue/pursue/swarm refuse before spawning when hook verification fails; the review gate skips visibly. `KIMI_PLUGIN_CC_SKIP_HOOK_CHECK=1` is a tests/diagnostics bypass for hook-verification refusals only — never a repair path, and it does not bypass the v2 preflight.
- - **Hook-escape monitor** (§1): a policy-denied streamed call whose result is not the managed denial (allowing upstream's repeat-breaker decoration) records `mismatch`; no result at settlement records `unresolved`. Findings are **detection evidence, never failure**: logged, persisted in `hook_escapes`, exposed by status/result/stderr and timeout details, and only annotated by the fail-open review gate. The stream has no outcome field, so upstream pre-hook rejection, another policy blocking first, or hook drift can look identical; never fail a job without positive execution evidence. Coverage is streamed read-only calls (swarm children are in-process) and pure plan/goal denials.
- - **Hook verification is exact and parser-based** (§4): rebuild canonical command bytes from install-paths; parse COMPLETE TOML with vendored smol-toml@1.9.1, validate EVERY hook against the reviewed version schema, then managed-block grammar. Unknown top-level tables are tolerated; unsupported nonzero schemas fail closed. Comment markers are not durable. ONLY absent-state fallback accepts a real-parser-proven clean enforcing table: event=PreToolUse, byte-exact command, no matcher spelling, no keys outside event/command/timeout. Never substitute substring or line scanning. `findBareApprovalHookTables` remains conservative, host-scoped prune-only. Re-verify parser/matcher/bytes/absent-state/prune boundaries on each audit; exact schema/version mechanics live in [docs/invariants.md §4](docs/invariants.md).
- - **Serialized config mutation** (§4): hold the private adjacent config lock for the entire read-modify-write; publish atomically. Lock reads are no-follow/nonblocking/regular-file-only and capped; acquisition bounded; live owners never stolen; stale recovery ABA-safe; inode/token ownership rechecked before writes. New home 0700, config/lock 0600. Normalize inline hooks with comment-loss warning. Setup/check fails on whole-file, hook-schema, block, path or shell-probe failure. Full protocol: [docs/invariants.md §4](docs/invariants.md).
- - **Host-scoped managed blocks.** Claude Code and Codex install to different version-stamped paths but share one config.toml, so the managed block is keyed by host id (`# === BEGIN kimi-plugin-cc-managed:<host-id> (vX) ===`; `resolveHostId` derives it version-independently from the hook path, `KIMI_PLUGIN_CC_HOST_ID` overrides). **Never collapse back to one shared block** — the hosts overwrote each other. `parseManagedBlock`/`evaluateInstalled` are per-host and exact; `runInstall` upserts only this host's block and prunes only orphaned marker-less blocks whose command is strictly ours (`isOurApprovalHookCommand`; hand-rolled hooks are never touched); `runUninstall` is host-scoped (`--uninstall --all` sweeps every host). Coexistence is safe because kimi-code fires every `[[hooks]]` any-block-wins. Re-verify: scoped install/uninstall preserves other hosts' live blocks; `--uninstall --all` is the explicit exception.
- - **Rescue defines the direct-workspace write allowlist** used by rescue and pursue (`runtime/rescue-approval.ts`, via `evaluateRescueHookRequest`): shell-quote parser, mutating-flag detector (`--fix`/`--write`/`--apply`/`--in-place`/`-i`/`-w` plus the output-to-file class `--output*`/`--output-dir*`, exact and `=` forms; per-tool `-o`), symlink reject, realpath check, a global `hasExecDelegatingFlag` (`--open-files-in-pager` — a CRITICAL RCE class — `-vettool`/`-toolexec`/`-exec`; git-local `-O`), and report-writer rejects (`mypy --junit-xml`/`--*-report`, `pytest --junitxml`/`--result-log`/`--report-log`). Test runners still execute repo code by design; internal Git context commands also trust repository Git configuration and can execute helpers outside tool hooks, including read-only swarm — explicit trust boundaries in `docs/safety.md` and `docs/invariants.md`.
- - **Rescue cannot mutate git state.** The calling host owns branch and commit decisions.
- - **`/kimi:pursue` (experimental)** (§4): runtime-foreground-only autonomous `/goal --`, with rescue workspace/write policy and hook enforcement on every turn; no git mutation, no resume, default mandatory finite budget 45m. Preserve the legacy 0.8–0.11 goal env flag. Only trusted operation `pursue` plus a trusted root permits exact GetGoal and terminal UpdateGoal complete/blocked; creation/reactivation/budget mutation remain denied and older coexisting hooks can veto. Persist rescue lineage, export the immutable operation instead of ambient values, and treat exits 0/3/6 as terminal. Turns are a hint; wall-clock budget is the bound. Full mechanics: [docs/invariants.md §4](docs/invariants.md).
- - **`/kimi:swarm` = read-only parallel fan-out** (`AgentSwarm`, 0.12.0+). Label `swarm` allows the read-only set PLUS the exact tool name `AgentSwarm` (the singular `Agent` is denied); every subagent inherits the label and fires the same hook through its own eager external-hooks service → no additional direct tool-write permission; the internal Git trust boundary still applies. `command_type:"review"` lineage. Refuses without the hook. Bounded by mandatory `--budget` (default 30m); `--cap N` is a SOFT total-count hint, `--max-concurrency N` a HARD peak cap on 0.18.0+ (`KIMI_CODE_AGENT_SWARM_MAX_CONCURRENCY`, default 4, always exported). **Never re-merge the two flags.** The model-invocable `kimi-swarm` agent wraps it. `docs/safety.md` § "Read-only swarm".
- - **`/kimi:swarm --write` = patch-only parallel writes** (§4): create an ephemeral detached worktree off born HEAD under the plugin data root; spawn there while `job.cwd` remains the user workspace. The forge-proof `KIMI_PLUGIN_CC_WORKSPACE_ROOT` confines every coordinator/child write through the rescue policy; missing trusted root refuses. Children are coder agents with disjoint targets, no git and no nested swarm; re-verify eager child hooks and shared-workspace permission stacks each audit. Requires CLI ≥0.18, verified hook, finite budget, dirty-tree warning and default HARD concurrency 1. Capture an untruncated binary patch on every ordinary terminal path BEFORE removal; capture/cleanup failure retains the tree. Never apply or commit. Abandoned worktrees are never deleted by age. Runtime-foreground-only does not prohibit the caller detaching its shell. Cancel detached shell runs with no-id `companion.sh cancel` (latest running repo job; disambiguate concurrent jobs); MCP uses its durable job ID. Prefer cancellation over Esc/TaskStop: harness hard-kill grace can end before teardown/patch capture. Pursue writes the REAL workspace; swarm-write returns a discardable patch. The model-invocable agent requires BOTH disjoint write targets and explicit fan-out intent. Full mechanics: [docs/safety.md](docs/safety.md).
- - **`goal.summary`** (0.8.0+, role-less) is captured on `CliClientResult.goalSummary`; `turn.step.retrying` meta is modeled and filtered like the other wrapper metadata; malformed shapes still enter diagnostics.
- - **Plugin data ownership** (§5): `KIMI_PLUGIN_CC_DATA` is the explicit absolute data-root override. Standard cache installs derive the expected host-specific directory from the loaded package, not shared root variables; conflicting shared data variables refuse before store access (`PLUGIN_DATA_CONFLICT`). Custom launches retain unambiguous legacy paths. No automatic data migration; detached workers pin the selected root and the Stop hook skips visibly on resolution failures.
- - **Jobs in SQLite are the source of truth.** Terminal states are permanent; nullable `budget_ms` persists response-time budgets for ask/rescue/review/challenge (NULL = command default resolved at execution; a patience knob, never a safety-field pattern), including resume inheritance and detached-worker replay; pursue/swarm persist their effective safety ceilings. Additive `budget_source` records explicit/inherited/default provenance (old NULL rows render default for a NULL budget, otherwise unknown); inherited ask/rescue budgets are printed at launch.
- - **Session visibility and titles** (§4): settled native-v2 sync fills only missing redacted previews (≤4,000 chars) and invalidates the shared index; manual/native titles are preserved, fallback titles replaceable. Never auto-invoke the native title endpoint (it resumes an agent). `repair-sessions` defaults to preview, writes only with `--apply` after a prompt-digest match, and excludes review-gate sessions. See [docs/session-visibility.md](docs/session-visibility.md).
- - **Model selection** (§4): `--thinking <effort>` requests effort through `KIMI_MODEL_THINKING_EFFORT`; omission preserves ambient env and resumes never inherit a prior request. Non-trait providers and an `off` base may ignore it; `alwaysThinking` receives `off` unnormalized. Stream-json has no thinking deltas, so honoring is unverifiable. Omit `-m` for fresh defaults; resumes keep session models; per-run aliases do not change saved defaults. `setup --models [--json]` is a credential-safe local inventory, not a connection test. Agents require one unambiguous configured alias, never guess/fall back; provider setup stays in `/login` or `/provider`, saved-default changes only in explicit `/model`. Review gate uses `KIMI_PLUGIN_CC_REVIEW_GATE_MODEL` when set; swarm children may use `[secondary_model]`. See [docs/models.md](docs/models.md).
- - **Prose pass-through**: review/challenge/ask/rescue emit raw prose; the review gate is the only command that parses Kimi output (JSON allow/block), as a Stop hook, disabled by default, fail-open on malformed output and timeout (a hook-escape finding only annotates `systemMessage`), skipping visibly (with the reason in `systemMessage`) when enforcement is missing or invalid.
- - **LLM-caller discipline**: stderr is for humans only; anything load-bearing goes in stdout, exit codes or SQLite. Parsers hard-fail on unknown flag-shaped tokens (`INVALID_ARGS`); `--help`/`-h` before `--` prints subcommand usage from the same supported-flag constants to stdout and exits 0. `RuntimeError.details` carries structured failure context. Role-less CLI errors stay bounded diagnostics (`cliError`, failure `details.cli_error`), never prose/lineage or exit overrides; marker-first still applies. `result <jobId> --json` opts into the structured envelope, including `budget_ms`, `budget_source`, the captured session ID and `kimi_session_id_source`.
- - **Cancellation** (§4): AbortController + SIGTERM → SIGKILL (1500ms), with teardown a settlement barrier. POSIX captures and revalidates PID/PGID/start-time identities before signalling, confirms bounded post-kill quiescence, never signals a replacement and emergency-kills the owned root on teardown failure; Windows remains direct-child-only. Ask/review/challenge default to 30m, rescue 45m; `--budget` ≤24h, unaffected by backgrounding. Clean non-aborted exit-0 wins same-tick budget expiry. Only budget timeouts may retain settled partial prose and a proven session (announced, or exactly one session matching cwd/time/full prompt digest); cancellation never captures partial prose. Preserve `settled_post_abort`, session-source diagnostics and all resume lineage/plan-taint gates. Full settlement/recovery rules: [docs/invariants.md §4](docs/invariants.md).
- - **Durable swarm recovery** (§4): exact 2.3.2/2.3.3/2.4.0 records launch intent/verified identities and session ownership BEFORE enabling swarm; no token or credential is stored. `recover-swarm <job-id>` previews; `--apply` only recovers the current repo’s stopped owner, revalidates identities, refuses ambiguous/changed groups, disables the exact recorded session through the verified CLI/hook, and captures but never applies/deletes the retained worktree. Concurrent recovery is identity-locked. Terminal job outcomes never change; abandoned running jobs become failed, not successful. Normal cancellation never launches recovery. A kill between spawn and identity persistence remains an explicit manual-inspection case, not proven cleanup. No age-only orphan deletion. See [docs/swarm-recovery.md](docs/swarm-recovery.md).
- - **Spawn diagnostics** (§4): preserve structured process causes; do not send argument/resource exhaustion to reinstall advice. Windows rejects known-overlimit UTF-16 command lines before spawn with `CLI_COMMAND_LINE_TOO_LONG`, without retaining the prompt in that diagnostic. Print prompts remain argv-backed; no stdin transport or Windows large-review support is implied. PR #22 does not close issue #14.
- - **Config writes preserve mode 0o600** (`writeConfigAtomic` chmods the temp file before rename).
+ - **Subprocess-first.** One `kimi -p` runner per job; certified swarm jobs add only the bounded activation lifecycle in §2. Session-id capture is dual-source, first-announce-wins, pinned, idempotent, and meta is filtered from `records[]`; ids round-trip through `kimi -r`.
+ - **MCP transport and ownership.** Seven model tools plus job controls use per-request tasks capability; Codex legacy gets ordinary results, not native tasks. Fixed task/job UUIDs, bounded attachment, pinned data root/workspace, JSON-only stdout, identity revalidation, and reconnect-by-ID are mandatory. Disconnects never cancel; `jobs` owns outcomes and failed jobs return `isError:true` with artifacts.
+ - **DSH native providers.** The bundle exposes all seven operations with the same write boundaries as other hosts, plus `/kimi-setup`, `/kimi-status`, `/kimi-result`, `/kimi-cancel`. DSH pins host id `dsh` and an explicit data root; provider-owned env clears other-host and bypass overlays. Prompts follow `--`; profile model/thinking/budget/review-base options precede them. Only complete successful stdout reaches the parent; failures are bounded diagnostics and disposal awaits managed-range quiescence. Setup never runs implicitly; one-shot activations have no parent context, follow-up input, or per-call model overrides.
+ - **Prompt and metadata separation.** Raw task text owns summaries/titles; full prefixed prompts own spawn/digest/recovery. Working-tree review includes untracked files; `--base` never treats dirty files as committed snapshots. Pursue uses `/goal --`, enforces the 4000-character objective budget including hints, and denies `SetGoalBudget`.
+ - **Managed hook enforcement.** Every model-spawning command requires the canonical PreToolUse hook; ask/review/challenge/rescue/pursue/swarm refuse before spawn when verification fails, and the review gate skips visibly. `KIMI_PLUGIN_CC_SKIP_HOOK_CHECK=1` is a tests/diagnostics bypass only, never a repair path or a v2-preflight bypass.
+ - **Hook-escape monitor** (§1): denied streamed calls whose result is not the managed denial record `mismatch`; absent results record `unresolved`. Findings are detection evidence, never failure; expose them through logs, `hook_escapes`, status/result/stderr, and timeout details. Never fail a job without positive execution evidence.
+ - **Exact hook verification.** Rebuild canonical command bytes; parse COMPLETE TOML with vendored smol-toml@1.9.1; validate every hook and the managed grammar. Markers are not durable: only absent-state fallback accepts a real-parser-proven clean enforcing table with byte-exact command, no matcher, and no extra keys. Never substitute substring/line scanning; pruning stays conservative and host-scoped.
+ - **Serialized config mutation.** Hold the private adjacent lock across read-modify-write and publish atomically. No-follow/nonblocking/regular-file capped reads, bounded acquisition, no live-owner theft, ABA-safe stale recovery, inode/token rechecks, home 0700, config/lock 0600. Normalize inline hooks with warning; setup/check fails on any config, schema, block, path, or probe failure.
+ - **Host-scoped managed blocks.** Claude Code, Codex, and DSH share config.toml but own separate host-id blocks; DSH pins `dsh`. Install/uninstall is per-host and exact, prunes only strictly owned marker-less hooks, and never touches hand-rolled hooks. `--uninstall --all` is the sole every-host sweep. Coexistence relies on kimi-code's any-block-wins aggregation.
+ - **Rescue write policy.** Rescue and pursue share `evaluateRescueHookRequest`: shell-quote parsing, mutating/output/exec-flag detection, symlink/realpath containment, and report-writer rejects. Test runners execute repo code by design; internal Git context commands retain the repository-config/helper trust boundary documented in [docs/safety.md](docs/safety.md).
+ - **No Git mutation.** Rescue cannot mutate Git state; the calling host owns branch and commit decisions.
+ - **Pursue (experimental).** Runtime-foreground-only autonomous `/goal --` with rescue write policy and hook enforcement on every turn; no Git mutation or resume, default finite 45m wall-clock budget. Only trusted pursue/root permits exact GetGoal and terminal UpdateGoal complete/blocked; creation/reactivation/budget mutation remains denied. Preserve rescue lineage, immutable operation export, legacy goal flag, and terminal exits 0/3/6.
+ - **Read-only swarm.** Exact `AgentSwarm` is added to the read-only set; singular `Agent` is denied and every child fires the same eager hook. Mandatory finite budget (default 30m); `--cap` is a soft total hint, `--max-concurrency` a hard peak cap (default 4, always exported). Never merge the flags. Internal Git trust boundaries still apply.
+ - **Write-swarm is patch-only.** Use a born-HEAD detached worktree under the plugin data root while `job.cwd` stays the user workspace. Forge-proof trusted root confines every write; children have disjoint targets, no Git, and no nested swarm. Require verified hooks, finite budget, dirty-tree warning, and default hard concurrency 1. Capture an untruncated binary patch before any ordinary cleanup; capture/cleanup failure retains the tree. Never apply/commit or delete abandoned trees by age. Prefer explicit cancellation over hard interruption. Pursue writes the real workspace; swarm-write returns a discardable patch.
+ - **`goal.summary` and retry metadata.** Capture role-less `goal.summary` on `CliClientResult.goalSummary`; model/filter `turn.step.retrying` like other wrapper metadata, while malformed shapes enter diagnostics.
+ - **Plugin data ownership** (§5): `KIMI_PLUGIN_CC_DATA` is the explicit absolute override. Standard installs derive host-specific paths from the loaded package; conflicting shared values refuse before store access. DSH selects `$DSH_HOME/plugin-data/kimi-plugin-cc` or an absolute override and clears shared host variables. No automatic migration; detached workers pin the selected root and Stop skips visibly on resolution failure.
+ - **Jobs are authoritative.** SQLite terminal states are permanent. Nullable response budgets and `budget_source` preserve explicit/inherited/default provenance for ask/rescue/review/challenge; pursue/swarm persist effective safety ceilings. Detached workers reload budgets from SQLite; inherited ask/rescue budgets print at launch.
+ - **Session visibility and titles.** Settled native-v2 sync fills only missing redacted previews (≤4000 chars) and invalidates the shared index; preserve manual/native titles and replaceable fallback semantics. Never auto-invoke the native title endpoint. `repair-sessions` previews by default, writes only with `--apply` after a prompt-digest match, and excludes review-gate sessions. See [docs/session-visibility.md](docs/session-visibility.md).
+ - **Model selection.** `--thinking` requests effort through `KIMI_MODEL_THINKING_EFFORT`; omission preserves ambient env and resumes do not inherit. Honoring is unverifiable over stream-json. Omit `-m` for fresh defaults; resumes keep session models and per-run aliases never change saved defaults. `setup --models [--json]` is a credential-safe inventory, not a connection test; never guess aliases or fall back silently. Provider setup stays in `/login` or `/provider`, saved-default changes in explicit `/model`. See [docs/models.md](docs/models.md).
+ - **Prose pass-through.** review/challenge/ask/rescue emit raw prose. Only the review gate parses Kimi output, as a disabled-by-default Stop hook that fails open on malformed output/timeout and skips visibly without valid enforcement.
+ - **LLM-caller discipline.** stderr is for humans; load-bearing results use stdout, exit codes, or SQLite. Unknown flag-shaped tokens fail `INVALID_ARGS`; leading `--help` prints supported usage and exits 0. `RuntimeError.details` carries structured context; CLI errors stay bounded diagnostics and never override lineage/prose/exits. `result <jobId> --json` exposes the structured envelope, budget provenance, and captured session id/source.
+ - **Cancellation.** AbortController + SIGTERM → SIGKILL (1500ms), with teardown a settlement barrier. Revalidate PID/PGID/start identities, confirm bounded quiescence, never signal replacements, and emergency-kill the owned root on teardown failure; Windows remains direct-child-only. Budgets are finite ≤24h and unaffected by backgrounding. Clean non-aborted exit-0 wins same-tick expiry. Only budget timeouts may retain partial prose and a proven session; cancellation never captures partial output. Preserve `settled_post_abort`, recovery, and lineage gates.
+ - **Durable swarm recovery.** Record launch intent and verified identities before enabling swarm; never store tokens. `recover-swarm` previews by default; `--apply` only recovers the current repo's stopped owner after identity/session revalidation, and captures but never applies/deletes the worktree. Terminal outcomes never change; abandoned running jobs fail, never succeed. Normal cancellation launches no recovery, and no age-only orphan deletion exists. See [docs/swarm-recovery.md](docs/swarm-recovery.md).
+ - **Spawn diagnostics.** Preserve structured process causes and never translate argument/resource exhaustion into reinstall advice. Windows rejects known-overlimit UTF-16 argv with `CLI_COMMAND_LINE_TOO_LONG` and omits the prompt. Print prompts remain argv-backed; no stdin transport or Windows large-review support is implied.
+ - **Config mode.** `writeConfigAtomic` preserves 0o600 by chmodding the temp file before rename.
## Post-GA roadmap
- See [ROADMAP-TO-GA.md](./ROADMAP-TO-GA.md) for the pre-GA history and the living Post-GA audit log. v1.0.0 GA shipped 2026-05-26. Remaining open items: H1 enforcement (2.2.1 ships the read-only DETECTION slice; failing/stopping a run needs positive execution evidence — filed upstream as MoonshotAI/kimi-code#4241, an `outcome` field on print-mode tool records), filesystem-dependent write-label coverage, swarm subagent coverage. H5 is closed as a requested thinking-effort knob, not an enforced guarantee. H3/H4/H8/H9 closed in v1.2.x; H7 (real-binary smoke, `bun run smoke:real`) ships as a manual-dispatch CI workflow; model calls require `KIMI_MODEL_API_KEY`. Adding the secret does not enable per-push runs.
+ See [ROADMAP-TO-GA.md](./ROADMAP-TO-GA.md) for pre-GA history and the living audit log. v1.0.0 GA shipped 2026-05-26. Open items: H1 enforcement (2.2.1 ships read-only DETECTION; fail/stop needs positive execution evidence in MoonshotAI/kimi-code#4241), filesystem-dependent write-label coverage, and swarm subagent coverage. H5 is a requested thinking knob, not a guarantee. H3/H4/H8/H9 closed in v1.2.x; H7 real-binary smoke (`bun run smoke:real`) is manual-dispatch CI and requires `KIMI_MODEL_API_KEY` for model calls.
## When editing
- - Read the code before changing it — the runtime has specific invariants that aren't obvious from file names
- - Run `bun run check` before considering any change done
- - After any `runtime/**` (or `scripts/*.sh`) change, regenerate the Codex package: `bun run build && bun run generate:surfaces`, then stage `dist/` AND `plugins/kimi-codex/`. The `plugins/kimi-codex/` tree is GENERATED — never hand-edit it; edit `runtime/`/`scripts/`/`scripts/surface-registry.ts` and regenerate.
- - Codex sidecars are generated by `bun run generate:surfaces` from `scripts/surface-registry.ts`. `bun run check:surfaces` (run after `build` inside `check`) verifies: the Claude plugin bytes match the locked hash gate, the generated Codex text surfaces are current, the bundled runtime mirror (`plugins/kimi-codex/{scripts,dist}`) byte-matches root, and no orphaned generated files remain.
- - Editing a Claude command/agent or any `.claude-plugin/*` file changes its bytes, so you MUST re-pin its sha256 in `CLAUDE_SURFACE_HASHES` (`scripts/surface-registry.ts`) — `shasum -a 256 <file>` — or `check:surfaces` fails closed.
- - `dist/` and `plugins/kimi-codex/` are committed intentionally (zero-build install for both hosts; Codex copies the subfolder to its cache). The drift gate catches forgotten rebuilds/regenerations.
- - Agent files register at session start. Adding or editing `agents/*.md` mid-session doesn't activate them until Claude Code reloads — reach for slash commands or direct `companion.sh` in the same session.
- - `.claude/` is gitignored — notes, worktrees, internal docs under it stay local. Don't try to commit them.
- - **Tests never inherit host plugin env.** `bunfig.toml` preloads `tests/helpers/preload.ts`, which deletes `CLAUDE_PLUGIN_DATA`/`PLUGIN_DATA`/`CLAUDE_PLUGIN_ROOT`/`PLUGIN_ROOT` (and the per-spawn `KIMI_PLUGIN_CC_*` overlays) before every test file. A host session exports these into the shell — possibly ANOTHER plugin's data dir — and a test that spreads `process.env` would otherwise write into a live plugin store (it did, 2026-09-09). Tests that need a plugin root/data dir set their own explicitly; never remove the preload.
- - **Comment hygiene**: keep only current, non-obvious constraints and rationale; delete stale cutover/release essays and redundant narration. History belongs in `CHANGELOG.md` or `ROADMAP-TO-GA.md`; update or remove comments when behavior changes.
- - **Changing a contract**: edit the full statement in `docs/invariants.md` AND its one-line summary here. Rationale and archaeology go there or in the log files, not here.
+ - Read the runtime before changing it; several safety invariants are not obvious from file names.
+ - Run `bun run check` before considering a change done.
+ - After `runtime/**` or `scripts/*.sh` changes, run `bun run build && bun run generate:surfaces`, then stage `dist/`, `plugins/kimi-codex/`, and `plugins/kimi-dsh/`. Both generated trees forbid hand edits; edit source and regenerate.
+ - `check:surfaces` verifies Claude byte hashes, current Codex/DSH generated text, byte-exact runtime mirrors, and no orphaned generated files. Re-pin Claude bytes in `CLAUDE_SURFACE_HASHES` with `shasum -a 256 <file>` whenever they change.
+ - `dist/` and both `plugins/` trees are committed for zero-build installs. The drift gate catches stale generated output.
+ - Agent definitions register at session start; mid-session edits require slash commands or direct `companion.sh` until the host reloads.
+ - `.claude/` is gitignored; local notes and worktrees stay uncommitted.
+ - **Tests never inherit host plugin env.** `bunfig.toml` preloads `tests/helpers/preload.ts` to delete host root/data and per-spawn overlays. Tests requiring those paths set their own explicitly; dependency directories stay out of test discovery and the preload is never removed.
+ - Keep comments limited to current, non-obvious constraints. Update comments/docstrings with behavior changes; history belongs in the changelog/audit log.
+ - **Changing a contract** requires the full statement in `docs/invariants.md` and its short summary here. Rationale and archaeology belong in owning docs or logs.
## Releasing
- Plugin versions advance independently of upstream using ordinary SemVer; a release may
- deliberately align its number with the kimi-code version it certifies (2.2.0 certifies
- CLI 2.2.0), but the number is never evidence — state the exact certified CLI versions separately. Never reuse a published version or
- tag, and never encode an upstream patch with leading zeros. The source audit, exact-
- binary controls, per-operation smoke and full check remain mandatory. The runtime
- certification table is the compatibility authority. See [docs/invariants.md](docs/invariants.md) §2.
+ Plugin versions use ordinary SemVer and may align with the certified kimi-code version, but the number is never compatibility evidence; state exact certified CLI versions separately. Never reuse a published version/tag or encode an upstream patch with leading zeros. Source audit, exact-binary controls, per-operation smoke, and full check are mandatory. The runtime certification table is authoritative ([docs/invariants.md §2](docs/invariants.md)).
- `runtime/version.ts` is the SINGLE version source — `scripts/surface-registry.ts` (`PLUGIN_VERSION`) imports it, so the Codex manifest/marketplace version propagates automatically. A version bump touches these and then regenerates:
+ `runtime/version.ts` is the SINGLE version source; `scripts/surface-registry.ts` propagates it to Codex and DSH manifests. A version bump touches:
- - `runtime/version.ts` (`KIMI_PLUGIN_CC_VERSION` — written into the managed-block marker comment by /kimi:setup; imported by surface-registry)
- - `package.json`
- - `.claude-plugin/plugin.json` — **also re-pin its sha256 in `CLAUDE_SURFACE_HASHES`** (its bytes change)
- - `.claude-plugin/marketplace.json` — **also re-pin its sha256 in `CLAUDE_SURFACE_HASHES`**
- - `AGENTS.md` — bump the version number, and **REPLACE** a contract bullet if the release changes a standing contract (present tense, one statement, never appended). What a release *did* goes in `CHANGELOG.md` / the ROADMAP audit log; the full mechanics go in `docs/invariants.md`.
- - `CHANGELOG.md` (add a new version section — see its top banner. Docs-only compat checkups that DON'T bump the version are logged in ROADMAP's Post-GA audit log instead, not here.)
- - then run `bun run build && bun run generate:surfaces` to regenerate `plugins/kimi-codex/` (manifest version + bundled runtime), and stage it.
+ - `runtime/version.ts`, `package.json`, `.claude-plugin/plugin.json`, and `.claude-plugin/marketplace.json`; re-pin both manifest hashes in `CLAUDE_SURFACE_HASHES`.
+ - `AGENTS.md`: bump the version and REPLACE any changed standing contract in present tense; what a release did belongs in `CHANGELOG.md` or the ROADMAP audit log, mechanics in `docs/invariants.md`.
+ - `CHANGELOG.md`: add a version section for releases, not docs-only compatibility checkups.
+ - Run `bun run build && bun run generate:surfaces` and stage all three distributions.
- Before release validation, run `bun install --frozen-lockfile`; never reuse an
- unchecked local dependency directory as proof of a reproducible build. Regenerate
- and stage both distributions, then run `bun run check`. Compiled-runtime
- regressions must execute the emitted JavaScript, not only Bun's TypeScript loader.
+ Before release validation, run `bun install --frozen-lockfile`; never reuse an unchecked dependency directory as proof of reproducibility. Regenerate/stage all distributions, then run `bun run check`. Compiled-runtime regressions must execute emitted JavaScript, not only Bun's TypeScript loader.
- Commit and push the release commit, wait for its CI check to pass, then
- `git tag -a vX.Y.Z -m "..."`, `git push origin vX.Y.Z`, and
- `gh release create --verify-tag`. A published tag is immutable; fix forward.
+ Commit and push the release commit, wait for CI, then `git tag -a vX.Y.Z -m "..."`, `git push origin vX.Y.Z`, and `gh release create --verify-tag`. Published tags are immutable; fix forward.
### npm distribution
- `package.json` allowlists publication files. `bun run check:package` packs it,
- rejects sync-conflict copies, byte-checks both compiled distributions, and runs
- isolated setup/check, unsupported-version refusal, and modern/legacy MCP protocol smokes on both.
- No live credentials, plugin state, or model calls. CI runs it after the full check. `publish.yml` builds/tests a release tag, then publishes that exact tarball
- through npm OIDC in the `npm` environment. See `docs/registry-releases.md`.
+ `package.json` allowlists publication files. `bun run check:package` packs the archive, rejects sync-conflict copies, byte-checks all three compiled distributions, and runs isolated setup/check, unsupported-version refusal, and modern/legacy MCP smokes without credentials, live plugin state, or model calls. CI runs it after the full check. `publish.yml` builds/tests a release tag and publishes that exact tarball through npm OIDC in the `npm` environment. See [docs/registry-releases.md](docs/registry-releases.md).