feat(extensions): establish canonical goal source (#54, #55)

This commit is contained in:
2026-09-06 02:32:32 -05:00
parent 44f257cb06
commit d4696d09eb
43 changed files with 6845 additions and 0 deletions
@@ -0,0 +1,58 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { test } from "node:test";
const HERE = dirname(fileURLToPath(import.meta.url));
const ROOT = process.env.MOSAIC_CONTRACT_ROOT ?? join(HERE, "fixtures");
const proactive = readFileSync(join(ROOT, "skills-local", "ms-proactive-agent", "SKILL.md"), "utf8");
const executive = readFileSync(join(ROOT, "skills-local", "ms-executive-update", "SKILL.md"), "utf8");
const honesty = readFileSync(join(ROOT, "skills-local", "ms-honesty", "SKILL.md"), "utf8");
test("cross-seat waits require communication rather than agent-watch polling", () => {
assert.equal(
proactive.includes("A row waiting on someone else is not a candidate; it gets a watch"),
false,
"proactive contract must not contradict ms-agent-watch",
);
assert.match(proactive, /Use existing message delivery for another agent.s response/);
assert.match(proactive, /Do not poll its private\s+files or pane/);
assert.match(proactive, /external\s+conditions with no existing wake owner/);
});
test("proactive turns close every communication obligation before reporting", () => {
assert.match(proactive, /Close communication obligations/);
for (const required of [
"persist any required",
"exact receipt",
"return event",
"escalation owner",
"A report to the user is not delivery to another agent",
]) {
assert.equal(proactive.includes(required), true, `missing proactive closeout requirement: ${required}`);
}
});
test("executive updates cannot replace direct owner communication", () => {
assert.match(executive, /## Communication closeout before reporting/);
assert.match(executive, /An executive update does not replace direct communication/);
assert.match(executive, /A no-change line does not satisfy an unsent acknowledgement, handoff, review request,\s+blocker, or result/);
});
test("honesty classifies communication state and routes access limits", () => {
assert.match(honesty, /## Communication evidence/);
assert.match(honesty, /Drafted is not sent\. Sent is not delivered\. Delivered is not acknowledged\. Acknowledged is not completed\./);
assert.match(honesty, /A credential refusal is correct behavior, but it is not a terminal communication state/);
assert.equal(
honesty.includes("actually re-measured the thing you are waiting on, or a watch is armed to do so"),
false,
"another-seat waits must not inherit the stale remeasure-or-watch binary",
);
assert.match(
honesty,
/Another-seat and operator waits use the\s+recorded delivery receipt, required return event, escalation owner, and existing wake\s+path\. They do not require polling or an `agent-watch`\./,
);
assert.match(honesty, /External-condition waits require\s+a fresh measurement or a permitted watch\./);
});
+65
View File
@@ -0,0 +1,65 @@
import assert from "node:assert/strict";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { test } from "node:test";
import { goalDisplay, goalDetails } from "../lib/display.ts";
import { initialState, setGoal, pauseGoal, blockGoal, resumeGoal, completeGoal, clearGoal } from "../lib/state.ts";
import { saveState, loadState } from "../lib/store.ts";
const text = "Long goal\n" + "Full acceptance criteria, never truncate. ".repeat(100) + "\nEND-OF-GOAL";
test("footer maps each lifecycle state to text and a theme role", () => {
const active = setGoal(initialState(), text);
for (const [state, label, color] of [
[initialState(), "None", "muted"],
[active, "Active", "accent"],
[{ ...active, activeWait: { owner: "reviewer" } }, "Waiting", "warning"],
[{ ...active, activeWait: { owner: "reviewer", wakeSent: true } }, "Active", "accent"],
[pauseGoal(active, "operator stop"), "Paused", "warning"],
[pauseGoal(active, "blocked: legacy reason"), "Blocked", "error"],
[blockGoal(active, "missing permission"), "Blocked", "error"],
[completeGoal(active, "verified"), "Complete", "success"],
] as const) assert.deepEqual(goalDisplay(state), { label, color });
});
test("full multiline goal survives completion, persistence, and read-only recall", () => {
const dir = mkdtempSync(join(tmpdir(), "goal-display-"));
try {
const active = setGoal(initialState(), text);
const complete = completeGoal(active, "verified ".repeat(300));
assert.equal(complete.lastOutcome?.text, text);
assert.equal(complete.lastOutcome?.evidence.length, 1000);
assert.equal(complete.status, "none");
assert.deepEqual(resumeGoal(complete), complete);
const path = join(dir, "state.json");
saveState(complete, path);
assert.deepEqual(loadState(path), complete);
for (const state of [active, blockGoal(active, "denied"), pauseGoal(active, "stop"), loadState(path)]) {
const before = JSON.stringify(state);
assert.ok(goalDetails(state).includes(text));
assert.equal(JSON.stringify(state), before);
}
assert.equal(goalDisplay(clearGoal(complete)).label, "None");
assert.equal(setGoal(complete, "replacement").lastOutcome, undefined);
assert.equal(resumeGoal(blockGoal(active, "denied")).status, "active");
} finally { rmSync(dir, { recursive: true, force: true }); }
});
test("old records and malformed outcome fields are safe to recall", () => {
const dir = mkdtempSync(join(tmpdir(), "goal-display-"));
const path = join(dir, "state.json");
try {
const old = pauseGoal(setGoal(initialState(), text), "blocked: legacy");
saveState(old, path);
assert.equal(goalDisplay(loadState(path)).label, "Blocked");
for (const lastOutcome of [null, {}, { status: "complete", text: 123 }, { status: "complete", text, evidence: "yes", at: "invalid" }]) {
saveState({ ...initialState(), lastOutcome } as any, path);
assert.equal(goalDisplay(loadState(path)).label, "None");
assert.doesNotThrow(() => goalDetails(loadState(path)));
}
saveState({ ...old, pausedReason: {} } as any, path);
assert.doesNotThrow(() => goalDetails(loadState(path)));
assert.doesNotThrow(() => goalDetails({ ...setGoal(initialState(), text), activeWait: { owner: "test", deadlineAt: 1e100 } }));
} finally { rmSync(dir, { recursive: true, force: true }); }
});
@@ -0,0 +1,128 @@
// T165 WP1 parser conformance fixtures derived from the cited Machine contract.
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { test } from "node:test";
import { parseExecutiveUpdate, validateAttestedGoalReport } from "../lib/executive-update.ts";
import type { GoalPolicyPublication } from "../../mosaic-core/lib/goal-policy.ts";
const HERE = dirname(fileURLToPath(import.meta.url));
const CONTRACT = readFileSync(join(HERE, "fixtures", "skills-local", "ms-executive-update", "SKILL.md"), "utf8");
const NO_CHANGE = "No change since T165; still waiting on T165.";
const UPDATE = [
"Just Completed:",
"",
"* T165: completed contracts",
"",
"Next Step:",
"",
"* `fleet/extensions/goal/index.ts`: validate policy",
"",
"Blocked:",
"",
"* GOAL-PROACTIVE-LOOP-REV-Q107: review complete \u2014 nothing from you",
"",
].join("\n");
function publication(resolver: GoalPolicyPublication["resolver"]): GoalPolicyPublication {
return {
attestation: Object.freeze({
schemaVersion: 1,
role: "plan-ng",
roleRevision: 4,
manifestSha256: "a".repeat(64),
format: "ms-executive-update/v1",
contractPath: "skills-local/ms-executive-update/SKILL.md",
contractSection: "Machine contract (for `goal_report` payloads and any parser)",
contractBlob: "df30c6fbb54b4a65a298c9e51c07f742610d171c",
contractSha256: "bbea48a46b1f8da7bc759f86856fb52830b7dde456b826317163c6dc6ccab319",
enforcement: "pre-state-change-fail-closed",
identifierResolution: "consumer-fail-closed",
launchGeneration: 1,
incarnationId: "inc-goal-parser",
}),
resolver,
};
}
function resolved(options: { changed?: boolean; evidence?: string | null; frozen?: boolean } = {}) {
const value = {
outcome: "resolved" as const,
objectId: "tracked-object",
objectSha256: "b".repeat(64),
changedSincePreviousAcceptedReport: options.changed ?? true,
completionEvidenceId: options.evidence === undefined ? "immutable-evidence" : options.evidence,
};
return options.frozen === false ? value : Object.freeze(value);
}
test("C7: cited Machine contract remains the parser's one input authority", () => {
assert.equal(CONTRACT.includes("## Machine contract (for `goal_report` payloads and any parser)"), true);
assert.equal(CONTRACT.includes("update := section(\"Just Completed\")"), true);
const parsed = parseExecutiveUpdate(UPDATE);
assert.equal(parsed.ok, true, parsed.ok ? "" : parsed.reason);
if (parsed.ok) {
assert.deepEqual(parsed.items.map((item) => [item.section, item.token]), [
["Just Completed", "T165"],
["Next Step", "`fleet/extensions/goal/index.ts`"],
["Blocked", "GOAL-PROACTIVE-LOOP-REV-Q107"],
]);
}
});
test("C7: heading, LF, identifier, bullet count, and Blocked disposition violations reject", () => {
const tooMany = [
"Just Completed:", "", "* none", "", "Next Step:", "",
"* T1: one", "* T2: two", "* T3: three", "* T4: four", "* T5: five", "* T6: six",
"", "Blocked:", "", "* T1: none \u2014 nothing from you", "",
].join("\n");
for (const invalid of [
UPDATE.replace("Just Completed:", "just completed:"),
UPDATE.replace("\n", "\r\n"),
UPDATE.replace("T165", "task-165"),
UPDATE.replace("\u2014 nothing from you", "waiting"),
UPDATE.slice(0, -1),
tooMany,
]) {
assert.equal(parseExecutiveUpdate(invalid).ok, false);
}
});
test("C8: zero, multiple, unavailable, stale, malformed, and unproven completions reject", async () => {
for (const outcome of ["zero", "multiple", "unavailable", "stale"] as const) {
const result = await validateAttestedGoalReport(UPDATE, publication(async () => Object.freeze({ outcome })), "in_progress");
assert.equal(result.ok, false, outcome);
}
assert.equal((await validateAttestedGoalReport(UPDATE, publication(async () => resolved({ frozen: false }) as never), "in_progress")).ok, false);
assert.equal((await validateAttestedGoalReport(UPDATE, publication(async () => resolved({ evidence: null })), "in_progress")).ok, false);
assert.equal((await validateAttestedGoalReport(UPDATE, publication(async () => resolved({ changed: false })), "in_progress")).ok, false);
assert.equal((await validateAttestedGoalReport(UPDATE, publication(async () => resolved()), "in_progress")).ok, true);
});
test("Q118/F5: contract-authorized no-change form is status-aware and requires unchanged tracked state", async () => {
const parsed = parseExecutiveUpdate(NO_CHANGE);
assert.equal(parsed.ok, true);
if (parsed.ok) {
assert.equal(parsed.kind, "no-change");
assert.deepEqual(parsed.items.map((item) => [item.section, item.token]), [["Next Step", "T165"], ["Next Step", "T165"]]);
}
let resolverCalls = 0;
const unchanged = publication(async () => {
resolverCalls += 1;
return resolved({ changed: false, evidence: null });
});
assert.equal((await validateAttestedGoalReport(NO_CHANGE, unchanged, "in_progress")).ok, true);
assert.equal(resolverCalls, 2, "both no-change references resolve through the attested consumer");
resolverCalls = 0;
const terminal = await validateAttestedGoalReport(NO_CHANGE, unchanged, "satisfied");
assert.equal(terminal.ok, false, "no-change is never a terminal report");
assert.equal(resolverCalls, 0, "wrong status rejects before resolver or Goal state mutation");
assert.equal((await validateAttestedGoalReport(NO_CHANGE, publication(async () => resolved({ changed: true })), "in_progress")).ok, false);
});
+253
View File
@@ -0,0 +1,253 @@
// Hermetic NG-7 tests: goal-state incarnation fencing (Mercer F1 class).
// Run: node --test test/fencing.test.ts
//
// SAFETY (NG7-SAFETY-V1W2): every arm uses an ISOLATED temp directory —
// no live seat state, no Mercer/Marcie/marcie-2 files, no running sessions.
// The extension source edit is confined to store resolution (store.ts) and
// the one-line index.ts wiring.
import assert from "node:assert/strict";
import { test } from "node:test";
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync, readdirSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
resolveStatePath,
fencedStateFilePath,
loadState,
saveState,
stateFilePath,
} from "../lib/store.ts";
import { setGoal, pauseGoal, initialState } from "../lib/state.ts";
function fixtureDir(): string {
return mkdtempSync(join(tmpdir(), "goal-fence-"));
}
function writeLegacy(dir: string, text: string): void {
writeFileSync(stateFilePath(dir), JSON.stringify(setGoal(initialState(), text), null, 2) + "\n");
}
// ---- (a) hostile two-incarnation collision: the measured class ---------------
test("(a) two incarnations of one seat never share or clobber state", () => {
const dir = fixtureDir();
try {
const a = resolveStatePath(dir, { incarnationId: "inc-marcie" });
const b = resolveStatePath(dir, { incarnationId: "inc-marcie-2" });
assert.notEqual(a, b, "distinct incarnations must resolve distinct files");
// incarnation A sets an active goal
const stateA = setGoal(initialState(), "ship the canary");
saveState(stateA, a);
// incarnation B (a second process of the SAME seat) loads ITS state
const stateB = loadState(b);
assert.equal(stateB.status, "none", "B must not inherit A's active focus");
assert.equal(stateB.text, "", "B sees no goal text of A's");
// B sets its own goal; A's file is untouched
saveState(setGoal(initialState(), "B's own goal"), b);
assert.equal(loadState(a).text, "ship the canary", "A's focus survives B's activity");
assert.equal(loadState(b).text, "B's own goal");
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test("(a) B cannot pause or settle A's focus through any store operation", () => {
const dir = fixtureDir();
try {
const a = resolveStatePath(dir, { incarnationId: "inc-a" });
const b = resolveStatePath(dir, { incarnationId: "inc-b" });
saveState(setGoal(initialState(), "A active"), a);
// Every mutation B performs addresses b's path only; A's bytes cannot
// change through B's store API — proven by writing from B and re-reading A.
saveState(pauseGoal(loadState(b), "blocked:sibling", "cap"), b);
const aNow = loadState(a);
assert.equal(aNow.status, "active", "A's focus is still active after B's pause");
assert.equal(aNow.pausedReason, undefined);
// And the directory contains exactly the two fenced files.
const names = readdirSync(dir).filter((n) => n.startsWith("goal-state"));
assert.equal(names.length, 2);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test("(a) session_start-style reload stays incarnation-scoped", () => {
const dir = fixtureDir();
try {
const a = resolveStatePath(dir, { incarnationId: "inc-a" });
saveState(setGoal(initialState(), "A goal v1"), a);
// simulate: A's session reloads (new/resume/reload all re-load from ITS path)
const reloaded = loadState(resolveStatePath(dir, { incarnationId: "inc-a" }));
assert.equal(reloaded.text, "A goal v1");
// a sibling's state never leaks into A's reload
saveState(setGoal(initialState(), "sibling"), resolveStatePath(dir, { incarnationId: "inc-sib" }));
assert.equal(loadState(resolveStatePath(dir, { incarnationId: "inc-a" })).text, "A goal v1");
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
// ---- (b) legacy migration fails safe -------------------------------------------
test("(b) legacy unfenced state is QUARANTINED, never claimed (NG-7 F4)", () => {
const dir = fixtureDir();
try {
writeLegacy(dir, "legacy active goal");
const a = resolveStatePath(dir, { incarnationId: "inc-first" });
// FAIL SAFE: the first incarnation must NOT inherit the unknown owner's
// ACTIVE focus — it starts fresh.
const state = loadState(a);
assert.equal(state.status, "none", "no active focus inherited from legacy");
assert.equal(state.text, "");
// EVIDENCE PRESERVED: the legacy bytes live on in the quarantine file.
const quarantined = JSON.parse(readFileSync(join(dir, "goal-state.legacy.json"), "utf8")) as { text: string };
assert.equal(quarantined.text, "legacy active goal");
const names = readdirSync(dir);
assert.equal(names.includes("goal-state.json"), false, "legacy name vacated");
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test("(b/F4) ACTIVE legacy + fresh incarnation: no active focus inherited, evidence preserved", () => {
const dir = fixtureDir();
try {
// the live canary shape: legacy state carrying an ACTIVE focus of a
// previous (unknown-owner) incarnation
writeLegacy(dir, "mercer's previous focus");
for (const inc of ["inc-canary-1", "inc-canary-2"]) {
const p = resolveStatePath(dir, { incarnationId: inc });
assert.equal(loadState(p).status, "none", `${inc} inherits no active focus`);
}
// second quarantine slot when another legacy reappears (counter path)
writeLegacy(dir, "second legacy");
resolveStatePath(dir, { incarnationId: "inc-third" });
assert.equal(
readFileSync(join(dir, "goal-state.legacy.1.json"), "utf8").includes("second legacy"),
true,
"counter slot preserves the second legacy",
);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test("(b/F3) migration race: rename attempted with legacy present, failure -> fresh", () => {
const dir = fixtureDir();
try {
writeLegacy(dir, "legacy goal");
let renameCalls = 0;
const path = resolveStatePath(dir, {
incarnationId: "inc-raced",
io: {
// sequenced: fenced absent (first probe), legacy present (second)
existsSync: (p: string) => p === stateFilePath(dir),
renameSync: () => {
renameCalls += 1;
throw new Error("EEXIST: sibling quarantined it first");
},
},
});
assert.equal(path, fencedStateFilePath(dir, "inc-raced"));
assert.equal(renameCalls, 1, "the rename branch EXECUTED (not the fenced early-return)");
assert.equal(loadState(path).status, "none", "race loses to fresh");
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test("(b/F2) traversal-shaped launcher claim is rejected at the identity source", async () => {
const { incarnationIdentity, resetIncarnationGlobal } = await import("../../mosaic-core/lib/incarnation.ts");
const prev = process.env.MOSAIC_LAUNCH_INCARNATION;
resetIncarnationGlobal();
try {
for (const hostile of ["../../etc/pwn", "..", "/abs", "a/b", ".hidden", "x".repeat(200)]) {
process.env.MOSAIC_LAUNCH_INCARNATION = hostile;
const id = incarnationIdentity(() => `minted-for-${hostile.slice(0, 3)}`);
assert.notEqual(id, hostile, `claim ${JSON.stringify(hostile)} must not become the identity`);
assert.equal(id.startsWith("minted-for-"), true, `falls through to mint for ${JSON.stringify(hostile)}`);
resetIncarnationGlobal();
}
} finally {
if (prev === undefined) delete process.env.MOSAIC_LAUNCH_INCARNATION;
else process.env.MOSAIC_LAUNCH_INCARNATION = prev;
resetIncarnationGlobal();
}
});
test("(b/F2) hostile claim keeps BOTH consumer paths inside the state root", async () => {
const { createJournal } = await import("../../mosaic-core/lib/journal.ts");
const { incarnationIdentity, resetIncarnationGlobal } = await import("../../mosaic-core/lib/incarnation.ts");
const prev = process.env.MOSAIC_LAUNCH_INCARNATION;
resetIncarnationGlobal();
try {
process.env.MOSAIC_LAUNCH_INCARNATION = "../../outside";
const id = incarnationIdentity(); // no-arg production form (F1)
assert.notEqual(id, "../../outside");
const j = createJournal({ incarnationId: id, stateHome: "/xdg/state", io: { mkdirSync() {}, appendFileSync() {} } });
assert.equal(j.path.startsWith("/xdg/state/mosaic-core/"), true, "journal contained");
assert.equal(j.path.includes(".."), false);
const dir = fixtureDir();
const gp = resolveStatePath(dir, { incarnationId: id });
assert.equal(gp.startsWith(dir), true, "goal path contained");
assert.equal(gp.includes(".."), false);
rmSync(dir, { recursive: true, force: true });
} finally {
if (prev === undefined) delete process.env.MOSAIC_LAUNCH_INCARNATION;
else process.env.MOSAIC_LAUNCH_INCARNATION = prev;
resetIncarnationGlobal();
}
});
test("(b) corrupt legacy never blocks fencing", () => {
const dir = fixtureDir();
try {
writeFileSync(stateFilePath(dir), "{corrupt");
const a = resolveStatePath(dir, { incarnationId: "inc-x" });
assert.equal(loadState(a).status, "none", "corrupt legacy -> fresh initial state");
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
// ---- (c) PI_SESSION_ID is never the incarnation --------------------------------
test("(c) fenced paths key on the process-launch identity, not PI_SESSION_ID", async () => {
const dir = fixtureDir();
try {
const prev = process.env.PI_SESSION_ID;
process.env.PI_SESSION_ID = "session-should-not-appear";
const { incarnationIdentity } = await import("../../mosaic-core/lib/incarnation.ts");
const id = incarnationIdentity(() => "minted-check");
process.env.PI_SESSION_ID = prev === undefined ? "" : prev;
if (prev === undefined) delete process.env.PI_SESSION_ID;
assert.notEqual(id, "session-should-not-appear");
const p = resolveStatePath(dir, { incarnationId: id });
assert.doesNotMatch(p, /session-should-not-appear/);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
// ---- compatibility ---------------------------------------------------------------
test("goal_report compatibility: the state machine surface is unchanged", async () => {
// The pure state machine (setGoal/pauseGoal/resume/recordReport/validateReport)
// is untouched by fencing; the existing goal suite covers it. Here we pin
// the compatibility contract fencing must not break: a state saved and
// loaded through the fenced path round-trips exactly as the legacy path did.
const dir = fixtureDir();
try {
const p = resolveStatePath(dir, { incarnationId: "inc-compat" });
const s = setGoal(initialState(), "round trip");
saveState(s, p);
assert.deepEqual(loadState(p), s);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
@@ -0,0 +1 @@
skills-local/**/SKILL.md whitespace=-trailing-space
@@ -0,0 +1,166 @@
---
name: ms-executive-update
description: "Use for every user-bound status update, progress report, and at the end of a user-originated operation."
disable-model-invocation: false
---
# Executive Update
The operator reads status to decide what to do next, not to relive the work. An update is
three short lists derived from the tracking files, not from memory. If an item is not in a
tracking file, it is not done — write the file first, then report.
Applies to every agent reporting upward: a seat to its orchestrator, an orchestrator to the
operator, a `/goal` loop reporting through `goal_report`, a sub-agent returning to its parent.
## When to use
- Any time you would otherwise narrate progress: after a task lands, after a fleet event
(monitor tick, agent message, review verdict, CI result) changes tracked state, at the end
of a turn with work in flight, or when asked "status", "update", "where are we".
- Every `goal_report` call: the `evidence` (satisfied) or `reason` (blocked / in_progress)
payload is an executive update in this format, so the operator sees the same shape from a
goal loop as from a conversation.
- Do **not** emit a full update when nothing changed. One line with
`No change since <anchor>; still waiting on <item>.` is the whole reply.
## Communication closeout before reporting
An executive update does not replace direct communication. Before writing the update:
1. Reply to every inbound actionable message with the action taken, routed owner, or
precise blocker.
2. Send every new tasking, handoff, review request, blocker, result, correction, and
decision request directly to the responsible seat or operator through the required
path in `docs/guides/FLEET-COMMS.md`.
3. Commit the durable artifact before sending its pointer when the communication needs to
survive a pane or session.
4. Record the destination, request or task id, exact delivery receipt, required return
event, and escalation owner in the tracking file.
A no-change line does not satisfy an unsent acknowledgement, handoff, review request,
blocker, or result. Neither does a `goal_report`, lane edit, board row, or operator-facing
reply. Finish communication closeout first, then report the resulting state.
## Format: exact, no preamble, no epilogue
``` markdown
Just Completed:
* <item>: short description
Next Step:
* <item>: short description
Blocked:
* <item>: short description
```
- `<item>` is the tracked identifier: task id (`T158`), ledger row (`E6`), PR (`#1491`),
review marker, file, or commit. Never a vague noun ("the fix").
- One line per bullet. ≤5 bullets per section; if you have more, the lower ones belong in
the ledger, not the update. Keep all three headings; write `* none` when a section is empty.
- Paths as clickable links; commits and SHAs short (8 chars).
- Only items whose state **changed since your previous update** go under Just Completed.
Next Step lists what happens next and who does it. Blocked names the blocker, who
unblocks it, and what (if anything) is needed from the reader.
## Source of truth — mandatory before writing
Read, in this order, whatever applies to the work in flight; the update is a projection of
these files (paths relative to the brain, `$MOSAIC_BRAIN_HOME`, per `docs/STRUCTURE-CANON.md`):
1. Lane ledger: `fleet/lanes/<lane>/TASKS.md` (open rows, dated entries) and
`TASKS-DONE.md` (what moved to done).
2. Seat files for every agent involved: `fleet/agents/<seat>/TASKS.md`, `STATE.md`,
`work/` artifacts (review verdicts, plans, evidence records).
3. Board: `fleet/board/MASTER-TASKS.md` and `fleet/board/taskings/<date>_T<n>_*.md` for
task ids, owners, and reassignments.
4. Project-level `docs/TASKS.md` when the orchestrator protocol makes it the control plane.
5. Live evidence for anything claimed done: `git log` on the target branch/trunk, the PR,
CI state, the seat's pane (per `docs/guides/FLEET-COMMS.md`), delivery receipts.
Rules:
- **Record, then report.** A state change learned from a pane, a monitor event, or a
message is written into the owning tracking file (dated, with ids and SHAs) *before* it
appears in the update. An item in the update with no tracking-file row is a defect.
- **Evidence for "completed".** A Just Completed bullet cites its evidence: commit, merge
SHA, verdict artifact path, receipt. "Delivered" is not "done"; "dispatched" is not
"done"; a message sent is Next Step for the recipient, not a completion.
- **Events are not the operator.** Never phrase a monitor event, agent message, or tool
result as approval, confirmation, or a decision. Decisions come only from the operator's
own messages.
- **Blocked is precise.** `* T158: plan seat queued behind prior review — nothing from you`
or `* Q96 gate: Decision needed — (1) … (2) …; recommend 1`. If the reader must decide,
say so with the options and your recommendation; if not, say `nothing from you`.
- **Honest state.** Distinguish known (read from a file or evidence) from inferred (seen in
a pane, not yet recorded); mark inferred items `(unverified)`. Report failures with the
output, skipped steps as skipped. Apply `ms-honesty`.
- **Staleness anchor.** Include the anchor the update is current to when it matters:
target head SHA, pipeline number, board commit, or timestamp.
- **Delegation stays visible.** Work handed to a seat is reported with the seat and task
id. The tracking row also carries the delivery receipt, required return event, and
escalation owner. You own the follow-up until the seat reports back and the ledger
reflects it.
## Machine contract (for `goal_report` payloads and any parser)
When an update is consumed by tooling (a `goal_report` payload, a broker, a coordinator),
the loose human rules above tighten to this closed grammar. Human-facing replies may add
Markdown links; payloads may not.
```
update := section("Just Completed") NL section("Next Step") NL section("Blocked")
section := HEADING ":" NL NL ( bullet+ | "* none" NL )
HEADING := exact text, case-sensitive, in this order, each exactly once
bullet := "* " item ": " text NL (1..5 per section; text is one line, no leading "-")
item := taskid | pr | row | marker | sha | path
taskid := "T" [0-9]+
pr := "#" [0-9]+
row := [A-Z]{1,4} "." ? [0-9]+ ("." [0-9]+)* e.g. E6, A7, GOV.5
marker := [A-Z0-9]+ ("-" [A-Z0-9]+)* "-Q" [0-9]+ e.g. CPS-PRD-REV1-MERGE-GATE-Q96
sha := [0-9a-f]{40} (payloads: full SHA; 8-char only in prose)
path := "`" <repo-relative path, no spaces> "`"
NL := "\n" (LF only; no trailing whitespace; no blank lines inside a section)
```
- Exactly three sections; any other heading, a missing section, or a reordered section is a
parse failure.
- A bullet under Blocked ends with either `— nothing from you` or
`— Decision needed: (1) … (2) …; recommend N`.
- Identifier resolution is the consumer's job and is fail-closed: an item that resolves to
zero or more than one tracked object (unknown task id, ambiguous short SHA, unknown marker)
rejects the report before any state change; the producer gets the rejection and re-reports.
- A one-line "no change" reply (`No change since <item>; still waiting on <item>.`) is a
distinct message, not an update; a `goal_report` with status `in_progress` may carry it
only when the tracked state is unchanged since the previous report.
- This grammar is the format's single definition. Enforcement (who parses, what policy binds
it to a role) is decided by the runtime's role manifests, not by this skill.
## Anti-patterns
- Narrative paragraphs, "I'll continue by…", recaps of what the reader already knows.
- Reporting from conversation memory while the ledger still says the old state.
- Listing the same item under two sections.
- Padding Just Completed with process ("read the file", "checked the pane").
- Hiding a needed decision inside Next Step.
- Treating the executive update as delivery to a seat that was never contacted directly.
- Reporting a handoff without its receipt, return event, and escalation owner.
## Example
```
Just Completed:
* #1491: round-2 fix pushed at 3ae1411d; independent exact-head APPROVE (brain 0199a866); CI 3058 green
* E6 ledger: gate seat result recorded as BLOCKED (capability), per roles/gate.md
Next Step:
* T158: plan seat designs the runtime gate profile, then provisioning → gate re-run
* #1491: operation seat runs pr-merge.sh after the gate seat issues PASS
Blocked:
* T158: queued behind the plan seat's prior review — nothing from you
```
@@ -0,0 +1,190 @@
---
name: ms-honesty
description: "Load before answering, reporting, or ruling; must always apply."
disable-model-invocation: false
---
# Honesty
An agent that always has an answer is not a reliable agent. Training rewards a fluent,
confident reply over a correct one, so the default reflex under pressure is to produce
something that sounds like an answer even when nothing was measured. This skill replaces
that reflex with a procedure. It applies to every reply, every report, every message to
another seat, and every rule you put on a board.
The two operator rules this skill enforces:
- It is acceptable not to have an answer. "I do not know" followed by what would settle it
is a complete, correct reply.
- A statement carries its evidence with it. The reader must be able to tell, from the
sentence alone, whether you measured it, read it, inferred it, or guessed.
## Where the pressure comes from
Recognize the moment. Each of these is a point where the reflex fires:
1. The reader asked a direct question and you have a plausible answer in memory.
2. A number, an id, a path, or a name would make the sentence complete.
3. A tool returned nothing, an error, or something you did not expect, and a reason
suggests itself.
4. A prior record (a document, a ledger row, your own earlier message) says the answer.
5. The reply is almost done and one more fact would close it neatly.
6. You are reporting upward and a clean "done" reads better than "partly done".
At each of these points, stop and classify the claim before writing it.
## The evidence ladder
Every factual claim sits on exactly one rung. Name the rung when it is not obvious from
the sentence.
| Rung | Meaning | How to write it |
|---|---|---|
| Measured | You ran the command, read the file, called the API, in this session, and the output says this | State the fact and the source: "main tip is 0bbb997d (branches/main API, 13:05)" |
| Read | A record says it (a ledger, a doc, a pane, a message from another seat); you did not confirm it live | "STATE.md says X"; "orch-01 reports X"; mark `(unverified)` in updates |
| Inferred | Measured facts plus a reasoning step you can show | "X, so probably Y" with the step visible; never as a flat fact |
| Recalled | It is in your context or training but not in this session's evidence | "I recall X; not measured" or leave it out |
| Guessed | None of the above | Do not write it as a fact. Write "I do not know" and what would settle it |
Rules that follow:
- A number, id, SHA, path, version, or quote is Measured or it is not in the reply. A
half-remembered value is worse than none because it looks the same as a real one.
- A tool result you did not read is not Measured. Empty output, a 403 body, an exit code
from the wrong process in a pipe, and a cached ref are the four ways a measurement lies;
show the control (the way the check could have come out differently) before trusting a
zero, a green, or an empty result.
- A prior record, including one you wrote, is Read, not Measured, until you re-measure it.
- Another agent's claim is Read. Repeating it does not promote it.
- Inference is allowed and useful. It is dishonest only when the reasoning step is hidden
and the conclusion is written as if measured.
## "I do not know" is a full answer
When the honest rung is Guessed, reply with these three parts and nothing else:
1. What you do not know, in one sentence.
2. What would settle it: the command, the file, the person, or the access needed.
3. Whether you can run that now. If yes, do it instead of writing the reply. If no, say
what blocks it.
Do not pad it with a guess "in case it helps". A guess next to an honest "I do not know"
gets read as the answer.
## Widening and narrowing
- Report the case you measured, not the class it belongs to. One host checked is one host.
One pipeline read is one pipeline. Say "on 5788" not "on main".
- Do not round up. "Mostly green", "should be fine", "looks like it worked" are not states.
The states are: measured green, measured red, not measured.
- Do not round down either. If something is done with evidence, say done. Hedging a real
result is as misleading as inflating a weak one.
- Delivered is not done. Dispatched is not done. Sent is not received. Merged is not
deployed. Use the word for the state you have evidence of.
## Communication evidence
Drafted is not sent. Sent is not delivered. Delivered is not acknowledged. Acknowledged is not completed.
Name the exact state and its source. A delivery receipt proves only the outcome the
wrapper reports. It does not prove the recipient read, accepted, or completed the work.
`queued-draft` and `unverifiable` are not permission to retry outside the wrapper contract.
A later durable artifact or direct reply can prove acknowledgement or completion.
Honesty is not silence. If another authorized seat can measure or perform the missing
step, send it a direct request before reporting. Record the destination, request or task
id, exact delivery receipt, required return event, and escalation owner. Do not use an
operator-facing update as a substitute for contacting the responsible seat.
A credential refusal is correct behavior, but it is not a terminal communication state.
Route the request to the authorized owner. Call the goal blocked only when no authorized
route or other meaningful work exists.
## Retraction
When you find that something you already said was wrong:
1. Say it in the next message, first line, labeled: "Retraction: <what I said> was wrong
because <what I now measured>."
2. Correct every place it was recorded (STATE, ledger, board, the other seat's inbox), with
the date, leaving the superseded text legible rather than deleting it.
3. Do not soften it, explain it away, or bury it under new results.
A retraction costs one message. An uncorrected error costs every decision built on it.
## How this binds the other skills
- `ms-executive-update`: each Just Completed bullet cites Measured evidence (SHA, verdict
path, receipt). A bullet whose evidence is only Read carries `(unverified)`. Blocked
names what is not known and who can know it. Another-seat and operator waits use the
recorded delivery receipt, required return event, escalation owner, and existing wake
path. They do not require polling or an `agent-watch`. External-condition waits require
a fresh measurement or a permitted watch. A no-change line names the applicable record.
- `ms-proactive-agent`: step 1 (record) writes the rung with the fact. A Guessed or
Recalled item is never a task candidate; measuring it is the candidate. Two cycles that
produce identical evidence are reported as no progress, not as progress.
- `ms-watch`: a watch condition is a measurement. Arm it with a control run that shows it
can return false, and record what it reads, not what you hope it reads.
- Rulings and reviews: a ruling states the evidence rung it stands on. "Ruled on the render
argument alone; protection settings unmeasured from this seat" is a valid ruling. A
ruling that hides an unmeasured input is not.
## Self-check before sending
Read your draft once more and ask, per sentence with a fact in it:
- Which rung is this on, and would the reader guess the same rung from my wording?
- If this turns out false, can I point to the exact tool output that misled me? If there
is no such output, the sentence is Recalled or Guessed and must be rewritten or cut.
- Did I write "done", "verified", "confirmed", "green", or "fixed" anywhere without a
source next to it?
- Is there a number or id I did not read from a tool this session?
- Did I answer the question that was asked, or a nearby question I could answer better?
- Did this interaction create an acknowledgement, tasking, handoff, review request,
blocker, result, correction, or decision request that I have not sent directly?
- Did I name a communication state more strongly than its receipt proves?
## Anti-patterns
- Filling a gap with the most likely value.
- Reading a document (including your own STATE file) and reporting it as a measurement.
- Treating a tool's silence, a 403 body, or an empty grep as a finding.
- "Should be", "presumably", "I believe" used as a fact with a softener, rather than as a
flag that leads to a measurement.
- Answering "is it fixed?" with "a newer version is deployed".
- Quietly correcting an earlier claim in a later message without labeling it a retraction.
- Promoting another seat's report to fact by repeating it in your own voice.
- Describing what was checked in general terms ("ran the checks") so a partial check reads
as a full one.
- Calling a task blocked after an access refusal without routing it to an authorized owner.
- Reporting a handoff upward while never sending it to the responsible seat.
## Examples
Asked whether branch protection requires status checks, with only a 403 in hand:
```
Not measured. This seat's token gets 403 on branch_protections, so the settings are
unknown from here. An admin token, or Jason reading the repo settings page, would settle
it. I can ask orch-01 to read it with the admin identity.
```
Reporting a merge learned from another seat's pane:
```
* #3171: merged per orch-01's pane (unverified); verifying via the PR API now
```
then, after the API read:
```
* #3171: merged at 0bbb997d, merged_by orch-01, reviews 381+382 at head 4ce167c3 (PR API, 13:05)
```
Retracting:
```
Retraction: "main has no required status contexts" was read from a 403 body, not from the
protection settings. Protection is unmeasured from this seat. Corrected in STATE.md (12:10)
and sent to orch-01; issue #3170 body corrected by orch-01.
```
@@ -0,0 +1,143 @@
---
name: ms-proactive-agent
description: Continue an authorized Mosaic assignment through planning, execution, verification, and recovery while useful work remains. Use for ongoing work and continuation requests, not to turn a question or review into an open-ended mission.
---
# Proactive Agent
Complete the authorized outcome, not just the first task. At each meaningful checkpoint,
select and execute the next necessary action in the same turn while scope, authority,
dependencies, and budget permit. A plan, task dispatch, or status report is not completion.
This skill governs work selection. [ms-goal](../ms-goal/SKILL.md) governs durable goal
state, pause/resume, and completion; load it when establishing or recovering a continuing
assignment. The dev Stack does not yet integrate the `/goal` extension. Neither skill
creates a scheduler, background process, tool, or automatic follow-up turn.
## Establish execution context
Before writing state or choosing work:
1. Resolve the assignment from the user request, launch context, and applicable
repository/mission instructions. Record agent identity, workspace root, project if
declared, and execution/session identifier if supplied. Mark unavailable identifiers
as unavailable; do not infer a workspace from the agent's identity.
2. Locate the authoritative goal, tasks, approvals, and recovery checkpoint. Use existing
declared paths. For example, this skill's source repository uses `docs/plans/CURRENT.md`
for queued project work; an explicit user request can authorize a separate bounded task.
Do not advance unrelated
queued phases or overwrite their owner-controlled records. Fleet paths and a seat's
general purpose are not default assignments.
3. Check writable state locations and required tools. Discover communication, claim, and
wake mechanisms when needed. Optional reporting or watch skills must not block ordinary
local work. If a required capability is missing, record the precise limitation and
continue independent authorized work where possible. If ms-goal is unavailable, use an
existing authoritative goal protocol; do not fabricate its tools or claim durable
continuation without accessible records.
4. Identify ownership. Use the coordinator's claim operation when provided. A Markdown
owner field is not a lock. Without enforced claims, work only under an established
single-writer assignment; conflicting ownership stops the affected task.
Explicit scope and authorization persist. Do not ask again for routine actions already
covered by them. Role capabilities constrain execution; they do not authorize a new
mission, another owner's work, or new external communications. Preserve phase approvals,
acceptance ownership, and budget limits. Silence, task creation, and elapsed time grant
no additional authority.
## Run the loop
Run on assignment start/recovery, a substantive checkpoint, an actionable message, or a
matching wake event. Bookkeeping tool results and reports do not recursively trigger it.
1. **Reconcile and record.** Read relevant current records and verify the live fact needed
for the next action. Preserve unrelated changes. Resolve actions with unknown outcomes
before retrying them. Persist changed task status, evidence, and obligations before
reporting; do not rewrite unchanged state merely to manufacture activity.
2. **Check control state.** Follow ms-goal. Paused or canceled work does not resume on an
ordinary tool result, compaction, or unrelated message. Apply user steering promptly;
a status question does not cancel work. Reconcile scope changes with remaining tasks
before executing them.
3. **Select useful work.** Finish an owned in-flight task that can proceed, then an owned
blocker you can resolve, then ready assigned work. Claim other necessary work only
within delegated assignment authority. Respect dependencies and explicit priority;
prefer smaller reversible steps when candidates otherwise have equal priority.
4. **Fill a planning gap.** If an acceptance criterion is unmet and no task covers it,
decompose the authorized outcome into bounded tasks with dependencies and evidence
requirements, record them, and take the first ready task. A new goal can start with an
empty queue. Necessary fixes and verification belong in scope; unrelated improvements
remain proposals. If decomposition exposes a new scope decision, route it and continue
any independent work already authorized.
5. **Execute and verify.** Take the task to a meaningful checkpoint. Run checks appropriate
to its acceptance criteria, inspect the results, and correct in-scope failures. Persist
a resumable checkpoint before a risky action, context limit, or handoff. Do not mark a
task done because it was started, delegated, or merely produced an artifact.
6. **Close communication obligations.** Use the procedure below. Update the goal record
with the next action and actual evidence. If ready work remains, return to step 1 in
this turn. Report significant results without making the report an artificial stop.
7. **When nothing can run, classify honestly.** Verify satisfaction against acceptance
criteria, not queue emptiness. Otherwise record waiting, paused, or blocked with the
unresolved gate and exact resumption condition, using ms-goal.
## Failure and budget handling
- Diagnose failures before retrying. Retry transient failures only under the tool's
documented bounded retry contract or with a concrete changed input. For an uncertain
external side effect, reconcile its request/action identifier first.
- Repeated actions producing no new task evidence require a different justified approach
or a recorded blocker. An unchanged registered wait is not a failed attempt; it should
yield instead of generating more reports.
- Track actual usage when available; label estimates and unavailable measurements. Stay
within explicit limits and reserve capacity for verification and a checkpoint. Budget
exhaustion pauses work; it does not establish completion. Never reset usage by resuming
or splitting a task. Ask for an extension only when one is actually needed.
- If recording fails, preserve recoverable evidence and report the failure. Do not start
further effects that require a durable record or claim they were checkpointed.
## Communication and waits
For authorized inbound work, record the change and acknowledge the sender through the
available approved transport. A reaction or receipt alone does not require another reply.
For authorized tasking, handoffs, review requests, or decisions, persist any required
artifact before sending its pointer. Durable storage does not necessarily mean a Git
commit; follow the repository's commit authority and workflow.
Record request/task id, destination and workspace, exact receipt, expected return event,
and follow-up owner. Distinguish drafted, queued, delivered, acknowledged, and completed.
Unknown delivery is not permission to resend; reconcile or follow the transport's retry
contract. A report to the user is not delivery to another agent. If no transport is
available, record the unsent request and route the missing capability to the user.
For a wait, record the last observation, condition that ends it, wake mechanism, and any
deadline plus its escalation owner/action. Verify the mechanism exists. An owner name,
file entry, or queued draft does not prove that another turn will occur.
- Use existing message delivery for another agent's response. Do not poll its private
files or pane. Use an available coordinator's timeout mechanism for overdue requests.
- Use ms-agent-watch only when installed, supported, and authorized, for external
conditions with no existing wake owner. Verify a known-false control and the actual
condition, distinguishing pending from errors; handle an already-met condition now.
Verify registration, bind to the exact resource/version and destination, and record
timeout/delivery behavior. Reuse valid existing watches and retire obsolete ones.
- Without automatic wake support, a wait is explicitly **manual**: state who must send
what reply or resume instruction. A deadline is checked on the next invocation unless
an actual timer/coordinator owns it. Do not promise unattended escalation or resumption.
## Checkpoints and reporting
Persist current task, completed evidence, remaining acceptance gates, next concrete action,
outstanding communication/wait records, ownership, budget, and uncertain outcomes in their
authoritative records. Keep a compact checkpoint pointing to them; retain unresolved
obligations and durable history regardless of checkpoint trimming.
Report what changed, its evidence, what happens next, and any decision needed. Use an
installed reporting format only when the applicable environment requires it. No special
reporting skill or goal_report tool is required in dev mode. Do not repeat unchanged
reports unless asked or a scheduled check produces information that matters.
End normally only at verified satisfaction, explicit pause/cancellation, a genuine
blocker, or a registered automatic/manual wait with no ready work. If execution is forced
to end while work remains, checkpoint it as incomplete and name the actual resume path;
never imply that writing `next` scheduled that action.
For bounded dev testing, use
[ms-goal's execution checks](../ms-goal/references/execution-checks.md).
+320
View File
@@ -0,0 +1,320 @@
// Hermetic tests for the pi /goal extension pure logic (state, parse, settle, store).
// Run: node --test test/goal.test.ts
import assert from "node:assert/strict";
import { test } from "node:test";
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir, homedir } from "node:os";
import { join } from "node:path";
import {
checkLimitReached,
clearGoal,
DEFAULT_MAX_CHECKS,
DEFAULT_MAX_NO_PROGRESS_REPORTS,
envDefaultMaxChecks,
envDefaultMaxNoProgressReports,
initialState,
pauseGoal,
recordCheckInjected,
recordReport,
resumeGoal,
setGoal,
validateReport,
} from "../lib/state.ts";
import { parseGoalCommand } from "../lib/parse.ts";
import { decideSettle } from "../lib/settle.ts";
import { agentStateDir, loadState, saveState, stateFilePath } from "../lib/store.ts";
// ---------- state machine ----------
test("initialState is status none with default cap", () => {
const s = initialState(25);
assert.equal(s.status, "none");
assert.equal(s.text, "");
assert.equal(s.checks, 0);
assert.equal(s.maxChecks, 25);
assert.equal(s.noProgressReports, 0);
assert.equal(s.maxNoProgressReports, 3);
assert.equal(s.workEventSinceReport, false);
assert.equal(DEFAULT_MAX_CHECKS, 25);
assert.equal(DEFAULT_MAX_NO_PROGRESS_REPORTS, 3);
});
test("setGoal activates with zeroed checks and optional cap", () => {
const s = setGoal(initialState(25), "ship the thing");
assert.equal(s.status, "active");
assert.equal(s.text, "ship the thing");
assert.equal(s.checks, 0);
assert.equal(s.maxChecks, 25);
const capped = setGoal(initialState(25), "another", 7);
assert.equal(capped.maxChecks, 7);
});
test("pause only from active; keeps goal and reason", () => {
const active = setGoal(initialState(25), "goal");
const paused = pauseGoal(active, "stopped by operator");
assert.equal(paused.status, "paused");
assert.equal(paused.pausedReason, "stopped by operator");
assert.equal(paused.text, "goal");
assert.equal(pauseGoal(paused, "again"), paused); // idempotent when not active
assert.equal(pauseGoal(initialState(25), "x").status, "none");
});
test("resume only from paused; resets checks and clears reason", () => {
const paused = pauseGoal(setGoal(initialState(25), "goal"), "cap");
paused.checks = 9;
paused.noProgressReports = 2;
paused.workEventSinceReport = true;
const resumed = resumeGoal(paused);
assert.equal(resumed.status, "active");
assert.equal(resumed.checks, 0);
assert.equal(resumed.noProgressReports, 0);
assert.equal(resumed.workEventSinceReport, false);
assert.equal(resumed.pausedReason, undefined);
assert.equal(resumeGoal(initialState(25)).status, "none");
});
test("clearGoal returns to none", () => {
const cleared = clearGoal(setGoal(initialState(25), "goal", 9));
assert.equal(cleared.status, "none");
assert.equal(cleared.text, "");
assert.equal(cleared.maxChecks, 9);
});
test("report resets consecutive checks; limit and injection counting interlock", () => {
let s = setGoal(initialState(25), "goal", 3);
assert.equal(checkLimitReached(s), false);
s = recordCheckInjected(s);
s = recordCheckInjected(s);
assert.equal(checkLimitReached(s), false); // 2 of 3 injected, one slot left
s = recordCheckInjected(s);
assert.equal(checkLimitReached(s), true); // cap exhausted: auto-pause, no injection
s = recordReport(s);
assert.equal(checkLimitReached(s), false); // any report resets the counter
});
// ---------- env default cap ----------
test("environment caps: valid overrides and invalid values fall back", () => {
assert.equal(envDefaultMaxChecks({}), 25);
assert.equal(envDefaultMaxChecks({ GOAL_MAX_CHECKS: "10" }), 10);
assert.equal(envDefaultMaxChecks({ GOAL_MAX_CHECKS: "abc" }), 25);
assert.equal(envDefaultMaxChecks({ GOAL_MAX_CHECKS: "0" }), 25);
assert.equal(envDefaultMaxChecks({ GOAL_MAX_CHECKS: "" }), 25);
assert.equal(envDefaultMaxNoProgressReports({}), 3);
assert.equal(envDefaultMaxNoProgressReports({ GOAL_MAX_NO_PROGRESS_REPORTS: "5" }), 5);
assert.equal(envDefaultMaxNoProgressReports({ GOAL_MAX_NO_PROGRESS_REPORTS: "0" }), 3);
assert.equal(envDefaultMaxNoProgressReports({ GOAL_MAX_NO_PROGRESS_REPORTS: "bad" }), 3);
});
// ---------- command parsing ----------
test("parse: verbs and status", () => {
assert.deepEqual(parseGoalCommand(""), { kind: "status" });
assert.deepEqual(parseGoalCommand(" "), { kind: "status" });
assert.deepEqual(parseGoalCommand("stop"), { kind: "stop" });
assert.deepEqual(parseGoalCommand("STOP"), { kind: "stop" });
assert.deepEqual(parseGoalCommand("clear"), { kind: "clear" });
assert.deepEqual(parseGoalCommand("resume"), { kind: "resume" });
});
test("parse: set with plain text", () => {
assert.deepEqual(parseGoalCommand("ship the thing"), { kind: "set", text: "ship the thing", max: undefined });
assert.deepEqual(parseGoalCommand(" padded "), { kind: "set", text: "padded", max: undefined });
});
test("parse: --max forms attach to set", () => {
assert.deepEqual(parseGoalCommand("do it --max 5"), { kind: "set", text: "do it", max: 5 });
assert.deepEqual(parseGoalCommand("--max 5 do it"), { kind: "set", text: "do it", max: 5 });
assert.deepEqual(parseGoalCommand("do it --max=7"), { kind: "set", text: "do it", max: 7 });
assert.deepEqual(parseGoalCommand("--max 40 write the spec"), {
kind: "set",
text: "write the spec",
max: 40,
});
});
test("parse: --max errors", () => {
assert.equal(parseGoalCommand("--max").kind, "error");
assert.equal(parseGoalCommand("--max abc").kind, "error");
assert.equal(parseGoalCommand("text --max 0").kind, "error");
assert.equal(parseGoalCommand("text --max").kind, "error");
assert.equal(parseGoalCommand("--max 5").kind, "error"); // no goal text
assert.equal(parseGoalCommand("text --max 5.0").kind, "error");
});
test("parse: text containing verbs is goal text, not a verb", () => {
assert.deepEqual(parseGoalCommand("stop now"), { kind: "set", text: "stop now", max: undefined });
});
test("setGoal without --max uses fresh default, not the previous goal's cap", () => {
let s = setGoal(initialState(25), "first", 3);
assert.equal(s.maxChecks, 3);
s = clearGoal(s);
s = setGoal(s, "second");
assert.equal(s.maxChecks, 25); // per-goal --max: no leak from the capped goal
s = setGoal(s, "third", 40);
assert.equal(s.maxChecks, 40);
s = setGoal(s, "fourth");
assert.equal(s.maxChecks, 25);
assert.equal(setGoal(initialState(25), "g", undefined).maxChecks, envDefaultMaxChecks({ GOAL_MAX_CHECKS: "" }));
});
// ---------- settle decision (abort/error/cap pause paths, AC4 + AC5) ----------
test("settle: normal completion injects, abort and error pause", () => {
const s = setGoal(initialState(25), "goal");
assert.deepEqual(decideSettle(s, "toolUse"), { action: "inject" });
assert.deepEqual(decideSettle(s, "stop"), { action: "inject" });
assert.deepEqual(decideSettle(s, undefined), { action: "inject" });
const aborted = decideSettle(s, "aborted");
assert.equal(aborted.action, "pause");
assert.match((aborted as { reason: string }).reason, /aborted/);
const errored = decideSettle(s, "error");
assert.equal(errored.action, "pause");
assert.match((errored as { reason: string }).reason, /error/);
});
test("settle: cap exhaustion pauses with cap reason", () => {
const s = setGoal(initialState(25), "goal", 2);
s.checks = 2;
const d = decideSettle(s, "stop");
assert.equal(d.action, "pause");
assert.match((d as { reason: string }).reason, /cap of 2/);
// an injected-check counter reset by a report makes the cap pass again
assert.deepEqual(decideSettle(recordReport(s), "stop"), { action: "inject" });
});
// ---------- report validation (FR3) ----------
test("validateReport: satisfied and blocked require evidence; in_progress does not", () => {
assert.match(validateReport("satisfied", "")!, /evidence/);
assert.match(validateReport("satisfied", " ")!, /evidence/);
assert.match(validateReport("satisfied", undefined)!, /evidence/);
assert.match(validateReport("blocked", "")!, /reason/);
assert.match(validateReport("blocked", undefined)!, /reason/);
assert.equal(validateReport("in_progress", ""), null);
assert.equal(validateReport("satisfied", "counted to three"), null);
assert.equal(validateReport("blocked", "missing credentials"), null);
});
// ---------- store ----------
test("store: round-trip, missing file, corrupt file", () => {
const dir = mkdtempSync(join(tmpdir(), "goal-test-"));
try {
const path = join(dir, "goal-state.json");
assert.deepEqual(loadState(path), initialState()); // missing file -> initial
const s = setGoal(initialState(25), "durable goal", 11);
s.checks = 4;
s.noProgressReports = 2;
s.workEventSinceReport = true;
s.lastProgressFingerprint = "abc";
s.lastNextAction = "measure gate";
s.activeWait = { owner: "reviewer", watchId: "review-watch", nextCheck: "review arrives" };
saveState(s, path);
const loaded = loadState(path);
assert.equal(loaded.text, "durable goal");
assert.equal(loaded.status, "active");
assert.equal(loaded.maxChecks, 11);
assert.equal(loaded.checks, 4);
assert.equal(loaded.noProgressReports, 2);
assert.equal(loaded.workEventSinceReport, true);
assert.equal(loaded.lastProgressFingerprint, "abc");
assert.equal(loaded.lastNextAction, "measure gate");
assert.equal(loaded.activeWait?.watchId, "review-watch");
writeFileSync(path, "{not json", "utf8");
assert.deepEqual(loadState(path), initialState()); // corrupt -> initial
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test("store: legacy version-1 active state gains NG8 defaults without losing focus", () => {
const dir = mkdtempSync(join(tmpdir(), "goal-test-legacy-v1-"));
try {
const path = join(dir, "goal-state.legacy-v1.json");
writeFileSync(
path,
JSON.stringify({
version: 1,
text: "preserve this active goal",
status: "active",
checks: 7,
maxChecks: 19,
setAt: "2026-08-30T00:00:00.000Z",
}),
"utf8",
);
const state = loadState(path);
assert.equal(state.text, "preserve this active goal");
assert.equal(state.status, "active");
assert.equal(state.checks, 7);
assert.equal(state.maxChecks, 19);
assert.equal(state.noProgressReports, 0);
assert.equal(state.maxNoProgressReports, DEFAULT_MAX_NO_PROGRESS_REPORTS);
assert.equal(state.workEventSinceReport, false);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test("store: normalize repairs bad counters", () => {
const dir = mkdtempSync(join(tmpdir(), "goal-test-"));
try {
const path = join(dir, "goal-state.json");
writeFileSync(
path,
JSON.stringify({
version: 1,
text: "g",
status: "active",
checks: -3,
maxChecks: 0,
noProgressReports: -9,
maxNoProgressReports: 0,
workEventSinceReport: "yes",
setAt: "t",
}),
"utf8",
);
const s = loadState(path);
assert.equal(s.checks, 0);
assert.equal(s.maxChecks, DEFAULT_MAX_CHECKS);
assert.equal(s.noProgressReports, 0);
assert.equal(s.maxNoProgressReports, DEFAULT_MAX_NO_PROGRESS_REPORTS);
assert.equal(s.workEventSinceReport, false);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test("store: agent dir resolves from PI_CODING_AGENT_DIR with ~/.pi/agent fallback", () => {
assert.equal(agentStateDir({ PI_CODING_AGENT_DIR: "/seat/.pi/agent" }), "/seat/.pi/agent");
assert.equal(agentStateDir({}), join(homedir(), ".pi", "agent"));
assert.equal(stateFilePath("/d"), join("/d", "goal-state.json"));
});
test("store pauses invalid persisted bounded-wait configuration", () => {
const dir = mkdtempSync(join(tmpdir(), "goal-wait-invalid-"));
try {
const path = join(dir, "state.json");
for (const fields of [
{ waitTimeoutSeconds: -1 },
{ waitTimeoutSeconds: 60, activeWait: { owner: "operator", deadlineAt: 1e100 } },
{ waitTimeoutSeconds: 60, waitWakeUsed: "false" },
{ waitTimeoutSeconds: 60, activeWait: { owner: "operator" } },
{ waitTimeoutSeconds: 60, activeWait: { owner: "operator", deadlineAt: 123456, wakeSent: true } },
]) {
writeFileSync(path, JSON.stringify({ ...setGoal(initialState(), "output"), ...fields }));
assert.equal(loadState(path).status, "paused");
assert.match(loadState(path).pausedReason!, /invalid persisted wait/);
}
} finally { rmSync(dir, { recursive: true, force: true }); }
});
+180
View File
@@ -0,0 +1,180 @@
import assert from "node:assert/strict";
import { readFileSync, writeFileSync, mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
import { test } from "node:test";
import {
DEFAULT_MAX_NO_PROGRESS_REPORTS,
initialState,
recordCheckInjected,
recordInProgressReport,
recordWorkEvent,
setGoal,
type ProgressDetails,
} from "../lib/state.ts";
const measurement: ProgressDetails = {
kind: "measurement",
gate: "PR review",
owner: "rev-code-01",
lastMeasurement: "PR head abc123 remains unreviewed at 2026-08-31T15:00:00Z",
nextAction: "dispatch exact-head review",
};
function reportOnlyCycle(state: ReturnType<typeof setGoal>) {
const checked = recordCheckInjected(state);
return recordInProgressReport(checked, { evidence: "", progress: undefined }).state;
}
test("exact report-only loop pauses within the no-progress bound", () => {
let state = setGoal(initialState(), "ship", 25);
for (let n = 1; n <= DEFAULT_MAX_NO_PROGRESS_REPORTS; n++) {
state = reportOnlyCycle(state);
assert.equal(state.noProgressReports, n);
}
assert.equal(state.status, "paused");
assert.match(state.pausedReason ?? "", /no substantive progress/);
});
test("empty, malformed, and duplicate evidence never reset no-progress", () => {
let state = setGoal(initialState(), "ship", 25);
state = recordInProgressReport(recordCheckInjected(state), { evidence: "", progress: undefined }).state;
assert.equal(state.noProgressReports, 1);
state = recordInProgressReport(recordCheckInjected(state), {
evidence: "measured",
progress: { ...measurement, owner: "" },
}).state;
assert.equal(state.noProgressReports, 2);
state = recordWorkEvent(state, "read", "2026-08-31T15:00:00Z");
const accepted = recordInProgressReport(recordCheckInjected(state), {
evidence: "measured live PR state",
progress: measurement,
});
assert.equal(accepted.classification, "progress");
state = accepted.state;
assert.equal(state.noProgressReports, 0);
state = recordWorkEvent(state, "read", "2026-08-31T15:01:00Z");
const duplicate = recordInProgressReport(recordCheckInjected(state), {
evidence: "measured live PR state",
progress: measurement,
});
assert.equal(duplicate.classification, "no_progress");
assert.equal(duplicate.state.noProgressReports, 1);
});
test("measurement, action, and delegation require an observed successful work event", () => {
for (const kind of ["measurement", "action", "delegation"] as const) {
const details = { ...measurement, kind };
let state = setGoal(initialState(), kind, 25);
const unsupported = recordInProgressReport(recordCheckInjected(state), {
evidence: `${kind} claim`,
progress: details,
});
assert.equal(unsupported.classification, "no_progress");
state = recordWorkEvent(unsupported.state, kind === "action" ? "edit" : "bash", "2026-08-31T15:00:00Z");
const accepted = recordInProgressReport(recordCheckInjected(state), {
evidence: `${kind} completed with a new result`,
progress: { ...details, lastMeasurement: `${details.lastMeasurement} ${kind}` },
});
assert.equal(accepted.classification, "progress");
assert.equal(accepted.state.noProgressReports, 0);
assert.equal(accepted.state.checks, 0);
}
});
test("explicit non-tool work requires a concrete artifact", () => {
let state = setGoal(initialState(), "draft", 25);
const missing = recordInProgressReport(recordCheckInjected(state), {
evidence: "drafted reasoning",
progress: { ...measurement, kind: "non_tool" },
});
assert.equal(missing.classification, "no_progress");
const accepted = recordInProgressReport(recordCheckInjected(missing.state), {
evidence: "completed architecture decision",
progress: { ...measurement, kind: "non_tool", artifact: "work/decision-D12.md" },
});
assert.equal(accepted.classification, "progress");
assert.equal(accepted.state.noProgressReports, 0);
});
test("a legitimate external wait remains active without false-positive pause", () => {
let state = setGoal(initialState(), "await review", 25);
state = recordInProgressReport(recordCheckInjected(state), { evidence: "", progress: undefined }).state;
state = recordInProgressReport(recordCheckInjected(state), { evidence: "", progress: undefined }).state;
assert.equal(state.noProgressReports, 2);
const waiting: ProgressDetails = {
kind: "wait",
gate: "independent review",
owner: "rev-code-01",
lastMeasurement: "review dispatched at 2026-08-31T15:00:00Z",
nextAction: "merge only after PASS",
watchId: "ng8-review",
nextCheck: "review artifact exists",
};
for (let n = 0; n < 10; n++) {
const outcome = recordInProgressReport(recordCheckInjected(state), {
evidence: "approved watch remains active",
progress: waiting,
});
assert.equal(outcome.classification, "waiting");
state = outcome.state;
assert.equal(state.status, "active");
assert.equal(state.noProgressReports, 2);
assert.equal(state.checks, 0);
}
});
test("RED CONTROL: neutralizing no-progress counting restores the infinite loop", async () => {
const source = readFileSync(new URL("../lib/state.ts", import.meta.url), "utf8");
const sabotaged = source.replace(
"const nextNoProgress = state.noProgressReports + 1; // NG8_COUNT_GATE",
"const nextNoProgress = state.noProgressReports; // NG8_COUNT_GATE neutralized",
);
assert.notEqual(sabotaged, source, "count sabotage must change source");
const dir = mkdtempSync(join(tmpdir(), "goal-ng8-count-red-"));
try {
const path = join(dir, "state.ts");
writeFileSync(path, sabotaged);
const red = await import(`${pathToFileURL(path).href}?red=${Date.now()}`);
let state = red.setGoal(red.initialState(), "ship", 25);
for (let n = 0; n < DEFAULT_MAX_NO_PROGRESS_REPORTS + 2; n++) {
state = red.recordCheckInjected(state);
state = red.recordInProgressReport(state, { evidence: "", progress: undefined }).state;
}
assert.equal(state.status, "active", "RED: report-only loop survives when counting is neutralized");
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test("RED CONTROL: neutralizing pause enforcement leaves the counted loop active", async () => {
const source = readFileSync(new URL("../lib/state.ts", import.meta.url), "utf8");
const sabotaged = source.replace(
"if (nextNoProgress >= state.maxNoProgressReports) { // NG8_PAUSE_GATE",
"if (false && nextNoProgress >= state.maxNoProgressReports) { // NG8_PAUSE_GATE neutralized",
);
assert.notEqual(sabotaged, source, "pause sabotage must change source");
const dir = mkdtempSync(join(tmpdir(), "goal-ng8-pause-red-"));
try {
const path = join(dir, "state.ts");
writeFileSync(path, sabotaged);
const red = await import(`${pathToFileURL(path).href}?red=${Date.now()}`);
let state = red.setGoal(red.initialState(), "ship", 25);
for (let n = 0; n < DEFAULT_MAX_NO_PROGRESS_REPORTS; n++) {
state = red.recordCheckInjected(state);
state = red.recordInProgressReport(state, { evidence: "", progress: undefined }).state;
}
assert.equal(state.noProgressReports, DEFAULT_MAX_NO_PROGRESS_REPORTS);
assert.equal(state.status, "active", "RED: counted loop survives when pause gate is neutralized");
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
+27
View File
@@ -0,0 +1,27 @@
import assert from "node:assert/strict";
import { test } from "node:test";
import { initialState, setGoal, recordInProgressReport } from "../lib/state.ts";
import { decideSettle } from "../lib/settle.ts";
import { parseGoalCommand } from "../lib/parse.ts";
const wait = { evidence: "Dependency pending", progress: { kind: "wait" as const, gate: "input", owner: "operator", lastMeasurement: "input absent", nextAction: "Read input when available", nextCheck: "input file arrives" } };
test("opted-in waits suspend settle injection", () => {
const state = { ...setGoal(initialState(), "prepare output"), waitTimeoutSeconds: 60 };
const waiting = recordInProgressReport(state, wait).state;
assert.equal(decideSettle(waiting, "stop").action, "wait");
});
test("operator can select bounded waits per goal", () => {
assert.deepEqual(parseGoalCommand("--wait-timeout 60 --max 5 prepare output"), { kind: "set", text: "prepare output", max: 5, waitTimeoutSeconds: 60 });
});
test("timeout input is bounded and opt-in never leaks to another goal", () => {
for (const args of ["--wait-timeout", "--wait-timeout=", "--wait-timeout 0 x", "--wait-timeout 9 x", "--wait-timeout 86401 x", "--wait-timeout 60.0 x", "--wait-timeout 60", "--wait-timeout 60 --wait-timeout 30 x"]) {
assert.equal(parseGoalCommand(args).kind, "error", args);
}
const opted = setGoal(initialState(), "one", undefined, 60);
assert.equal(setGoal(opted, "two").waitTimeoutSeconds, undefined);
const legacyWait = recordInProgressReport(setGoal(initialState(), "legacy"), wait).state;
assert.equal(decideSettle(legacyWait, "stop").action, "inject");
});
+599
View File
@@ -0,0 +1,599 @@
import assert from "node:assert/strict";
import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { test } from "node:test";
import { fencedStateFilePath } from "../lib/store.ts";
import {
publishGoalPolicy,
registerGoalTrackedItemResolver,
resetGoalPolicyRegistryForTest,
} from "../../mosaic-core/lib/goal-policy.ts";
interface SentMessage {
content: string;
options?: { deliverAs?: "steer" | "followUp" };
}
const GOAL_ROOT = dirname(fileURLToPath(new URL("../index.ts", import.meta.url)));
const INCARNATION_SOURCE = join(GOAL_ROOT, "..", "mosaic-core", "lib", "incarnation.ts");
const EXECUTIVE_UPDATE_FORMAT = "structured progress in the ms-executive-update format (Just Completed / Next Step / Blocked)";
const PROACTIVE_LOOP_DIRECTIVE =
"Before reporting, run the ms-proactive-agent loop: reconcile records, perform the next authorized ready task, and verify its result. If no task can proceed, report a concrete wait or blocker. This extension owns goal lifecycle; use skills for task tracking, not a second goal loop.";
async function loadGoalExtension(root: string): Promise<(pi: any) => void> {
const extensionRoot = join(root, "extensions", "goal-runtime");
cpSync(join(GOAL_ROOT, "lib"), join(extensionRoot, "lib"), { recursive: true });
mkdirSync(join(root, "extensions", "mosaic-core", "lib"), { recursive: true });
cpSync(INCARNATION_SOURCE, join(root, "extensions", "mosaic-core", "lib", "incarnation.ts"));
cpSync(join(GOAL_ROOT, "..", "mosaic-core", "lib", "goal-policy.ts"), join(root, "extensions", "mosaic-core", "lib", "goal-policy.ts"));
const source = readFileSync(join(GOAL_ROOT, "index.ts"), "utf8");
const withoutTypebox = source.replace(
'import { Type } from "typebox";',
'const Type = { Object: (properties: unknown) => ({ type: "object", properties }), Optional: (schema: unknown) => schema, String: (options: unknown = {}) => ({ type: "string", ...(options as object) }) };',
);
const selfContained = withoutTypebox.replace(
'import { StringEnum } from "@earendil-works/pi-ai";',
'const StringEnum = (values: readonly string[]) => ({ type: "string", enum: values });',
);
assert.notEqual(withoutTypebox, source, "headless harness must replace TypeBox runtime import");
assert.notEqual(selfContained, withoutTypebox, "headless harness must replace pi-ai runtime import");
writeFileSync(join(extensionRoot, "index.ts"), selfContained);
return (await import(`${pathToFileURL(join(extensionRoot, "index.ts")).href}?runtime=${Date.now()}`)).default;
}
async function createHeadlessHarness(root: string, incarnation: string) {
const agentDir = join(root, "state", "goal");
const handlers = new Map<string, Array<(event: any, ctx: any) => any>>();
const commands = new Map<string, any>();
const tools = new Map<string, any>();
const sent: SentMessage[] = [];
const notifications: Array<{ message: string; level: string }> = [];
const widgets: unknown[] = [];
const statuses: string[] = [];
const shortcuts = new Map<string, any>();
const previousAgentDir = process.env.PI_CODING_AGENT_DIR;
const previousIncarnation = process.env.MOSAIC_LAUNCH_INCARNATION;
process.env.PI_CODING_AGENT_DIR = agentDir;
process.env.MOSAIC_LAUNCH_INCARNATION = incarnation;
const pi: any = {
on(name: string, handler: (event: any, ctx: any) => any) {
handlers.set(name, [...(handlers.get(name) ?? []), handler]);
},
registerCommand(name: string, command: any) {
commands.set(name, command);
},
registerShortcut(key: string, shortcut: any) { shortcuts.set(key, shortcut); },
registerTool(tool: any) {
tools.set(tool.name, tool);
},
sendUserMessage(content: string, options?: SentMessage["options"]) {
sent.push({ content, options });
},
};
let idle = false;
const ctx: any = {
mode: "interactive",
hasUI: true,
isIdle: () => idle,
ui: {
theme: { fg: (color: string, text: string) => `[${color}]${text}` },
setStatus(_id: string, text: string) { statuses.push(text); },
notify(message: string, level: string) {
notifications.push({ message, level });
},
setWidget(_id: string, value: unknown) {
widgets.push(value);
},
},
sessionManager: {
getBranch: () => [{ type: "message", message: { role: "assistant", stopReason: "stop" } }],
},
};
const goalExtension = await loadGoalExtension(root);
goalExtension(pi);
return {
commands,
tools,
sent,
notifications,
widgets,
statuses,
shortcuts,
ctx,
setIdle(value: boolean) {
idle = value;
},
async emit(name: string, event: any = { type: name }) {
for (const handler of handlers.get(name) ?? []) await handler(event, ctx);
},
state() {
return JSON.parse(readFileSync(fencedStateFilePath(agentDir, incarnation), "utf8"));
},
restoreEnv() {
if (previousAgentDir === undefined) delete process.env.PI_CODING_AGENT_DIR;
else process.env.PI_CODING_AGENT_DIR = previousAgentDir;
if (previousIncarnation === undefined) delete process.env.MOSAIC_LAUNCH_INCARNATION;
else process.env.MOSAIC_LAUNCH_INCARNATION = previousIncarnation;
},
};
}
test("goal reporting instructions name the executive-update format and proactive loop", () => {
const source = readFileSync(join(GOAL_ROOT, "index.ts"), "utf8");
const checkPrompt = source.slice(source.indexOf("function checkPrompt(): string"), source.indexOf("/** Inject one check prompt"));
const goalReport = source.slice(source.indexOf('name: "goal_report"'), source.indexOf("promptSnippet:", source.indexOf('name: "goal_report"')));
const beforeAgentStart = source.slice(source.indexOf('pi.on("before_agent_start"'), source.indexOf('pi.on("tool_result"'));
for (const [section, text] of Object.entries({ checkPrompt, goalReport, beforeAgentStart })) {
assert.ok(text.includes(EXECUTIVE_UPDATE_FORMAT), `${section} must name the executive-update format`);
assert.ok(text.includes(PROACTIVE_LOOP_DIRECTIVE), `${section} must name the proactive loop`);
}
});
test("headless runtime exercises goal_report, agent_settled, follow-up injection, and pause", async () => {
const dir = mkdtempSync(join(tmpdir(), "goal-ng8-runtime-"));
const harness = await createHeadlessHarness(dir, "ng8-headless-runtime");
try {
const goal = harness.commands.get("goal");
const report = harness.tools.get("goal_report");
assert.ok(goal);
assert.ok(report);
await goal.handler("ship safely", harness.ctx);
assert.equal(harness.sent.length, 1);
assert.equal(harness.sent[0].options?.deliverAs, "followUp");
assert.match(harness.sent[0].content, /no-progress 0\/3/);
for (let cycle = 1; cycle <= 3; cycle++) {
const result = await report.execute(`report-${cycle}`, { status: "in_progress" }, undefined, undefined, harness.ctx);
await harness.emit("tool_result", {
type: "tool_result",
toolCallId: `report-${cycle}`,
toolName: "goal_report",
input: { status: "in_progress" },
content: result.content,
isError: false,
});
await harness.emit("agent_settled");
if (cycle < 3) assert.equal(harness.sent.length, cycle + 1);
}
assert.equal(harness.sent.length, 3, "paused loop injects no fourth check");
assert.equal(harness.state().status, "paused");
assert.equal(harness.state().noProgressReports, 3);
assert.ok(harness.notifications.some((notice) => notice.level === "warning" && /no substantive progress/.test(notice.message)));
} finally {
harness.restoreEnv();
rmSync(dir, { recursive: true, force: true });
}
});
test("C7/C8: attested parser and resolver reject before Goal state changes", async () => {
resetGoalPolicyRegistryForTest();
const dir = mkdtempSync(join(tmpdir(), "goal-v4-policy-"));
const incarnation = "inc-goal-v4";
const harness = await createHeadlessHarness(dir, incarnation);
try {
assert.equal(registerGoalTrackedItemResolver(incarnation, async () => Object.freeze({ outcome: "zero" as const })).ok, true);
const journalReasons: string[] = [];
const publication = publishGoalPolicy({
schemaVersion: 1,
role: "plan-ng",
roleRevision: 4,
manifestSha256: "a".repeat(64),
format: "ms-executive-update/v1",
contractPath: "skills-local/ms-executive-update/SKILL.md",
contractSection: "Machine contract (for `goal_report` payloads and any parser)",
contractBlob: "df30c6fbb54b4a65a298c9e51c07f742610d171c",
contractSha256: "bbea48a46b1f8da7bc759f86856fb52830b7dde456b826317163c6dc6ccab319",
enforcement: "pre-state-change-fail-closed",
identifierResolution: "consumer-fail-closed",
launchGeneration: 1,
incarnationId: incarnation,
}, (reason) => journalReasons.push(reason));
assert.equal(publication.ok, true);
const goal = harness.commands.get("goal");
const report = harness.tools.get("goal_report");
await goal.handler("attested policy", harness.ctx);
const before = harness.state();
const update = [
"Just Completed:", "", "* T165: contracts added", "", "Next Step:", "", "* T165: run tests", "", "Blocked:", "", "* T165: none \u2014 nothing from you", "",
].join("\n");
await assert.rejects(
() => report.execute("attested-reject", { status: "satisfied", evidence: update }, undefined, undefined, harness.ctx),
/resolver-zero/,
);
const after = harness.state();
assert.equal(after.status, "active");
assert.equal(after.checks, before.checks);
assert.equal(after.noProgressReports, before.noProgressReports);
const noChange = "No change since T165; still waiting on T165.";
for (const status of ["satisfied", "blocked"] as const) {
await assert.rejects(
() => report.execute(`no-change-${status}`, { status, evidence: noChange }, undefined, undefined, harness.ctx),
/no-change-status/,
);
}
const terminalAfter = harness.state();
assert.equal(terminalAfter.status, "active", "unchanged form cannot settle or pause the goal");
assert.equal(terminalAfter.checks, before.checks);
assert.equal(terminalAfter.noProgressReports, before.noProgressReports);
assert.deepEqual(
journalReasons,
["resolver-zero", "no-change-status", "no-change-status"],
"all denials journal stable codes without report payload text",
);
} finally {
resetGoalPolicyRegistryForTest();
harness.restoreEnv();
rmSync(dir, { recursive: true, force: true });
}
});
test("headless runtime accepts a tool-backed measurement and preserves terminal behavior", async () => {
const dir = mkdtempSync(join(tmpdir(), "goal-ng8-runtime-work-"));
const harness = await createHeadlessHarness(dir, "ng8-headless-work");
try {
const goal = harness.commands.get("goal");
const report = harness.tools.get("goal_report");
await goal.handler("measure", harness.ctx);
await harness.emit("tool_result", {
type: "tool_result",
toolCallId: "read-error",
toolName: "read",
input: { path: "/tmp/missing" },
content: [{ type: "text", text: "missing" }],
isError: true,
});
assert.equal(harness.state().workEventSinceReport, false, "failed tools are not progress events");
await harness.emit("tool_result", {
type: "tool_result",
toolCallId: "read-1",
toolName: "read",
input: { path: "/tmp/status" },
content: [{ type: "text", text: "measured" }],
isError: false,
});
assert.equal(harness.state().workEventSinceReport, true);
const progress = {
kind: "measurement",
gate: "review",
owner: "reviewer",
lastMeasurement: "head abc remains pending at 2026-08-31T15:00:00Z",
nextAction: "dispatch reviewer",
};
const accepted = await report.execute(
"progress-1",
{ status: "in_progress", evidence: "measured exact head", progress },
undefined,
undefined,
harness.ctx,
);
assert.match(accepted.content[0].text, /Recorded substantive progress/);
assert.equal(harness.state().noProgressReports, 0);
assert.equal(harness.state().workEventSinceReport, false);
await goal.handler("done goal", harness.ctx);
await assert.rejects(
() => report.execute("satisfied-empty", { status: "satisfied" }, undefined, undefined, harness.ctx),
/requires non-empty evidence/,
);
const satisfied = await report.execute(
"satisfied",
{ status: "satisfied", evidence: "acceptance evidence" },
undefined,
undefined,
harness.ctx,
);
assert.match(satisfied.content[0].text, /SATISFIED/);
assert.equal(harness.state().status, "none");
await goal.handler("blocked goal", harness.ctx);
const blocked = await report.execute(
"blocked",
{ status: "blocked", evidence: "missing credential" },
undefined,
undefined,
harness.ctx,
);
assert.match(blocked.content[0].text, /BLOCKED/);
assert.equal(harness.state().status, "blocked");
} finally {
harness.restoreEnv();
rmSync(dir, { recursive: true, force: true });
}
});
const waitReport = {
status: "in_progress", evidence: "Input is pending",
progress: { kind: "wait", gate: "input", owner: "operator", lastMeasurement: "input absent",
nextAction: "Read input and finish output", nextCheck: "input file available" },
};
test("quiet wait: no heartbeats, one deadline wake, unresolved dependency pauses", async (t) => {
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 100000 });
const dir = mkdtempSync(join(tmpdir(), "goal-quiet-runtime-"));
const harness = await createHeadlessHarness(dir, "quiet-runtime");
try {
harness.setIdle(true);
const goal = harness.commands.get("goal");
const report = harness.tools.get("goal_report");
await goal.handler("--wait-timeout 60 write output", harness.ctx);
await report.execute("wait-1", waitReport, undefined, undefined, harness.ctx);
const deadline = harness.state().activeWait.deadlineAt;
for (let i = 0; i < 100; i++) await harness.emit("agent_settled");
assert.equal(harness.sent.length, 1, "settle events cannot generate idle heartbeats");
t.mock.timers.tick(30000);
await report.execute("wait-2", { ...waitReport, evidence: "Still pending, differently worded" }, undefined, undefined, harness.ctx);
assert.equal(harness.state().activeWait.deadlineAt, deadline, "unchanged waits never extend deadline");
t.mock.timers.tick(29999);
assert.equal(harness.sent.length, 1);
t.mock.timers.tick(1);
assert.equal(harness.sent.length, 2);
assert.match(harness.sent[1].content, /Wait deadline reached/);
assert.equal(harness.state().waitWakeUsed, true);
const result = await report.execute("wait-3", waitReport, undefined, undefined, harness.ctx);
assert.equal(result.details.goalStatus, "paused");
t.mock.timers.tick(86400000);
await harness.emit("agent_settled");
assert.equal(harness.sent.length, 2, "unresolved deadline never re-arms itself");
await assert.rejects(() => report.execute("late-satisfied", { status: "satisfied", evidence: "late result" }, undefined, undefined, harness.ctx), /paused/);
} finally {
await harness.emit("session_shutdown");
harness.restoreEnv();
rmSync(dir, { recursive: true, force: true });
}
});
test("quiet wait: persisted deadline survives reload without an immediate check", async (t) => {
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 100000 });
const dir = mkdtempSync(join(tmpdir(), "goal-quiet-reload-"));
const harness = await createHeadlessHarness(dir, "quiet-reload");
try {
harness.setIdle(true);
await harness.commands.get("goal").handler("--wait-timeout 60 write output", harness.ctx);
await harness.tools.get("goal_report").execute("wait", waitReport, undefined, undefined, harness.ctx);
await harness.emit("session_shutdown");
t.mock.timers.tick(30000);
await harness.emit("session_start");
assert.equal(harness.sent.length, 1);
t.mock.timers.tick(30000);
assert.equal(harness.sent.length, 2);
assert.match(harness.sent[1].content, /Wait deadline reached/);
} finally {
await harness.emit("session_shutdown");
harness.restoreEnv();
rmSync(dir, { recursive: true, force: true });
}
});
test("quiet wait: stop, clear, replacement, and completion cancel the timer", async (t) => {
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 100000 });
const dir = mkdtempSync(join(tmpdir(), "goal-quiet-cancel-"));
const harness = await createHeadlessHarness(dir, "quiet-cancel");
try {
harness.setIdle(true);
const goal = harness.commands.get("goal");
const report = harness.tools.get("goal_report");
for (const operation of ["stop", "clear", "replacement", "satisfied"]) {
await goal.handler("--wait-timeout 60 write output", harness.ctx);
await report.execute("wait", waitReport, undefined, undefined, harness.ctx);
if (operation === "satisfied") {
await report.execute("done", { status: "satisfied", evidence: "Output verified" }, undefined, undefined, harness.ctx);
} else {
await goal.handler(operation, harness.ctx);
}
const before = harness.sent.length;
t.mock.timers.tick(61000);
assert.equal(harness.sent.length, before, `${operation} leaves no deadline wake`);
}
} finally {
await harness.emit("session_shutdown");
harness.restoreEnv();
rmSync(dir, { recursive: true, force: true });
}
});
test("quiet wait: a busy turn is allowed to settle before deadline injection", async (t) => {
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 100000 });
const dir = mkdtempSync(join(tmpdir(), "goal-quiet-busy-"));
const harness = await createHeadlessHarness(dir, "quiet-busy");
try {
await harness.commands.get("goal").handler("--wait-timeout 60 write output", harness.ctx);
await harness.tools.get("goal_report").execute("wait", waitReport, undefined, undefined, harness.ctx);
t.mock.timers.tick(60000);
assert.equal(harness.sent.length, 1, "no deadline queued during a running turn");
await harness.commands.get("goal").handler("stop", harness.ctx);
await harness.emit("agent_settled");
assert.equal(harness.sent.length, 1, "stop cancels deferred deadline");
await harness.commands.get("goal").handler("resume", harness.ctx);
assert.equal(harness.state().activeWait, undefined);
await harness.tools.get("goal_report").execute("wait-again", waitReport, undefined, undefined, harness.ctx);
t.mock.timers.tick(60000);
const before = harness.sent.length;
await harness.emit("agent_settled");
assert.equal(harness.sent.length, before + 1);
} finally {
await harness.emit("session_shutdown");
harness.restoreEnv();
rmSync(dir, { recursive: true, force: true });
}
});
test("quiet wait: relevant progress cancels the wait; print mode has no timer wake", async (t) => {
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 100000 });
const dir = mkdtempSync(join(tmpdir(), "goal-quiet-progress-"));
const harness = await createHeadlessHarness(dir, "quiet-progress");
try {
harness.setIdle(true);
const goal = harness.commands.get("goal");
const report = harness.tools.get("goal_report");
await goal.handler("--wait-timeout 60 write output", harness.ctx);
await report.execute("wait", waitReport, undefined, undefined, harness.ctx);
await harness.emit("tool_result", { toolName: "read", isError: false });
await report.execute("progress", { status: "in_progress", evidence: "Input arrived and was verified", progress: { ...waitReport.progress, kind: "measurement", lastMeasurement: "Input present" } }, undefined, undefined, harness.ctx);
assert.equal(harness.state().activeWait, undefined);
t.mock.timers.tick(60000);
assert.equal(harness.sent.length, 1);
await harness.emit("agent_settled");
assert.equal(harness.sent.length, 2, "ready work continues after wait is resolved");
harness.ctx.mode = "print";
await goal.handler("--wait-timeout 60 write output", harness.ctx);
await report.execute("wait-print", waitReport, undefined, undefined, harness.ctx);
t.mock.timers.tick(60000);
await harness.emit("agent_settled");
assert.equal(harness.sent.length, 2, "print mode never schedules a model wake");
} finally {
await harness.emit("session_shutdown");
harness.restoreEnv();
rmSync(dir, { recursive: true, force: true });
}
});
test("deadline readiness recovers after compaction without agent_settled", async (t) => {
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 100000 });
const dir = mkdtempSync(join(tmpdir(), "goal-compaction-"));
const harness = await createHeadlessHarness(dir, "quiet-compaction");
try {
await harness.commands.get("goal").handler("--wait-timeout 60 output", harness.ctx);
await harness.tools.get("goal_report").execute("wait", waitReport, undefined, undefined, harness.ctx);
t.mock.timers.tick(60000);
assert.equal(harness.sent.length, 1);
harness.setIdle(true);
t.mock.timers.tick(1000);
assert.equal(harness.sent.length, 2, "runtime readiness alone releases the deferred wake");
} finally {
await harness.emit("session_shutdown"); harness.restoreEnv(); rmSync(dir, { recursive: true, force: true });
}
});
test("unobserved deadline dispatch pauses once; observed start cancels watchdog", async (t) => {
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 100000 });
const dir = mkdtempSync(join(tmpdir(), "goal-dispatch-"));
const harness = await createHeadlessHarness(dir, "quiet-dispatch");
try {
harness.setIdle(true);
const goal = harness.commands.get("goal");
const report = harness.tools.get("goal_report");
await goal.handler("--wait-timeout 60 output", harness.ctx);
await report.execute("wait", waitReport, undefined, undefined, harness.ctx);
t.mock.timers.tick(60000);
const sent = harness.sent.length;
// Ordinary Pi sendUserMessage returns void; a rejected async delivery may never start.
t.mock.timers.tick(30000);
assert.equal(harness.state().status, "paused");
assert.match(harness.state().pausedReason, /not observed/);
assert.equal(harness.sent.length, sent, "watchdog never retries a potentially delivered request");
await goal.handler("resume", harness.ctx);
await report.execute("wait-again", waitReport, undefined, undefined, harness.ctx);
t.mock.timers.tick(60000);
await harness.emit("before_agent_start", { prompt: harness.sent.at(-1)!.content, systemPrompt: "base" });
assert.equal(harness.state().activeWait.wakeObserved, true);
t.mock.timers.tick(30000);
assert.equal(harness.state().status, "active", "observed model start cancels the acknowledgment timer");
} finally {
await harness.emit("session_shutdown"); harness.restoreEnv(); rmSync(dir, { recursive: true, force: true });
}
});
test("continuously busy runtime pauses after bounded readiness checks", async (t) => {
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 100000 });
const dir = mkdtempSync(join(tmpdir(), "goal-busy-limit-"));
const harness = await createHeadlessHarness(dir, "quiet-busy-limit");
try {
await harness.commands.get("goal").handler("--wait-timeout 60 output", harness.ctx);
await harness.tools.get("goal_report").execute("wait", waitReport, undefined, undefined, harness.ctx);
t.mock.timers.tick(60000);
for (let i = 0; i < 60; i++) t.mock.timers.tick(1000);
assert.equal(harness.state().status, "paused");
assert.match(harness.state().pausedReason, /remained busy/);
assert.equal(harness.sent.length, 1);
} finally {
await harness.emit("session_shutdown"); harness.restoreEnv(); rmSync(dir, { recursive: true, force: true });
}
});
test("footer and both recall paths cover lifecycle without adding widget content", async () => {
const dir = mkdtempSync(join(tmpdir(), "goal-footer-"));
const harness = await createHeadlessHarness(dir, "footer");
const previousNoColor = process.env.NO_COLOR;
delete process.env.NO_COLOR;
try {
const goal = harness.commands.get("goal");
const report = harness.tools.get("goal_report");
const text = "Full goal " + "acceptance criteria ".repeat(100) + " END-OF-GOAL";
await harness.emit("session_start");
assert.match(harness.statuses.at(-1)!, /\[muted\]Goal: None/);
await goal.handler(text, harness.ctx);
assert.match(harness.statuses.at(-1)!, /\[accent\]Goal: Active/);
const sent = harness.sent.length;
await goal.handler("", harness.ctx);
const recall = harness.notifications.at(-1)!.message;
assert.ok(recall.includes(text));
await harness.shortcuts.get("alt+g").handler(harness.ctx);
assert.equal(harness.notifications.at(-1)!.message, recall);
assert.equal(harness.sent.length, sent, "recall never starts a turn");
assert.equal(harness.shortcuts.has("tab"), false);
await report.execute("wait", waitReport, undefined, undefined, harness.ctx);
assert.match(harness.statuses.at(-1)!, /\[warning\]Goal: Waiting/);
await goal.handler("stop", harness.ctx);
assert.match(harness.statuses.at(-1)!, /\[warning\]Goal: Paused/);
await goal.handler("resume", harness.ctx);
await report.execute("blocked", { status: "blocked", evidence: "permission needed" }, undefined, undefined, harness.ctx);
assert.match(harness.statuses.at(-1)!, /\[error\]Goal: Blocked/);
await goal.handler("resume", harness.ctx);
await report.execute("done", { status: "satisfied", evidence: "verified" }, undefined, undefined, harness.ctx);
assert.match(harness.statuses.at(-1)!, /\[success\]Goal: Complete/);
assert.equal(harness.state().lastOutcome.text, text);
const finishedSent = harness.sent.length;
await harness.emit("agent_settled");
await harness.emit("session_start");
await goal.handler("resume", harness.ctx);
assert.equal(harness.sent.length, finishedSent);
await goal.handler("", harness.ctx);
assert.ok(harness.notifications.at(-1)!.message.includes(text));
process.env.NO_COLOR = "1";
await harness.emit("session_start");
assert.equal(harness.statuses.at(-1), "Goal: Complete (/goal, Alt+G)");
await goal.handler("clear", harness.ctx);
assert.equal(harness.state().lastOutcome, undefined);
assert.equal(harness.statuses.at(-1), "Goal: None (/goal, Alt+G)");
assert.ok(harness.widgets.every(value => value === undefined));
} finally {
if (previousNoColor === undefined) delete process.env.NO_COLOR;
else process.env.NO_COLOR = previousNoColor;
await harness.emit("session_shutdown"); harness.restoreEnv(); rmSync(dir, { recursive: true, force: true });
}
});
test("state-write failure cannot report goal completion or inject another check", async () => {
const dir = mkdtempSync(join(tmpdir(), "goal-save-failure-"));
const incarnation = "quiet-save-failure";
const harness = await createHeadlessHarness(dir, incarnation);
try {
await harness.commands.get("goal").handler("--wait-timeout 60 output", harness.ctx);
const statePath = fencedStateFilePath(join(dir, "state", "goal"), incarnation);
rmSync(statePath);
mkdirSync(statePath); // force atomic replacement failure without mocking away filesystem behavior
await assert.rejects(() => harness.tools.get("goal_report").execute("done", { status: "satisfied", evidence: "done" }, undefined, undefined, harness.ctx), /could not be saved/);
await harness.emit("agent_settled");
assert.equal(harness.sent.length, 1);
assert.equal(harness.notifications.some((n) => n.message.includes("satisfied and cleared")), false);
} finally {
await harness.emit("session_shutdown"); harness.restoreEnv(); rmSync(dir, { recursive: true, force: true });
}
});