feat(maintainer): Pi/Minimax variant of maintainer-standup + dual-format persist#1480
feat(maintainer): Pi/Minimax variant of maintainer-standup + dual-format persist#1480
Conversation
…mat persist The original maintainer-standup workflow relies on Claude SDK-enforced output_format (one big JSON wrapping the brief markdown + state). It works on Claude but hangs in nested Claude Code sessions (#1067), and Pi/Minimax can't reliably emit a 30KB JSON wrapper around markdown content. Changes: - Drop output_format from synthesize node. Synthesis now emits brief markdown plain, followed by a delimited ARCHON_STATE_JSON_BEGIN/END block. - Replace persist (script:) with bash: that pipes synth output into a new .archon/scripts/maintainer-standup-persist.ts. The script tries the delimiter format first and falls back to the legacy {brief_markdown, next_state} JSON wrapper Pi/Minimax tends to emit regardless of prompt instructions. Either format yields the same brief + state.json on disk. - Add maintainer-standup-minimax.yaml — a 1:1 copy with provider: pi, model: minimax/MiniMax-M2.7 — for use when the Claude variant hangs or when the maintainer wants to spend Pi tokens instead. - Update the maintainer-standup synthesizer prompt: top-of-file output format directive, explicit "do not wrap in JSON object" guidance, delimiter-based example. Tested end-to-end on the Minimax variant against today's PR/issue payload; brief was extracted via the JSON-wrapper fallback path and written to .archon/maintainer-standup/briefs/2026-04-29.md.
📝 WalkthroughWalkthroughThis change refactors the maintainer standup workflow by extracting persistence logic into a dedicated script and updating the output contract format. It introduces a new persistence script that handles two parsing strategies, modifies the main workflow to use external script-based persistence, adds a new minimax workflow variant, and updates command documentation to reflect the new delimiter-based JSON state format. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
.archon/commands/maintainer-standup.md (1)
30-42: Add language identifiers to fenced examples to satisfy markdownlint.Line 30 and Line 196 use fenced blocks without a language, which triggers MD040 warnings.
Suggested lint-only doc fix
-``` +```text # Maintainer Standup — 2026-04-29 @@ ARCHON_STATE_JSON_END@@
-+text
ARCHON_STATE_JSON_BEGIN
{
@@
ARCHON_STATE_JSON_ENDAlso applies to: 196-213
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.archon/commands/maintainer-standup.md around lines 30 - 42, The markdown contains fenced code blocks around the maintainer standup and the embedded ARCHON_STATE_JSON sections that lack a language identifier (triggering MD040); update the opening fences from ``` to ```text for the blocks delimited by ARCHON_STATE_JSON_BEGIN and ARCHON_STATE_JSON_END (both the top example and the later occurrence) so the fenced examples include the "text" language identifier and satisfy markdownlint.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.archon/scripts/maintainer-standup-persist.ts:
- Around line 28-31: The parsed JSON assigned to the module-level variables
(brief, state, source) is not schema-validated before being used or persisted;
add a runtime validation step (either a lightweight type-guard function or a
schema using zod/io-ts) that checks required keys and types for the expected
state shape, ensures brief is a non-empty string and source is one of
'delimiter'|'json-wrapper', and call this validator wherever parsed data is
assigned to state (the places that currently set state from JSON) and
immediately before the persist/write routine that saves state; if validation
fails, log a clear error via the existing logger and skip persisting (or recover
to a safe default) so malformed data is never written back.
- Around line 34-39: The end-marker lookup currently uses raw.indexOf(END) which
can find an earlier END before the real BEGIN; change it to search for END
starting after the found BEGIN (use the variant of indexOf that accepts a start
position) so endIdx = raw.indexOf(END, beginIdx + BEGIN.length) and keep the
existing checks (beginIdx !== -1 && endIdx !== -1 && endIdx > beginIdx) before
slicing stateText from raw.slice(beginIdx + BEGIN.length, endIdx). This ensures
the END anchor is anchored to the found BEGIN and prevents mistaking earlier
marker-like text.
---
Nitpick comments:
In @.archon/commands/maintainer-standup.md:
- Around line 30-42: The markdown contains fenced code blocks around the
maintainer standup and the embedded ARCHON_STATE_JSON sections that lack a
language identifier (triggering MD040); update the opening fences from ``` to
```text for the blocks delimited by ARCHON_STATE_JSON_BEGIN and
ARCHON_STATE_JSON_END (both the top example and the later occurrence) so the
fenced examples include the "text" language identifier and satisfy markdownlint.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f01079fe-b116-4856-8601-66d45e8784e3
📒 Files selected for processing (4)
.archon/commands/maintainer-standup.md.archon/scripts/maintainer-standup-persist.ts.archon/workflows/maintainer/maintainer-standup-minimax.yaml.archon/workflows/maintainer/maintainer-standup.yaml
| type State = Record<string, unknown>; | ||
| let brief: string | null = null; | ||
| let state: State | null = null; | ||
| let source: 'delimiter' | 'json-wrapper' | null = null; |
There was a problem hiding this comment.
Validate required state schema before persisting.
Line 41 and Line 64 accept any parsed JSON as state, and Line 104 writes it directly. If required fields are missing or wrong types, next runs can silently degrade or fail during progress detection.
Proposed guardrail for state shape validation
-type State = Record<string, unknown>;
+type CarryOverItem = { kind: string; id: string; note: string; first_seen: string };
+type ObservedItem = { number: number; title: string };
+type State = {
+ last_run_at: string;
+ last_dev_sha: string;
+ carry_over: CarryOverItem[];
+ observed_prs: ObservedItem[];
+ observed_issues: ObservedItem[];
+ direction_questions: string[];
+};
+
+const isObjectRecord = (v: unknown): v is Record<string, unknown> =>
+ typeof v === 'object' && v !== null && !Array.isArray(v);
+
+function isState(v: unknown): v is State {
+ if (!isObjectRecord(v)) return false;
+ return (
+ typeof v.last_run_at === 'string' &&
+ typeof v.last_dev_sha === 'string' &&
+ Array.isArray(v.carry_over) &&
+ Array.isArray(v.observed_prs) &&
+ Array.isArray(v.observed_issues) &&
+ Array.isArray(v.direction_questions)
+ );
+}
@@
- state = JSON.parse(stateText) as State;
+ const parsedState = JSON.parse(stateText);
+ if (!isState(parsedState)) {
+ throw new Error('Delimiter state JSON does not match required schema');
+ }
+ state = parsedState;
@@
- const parsed = JSON.parse(candidate) as Record<string, unknown>;
+ const parsed = JSON.parse(candidate) as Record<string, unknown>;
if (
typeof parsed.brief_markdown === 'string' &&
- typeof parsed.next_state === 'object' &&
- parsed.next_state !== null
+ isState(parsed.next_state)
) {
brief = parsed.brief_markdown;
- state = parsed.next_state as State;
+ state = parsed.next_state;Also applies to: 38-42, 57-65, 104-104
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.archon/scripts/maintainer-standup-persist.ts around lines 28 - 31, The
parsed JSON assigned to the module-level variables (brief, state, source) is not
schema-validated before being used or persisted; add a runtime validation step
(either a lightweight type-guard function or a schema using zod/io-ts) that
checks required keys and types for the expected state shape, ensures brief is a
non-empty string and source is one of 'delimiter'|'json-wrapper', and call this
validator wherever parsed data is assigned to state (the places that currently
set state from JSON) and immediately before the persist/write routine that saves
state; if validation fails, log a clear error via the existing logger and skip
persisting (or recover to a safe default) so malformed data is never written
back.
| const BEGIN = 'ARCHON_STATE_JSON_BEGIN'; | ||
| const END = 'ARCHON_STATE_JSON_END'; | ||
| const beginIdx = raw.indexOf(BEGIN); | ||
| const endIdx = raw.indexOf(END); | ||
| if (beginIdx !== -1 && endIdx !== -1 && endIdx > beginIdx) { | ||
| const stateText = raw.slice(beginIdx + BEGIN.length, endIdx).trim(); |
There was a problem hiding this comment.
Anchor closing-marker lookup to the opening marker.
Line 37 searches ARCHON_STATE_JSON_END from the start of the payload. If marker-like text appears before the real block, valid delimiter output can be misread.
Suggested delimiter extraction hardening
const BEGIN = 'ARCHON_STATE_JSON_BEGIN';
const END = 'ARCHON_STATE_JSON_END';
const beginIdx = raw.indexOf(BEGIN);
-const endIdx = raw.indexOf(END);
+const endIdx = beginIdx === -1 ? -1 : raw.indexOf(END, beginIdx + BEGIN.length);
if (beginIdx !== -1 && endIdx !== -1 && endIdx > beginIdx) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.archon/scripts/maintainer-standup-persist.ts around lines 34 - 39, The
end-marker lookup currently uses raw.indexOf(END) which can find an earlier END
before the real BEGIN; change it to search for END starting after the found
BEGIN (use the variant of indexOf that accepts a start position) so endIdx =
raw.indexOf(END, beginIdx + BEGIN.length) and keep the existing checks (beginIdx
!== -1 && endIdx !== -1 && endIdx > beginIdx) before slicing stateText from
raw.slice(beginIdx + BEGIN.length, endIdx). This ensures the END anchor is
anchored to the found BEGIN and prevents mistaking earlier marker-like text.
Summary
output_format(one big JSON wrapping a ~12KB markdown brief plus state). It works on Claude Sonnet but hangs in nested Claude Code sessions (2 issues: v0.3.5: CLI workflow run silently hangs — dotenv loads .env from CWD instead of ~/.archon/.env,, + rchon serve hardcodes skipPlatformAdapters:true — Telegram/Discord/Slack adapters are unreachable #1067), and Pi/Minimax can't reliably emit a 30KB JSON wrapper around markdown content (parser failures, prose preamble interference).output_formatfrom the synthesize node.ARCHON_STATE_JSON_BEGIN/ARCHON_STATE_JSON_ENDdelimited state JSON block.script:persist node with abash:node that pipes raw synth output into a new.archon/scripts/maintainer-standup-persist.ts. The script tries the delimiter format first; falls back to extracting{brief_markdown, next_state}from a JSON wrapper (the format Pi/Minimax stubbornly emits regardless of prompt instructions). Either format yields the same files on disk..archon/workflows/maintainer/maintainer-standup-minimax.yaml— a 1:1 copy of the Claude variant but withprovider: pi,model: minimax/MiniMax-M2.7..archon/that don't ship with Archon binaries.Label Snapshot
risk: lowsize: Mcoretools:maintainer-standupChange Metadata
featurecoreLinked Issue
Validation Evidence (required)
End-to-end behavioral test: ran
archon workflow run maintainer-standup-minimaxagainst today's payload (60+ open PRs, 19 open issues). Synthesize node emitted prose-preamble + JSON-wrapper format despite prompt instructions; persist script recovered via the fallback path and wrote.archon/maintainer-standup/briefs/2026-04-29.md(17KB) +state.json. LoggedSynth output used JSON-wrapper format (delimiter contract not followed); recovered via fallback.to stderr as designed.bun run generate:bundlednot needed (no bundled-default files touched).Security Impact (required)
gh apicalls and Pi/Claude provider calls as today..archon/maintainer-standup/writes.Compatibility / Migration
Human Verification (required)
archon workflow run maintainer-standup-minimaxend-to-end on macOS arm64; brief + state both written; verdict logging showssource: 'json-wrapper'(fallback path).String.rawtemplate substitution; raw text is shell-quoted by the framework's bash substitution.#heading onward" pass.Side Effects / Blast Radius (required)
maintainer-standupandmaintainer-standup-minimaxonly. No other workflow consumes the synthesizer command file. No engine changes.source: 'delimiter'vs'json-wrapper') to stdout, and stderr-logs the fallback case. If the fallback ever stops working, the maintainer sees aPERSIST FAILEDerror with the raw output dumped for log recovery.Rollback Plan (required)
.archon/; no engine impact.PERSIST FAILED: could not extract brief and state from synth outputin stderr if both extraction paths fail.Risks and Mitigations
ARCHON_STATE_JSON_BEGINliterally inside the brief markdown.Summary by CodeRabbit
New Features
Refactor