feat(claude_code): per-agent reasoning effort via claudeConfig#283
feat(claude_code): per-agent reasoning effort via claudeConfig#283vprudnikoff wants to merge 1 commit into
Conversation
Add a `claudeConfig` field to the agent profile, the Claude Code analog of `codexConfig` for the codex provider. The claude_code provider maps it to Claude Code CLI flags at launch: - effort -> --effort <level> - fallback_model -> --fallback-model <model> This lets a profile set per-agent reasoning effort (e.g. an orchestrator at xhigh) without relying on the machine-global `effortLevel` in ~/.claude/settings.json, which silently differs between machines. The top-level `model` field still maps to `--model`.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #283 +/- ##
=======================================
Coverage ? 92.15%
=======================================
Files ? 70
Lines ? 7115
Branches ? 0
=======================================
Hits ? 6557
Misses ? 558
Partials ? 0
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Adds per-agent Claude Code launch overrides by introducing a claudeConfig map on AgentProfile, and mapping selected keys to Claude Code CLI flags when building the claude command. This brings profile-level control over reasoning effort / fallback model without relying on machine-global ~/.claude/settings.json settings.
Changes:
- Add
claudeConfigtoAgentProfileand document it indocs/agent-profile.md. - Update
ClaudeCodeProvider._build_claude_command()to emit--effortand--fallback-modelwhenprofile.claudeConfigis set. - Add unit tests ensuring the flags are emitted/omitted as expected.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/cli_agent_orchestrator/providers/claude_code.py |
Map profile.claudeConfig entries to claude CLI flags during command construction. |
src/cli_agent_orchestrator/models/agent_profile.py |
Extend the agent profile schema with the new optional claudeConfig field. |
test/providers/test_claude_code_unit.py |
Add unit tests covering claudeConfig → CLI flag behavior. |
docs/agent-profile.md |
Document the new claudeConfig profile field and its flag mappings. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Apply Claude Code-only per-agent knobs from claudeConfig: | ||
| # effort -> --effort <level> | ||
| # fallback_model -> --fallback-model <model> | ||
| # Claude analog of codexConfig: per-agent reasoning effort without | ||
| # depending on the machine-global effortLevel in | ||
| # ~/.claude/settings.json. |
There was a problem hiding this comment.
For context: codexConfig is introduced by the sibling PR #278 (feat/codex-agent-config). This launch-override comment intentionally mirrors that field — claudeConfig is the claude_code analog — so it resolves once both PRs merge. If reviewers prefer this PR to stand alone regardless of merge order, I'm happy to drop the codexConfig mention and describe the behavior on its own.
| # Claude Code-only. Per-agent Claude Code knobs mapped to CLI flags at | ||
| # launch: {"effort": "<low|medium|high|xhigh>"} -> `--effort <level>` and | ||
| # {"fallback_model": "<model>"} -> `--fallback-model <model>`. Lets a | ||
| # profile set per-agent reasoning effort without relying on the | ||
| # machine-global `effortLevel` in ~/.claude/settings.json. This is the | ||
| # Claude analog of codexConfig for the codex provider; the top-level | ||
| # `model` field still maps to `--model`. |
There was a problem hiding this comment.
For context: codexConfig is added by the sibling PR #278 (feat/codex-agent-config). claudeConfig here is the deliberate claude_code analog of that field, so this comment cross-references it on purpose; it becomes accurate once both PRs merge. If you'd rather keep this PR independent of merge order, I can drop the codexConfig reference and just describe claudeConfig on its own.
| - `model` (string): AI model to use | ||
| - `permissionMode` (string, `claude_code` only): One of `"default"`, `"acceptEdits"`, `"plan"`, `"auto"`, `"bypassPermissions"`. When set, the `claude_code` provider passes `--permission-mode <value>` instead of `--dangerously-skip-permissions`. `cao launch --yolo` overrides this and forces bypass. See [Claude Code permission modes](https://code.claude.com/docs/en/permission-modes). | ||
| - `native_agent` (string, `claude_code` only): Name of a native Claude Code agent (`~/.claude/agents/`). When set, the provider passes `--agent <name>` directly and skips system prompt / MCP config decomposition (thin-wrapper mode). See [Claude Code native agent routing](claude-code.md#native-agent-routing). | ||
| - `claudeConfig` (object, `claude_code` only): Per-agent Claude Code knobs mapped to CLI flags at launch. `{"effort": "<low|medium|high|xhigh>"}` adds `--effort <level>` and `{"fallback_model": "<model>"}` adds `--fallback-model <model>`. Lets a profile set per-agent reasoning effort without relying on the machine-global `effortLevel` in `~/.claude/settings.json`. The Claude analog of `codexConfig`; the top-level `model` field still maps to `--model`. |
There was a problem hiding this comment.
Context: codexConfig is introduced by the sibling PR #278 (feat/codex-agent-config). These two are a pair — per-agent provider config: codexConfig for the codex provider, claudeConfig for claude_code — so the cross-reference is intentional and resolves once both land. If you'd prefer each PR to read as fully self-contained regardless of merge order, I'm happy to drop the codexConfig mention here.
|
@vprudnikoff great PR. I think this needs one behavior fix before approval. The new Could we make On the existing One smaller compatibility note: local Claude Code 2.1.181 help says Results:
|
|
@vprudnikoff do you get a chance to work on this PR to address the feedbacks and the conflicts ? many thanks. |
gutosantos82
left a comment
There was a problem hiding this comment.
PR Review: #283 — feat(claude_code): per-agent reasoning effort via claudeConfig
Summary
Adds a claudeConfig object to the agent profile so a claude_code agent can set
per-agent reasoning effort (--effort) and fallback_model (--fallback-model) at
launch, instead of depending on the machine-global effortLevel in
~/.claude/settings.json. The implementation is small, safe, and follows the existing
flag-emission pattern; the flags are real Claude Code launch options and values are
shell-escaped via shlex.join. No blocking issues. The recurring, cross-reviewer theme is
documentation/model-consistency drift: the PR repeatedly calls this "the Claude analog
of codexConfig", but no codexConfig field exists in the codebase — the real codex knob
is codexProfile, which is validated and shaped differently. Recommend fixing the wording,
adding light validation, and a CHANGELOG entry before merge.
Blocking (must fix before merge)
None. The introduced code is functionally correct and secure.
Important (should fix)
- [correctness/consistency] src/cli_agent_orchestrator/providers/claude_code.py:131-136, models/agent_profile.py:62 — No validation of
effort/fallback_model; any value is forwarded verbatim viastr(...). A typo or invalid level ({"effort": "High"},{"effort": "mx"},effort: 3→--effort 3) produces a flag Claude Code rejects, so the spawnedclaudeprocess exits immediately and CAO surfaces the opaque "Claude Code initialization timed out after 30 seconds" (claude_code.py:337) rather than a clear config error. This also diverges from how every other per-provider knob is modeled (codexProfile/hermesProfileuseField(min_length=1)). This lands inproviders/(high-risk area), so weighting as important. Fix: validateeffortagainst the accepted set (aLiteralor model validator) and reject non-str types; fail fast with a clearProviderError. - [conventions] CHANGELOG.md — User-facing feature ships with no CHANGELOG entry. The
[Unreleased] > Addedsection exists (CHANGELOG.md:7-11) and the siblinghermesProfilefeature set the precedent one line above. Fix: add an### Addedbullet under[Unreleased]. - [consistency] docs/agent-profile.md:37 — Effort enum is both unenforced and incomplete: docs list
low|medium|high|xhigh, but Claude Code CLI also acceptsmax(omitted) and the code enforces no enum at all. Fix: includemaxand either validate the set or phrase as "e.g.". (Note: the correctness reviewer treatedxhigh/maxas valid; confirm the exact accepted set against the installed CLI.)
Nits (optional)
- [conventions/consistency] src/cli_agent_orchestrator/providers/claude_code.py:129 —
getattr(profile, "claudeConfig", None)is unnecessary defensive access for a declared pydantic field, and inconsistent with siblingsprofile.codexProfile(codex.py:187) /profile.hermesProfile(hermes.py:148) which use direct attribute access. (It does match the localnative_agentgetattr, the outlier.) Useprofile.claudeConfigdirectly. - [correctness] src/cli_agent_orchestrator/providers/claude_code.py:131-136 —
str()coercion turns non-string YAML (true,3, lists) into invalid flags rather than rejecting them; folds into the validation fix above. - [consistency] docs/claude-code.md (not updated) — Doc-parity gap:
codexProfilegets a dedicated section indocs/codex-cli.md;claudeConfiggets only a one-line bullet. Consider a short section indocs/claude-code.md.
Prior feedback (already raised — not restating)
- ↩︎ Phantom "analog of
codexConfig" wording / doc↔code drift —codexConfigdoesn't exist onmain, so the references should be dropped or corrected — already raised by @haofeif; we concur.
Tests
Adequate for the happy path and consistent with file conventions (new
TestClaudeCodeProviderClaudeConfig, mirrors TestClaudeCodeProviderModelFlag). The
tests do reach the real code path — the tests reviewer verified the MagicMock
truthy-attribute trap is narrowly avoided by the isinstance(native, str) guard at
claude_code.py:107-108 (worth a comment, since relaxing that guard would silently divert
all three tests into the --agent branch).
Coverage gaps (all on the changed lines):
- [important] The two defensive guards in the diff have zero coverage: empty-string value
{"effort": ""}(theif effort:guard) and non-dictclaudeConfig(theisinstance(..., dict)guard). The only negative test usesNone. - [nit] Both keys set together (
effort+fallback_model) untested; no co-test with a set top-level--model; assertions use bare substring checks rather thanshlex.splittoken checks (robust here only because values are enum-like); the "absent" test asserts only--effortabsent, not--fallback-model.
Recommend ~3 small tests: empty-string value, non-dict config, and both-keys-together — these are exactly the guard branches added in this PR.
Verdict
Approve with nits. No blocking or security issues; the introduced code is correct and
safe. Before merge, the author should (1) fix the false "analog of codexConfig" wording
across the three code/doc sites and the PR body, (2) add light validation for effort
(and reject non-str types) so misconfigs fail fast instead of as a 30s launch timeout,
(3) add a CHANGELOG entry, and (4) correct/complete the effort enum in the docs. The
untested guard branches are worth 3 quick tests.
|
@vprudnikoff can you please help to resolve the conflict ? |
What
Adds a
claudeConfigfield to the agent profile, the Claude Code analog ofcodexConfig(PR #278) for the codex provider. Theclaude_codeprovider maps it to Claude Code CLI flags at launch:claudeConfigkeyeffort--effort <level>fallback_model--fallback-model <model>The top-level
modelfield still maps to--model(unchanged), mirroring how codex keepsmodelseparate fromcodexConfig.Why
Today the only lever for a
claude_codeagent's reasoning effort is the machine-globaleffortLevelin~/.claude/settings.json(or theCLAUDE_CODE_EFFORT_LEVELenv var, which the provider already preserves). That silently differs between machines: an orchestrator profile meant to run atxhighfalls back to the Claude Code default (high) on any host where the global setting is absent.codexConfigalready solves the equivalent problem per-agent for codex; this brings parity to claude_code.Example
Tests
test/providers/test_claude_code_unit.py::TestClaudeCodeProviderClaudeConfig: effort flag emitted when set, fallback-model emitted when set, no flag whenclaudeConfigis absent. Docs updated indocs/agent-profile.md.