# 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.

## Quick reference

- **Version**: 2.4.1 (kimi-code, subprocess transport, **native agent-core-v2**).
- **The native-v2 safety contract — do not regress** (§1): every executed tool call in a plugin-managed session passes the managed PreToolUse hook, and the engine's sole chain-breaking final allow (the plan-file guard, `features/plan/planService.ts`, active plan mode only) is NEVER armed. That is a plugin-owned construction, not an upstream ordering promise, resting on four closures that must all stay in place: (1) `runtime/native-v2-preflight.ts` refuses `default_plan_mode` under every spelling upstream's `snakeToCamel` normalizes (parsed from the same `<KIMI_CODE_HOME>/config.toml` the child reads, before spawn and at the spawn boundary, `KIMI_CODE_HOME` resolved against the child's cwd on every entry path incl. the Stop hook; `CLI_V2_PLAN_MODE_CONFIGURED`, remedy is the config key, never `/kimi:setup`); (2) the hook denies `EnterPlanMode`/`ExitPlanMode` for every label; (3) resume accepts only proven `native-v2` plugin lineage after a raw scan of every agent `wire.jsonl` for `plan_mode.*`/`plan.*` (`KIMI_SESSION_LINEAGE_UNKNOWN` / `KIMI_SESSION_ENGINE_MISMATCH` / `KIMI_SESSION_PLAN_TAINTED`); (4) v2-only experimental features (`tower`, `subagent_fork`) and `KIMI_CODE_EXPERIMENTAL_FLAG` refuse before spawn. Certification is per EXACT version and per operation (`NATIVE_V2_CERTIFIED` in `runtime/kimi-engine.ts`; `KIMI_TESTED_MINORS` is the legacy-v1 table and never gains 0.42): a version is appended only after `tests/audit/v2-tag-scan.test.ts` passes on its source tree, the plan-mode-ON live control still shows the bypass (proving the refusal is load-bearing), and the per-operation smoke is green. A v2 plan requires the `system.version` marker as the first stream-json line, equal to the probed version; a legacy plan refuses it (`CLI_ENGINE_PROVENANCE_MISMATCH`). `Bash.cwd` is validated against the trusted root before the command string (`runtime/rescue-approval.ts`). Basis and residuals: `docs/native-v2-certification-provenance.md` §2.
- **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 certified at exact `@moonshot-ai/kimi-code@0.42.0`, `0.43.0`, `0.43.1`, `2.0.0`, `2.0.1`, `2.0.2`, `2.1.0`, `2.1.1`, `2.2.0`, `2.3.0`, `2.3.1` and `2.3.2` for all eight operations. Legacy-v1 through **0.41.x** for an explicitly pinned binary only (0.42.0 deleted v1). `selectIntendedEngine` chooses v2 only for an exact operation/version row, v1 only for the legacy table, else refuses. The plan-register-before-hooks defect and sole `.allow()` at `planService.ts:110` remain through 2.3.2 (MoonshotAI/kimi-code#3431). Evidence: [docs/native-v2-status.md](docs/native-v2-status.md); routine: [docs/upstream-compat-audit.md](docs/upstream-compat-audit.md) + tag scan. **Re-verify each audit** (full statements in §2): per-job engine/session provenance and plugin-owned trusted roots; the subscriber set and sole allow; hook schema, deny/fail-open/aggregation and Write/Edit `path`, Bash `command`+`cwd`; print argv, `KIMI_CODE_NO_AUTO_UPDATE=1`, absolute `KIMI_CODE_HOME`, no `KIMI_CODE_TRUST_WORKSPACE` (interactive trust still boots project MCP, a residual); all plan-arming routes and same-wire restore/stale retry; host-only commands, `/goal` as the only `-p` interception, profile limits, goal exits 0/3/6, eager child hooks, always-exported swarm cap, exact tuple and finite budgets. Daily monitor reports (`.claude/kimi-code-research/daily-monitor/`, gitignored) never certify. **2.3.2 swarm activation:** upstream removed model self-activation, so `runtime/swarm-activation.ts` uses the exact CLI's authenticated loopback web API to verify backend/version/no-auth-bypass, bind the selected model + auto permission + swarm mode with plan off, shut down, then resumes the same job through `kimi -p -r`; a second bounded lifecycle deactivates and requires `swarm_mode.exit`. The token stays in memory; activation uses `--budget`; print must return the preactivated ID. `runSwarm` is the sole deactivation owner (remaining budget normally; separate 2s cancellable failure recovery, never retried), preserving print errors, post-mutation identity and process/group uncertainty. Root loss needs prior identity; PGID moves are not exit. Cleanup uncertainty blocks success and retains diagnostics/raw output plus the write worktree; cancellation starts no fresh server. Swarm smoke MUST prove distinct child journals and correlate each assigned Write/Edit path and toolCallId with its result; an AgentSwarm record, aggregate denial or coordinator-only patch is NOT evidence.
- **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.

## 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:
  ├── .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
  ├── 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)
  ├── 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)
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), 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`

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`, `mcp`.

## Architecture

**Thin plugin, 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`

**Key invariants** (each in full in docs/invariants.md §4):

- **Subprocess-first.** One `kimi -p` prompt runner per job; exact 2.3.2 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 strict-by-default and exact.** `verifyHookInstalled` rebuilds the canonical command from env (`install-paths.ts::tryBuildExpectedHookCommand`, the single source of the command bytes) and equality-checks the host's installed command. Setup/check parse the COMPLETE TOML with vendored `smol-toml@1.9.1` (upstream fixes included; no backport needed) and strictly validate every hook against kimi-code's schema before the managed-block grammar; unknown top-level tables are tolerated. Additive hook events use 0.x minimum minors or the reviewed exact 2.0.0/2.0.1/2.0.2/2.1.0/2.1.1/2.2.0/2.3.0/2.3.1/2.3.2 schemas (schema review alone does not certify execution); other nonzero versions fail closed. Substring/partial-parse paths are forbidden. **Markers are NOT durable** (kimi-code strips comments on every config write) — `evaluateInstalled` falls back, ONLY from the absent state, to a marker-less clean enforcing table, decided with the REAL parser (`hasCleanEnforcingHookEntry`): `event="PreToolUse"`, byte-exact `command`, NO `matcher` in any spelling, no keys beyond `{event,command,timeout}`. Never regress this to a line scanner. `findBareApprovalHookTables` is ONLY the conservative prune scanner. Re-verify each audit: installed check stays parser-based, matcher-rejecting, byte-exact, absent-state-only; the prune stays host-scoped.
- **Serialized config mutation.** Install/uninstall hold a private adjacent `config.toml.kimi-plugin-cc.lock` across the whole read-modify-write; publication is atomic; lock reads are no-follow/nonblocking/regular-file-only and capped; acquisition is bounded; live owners are never stolen; stale recovery is ABA-safe; inode/token ownership is rechecked before mutation. New kimi-code home `0700`, config and lock `0600`. Inline `hooks = [...]` is normalized to `[[hooks]]` with a comment-loss warning. `/kimi:setup --check` exits nonzero on any whole-file, hook-schema, managed-block, path or shell-probe failure.
- **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) = autonomous goal mode** (`kimi -p "/goal -- <objective>"`, 0.8.0+; `KIMI_CODE_EXPERIMENTAL_GOAL_COMMAND=1` kept for 0.8–0.11). Write-capable with the rescue workspace boundary (`commandLabel: "rescue"`, hook on every continuation turn, `command_type:"rescue"` lineage, no git mutation). Trusted `executionPlan.operationKind` is exported as `KIMI_PLUGIN_CC_OPERATION` on every spawn, replacing ambient values; only `pursue` plus a trusted root permits exact `GetGoal {}` and terminal `UpdateGoal {status:"complete"|"blocked"}`. Goal creation, reactivation and budget mutation stay denied; older coexisting hooks can still veto these tools. The only new risk is unboundedness, bounded by a **mandatory finite `--budget`** (default 45m; `--turns` is a soft hint). Runtime-foreground-only; the caller detaches its own shell call. No `--resume` (goalId ≠ sessionId). Exit 0/3/6 are terminal, not failures. `docs/safety.md` § "Autonomous goal mode".
- **`/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` = write-capable parallel fan-out.** Ephemeral detached git worktree off HEAD under the plugin's own `worktreesDir`; coordinator spawned with cwd = that worktree (`spawnCwd` seam; `job.cwd` stays the user's cwd); `subagent_type:"coder"`, disjoint targets, no git, no nested `AgentSwarm`. Label `swarm-write` routes writes through `evaluateRescueHookRequest` scoped to a **forge-proof trusted root** from `KIMI_PLUGIN_CC_WORKSPACE_ROOT` (never the payload `cwd`); fail-closed when missing. The user's real tree is never touched (subagents share the one worktree — re-verify that permission-stack claim each audit). The change set is captured as an untruncated applyable `.patch` (`git add -N` + `git diff --binary`) on EVERY terminal path; capture failure fails the job and retains the worktree for recovery; a startup sweep reaps orphans. **The plugin never applies or commits.** `command_type:"rescue"` lineage; refuses without the hook; requires kimi-code ≥ 0.18.0, a born HEAD, loud dirty-tree warning; default `--max-concurrency` 1. Runtime-foreground-only ≠ caller-foreground-only. **Detached-shell cancel is no-id** (`companion.sh cancel`; MCP uses its job ID; prefer it over Esc/TaskStop — harness grace ~1.35 s is shorter than teardown + patch capture). Pursue is NOT swarm-write (real tree vs discardable patch). The model-invocable `kimi-swarm-write` agent wraps it under strict triggering. `docs/safety.md` § "Write-capable swarm".
- **`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** is AbortController + SIGTERM → SIGKILL (1500 ms) with teardown as a settlement barrier: POSIX spawns detached, snapshots descendant identity (PID/PGID/start time) and revalidates before signalling, waits through a bounded post-SIGKILL quiescence check, never signals a changed identity, and emergency-kills the controlled root rather than stranding settlement. win32 remains direct-child-only (known gap). Ask/review/challenge default to 30m and rescue to 45m, overridable with `--budget` up to 24h. Clean non-aborted exit-0 settlement wins a same-tick budget expiry; timeouts retain `settled_post_abort:true` when a result arrives after abort. Only budget timeouts retain settled partial prose in failed jobs (and an announced session ID or a native-v2 session recovered from bounded session-store enumeration when exactly one session was created between spawn and settlement in the same cwd AND its main journal's first user message carries the job's exact prompt (`prompt_digest`); `kimi_session_id_source` records which, `session_recovery` in error details says why not, and normal lineage/plan-taint checks still gate resume); cancellation never captures partial output, and backgrounding does not extend a budget.
- **Config writes preserve mode 0o600** (`writeConfigAtomic` chmods 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.

## 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.

## 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.

`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` (`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.

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.

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.

### npm distribution

`package.json` allowlists publication files. `bun run check:package` packs it,
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`.
