feat(fleet): F3-m2 — native Pi heartbeat + model surface + mosaic_mission_status tool #602

Merged
jason.woltje merged 4 commits from feat/f3-m2-pi-harness into main 2026-06-22 01:43:19 +00:00
Owner

F3-m2 — Custom Pi harness: native heartbeat + model surface + model-callable tool

Rebased onto main (on top of #599 6ffb277). Refs #588.

What this delivers

1. Native Pi heartbeat writer (runtime/pi/mosaic-extension.ts)

  • Writes the same .hb contract fleet ps reads (ts/pid/status[/model]) on a MOSAIC_HEARTBEAT_INTERVAL timer.
  • turn_start/turn_end flip status busy↔ok, so fleet ps reflects real turn activity (more accurate than the pane-PID-only sidecar).
  • Self-reports the active model via ctx.model.
  • Atomic tmp+rename write; touches a .hb.native precedence marker.

2. Sidecar reconciliation (tools/fleet/start-agent-session.sh)

  • The launcher sidecar defers when a fresh .hb.native marker exists (< 2×interval), else writes the status=ok fallback — native precedence, sidecar fallback, same contract so fleet ps is runtime-agnostic.
  • Preserves #599's %q quoting on the printf format (merged with the deferral block during rebase).

3. Model surfaced end-to-end in fleet ps (commands/fleet.ts)

  • parseHeartbeat reads an optional model= line into HeartbeatInfo.model.
  • fleet ps shows a MODEL column (table) and emits it in --json (rows[].heartbeat.model).
  • Legacy/sidecar beats omit the line → model=null → column shows -.

4. mosaic_mission_status model-callable tool (R14 proper tool usage)

  • registerTool so the Pi agent can load its active mission, milestone progress, task counts, and latest scratchpad as a tool call before planning.
  • Shape verified against the real @earendil-works/pi-coding-agent@0.79.9 ToolDefinition.

Bug fixes folded in

  • session_endsession_shutdown: the prior handler never fired — repo hooks + session-lock cleanup were dead. Now runs on shutdown (+ clears HB timer/marker).
  • Import scope corrected @mariozechner/...@earendil-works/pi-coding-agent.

Verification

  • Prettier clean on every touched .ts (prettier@3.8.1).
  • Reader contract pinned in fleet.spec.ts (legacy beat → null/-; native beat → parsed id, busy, healthy).
  • Sidecar verified end-to-end from the actual file: fallback writes status=ok; defer leaves the native status=busy/model untouched; generated script bash -n clean.
  • Native-HB writer is reader-contract-pinned + shell deferral-tested per Lead direction; live-validated on deploy (launch a real pi agent → fleet ps shows self-reported MODEL + busy/ok flipping on turn boundaries) — the situational gate.

Note: test-start-agent-session.sh Test-1 (agent workdir mismatch) fails identically on clean origin/main — pre-existing, environment-sensitive (pane pane_current_path settle), addressed by #601 which lands next in the batch. Not introduced here.

🤖 Generated with Claude Code

## F3-m2 — Custom Pi harness: native heartbeat + model surface + model-callable tool Rebased onto `main` (on top of #599 `6ffb277`). Refs #588. ### What this delivers **1. Native Pi heartbeat writer** (`runtime/pi/mosaic-extension.ts`) - Writes the same `.hb` contract `fleet ps` reads (`ts`/`pid`/`status`[/`model`]) on a `MOSAIC_HEARTBEAT_INTERVAL` timer. - `turn_start`/`turn_end` flip `status` busy↔ok, so `fleet ps` reflects *real* turn activity (more accurate than the pane-PID-only sidecar). - Self-reports the active model via `ctx.model`. - Atomic `tmp`+`rename` write; touches a `.hb.native` precedence marker. **2. Sidecar reconciliation** (`tools/fleet/start-agent-session.sh`) - The launcher sidecar **defers** when a fresh `.hb.native` marker exists (`< 2×interval`), else writes the `status=ok` fallback — native precedence, sidecar fallback, **same contract** so `fleet ps` is runtime-agnostic. - Preserves #599's `%q` quoting on the printf format (merged with the deferral block during rebase). **3. Model surfaced end-to-end in `fleet ps`** (`commands/fleet.ts`) - `parseHeartbeat` reads an optional `model=` line into `HeartbeatInfo.model`. - `fleet ps` shows a `MODEL` column (table) and emits it in `--json` (`rows[].heartbeat.model`). - Legacy/sidecar beats omit the line → `model=null` → column shows `-`. **4. `mosaic_mission_status` model-callable tool** (R14 proper tool usage) - `registerTool` so the Pi agent can load its active mission, milestone progress, task counts, and latest scratchpad as a tool call before planning. - Shape verified against the real `@earendil-works/pi-coding-agent@0.79.9` `ToolDefinition`. ### Bug fixes folded in - `session_end` → `session_shutdown`: the prior handler **never fired** — repo hooks + session-lock cleanup were dead. Now runs on shutdown (+ clears HB timer/marker). - Import scope corrected `@mariozechner/...` → `@earendil-works/pi-coding-agent`. ### Verification - Prettier clean on every touched `.ts` (`prettier@3.8.1`). - Reader contract pinned in `fleet.spec.ts` (legacy beat → `null`/`-`; native beat → parsed id, busy, healthy). - Sidecar verified end-to-end from the actual file: **fallback** writes `status=ok`; **defer** leaves the native `status=busy`/`model` untouched; generated script `bash -n` clean. - Native-HB *writer* is reader-contract-pinned + shell deferral-tested per Lead direction; live-validated on deploy (launch a real pi agent → `fleet ps` shows self-reported MODEL + busy/ok flipping on turn boundaries) — the situational gate. > Note: `test-start-agent-session.sh` Test-1 (`agent workdir mismatch`) fails identically on clean `origin/main` — pre-existing, environment-sensitive (pane `pane_current_path` settle), addressed by #601 which lands next in the batch. Not introduced here. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jason.woltje added 3 commits 2026-06-22 01:26:40 +00:00
WIP — not for merge yet. Implements the core of the custom Pi harness (R14/R15):
- runtime/pi/mosaic-extension.ts: native heartbeat — writes the same .hb contract
  (ts/pid/status[/model]) on a MOSAIC_HEARTBEAT_INTERVAL timer; turn_start/turn_end
  flip status busy/ok; model self-report via ctx.model; touches a .hb.native
  precedence marker. Also FIXES a latent bug: session_end -> session_shutdown (the
  old handler never fired) + corrects the import scope to @earendil-works/pi-coding-agent.
- start-agent-session.sh: sidecar DEFERS when the .hb.native marker is fresh
  (< 2x interval), else writes the fallback — native precedence, sidecar fallback,
  same contract so fleet ps is agnostic (per Lead's design). Generated script
  validated (bash -n) + deferral/fallback behavior tested.

REMAINING before PR: surface model in `fleet ps` (parseHeartbeat + row); vitest for
the native-HB writer; "proper tool usage" (registerTool) piece; rebase onto #599's
%q sidecar (overlap on the printf line).

Refs #588

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parseHeartbeat now reads an optional model= line from the heartbeat
file (written by native runtime heartbeats) into HeartbeatInfo.model,
and fleet ps surfaces it as a MODEL column (table) and in --json
(via rows[].heartbeat.model). Legacy/sidecar heartbeats omit the line
and report model=null, so the column shows '-'.

Closes the model self-report gap end-to-end with the native Pi
heartbeat writer (F3-m2): the runtime self-reports its active model
and the fleet operator can see it in ps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EsgTQzV5YUGk1JtCLP4B83
feat(pi): register model-callable mosaic_mission_status tool
Some checks failed
ci/woodpecker/push/ci Pipeline was canceled
ci/woodpecker/pr/ci Pipeline was canceled
b26bbb02e9
Adds a first-class registerTool (R14 'proper tool usage') so the Pi
agent can load its active Mosaic mission, milestone progress, task
counts, and latest scratchpad as a tool call before planning — instead
of shelling out or guessing. Reuses detectMission/buildMissionSummary;
returns AgentToolResult text + structured details. promptGuidelines
names the tool explicitly per the pi extension authoring contract.

Tool shape verified against @earendil-works/pi-coding-agent@0.79.9
ToolDefinition (name/label/description/promptSnippet/promptGuidelines/
parameters + execute(toolCallId,params,signal,onUpdate,ctx)).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EsgTQzV5YUGk1JtCLP4B83
jason.woltje added 1 commit 2026-06-22 01:41:46 +00:00
fix(fleet): bake MOSAIC_AGENT_NAME into the agent pane so native HB fires
Some checks failed
ci/woodpecker/pr/ci Pipeline failed
ci/woodpecker/push/ci Pipeline failed
5c643cd54e
Live-validation (Lead, w-jarvis) found the native heartbeat was INERT in
production: the Pi extension gates on MOSAIC_AGENT_NAME, but tmux panes
inherit the tmux SERVER environment (not this script's env, nor the systemd
unit's), so the name was empty in-pane for BOTH ad-hoc and systemd agents.
Result: no native .hb, no model self-report — only the sidecar fallback ran.

Fix: %q-quote the agent name and export it into the pane command alongside
PATH, so the extension sees it -> nativeHbEnabled() -> writes <name>.hb with
model + busy/ok turn state.

Re-validated live via the launcher (isolated socket, real pi on glm-5.2):
  - pane env now carries MOSAIC_AGENT_NAME
  - <name>.hb written with status=ok + model=glm-5.2 + .hb.native marker
  - status flips ok -> busy on a real turn -> ok on turn end
  - sidecar defers to the fresh native marker

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EsgTQzV5YUGk1JtCLP4B83
jason.woltje merged commit 59c755067e into main 2026-06-22 01:43:19 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#602