Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .agents/skills/harness-adapters/references/harness/kiro.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Verified for all four kinds: `../../../../../bin/fm-spawn.sh` accepts a `--secon
| Launch | V3 (default): `chat --v3 -a --agent firstmate-kiro-<id> "<brief>"`, with the resolved absolute binary, per-task `KIRO_HOME`/`KIRO_DATA_DIR`/`KIRO_CHAT_LOG_FILE`, and task-specific project agent and hook files copied from that worker's `KIRO_HOME`; explicit `FM_KIRO_ENGINE=v2` falls back to `chat --v2 --trust-all-tools --agent firstmate-kiro-v2-<id>` with an embedded-hook agent inside `KIRO_HOME`. The trust flag is part of the correct launch on both engines (see "Turn end and trust"). The brief is a single firstmate-encoded positional query that auto-submits with no extra Enter and stays steerable (verified live on 2026-09-20: an 18 KB multi-line brief ran its first turn, the pane stayed alive on the idle `ask a question or describe a task` prompt, and a later typed message completed a second turn). The INTERACTIVE steerable TUI is used, not the `--no-interactive` one-shot, because every kind must stay alive to be steered and to drive `/no-mistakes` across turns. |
| Busy state | Semantic hooks are the writer: `UserPromptSubmit` opens `busy kiro-hook`, `PreToolUse`/`PostToolUse` publish native progress, and `Stop` closes idle, all generation-bound through `../../../../../bin/fm-busy-event.sh` so a late hook from an older incarnation is rejected; the `kiro-regex` rendered-tail fallback in `../../../../../bin/fm-busy-lib.sh` remains for a pane whose generation is absent. |
| Rendered tail | Busy shows the composer footer `Thinking... (esc to cancel)` beside a braille spinner and the status line `Kiro is working` (`Kiro is working · Type to steer · Ctrl+S to queue`); idle shows the `ask a question or describe a task` placeholder and neither anchor. Both anchors are ASCII that survive `KIRO_ASCII_MODE=1`; the spinner is locale-sensitive and is not a signal. |
| Turn end | A first-class `stop` agent hook, kiro-cli's authoritative turn-end signal, firing at every turn end in both interactive and headless modes and carrying `KIRO_SESSION_ID` - the same Stop-hook shape as claude and cursor. The tracked `../../../../../bin/fm-kiro-turnend-hook.sh` serves BOTH the crewmate/primary turn-ended touch and the primary supervision re-arm backstop; see "Turn end and trust" below. |
| Turn end | A first-class `stop` agent hook, kiro-cli's authoritative turn-end signal, firing at every turn end in both interactive and headless modes and carrying `KIRO_SESSION_ID` - the same Stop-hook shape as claude and cursor. The tracked `../../../../../bin/fm-kiro-turnend-hook.sh` serves BOTH the crewmate/primary turn-ended touch and, for a primary, keeping the doorbell owner (`../../../../../bin/fm-primary-doorbell.sh`) running; see "Turn end and trust" below. |
| Exit | `/quit`, one Enter (or `/exit`); both quit with save. `fm-control exit` types `/quit` only after the shared composer classifier reads the pane empty: kiro-cli's idle placeholder renders in a grey (RGB ~158) above the fleet ghost-strip ceiling, so `../../../../../bin/fm-composer-lib.sh` treats the exact `ask a question or describe a task` text as empty only under the recorded `kiro-cli` identity, and it treats the helper row below the composer as furniture in both of its shapes (V3's `/sessions to resume · /copy to clipboard` and the bare `/copy to clipboard` of an agent launched before the V3 wiring). |
| Interrupt | Single `Escape` cancels the streaming turn and leaves the session alive (the busy footer reads `esc to cancel`); no clear key follows. A single `Ctrl+C` does NOT quit the new default TUI - `Ctrl+C` twice quits - so never rely on one `Ctrl+C` to abort a turn. |
| Skill | `/<skill>`, for example `/no-mistakes`; kiro-cli discovers project skills only in `.kiro/skills`, so Firstmate's agent-only skills reach it through the tracked `.kiro/skills -> ../.agents/skills` symlink (the same shape as `.claude/skills`), beside the user skills under `~/.kiro/skills`. |
Expand All @@ -25,7 +25,7 @@ Verified for all four kinds: `../../../../../bin/fm-spawn.sh` accepts a `--secon

## Turn end and trust

kiro-cli declares lifecycle hooks in an agent's JSON config, and the `stop` hook fires at the end of every turn in both interactive and headless modes, delivering `{hook_event_name:"stop", cwd, session_id, assistant_response}` on the hook script's STDIN with `KIRO_SESSION_ID` in the environment.
kiro-cli declares lifecycle hooks in an agent's JSON config, and the `stop` hook fires at the end of every turn in both interactive and headless modes, delivering `{hook_event_name:"stop", cwd, session_id}` on the hook script's STDIN with `KIRO_SESSION_ID` in the environment; kiro-cli 2.24.1 V3 workspace `Stop` hooks carry no `assistant_response` or other reply text, and `UserPromptSubmit` carries the prompt in `prompt` ([verification](../../../../../docs/verification/supervision.md#kiro-cli-2241-hook-surface-and-process-tree-2026-09-29)).
Note that `KIRO_WORKSPACE_ROOT` was UNSET in live sessions; the hook falls back to `$PWD`, which is the launch directory.
Everything Firstmate needs lives inside a per-task `KIRO_HOME` at `state/<id>.kiro-home`, which `../../../../../bin/fm-spawn.sh` rebuilds fresh on every spawn, so generated payloads and the turn-end pointer are not written into the project's own worktree and no captain-shared file is edited:

Expand All @@ -36,14 +36,14 @@ Everything Firstmate needs lives inside a per-task `KIRO_HOME` at `state/<id>.ki
- `.fm-kiro-session` records the generation and Kiro session id of the launched incarnation's latest accepted event; a conversation resumed without `FM_KIRO_BUSY_GEN` writes busy state only under that generation and only for that session id.
- `../../../../../bin/fm-kiro-turnend-hook.sh` is the tracked, shared hook.
It is a guarded no-op for its turn-ended touch: it touches `state/<id>.turn-ended` ONLY when the isolated-home pointer inside the `KIRO_HOME` its command names carries the token of that home's registry entry, with the old workspace pointer accepted only for migration, so a stray kiro-cli session under this `KIRO_HOME` that is not this task changes nothing.
For a PRIMARY or SECONDMATE session the same hook additionally serves as the supervision re-arm backstop, which acts only in primary scope (`../../../../../bin/fm-primary-scope-lib.sh`'s `fm_primary_scope_matches`) for the session that owns that home's lock, so a worker's hook stops after its task updates.
For a PRIMARY or SECONDMATE session the same hook additionally keeps the doorbell owner running (falling back to one plain watcher re-arm when no owner can run), which acts only in primary scope (`../../../../../bin/fm-primary-scope-lib.sh`'s `fm_primary_scope_matches`) for the session that owns that home's lock, so a worker's hook stops after its task updates.

`../../../../../bin/fm-teardown.sh` retires the whole `state/<id>.kiro-home` directory with `rm -rf` and it is rewritten fresh on relaunch, so no shared global registry cleanup is needed; `../../../../../bin/fm-control-lib.sh`'s turn-end auth path is therefore empty for kiro-cli, while its wiring paths list the isolated-home `.fm-kiro-turnend` pointer and the `state/<id>.kiro-turnend-token`.

## Hook commands

Verified on kiro-cli 2.22.1 (KAS 0.66.4) on 2026-09-28: Kiro runs each hook command through `/bin/sh` with the session's workspace root as its working directory and a compact JSON payload on stdin carrying `session_id`, `hook_event_name`, and `cwd` first.
It reloads `.kiro/hooks` files while a session runs, applies each hook's 60-second default timeout, fires `SessionStart` only for a conversation's first prompt, and runs no workspace hooks under `--no-interactive`.
It reloads `.kiro/hooks` files while a session runs, applies each hook's 60-second default timeout (a per-hook `"timeout"` in seconds extends it on 2.24.1), fires `SessionStart` only for a conversation's first prompt, and runs no workspace hooks under `--no-interactive`.
Every Firstmate registration therefore reaches `../../../../../bin/fm-kiro-turnend-hook.sh` without the hook's folder or launcher environment: generated task hooks name it by absolute path with `--kiro-home <per-task KIRO_HOME>`, and the tracked primary hook resolves it from the payload's `cwd`, so a linked task worktree that inherits the tracked file runs its own copy and stays inert.
A resumed primary retakes a lock whose recorded pid is not a live harness on its first `UserPromptSubmit`, and the `Stop` re-arm forks into its own session so it never holds the turn end.
`tests/fm-kiro-harness.test.sh` pins these commands from an unrelated folder with a scrubbed environment, and `FM_KIRO_RESUME_LIVE_E2E=1 bash tests/fm-kiro-resume-live-e2e.test.sh` refreshes the live resume evidence.
Expand All @@ -54,7 +54,7 @@ Detected by ancestry alone: `../../../../../bin/fm-harness.sh` matches the ancho
No environment marker is promoted: the `KIRO_*` family is inheritable environment state, not identity, and kiro-cli does not clear an inherited `CLAUDECODE` - but a structural kiro-cli ancestor outranks that retained marker, which `../../../../../bin/fm-harness.sh` decides without depending on the spawn's own launch-boundary marker clearing.
`fm_kiro_cli_process_matches` in `../../../../../bin/fm-session-lock-lib.sh` is the one owner of those frame names, used by both detection and the session lock, so a session running on kiro-cli can acquire this home's lock.
Both walks climb the same shared ancestry (`FM_ANCESTRY_MAX_HOPS` there), so detection and the lock name the same session from a shallow tool call and from a hook several wrapper shells deep.
Its interactive session is a contiguous four-frame run (`kiro-cli` -> `kiro-cli-chat` -> `bun` -> `kiro-cli-chat`) whose tool subprocesses and `stop` hook both descend from the innermost `acp` frame, so the lock walk reports the whole run and anchors on the stable OUTERMOST `kiro-cli` frame rather than the transient inner engine pid.
Its interactive session on 2.22.1 is a contiguous four-frame run (`kiro-cli` -> `kiro-cli-chat` -> `bun` -> `kiro-cli-chat`) whose tool subprocesses and `stop` hook both descend from the innermost `acp` frame; on 2.24.1 that innermost frame is a `node` running `acp-server.js` in its own session ([verification](../../../../../docs/verification/supervision.md#kiro-cli-2241-hook-surface-and-process-tree-2026-09-29)), so the lock walk reports the whole run and anchors on the stable OUTERMOST `kiro-cli` frame rather than the transient inner engine pid.

## Worker busy state and turn end

Expand All @@ -65,8 +65,8 @@ The authoritative turn-end signal is the `stop` hook above, not this rendered fa
## Primary integration

SUPPORTED: `../../../../../docs/supervision-protocols/kiro-cli.md` owns the wake protocol.
kiro-cli's `Stop` hook fires and is awaited synchronously, but exit 2 is a no-op on `Stop` and neither the hook's stdout nor a `followup_message` is consumed as a continuation (all verified live, kiro-cli 2.22.1), so it cannot block the turn end or force a follow-up and serves only as the watcher re-arm backstop.
The structural path is external: `../../../../../bin/fm-kiro-primary.sh` launches the V3 primary with the tracked `.kiro/agents/firstmate-kiro.json` and `.kiro/hooks/fm-firstmate.json`, the `SessionStart` hook runs the digest into context and publishes the pane as `state/.primary-endpoint`, the `UserPromptSubmit` and `Stop` hooks keep that record current every turn for the lock-owning session (hooks that arrive after a conversation's first prompt never see `SessionStart`), the background watcher rings one constant doorbell line into that pane after each actionable wake, and the `UserPromptSubmit` hook attaches the drained queue as that turn's context (`../../../../../bin/fm-primary-endpoint-lib.sh`); the foreground `../../../../../bin/fm-watch-checkpoint.sh` remains the fallback when no endpoint is published, and `../../../../../docs/turnend-guard.md` records the limitation.
kiro-cli's `Stop` hook fires and is awaited synchronously, but exit 2 and a `followup_message` are no-ops, and the one continuation 2.24.1 honors (a `{"decision":"block"}` reply) fires once with no further `Stop`, so it cannot keep the primary supervised across turns; it only keeps the doorbell owner running.
The structural path is external: `../../../../../bin/fm-kiro-primary.sh` launches the V3 primary with the tracked `.kiro/agents/firstmate-kiro.json` and `.kiro/hooks/fm-firstmate.json`, the `SessionStart` hook runs the digest into context and publishes the pane as `state/.primary-endpoint`, the `UserPromptSubmit` and `Stop` hooks keep that record current every turn for the lock-owning session (hooks that arrive after a conversation's first prompt never see `SessionStart`), the hooks keep one detached doorbell owner running (`../../../../../bin/fm-primary-doorbell.sh`), the only component that rings this pane ([watcher continuity](../../../../../docs/watcher-continuity.md#kiro-doorbell-owner)), and the `UserPromptSubmit` hook attaches the drained queue as that turn's context; the foreground `../../../../../bin/fm-watch-checkpoint.sh` remains the fallback when no endpoint is published, and `../../../../../docs/turnend-guard.md` records the limitation.
ACP: kiro-cli 2.22.1 exposes two client-driven surfaces (`kiro-cli acp`, an Agent Client Protocol agent over stdio taking `--agent`, `--model`, `--effort`, `-a`, and `--agent-engine v3`; and `chat --output-format stream-json`, which emits the run's ACP events as JSON Lines and implies `--no-interactive`), and the V3 TUI itself talks to its engine over an inner `kiro-cli-chat acp` frame.
Firstmate does not drive ACP directly: a stdio ACP agent has no pane to steer, observe, or ring, so it would need a Firstmate-side ACP client, a second permission plane, and a second wake protocol beside the durable queue and doorbell, while `stream-json` is one-shot and cannot be steered across turns; the steerable V3 TUI in a pane therefore stays the verified surface, and its rendering is read through the shared composer and busy classifiers.

Expand Down
5 changes: 2 additions & 3 deletions .agents/skills/operational-home-layout/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ metadata:
AGENTS.md this file (CLAUDE.md is a real @AGENTS.md pointer to it)
CONTRIBUTING.md contributor workflow and repo conventions
README.md public overview and development notes
.github/workflows/ shared CI and PR enforcement, committed
.tasks.toml tracked tasks-axi markdown backend config for the default backlog backend (section 10)
.agents/skills/ firstmate-loaded internal skills, committed; each carries metadata.internal=true for installers
.claude/skills symlink to .agents/skills for claude compatibility
Expand All @@ -26,7 +25,7 @@ config/claude-account config/pi-account optional per-home worker account pin fo
config/crew-dispatch.json optional crewmate dispatch profiles; LOCAL, gitignored; firstmate-maintained but human-editable natural-language rules that choose a per-task harness/model/effort profile (section 4). Inherited by secondmate homes
config/secondmate-harness harness the PRIMARY uses to launch SECONDMATE agents, optionally followed by a model and effort token on the same line ("<harness> [<model>] [<effort>]"; section 4); LOCAL, gitignored; absent or "default" harness falls back to config/crew-harness then firstmate's own. The primary's own setting; NOT inherited into secondmate homes (secondmates do not spawn secondmates)
config/backlog-backend backlog backend override; LOCAL, gitignored; absent or "tasks-axi" = the configured tasks-axi backend, "manual" = force routine backlog updates to hand-editing; inherited by secondmate homes (section 10)
config/backend runtime session-provider backend override for new tasks; LOCAL, gitignored; absent = falls through to runtime auto-detection (the runtime firstmate itself is executing inside), then tmux; tmux is the verified reference backend (docs/tmux-backend.md), herdr has its own required CI lane (docs/herdr-backend.md), while zellij, orca, and cmux remain experimental with no dedicated real-backend CI lane (docs/zellij-backend.md, docs/orca-backend.md, docs/cmux-backend.md) - herdr and cmux can also be selected by runtime auto-detection, zellij and orca never are (always explicit), and codex-app is not accepted; see docs/codex-app-backend.md; inherited by secondmate homes under the primary-authoritative contract in secondmate-provisioning
config/backend runtime session-provider backend override for new tasks; LOCAL, gitignored; absent = falls through to runtime auto-detection (the runtime firstmate itself is executing inside), then tmux; tmux is the verified reference backend (docs/tmux-backend.md), herdr has its own real-Herdr test lane (docs/herdr-backend.md), while zellij, orca, and cmux remain experimental with no dedicated real-backend CI lane (docs/zellij-backend.md, docs/orca-backend.md, docs/cmux-backend.md) - herdr and cmux can also be selected by runtime auto-detection, zellij and orca never are (always explicit), and codex-app is not accepted; see docs/codex-app-backend.md; inherited by secondmate homes under the primary-authoritative contract in secondmate-provisioning
config/calm Calm presentation preference shared by the Pi extension and the Claude Code mod; LOCAL, gitignored, and not inherited; see docs/configuration.md "Calm preference"
config/keep-ai-trailers optional presence flag to keep AI co-author trailers in this home's fleet commits; LOCAL, gitignored; inherited by secondmate homes; see docs/configuration.md "Commit attribution"
config/supervision-branch-model config/supervision-branch-effort Pi supervision-branch model and reasoning-effort pins written by /supervision-model; LOCAL, gitignored, independently settable, and not inherited; see docs/configuration.md "Pi supervision branch model and effort"
Expand Down Expand Up @@ -83,7 +82,7 @@ state/ runtime records and signals; gitignored
.branch-eligible-rows .branch-eligible-owner .main-eligible-rows per-actor wake-row claims and branch-owner evidence; docs/watcher-continuity.md owns the acknowledgement contract
.supervision-host* supervision host process record, engine conversation, current turn scope and report receipts, and bounded ledger of every close and engine turn; bin/fm-supervision-host.sh owns them; never touch
.lease-<task> per-task supervision lease naming which actor (main or branch) may change that task; bin/fm-lease-lib.sh owns the contract the guarded scripts enforce
.primary-endpoint .primary-doorbell-rung the Kiro V3 primary's identity-bound pane record published by its SessionStart hook and kept current by its every-turn hooks, and the newest wake-queue sequence the watcher's doorbell last rang into it; bin/fm-primary-endpoint-lib.sh owns both (docs/supervision-protocols/kiro-cli.md)
.primary-endpoint .primary-doorbell.lock .primary-doorbell-failed the Kiro V3 primary's identity-bound pane record published by its SessionStart hook and kept current by its every-turn hooks (bin/fm-primary-endpoint-lib.sh), then the doorbell owner's singleton lock and its continuity-failure record (bin/fm-primary-doorbell.sh); see docs/supervision-protocols/kiro-cli.md
x-watch.check.sh generated Relay poll shim; present only when opted in (section 14)
tool-updates.check.sh generated watched-tool update poll shim and its .check-trust binding; present only after bin/fm-tool-update-check.sh arm; its report record .tool-updates is what keeps one pending update from being reported on every poll
mail.check.sh generated received-mail poll shim and its .check-trust binding; present only after bin/fm-mail-check.sh arm; report record .mail-check (mail schema: docs/configuration.md "Mail plane")
Expand Down
Loading