Skip to content

Support Kimi Code CLI as a builder (PIR #1201) - #1203

Draft
mohidmakhdoomi wants to merge 47 commits into
cluesmith:mainfrom
mohidmakhdoomi:builder/pir-1201
Draft

Support Kimi Code CLI as a builder (PIR #1201)#1203
mohidmakhdoomi wants to merge 47 commits into
cluesmith:mainfrom
mohidmakhdoomi:builder/pir-1201

Conversation

@mohidmakhdoomi

@mohidmakhdoomi mohidmakhdoomi commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

PIR Review: Support Kimi Code CLI as a builder

Fixes #1201

Re-integration notice (2026-08-09). This PR was parked on two upstream blockers, both now merged (#1317/#1267, #1356/PIR #1233), and main moved 900+ commits — including Spec 1313, which made afx send mailbox-first. kimi itself moved 0.27.0 → 0.34.0. The branch has been merged up and the feature substantially redesigned against what changed underneath it. The delivery mechanism, the resume mechanism, and the pacing seam are all different from what earlier reviewers saw; the summary below describes the current design, and "What changed since your last review" near the end lists the deltas. Re-review from scratch rather than from memory.

Summary

Adds the Kimi Code CLI (kimi, ≥ 0.33.0) as a supported builder harness — shell.builder: "kimi" / builderHarness: "kimi" / --builder-cmd kimi now produce a working builder instead of the #1062 false-Claude fallthrough (which appended --append-system-prompt and a positional prompt, both rejected by kimi, and could route a stale Claude --resume <uuid> into it).

Kimi has no system-prompt flag and takes no positional prompt, so role and task travel on two different channels:

  • Role → --agent-file (kimi 0.31.0+). getWorktreeFiles writes an agent-definition file into the worktree whose body wraps the role around ${base_prompt} — the token that interpolates kimi's own default system prompt, so the role extends rather than replaces it. This is the claude --append-system-prompt analogue.
  • Task → the Spec 1313 mailbox. The generated launch script queues the task with afx send, and the render gate delivers it onto a verified-empty composer. Never a direct PTY write, so a boot screen, a busy line, or kimi's folder-trust dialog holds the message rather than corrupting or losing it.

Crash restarts resume with the documented, cwd-scoped kimi -c — no session id is ever baked into generated bash. Kimi as an architect remains out of scope (stage 2); buildRoleInjection throws and doctor warns, so misconfiguration fails loudly rather than falling through to claude flags.

The finding that shaped the design

kimi -c does not fail when there is nothing to continue. It prints No sessions to continue under "<cwd>"; starting a fresh session. and starts one anyway — and that session never saw --agent-file, so it runs silently roleless. That is the #929 hazard class, arriving through a documented flag.

So the launch loop only takes -c after an inlined node -e store probe proves a conversation exists for this cwd, and the probe fails closed: any error (no store, unreadable dir, malformed JSON) exits non-zero and the loop relaunches fresh with the role, which is always safe. Tests execute that probe against fixture stores and cross-check its verdict against the TypeScript discovery it mirrors, so the hand-written snippet cannot silently drift from findLatestKimiSessionId.

The probe asks "would kimi -c continue this?", not "does a directory exist" — a distinction review had to teach me (see below). Kimi lists a cwd's sessions before continuing one, and that listing drops archived sessions and ids it does not recognize, so a session we call resumable but kimi skips lands right back on the roleless path. Both filters now apply on both sides of the mirror.

The same probe makes the script's entry self-configuring, so afx spawn --resume and a Tower-side terminal re-create need no second script shape — and a re-run never re-queues the task into a live conversation.

⚠️ Please look here first: two edits to shared, just-merged gate logic

servers/render-gate.ts is Spec 1313 code that landed recently. This PR touches its classifier in two places. Both are opt-in per profile, and neither can change behaviour for claude, codex or agy — argued below and pinned by tests.

1. The marker exemption follows the matched span instead of column 0

-      if (row === markerRow && col === 0) continue; // the marker glyph itself
+      if (col < markerEnd) continue; // the marker glyph itself

Why: kimi draws its composer inside a rounded box, so its prompt marker sits at column 3 (│ > ), not the row start. Under the column-0 rule the > glyph counts as user text, so a genuinely empty Kimi composer classifies user-text forever — i.e. holds all of its mail, permanently.

2. A profile may declare an upper bound for the composer region

GateProfile gains an optional regionStartPatterns. Kimi sets it to the box top; every other profile leaves it unset and keeps scanning from the marker row exactly as before.

This one fixes a false CLEAN, and it was found by review, not by me. KIMI_MARKER matches │ >, findMarkerRow takes the last match, and the scan started at that row. So a draft whose final line begins with > — a pasted quote, a markdown blockquote — puts the marker on the continuation row, leaving the real draft text above the scanned region. Measured on real kimi 0.34.0:

 ╭──────────────────────────────
 │ > implement the whole feature
 │   >
 ╰──────────────────────────────

→ classified {clean: true, detail: "empty"}. A queued message would then have been typed on top of unsent user input — the exact corruption Spec 1313 removes by construction. Committed as the kimi-multiline-bare fixture, captured rather than constructed.

The bound is exclusive, mirroring the region end, and that is load-bearing rather than stylistic: the box-top row's right corner is not in the classifier's ignorable-glyph set, so an inclusive bound counted it as user text and held every idle kimi composer forever. The fixture suite caught that on the first attempt.

Why other profiles cannot reach it: they declare no regionStartPatterns, so the region starts at markerRow; and since findMarkerRow returns the last match, no row below it can match either. The set of marker-matching rows in their region is exactly {markerRow} — the previous behaviour, by construction. A test asserts all three declare no region start, and that text above the composer still classifies clean for claude and codex.

Why it is a no-op for every other app, argued and then pinned by tests:

profile marker span effect
claude ^[❯›] 1 col < 1col === 0 — literally the old rule
codex ^[❯›] 1 same
agy ^> 2 extra cell is the space, already skipped by the whitespace rule that runs before the marker check
kimi ^\s*│\s*> 4 the case this exists for

Guardrail tests (render-gate.test.ts, "marker-span exemption is a no-op…"): the exact span per shipped profile; a tightest-possible 1-char draft in the first cell the exemption could wrongly reach, per profile, all still busy (over-skipping is the only direction that could cause harm — a false CLEAN); proof that agy's span can never over-reach a typed character (>x doesn't match its marker at all); and a direct before/after demonstration that a span-2 kimi profile classifies the real 0.34.0 idle capture user-text while the shipped span-4 one classifies it clean. Every pre-existing claude/codex/agy fixture still passes unchanged.

Undocumented-surface reliance (audited against kimi 0.34.0, 2026-08-09)

Two surfaces, both dated so the reliance can be re-checked on each Kimi major:

  1. Session store ~/.kimi-code/sessions/wd_*/session_*/state.json. This has already drifted once: 0.33.0 renamed workDircwd, moved timestamps from ISO strings to epoch ms, and dropped lastPrompt. Readers accept both shapes, and codev doctor asserts the load-bearing facts explicitly and names the one that broke rather than reporting "something changed".

  2. Workspace-trust record ~/.kimi-code/workspace-trust/wd_<basename>_<sha256(root)[:12]>{root, trustedAt}. This is the maintainer veto point, so here is the full argument.

    kimi 0.33.0 added a startup "Trust this folder?" dialog. A builder worktree is always a brand-new directory; the dialog renders before any composer, and its only non-trusting option exits kimi — so an unattended builder would sit on it forever. The spawn path therefore pre-writes the trust record.

    • No sanctioned bypass exists. kimi --help has no flag; a full strings sweep of the 0.34.0 binary for KIMI_* env vars and for trust config keys found nothing (every "trust" hit was KaTeX/V8/OpenSSL noise). I looked for a supported knob first, as requested, and there isn't one.
    • What trust actually gates is narrow: whether project-level MCP servers (.mcp.json, .kimi-code/mcp.json) load from the folder. It does not gate tool execution or writes.
    • Scope: written only for a worktree Codev itself created, for a builder the human explicitly spawned, already running --yolo. It grants strictly less than launching the builder already authorized, and never touches a directory the user did not hand us.
    • Fail-soft: on any error the dialog simply appears, the gate holds the task message (no composer marker → busy), and mailbox escalation surfaces it. Never a silent misdelivery.
    • Drift is detected, not silent: codev doctor validates our derivation against kimi's own records (each record carries the root it was written for, so the expected filename is recomputable). A scheme change surfaces as a named warning instead of silently stranding every new builder on the dialog.

    My reviewers split on this one, so you should see both sides rather than just my case. Codex argued it is a real security boundary: --yolo governs approval of the agent's tool calls, whereas workspace trust governs whether repository-controlled MCP configuration is loaded and its processes started at all — so a builder spawned on a fork PR or other untrusted branch could have attacker-controlled MCP config loaded without a human ever seeing the decision. Claude reviewed the same code and reached the opposite conclusion: a --yolo builder in a Codev-created worktree already holds strictly more authority than MCP loading confers. I find Claude's reading more persuasive for the worktrees Codev creates, but Codex's fork-PR scenario is the case where the two arguments genuinely diverge, and that is a policy call I do not think is mine to settle.

    If you'd rather not ship the hash write at all, the fallback is that Kimi builders require one human keypress at first launch; say the word and I'll cut it. A narrower option, if you want the automation but not the blanket: refuse the pre-write when the worktree carries project-level MCP configuration.

Corrections to claims this PR previously made

  • "Kimi has no hook seam, so Builder worktree write-guard: prevent writes anchored at the main checkout root #1018 write-guard parity is impossible" — obsolete. Kimi documents blocking PreToolUse hooks ([[hooks]] in config.toml, exit code 2 blocks, 18 events as of 0.32.0). Parity is achievable. I have scoped it as follow-up rather than growing this PR further, but that is your call — say so and I'll add it here. Until it lands, a Kimi builder can write outside its worktree, and the docs now say exactly that.

Two decisions that are yours, not mine

A. The version floor moves 0.27.0 → 0.33.0, which drops working installs

This is a real compatibility reduction and I want it visible rather than buried in a diff. The reasoning chain:

  1. --agent-file requires ≥ 0.31.0. Below that the role does not inject at all and the builder runs silently roleless — the worst available failure mode, and not one a user would notice quickly.
  2. The folder-trust dialog appears at 0.33.0, which is what the trust pre-write exists to handle. On 0.31–0.32 there is no dialog, so that machinery is inert — but it also means those versions are a genuinely different startup path from the one I exercised.
  3. 0.33.0 is an engine boundary: it made agent-core-v2 the default. Every live measurement backing this PR — store shape, trust behaviour, the render-gate composer profile — was taken on 0.34.0, i.e. on that engine. 0.31–0.32 run the old engine and are unmeasured.
  4. Kimi ships weekly, and its store has already renamed a load-bearing field once inside this PR's lifetime. Under that cadence a narrow, evidence-backed floor is safer than a wide compatibility claim I cannot stand behind.

So the floor sits at the oldest version the evidence actually covers, not the oldest that would nominally function. If you would rather accept 0.31.0 (functional minimum, unmeasured) or hold at 0.27.0 (maximum compat, definitely broken for role injection), say which and I'll change the one constant in doctor.ts plus the docs.

B. The .builder-kimi marker is deleted — this is not a revert of your July fix

Your July REQUEST_CHANGES found a real bug: the bare launch shape (no role, no prompt) never persisted .builder-kimi-session, so an override-spawned bare Kimi builder (--builder-cmd kimi in a claude-configured workspace) fell through to claude's 80ms Enter and its mail was swallowed. That fix — touch the marker in the bare branch — was correct and shipped.

This PR removes the marker entirely, so I want to be explicit that the property your finding protected is now stronger, not weaker.

The marker was a separate artifact that every launch shape had to remember to write. That is a standing obligation, and the bare shape is precisely the one that forgot it — which is why your review caught a bug rather than a typo. Adding the missing touch fixed that instance; it did not remove the class. A future fifth launch shape could forget it again.

Pacing now reads the harness name out of the generated .builder-start.sh, which is generated from the resolved harness. There is nothing to remember: any shape that launches kimi necessarily names kimi in command position, because that is the launch. The obligation is discharged by construction rather than by discipline, and it is still override-proof for exactly your scenario — a --builder-cmd kimi spawn against a claude-configured workspace resolves kimi, because the script was generated from the override.

Tests keep your scenario pinned directly (mailbox-pacing.test.ts: "resolves kimi for the BARE launch shape too — the shape the old marker probe missed", plus an explicit override-proofness test), and spawn-worktree.test.ts pins that both generated shapes put kimi in command position, so a refactor that hid it would fail rather than silently degrade pacing.

What changed since your last review

Was Now Why
Seed bootstrap: kimi -p seed → capture session.resume_hint → pinned kimi -S <id> loop Role via --agent-file; task via the mailbox Drops 3 undocumented surfaces; role rides a system prompt instead of a user turn
seed-kick.ts: sentinel watcher + grace + BEGIN written straight to the PTY, verified via state.json.lastPrompt Deleted. Spec 1313's render gate is the readiness barrier A direct PTY write is exactly what Spec 1313 forbids; the gate already answers "is this composer empty?"
Resume: explicit -S <discovered-id> kimi -c behind a fail-closed store probe Documented flag; no undocumented id in generated bash
Pacing via a .builder-kimi marker file Harness read from the generated .builder-start.sh The marker obliged every launch shape to write one — the bare shape didn't, which was your finding last round. The launcher is generated from the resolved harness, so it cannot be forgotten or overridden away
message-pacing.ts + seedKick on createTerminal resolvePacingForSession in mailbox-wiring.ts; SeedKickRequest removed from the SDK Spec 1313 replaced the routes the old pacing hooked into — it was wired to nothing after the merge

What the 3-way review round found (and what it changed)

Before opening this for re-review I ran gemini, codex and claude over the post-merge delta, asking them to attack the shared gate edit hardest. gemini APPROVE; codex and claude both REQUEST_CHANGES — and they were right. Full dispositions are committed at codev/projects/1201-*/1201-cmap-postpivot-dispositions.md; the two blocking ones are worth stating here because they say something about where the risk in this PR actually lives:

  • The false CLEAN described above. Claude reproduced it on a constructed screen and flagged honestly that it had no live kimi to confirm the real multi-row geometry. I measured it — kimi renders exactly that shape. Claude also proposed a second input (a marker row inside a second box below the composer); measured, that one is not reachable, because kimi's / menu renders as unclosed rows with no beneath them, so anything inside it yields no-region-end → held. Both are now fixtures.
  • The store probe diverged from the TypeScript it mirrors, and the cross-check test did not catch it because it compared two implementations of the same omissions. Codex found the dangerous direction: an archived session authorized -c, which kimi then refuses to continue, producing the silently-roleless session the guard exists to prevent. Claude found the safe-but-harmful direction: readdirSync on a stray non-directory threw ENOTDIR into the single outer try, so one .DS_Store in ~/.kimi-code/sessions/ disabled resume machine-wide, permanently and silently.

Also fixed from that round: shell metacharacters in a builder id or task path could execute when the launch script printed a recovery hint (all three reviewers, from different angles); a crash loop re-queued the same task every ~2s even though the mailbox persists held rows; and both drift probes reported healthy forever after a store migration, because "any record still matches" is satisfied by the pre-migration records.

The common thread: every one of these lives in a state a happy-path run does not produce — an empty composer and a clean store both behave correctly, which is precisely why three passing live demos missed them.

One more disclosure, since it affects how you should read the demo. Two demo steps were failing when I picked this back up, and the cause was the demo, not the product: its role told the model to prefix every reply with a token, which measures whether K3 honours a persistent output-format constraint rather than whether the role was injected. The --agent-file probe, run against a production-identical agent file, passed 7/7 including role survival across kimi -c. The demo now asks for a codeword instead — the same oracle the probe uses — and carries a comment explaining why, so the weaker check does not come back.

Verification

  • pnpm build clean; full suite 4900 passed / 48 skipped / 0 failed.
  • Live demo against real kimi 0.34.0, 7/7 (codev/spikes/pir-1201-kimi-builder-demo.mjs, runs the REAL dist modules): render gate classifies the live composer; --agent-file role honored in the interactive TUI; paced multi-line delivery submits; crash → store probe → kimi -c → role survives; probe fails closed on an empty store; trust pre-write idempotent.
  • Pivot validation, 7/7 (pir-1201-kimi-agentfile-probe.mjs): --agent-file injects in both -p and the interactive TUI; TUI start mints no session, the first message mints exactly one; kimi -c resumes with the role binding intact and mints no second session.
  • Gate fixtures are real 0.34.0 captures (pir-1201-kimi-gate-measure.mjs), committed raw — they carry only throwaway /tmp paths. Idle → clean; draft, multi-line draft, the bare-> multi-line draft, / menu, @ picker and the folder-trust dialog → busy (so a blind Enter can never confirm filesystem trust).

Out of scope

Kimi as architect (stage 2); ACP / kimi server adapter; #1018 write-guard parity (now achievable — see above).

mohidmakhdoomi and others added 26 commits July 18, 2026 18:59
…ilder and architect

Seed-session bootstrap (kimi -p role seed -> capture session id from
stream-json -> TUI resume via -S) validated end-to-end; solves role
injection, initial prompt delivery, and the stored-ID session contract.
Includes reproducible POC script and full impact map / test matrix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cluesmith#1149 parity requirement

Two new observations: Kimi TUI never enters the alternate screen (no
escape-based readiness signal), and PTY input during the seed window has
no defined consumer (silently lost). Barrier design: sentinel + grace +
store-verified delivery with retry; seed carries role+task, kick is a
single BEGIN line. Architect parity correction: stored-ID resume without
an async-buildable CrashLoopFallback is cap-exhaustion outage, not cluesmith#1149
safety — ship Codex-like (stage 1) or stored-ID + async fallback (stage
2), no middle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ipt, builder resume

- KIMI_HARNESS in harness.ts + detectHarnessFromCommand('kimi') — kills the
  cluesmith#1062 false-Claude fallthrough; buildRoleInjection throws (builder-only)
- New optional HarnessProvider.buildBuilderLaunchScript capability; Kimi
  generates the seed-session bootstrap script (idempotent seed via kimi -p
  stream-json, session.resume_hint capture, sentinel, pinned -S --yolo loop)
- kimi-session-discovery.ts: store scan / ownership verify / state reader
  (undocumented store layout, observed on kimi 0.27.0; fail-soft)
- buildResume: .builder-kimi-session precedence (ownership-verified) → store
  scan → null → fresh-with-role fallback
- spawn-worktree branches on the capability; writes .builder-seed.txt and
  passes the seedKick request through createPtySession
- Tests incl. the cluesmith#929-class regression: kimi + stale Claude jsonl never
  yields --resume <claude-uuid> or --append-system-prompt

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ss Enter pacing

- seed-kick.ts: readiness barrier — waits for the launch script's
  __CODEV_KIMI_SEED_DONE__ sentinel (writes during the seed window are
  silently lost), grace, then a store-verified BEGIN kick with an
  Enter-resend → kick-resend → loud-warn retry ladder
- createTerminal grows an optional seedKick field (core SeedKickRequest);
  handleTerminalCreate validates and arms it (malformed → ignored)
- message-write.ts: optional pacing.enterDelayMs overriding both default
  Enter delays (Kimi swallows an 80ms Enter; defaults unchanged otherwise)
- message-pacing.ts: resolves pacing per target — worktree marker probe
  first (override-proof for --builder-cmd spawns, survives Tower restarts),
  then config-resolved harness by terminal role
- Wired at all delivery paths: send direct + buffered, cron

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…store smoke probe

- Kimi in AI_DEPENDENCIES: kimi --version presence with minVersion 0.27.0
  (pins the version the undocumented surfaces were observed against)
- verifyKimi(): credential-artifact heuristic (no billed probe — Kimi
  documents no auth status command), kimi login guidance; supplementary
  'kimi doctor' config check (documented exit codes, not an auth check)
- Session-store layout smoke probe warns loudly on drift
- Architect-shell branch: kimi configured as architect → builder-only warning

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…mples, skeleton mirror)

- arch.md: dedicated Kimi subsection (builder-only, seed-session bootstrap,
  sentinel-gated store-verified BEGIN, per-harness pacing, explicit-ID
  resume, undocumented-surface caveats + 0.27.0 pin, NO write-guard parity)
- agent-farm.md (instance + skeleton): builder-harness config examples

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…on session type

- resolvePacingForSession wraps its whole body in try/catch: pacing is
  advisory and must never break message delivery (a missing DB in the
  tower-routes test env surfaced this as 500s on /api/send)
- CronDeps session shape carries id/cwd (the real PtySession provides both)
- tower-routes test mock gains getTerminalSessionById

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bisected on kimi 0.27.0: 80ms and 100ms swallowed; 120/250/500/1000ms
submit. Threshold ~100-120ms; shipped value stays 1000ms (~9x margin).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s pass)

codev/spikes/pir-1201-kimi-builder-demo.mjs runs the real dist modules
(script generator, armSeedKick, writeMessageToSession, buildResume)
against a real kimi PTY: seed bootstrap, sentinel-gated store-verified
BEGIN, multiline delivery at the pinned Enter delay, inner-restart
context retention, and -S resume. Executed against kimi 0.27.0 — 5/5
PASS; the ack-and-wait-with-task seed discipline held (no fallback
needed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ier)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… consultation finding)

The delivery check used lastPrompt.includes(kickMessage) — but on a fresh
spawn lastPrompt initially holds the SEED prompt, whose ack-and-wait
wrapper itself mentions BEGIN, so the verifier reported success before the
kick ever submitted (silently defeating the swallowed-Enter recovery; the
live demo's happy path masked it). Confirmation now requires
whitespace-normalized EQUALITY (submitted messages land in lastPrompt with
newlines flattened to spaces — observed on kimi 0.27.0). Two pinning
regression tests added; live demo re-run post-fix: 5/5 PASS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mohidmakhdoomi

Copy link
Copy Markdown
Collaborator Author

Architect Integration Review

Contributor-side review summary for maintainers (this PR was developed under the PIR protocol on our fork; we do not merge — that call is yours).

Process: Plan and dev-approval were human-gated pre-PR. The dev-approval gate included a full-path live demo through a locally installed build — a real afx spawn of a Kimi builder through Tower showing (1) seed-session bootstrap with session-id capture, (2) sentinel-gated, store-verified BEGIN delivery, (3) multiline afx send at the pinned Enter delay, and (4) inner-restart context retention via kimi -S — plus codev doctor's new kimi checks.

Consultation (CMAP, single advisory pass): gemini APPROVE, claude APPROVE, codex REQUEST_CHANGES. The codex finding was real and was accepted + fixed in 732f04b: seed-kick delivery confirmation was a substring match on lastPrompt, and the fresh-spawn seed prompt itself contains "BEGIN", so verification false-positived before the kick submitted — defeating the swallowed-Enter recovery. Fix is whitespace-normalized equality, with two pinning regression tests (both fail pre-fix), seed-kick suite 14/14, and a post-fix live demo re-run (5/5).

Architect verification of the post-CMAP fix (since PIR's single-pass consultation does not re-review fixes): I reviewed confirmed() in seed-kick.ts and the pinning tests directly — the equality predicate is correct, preserves the multiline-payload fallback (kimi flattens submitted newlines; normalization covers it), and the retry ladder (Enter re-send → one re-kick → loud warn) is intact. Known benign edge: a message raced into the session during the ~10s verify window causes one bounded re-kick, then a warning.

Scope: verified against issue #1201's builder-MVI checklist — no architect-parity changes (tower-utils/tower-instances/tower-terminals/session-manager/architect.ts untouched), no ACP adapter; kimi-as-architect fails loudly. Undocumented Kimi surfaces are labeled as observed, pinned to kimi >= 0.27.0 with a doctor smoke probe.


Architect integration review

mohidmakhdoomi and others added 2 commits July 18, 2026 20:55
… for maintainers

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cepted+fixed)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@waleedkadous waleedkadous left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outstanding work — this is one of the most disciplined external PRs we've received; the observed-vs-documented honesty around Kimi's store and the store-verified BEGIN ladder are exactly right. I'd like to see one change before merge: the bare launch path (no role, no prompt) doesn't persist .builder-kimi-session, so pacing resolution falls back to workspace config and an override-spawned bare Kimi builder gets Claude's Enter timing — the swallowed-Enter bug this PR fixes. Persisting a marker on every Kimi launch shape (plus a regression test for the override-spawn case, and softening the arch.md "exists iff Kimi-shaped" claim) closes it. I've added the area/tower label to #1201 for you.

…launch shape

Maintainer review (PR cluesmith#1203): the bare no-role/no-prompt shape never wrote
.builder-kimi-session, so pacing resolution fell back to the config-resolved
harness and an override-spawned bare Kimi builder got claude's 80ms Enter —
the swallowed-Enter bug this PR fixes. The bare branch now touches the marker
(empty; preserves a previously seeded id; seed `! -s` guard and resume
empty-id fallthrough unaffected), so every Kimi launch shape persists it.

Regression tests: bare-script marker pins at the harness and spawn-worktree
layers (both fail pre-fix; the spawn-worktree one previously asserted the
marker's ABSENCE), plus a real-fs pacing test pinning the probe as
existence-based (empty marker beats claude config). arch.md and the
message-pacing.ts header now carry the softened, accurate claim: every launch
shape persists the marker; presence is a breadcrumb, not an iff.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mohidmakhdoomi

mohidmakhdoomi commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator Author

Architect Response — maintainer review addressed (2abd362)

Thank you for the review — the finding is real and is fixed in 2abd362.

The gap, confirmed: KIMI_HARNESS.buildBuilderLaunchScript's bare branch (no role, no prompt — the --builder-cmd kimi override-spawn / afx spawn --worktree shape) returned a plain TUI loop that never wrote .builder-kimi-session. Seed and resume shapes both persist it, so pacing's marker-first probe held for them — but a bare builder fell through to the config-resolved harness and, in a claude-configured workspace, got the 80ms Enter that Kimi's paste detection swallows.

Fix: the bare branch now touches the marker before the loop — empty, since there is no session id to pin. touch preserves a previously seeded id; an empty file keeps the seed ! -s guard and buildResume's empty-id fallthrough intact, so no other shape's semantics move.

Regression tests (all fail pre-fix where noted):

  • spawn-worktree.test.ts — the override-spawn pin you asked for; notably this test previously asserted the marker's absence in the bare script, which is how the gap survived review. Flipped and failing pre-fix.
  • harness.test.ts — same pin at the provider-script layer; failing pre-fix.
  • message-pacing.test.ts — real-fs test: an empty marker (exactly what touch produces) must resolve Kimi pacing over a claude workspace config. Passes pre-fix by design — it pins the probe as existence-based so a future content-based "improvement" can't silently re-open the bare shape.

Docs: arch.md's pacing paragraph and the message-pacing.ts header now carry the softened claim — every Kimi launch shape persists the marker (existence-based probe), and the converse is explicitly not claimed: a leftover marker is a launch breadcrumb, not proof of a live Kimi session (staleness cost: a ~1s-slower Enter). The literal "exists iff Kimi-shaped" phrasing traced to the builder's thread journal; corrected there via an appended dated entry rather than rewriting history.

Suites: harness 75/75, spawn-worktree 86/86, message-pacing 8/8, seed-kick 14/14; package build clean.


Architect integration review

CMAP results (iterated to convergence)

The maintainer-response change set was reviewed by a 3-way CMAP loop (gemini, codex, claude), re-run after every change until a full pass returned zero findings: iteration 1 reviewed the fix (2abd362), iteration 2 the resulting test-hardening commit (642b172), and iteration 3 the entire cumulative response diff (47d12ba..1de55e1) as a holistic convergence check.

Model Verdict Findings across iterations
gemini APPROVE ×3 Iter 2 NIT (accepted): the pre-existing exit 1-before-loop assertion lacked a toContain('exit 1') guard — removing exit 1 would vacuous-pass (indexOf → -1, and -1 < any positive index) → fixed in 1de55e1. Iters 1 and 3: none.
codex APPROVE ×3 Iter 1 MINOR (accepted): tests pinned the touch's presence but not that it stays before the while true loop → fixed in 642b172 (ordering assertions at both test layers). Iters 2 and 3: none.
claude APPROVE ×3 Iter 1 NIT (thread-log phrasing) — explicitly no-action. Iter 2 NIT (accepted): the new ordering assertions lacked toContain('while true') — sound, but opaque failure diagnostic → fixed in 1de55e1; every ordering assertion now guards both substrings. Iter 3: none — additionally verified the no-race property (the touch completes before Tower can deliver a first send) and that the marker claims in the KIMI_SESSION_FILE JSDoc, message-pacing.ts header, and arch.md are mutually consistent.

Loop terminated at iteration 3: unanimous APPROVE, zero findings, on the full change set. Suites green throughout (harness 75, spawn-worktree 86, message-pacing 8, seed-kick 14); package build clean.

mohidmakhdoomi and others added 3 commits July 22, 2026 21:27
…pe marker tests

Post-fix CMAP on 2abd362: gemini APPROVE, claude APPROVE, codex APPROVE
with one MINOR — the regression tests asserted `touch .builder-kimi-session`
appears in the generated script but not that it precedes the restart loop,
so a refactor moving it after/inside the loop would keep them green while
racing the first send. Both script-shape pins now assert the ordering,
matching the suite's existing exit-1-before-loop precedent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rtions (CMAP iter 2)

CMAP iteration 2 on 642b172: 3x APPROVE with two complementary NITs, both
accepted: the pre-existing exit-1-before-loop assertion lacked a
toContain('exit 1') guard (removing exit 1 would vacuous-pass — indexOf
returns -1, and -1 < anything), and the new touch-before-loop assertions
lacked toContain('while true') (sound — a positive index is never < -1 —
but the failure diagnostic would be opaque). Every ordering assertion in the
kimi script-shape tests now guards both of its substrings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, zero findings)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolve the tower client re-export conflict by retaining the Husk types from main alongside SeedKickRequest from PIR cluesmith#1201.

Co-Authored-By: Claude <noreply@anthropic.com>
@waleedkadous

Copy link
Copy Markdown
Contributor

Heads-up for your next iteration: PR #1244 (in review) changes the builder launch-loop contract — a deliberate agent exit (exit 0, no signal; see isDeliberateExit() in shellper-protocol.ts) now ends the loop with a keypress-gated relaunch instead of auto-respawning. The Kimi provider-owned loops in this PR replicate the old blind while true shape, so once #1244 lands, please adopt the shared LAUNCH_LOOP_TAIL (or its exit-code branch) in the Kimi script paths too — happy to point at the exact spots if useful.

mohidmakhdoomi and others added 3 commits July 25, 2026 14:01
# Conflicts:
#	packages/codev/src/agent-farm/commands/spawn-worktree.ts
…n Kimi provider-owned loops

Deliberate exit 0 now gates relaunch on a keypress instead of blind
auto-respawn, matching the post-cluesmith#1244 builder launch-loop contract.
LAUNCH_LOOP_TAIL moves to utils/harness.ts (exported) so provider-owned
scripts share it without a circular import; tests pin the tail across
all Kimi launch shapes (fresh/resume/bare, seeded/bare interactive).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…il adoption

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mohidmakhdoomi

Copy link
Copy Markdown
Collaborator Author

#1244 launch-loop contract adopted (bfc8d62)

Per the heads-up above: merged origin/main (brings PR #1244) and adopted the shared LAUNCH_LOOP_TAIL in the Kimi provider-owned launch scripts.

Change: LAUNCH_LOOP_TAIL was module-local to spawn-worktree.ts; since spawn-worktree.ts already imports from utils/harness.ts, the constant moved there (exported, JSDoc noting why it lives in the harness module) — no circular import, and both Kimi loops (the pinned -S loop shared by the fresh/resume shapes, and the bare TUI loop) now interpolate it. A deliberate exit 0 gates the relaunch on a keypress; nonzero/signal exits keep the auto-restart.

Tests: new it.each pins in harness.test.ts (fresh/resume/bare provider shapes) and spawn-worktree.test.ts (seeded + bare-override worktree shapes) assert status=$?, the exit-0 branch, the keypress gate, and the untouched crash path. Suites: harness + spawn-worktree 169/169, message-pacing + seed-kick 22/22, full package suite 3802 passed / 48 skipped; build clean.

Live verification (kimi 0.29.1, tmux PTY, the actual bare launch script generated from built dist):

  • /quit in the TUI → exit 0 → Agent exited at your request. Press Enter to relaunch… — gate held with no respawn (observed >10s); pressing Enter relaunched the TUI via continue.
  • SIGKILL on the kimi-code process → Agent exited (code 137). Restarting in 2 seconds… → auto-restarted, TUI back up.

CMAP results (gemini, codex, claude — parallel review of the merge-resolution + adoption change set; clean in one iteration):

Model Verdict Findings
gemini APPROVE none — confirmed status=$? placement, read -r || exit 0 EOF semantics, all three Kimi shapes converted, no import cycle
codex APPROVE none — no remaining blind-restart Kimi loop; test coverage adequate across generic + provider shapes
claude APPROVE none — grep-verified the only sleep 2/restart echo in production code is inside LAUNCH_LOOP_TAIL's crash branch, across all 8 interpolation sites; template-literal $status/$SID interpolation safe

Ready for merge whenever you are.

# Conflicts:
#	codev/resources/commands/agent-farm.md
@waleedkadous

Copy link
Copy Markdown
Contributor

Heads-up on the exit-handling contract I asked this PR to adopt — it just evolved, and I want you building against the current state rather than the deprecated one.

Practical upshot for this PR: mirror the current builder bash-loop behavior (Enter-gated relaunch on clean exit, auto-restart on crash) rather than the architect-side contract, and keep an eye on #1267 — when it lands, Kimi loops should move with it. Happy to review again whenever you've iterated!

@mohidmakhdoomi

mohidmakhdoomi commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator Author

Will wait for #1267 to be completed before updating this PR. Also once #1330 is merged to main, will need to update this PR to have Kimi work with the new afx send mailbox delivery.

@mohidmakhdoomi
mohidmakhdoomi marked this pull request as draft August 1, 2026 01:48
@mohidmakhdoomi

This comment was marked as outdated.

@mohidmakhdoomi

This comment was marked as outdated.

@mohidmakhdoomi

This comment was marked as outdated.

mohidmakhdoomi and others added 5 commits August 8, 2026 22:29
… top

[PIR cluesmith#1201] The branch had been parked on two upstream blockers and went 900+
commits stale. Merging up brings in three changes that the Kimi integration was
built against the absence of, so the feature is reworked rather than replayed:

- Spec 1313 (cluesmith#1330) made `afx send` mailbox-first: messages persist first and
  deliver only onto a render-gate-verified empty prompt. Message writers never
  write a PTY directly. This retires the whole seed-kick mechanism.
- cluesmith#1317/cluesmith#1267 made a builder's clean-exit relaunch run the harness fresh.
- cluesmith#1356 (PIR cluesmith#1233) gave crash restarts a session-aware resume contract.

Conflict resolution: took main's rewritten spawn-worktree.ts, tower-routes.ts,
tower-cron.ts, tower-client.ts and discover-resume-session.test.ts wholesale —
our versions were the retired SendBuffer / direct-PTY-write paths and a launch
loop that cluesmith#1233/cluesmith#1317 superseded — then re-applied the Kimi seams by hand.
doctor.ts and the three docs were hand-merged.

The design pivot itself (validated live against real kimi 0.34.0 before any of
it was committed):

- Role now rides `--agent-file` (kimi 0.31.0+), composed around `${base_prompt}`
  so it EXTENDS kimi's own system prompt instead of replacing it. Previously the
  role rode a user turn via a `kimi -p` seed session.
- Task now rides the Spec 1313 mailbox and is delivered by the render gate onto
  a verified-empty composer, never a direct PTY write.
- Crash restarts resume with the documented cwd-scoped `kimi -c`, gated on an
  inlined store probe. This guard is load-bearing: `kimi -c` does NOT fail when
  there is nothing to continue — it starts a fresh session that never saw
  `--agent-file`, i.e. a silently ROLELESS builder (cluesmith#929 hazard class). The
  probe fails closed to a role-carrying fresh launch, and tests EXECUTE it
  against fixture stores and cross-check it against findLatestKimiSessionId so
  the hand-written snippet cannot drift from the TypeScript it mirrors.
- Deleted seed-kick.ts, the sentinel, the seed bootstrap, .builder-seed.txt, the
  ack-and-wait BEGIN discipline, and the now-dead SeedKickRequest SDK surface.

Pacing was left wired to nothing by the merge (Spec 1313 replaced the routes
message-pacing.ts hooked into), which would have meant every `afx send` to a
Kimi builder was typed but never submitted. It is re-homed onto the mailbox
delivery path (resolvePacingForSession in mailbox-wiring.ts), and both
message-pacing.ts and the `.builder-kimi` marker are deleted: the harness now
comes out of the generated .builder-start.sh, which is generated FROM the
resolved harness and so cannot be forgotten — the marker's coverage obligation
is exactly what the maintainer's earlier review finding was about.

Shared-code edit, flagged for review: render-gate.ts's marker exemption now
follows the profile's matched span instead of column 0, because kimi's marker
sits at column 3 inside a rounded box. Carries before/after pinning tests for
claude/codex/agy plus three real 0.34.0 gate captures as fixtures.

Also: store-drift fix (workDir->cwd, ISO->epoch-ms, lastPrompt gone, with v1
back-compat), a trust-record drift probe that validates our undocumented
derivation against kimi's own records, version floor 0.27.0 -> 0.33.0, and the
correction that kimi DOES have a blocking PreToolUse hook seam (so cluesmith#1018
write-guard parity is achievable follow-up, not impossible).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The render gate delivers a message only onto a composer it can prove empty.
For kimi it could not: KIMI_MARKER matches `│ >`, findMarkerRow takes the LAST
match, and the scan started at that row — so a draft whose final line begins
with `>` (a pasted quote, a markdown blockquote) put the marker on the
CONTINUATION row and left the real text above the scanned region. The composer
classified clean while holding unsent input, and a queued message would have
been typed on top of it: the exact corruption Spec 1313 removes by construction.

Found by the 3-way review, which reproduced it on a constructed screen and
flagged that it could not confirm kimi's real multi-row geometry. Measured
against real kimi 0.34.0 — it renders exactly that shape, so the defect is
reachable, not theoretical.

GateProfile gains an optional regionStartPatterns: an upper bound for the
composer region, which kimi sets to the box top. The bound is EXCLUSIVE,
mirroring the region end — the box-top row's right corner `╮` is not an
ignorable glyph, so including that row counted it as user text and held every
idle composer forever (caught by the fixture suite on the first attempt).
Profiles that declare no region start keep scanning from the marker row exactly
as before, and since no row below a last match can match, claude/codex/agy
cannot reach any of the new behavior.

The other input the review proposed — a marker-matching row below the composer
in a second box — is NOT reachable in the shipped UI: measured, kimi's `/` menu
renders as unclosed `│` rows with no `╰` beneath them, so a marker inside it
yields no-region-end and holds. Captured as a fixture rather than argued.

Four new fixtures, all from live capture: multiline-bare (the false CLEAN
itself), multiline, menu, picker — the last two also answering the review's
point that kimi shipped 3 fixtures where claude/codex ship menu and picker.
…inues

The inlined `node -e` store probe and findLatestKimiSessionId are the same
question in two languages, and the cross-check test compared them against each
other — agreement between duplicated omissions, not validation against kimi's
continuation semantics. Two reviewers broke it from opposite directions:

- DANGEROUS: an `archived: true` session matched on cwd alone, so the probe
  authorized `-c`. Kimi excludes archived sessions from the listing `-c`
  continues from, so it starts a FRESH session instead — one that never saw
  --agent-file, i.e. a silently roleless builder. That is the cluesmith#929 hazard class
  this guard exists to prevent, reached through the guard itself.
- SAFE BUT HARMFUL: readdirSync on a stray non-directory threw ENOTDIR into the
  single OUTER try, aborting the whole scan. One .DS_Store in the store disabled
  resume machine-wide, permanently and silently — a builder that crashed four
  hours in would restart with no context and its task re-queued. Same for a
  symlinked worktree and a trailing slash on the recorded cwd.

Both implementations now share one resumability predicate — archived !== true
and a `session_`-prefixed id, the filters kimi's own listing applies — plus
sameDir's realpath tolerance, and each directory level gets its own try. Both
filters err toward "not resumable", whose fallback is the role-carrying fresh
launch. The predicate is deliberately NOT folded into iterateSessionDirs:
inspectKimiStoreLayout must keep seeing unrecognized ids, because reporting that
drift is its whole job.

Every listed case is now a test asserting BOTH implementations.

Also in the generated launch script:

- Shell metacharacters. All three reviewers flagged the recovery hints, which
  interpolated builderId/taskFile into double-quoted bash echoes where bash
  re-scans them, so `$(…)` in a builder id executed when the hint printed. Every
  value now enters the script once as a single-quoted escaped assignment and is
  used through the shell variable; hints print via printf on the expansion,
  which bash does not re-scan. Pinned by running the generated function with a
  metacharacter id and asserting nothing executed.
- Unbounded re-queueing. codev_launch_fresh queues the task, so a kimi dying
  before it minted a session re-queued the same mission every ~2s. The mailbox
  PERSISTS a held row, so one enqueue is enough; the guard resets only on the
  human-gated clean-exit relaunch, which is a deliberate new conversation and
  does want its task again.

And in the doctor probes: both reported ok if ANY record matched, so after a
store migration the pre-migration records hide every new one and the probe
reports healthy through exactly the rename it exists to catch. Drift is now
reported only when the newest non-conforming record is strictly newer than every
conforming one; ties stay ok so the verdict never depends on directory
iteration order. verifyKimi no longer reports "config issues" when spawnSync
returns status null (spawn failure or timeout), which accused a healthy install
on a slow machine, and two user-facing strings that still described the retired
seed-session bootstrap now describe --agent-file.
arch.md's Kimi section gains the facts this round established: the composer
region's upper bound and why it is exclusive, the resumability filters and the
probe divergences they close, the recency rule in both drift probes, and the
single-quoted-assignment discipline in the generated script.

The demo's two failing steps were the DEMO's fault, not the product's. Its role
told the model to prefix every reply with a token and asserted on the prefix —
which measures whether K3 honors a persistent output-format constraint, not
whether the role was injected. Measured: it answered the task correctly while
dropping the prefix, and when asked about its prefix it discussed the idea
rather than emitting the token. The live --agent-file probe, run against a
production-identical agent file, passed 7/7 including role survival across
`kimi -c`. The demo now asks for a codeword — the same oracle the probe uses —
with a comment saying why, so the weaker one does not come back.

The measurement harness gains the screens the review said a happy-path run never
produces: a multi-line draft, the same draft ending in a bare `>`, the `/` menu,
and the `@` file picker. Those captures are what settled which of the two
proposed false-CLEAN inputs was real.

Commits the three probe scripts that arch.md and the profiles cite as evidence;
they were untracked, so the "measured, see harness X" chain would have dangled
after merge.
@mohidmakhdoomi

This comment was marked as outdated.

Architect review finding 1. Enter a newline then `>` and kimi renders `│ > ` /
`│   >`: row one empty, row two matching the marker so its `>` is span-exempted.
Every cell is whitespace, box chrome, or an exempted marker, so userCells is 0 and
the composer reads CLEAN while holding unsent input — a held message would be typed
on top of the user's draft. Bounding the region correctly does not help; the draft
is real but literally uncountable.

So hold on the region's SHAPE instead: a boxed composer taller than one interior
row is a multi-line draft. That is only sound if box growth is exclusive to
multi-line drafts, which is a claim about kimi, not about our code — so it was
measured on real 0.34.0 first (pir-1201-kimi-box-growth.mjs). Idle, single-line
draft, `/` menu, `@` picker and the post-reply steady state all hold at one interior
row; only the newline drafts grow. The steady state is load-bearing: growth there
would hold every later message forever, a liveness bug rather than a fail-safe one.
The working states were measured too (pir-1201-kimi-working-states.mjs) — mid-
generation, mode chrome, and a draft typed while the agent works are all one row, so
"deliver while busy" does not silently become "hold until idle".

Placed AFTER the cell scan, not before it, so the count keeps its ground-truth role:
a text-bearing multi-row draft still reports `user-text` and every pre-existing
fixture verdict is unchanged.

Armed by a dedicated `growsWithDraft` profile field rather than by
`regionStartPatterns` (CMAP: codex #1, claude Q5). The two are unrelated properties
that merely coincide for kimi, and the hazard is concrete: the shipped
codex-idle.clean.txt capture — a real, genuinely EMPTY composer — already spans two
interior rows, so arming on the scan bound would have killed codex delivery the day
anyone declared a region start for it. The rule now needs both opt-ins, and the
inertness tests run on that real capture under four profile variants.

Also: `isClassifierStuck` enumerated details as a closed || chain, so widening the
union never forced a decision — now a Record keyed by the union, making the next new
detail a compile error rather than a silent false (claude F2).

Full suite 4906 passed / 48 skipped / 0 failed.
Architect review findings 2 and 3, both message/comment only — no behavior change.

The fast-fail branch echoed "Starting a fresh conversation with the original task",
but it does not reset codev_task_queued, so codev_queue_task early-returns and
nothing is re-queued. That behavior is correct — an undelivered row persists on the
mailbox and re-queueing would duplicate it — the message was simply wrong, and in
the delivered-then-crash-looping case it would tell an operator the fresh session
has its mission when it does not.

The first rewording then overcorrected: it asserted unconditionally that a task was
still queued, which is false when `afx send` never succeeded (afx off PATH, Tower
down), since the flag is only set on success and the fresh launch really does retry
in that case (CMAP: codex cluesmith#2, claude F4). The hint now branches on the flag and is
accurate in both cases.

Finding 3 records the accepted tradeoff in the other direction: the clean-exit
branch does reset the flag, so a row that was never delivered gets queued twice and
the mission arrives stated twice. Documented rather than fixed — de-duplicating
needs either a delivery receipt the script cannot see or a mailbox-side identity
check, and a duplicated instruction to an agent that has not started yet is
recoverable by reading, unlike the crash-loop direction. Softened "delivered
whenever the operator saw a composer": seeing one is necessary, not sufficient — the
gate also has to have polled it empty, so a quit-before-delivery race leaves the row
held too (CMAP: codex cluesmith#3, claude F5).
…ositions

Builder thread plus the full disposition record for this round: gemini APPROVE,
codex REQUEST_CHANGES, claude APPROVE-with-changes, every finding from both
non-approving reviews accepted and none rejected.

Notes the two deviations worth knowing about — the geometry rule moved after the
cell scan rather than short-circuiting before it, and arming decoupled from
regionStartPatterns onto its own field after codex-idle.clean.txt turned out to
already satisfy the geometric predicate while being genuinely empty.
Architect finding 4. cluesmith#1267's contract is "clean exit -> fresh rerun, no recovery",
and main's claude loop enforces it BY IDENTITY: a clean exit mints a new session id
and the superseded one is never named again. kimi cannot mint on demand and `-c` is
cwd-scoped, so identity was never pinned — the guard only asked whether ANY session
existed for this cwd. 0.33+ mints no session until the first message lands, so a
crash between a clean-exit relaunch and the first delivery found the just-ended
conversation still the newest, continued it, and delivered the re-queued task into
the conversation the human walked away from — cluesmith#1267's own motivating defect class.

The probe now answers WHICH session rather than WHETHER one exists: it prints the
newest resumable id for the cwd. The clean-exit branch records that id, and the
crash branch takes `-c` only once the newest id differs. Boolean uses derive from
"printed something", so there is still one probe and one mirror.

Measured before building, since the design assumes `-c` targets the newest session
and the existing probe only covered the zero-session case: two live sessions in one
cwd on 0.34.0, two oracles — content (codewords ALPHA/BRAVO -> BRAVO) and store
identity (only the newest session's dir was touched, nothing new minted, exit 0, no
prompt). pir-1201-kimi-continue-newest-probe.mjs.

CMAP found a defect this change INTRODUCED (claude F1, codex cluesmith#2, blocking): moving
the decision from $? onto stdout meant anything else writing to stdout counted as a
session. Measured with NODE_OPTIONS=--require preloading a module that prints — the
probe exits 1, the script read RESUME, and `kimi -c` with nothing to continue starts
a session that never saw --agent-file. Silently roleless, the cluesmith#929 class, produced
by the guard's own upgrade. Now consumes both signals.

The sketch's "empty on any error is fail-closed" was also wrong (claude F2, codex
#1): a TRANSIENT probe failure records '' and the next crash sees the ended session
as different-from-empty. Failure and empty store are now told apart by status, and
an unknown baseline blocks resume until the next clean exit re-establishes one.

Two probe/discovery divergences fixed rather than documented: `j.cwd ?? j.workDir`
short-circuited where readStateJson falls through per-field, and the probe stripped
a trailing slash before realpathSync while sameDir does not (the unsafe direction —
realpathSync already normalizes one for any directory that exists, so the strip
bought nothing).

Tests: the composition is now driven for real — the actual `while` loop with stubbed
launches, asserting resume,fresh,fresh — because injecting the superseded id from
the test left the generated assignment pinned only by a string match. Non-vacuity is
demonstrated by running the pre-fix predicate against the same store.

Full suite 4915 passed / 48 skipped / 0 failed.
…spositions

arch.md's kimi crash-resume section described an existence guard; it now describes
the identity one, the measured fact it rests on (`kimi -c` continues the newest
session for a cwd), and the two accepted residuals — the in-memory superseded id
(contract parity with claude's per-process minted id) and a store GC that evicted
newest-first.

Plus the builder thread and the full CMAP disposition record: gemini APPROVE, codex
and claude REQUEST_CHANGES, every finding accepted, including the blocking one that
this round's own change introduced.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support Kimi Code CLI as a builder

2 participants