Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
bd74468
fix(bin): run no repository hook when core.hooksPath is empty (#6216)
karotkriss Sep 30, 2026
65c75b0
fix(bin): let a stale record on a reassigned slot retire records-only…
karotkriss Sep 30, 2026
23e5584
fix(bin): keep the steering doorbell short under deep homes (#6240)
karotkriss Sep 30, 2026
fd325b1
feat(bin): add opt-in config/wait-no-turns so a waiting worker spends…
tiago-peixoto Sep 30, 2026
aedb7bb
fix(bin): close Gerrit-landed backlog items with the change URL as a …
slnkjthien Oct 1, 2026
549e07f
fix: reduce remote-job and supervision polling churn (#6255)
kunchenguid Oct 1, 2026
589ccec
fix(bin): load backend sibling libraries when sourced under zsh (#6221)
guanchengh-lgtm Oct 1, 2026
f593060
fix(bin): exclude a remote mate's own parent channel from self-home s…
kesslerio Oct 1, 2026
b5d9061
fix(bin): document accepted contribution verdict actors (#6307)
mremond Oct 1, 2026
8f756bb
fix(bin): recognize clone roots across path spelling differences (#6306)
mremond Oct 1, 2026
349e189
test: preserve Pi calm transcript captures with Pi 1.0 (#6338)
kunchenguid Oct 1, 2026
6af8331
fix(bin): preserve hold reasons and reject invalid completion invento…
mremond Oct 1, 2026
8690c41
fix: reclaim orphaned watcher arms on the next park (#6335)
kunchenguid Oct 2, 2026
241d461
fix(bin): restore downtime on supervision-host hand-back when the suc…
tiago-peixoto Oct 2, 2026
65e2aa4
fix: reduce remote-job polling process churn (#6363)
kunchenguid Oct 2, 2026
f50e9cd
fix(bin): recognize titled Claude top rules and preserve grey slash c…
RibatTRW Oct 2, 2026
87fa81b
fix: prevent Pi trust prompts in seeded secondmate homes (#6387)
kunchenguid Oct 2, 2026
d719ef3
fix(bin): retry ShellCheck roots that hit the memory ceiling without …
tiago-peixoto Oct 2, 2026
e31bc6e
fix(pi): restore watcher continuity across successor gaps and make ex…
RibatTRW Oct 3, 2026
1f3e769
fix(pi): hide duplicate assistant finals from hidden processing retri…
zestysoft Oct 3, 2026
3744c28
merge: sync upstream kunchenguid/firstmate main into the fork
yelenplays Oct 3, 2026
0ab5d07
no-mistakes(ci): Fixed both CI failures: already-acknowledged recover…
yelenplays Oct 3, 2026
7979d43
no-mistakes(ci): Fixed the watcher confirmation failure: an absent wa…
yelenplays Oct 3, 2026
b3a3aff
no-mistakes(ci): Fixed the Pi reload E2E’s version-specific status ma…
yelenplays Oct 3, 2026
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
1 change: 1 addition & 0 deletions .agents/skills/captain-hold-lifecycle/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ The agent performs the semantic inventory because scripts must not infer captain
Every unresolved question that belongs to the captain and is discovered while producing, reading, presenting, or ending an investigation or visual review must be carried by a captain-held task in the authoritative backlog of the home that owns the originating work before that work or review may be treated as complete.
For a Lavish board-backed handoff, pass the reply through `bin/fm-procevent-lavish.sh arm --agent-reply-file` before appending the status; the adapter owns version-specific acceptance ordering.
Prefer holding the work item the question gates over minting a new row; create a new task only when no work item exists to hold.
The originating investigation or review is never its own inventory entry, so hold a separate task for the call and pass `--origin <origin-id>` so `complete` can check it.
Put the question and its options in the hold reason, and keep one held task per genuine gate: a multi-question review is one held task pointing at its report, not a row per question. Represent that task with exactly one board card that consolidates its questions and options; never fan one task id into duplicate same-key cards.
Register or re-hold through `bin/fm-captain-hold.sh hold`, which is idempotent per task id.
After inventorying the whole report and review surface, run `bin/fm-captain-hold.sh complete` with every captain-held task id, or with `--none` only when the reviewed surface leaves nothing waiting on the captain.
Expand Down
6 changes: 4 additions & 2 deletions .agents/skills/harness-adapters/references/harness/pi.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ Verified on 2026-07-27 with Pi and Pi-signed 0.82.0 unless a fact gives another

Native Codex sessions may request `ultra` through the native extension flag described by `../../../bin/fm-spawn.sh`; it is separate from Pi's thinking levels.
Pi has no permission system, so workers are always autonomous.
Pi's installed `packages/coding-agent/docs/settings.md` UI and display section documents `regular` as the `tuiMode` default and `fullscreen` as experimental.
Fullscreen can bury steering messages by rewriting scrollback, so Firstmate avoids it when the installed CLI supports the override.
`../../../bin/fm-spawn.sh --help` owns the executable-pinning and version-safe launch mechanics.

Expand All @@ -33,7 +32,10 @@ Multiple positional arguments become separate queued messages; the spawn templat

A fresh Pi or Pi-signed spawn for a ship, scout, or task registers its exact isolated worktree in the Pi trust store under the spawn's `PI_CODING_AGENT_DIR` (default `~/.pi/agent/trust.json`, or the pinned root's `trust.json` under a worker account pin), so the worker does not pause for the project-trust dialog.
Relaunches and secondmates do not register trust.
For a manually launched Pi in an untrusted directory, accept the dialog with Enter and verify the instructions begin processing.
A project trust dialog can appear on the first Pi run in any not-yet-trusted directory that holds a trust-requiring resource such as `.pi/extensions/`, including a clean worktree and a freshly seeded secondmate home.
Accept it with Enter and verify the instructions begin processing.
The decision persists per path in `~/.pi/agent/trust.json`, or in the pinned root's `trust.json` under a worker account pin, so later spawns in the same pooled slot under that root skip it.
For unattended seeded-secondmate launches, `../../../bin/fm-spawn.sh --help` owns the capability-gated project-trust approval mechanics; [runtime verification](../../../../../docs/verification/runtime-backends.md#pi-seeded-secondmate-project-trust) owns the regression evidence.

## Worker turn-end extension

Expand Down
1 change: 1 addition & 0 deletions .agents/skills/operational-home-layout/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ config/trace-context optional presence flag enabling default-off native W3C tra
config/lavish-axi-host optional one-line per-machine Lavish server address; LOCAL, gitignored, inherited by secondmate homes, and exported into every worker launch; see docs/configuration.md "Lavish server address" for opening versus polling
config/brief-include.md optional standing worker instructions appended verbatim as the last section of every ship and scout scaffold; LOCAL, gitignored, and not inherited; keep its text out of `## Firstmate spec`; see docs/configuration.md "Home brief include"
config/fleet-ledger optional presence flag opting this home in to the default-off fleet activity ledger state/fleet-ledger.jsonl that outside tools can follow; LOCAL, gitignored, and not inherited; see docs/fleet-ledger.md
config/wait-no-turns optional presence flag opting this home into default-off waiting-worker behavior (brief waiting section, foreground pipeline drive, pending-reply hold, one fire-and-forget retry ring); LOCAL, gitignored, and not inherited; see docs/configuration.md "Waiting worker spends no turns"
config/turnend-churn-absorb optional presence flag opting this home into the default-off absorb of bare turn-end wakes on pane churn; LOCAL, gitignored, and not inherited; see docs/configuration.md "Turn-end pane-churn absorb"
config/wedge-defer-parked-gate optional presence flag opting this home into the default-off deferral of a wedge escalation for a lane parked at a validation gate awaiting the supervisor's own still-open decision; LOCAL, gitignored, and not inherited; see docs/configuration.md "Parked-gate wait deferral"
config/cmux-socket-password optional cmux control-socket password; LOCAL, gitignored; read fresh on every cmux CLI call and passed through without ever overriding an operator's own ambient CMUX_SOCKET_PASSWORD when absent (docs/cmux-backend.md "Setup")
Expand Down
62 changes: 59 additions & 3 deletions .pi/extensions/fm-branch-supervision.ts
Original file line number Diff line number Diff line change
Expand Up @@ -653,10 +653,16 @@ export default function (pi: ExtensionAPI) {
// queued for the captain's next prompt. The durable truth is the store's
// processed marker; this only paces re-presentation and resets with the
// session generation.
type ProcessingState = { sequences: string; through: number; triggered: number; pending: boolean; nextTurnQueued: boolean };
type ProcessingState = { sequences: string; through: number; triggered: number; pending: boolean; nextTurnQueued: boolean; visibleFinals: Set<string> };
let processing: ProcessingState | null = null;
let queuedProcessingContent: string | null = null;
let processingOpenedThisRun = false;
// Compare only replies to the same consumed sequence set. A retry can be
// the first real handling, so only an empty or exact-repeat final is hidden.
// Buffer retry streaming until message_end can make that decision; tool
// messages always keep their prose, and a user message ends this scope.
let activeProcessing: { request: ProcessingState; retry: boolean } | null = null;
let userMessageThisTurn = false;
let processedInitializedGeneration = -1;
// One revision for BOTH selections: a model or effort change invalidates an
// in-flight branch build exactly the same way.
Expand Down Expand Up @@ -1095,7 +1101,7 @@ export default function (pi: ExtensionAPI) {
}
if (processing?.pending) return true;
if (!processing || processing.sequences !== sequences) {
processing = { sequences, through, triggered: 0, pending: false, nextTurnQueued: false };
processing = { sequences, through, triggered: 0, pending: false, nextTurnQueued: false, visibleFinals: new Set() };
}
// A presentation already sent is consumed by the run it joins or opens;
// until that run settles, sending a widened or identical copy would hand
Expand Down Expand Up @@ -1677,7 +1683,6 @@ ${context.command}
// duplicate suppression. Operational extension injections are not dialog.
const prompt = event.prompt;
processingOpenedThisRun = queuedProcessingContent !== null && prompt === queuedProcessingContent;
if (processingOpenedThisRun) queuedProcessingContent = null;
const trimmed = prompt.trim();
if (!trimmed || isOperationalUserText(trimmed)) return;
const file = currentMainSession.getSessionFile() ?? "";
Expand All @@ -1692,6 +1697,48 @@ ${context.command}
// so a fresh copy may be queued again once this run settles unacknowledged.
if (processing) processing.nextTurnQueued = false;
});
pi.on?.("turn_start", () => {
userMessageThisTurn = false;
});
pi.on?.("message_start", (event) => {
if (event.message.role === "user") {
userMessageThisTurn = true;
activeProcessing = null;
} else if (
event.message.role === "custom" &&
isProcessingCustomMessage(event.message) &&
queuedProcessingContent !== null &&
event.message.content === queuedProcessingContent
) {
// message_start covers both an idle custom prompt and a follow-up
// consumed inside an existing run; neither needs before_agent_start.
activeProcessing = !userMessageThisTurn && processing ? { request: processing, retry: processing.triggered > 1 } : null;
queuedProcessingContent = null;
}
});
pi.registerMarkdownTransformer?.((markdown, context) =>
activeProcessing?.retry && context.isStreaming && context.messageType !== "user" ? "" : markdown,
);
pi.on?.("message_end", (event) => {
if (!activeProcessing || event.message.role !== "assistant") return;
// message_end runs before tool execution. Keep the whole message when
// it carries a call, including prose alongside fm_branch_processed.
if (event.message.content.some((part) => part.type === "toolCall")) return;
const text = event.message.content.filter((part) => part.type === "text").map((part) => part.text).join("\n").trim();
const { request, retry } = activeProcessing;
if (!retry || (text && !request.visibleFinals.has(text))) {
if (text) request.visibleFinals.add(text);
return;
}
// Pi applies the replacement before persistence and transcript rendering.
// Preserve the message envelope, including provider usage accounting.
return {
message: {
...event.message,
content: [],
},
};
});
pi.on?.("context", (event, ctx) => {
if (!afkPostureRecordPresent(state)) return;
const messages = event.messages ?? [];
Expand All @@ -1714,6 +1761,7 @@ ${context.command}
mainStreaming = false;
queuedProcessingContent = null;
processingOpenedThisRun = false;
activeProcessing = null;
if (processing) processing.pending = false;
const settledGeneration = generation;
await enqueueDelivery(async () => {
Expand Down Expand Up @@ -1773,6 +1821,8 @@ ${context.command}
consecutiveProviderErrors = 0;
providerRecovery = null;
generation += 1;
activeProcessing = null;
userMessageThisTurn = false;
mirrorCollection.collectAnchor = null;
mirrorCollection.pendingCursor = null;
mirrorCollection.stagedCaptain = null;
Expand Down Expand Up @@ -1821,6 +1871,9 @@ ${context.command}
shuttingDown = true;
generation += 1;
processing = null;
queuedProcessingContent = null;
activeProcessing = null;
userMessageThisTurn = false;
pendingMirror.length = 0;
currentMainSession = null;
mirrorCollection.collectAnchor = null;
Expand Down Expand Up @@ -2369,6 +2422,9 @@ ${context.command}
};
}
const remaining = await readUnprocessedOutcomes(acknowledgedGeneration);
if (acknowledgedGeneration === generation && activeProcessing && through >= activeProcessing.request.through) {
activeProcessing = null;
}
if (remaining !== null && remaining.length === 0) processing = null;
const open = remaining === null
? "the remaining outcomes could not be read"
Expand Down
Loading
Loading