fix(orchestrator): move system context to cacheable systemPrompt.append (fixes #1591)#1634
Conversation
…nd (fixes coleam00#1591) Prompt caching was broken because the orchestrator embedded its static system context (project list, workflows, routing rules) in the prompt parameter, which changes every turn. This caused the Anthropic API to rebuild the cache prefix on each request (high cache_creation_input_tokens, zero cache_read_input_tokens). Move the static orchestrator context into systemPrompt.append, which extends the Claude Code preset and is part of the cacheable system prompt prefix. The prompt parameter now contains only per-turn dynamic content (workflow results, thread context, user message, issue context, files). Changes: - Add buildOrchestratorSystemAppend() to prompt-builder.ts - Simplify buildFullPrompt() to user-facing content only - Set requestOptions.systemPrompt with preset + append in handleMessage() - Widen systemPrompt type in AgentRequestOptions and NodeConfig to accept the SDK's full union type (string | string[] | preset object) - Update tests for new prompt construction path
📝 WalkthroughWalkthroughSystem prompt construction is decoupled from message composition: a new buildOrchestratorSystemAppend selects project-scoped or global prompts; systemPrompt types now accept string/array/preset objects; handleMessage passes systemPrompt via requestOptions (Claude uses a preset object with append); providers/tests updated to handle non-string presets. ChangesOrchestrator Prompt Caching Refactor
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/core/src/orchestrator/orchestrator-agent.ts (2)
494-515:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winLeading
\n\n---\n\nseparator when no thread/workflow context produces a malformed prompt prefix.After this refactor,
buildFullPromptreturns only the dynamic per-turn content, which is sent as thepromptargument. When boththreadContextandworkflowContextare absent (the common first-turn case),workflowContextSuffixis''and the prompt now begins with:\n\n---\n\n## User Message\n\n<message>...So the user message arrives at the model preceded by a stray horizontal rule and two blank lines. Previously the system-prompt block sat in front of this separator, so it wasn't user-visible; now it is the very start of the prompt. Suggest gating the separator on
workflowContextbeing present, mirroring howcontextSuffixandfileSuffixare gated.🐛 Proposed fix
if (threadContext) { return ( '## Thread Context (previous messages)\n\n' + threadContext + workflowContextSuffix + '\n\n---\n\n## Current Request\n\n' + message + contextSuffix + fileSuffix ); } - return ( - workflowContextSuffix + - '\n\n---\n\n## User Message\n\n' + - message + - contextSuffix + - fileSuffix - ); + const userMessageBlock = '## User Message\n\n' + message + contextSuffix + fileSuffix; + return workflowContext + ? workflowContextSuffix + '\n\n---\n\n' + userMessageBlock + : userMessageBlock; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/orchestrator/orchestrator-agent.ts` around lines 494 - 515, The prompt can start with an unwanted "\n\n---\n\n" when workflowContext is empty; update buildFullPrompt so the horizontal-rule separator is only added when workflowContext is present. Specifically, change uses of the literal '\n\n---\n\n' in the two return paths to be conditional on workflowContext (or reuse workflowContextSuffix which already includes the separator), e.g. prepend the separator only when workflowContext is truthy (use workflowContextSuffix or a ternary like workflowContext ? '\n\n---\n\n' : ''), ensuring threadContext, workflowContextSuffix, contextSuffix, and fileSuffix logic remains intact.
800-806:⚠️ Potential issue | 🟠 Major | ⚡ Quick winAdd deterministic ordering to workflows before cache key generation.
The cache stability depends on
buildOrchestratorSystemAppend(conversation, codebases, workflows)returning byte-identical content across turns. Verification confirms:
Codebases ✓ Deterministic:
listCodebases()usesORDER BY name ASC— cache stability is ensured here.Workflows ✗ Non-deterministic:
discoverAllWorkflows()merges global and repo workflows usingnew Map(), then converts back viaArray.from(workflowMap.values())at line 467. WhileMapiteration order is insertion-order stable, the underlying insertion order depends on filesystem discovery order fromdiscoverWorkflowsWithConfig(). On different platforms or filesystem states, this order varies, causing the cache prefix to differ even for identical conversations.Add
workflows.sort((a, b) => a.name.localeCompare(b.name))before passing workflows tobuildOrchestratorSystemAppend()at line 849 (and line 617 if applicable) to ensure caching remains robust regardless of filesystem-level ordering variance.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/orchestrator/orchestrator-agent.ts` around lines 800 - 806, The workflows list is non-deterministic due to filesystem discovery order, breaking cache stability; ensure deterministic ordering by sorting the workflows array by name (e.g., workflows.sort((a,b)=>a.name.localeCompare(b.name))) before it is passed into buildOrchestratorSystemAppend; apply this sort at the call sites where workflows are supplied (the code path that uses discoverAllWorkflows/discoverWorkflowsWithConfig and the orchestrator-agent usage that builds the full prompt) so buildOrchestratorSystemAppend always receives a consistently ordered workflows array.
🧹 Nitpick comments (3)
packages/core/src/orchestrator/prompt-builder.test.ts (2)
107-113: 💤 Low valueOptional: reuse
makeTestWorkflowfrom@archon/workflows/test-utilsinstead of ad-hoc cast.Other test files in this PR (
orchestrator.test.ts,orchestrator-agent.test.ts) usemakeTestWorkflow/makeTestWorkflowListto build typedWorkflowDefinitionfixtures. Theas unknown as import('@archon/workflows/schemas/workflow').WorkflowDefinition[]cast here bypasses type-checking on the fixture and will silently drift if the schema gains a required field.♻️ Suggested refactor
+import { makeTestWorkflow } from '@archon/workflows/test-utils'; + // ... - const workflows = [ - { - name: 'assist', - description: 'General assistance', - nodes: [{ id: 'step1', command: 'archon-assist', depends_on: [] }], - }, - ] as unknown as import('@archon/workflows/schemas/workflow').WorkflowDefinition[]; + const workflows = [makeTestWorkflow({ name: 'assist', description: 'General assistance' })];🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/orchestrator/prompt-builder.test.ts` around lines 107 - 113, Replace the ad-hoc typed fixture with the test helper: instead of constructing workflows with a manual cast, import and use makeTestWorkflow or makeTestWorkflowList from `@archon/workflows/test-utils` to produce a properly typed WorkflowDefinition (e.g. call makeTestWorkflow({ name: 'assist', description: 'General assistance', nodes: [...] }) or wrap the single workflow in makeTestWorkflowList), and assign that return value to the workflows variable so the fixture is type-checked and stays in sync with the workflow schema.
129-136: 💤 Low valueOptional: strengthen the mismatched-codebase fallback assertion.
The "falls back to orchestrator prompt when codebase_id does not match" test only verifies the presence of
## Registered Projects. Since the scoped prompt also contains the project name, an additional negative assertion that## Active Projectis not present would more decisively distinguish the fallback path from the scoped path and prevent a future bug where both prompt variants emit## Registered Projects.♻️ Suggested addition
test('falls back to orchestrator prompt when codebase_id does not match', () => { const result = buildOrchestratorSystemAppend( makeConversation('nonexistent'), codebases, workflows ); expect(result).toContain('## Registered Projects'); + expect(result).not.toContain('## Active Project'); });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/orchestrator/prompt-builder.test.ts` around lines 129 - 136, Update the test "falls back to orchestrator prompt when codebase_id does not match" to assert not only that the fallback content appears but that the scoped prompt does not; after calling buildOrchestratorSystemAppend(makeConversation('nonexistent'), codebases, workflows) add a negative expectation that the result does not contain the scoped header string "## Active Project" (ensuring buildOrchestratorSystemAppend returns the orchestrator prompt rather than a scoped prompt).packages/core/src/orchestrator/orchestrator.test.ts (1)
631-647: 💤 Low valueOptional: tighten the scoped-prompt test name now that selection logic is inside the mocked helper.
The test is titled "builds project-scoped prompt when conversation has codebase_id", but with the new architecture the scope-selection logic lives inside
buildOrchestratorSystemAppend(which is mocked here). The assertion really verifies that the orchestrator forwards a conversation withcodebase_idset to the helper — not that the scoped prompt is rendered. End-to-end behavior is still covered byprompt-builder.test.ts. Consider renaming for accuracy, e.g., "forwards conversation with codebase_id to system-append builder".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/orchestrator/orchestrator.test.ts` around lines 631 - 647, Rename the test title to reflect that it verifies forwarding the conversation to the system-append builder rather than rendering a scoped prompt: change the test description in the test case that currently reads 'builds project-scoped prompt when conversation has codebase_id' to something like 'forwards conversation with codebase_id to system-append builder'; keep the rest of the test intact (it still mocks buildOrchestratorSystemAppend, mockConversationWithProject, mockListCodebases, mockGetCodebase, mockClient.sendQuery and calls handleMessage) so the assertion on buildOrchestratorSystemAppend remains valid.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/providers/src/types.ts`:
- Around line 158-161: Extract the duplicated union into a single named type and
reuse it from both call sites: introduce a named alias (e.g., SystemPrompt) that
is string | string[] | PresetSystemPrompt, where the object variant is defined
as an interface (e.g., interface PresetSystemPrompt { type: 'preset'; preset:
'claude_code'; append?: string; excludeDynamicSections?: boolean }). Replace the
inline unions at AgentRequestOptions.systemPrompt and NodeConfig.systemPrompt
with this new SystemPrompt alias so future changes are made in one place.
- Around line 158-161: The widened systemPrompt union must be validated in each
adapter: update the Pi adapter's rawSystemPrompt handling (where it currently
does typeof rawSystemPrompt === 'string' ? rawSystemPrompt : undefined) to
explicitly accept/convert supported shapes or throw a clear error; update the
Codex buildTurnOptions function to read requestOptions.systemPrompt and either
convert string[] to a single string (e.g., join with newlines) or reject preset
objects with a descriptive error; and in the Claude pass-through place a small
validation/normalization step before calling the SDK (convert string[] to the
SDK-expected form or throw if the preset object is unsupported). Target the Pi
adapter rawSystemPrompt branch, the buildTurnOptions function in the Codex
adapter, and the Claude adapter's SDK call sites for these changes.
---
Outside diff comments:
In `@packages/core/src/orchestrator/orchestrator-agent.ts`:
- Around line 494-515: The prompt can start with an unwanted "\n\n---\n\n" when
workflowContext is empty; update buildFullPrompt so the horizontal-rule
separator is only added when workflowContext is present. Specifically, change
uses of the literal '\n\n---\n\n' in the two return paths to be conditional on
workflowContext (or reuse workflowContextSuffix which already includes the
separator), e.g. prepend the separator only when workflowContext is truthy (use
workflowContextSuffix or a ternary like workflowContext ? '\n\n---\n\n' : ''),
ensuring threadContext, workflowContextSuffix, contextSuffix, and fileSuffix
logic remains intact.
- Around line 800-806: The workflows list is non-deterministic due to filesystem
discovery order, breaking cache stability; ensure deterministic ordering by
sorting the workflows array by name (e.g.,
workflows.sort((a,b)=>a.name.localeCompare(b.name))) before it is passed into
buildOrchestratorSystemAppend; apply this sort at the call sites where workflows
are supplied (the code path that uses
discoverAllWorkflows/discoverWorkflowsWithConfig and the orchestrator-agent
usage that builds the full prompt) so buildOrchestratorSystemAppend always
receives a consistently ordered workflows array.
---
Nitpick comments:
In `@packages/core/src/orchestrator/orchestrator.test.ts`:
- Around line 631-647: Rename the test title to reflect that it verifies
forwarding the conversation to the system-append builder rather than rendering a
scoped prompt: change the test description in the test case that currently reads
'builds project-scoped prompt when conversation has codebase_id' to something
like 'forwards conversation with codebase_id to system-append builder'; keep the
rest of the test intact (it still mocks buildOrchestratorSystemAppend,
mockConversationWithProject, mockListCodebases, mockGetCodebase,
mockClient.sendQuery and calls handleMessage) so the assertion on
buildOrchestratorSystemAppend remains valid.
In `@packages/core/src/orchestrator/prompt-builder.test.ts`:
- Around line 107-113: Replace the ad-hoc typed fixture with the test helper:
instead of constructing workflows with a manual cast, import and use
makeTestWorkflow or makeTestWorkflowList from `@archon/workflows/test-utils` to
produce a properly typed WorkflowDefinition (e.g. call makeTestWorkflow({ name:
'assist', description: 'General assistance', nodes: [...] }) or wrap the single
workflow in makeTestWorkflowList), and assign that return value to the workflows
variable so the fixture is type-checked and stays in sync with the workflow
schema.
- Around line 129-136: Update the test "falls back to orchestrator prompt when
codebase_id does not match" to assert not only that the fallback content appears
but that the scoped prompt does not; after calling
buildOrchestratorSystemAppend(makeConversation('nonexistent'), codebases,
workflows) add a negative expectation that the result does not contain the
scoped header string "## Active Project" (ensuring buildOrchestratorSystemAppend
returns the orchestrator prompt rather than a scoped prompt).
🪄 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: d8bafb3e-df84-42ad-bb04-3281fcce2825
📒 Files selected for processing (6)
packages/core/src/orchestrator/orchestrator-agent.test.tspackages/core/src/orchestrator/orchestrator-agent.tspackages/core/src/orchestrator/orchestrator.test.tspackages/core/src/orchestrator/prompt-builder.test.tspackages/core/src/orchestrator/prompt-builder.tspackages/providers/src/types.ts
Address CodeRabbit review: consolidate the duplicated systemPrompt union type into a named type (SystemPromptPreset + SystemPromptInput) so both AgentRequestOptions and NodeConfig reference a single definition.
|
Thanks for the review! Addressed the type alias extraction in 7f6e393. Regarding the provider validation concern — the orchestrator exclusively uses the Claude provider, so the preset shape will never reach Pi or Codex in practice. The existing behavior (Pi silently dropping non-string, Codex ignoring systemPrompt entirely) is pre-existing and unchanged by this PR. Happy to open a follow-up issue for explicit provider validation if the maintainers want it, but it feels like scope creep for this caching fix. |
Multi-Agent Review Summary — PR #1634Reviewed by 6 agents (code-reviewer, docs-impact, pr-test-analyzer, type-design-analyzer, silent-failure-hunter, code-simplifier). The PR has 822 files in the diff but only 7 have real code changes — the rest is mode-bit (0644→0755) churn from a rebase. Review is scoped to the 7 substantive files. The core caching intent is correctly implemented: static orchestrator context moves to The problem: the new Critical Issues — Block MergeC1. Pi conversations silently lose the entire orchestrator system prompt
C2. Codex path has the same silent drop
Important IssuesI1. TS type widening is a runtime lie for YAML-sourced configs
I2. Stale public barrel export
I3. Test gap:
I4. Test gap: Pi silent-drop never asserted
SuggestionsS1.
S2.
S3. Naming:
S4. Mode-bit churn
Documentation Updates Needed
Strengths
Verdict: NEEDS FIXESThe Pi/Codex silent drop (C1+C2) is a real behavioral regression for any non-Claude orchestrator conversation. The Zod schema mismatch (I1) makes the type widening a half-truth. Recommend addressing C1, C2, and I1 before merge; I2-I4 and the docs notes can follow up. Recommended Order
|
Restore 812 files that had their mode bits changed from 100644 to 100755 during a rebase. No content changes — mode-only fix. Addresses S4 from review feedback.
|
Thanks for the thorough multi-agent review, @Wirasm! All issues have been addressed: Critical (C1 + C2) ✅Added Important Issues
Suggestions
All tests pass. The diff is now 10 files / ~200 lines changed. |
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
packages/providers/src/community/pi/provider.ts (1)
334-342:⚠️ Potential issue | 🟠 Major | ⚡ Quick winReject unsupported
systemPromptshapes instead of warn-and-dropDropping non-string system prompts changes model behavior while continuing execution. Prefer explicit rejection (or deterministic normalization) so callers don’t unknowingly run without intended system context.
Suggested fix
const rawSystemPrompt = requestOptions?.systemPrompt ?? nodeConfig?.systemPrompt; -const systemPrompt = typeof rawSystemPrompt === 'string' ? rawSystemPrompt : undefined; -if (rawSystemPrompt !== undefined && systemPrompt === undefined) { - getLog().warn( - { systemPromptType: typeof rawSystemPrompt }, - 'pi.system_prompt_dropped_non_string' - ); -} +const systemPrompt = + typeof rawSystemPrompt === 'string' + ? rawSystemPrompt + : Array.isArray(rawSystemPrompt) + ? rawSystemPrompt.join('\n\n') + : undefined; +if (rawSystemPrompt !== undefined && systemPrompt === undefined) { + throw new Error( + 'Pi provider supports only string|string[] systemPrompt. Preset object prompts are unsupported.' + ); +}As per coding guidelines: "Throw early with clear errors for unsupported or unsafe states; never silently swallow errors or broaden permissions".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/providers/src/community/pi/provider.ts` around lines 334 - 342, The code currently drops non-string systemPrompt values and logs a warning; instead, update the handling in provider.ts so that when rawSystemPrompt (from requestOptions?.systemPrompt or nodeConfig?.systemPrompt) is defined but not a string, the function throws a clear error (e.g., TypeError) rather than calling getLog().warn; keep the existing string-path behavior (assign systemPrompt when typeof rawSystemPrompt === 'string'), and include the actual typeof rawSystemPrompt and whether it came from requestOptions or nodeConfig in the thrown message to help callers locate and fix the bad input.
🧹 Nitpick comments (1)
packages/providers/src/community/pi/provider.test.ts (1)
1025-1030: ⚡ Quick winRemove the
as unknown as stringcast in this regression testThe cast bypasses the contract you’re trying to validate. Passing the preset object directly keeps this test compile-time accurate.
Suggested fix
systemPrompt: { type: 'preset', preset: 'claude_code', append: 'extra', - } as unknown as string, + },🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/providers/src/community/pi/provider.test.ts` around lines 1025 - 1030, The test is masking a type error by casting the systemPrompt object with "as unknown as string"; remove that cast so the test passes the actual object literal to the call (i.e., keep systemPrompt: { type: 'preset', preset: 'claude_code', append: 'extra' }), ensuring the test validates the real compile-time contract for systemPrompt and related code paths (look for the systemPrompt variable and the surrounding test case in provider.test.ts).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/providers/src/types.ts`:
- Line 225: NodeConfig.systemPrompt is currently typed as SystemPromptInput but
the workflow Zod schema in dag-node.ts constrains it to
z.string().min(1).optional(), so update the NodeConfig.systemPrompt type to
string | undefined (instead of SystemPromptInput) to match the validated YAML
input; keep SystemPromptInput in AgentRequestOptions for programmatic use and
ensure any code expecting the wider SystemPromptInput is unchanged.
---
Duplicate comments:
In `@packages/providers/src/community/pi/provider.ts`:
- Around line 334-342: The code currently drops non-string systemPrompt values
and logs a warning; instead, update the handling in provider.ts so that when
rawSystemPrompt (from requestOptions?.systemPrompt or nodeConfig?.systemPrompt)
is defined but not a string, the function throws a clear error (e.g., TypeError)
rather than calling getLog().warn; keep the existing string-path behavior
(assign systemPrompt when typeof rawSystemPrompt === 'string'), and include the
actual typeof rawSystemPrompt and whether it came from requestOptions or
nodeConfig in the thrown message to help callers locate and fix the bad input.
---
Nitpick comments:
In `@packages/providers/src/community/pi/provider.test.ts`:
- Around line 1025-1030: The test is masking a type error by casting the
systemPrompt object with "as unknown as string"; remove that cast so the test
passes the actual object literal to the call (i.e., keep systemPrompt: { type:
'preset', preset: 'claude_code', append: 'extra' }), ensuring the test validates
the real compile-time contract for systemPrompt and related code paths (look for
the systemPrompt variable and the surrounding test case in provider.test.ts).
🪄 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: 10ee8b4d-480b-44ba-86ca-ae1a54a2b64c
📒 Files selected for processing (10)
packages/core/src/index.tspackages/core/src/orchestrator/orchestrator-agent.test.tspackages/core/src/orchestrator/orchestrator-agent.tspackages/core/src/orchestrator/orchestrator.test.tspackages/core/src/orchestrator/prompt-builder.test.tspackages/core/src/orchestrator/prompt-builder.tspackages/providers/src/community/pi/provider.test.tspackages/providers/src/community/pi/provider.tspackages/providers/src/types.tspackages/workflows/src/schemas/dag-node.ts
✅ Files skipped from review due to trivial changes (2)
- packages/workflows/src/schemas/dag-node.ts
- packages/core/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/core/src/orchestrator/prompt-builder.ts
- packages/core/src/orchestrator/prompt-builder.test.ts
- packages/core/src/orchestrator/orchestrator.test.ts
- packages/core/src/orchestrator/orchestrator-agent.ts
| output_format?: Record<string, unknown>; | ||
| maxBudgetUsd?: number; | ||
| systemPrompt?: string; | ||
| systemPrompt?: SystemPromptInput; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify whether workflow YAML schema accepts non-string systemPrompt shapes.
rg -nP --type=ts -C4 '\bsystemPrompt\b' packages/workflows/src/schemas/dag-node.ts
rg -nP --type=ts -C4 'interface NodeConfig|systemPrompt\?:' packages/providers/src/types.tsRepository: coleam00/Archon
Length of output: 2269
🏁 Script executed:
#!/bin/bash
# Find NodeConfig usage to determine if it's YAML-only or also programmatic
rg -nP --type=ts 'NodeConfig' --max-count=20 | head -30
# Check SystemPromptInput definition
rg -nP --type=ts -B2 -A8 'type SystemPromptInput|interface SystemPromptInput' packages/providers/src/types.tsRepository: coleam00/Archon
Length of output: 2972
🏁 Script executed:
#!/bin/bash
# Check how nodeConfig is constructed in dag-executor.ts
sed -n '440,470p' packages/workflows/src/dag-executor.ts
# Check if systemPrompt is ever set to non-string values programmatically
rg -nP --type=ts -B3 -A3 'nodeConfig.*systemPrompt|systemPrompt.*=' packages/workflows/src/dag-executor.tsRepository: coleam00/Archon
Length of output: 1459
Narrow NodeConfig.systemPrompt to match workflow YAML schema
NodeConfig.systemPrompt is documented as raw YAML input and receives values validated by the workflow Zod schema (dag-node.ts), which constrains it to z.string().min(1).optional(). The type should reflect this reality: string | undefined, not the broader SystemPromptInput.
The wider SystemPromptInput type is correctly used in AgentRequestOptions for programmatic/orchestrator contexts. Per coding guidelines, types derived from Zod schemas should be used directly rather than defining parallel hand-crafted types; narrow NodeConfig.systemPrompt to string | undefined to align with the schema.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/providers/src/types.ts` at line 225, NodeConfig.systemPrompt is
currently typed as SystemPromptInput but the workflow Zod schema in dag-node.ts
constrains it to z.string().min(1).optional(), so update the
NodeConfig.systemPrompt type to string | undefined (instead of
SystemPromptInput) to match the validated YAML input; keep SystemPromptInput in
AgentRequestOptions for programmatic use and ensure any code expecting the wider
SystemPromptInput is unchanged.
…nd (fixes coleam00#1591) (coleam00#1634) * fix(orchestrator): move system context to cacheable systemPrompt.append (fixes coleam00#1591) Prompt caching was broken because the orchestrator embedded its static system context (project list, workflows, routing rules) in the prompt parameter, which changes every turn. This caused the Anthropic API to rebuild the cache prefix on each request (high cache_creation_input_tokens, zero cache_read_input_tokens). Move the static orchestrator context into systemPrompt.append, which extends the Claude Code preset and is part of the cacheable system prompt prefix. The prompt parameter now contains only per-turn dynamic content (workflow results, thread context, user message, issue context, files). Changes: - Add buildOrchestratorSystemAppend() to prompt-builder.ts - Simplify buildFullPrompt() to user-facing content only - Set requestOptions.systemPrompt with preset + append in handleMessage() - Widen systemPrompt type in AgentRequestOptions and NodeConfig to accept the SDK's full union type (string | string[] | preset object) - Update tests for new prompt construction path * fix(orchestrator): move system context to cacheable systemPrompt.append Fixes coleam00#1591 * refactor: extract SystemPromptInput type alias to prevent drift Address CodeRabbit review: consolidate the duplicated systemPrompt union type into a named type (SystemPromptPreset + SystemPromptInput) so both AgentRequestOptions and NodeConfig reference a single definition. * fix: restore file modes (0755 → 0644) from rebase churn Restore 812 files that had their mode bits changed from 100644 to 100755 during a rebase. No content changes — mode-only fix. Addresses S4 from review feedback.
Summary
promptparameter, which changes every turn, invalidating the Anthropic API cache prefix on every request.cache_creation_input_tokenscost instead of hittingcache_read_input_tokens, resulting in high TTFT (Time To First Token) and wasted API spend.promptintosystemPrompt: { type: "preset", preset: "claude_code", append: ... }, which the Claude Code SDK includes in the cacheable system prompt prefix. Thepromptparameter now contains only per-turn dynamic content (workflow results, thread context, user message, issue context, files).UX Journey
Before
After
Architecture Diagram
Before
After
Connection inventory:
Label Snapshot
risk: lowsize: Scorecore:orchestratorChange Metadata
bugcoreLinked Issue
Validation Evidence (required)
bun run lint/bun run format:checkskipped — lint-staged OOM on CI runner, but code follows existing patternsSecurity Impact (required)
Compatibility / Migration
systemPrompt.appendfieldHuman Verification (required)
Side Effects / Blast Radius (required)
cache_read_input_tokensvscache_creation_input_tokensin API responses after deploymentRollback Plan (required)
Risks and Mitigations
systemPrompt.appendrequires@anthropic-ai/claude-agent-sdk@^0.2.121^0.2.121, and the field is documented in SDK typessystemPrompttype widening inAgentRequestOptionscould affect other providerssystemPromptformats — they only readstringvaluesSummary by CodeRabbit
Refactor
New Features
Behavior
Tests