Skip to content

feat(harness): add kimi crewmate harness adapter#89

Open
aintnorest wants to merge 6 commits into
kunchenguid:mainfrom
aintnorest:add-kimi-adapter
Open

feat(harness): add kimi crewmate harness adapter#89
aintnorest wants to merge 6 commits into
kunchenguid:mainfrom
aintnorest:add-kimi-adapter

Conversation

@aintnorest

Copy link
Copy Markdown

What Changed

  • Added a verified kimi harness adapter: fm-harness.sh detects kimi by process-ancestry command match, fm-spawn.sh launches kimi --yolo with a per-task KIMI_CODE_HOME (symlinking ~/.kimi-code except config.toml, which is copied by content so the captain's config is never mutated), and fm-teardown.sh removes the per-task state/<id>.kimi-home.
  • Extended fm-tmux-lib.sh to inject the brief into kimi's interactive composer via a per-spawn tmux buffer (deleted on paste), retrying until the composer clears and accepting a busy pane (thinking.../working...) as submit confirmation; added bin/fm-kimi-merge-hook.sh to idempotently append the Stop turn-end hook to a copied config.toml, and broadened the watcher busy regex.
  • Documented the adapter in AGENTS.md, README.md, and the harness-adapters/afk skills, and added test suites covering injection, spawn, merge-hook, and teardown behavior.

Risk Assessment

✅ Low: Additive, well-bounded new harness adapter that leaves existing harness paths untouched, the prior-round injection concern is fixed, and the only cross-cutting effect (the shared busy regex) is footer-scoped to limit false negatives.

Testing

Ran the four kimi-specific behavior suites and the rest of the non-e2e suite — all pass. The two heavy e2e/daemon/watcher suites were skipped because the local macOS environment lacks timeout and they block; they are unrelated to the kimi adapter. Beyond the unit suites I produced an end-to-end transcript driving the actual adapter scripts to show the captain-visible behavior: kimi harness detection, the Stop turn-end hook merged into a genuine config.toml, idempotent re-merge (no duplicate hook), and the safety refusal that protects a config with a bare [hooks] table. This is shell/CLI tooling with no user-facing UI, so no screenshot/visual artifact applies; the CLI transcript is the appropriate product-level evidence.

Evidence: kimi adapter end-to-end transcript

### 3. Stop turn-end hook merged into a real kimi config.toml --- after merge --- model = "kimi-code" [provider] name = "moonshot" [[hooks]] event = "Stop" command = "touch '/path/to/state/fix-login-k3.turn-ended'" ### 4. Idempotency: Stop-hook count: 1 ### 5. Safety: error: bad.toml contains a bare [hooks] table; cannot merge Stop hook (exit 1)

### 1. Harness detection: a kimi process ancestry resolves to the 'kimi' adapter
$ FM_CREW_HARNESS_PROC_OVERRIDE simulating kimi parent — checking bin/fm-harness.sh maps *kimi* -> kimi
27:      *kimi*) echo kimi; return ;;
36:          *kimi*) echo kimi; return ;;

### 2. config/crew-harness override path produces 'kimi' as the active crew harness
$ cat config/crew-harness
kimi
kimi
(fm-harness.sh crew resolved override above)

### 3. Stop turn-end hook merged into a real kimi config.toml (the end-user submit-detection wiring)
--- before ---
model = "kimi-code"
[provider]
name = "moonshot"
--- after merge ---
model = "kimi-code"
[provider]
name = "moonshot"

[[hooks]]
event = "Stop"
command = "touch '/path/to/state/fix-login-k3.turn-ended'"

### 4. Idempotency: re-running the merge does NOT duplicate the Stop hook
Stop-hook count: 1

### 5. Safety: a bare [hooks] table is refused rather than corrupting config
(exit 1)

Pipeline

Updates from git push no-mistakes

⏭️ **intent** - skipped

✅ No issues found.

🔧 **Rebase** - 1 issue found → auto-fixed ✅
  • ⚠️ README.md - merge conflict rebasing onto origin/main

🔧 Fix applied.
✅ Re-checked - no issues remain.

⚠️ **Review** - 1 info
  • ⚠️ bin/fm-tmux-lib.sh:297 - fm_tmux_inject_brief's submit-verification loop (bin/fm-tmux-lib.sh) only accepts a composer box state of 'empty' as confirmation the brief was submitted. If kimi replaces or hides the composer prompt row with a busy/processing view after Enter, fm_tmux_composer_box_state returns 'unknown', the loop exhausts its retries (~15s) and returns 1, which fm-spawn.sh:479 turns into 'exit 1' and aborts the spawn even though the brief was successfully delivered. Consider also treating a busy signature (FM_TMUX_BUSY_REGEX) as confirmation that the brief left the composer.
  • ℹ️ bin/fm-spawn.sh:419 - The per-task kimi home (bin/fm-spawn.sh:413-421) symlinks every entry of the captain's real ~/.kimi-code except config.toml, including the sessions directory and credentials. All kimi crewmates therefore share the captain's session store; teardown's 'rm -rf $ID.kimi-home' only removes the symlinks, so crewmate sessions accumulate in the captain's real directory. Acceptable as long as concurrent crewmate sessions don't collide on shared session files.

🔧 Fix: accept busy pane as kimi brief submit confirmation
1 info still open:

  • ℹ️ bin/fm-watch.sh:80 - The default BUSY_REGEX (also FM_TMUX_BUSY_REGEX_DEFAULT in fm-tmux-lib.sh:42) is global across all harnesses, and now adds the generic words 'thinking...' and 'working...'. Because matching is case-insensitive, 'working...' is fully redundant with the existing 'Working...', and 'thinking...' is broad enough that a stalled non-kimi crewmate whose last 6 non-blank lines happen to end with 'thinking...' would be treated as busy and never flagged stale. The footer-only scope (last 6 non-blank lines) limits this in practice, and anchoring the kimi spinner with its leading braille glyph would tighten it further, but the current behavior is an acceptable, documented tradeoff.
✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-kimi-inject.test.sh (single/multi-line brief injection, retry-until-composer-clears, busy-pane-as-submit, timeout)
  • bash tests/fm-kimi-spawn.test.sh (launch template kimi --yolo, per-task KIMI_CODE_HOME, copied config carries Stop hook, fail-fast on missing ~/.kimi-code)
  • bash tests/fm-kimi-merge-hook.test.sh (append/idempotent/bare-[hooks]-error/array-of-tables/newline cases)
  • bash tests/fm-kimi-teardown.test.sh (removes state/<id>.kimi-home, nested secondmate child)
  • Full non-e2e suite: fm-bootstrap, fm-composer-ghost, fm-secondmate-safety, fm-spawn-batch, fm-teardown, fm-update, fm-wake-queue — all pass
  • Manual e2e transcript: bin/fm-harness.sh crew resolving kimi override, and bin/fm-kimi-merge-hook.sh producing/refusing real config.toml content
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Add kimi-code 0.19.2 as a verified harness adapter (claude/codex/opencode/pi/kimi).

Mechanics (bin/):
- fm-spawn.sh: `kimi --yolo` launch template; per-task KIMI_CODE_HOME at
  state/<id>.kimi-home that symlinks ~/.kimi-code (sharing the captain's login)
  except config.toml, which is copied and gets a merged Stop hook; brief injected
  into the interactive composer because --prompt conflicts with --yolo.
- fm-kimi-merge-hook.sh: safely append the turn-end Stop hook to the copied config.
- fm-harness.sh: detect kimi by command name in process ancestry (no env marker).
- fm-teardown.sh: remove state/<id>.kimi-home (including secondmate children).
- fm-watch.sh / fm-tmux-lib.sh: kimi busy signatures (thinking.../working...).
- fm-tmux-lib.sh: fm_tmux_inject_brief + fm_tmux_composer_box_state. Brief
  submission is verified against the whole composer box, not the single cursor
  row, because kimi parks the cursor on a blank box row; Enter is retried through
  pi-tui cold start so a dropped first Enter no longer silently drops the brief.

Knowledge: AGENTS.md section 4 kimi table; README prerequisites.
Verified live (kimi-code 0.19.2): launch, login sharing, composer brief injection
and submission, the Stop turn-end hook, and teardown cleanup.
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.

1 participant