Fix malformed Face Mind retry routing
This commit is contained in:
parent
aa0b13ae1c
commit
72089c73e4
5 changed files with 78 additions and 14 deletions
|
|
@ -544,6 +544,7 @@ async function executeRepoFaceJobWithInterpreter(
|
|||
const firstInterpretation = firstForbiddenTools.length > 0
|
||||
? {
|
||||
decision: "retry" as const,
|
||||
parseable: true,
|
||||
reasons: [
|
||||
`Face used forbidden substrate/tool-discovery tool(s): ${firstForbiddenTools.join(", ")}`,
|
||||
"Face turns may use retrieval tools only; state and substrate inventory belong to the parent/interpreter.",
|
||||
|
|
@ -563,6 +564,48 @@ async function executeRepoFaceJobWithInterpreter(
|
|||
return dropRepoFaceActionBlocks(firstResponse, firstInterpretation);
|
||||
}
|
||||
|
||||
if (!firstInterpretation.parseable) {
|
||||
await auditLog.record({
|
||||
type: "repo_face.parent_interpreter_retry",
|
||||
actorId: job.requester.id,
|
||||
jobId: job.id,
|
||||
provider: job.provider,
|
||||
details: {
|
||||
decision: firstInterpretation.decision,
|
||||
reasons: firstInterpretation.reasons,
|
||||
owner: "parent_interpreter",
|
||||
},
|
||||
});
|
||||
console.warn(
|
||||
`Repo Face parent interpreter retrying its own malformed output for job ${job.id}: ${firstInterpretation.reasons.join("; ")}`,
|
||||
);
|
||||
const secondInterpretation = await interpretRepoFaceTurnOutput(provider, job, firstText, {
|
||||
attempt: 2,
|
||||
});
|
||||
if (secondInterpretation.decision === "route" && secondInterpretation.parseable) {
|
||||
return routeRepoFaceInterpretedOutput(firstResponse, secondInterpretation, {
|
||||
job,
|
||||
faceOutputText: firstText,
|
||||
});
|
||||
}
|
||||
|
||||
await auditLog.record({
|
||||
type: "repo_face.parent_interpreter_drop",
|
||||
actorId: job.requester.id,
|
||||
jobId: job.id,
|
||||
provider: job.provider,
|
||||
details: {
|
||||
decision: secondInterpretation.decision,
|
||||
reasons: secondInterpretation.reasons,
|
||||
owner: "parent_interpreter",
|
||||
},
|
||||
});
|
||||
console.warn(
|
||||
`Repo Face parent interpreter dropped job ${job.id} after malformed interpreter retry: ${secondInterpretation.reasons.join("; ")}`,
|
||||
);
|
||||
return dropRepoFaceActionBlocks(firstResponse, secondInterpretation);
|
||||
}
|
||||
|
||||
await auditLog.record({
|
||||
type: "repo_face.parent_interpreter_retry",
|
||||
actorId: job.requester.id,
|
||||
|
|
@ -597,6 +640,7 @@ async function executeRepoFaceJobWithInterpreter(
|
|||
const retryInterpretation = retryForbiddenTools.length > 0
|
||||
? {
|
||||
decision: "drop" as const,
|
||||
parseable: true,
|
||||
reasons: [
|
||||
`Face used forbidden substrate/tool-discovery tool(s) on retry: ${retryForbiddenTools.join(", ")}`,
|
||||
"Dropping action blocks rather than routing a turn that crossed the child/tool boundary.",
|
||||
|
|
@ -630,6 +674,7 @@ async function executeRepoFaceJobWithInterpreter(
|
|||
|
||||
interface RepoFaceParentInterpretation {
|
||||
decision: "route" | "retry" | "drop";
|
||||
parseable: boolean;
|
||||
reasons: string[];
|
||||
routedOutput?: string;
|
||||
}
|
||||
|
|
@ -714,8 +759,9 @@ function parseRepoFaceParentInterpretation(
|
|||
): RepoFaceParentInterpretation {
|
||||
const block = parseInterpretationBlock(interpretationText);
|
||||
const decision = block.decision?.trim().toLowerCase();
|
||||
const parseable = decision === "route" || decision === "retry" || decision === "drop";
|
||||
const parsedDecision =
|
||||
decision === "route" || decision === "retry" || decision === "drop"
|
||||
parseable
|
||||
? decision
|
||||
: attempt === 1
|
||||
? "retry"
|
||||
|
|
@ -728,7 +774,12 @@ function parseRepoFaceParentInterpretation(
|
|||
|
||||
return {
|
||||
decision: parsedDecision,
|
||||
reasons: reasons.length > 0 ? reasons : ["parent interpreter did not provide a parseable reason"],
|
||||
parseable,
|
||||
reasons: reasons.length > 0
|
||||
? reasons
|
||||
: [parseable
|
||||
? "parent interpreter did not provide a parseable reason"
|
||||
: "parent interpreter did not provide a parseable INTERPRETATION decision"],
|
||||
routedOutput: extractRoutedRepoFaceOutput(interpretationText),
|
||||
};
|
||||
}
|
||||
|
|
@ -886,8 +937,10 @@ function dropRepoFaceActionBlocks(
|
|||
interpretation: RepoFaceParentInterpretation,
|
||||
): ProviderResponse {
|
||||
const text = fitDiscordMessage(response.outputText ?? response.summary);
|
||||
const privateSummary = stripRepoIdentityPostIntents(text) ||
|
||||
`Parent interpreter dropped repo Face action blocks: ${interpretation.reasons.join("; ")}`;
|
||||
const stripped = stripRepoIdentityPostIntents(text);
|
||||
const privateSummary = stripped !== text && stripped.trim().length > 0
|
||||
? stripped
|
||||
: `Parent interpreter dropped repo Face output: ${interpretation.reasons.join("; ")}`;
|
||||
return {
|
||||
...response,
|
||||
outputText: privateSummary,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou
|
|||
## Current Shape
|
||||
|
||||
- Discord bot and worker may be live, but the unattended repo Face / CTB swarm brake is active as of 2026-05-26.
|
||||
- The swarm was paused because live Face turns leaked owner-reply/Mind scaffolding into public Discord: Mind was being wrapped by the owner Discord reply prompt, then a worker fallback treated plain malformed Mind prose as public `SAY`. The fixed worker stack was restarted on 2026-05-26, typecheck/build pass, `agents:interpreter-loop-smoke` passes, and `agents:heartbeat-model-smoke -- --model gpt-5.4 --reasoning-effort none` passes. The Mind prompt has now been slimmed from incident-by-incident scar tissue into semantic route/retry/drop judgment plus a hard STATE NOTE/ARTICLE/SAY DSL contract. Keep `state/agent-swarm-paused.json` active until the operator explicitly asks to thaw; before thawing, run one controlled current-room turn and inspect model-output logs for `INTERPRETATION` plus explicit DSL actions only. Bifrost dispatch remains disabled by default behind `BIFROST_DISPATCH_ENABLED=false`.
|
||||
- The swarm is paused because live Face turns leaked owner-reply/Mind scaffolding into public Discord twice on 2026-05-26. The first failure wrapped parent Mind in the owner Discord reply prompt and let malformed plain Mind prose become public `SAY`. The second thaw attempt showed the deeper split: malformed parent Mind output caused the worker to ask the Face to revise, and that retry path turned owner-question boilerplate into Bifrost/Nibu `SAY`. The current worker retries malformed Mind output as Mind-owned interpretation, drops to diagnostic private summary instead of re-prompting the Face when Mind stays malformed, and never preserves raw dropped prose as the job summary. `scripts/inspect-model-output-logs.mjs` now flags repo-face parent interpreter records missing an `INTERPRETATION` decision. Keep `state/agent-swarm-paused.json` active until a new controlled thaw explicitly proves the live worker, parent Mind, model-output inspection, and Discord side effects all agree.
|
||||
- The teardown authority remains `notes/agent-swarm-teardown-plan-2026-05-21.md`, but it is now a restart checklist and smell ledger rather than a stop sign. Keep using it when a Face/Bifrost/CTB boundary smells like prompt/parser/transport Jenga.
|
||||
- The repo-controlled pause flag is `state/agent-swarm-paused.json`. While it is missing or has `"paused": false`, swarm runners may operate. While it has `"paused": true` or is malformed, the Face heartbeat runner skips and the orchestrator skips agent-swarm organs. This is the brake. Do not route around it.
|
||||
- Postgres owns jobs, audit events, interaction memory, and rate-limit state.
|
||||
|
|
|
|||
|
|
@ -64,6 +64,11 @@ function inspect(records) {
|
|||
const speechSmells = records.filter((record) =>
|
||||
/repo-face heartbeat|heartbeat from|bright bridge note|tiny fish sorting note|librarian note|maintenance pass/i.test(String(record.finalMessage ?? "")),
|
||||
);
|
||||
const malformedRepoFaceInterpreter = records.filter((record) =>
|
||||
record.command === "repo-face-rumination" &&
|
||||
record.promptMarker === "repo-face-turn-interpreter" &&
|
||||
!/\bINTERPRETATION\b[\s\S]*?\bdecision:\s*(route|retry|drop)\b/i.test(String(record.finalMessage ?? "")),
|
||||
);
|
||||
const quotaSignals = records.filter((record) =>
|
||||
/quota|rate limit|rate-limit|usage limit|capacity|too many requests|(?:http|status|code|error)\s*429|429\s*(?:too many requests|rate)|insufficient_quota|model.*unavailable|model.*access|limit exceeded/i.test(
|
||||
`${record.stderrTail ?? ""}\n${record.stdoutTail ?? ""}\n${record.handoffReason ?? ""}`,
|
||||
|
|
@ -74,6 +79,7 @@ function inspect(records) {
|
|||
pushWarning(warnings, noFinalMessage.length, "model runs with no final message", noFinalMessage);
|
||||
pushWarning(warnings, forbiddenToolRecords.length, "Face/model runs using forbidden substrate tools", forbiddenToolRecords);
|
||||
pushWarning(warnings, speechSmells.length, "robotic/provenance speech smells", speechSmells);
|
||||
pushWarning(warnings, malformedRepoFaceInterpreter.length, "repo Face parent interpreter outputs missing INTERPRETATION decision", malformedRepoFaceInterpreter);
|
||||
pushWarning(warnings, quotaSignals.length, "quota/rate/capacity fallback signals", quotaSignals);
|
||||
|
||||
return {
|
||||
|
|
@ -84,6 +90,7 @@ function inspect(records) {
|
|||
noFinalMessage: summarizeRecords(noFinalMessage),
|
||||
forbiddenToolRecords: summarizeRecords(forbiddenToolRecords),
|
||||
speechSmells: summarizeRecords(speechSmells),
|
||||
malformedRepoFaceInterpreter: summarizeRecords(malformedRepoFaceInterpreter),
|
||||
quotaSignals: summarizeRecords(quotaSignals),
|
||||
warnings,
|
||||
recent: summarizeRecords(records.slice(-12)),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"paused": true,
|
||||
"reason": "Paused after live Face turns leaked owner-Codex/bridge artifact text into public Discord despite Face stdout choosing private silence. Fix side-effect extraction/routing before thawing.",
|
||||
"updatedAt": "2026-05-26T11:29:00Z"
|
||||
"reason": "Re-paused after live Bifrost Face post leaked owner-question wording into public Discord during thaw validation.",
|
||||
"updatedAt": "2026-05-26T19:45:00.000Z"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,13 +14,17 @@ objective:
|
|||
Aetheria lore, while keeping the system understandable enough to modify
|
||||
safely.
|
||||
current: >
|
||||
Keep the repo Face swarm paused after the 2026-05-26 Mind/owner-reply leak
|
||||
Keep the repo Face swarm paused after the 2026-05-26 Mind/owner-reply leaks
|
||||
until a controlled thaw verifies the Face/Mind/speech boundary in current
|
||||
room context. Bifrost work routing remains paused because the governance
|
||||
bridge proved too disorienting for live social traffic. Parent Mind now has
|
||||
a slimmer semantic prompt with a hard DSL contract: interpret natural Face
|
||||
thought, decide route/retry/drop, and emit only explicit STATE NOTE, ARTICLE,
|
||||
or SAY blocks for side effects.
|
||||
room context. The second thaw attempt proved malformed parent Mind output
|
||||
was being treated as a Face retry, allowing owner-question boilerplate to
|
||||
become public Bifrost/Nibu SAY. The worker now treats malformed Mind output
|
||||
as Mind-owned retry/drop and the model-output inspector flags interpreter
|
||||
records missing INTERPRETATION decisions. Bifrost work routing remains
|
||||
paused because the governance bridge proved too disorienting for live social
|
||||
traffic. Parent Mind has a slimmer semantic prompt with a hard DSL contract:
|
||||
interpret natural Face thought, decide route/retry/drop, and emit only
|
||||
explicit STATE NOTE, ARTICLE, or SAY blocks for side effects.
|
||||
|
||||
constraints:
|
||||
- Public traffic still rides one remote Qwen box plus local embedding and ops infrastructure.
|
||||
|
|
@ -57,7 +61,7 @@ data_flow:
|
|||
- keep persistent state lean enough that re-entry sharpens the next turn instead of drowning it
|
||||
|
||||
active_subgoals:
|
||||
- Treat `notes/agent-swarm-teardown-plan-2026-05-21.md` as the historical teardown authority and the restart checklist. The repo Face swarm is currently paused after the 2026-05-26 owner-reply/Mind leak, and should thaw only through controlled evidence that the scheduler, prompt, model-output logs, parent Mind, and worker side-effect rails still have explicit observable boundaries. Keep watching for prompt/parser/transport Jenga before adding new swarm features.
|
||||
- Treat `notes/agent-swarm-teardown-plan-2026-05-21.md` as the historical teardown authority and the restart checklist. The repo Face swarm is currently paused after the 2026-05-26 owner-reply/Mind leaks, including the second thaw failure where malformed parent Mind output was converted into public owner-question SAY. It should thaw only through controlled evidence that the scheduler, prompt, model-output logs, parent Mind retry/drop ownership, and worker side-effect rails still have explicit observable boundaries. Keep watching for prompt/parser/transport Jenga before adding new swarm features.
|
||||
- Make the new project-state surfaces the default continuity path for nontrivial VoidBot work.
|
||||
- Keep the rebuilt moderation/self-state foundation on canonical CultCache `.cc` typed operations, never an editable JSON working projection.
|
||||
- Keep the typed moderation, mood, repo Face, and watchdog organs behind the single `GameCult Local Orchestrator` scheduled pulse; the per-organ scheduled tasks are legacy installer surfaces and should stay disabled unless a deliberate isolation test needs one. Bifrost dispatch is disabled by default behind `BIFROST_DISPATCH_ENABLED=false` until the governance bridge is rebuilt cleanly. As of the 2026-05-21 restart, the orchestrator task is enabled and is the live swarm pulse owner.
|
||||
|
|
|
|||
Loading…
Reference in a new issue