Skip to content

refactor: extract providers from @archon/core into @archon/providers#1137

Merged
Wirasm merged 7 commits intodevfrom
feature/extract-providers-from-core
Apr 13, 2026
Merged

refactor: extract providers from @archon/core into @archon/providers#1137
Wirasm merged 7 commits intodevfrom
feature/extract-providers-from-core

Conversation

@Wirasm
Copy link
Copy Markdown
Collaborator

@Wirasm Wirasm commented Apr 12, 2026

Summary

Extract all AI agent provider logic from @archon/core into a new @archon/providers package. Providers own their SDK dependencies and translate raw config to SDK-specific options internally. The dag-executor is now fully provider-agnostic — zero provider === 'codex' or provider === 'claude' branches.

Zero user-facing changes — same providers, same config, same behavior.

Changes

  • New @archon/providers package — owns IAgentProvider interface, Claude/Codex implementations, SDK deps, and a zero-dep contract layer (@archon/providers/types)
  • @archon/core no longer depends on SDK packages — imports from @archon/providers instead; no provider re-exports (clean break)
  • @archon/workflows/deps.ts — ~200 lines of mirror types replaced with direct imports from the contract layer
  • dag-executor.ts — 9 provider-specific option-building branches replaced with capability warnings + nodeConfig/assistantConfig pass-through
  • Structured output normalization — each provider handles its own output semantics (Claude via native structured_output, Codex via inline JSON parsing)
  • IAgentProvider.getCapabilities() — new method enabling provider-agnostic capability warnings
  • UnknownProviderError — standardized error shape across all surfaces
  • Config type dedupClaudeProviderDefaults and CodexProviderDefaults canonical definitions live in the contract layer; @archon/core/config imports from there

Files Changed

44 files changed across 8 packages

File list

New package (@archon/providers)

  • packages/providers/ — package.json, tsconfig, types, factory, errors, barrel
  • packages/providers/src/claude/ — provider, config, tests
  • packages/providers/src/codex/ — provider, config, binary-resolver, tests

Updated packages

  • packages/core/ — removed src/providers/, stripped SDK deps, updated consumers
  • packages/workflows/ — deps.ts imports from contract layer, dag-executor refactored
  • packages/server/ — added @archon/providers dep for MessageChunk import

Testing

  • Type check passes (all 10 packages)
  • Lint passes (0 errors, 0 warnings)
  • Format check passes
  • All tests pass (bun run validate)
  • Grep verification: zero provider branches in dag-executor, zero mirror types, zero SDK deps in core, zero SDK imports in contract layer

Not in scope

Summary by CodeRabbit

  • New Features

    • Centralized providers package with typed provider contracts and config parsers.
    • Providers expose capabilities and accept per-node assistantConfig/nodeConfig for finer control.
    • Improved structured-output handling and provider-driven tool/config enforcement.
    • Added end-to-end smoke workflows for Claude and Codex.
  • Bug Fixes

    • Clearer unknown-provider errors via a dedicated error type.
  • Documentation

    • Docs updated to reflect new provider locations and request semantics.

Wirasm added 3 commits April 12, 2026 22:39
Move Claude and Codex provider implementations, factory, and SDK
dependencies into a new @archon/providers package. This establishes a
clean boundary: providers own SDK translation, core owns business logic.

Key changes:
- New @archon/providers package with zero-dep contract layer (types.ts)
- @archon/workflows imports from @archon/providers/types — no mirror types
- dag-executor delegates option building to providers via nodeConfig
- IAgentProvider gains getCapabilities() for provider-agnostic warnings
- @archon/core no longer depends on SDK packages directly
- UnknownProviderError standardizes error shape across all surfaces

Zero user-facing changes — same providers, same config, same behavior.
Address review findings:
- Move ClaudeProviderDefaults and CodexProviderDefaults to the
  @archon/providers/types contract layer as the single source of truth.
  @archon/core/config/config-types.ts now imports from there.
- Remove provider re-exports from @archon/core (index.ts and types/).
  Consumers should import from @archon/providers directly.
- Update @archon/server to depend on @archon/providers for MessageChunk.
Each provider now normalizes its own structured output semantics:
- Claude already yields structuredOutput from the SDK's native field
- Codex now parses inline agent_message text as JSON when outputFormat
  is set, populating structuredOutput on the result chunk

This eliminates the last provider === 'codex' branch from dag-executor,
making it fully provider-agnostic. The dag-executor checks structuredOutput
uniformly regardless of provider.

Also removes the ClaudeCodexProviderDefaults deprecated alias — all
consumers now use ClaudeProviderDefaults directly.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 2026

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 31d208ae-3d34-4cd6-a709-a6b785fe784f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Extracts AI agent providers into a new @archon/providers package, migrating provider implementations, types, parsers, and factory out of @archon/core; updates imports across core, workflows, server, CLI; changes request option shapes to use assistantConfig/nodeConfig; and updates tests and docs accordingly.

Changes

Cohort / File(s) Summary
New providers package
packages/providers/package.json, packages/providers/tsconfig.json, packages/providers/src/index.ts, packages/providers/src/types.ts
Add @archon/providers package and contract-only ./types subpath defining provider interfaces, SendQueryOptions, MessageChunk, TokenUsage, ProviderCapabilities, NodeConfig, defaults.
Providers — Claude
packages/providers/src/claude/provider.ts, packages/providers/src/claude/config.ts, packages/providers/src/claude/index.ts, packages/providers/src/claude/provider.test.ts
New Claude provider module and index, parseClaudeConfig, MCP/hooks helpers, applyNodeConfig, getCapabilities, tests updated to use assistantConfig/nodeConfig and capability checks.
Providers — Codex
packages/providers/src/codex/provider.ts, packages/providers/src/codex/config.ts, packages/providers/src/codex/index.ts, packages/providers/src/codex/binary-resolver.ts, packages/providers/src/codex/*.test.ts
New Codex provider, parseCodexConfig, binary-resolver utilities, structured-output normalization, updated sendQuery signature and capability reporting; tests adjusted.
Providers — factory, errors, tests, mocks
packages/providers/src/factory.ts, packages/providers/src/errors.ts, packages/providers/src/factory.test.ts, packages/providers/src/test/mocks/logger.ts
Introduce getAgentProvider with REGISTERED_PROVIDERS, UnknownProviderError, capability-aware factory tests, and a mock logger for tests.
Core package — exports & deps
packages/core/package.json, packages/core/src/providers/index.ts, packages/core/src/index.ts
Remove provider submodule exports and SDK deps from core; core now delegates runtime providers to @archon/providers and removes local provider re-exports/tests.
Core package — types & config
packages/core/src/types/index.ts, packages/core/src/config/config-types.ts
Remove local provider types (IAgentProvider, MessageChunk, AgentRequestOptions, TokenUsage); import/re-export provider defaults/types from @archon/providers/types and update config shapes.
Core runtime & tests updates
packages/core/src/orchestrator/*.ts, packages/core/src/services/title-generator.ts, related tests
Switch imports to @archon/providers, change option construction to nest assistantConfig/nodeConfig, update tests to mock @archon/providers and assert nested option shapes.
Workflows — types & executor refactor
packages/workflows/src/deps.ts, packages/workflows/src/dag-executor.ts, packages/workflows/src/dag-executor.test.ts, packages/workflows/package.json
Replace workflow-local provider mirrors with @archon/providers/types, refactor resolver to return SendQueryOptions, remove local MCP/hook loaders, use capability-driven unsupported-feature warnings and thread node/assistant configs.
Server / CLI / docs / infra
packages/server/package.json, packages/server/src/adapters/web.ts, packages/cli/package.json, eslint.config.mjs, CLAUDE.md, packages/docs-web/.../architecture.md, Dockerfile
Add @archon/providers dependency to server/cli/workflows, update imports to @archon/providers/types, add ESLint ignore for **/src/test/**, update docs and Dockerfile to include new package.
E2E workflows
.archon/workflows/e2e-claude-smoke.yaml, .archon/workflows/e2e-codex-smoke.yaml
Add smoke workflow YAMLs exercising provider selection, structured output, and tool use for Claude and Codex.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Core as Core / Orchestrator
  participant Workflows as Workflows / DAG
  participant ProvidersPkg as Providers (`@archon/providers`)
  participant Provider as ProviderImpl (Claude/Codex)
  participant SDK as ModelSDK

  Client->>Core: trigger request / message
  Core->>Workflows: resolve node & build base options
  Workflows->>ProvidersPkg: deps.getAgentProvider(provider)
  ProvidersPkg->>Provider: return provider instance
  Core->>Provider: sendQuery(prompt, cwd, resumeId, SendQueryOptions{assistantConfig,nodeConfig})
  Provider->>SDK: call provider SDK with mapped options
  SDK-->>Provider: stream MessageChunk events
  Provider-->>Core: yield MessageChunk generator
  Core-->>Client: stream aggregated results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • leex279

Poem

🐰 I hopped into a tidy den,

Providers moved and sat like friends.
Claude and Codex now take flight,
Types aligned and options tight.
A joyful nibble — code well penned. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main refactoring: extracting provider logic from @archon/core into a new @archon/providers package. It is concise, clear, and specific enough to understand the primary change.
Description check ✅ Passed The description is comprehensive and well-structured, covering summary, changes, files affected, testing, and scope boundaries. However, it deviates from the template format by not including UX Journey, Architecture Diagram, Label Snapshot, and Validation Evidence sections, though it does address many template topics informally.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/extract-providers-from-core

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Wirasm
Copy link
Copy Markdown
Collaborator Author

Wirasm commented Apr 12, 2026

PR Review Summary

Reviewed by: 6 specialized agents (code-reviewer, docs-impact, type-design-analyzer, silent-failure-hunter, pr-test-analyzer, code-simplifier)


Critical Issues (1 found)

Agent Issue Location
silent-failure-hunter Env leak gate removed with no code-level tracking — The pre-spawn env-leak safety check was removed from both ClaudeProvider.sendQuery() and CodexProvider.sendQuery(). PR description says tracked as #1135, but zero references to #1135 exist in the changed code. The comment in buildSubprocessEnv() ("process.env is already clean") is misleading — stripCwdEnv() is a startup-time strip, not a per-spawn consent-aware gate. providers/src/claude/provider.ts, providers/src/codex/provider.ts

Note: The code-reviewer marked this as correctly out-of-scope per #1135. However, the silent-failure-hunter flags the absence of any // TODO(#1135) comment in the provider files. At minimum, add a tracked comment so the gap is visible in code.


Important Issues (6 found)

Agent Issue Location
silent-failure-hunter MCP missing env vars user warning silently dropped — Old dag-executor sent a platform message when MCP config referenced undefined env vars. New applyNodeConfig only logs, no user-facing message. Users get silent MCP auth failures. providers/src/claude/provider.ts:388-391
silent-failure-hunter Haiku + MCP warning completely removed — Old dag-executor warned when Haiku model was combined with MCP (tool search unsupported). Not re-added anywhere in the provider. Removed from dag-executor.ts, not re-added
silent-failure-hunter buildLoopNodeOptions never passes nodeConfig — Loop nodes with workflow-level effort, thinking, betas, sandbox silently lose these settings. dag-executor.ts:1401-1413
code-reviewer assistantConfig cast discards precise typesconfig.assistants[provider] is typed as ClaudeProviderDefaults | CodexProviderDefaults but cast to Record<string, unknown> at every call site. dag-executor.ts:364, orchestrator-agent.ts:763
code-reviewer NodeConfig.effort typed as string instead of EffortLevel then unsafely cast via as Options['effort']. Inline 'low' | 'medium' | 'high' | 'max' in types.ts would restore safety without circular deps. providers/src/types.ts:117, providers/src/claude/provider.ts:422
code-reviewer Internal helpers exported from public index.tswithFirstMessageTimeout and getProcessUid are Claude-internal, only used in tests. YAGNI. providers/src/index.ts:32-37

Suggestions (7 found)

Agent Suggestion Location
type-design AgentRequestOptions carries Claude-only fields (forkSession, persistSession) declared universal — consider moving to NodeConfig providers/src/types.ts:82-94
type-design NodeConfig index signature [key: string]: unknown silently absorbs typos — verify dagNodeSchema rejects unknown YAML keys providers/src/types.ts:115
code-simplifier Remove 3 unused backward-compat aliases in deps.ts (WorkflowMessageChunk, WorkflowAgentOptions, IWorkflowAgentProvider); keep WorkflowTokenUsage (used by logger.ts) workflows/src/deps.ts:31-35
code-simplifier Replace as Record<string, unknown> casts with explicit branch on providerKey === 'claude' orchestrator-agent.ts:763, dag-executor.ts:364
silent-failure-hunter parseClaudeConfig/parseCodexConfig silently drop invalid values without logging — add warn for non-undefined rejected values providers/src/claude/config.ts, providers/src/codex/config.ts
test-analyzer applyNodeConfig has zero unit tests (9/10 criticality) — hook-merge with PostToolUse, MCP wildcard expansion, skills/agents wrapping all untested providers/src/claude/provider.ts:341-462
test-analyzer parseClaudeConfig/parseCodexConfig have no direct tests (8/10 criticality) — defensive-drop paths unverified providers/src/claude/config.ts, providers/src/codex/config.ts

Documentation Issues (9 stale references)

File Issue
CLAUDE.md:451 "AI Agent Providers" section still points to packages/core/src/providers/
packages/docs-web/.../architecture.md:305 IAgentProvider location cites packages/core/src/types/index.ts — now in packages/providers/src/types.ts
packages/docs-web/.../architecture.md:331,336 Provider creation example paths point to deleted packages/core/src/providers/
packages/docs-web/.../architecture.md:380 Factory path cites packages/core/src/providers/factory.ts
packages/docs-web/.../architecture.md:443,465 Claude/Codex provider paths cite packages/core/src/providers/
packages/docs-web/.../architecture.md:1241,1246 Checklist entries point to deleted paths
packages/docs-web/.../architecture.md:1367 "See also" section cites deleted paths

Strengths

  • Clean contract layer design (@archon/providers/types with zero SDK deps)
  • Capability flags pattern correctly replaces per-provider if branches in dag-executor
  • ProviderCapabilities is well-designed (8/10) — all 12 flags required, keyof usage ensures compile-time sync
  • IAgentProvider interface is narrow and well-focused (8.5/10)
  • Codex structured output normalization has solid test coverage
  • UnknownProviderError standardizes error shape across all surfaces
  • ~200 lines of mirror types in deps.ts eliminated

Verdict: NEEDS FIXES

Must address before merge:

  1. Add // TODO(#1135) comments in both provider sendQuery methods documenting the env-leak gate gap
  2. Restore MCP missing env vars user-facing warning (was silently dropped during refactor)
  3. Restore Haiku + MCP user-facing warning (was silently removed)

Should address before merge:
4. Fix buildLoopNodeOptions to pass workflow-level nodeConfig (effort/thinking/betas/sandbox)
5. Fix 9 stale documentation paths (1 in CLAUDE.md, 8 in docs-web architecture.md)

Recommended follow-ups:
6. Add applyNodeConfig unit tests (highest test gap)
7. Add parseClaudeConfig/parseCodexConfig direct tests
8. Inline EffortLevel literal union in types.ts to restore type safety

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (5)
eslint.config.mjs (1)

23-23: Narrow this ignore pattern to helpers only.

'**/src/test/**' is broad and disables linting/type-aware checks for every file under src/test, not just fixtures/mocks. Prefer targeting specific helper paths (for example **/src/test/{fixtures,mocks}/**) so real test code stays covered.

Based on learnings: Enforce strict TypeScript configuration with complete type annotations for all functions; no any types without explicit justification.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eslint.config.mjs` at line 23, The ignore pattern '**/src/test/**' in
eslint.config.mjs is too broad—replace it with a narrower pattern that only
skips helper files (e.g. '**/src/test/{fixtures,mocks}/**' or similar) so real
test code remains linted; update the array entry that currently contains
'**/src/test/**' to the new scoped pattern. Additionally enable strict
TypeScript checks by ensuring your TypeScript/ESLint config enforces no implicit
any and full type annotations (activate "strict": true in tsconfig.json and
enable ESLint rules like "@typescript-eslint/no-explicit-any" and
"@typescript-eslint/typedef" where applicable) so functions require complete
type annotations.
packages/providers/src/types.ts (1)

1-177: Complete the contract subpath with UnknownProviderError.

The PR objective calls out UnknownProviderError as part of @archon/providers/types, but this file currently only exports the defaults, chunk types, and IAgentProvider. Without the shared error shape here, callers still need a runtime-package dependency or a duplicate type to distinguish bad provider IDs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/providers/src/types.ts` around lines 1 - 177, Add a shared
UnknownProviderError export to the providers contract so callers can
type/identify missing provider errors without pulling runtime packages: define
and export an interface or class named UnknownProviderError (e.g., export class
UnknownProviderError extends Error { providerId?: string } or export interface
UnknownProviderError { name: 'UnknownProviderError'; providerId?: string;
message: string }) in the same file alongside MessageChunk and IAgentProvider,
and ensure it is part of the public types surface so consumers can import
UnknownProviderError from the contract subpath to detect bad provider IDs.
packages/providers/tsconfig.json (1)

6-7: Exclude test support files from the package tsconfig.

"**/*.test.ts" misses src/test/mocks/logger.ts, so that helper is now part of the main package compilation set. Excluding src/test/** keeps test-only mocks and their pino types out of the runtime source surface.

♻️ Proposed change
-  "exclude": ["node_modules", "dist", "**/*.test.ts"]
+  "exclude": ["node_modules", "dist", "src/test/**/*", "**/*.test.ts"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/providers/tsconfig.json` around lines 6 - 7, The package tsconfig's
exclude array currently only excludes "**/*.test.ts" which still allows test
helper files like src/test/mocks/logger.ts into compilation; update the
tsconfig.json exclude array (the "exclude" property) to also exclude the test
folder pattern (e.g., add "src/test/**") so all test-only mocks and types are
omitted from the package build, leaving the existing "include" and other exclude
entries intact.
packages/core/src/services/title-generator.test.ts (1)

29-31: Stale type annotation — tools is no longer used.

The options type annotation still includes tools?: string[], but the implementation now passes nodeConfig: { allowed_tools: [] } instead. This doesn't break tests but creates misleading documentation.

🧹 Suggested type update
 const mockSendQuery = mock(async function* (): AsyncGenerator<MessageChunk> {
   yield { type: 'assistant', content: 'Summarize Project README' };
   yield { type: 'result' };
 }) as Mock<
   (
     prompt: string,
     cwd: string,
     resumeSessionId?: string,
-    options?: { model?: string; tools?: string[] }
+    options?: { model?: string; nodeConfig?: { allowed_tools?: string[] } }
   ) => AsyncGenerator<MessageChunk>
 >;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/services/title-generator.test.ts` around lines 29 - 31,
Update the stale type annotation on the function signature that returns
AsyncGenerator<MessageChunk>: remove the obsolete "tools?: string[]" from the
options type so it reads options?: { model?: string } (or equivalent) to match
current implementation that uses nodeConfig: { allowed_tools: [] }; locate the
options param in the test (the function returning AsyncGenerator<MessageChunk>)
and adjust any related test/fixture types or comments to reflect the removal of
tools.
packages/workflows/src/dag-executor.test.ts (1)

96-125: Type the capability helpers against the provider contract.

Give mockClaudeCapabilities and mockCodexCapabilities an explicit ProviderCapabilities return type so these mocks fail fast if the shared contract adds or renames a flag.

As per coding guidelines "**/*.{ts,tsx}: Use strict TypeScript configuration with complete type annotations for all functions; no any types without explicit justification`."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/workflows/src/dag-executor.test.ts` around lines 96 - 125, The
helpers mockClaudeCapabilities and mockCodexCapabilities lack an explicit return
type and should be annotated to match the shared ProviderCapabilities contract;
update both function signatures to return ProviderCapabilities (e.g., const
mockClaudeCapabilities = (): ProviderCapabilities => { ... } and likewise for
mockCodexCapabilities) so TypeScript will enforce the capability flags align
with the provider contract and surface errors if flags are added/renamed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/orchestrator/orchestrator-agent.test.ts`:
- Around line 96-101: The mock for getAgentProvider currently returns an empty
object from getCapabilities(), which breaks the ProviderCapabilities contract;
update the mock in the test so getCapabilities() returns a full, test-safe
ProviderCapabilities-shaped object (include required keys such as model,
supportsStreaming, maxTokens, inputFormats, outputFormats, and any other
contract fields) so downstream consumers like dag-executor see a realistic,
typed capabilities object; locate the mock definition for
getAgentProvider/getCapabilities in orchestrator-agent.test.ts and replace the
{} return with the complete capabilities stub.

In `@packages/core/src/workflows/store-adapter.test.ts`:
- Around line 47-49: The test is using mock.module('@archon/providers', ...)
with getAgentProvider returning an empty object which conflicts with other tests
that mock the same module with different shapes; update the mock to use a
consistent, shared shape across tests (e.g., provide a getAgentProvider
implementation that matches the full object shape used elsewhere including
sendQuery/getType/getCapabilities) or remove mock.module usage and import a
single shared test double for `@archon/providers`; specifically change the
mock.module call that defines getAgentProvider in store-adapter.test.ts so it
returns the same interface/implementation used by orchestrator-agent.test.ts (or
centralize the mock into a shared test helper) to eliminate order-dependent,
process-global conflicts.

In `@packages/providers/src/claude/config.ts`:
- Around line 21-27: The code currently filters raw.settingSources into valid
values and only assigns result.settingSources when valid.length > 0, which
silently broadens to Claude's default when the input was an explicit empty array
or contained typos; update the validation in claude/config.ts to detect invalid
inputs and either (a) preserve an explicit empty array by assigning
result.settingSources = [] when raw.settingSources is an array with no valid
entries, or (b) throw a clear validation error if raw.settingSources contains
unknown entries; locate the logic around raw.settingSources and
result.settingSources and implement one of these behaviors (preserve empty array
or throw) and include a brief error message mentioning invalid settingSources
values when throwing.

In `@packages/providers/src/claude/provider.ts`:
- Around line 395-418: The current check uses if (nodeConfig.skills) which is
truthy for an empty array and thus creates a synthetic 'Skill' agent even when
no skills exist; change the guard to check for a non-empty array (e.g.,
nodeConfig.skills && nodeConfig.skills.length > 0) before building agentId
'dag-node-skills', agentDef, assigning options.agents/options.agent, extending
options.allowedTools with 'Skill', and logging via getLog().info so the
synthetic agent/tool is only created when skills.length > 0.

In `@packages/providers/src/codex/provider.ts`:
- Around line 178-183: The code memoizes a single process-wide Codex instance so
passing assistantConfig.codexBinaryPath per request in sendQuery() only affects
the first request; update getCodex() to either stop using a single global
instance or to cache instances keyed by the binary path
(assistantConfig.codexBinaryPath) so each unique binary path returns its own
Codex process. Locate getCodex and change its memoization strategy to use the
codexBinaryPath as the cache key (or always spawn a new Codex when a different
path is provided), and ensure sendQuery()/provider.ts continues to pass
requestOptions?.assistantConfig.codexBinaryPath through to getCodex().
- Around line 476-479: The current getLog().warn call logs raw assistant output
via the outputPreview field (accumulatedText) which may contain PII; update the
log at the codex.structured_output_not_json site to remove outputPreview and
instead log only non-sensitive metadata (for example the output length via
accumulatedText.length and/or a deterministic non-reversible fingerprint like a
short hash), using the existing getLog().warn invocation and leaving the event
name 'codex.structured_output_not_json' unchanged.
- Around line 269-273: The loop that listens for streaming events in sendQuery
currently breaks silently when requestOptions?.abortSignal?.aborted is true;
instead throw a clear abort error so callers can't interpret a partial stream as
success. Update the for-await loop in sendQuery (the block iterating
result.events) to detect requestOptions?.abortSignal?.aborted and throw a
specific error (e.g., an AbortError or Error with "query aborted" message)
rather than calling getLog().info and break; ensure the thrown error is
documented in sendQuery's behavior so callers can handle cancellation
explicitly.

In `@packages/workflows/src/dag-executor.ts`:
- Around line 1401-1412: buildLoopNodeOptions currently only passes model, env,
and assistantConfig into loop nodes and executeLoopNode never receives
workflow-level options, so workflow-level fields (effort, thinking,
fallbackModel, betas, sandbox) are ignored for loop: nodes; update
buildLoopNodeOptions/executeLoopNode to either call/return
resolveNodeProviderAndModel(...) for the node (so loop nodes inherit the same
resolved provider/model/options) or explicitly include the workflow-level fields
(effort, thinking, fallbackModel, betas, sandbox) and the nodeConfig into the
returned SendQueryOptions so executeLoopNode receives the same
workflowLevelOptions used by prompt/command nodes (refer to functions
buildLoopNodeOptions, executeLoopNode, and resolveNodeProviderAndModel to locate
and apply the fix).

---

Nitpick comments:
In `@eslint.config.mjs`:
- Line 23: The ignore pattern '**/src/test/**' in eslint.config.mjs is too
broad—replace it with a narrower pattern that only skips helper files (e.g.
'**/src/test/{fixtures,mocks}/**' or similar) so real test code remains linted;
update the array entry that currently contains '**/src/test/**' to the new
scoped pattern. Additionally enable strict TypeScript checks by ensuring your
TypeScript/ESLint config enforces no implicit any and full type annotations
(activate "strict": true in tsconfig.json and enable ESLint rules like
"@typescript-eslint/no-explicit-any" and "@typescript-eslint/typedef" where
applicable) so functions require complete type annotations.

In `@packages/core/src/services/title-generator.test.ts`:
- Around line 29-31: Update the stale type annotation on the function signature
that returns AsyncGenerator<MessageChunk>: remove the obsolete "tools?:
string[]" from the options type so it reads options?: { model?: string } (or
equivalent) to match current implementation that uses nodeConfig: {
allowed_tools: [] }; locate the options param in the test (the function
returning AsyncGenerator<MessageChunk>) and adjust any related test/fixture
types or comments to reflect the removal of tools.

In `@packages/providers/src/types.ts`:
- Around line 1-177: Add a shared UnknownProviderError export to the providers
contract so callers can type/identify missing provider errors without pulling
runtime packages: define and export an interface or class named
UnknownProviderError (e.g., export class UnknownProviderError extends Error {
providerId?: string } or export interface UnknownProviderError { name:
'UnknownProviderError'; providerId?: string; message: string }) in the same file
alongside MessageChunk and IAgentProvider, and ensure it is part of the public
types surface so consumers can import UnknownProviderError from the contract
subpath to detect bad provider IDs.

In `@packages/providers/tsconfig.json`:
- Around line 6-7: The package tsconfig's exclude array currently only excludes
"**/*.test.ts" which still allows test helper files like
src/test/mocks/logger.ts into compilation; update the tsconfig.json exclude
array (the "exclude" property) to also exclude the test folder pattern (e.g.,
add "src/test/**") so all test-only mocks and types are omitted from the package
build, leaving the existing "include" and other exclude entries intact.

In `@packages/workflows/src/dag-executor.test.ts`:
- Around line 96-125: The helpers mockClaudeCapabilities and
mockCodexCapabilities lack an explicit return type and should be annotated to
match the shared ProviderCapabilities contract; update both function signatures
to return ProviderCapabilities (e.g., const mockClaudeCapabilities = ():
ProviderCapabilities => { ... } and likewise for mockCodexCapabilities) so
TypeScript will enforce the capability flags align with the provider contract
and surface errors if flags are added/renamed.
🪄 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: 4334841a-2d3b-4a41-aa30-ca676101d7f0

📥 Commits

Reviewing files that changed from the base of the PR and between eb75ab6 and 407ce39.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (43)
  • CLAUDE.md
  • eslint.config.mjs
  • packages/core/package.json
  • packages/core/src/config/config-types.ts
  • packages/core/src/index.ts
  • packages/core/src/orchestrator/orchestrator-agent.test.ts
  • packages/core/src/orchestrator/orchestrator-agent.ts
  • packages/core/src/orchestrator/orchestrator-isolation.test.ts
  • packages/core/src/orchestrator/orchestrator.test.ts
  • packages/core/src/providers/factory.test.ts
  • packages/core/src/providers/index.ts
  • packages/core/src/services/title-generator.test.ts
  • packages/core/src/services/title-generator.ts
  • packages/core/src/types/index.ts
  • packages/core/src/workflows/store-adapter.test.ts
  • packages/core/src/workflows/store-adapter.ts
  • packages/providers/package.json
  • packages/providers/src/claude/config.ts
  • packages/providers/src/claude/index.ts
  • packages/providers/src/claude/provider.test.ts
  • packages/providers/src/claude/provider.ts
  • packages/providers/src/codex/binary-guard.test.ts
  • packages/providers/src/codex/binary-resolver-dev.test.ts
  • packages/providers/src/codex/binary-resolver.test.ts
  • packages/providers/src/codex/binary-resolver.ts
  • packages/providers/src/codex/config.ts
  • packages/providers/src/codex/index.ts
  • packages/providers/src/codex/provider.test.ts
  • packages/providers/src/codex/provider.ts
  • packages/providers/src/errors.ts
  • packages/providers/src/factory.test.ts
  • packages/providers/src/factory.ts
  • packages/providers/src/index.ts
  • packages/providers/src/test/mocks/logger.ts
  • packages/providers/src/types.ts
  • packages/providers/tsconfig.json
  • packages/server/package.json
  • packages/server/src/adapters/web.ts
  • packages/workflows/package.json
  • packages/workflows/src/dag-executor.test.ts
  • packages/workflows/src/dag-executor.ts
  • packages/workflows/src/deps.ts
  • packages/workflows/src/hooks.test.ts
💤 Files with no reviewable changes (4)
  • packages/providers/src/codex/binary-resolver.ts
  • packages/core/src/providers/index.ts
  • packages/core/src/index.ts
  • packages/core/src/providers/factory.test.ts

Comment on lines +96 to 101
mock.module('@archon/providers', () => ({
getAgentProvider: mock(() => ({
sendQuery: mock(async function* () {}),
getType: mock(() => 'claude'),
getCapabilities: mock(() => ({})),
})),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cd /repo && find . -name "orchestrator-agent.test.ts" -type f | head -5

Repository: coleam00/Archon

Length of output: 114


🏁 Script executed:

cd /repo && wc -l packages/core/src/orchestrator/orchestrator-agent.test.ts

Repository: coleam00/Archon

Length of output: 114


🏁 Script executed:

cd /repo && sed -n '85,115p' packages/core/src/orchestrator/orchestrator-agent.test.ts

Repository: coleam00/Archon

Length of output: 114


🏁 Script executed:

cd /repo && find . -path "*/providers/types*" -name "*.ts" -type f | head -10

Repository: coleam00/Archon

Length of output: 114


🏁 Script executed:

cd /repo && rg "ProviderCapabilities" --type ts -A 5 | head -80

Repository: coleam00/Archon

Length of output: 114


🏁 Script executed:

git ls-files "packages/core/src/orchestrator/orchestrator-agent.test.ts"

Repository: coleam00/Archon

Length of output: 116


🏁 Script executed:

wc -l packages/core/src/orchestrator/orchestrator-agent.test.ts

Repository: coleam00/Archon

Length of output: 121


🏁 Script executed:

sed -n '85,115p' packages/core/src/orchestrator/orchestrator-agent.test.ts

Repository: coleam00/Archon

Length of output: 955


🏁 Script executed:

find . -path "*/providers/*" -name "*.ts" | grep -i "type\|interface" | head -20

Repository: coleam00/Archon

Length of output: 92


🏁 Script executed:

rg "ProviderCapabilities" --type ts -B 2 -A 10 | head -100

Repository: coleam00/Archon

Length of output: 5924


🏁 Script executed:

grep -n "getCapabilities" packages/core/src/orchestrator/orchestrator-agent.test.ts

Repository: coleam00/Archon

Length of output: 101


🏁 Script executed:

grep -n "getCapabilities" packages/core/src/orchestrator/orchestrator-agent.ts

Repository: coleam00/Archon

Length of output: 41


🏁 Script executed:

cat -n packages/providers/src/types.ts | grep -A 15 "interface ProviderCapabilities"

Repository: coleam00/Archon

Length of output: 503


🏁 Script executed:

sed -n '1,50p' packages/core/src/orchestrator/orchestrator-agent.test.ts

Repository: coleam00/Archon

Length of output: 2006


🏁 Script executed:

grep -n "getCapabilities" packages/core/src/orchestrator/orchestrator-agent.ts -B 3 -A 5

Repository: coleam00/Archon

Length of output: 41


🏁 Script executed:

rg "getCapabilities()" packages/core/src --type ts -B 2 -A 3 | head -80

Repository: coleam00/Archon

Length of output: 537


🏁 Script executed:

cat -n packages/providers/src/codex/provider.ts | sed -n '50,90p'

Repository: coleam00/Archon

Length of output: 1476


🏁 Script executed:

cat -n packages/providers/src/claude/provider.ts | grep -A 15 "getCapabilities"

Repository: coleam00/Archon

Length of output: 536


🏁 Script executed:

rg "\.getCapabilities" packages/core/src/orchestrator --type ts -B 3 -A 3

Repository: coleam00/Archon

Length of output: 41


🏁 Script executed:

grep -n "sendQuery\|getType\|getCapabilities" packages/core/src/orchestrator/orchestrator-agent.ts

Repository: coleam00/Archon

Length of output: 158


🏁 Script executed:

sed -n '96,110p' packages/core/src/orchestrator/orchestrator-agent.test.ts

Repository: coleam00/Archon

Length of output: 519


🏁 Script executed:

grep -A 20 "getAgentProvider: mock" packages/core/src/orchestrator/orchestrator-agent.test.ts

Repository: coleam00/Archon

Length of output: 749


🏁 Script executed:

rg "capabilities" packages/core/src/orchestrator/orchestrator-agent.test.ts -i

Repository: coleam00/Archon

Length of output: 97


🏁 Script executed:

rg "getCapabilities" packages/workflows/src/dag-executor.ts -B 3 -A 5

Repository: coleam00/Archon

Length of output: 393


🏁 Script executed:

grep -n "getAgentProvider\|parseOrchestratorCommands\|filterToolIndicators" packages/core/src/orchestrator/orchestrator-agent.test.ts | head -20

Repository: coleam00/Archon

Length of output: 1544


🏁 Script executed:

sed -n '150,250p' packages/core/src/orchestrator/orchestrator-agent.test.ts | head -60

Repository: coleam00/Archon

Length of output: 1740


🏁 Script executed:

rg "describe\|test\(" packages/core/src/orchestrator/orchestrator-agent.test.ts | head -20

Repository: coleam00/Archon

Length of output: 41


🏁 Script executed:

sed -n '194,350p' packages/core/src/orchestrator/orchestrator-agent.test.ts | grep -A 30 "describe('parseOrchestratorCommands'"

Repository: coleam00/Archon

Length of output: 1426


🏁 Script executed:

grep -n "handleMessage" packages/core/src/orchestrator/orchestrator-agent.test.ts | head -15

Repository: coleam00/Archon

Length of output: 1187


🏁 Script executed:

sed -n '950,1050p' packages/core/src/orchestrator/orchestrator-agent.test.ts

Repository: coleam00/Archon

Length of output: 3849


🏁 Script executed:

grep -A 50 "describe('handleMessage" packages/core/src/orchestrator/orchestrator-agent.test.ts | head -70

Repository: coleam00/Archon

Length of output: 41


🏁 Script executed:

cat -n packages/core/src/orchestrator/orchestrator-agent.ts | sed -n '1,100p'

Repository: coleam00/Archon

Length of output: 4681


🏁 Script executed:

grep -n "getCapabilities\|\.getCapabilities" packages/core/src/orchestrator/orchestrator-agent.ts

Repository: coleam00/Archon

Length of output: 41


🏁 Script executed:

sed -n '830,870p' packages/core/src/orchestrator/orchestrator-agent.ts

Repository: coleam00/Archon

Length of output: 1553


🏁 Script executed:

grep -C 5 "getCapabilities" packages/workflows/src/dag-executor.ts | head -60

Repository: coleam00/Archon

Length of output: 404


🏁 Script executed:

sed -n '820,900p' packages/core/src/orchestrator/orchestrator-agent.ts | grep -i "capability\|getCapabilities"

Repository: coleam00/Archon

Length of output: 41


🏁 Script executed:

rg "executeWorkflow|createWorkflowDeps" packages/core/src/orchestrator/orchestrator-agent.ts -A 5 -B 2

Repository: coleam00/Archon

Length of output: 1463


🏁 Script executed:

sed -n '1000,1200p' packages/core/src/orchestrator/orchestrator-agent.test.ts | grep -A 30 "describe('handleMessage"

Repository: coleam00/Archon

Length of output: 41


🏁 Script executed:

rg "executeWorkflow\(" packages/core/src/orchestrator/orchestrator-agent.test.ts -A 3 -B 1

Repository: coleam00/Archon

Length of output: 41


🏁 Script executed:

grep -n "aiClient.getCapabilities\|getAgentProvider" packages/workflows/src/dag-executor.ts -B 3 -A 10 | head -60

Repository: coleam00/Archon

Length of output: 2060


🏁 Script executed:

sed -n '1000,1410p' packages/core/src/orchestrator/orchestrator-agent.test.ts

Repository: coleam00/Archon

Length of output: 17354


🏁 Script executed:

grep -n "describe('handleMessage" packages/core/src/orchestrator/orchestrator-agent.test.ts

Repository: coleam00/Archon

Length of output: 41


🏁 Script executed:

sed -n '820,950p' packages/core/src/orchestrator/orchestrator-agent.test.ts

Repository: coleam00/Archon

Length of output: 5739


🏁 Script executed:

rg "getCapabilities" packages/core/src/orchestrator/orchestrator-agent.test.ts -C 3

Repository: coleam00/Archon

Length of output: 223


🏁 Script executed:

grep -n "import.*ProviderCapabilities\|from.*providers" packages/core/src/orchestrator/orchestrator-agent.test.ts | head -15

Repository: coleam00/Archon

Length of output: 41


🏁 Script executed:

sed -n '1,30p' packages/core/src/orchestrator/orchestrator-agent.test.ts | grep "import"

Repository: coleam00/Archon

Length of output: 579


🏁 Script executed:

grep "getCapabilities" packages/core/src/orchestrator/orchestrator-agent.ts

Repository: coleam00/Archon

Length of output: 41


🏁 Script executed:

rg "sendQuery\|getType\|getCapabilities" packages/core/src/orchestrator/orchestrator-agent.ts -n

Repository: coleam00/Archon

Length of output: 41


🏁 Script executed:

sed -n '27,35p' packages/core/src/orchestrator/orchestrator-agent.ts

Repository: coleam00/Archon

Length of output: 577


🏁 Script executed:

rg "from.*@archon/providers" packages/core/src --type ts | head -10

Repository: coleam00/Archon

Length of output: 711


🏁 Script executed:

cat -n packages/providers/src/index.ts | grep "export.*ProviderCapabilities"

Repository: coleam00/Archon

Length of output: 41


🏁 Script executed:

grep "ProviderCapabilities" packages/providers/src/index.ts

Repository: coleam00/Archon

Length of output: 82


Return a full capabilities object from getCapabilities() (Line 100) to align with the ProviderCapabilities contract.

Using {} diverges from the provider interface contract and weakens test determinism. While this mock is not currently exercised by the orchestrator-agent tests, the provider's getCapabilities() is called downstream in dag-executor, and a complete typed mock improves test fidelity and prevents future regressions.

Suggested test-safe contract-aligned mock
+import type { ProviderCapabilities } from '@archon/providers/types';
+
+const mockCapabilities: ProviderCapabilities = {
+  sessionResume: false,
+  mcp: false,
+  hooks: false,
+  skills: false,
+  toolRestrictions: false,
+  structuredOutput: false,
+  envInjection: false,
+  costControl: false,
+  effortControl: false,
+  thinkingControl: false,
+  fallbackModel: false,
+  sandbox: false,
+};
+
 mock.module('@archon/providers', () => ({
   getAgentProvider: mock(() => ({
     sendQuery: mock(async function* () {}),
     getType: mock(() => 'claude'),
-    getCapabilities: mock(() => ({})),
+    getCapabilities: mock(() => mockCapabilities),
   })),
 }));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
mock.module('@archon/providers', () => ({
getAgentProvider: mock(() => ({
sendQuery: mock(async function* () {}),
getType: mock(() => 'claude'),
getCapabilities: mock(() => ({})),
})),
import type { ProviderCapabilities } from '@archon/providers/types';
const mockCapabilities: ProviderCapabilities = {
sessionResume: false,
mcp: false,
hooks: false,
skills: false,
toolRestrictions: false,
structuredOutput: false,
envInjection: false,
costControl: false,
effortControl: false,
thinkingControl: false,
fallbackModel: false,
sandbox: false,
};
mock.module('@archon/providers', () => ({
getAgentProvider: mock(() => ({
sendQuery: mock(async function* () {}),
getType: mock(() => 'claude'),
getCapabilities: mock(() => mockCapabilities),
})),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/orchestrator/orchestrator-agent.test.ts` around lines 96 -
101, The mock for getAgentProvider currently returns an empty object from
getCapabilities(), which breaks the ProviderCapabilities contract; update the
mock in the test so getCapabilities() returns a full, test-safe
ProviderCapabilities-shaped object (include required keys such as model,
supportsStreaming, maxTokens, inputFormats, outputFormats, and any other
contract fields) so downstream consumers like dag-executor see a realistic,
typed capabilities object; locate the mock definition for
getAgentProvider/getCapabilities in orchestrator-agent.test.ts and replace the
{} return with the complete capabilities stub.

Comment on lines +47 to 49
mock.module('@archon/providers', () => ({
getAgentProvider: mock(() => ({})),
}));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate all `@archon/providers` module mocks:"
rg -n --type ts "mock\.module\('@archon/providers'" -A8 -B2

echo
echo "Show getAgentProvider mock return shapes around those mocks:"
rg -n --type ts "getAgentProvider:\s*mock\(" -A6 -B2

Repository: coleam00/Archon

Length of output: 9106


Consolidate @archon/providers mock implementations across test files.

Multiple test files mock @archon/providers with conflicting return shapes: store-adapter.test.ts returns {}, orchestrator-isolation.test.ts returns null, and orchestrator-agent.test.ts returns a full object with sendQuery, getType, and getCapabilities methods. With Bun's process-global and irreversible mock.module(), this pattern creates order-dependent test failures when tests run in the same process.

Per coding guidelines: "never mock.module() a module path that another test file also mocks with different implementation."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/workflows/store-adapter.test.ts` around lines 47 - 49, The
test is using mock.module('@archon/providers', ...) with getAgentProvider
returning an empty object which conflicts with other tests that mock the same
module with different shapes; update the mock to use a consistent, shared shape
across tests (e.g., provide a getAgentProvider implementation that matches the
full object shape used elsewhere including sendQuery/getType/getCapabilities) or
remove mock.module usage and import a single shared test double for
`@archon/providers`; specifically change the mock.module call that defines
getAgentProvider in store-adapter.test.ts so it returns the same
interface/implementation used by orchestrator-agent.test.ts (or centralize the
mock into a shared test helper) to eliminate order-dependent, process-global
conflicts.

Comment on lines +21 to +27
if (Array.isArray(raw.settingSources)) {
const valid = raw.settingSources.filter(
(s): s is 'project' | 'user' => s === 'project' || s === 'user'
);
if (valid.length > 0) {
result.settingSources = valid;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't silently broaden settingSources on invalid input.

If this array contains a typo—or an explicit empty array—result.settingSources stays undefined, so downstream falls back to Claude's default ['project']. That re-enables project CLAUDE.md loading instead of honoring the user's restriction. Please validate and throw, or preserve the explicit empty array.

🛡️ Suggested fix
   if (Array.isArray(raw.settingSources)) {
     const valid = raw.settingSources.filter(
       (s): s is 'project' | 'user' => s === 'project' || s === 'user'
     );
-    if (valid.length > 0) {
-      result.settingSources = valid;
-    }
+    if (valid.length !== raw.settingSources.length) {
+      throw new Error('claude.settingSources must contain only "project" or "user"');
+    }
+    result.settingSources = valid;
   }

As per coding guidelines, "**/*.ts: Fail fast with clear errors for unsupported or unsafe states — never silently swallow errors or silently broaden permissions; document fallback behavior with a comment when intentional and safe".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (Array.isArray(raw.settingSources)) {
const valid = raw.settingSources.filter(
(s): s is 'project' | 'user' => s === 'project' || s === 'user'
);
if (valid.length > 0) {
result.settingSources = valid;
}
if (Array.isArray(raw.settingSources)) {
const valid = raw.settingSources.filter(
(s): s is 'project' | 'user' => s === 'project' || s === 'user'
);
if (valid.length !== raw.settingSources.length) {
throw new Error('claude.settingSources must contain only "project" or "user"');
}
result.settingSources = valid;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/providers/src/claude/config.ts` around lines 21 - 27, The code
currently filters raw.settingSources into valid values and only assigns
result.settingSources when valid.length > 0, which silently broadens to Claude's
default when the input was an explicit empty array or contained typos; update
the validation in claude/config.ts to detect invalid inputs and either (a)
preserve an explicit empty array by assigning result.settingSources = [] when
raw.settingSources is an array with no valid entries, or (b) throw a clear
validation error if raw.settingSources contains unknown entries; locate the
logic around raw.settingSources and result.settingSources and implement one of
these behaviors (preserve empty array or throw) and include a brief error
message mentioning invalid settingSources values when throwing.

Comment on lines +395 to +418
if (nodeConfig.skills) {
const skills = nodeConfig.skills;
const agentId = 'dag-node-skills';
const agentTools = options.tools ? [...(options.tools as string[]), 'Skill'] : ['Skill'];
const agentDef: {
description: string;
prompt: string;
skills: string[];
tools: string[];
model?: string;
} = {
description: 'DAG node with skills',
prompt: `You have preloaded skills: ${skills.join(', ')}. Use them when relevant.`,
skills,
tools: agentTools,
};
if (options.model) agentDef.model = options.model;
options.agents = { [agentId]: agentDef };
options.agent = agentId;
if (!options.allowedTools?.includes('Skill')) {
options.allowedTools = [...(options.allowedTools ?? []), 'Skill'];
}
getLog().info({ skills, agentId }, 'claude.skills_agent_created');
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Treat skills: [] as unset.

if (nodeConfig.skills) is true for an empty array, so this still creates the synthetic Skill agent/tool even when no skills were configured. The executor side already treats skills.length > 0 as the enabled case, so this should gate on nodeConfig.skills.length > 0 too.

💡 Minimal fix
-  if (nodeConfig.skills) {
+  if (nodeConfig.skills && nodeConfig.skills.length > 0) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (nodeConfig.skills) {
const skills = nodeConfig.skills;
const agentId = 'dag-node-skills';
const agentTools = options.tools ? [...(options.tools as string[]), 'Skill'] : ['Skill'];
const agentDef: {
description: string;
prompt: string;
skills: string[];
tools: string[];
model?: string;
} = {
description: 'DAG node with skills',
prompt: `You have preloaded skills: ${skills.join(', ')}. Use them when relevant.`,
skills,
tools: agentTools,
};
if (options.model) agentDef.model = options.model;
options.agents = { [agentId]: agentDef };
options.agent = agentId;
if (!options.allowedTools?.includes('Skill')) {
options.allowedTools = [...(options.allowedTools ?? []), 'Skill'];
}
getLog().info({ skills, agentId }, 'claude.skills_agent_created');
}
if (nodeConfig.skills && nodeConfig.skills.length > 0) {
const skills = nodeConfig.skills;
const agentId = 'dag-node-skills';
const agentTools = options.tools ? [...(options.tools as string[]), 'Skill'] : ['Skill'];
const agentDef: {
description: string;
prompt: string;
skills: string[];
tools: string[];
model?: string;
} = {
description: 'DAG node with skills',
prompt: `You have preloaded skills: ${skills.join(', ')}. Use them when relevant.`,
skills,
tools: agentTools,
};
if (options.model) agentDef.model = options.model;
options.agents = { [agentId]: agentDef };
options.agent = agentId;
if (!options.allowedTools?.includes('Skill')) {
options.allowedTools = [...(options.allowedTools ?? []), 'Skill'];
}
getLog().info({ skills, agentId }, 'claude.skills_agent_created');
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/providers/src/claude/provider.ts` around lines 395 - 418, The
current check uses if (nodeConfig.skills) which is truthy for an empty array and
thus creates a synthetic 'Skill' agent even when no skills exist; change the
guard to check for a non-empty array (e.g., nodeConfig.skills &&
nodeConfig.skills.length > 0) before building agentId 'dag-node-skills',
agentDef, assigning options.agents/options.agent, extending options.allowedTools
with 'Skill', and logging via getLog().info so the synthetic agent/tool is only
created when skills.length > 0.

Comment on lines +178 to +183
const assistantConfig = requestOptions?.assistantConfig ?? {};
const codexConfig = parseCodexConfig(assistantConfig);

// Initialize Codex SDK with binary path override (resolved from env/config/vendor).
// In dev mode, resolveCodexBinaryPath returns undefined and the SDK uses node_modules.
// In binary mode, it resolves from env/config/vendor or throws with install instructions.
const codex = await getCodex(mergedConfig?.assistants.codex.codexBinaryPath);
const threadOptions = buildThreadOptions(cwd, options);
// Initialize Codex SDK with binary path override
const codex = await getCodex(codexConfig.codexBinaryPath);
const threadOptions = buildThreadOptions(cwd, requestOptions?.model, assistantConfig);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

assistantConfig.codexBinaryPath only works for the first request.

sendQuery() now passes a per-request binary override into getCodex(), but getCodex() still memoizes a single process-wide Codex instance. In a long-lived server, the first caller wins and later projects/workflows silently reuse the wrong Codex binary.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/providers/src/codex/provider.ts` around lines 178 - 183, The code
memoizes a single process-wide Codex instance so passing
assistantConfig.codexBinaryPath per request in sendQuery() only affects the
first request; update getCodex() to either stop using a single global instance
or to cache instances keyed by the binary path (assistantConfig.codexBinaryPath)
so each unique binary path returns its own Codex process. Locate getCodex and
change its memoization strategy to use the codexBinaryPath as the cache key (or
always spawn a new Codex when a different path is provided), and ensure
sendQuery()/provider.ts continues to pass
requestOptions?.assistantConfig.codexBinaryPath through to getCodex().

Comment on lines 269 to 273
for await (const event of result.events) {
// Check abort signal between events
if (options?.abortSignal?.aborted) {
if (requestOptions?.abortSignal?.aborted) {
getLog().info('query_aborted_between_events');
break;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Aborting mid-stream must throw, not end cleanly.

This branch currently exits the event loop and then returns from sendQuery() without a result or an error. Callers see a normal EOF and can treat a partially streamed turn as successful instead of cancelled.

Suggested fix
-          if (requestOptions?.abortSignal?.aborted) {
-            getLog().info('query_aborted_between_events');
-            break;
-          }
+          if (requestOptions?.abortSignal?.aborted) {
+            getLog().info('query_aborted_between_events');
+            throw new Error('Query aborted');
+          }

As per coding guidelines "**/*.ts: Fail fast with clear errors for unsupported or unsafe states — never silently swallow errors or silently broaden permissions; document fallback behavior with a comment when intentional and safe`."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for await (const event of result.events) {
// Check abort signal between events
if (options?.abortSignal?.aborted) {
if (requestOptions?.abortSignal?.aborted) {
getLog().info('query_aborted_between_events');
break;
}
for await (const event of result.events) {
if (requestOptions?.abortSignal?.aborted) {
getLog().info('query_aborted_between_events');
throw new Error('Query aborted');
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/providers/src/codex/provider.ts` around lines 269 - 273, The loop
that listens for streaming events in sendQuery currently breaks silently when
requestOptions?.abortSignal?.aborted is true; instead throw a clear abort error
so callers can't interpret a partial stream as success. Update the for-await
loop in sendQuery (the block iterating result.events) to detect
requestOptions?.abortSignal?.aborted and throw a specific error (e.g., an
AbortError or Error with "query aborted" message) rather than calling
getLog().info and break; ensure the thrown error is documented in sendQuery's
behavior so callers can handle cancellation explicitly.

Comment on lines +476 to +479
getLog().warn(
{ outputPreview: accumulatedText.slice(0, 200) },
'codex.structured_output_not_json'
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Do not log assistant output previews here.

outputPreview captures raw model text, which can contain user content, secrets, or PII. This warning should log metadata only.

Suggested fix
-                getLog().warn(
-                  { outputPreview: accumulatedText.slice(0, 200) },
-                  'codex.structured_output_not_json'
-                );
+                getLog().warn(
+                  { outputLength: accumulatedText.length },
+                  'codex.structured_output_not_json'
+                );

As per coding guidelines "**/*.ts: Use Pino logger factory from @archon/paths with event naming format {domain}.{action}_{state} (states: _started, _completed, _failed, _validated, _rejected). Never log API keys, tokens, user message content, or PII`."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
getLog().warn(
{ outputPreview: accumulatedText.slice(0, 200) },
'codex.structured_output_not_json'
);
getLog().warn(
{ outputLength: accumulatedText.length },
'codex.structured_output_not_json'
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/providers/src/codex/provider.ts` around lines 476 - 479, The current
getLog().warn call logs raw assistant output via the outputPreview field
(accumulatedText) which may contain PII; update the log at the
codex.structured_output_not_json site to remove outputPreview and instead log
only non-sensitive metadata (for example the output length via
accumulatedText.length and/or a deterministic non-reversible fingerprint like a
short hash), using the existing getLog().warn invocation and leaving the event
name 'codex.structured_output_not_json' unchanged.

Comment thread packages/workflows/src/dag-executor.ts
Wirasm added 2 commits April 12, 2026 23:23
Critical fixes:
- Restore MCP missing env vars user-facing warning (was silently dropped)
- Restore Haiku + MCP tool search warning
- Fix buildLoopNodeOptions to pass workflow-level nodeConfig (effort,
  thinking, betas, sandbox were silently lost for loop nodes)
- Add TODO(#1135) comments documenting env-leak gate gap

Cleanup:
- Remove backward-compat type aliases from deps.ts (keep WorkflowTokenUsage)
- Remove 26 unnecessary eslint-disable comments from test files
- Trim internal helpers from providers barrel (withFirstMessageTimeout,
  getProcessUid, loadMcpConfig, buildSDKHooksFromYAML)
- Add @archon/providers dep to CLI package.json
- Fix 8 stale documentation paths pointing to deleted core/src/providers/
- Add E2E smoke test workflows for both Claude and Codex providers
The dag-executor only forwarded system chunks starting with
"MCP server connection failed:" — all other provider warnings
(missing env vars, Haiku+MCP, structured output issues) were
logged but never reached the user.

Now forwards all system chunks starting with ⚠️ (the prefix
providers use for user-actionable warnings).
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (3)
packages/providers/src/codex/provider.ts (3)

473-481: ⚠️ Potential issue | 🟠 Major

Remove outputPreview from this warning log.

accumulatedText is raw model output. Logging even a 200-char preview can leak user content, secrets, or PII.

Suggested fix
               } catch {
                 getLog().warn(
-                  { outputPreview: accumulatedText.slice(0, 200) },
+                  { outputLength: accumulatedText.length },
                   'codex.structured_output_not_json'
                 );

As per coding guidelines "**/*.{ts,tsx}: Never log API keys or tokens - mask as token.slice(0, 8) + '...'; never log user message content or PII`."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/providers/src/codex/provider.ts` around lines 473 - 481, The warning
log in the JSON parse failure handling (inside the hasOutputFormat branch that
uses accumulatedText and sets structuredOutput) currently includes a preview of
accumulatedText which may leak user data; remove the outputPreview property from
the getLog().warn call and replace it with a generic, non-sensitive message
(e.g., 'codex.structured_output_not_json' with no preview or any masked content)
so that accumulatedText is never logged; update the catch block around
JSON.parse(accumulatedText) that calls getLog().warn to only emit non-sensitive
context.

271-275: ⚠️ Potential issue | 🟠 Major

Aborting mid-stream should throw, not return cleanly.

This break falls through to Line 500, so callers can observe a partial stream as a normal completion instead of an explicit cancellation.

Suggested fix
         for await (const event of result.events) {
           if (requestOptions?.abortSignal?.aborted) {
             getLog().info('query_aborted_between_events');
-            break;
+            throw new Error('Query aborted');
           }

As per coding guidelines "**/*.{ts,tsx}: Prefer throwing early with a clear error for unsupported or unsafe states - never silently swallow errors; never silently broaden permissions or capabilities`."

Also applies to: 500-500

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/providers/src/codex/provider.ts` around lines 271 - 275, The loop
over result.events currently uses "break" when
requestOptions?.abortSignal?.aborted is true, which silently completes the
stream; instead throw an explicit abort error so callers observe cancellation.
Replace the break in the for-await-of handling of result.events with throwing a
clear AbortError (e.g., propagate requestOptions.abortSignal.reason if present
or throw a new DOMException('Aborted', 'AbortError') / new Error('Query
aborted')), keeping the existing getLog().info('query_aborted_between_events')
call before throwing to preserve logging; ensure the thrown error propagates out
of the async iterator so callers won't treat the stream as a normal completion.

183-185: ⚠️ Potential issue | 🟠 Major

Key the Codex cache by codexBinaryPath.

sendQuery() now passes a per-request binary override, but getCodex() still memoizes a single process-wide instance. After the first request, later workflows silently reuse the wrong Codex binary.

Fix sketch
- let codexInstance: Codex | null = null;
- let codexInitPromise: Promise<Codex> | null = null;
+ const codexInstances = new Map<string, Codex>();
+ const codexInitPromises = new Map<string, Promise<Codex>>();

 async function getCodex(configCodexBinaryPath?: string): Promise<Codex> {
-  if (codexInstance) return codexInstance;
+  const resolvedPath = await resolveCodexBinaryPath(configCodexBinaryPath);
+  const cacheKey = resolvedPath ?? '__default__';
+
+  const existing = codexInstances.get(cacheKey);
+  if (existing) return existing;
+
+  const pending = codexInitPromises.get(cacheKey);
+  if (pending) return pending;

-  if (!codexInitPromise) {
-    codexInitPromise = (async (): Promise<Codex> => {
-      const codexPathOverride = await resolveCodexBinaryPath(configCodexBinaryPath);
-      const instance = new Codex({ codexPathOverride });
-      codexInstance = instance;
+  const init = (async (): Promise<Codex> => {
+      const instance = new Codex({ codexPathOverride: resolvedPath });
+      codexInstances.set(cacheKey, instance);
       return instance;
     })().catch(err => {
-      codexInitPromise = null;
+      codexInitPromises.delete(cacheKey);
       throw err;
     });
-  }
-  return codexInitPromise;
+  codexInitPromises.set(cacheKey, init);
+  return init;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/providers/src/codex/provider.ts` around lines 183 - 185, The Codex
instance is being memoized globally causing reuse of the wrong binary when
requests pass a per-request codexBinaryPath; update the caching/memoization in
getCodex so it keys instances by the provided codexBinaryPath (i.e., maintain a
map from codexBinaryPath -> Codex instance) and ensure sendQuery / the call site
still passes the per-request codexBinaryPath into getCodex; reference getCodex
and sendQuery (and codexConfig.codexBinaryPath) to locate and change the
memoization strategy so each unique binary path gets its own cached Codex
process.
🧹 Nitpick comments (1)
packages/workflows/src/dag-executor.test.ts (1)

4582-4642: Add a loop-node assertion for the workflow-level effort fix.

These cases only cover normal DAG nodes. The regression fixed in this PR was in buildLoopNodeOptions, so the loop path is still unprotected here. Please add at least one loop-node case that proves workflow-level effort reaches sendQuery() there too.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/workflows/src/dag-executor.test.ts` around lines 4582 - 4642, Add a
test that exercises the loop-node path so the regression in buildLoopNodeOptions
is covered: create a workflow with a loop node (e.g., node with type or
properties that trigger buildLoopNodeOptions) and call executeDagWorkflow, then
inspect mockSendQueryDag.mock.calls[0][3].nodeConfig.effort to assert it equals
the workflow-level effort when the loop node has no per-node effort; also add a
second loop-node test where the loop node includes an effort override and assert
nodeConfig.effort matches that override. Use the existing test pattern
(executeDagWorkflow, makeWorkflowRun, mockSendQueryDag) and reference
buildLoopNodeOptions/sendQuery via the mockSendQueryDag call to locate the
assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/providers/src/codex/provider.ts`:
- Around line 473-481: The warning log in the JSON parse failure handling
(inside the hasOutputFormat branch that uses accumulatedText and sets
structuredOutput) currently includes a preview of accumulatedText which may leak
user data; remove the outputPreview property from the getLog().warn call and
replace it with a generic, non-sensitive message (e.g.,
'codex.structured_output_not_json' with no preview or any masked content) so
that accumulatedText is never logged; update the catch block around
JSON.parse(accumulatedText) that calls getLog().warn to only emit non-sensitive
context.
- Around line 271-275: The loop over result.events currently uses "break" when
requestOptions?.abortSignal?.aborted is true, which silently completes the
stream; instead throw an explicit abort error so callers observe cancellation.
Replace the break in the for-await-of handling of result.events with throwing a
clear AbortError (e.g., propagate requestOptions.abortSignal.reason if present
or throw a new DOMException('Aborted', 'AbortError') / new Error('Query
aborted')), keeping the existing getLog().info('query_aborted_between_events')
call before throwing to preserve logging; ensure the thrown error propagates out
of the async iterator so callers won't treat the stream as a normal completion.
- Around line 183-185: The Codex instance is being memoized globally causing
reuse of the wrong binary when requests pass a per-request codexBinaryPath;
update the caching/memoization in getCodex so it keys instances by the provided
codexBinaryPath (i.e., maintain a map from codexBinaryPath -> Codex instance)
and ensure sendQuery / the call site still passes the per-request
codexBinaryPath into getCodex; reference getCodex and sendQuery (and
codexConfig.codexBinaryPath) to locate and change the memoization strategy so
each unique binary path gets its own cached Codex process.

---

Nitpick comments:
In `@packages/workflows/src/dag-executor.test.ts`:
- Around line 4582-4642: Add a test that exercises the loop-node path so the
regression in buildLoopNodeOptions is covered: create a workflow with a loop
node (e.g., node with type or properties that trigger buildLoopNodeOptions) and
call executeDagWorkflow, then inspect
mockSendQueryDag.mock.calls[0][3].nodeConfig.effort to assert it equals the
workflow-level effort when the loop node has no per-node effort; also add a
second loop-node test where the loop node includes an effort override and assert
nodeConfig.effort matches that override. Use the existing test pattern
(executeDagWorkflow, makeWorkflowRun, mockSendQueryDag) and reference
buildLoopNodeOptions/sendQuery via the mockSendQueryDag call to locate the
assertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: aef43b9b-92b6-4949-bbc8-af96bc06c4c1

📥 Commits

Reviewing files that changed from the base of the PR and between 407ce39 and 12c4b1a.

📒 Files selected for processing (14)
  • .archon/workflows/e2e-claude-smoke.yaml
  • .archon/workflows/e2e-codex-smoke.yaml
  • CLAUDE.md
  • packages/cli/package.json
  • packages/docs-web/src/content/docs/reference/architecture.md
  • packages/providers/src/claude/provider.test.ts
  • packages/providers/src/claude/provider.ts
  • packages/providers/src/codex/provider.test.ts
  • packages/providers/src/codex/provider.ts
  • packages/providers/src/index.ts
  • packages/workflows/src/dag-executor.test.ts
  • packages/workflows/src/dag-executor.ts
  • packages/workflows/src/deps.ts
  • packages/workflows/src/hooks.test.ts
✅ Files skipped from review due to trivial changes (7)
  • packages/cli/package.json
  • packages/workflows/src/hooks.test.ts
  • .archon/workflows/e2e-claude-smoke.yaml
  • packages/docs-web/src/content/docs/reference/architecture.md
  • .archon/workflows/e2e-codex-smoke.yaml
  • CLAUDE.md
  • packages/providers/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/providers/src/claude/provider.test.ts
  • packages/providers/src/claude/provider.ts
  • packages/workflows/src/dag-executor.ts

- Add packages/providers/ to all three Dockerfile stages (deps,
  production package.json copy, production source copy)
- Replace wildcard export map (./*) with explicit subpath entries
  to fix module resolution in CI (bun workspace linking)
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/workflows/src/dag-executor.ts (1)

2349-2367: Consider reusing resolveNodeProviderAndModel() for loop nodes too.

Loop dispatch still duplicates provider/model compatibility logic. Reusing the shared resolver would reduce drift risk and keep capability handling consistent across AI node types.

Also applies to: 2474-2485

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/workflows/src/dag-executor.ts` around lines 2349 - 2367, The loop
node code duplicates provider/model resolution and compatibility checks; replace
the manual logic around loopProvider and loopModel and the subsequent
isModelCompatible check by calling the shared resolver function
resolveNodeProviderAndModel(node, workflowProvider, workflowModel, config) (or
the existing signature used elsewhere) to obtain the resolved provider and model
for loop nodes, then use its result for compatibility/capability decisions;
update any variable names (loopProvider, loopModel) to use the returned values
and remove the duplicated branches so loop nodes use the same resolution logic
as other AI node types.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/workflows/src/dag-executor.ts`:
- Around line 783-786: The warning currently logs raw provider system payload
via getLog().warn({ nodeId: node.id, systemContent: msg.content },
'dag.provider_warning_forwarded'); which may include user-derived text; change
this to avoid raw content by replacing msg.content with a normalized summary
(e.g., kind/type string) and a safe metric such as content length or a redacted
flag; update the getLog().warn call to include node.id and either { systemKind:
'<kind>', contentLength: msg.content?.length } or { systemRedacted: true,
contentLength: msg.content?.length } (use the same key names where systemContent
was used) so no user text is written to logs while preserving useful debugging
info.

---

Nitpick comments:
In `@packages/workflows/src/dag-executor.ts`:
- Around line 2349-2367: The loop node code duplicates provider/model resolution
and compatibility checks; replace the manual logic around loopProvider and
loopModel and the subsequent isModelCompatible check by calling the shared
resolver function resolveNodeProviderAndModel(node, workflowProvider,
workflowModel, config) (or the existing signature used elsewhere) to obtain the
resolved provider and model for loop nodes, then use its result for
compatibility/capability decisions; update any variable names (loopProvider,
loopModel) to use the returned values and remove the duplicated branches so loop
nodes use the same resolution logic as other AI node types.
🪄 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: 92a06e84-5e4e-4a9d-a8f3-6b77c70a9087

📥 Commits

Reviewing files that changed from the base of the PR and between 12c4b1a and cc863aa.

📒 Files selected for processing (1)
  • packages/workflows/src/dag-executor.ts

Comment on lines 783 to 786
getLog().warn(
{ nodeId: node.id, mcpStatus: msg.content },
'dag.mcp_server_connection_failed'
{ nodeId: node.id, systemContent: msg.content },
'dag.provider_warning_forwarded'
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid logging raw provider system payloads.

Line 784 logs msg.content verbatim. Provider system chunks can contain user-derived text; this increases sensitive-data retention risk in logs. Log a normalized warning kind (and maybe length) instead of full content.

Proposed fix
-          getLog().warn(
-            { nodeId: node.id, systemContent: msg.content },
-            'dag.provider_warning_forwarded'
-          );
+          getLog().warn(
+            {
+              nodeId: node.id,
+              warningType: msg.content.startsWith('MCP server connection failed:')
+                ? 'mcp_connection_failed'
+                : 'provider_warning',
+              contentLength: msg.content.length,
+            },
+            'dag.provider_warning_forwarded'
+          );

As per coding guidelines, "Never log API keys, tokens ... user message content, or PII."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
getLog().warn(
{ nodeId: node.id, mcpStatus: msg.content },
'dag.mcp_server_connection_failed'
{ nodeId: node.id, systemContent: msg.content },
'dag.provider_warning_forwarded'
);
getLog().warn(
{
nodeId: node.id,
warningType: msg.content.startsWith('MCP server connection failed:')
? 'mcp_connection_failed'
: 'provider_warning',
contentLength: msg.content.length,
},
'dag.provider_warning_forwarded'
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/workflows/src/dag-executor.ts` around lines 783 - 786, The warning
currently logs raw provider system payload via getLog().warn({ nodeId: node.id,
systemContent: msg.content }, 'dag.provider_warning_forwarded'); which may
include user-derived text; change this to avoid raw content by replacing
msg.content with a normalized summary (e.g., kind/type string) and a safe metric
such as content length or a redacted flag; update the getLog().warn call to
include node.id and either { systemKind: '<kind>', contentLength:
msg.content?.length } or { systemRedacted: true, contentLength:
msg.content?.length } (use the same key names where systemContent was used) so
no user text is written to logs while preserving useful debugging info.

@Wirasm Wirasm merged commit c1ed765 into dev Apr 13, 2026
3 of 4 checks passed
@Wirasm Wirasm deleted the feature/extract-providers-from-core branch April 13, 2026 06:21
kagura-agent pushed a commit to kagura-agent/Archon that referenced this pull request Apr 13, 2026
…oleam00#1137)

* refactor: extract providers from @archon/core into @archon/providers

Move Claude and Codex provider implementations, factory, and SDK
dependencies into a new @archon/providers package. This establishes a
clean boundary: providers own SDK translation, core owns business logic.

Key changes:
- New @archon/providers package with zero-dep contract layer (types.ts)
- @archon/workflows imports from @archon/providers/types — no mirror types
- dag-executor delegates option building to providers via nodeConfig
- IAgentProvider gains getCapabilities() for provider-agnostic warnings
- @archon/core no longer depends on SDK packages directly
- UnknownProviderError standardizes error shape across all surfaces

Zero user-facing changes — same providers, same config, same behavior.

* refactor: remove config type duplication and backward-compat re-exports

Address review findings:
- Move ClaudeProviderDefaults and CodexProviderDefaults to the
  @archon/providers/types contract layer as the single source of truth.
  @archon/core/config/config-types.ts now imports from there.
- Remove provider re-exports from @archon/core (index.ts and types/).
  Consumers should import from @archon/providers directly.
- Update @archon/server to depend on @archon/providers for MessageChunk.

* refactor: move structured output validation into providers

Each provider now normalizes its own structured output semantics:
- Claude already yields structuredOutput from the SDK's native field
- Codex now parses inline agent_message text as JSON when outputFormat
  is set, populating structuredOutput on the result chunk

This eliminates the last provider === 'codex' branch from dag-executor,
making it fully provider-agnostic. The dag-executor checks structuredOutput
uniformly regardless of provider.

Also removes the ClaudeCodexProviderDefaults deprecated alias — all
consumers now use ClaudeProviderDefaults directly.

* fix: address PR review — restore warnings, fix loop options, cleanup

Critical fixes:
- Restore MCP missing env vars user-facing warning (was silently dropped)
- Restore Haiku + MCP tool search warning
- Fix buildLoopNodeOptions to pass workflow-level nodeConfig (effort,
  thinking, betas, sandbox were silently lost for loop nodes)
- Add TODO(coleam00#1135) comments documenting env-leak gate gap

Cleanup:
- Remove backward-compat type aliases from deps.ts (keep WorkflowTokenUsage)
- Remove 26 unnecessary eslint-disable comments from test files
- Trim internal helpers from providers barrel (withFirstMessageTimeout,
  getProcessUid, loadMcpConfig, buildSDKHooksFromYAML)
- Add @archon/providers dep to CLI package.json
- Fix 8 stale documentation paths pointing to deleted core/src/providers/
- Add E2E smoke test workflows for both Claude and Codex providers

* fix: forward provider system warnings to users in dag-executor

The dag-executor only forwarded system chunks starting with
"MCP server connection failed:" — all other provider warnings
(missing env vars, Haiku+MCP, structured output issues) were
logged but never reached the user.

Now forwards all system chunks starting with ⚠️ (the prefix
providers use for user-actionable warnings).

* fix: add providers package to Dockerfile and fix CI module resolution

- Add packages/providers/ to all three Dockerfile stages (deps,
  production package.json copy, production source copy)
- Replace wildcard export map (./*) with explicit subpath entries
  to fix module resolution in CI (bun workspace linking)

* chore: update bun.lock for providers package exports
tboome33 pushed a commit to tboome33/Archon that referenced this pull request Apr 14, 2026
…oleam00#1137)

* refactor: extract providers from @archon/core into @archon/providers

Move Claude and Codex provider implementations, factory, and SDK
dependencies into a new @archon/providers package. This establishes a
clean boundary: providers own SDK translation, core owns business logic.

Key changes:
- New @archon/providers package with zero-dep contract layer (types.ts)
- @archon/workflows imports from @archon/providers/types — no mirror types
- dag-executor delegates option building to providers via nodeConfig
- IAgentProvider gains getCapabilities() for provider-agnostic warnings
- @archon/core no longer depends on SDK packages directly
- UnknownProviderError standardizes error shape across all surfaces

Zero user-facing changes — same providers, same config, same behavior.

* refactor: remove config type duplication and backward-compat re-exports

Address review findings:
- Move ClaudeProviderDefaults and CodexProviderDefaults to the
  @archon/providers/types contract layer as the single source of truth.
  @archon/core/config/config-types.ts now imports from there.
- Remove provider re-exports from @archon/core (index.ts and types/).
  Consumers should import from @archon/providers directly.
- Update @archon/server to depend on @archon/providers for MessageChunk.

* refactor: move structured output validation into providers

Each provider now normalizes its own structured output semantics:
- Claude already yields structuredOutput from the SDK's native field
- Codex now parses inline agent_message text as JSON when outputFormat
  is set, populating structuredOutput on the result chunk

This eliminates the last provider === 'codex' branch from dag-executor,
making it fully provider-agnostic. The dag-executor checks structuredOutput
uniformly regardless of provider.

Also removes the ClaudeCodexProviderDefaults deprecated alias — all
consumers now use ClaudeProviderDefaults directly.

* fix: address PR review — restore warnings, fix loop options, cleanup

Critical fixes:
- Restore MCP missing env vars user-facing warning (was silently dropped)
- Restore Haiku + MCP tool search warning
- Fix buildLoopNodeOptions to pass workflow-level nodeConfig (effort,
  thinking, betas, sandbox were silently lost for loop nodes)
- Add TODO(coleam00#1135) comments documenting env-leak gate gap

Cleanup:
- Remove backward-compat type aliases from deps.ts (keep WorkflowTokenUsage)
- Remove 26 unnecessary eslint-disable comments from test files
- Trim internal helpers from providers barrel (withFirstMessageTimeout,
  getProcessUid, loadMcpConfig, buildSDKHooksFromYAML)
- Add @archon/providers dep to CLI package.json
- Fix 8 stale documentation paths pointing to deleted core/src/providers/
- Add E2E smoke test workflows for both Claude and Codex providers

* fix: forward provider system warnings to users in dag-executor

The dag-executor only forwarded system chunks starting with
"MCP server connection failed:" — all other provider warnings
(missing env vars, Haiku+MCP, structured output issues) were
logged but never reached the user.

Now forwards all system chunks starting with ⚠️ (the prefix
providers use for user-actionable warnings).

* fix: add providers package to Dockerfile and fix CI module resolution

- Add packages/providers/ to all three Dockerfile stages (deps,
  production package.json copy, production source copy)
- Replace wildcard export map (./*) with explicit subpath entries
  to fix module resolution in CI (bun workspace linking)

* chore: update bun.lock for providers package exports
kagura-agent pushed a commit to kagura-agent/Archon that referenced this pull request Apr 17, 2026
…oleam00#1137)

* refactor: extract providers from @archon/core into @archon/providers

Move Claude and Codex provider implementations, factory, and SDK
dependencies into a new @archon/providers package. This establishes a
clean boundary: providers own SDK translation, core owns business logic.

Key changes:
- New @archon/providers package with zero-dep contract layer (types.ts)
- @archon/workflows imports from @archon/providers/types — no mirror types
- dag-executor delegates option building to providers via nodeConfig
- IAgentProvider gains getCapabilities() for provider-agnostic warnings
- @archon/core no longer depends on SDK packages directly
- UnknownProviderError standardizes error shape across all surfaces

Zero user-facing changes — same providers, same config, same behavior.

* refactor: remove config type duplication and backward-compat re-exports

Address review findings:
- Move ClaudeProviderDefaults and CodexProviderDefaults to the
  @archon/providers/types contract layer as the single source of truth.
  @archon/core/config/config-types.ts now imports from there.
- Remove provider re-exports from @archon/core (index.ts and types/).
  Consumers should import from @archon/providers directly.
- Update @archon/server to depend on @archon/providers for MessageChunk.

* refactor: move structured output validation into providers

Each provider now normalizes its own structured output semantics:
- Claude already yields structuredOutput from the SDK's native field
- Codex now parses inline agent_message text as JSON when outputFormat
  is set, populating structuredOutput on the result chunk

This eliminates the last provider === 'codex' branch from dag-executor,
making it fully provider-agnostic. The dag-executor checks structuredOutput
uniformly regardless of provider.

Also removes the ClaudeCodexProviderDefaults deprecated alias — all
consumers now use ClaudeProviderDefaults directly.

* fix: address PR review — restore warnings, fix loop options, cleanup

Critical fixes:
- Restore MCP missing env vars user-facing warning (was silently dropped)
- Restore Haiku + MCP tool search warning
- Fix buildLoopNodeOptions to pass workflow-level nodeConfig (effort,
  thinking, betas, sandbox were silently lost for loop nodes)
- Add TODO(coleam00#1135) comments documenting env-leak gate gap

Cleanup:
- Remove backward-compat type aliases from deps.ts (keep WorkflowTokenUsage)
- Remove 26 unnecessary eslint-disable comments from test files
- Trim internal helpers from providers barrel (withFirstMessageTimeout,
  getProcessUid, loadMcpConfig, buildSDKHooksFromYAML)
- Add @archon/providers dep to CLI package.json
- Fix 8 stale documentation paths pointing to deleted core/src/providers/
- Add E2E smoke test workflows for both Claude and Codex providers

* fix: forward provider system warnings to users in dag-executor

The dag-executor only forwarded system chunks starting with
"MCP server connection failed:" — all other provider warnings
(missing env vars, Haiku+MCP, structured output issues) were
logged but never reached the user.

Now forwards all system chunks starting with ⚠️ (the prefix
providers use for user-actionable warnings).

* fix: add providers package to Dockerfile and fix CI module resolution

- Add packages/providers/ to all three Dockerfile stages (deps,
  production package.json copy, production source copy)
- Replace wildcard export map (./*) with explicit subpath entries
  to fix module resolution in CI (bun workspace linking)

* chore: update bun.lock for providers package exports
kagura-agent pushed a commit to kagura-agent/Archon that referenced this pull request Apr 18, 2026
…oleam00#1137)

* refactor: extract providers from @archon/core into @archon/providers

Move Claude and Codex provider implementations, factory, and SDK
dependencies into a new @archon/providers package. This establishes a
clean boundary: providers own SDK translation, core owns business logic.

Key changes:
- New @archon/providers package with zero-dep contract layer (types.ts)
- @archon/workflows imports from @archon/providers/types — no mirror types
- dag-executor delegates option building to providers via nodeConfig
- IAgentProvider gains getCapabilities() for provider-agnostic warnings
- @archon/core no longer depends on SDK packages directly
- UnknownProviderError standardizes error shape across all surfaces

Zero user-facing changes — same providers, same config, same behavior.

* refactor: remove config type duplication and backward-compat re-exports

Address review findings:
- Move ClaudeProviderDefaults and CodexProviderDefaults to the
  @archon/providers/types contract layer as the single source of truth.
  @archon/core/config/config-types.ts now imports from there.
- Remove provider re-exports from @archon/core (index.ts and types/).
  Consumers should import from @archon/providers directly.
- Update @archon/server to depend on @archon/providers for MessageChunk.

* refactor: move structured output validation into providers

Each provider now normalizes its own structured output semantics:
- Claude already yields structuredOutput from the SDK's native field
- Codex now parses inline agent_message text as JSON when outputFormat
  is set, populating structuredOutput on the result chunk

This eliminates the last provider === 'codex' branch from dag-executor,
making it fully provider-agnostic. The dag-executor checks structuredOutput
uniformly regardless of provider.

Also removes the ClaudeCodexProviderDefaults deprecated alias — all
consumers now use ClaudeProviderDefaults directly.

* fix: address PR review — restore warnings, fix loop options, cleanup

Critical fixes:
- Restore MCP missing env vars user-facing warning (was silently dropped)
- Restore Haiku + MCP tool search warning
- Fix buildLoopNodeOptions to pass workflow-level nodeConfig (effort,
  thinking, betas, sandbox were silently lost for loop nodes)
- Add TODO(coleam00#1135) comments documenting env-leak gate gap

Cleanup:
- Remove backward-compat type aliases from deps.ts (keep WorkflowTokenUsage)
- Remove 26 unnecessary eslint-disable comments from test files
- Trim internal helpers from providers barrel (withFirstMessageTimeout,
  getProcessUid, loadMcpConfig, buildSDKHooksFromYAML)
- Add @archon/providers dep to CLI package.json
- Fix 8 stale documentation paths pointing to deleted core/src/providers/
- Add E2E smoke test workflows for both Claude and Codex providers

* fix: forward provider system warnings to users in dag-executor

The dag-executor only forwarded system chunks starting with
"MCP server connection failed:" — all other provider warnings
(missing env vars, Haiku+MCP, structured output issues) were
logged but never reached the user.

Now forwards all system chunks starting with ⚠️ (the prefix
providers use for user-actionable warnings).

* fix: add providers package to Dockerfile and fix CI module resolution

- Add packages/providers/ to all three Dockerfile stages (deps,
  production package.json copy, production source copy)
- Replace wildcard export map (./*) with explicit subpath entries
  to fix module resolution in CI (bun workspace linking)

* chore: update bun.lock for providers package exports
joaobmonteiro pushed a commit to joaobmonteiro/Archon that referenced this pull request Apr 26, 2026
…oleam00#1137)

* refactor: extract providers from @archon/core into @archon/providers

Move Claude and Codex provider implementations, factory, and SDK
dependencies into a new @archon/providers package. This establishes a
clean boundary: providers own SDK translation, core owns business logic.

Key changes:
- New @archon/providers package with zero-dep contract layer (types.ts)
- @archon/workflows imports from @archon/providers/types — no mirror types
- dag-executor delegates option building to providers via nodeConfig
- IAgentProvider gains getCapabilities() for provider-agnostic warnings
- @archon/core no longer depends on SDK packages directly
- UnknownProviderError standardizes error shape across all surfaces

Zero user-facing changes — same providers, same config, same behavior.

* refactor: remove config type duplication and backward-compat re-exports

Address review findings:
- Move ClaudeProviderDefaults and CodexProviderDefaults to the
  @archon/providers/types contract layer as the single source of truth.
  @archon/core/config/config-types.ts now imports from there.
- Remove provider re-exports from @archon/core (index.ts and types/).
  Consumers should import from @archon/providers directly.
- Update @archon/server to depend on @archon/providers for MessageChunk.

* refactor: move structured output validation into providers

Each provider now normalizes its own structured output semantics:
- Claude already yields structuredOutput from the SDK's native field
- Codex now parses inline agent_message text as JSON when outputFormat
  is set, populating structuredOutput on the result chunk

This eliminates the last provider === 'codex' branch from dag-executor,
making it fully provider-agnostic. The dag-executor checks structuredOutput
uniformly regardless of provider.

Also removes the ClaudeCodexProviderDefaults deprecated alias — all
consumers now use ClaudeProviderDefaults directly.

* fix: address PR review — restore warnings, fix loop options, cleanup

Critical fixes:
- Restore MCP missing env vars user-facing warning (was silently dropped)
- Restore Haiku + MCP tool search warning
- Fix buildLoopNodeOptions to pass workflow-level nodeConfig (effort,
  thinking, betas, sandbox were silently lost for loop nodes)
- Add TODO(coleam00#1135) comments documenting env-leak gate gap

Cleanup:
- Remove backward-compat type aliases from deps.ts (keep WorkflowTokenUsage)
- Remove 26 unnecessary eslint-disable comments from test files
- Trim internal helpers from providers barrel (withFirstMessageTimeout,
  getProcessUid, loadMcpConfig, buildSDKHooksFromYAML)
- Add @archon/providers dep to CLI package.json
- Fix 8 stale documentation paths pointing to deleted core/src/providers/
- Add E2E smoke test workflows for both Claude and Codex providers

* fix: forward provider system warnings to users in dag-executor

The dag-executor only forwarded system chunks starting with
"MCP server connection failed:" — all other provider warnings
(missing env vars, Haiku+MCP, structured output issues) were
logged but never reached the user.

Now forwards all system chunks starting with ⚠️ (the prefix
providers use for user-actionable warnings).

* fix: add providers package to Dockerfile and fix CI module resolution

- Add packages/providers/ to all three Dockerfile stages (deps,
  production package.json copy, production source copy)
- Replace wildcard export map (./*) with explicit subpath entries
  to fix module resolution in CI (bun workspace linking)

* chore: update bun.lock for providers package exports
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant