Skip to content

feat(factory): wire per-role reasoning_effort from LlmConfig through spawn pipeline#4

Closed
pippenz wants to merge 27 commits into
codingagentsystem:mainfrom
pippenz:factory/fast-hawk-76
Closed

feat(factory): wire per-role reasoning_effort from LlmConfig through spawn pipeline#4
pippenz wants to merge 27 commits into
codingagentsystem:mainfrom
pippenz:factory/fast-hawk-76

Conversation

@pippenz
Copy link
Copy Markdown

@pippenz pippenz commented May 2, 2026

Summary

  • Threads reasoning_effort config per-role (supervisor/worker) through the full factory spawn pipeline, mirroring the existing model pattern
  • Both spawn paths covered: Mux::factory() (static init) and FactoryCore::spawn_supervisor/spawn_worker() (dynamic)
  • Role-based defaults preserved when no config is set: high for supervisor, medium for worker
  • Autofix applied: FactoryCore::new now also calls mux.set_worker_model() for symmetry with set_worker_effort()

Files changed

  • crates/cas-pty/src/pty.rs — added effort: Option<&str> param; replaced hardcoded high/medium with unwrap_or default
  • crates/cas-mux/src/pane/mod.rs — forwarded effort through Pane::supervisor() and Pane::worker()
  • crates/cas-mux/src/mux.rs — added supervisor_effort/worker_effort to MuxConfig, stored worker_effort in Mux for dynamic spawn
  • crates/cas-factory/src/config.rs — added supervisor_effort/worker_effort to FactoryConfig
  • crates/cas-factory/src/core.rs — wired worker_effort and worker_model through FactoryCore::new()
  • cas-cli/src/cli/factory/mod.rs, daemon.rs — read from LlmConfig::reasoning_effort_for_role()
  • cas-cli/src/ui/factory/app/init.rs, daemon/fork_first.rs — forwarded through MuxConfig

Test plan

  • 3 new unit tests in pty.rs: custom effort, supervisor default, worker default
  • All existing tests updated for new signatures
  • cargo test --package cas-pty passes
  • cargo test --package cas-mux passes
  • cargo test --package cas-factory passes
  • Verifier approved (confidence 0.95)
  • Code review: no P0 findings; 1 P2 safe_auto autofix applied; 3 P1 + 1 P2 residual findings tracked as follow-up tasks

Closes: cas-63d9

🤖 Generated with Claude Code

pippenz and others added 27 commits March 17, 2026 09:08
- Add custom option_i32/option_u8 serde deserializers so MCP count
  fields accept numbers sent as floats (e.g. 3.0) from Claude Code
- Fix notify_rx ref mut in factory lifecycle to allow mutable drain/recv
- Refactor DaemonNotifier socket to lazy tokio conversion (std socket
  bound before Tokio runtime, converted on first async use)
- Use gcc linker instead of clang for local build
….json

Workers were only getting Bash(cas :*) permissions in their generated
settings.json, leaving mcp__cas__* tools unallowed. This required
unreliable fallback to the global ~/.claude/settings.json.

Add get_cas_mcp_permissions() alongside get_cas_bash_permissions() and
merge both into the permissions.allow array in get_cas_hooks_config().

The 10 MCP tools added: task, coordination, memory, search, rule, skill,
spec, verification, system, pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When workers use isolated git worktrees, .mcp.json must be committed to git
or it won't appear in their checkout — leaving them with no MCP tools and
silently idle.

Changes:
- preflight_factory_launch(): add git ls-files check for .mcp.json.
  Hard failure when workers > 0; advisory notice when supervisor-only.
  Quick-start steps show 'git add .mcp.json && git commit' when only
  .mcp.json is missing (not duplicated if .claude/ is also missing).
- queue_codex_worker_intro_prompt(): inject MCP fallback instructions for
  Claude workers at startup. Tells workers to check .mcp.json, run
  'cas init -y' to regenerate it, and notify supervisor via
  'cas factory message' if MCP tools are unavailable — preventing
  silent idle loops.

Closes cas-8397 (based on spike cas-4567).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace hand-written option_u8/option_i32 deserializers with a single
option_numeric_deser! macro generating 6 variants (u8, i32, i64, u32,
usize, u64). Apply to all ~30 Option<numeric> fields across MCP request
types so Claude Code's string-encoded numbers ("3") are accepted
alongside native JSON numbers (3).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- handle_mouse_up() returns selected text instead of calling arboard
  directly (daemon is headless, can't access system clipboard)
- Daemon relays selected text to client terminal via OSC 52 escape
  sequence for native clipboard write
- Auto-enter inject mode after image drop so user can immediately
  type context for pasted images

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…reen

Adds a "minions" theme variant selectable via `[theme] variant = "minions"`
in config.toml. When active: workers get minion names (kevin, stuart, bob),
supervisor is named gru/dru/nefario, the color palette shifts to yellow
primary with denim blue accents, and the boot screen shows a minion ASCII
art logo with "BANANA!" ready message and "Bee-do Bee-do" launch animation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… test compilation

- Replace generic box ASCII art with recognizable minion character (pill
  body, goggles with eyes, overalls, "BANANA" text)
- Wire MinionsIcons (banana/eye/sleep emoji) into agent_status_icon() and
  agent_status_icon_simple() via is_minions() theme flag
- Add missing minions_theme field to test_config() in core.rs and
  factory_integration.rs
- Fix pre-existing mut warnings in notify.rs tests (lines 132, 155)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Redesign MINION_LOGO with better pill-shaped body silhouette,
  symmetric goggle strap, arm stubs (─┤/├─), and cleaner feet
- Replace 8 non-canonical worker names (larry, tom, chris, john, paul,
  mike, ken, donny) with actual Minion film characters (jorge, otto,
  steve, herb, pete, donnie, abel, walter)
- Add otto (main character from Minions: The Rise of Gru)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The test was failing because try_recv on a Tokio UnixDatagram requires
the socket to be registered with the reactor first. Added an initial
recv().await call before the drain test sequence to ensure the Tokio
socket is properly registered.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- OSC 52: base64 encoding, empty string, unicode, multiline round-trip
- ThemeVariant: default value, Display, FromStr, serde round-trip
- ActiveTheme: minions config wiring, is_minions() check
- ColorPalette::minions(): yellow primary, denim blue info, differs
  from dark, preserves base bg
- MinionsIcons: non-empty constants, differ from default circles

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace all `mcp__cs__` prefixes with `mcp__cas__` in the hardcoded
Codex worker and supervisor instructions in pty.rs. Extract inline
instruction strings to module-level constants for maintainability.

Add tests verifying Codex worker instructions use the correct
mcp__cas__ prefix and supervisor instructions are present.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…essage

Remove the response hint ("To respond, use: coordination...") that was
appended to every agent message. This instruction is already provided at
session start via the cas-worker skill and is also enforced by the
PreToolUse hook that blocks SendMessage in factory mode. Removing the
per-message hint saves tokens on every inter-agent exchange.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Revert the prefix change per supervisor guidance: mcp__cs__ is the
correct Codex tool prefix (configured in harness.rs). Keep the
extraction of inline instruction strings to module-level constants
(CODEX_SUPERVISOR_INSTRUCTIONS, CODEX_WORKER_INSTRUCTIONS,
CODEX_WORKER_STARTUP_PREFIX) for maintainability.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The function reads CAS_AGENT_ROLE and CAS_FACTORY_WORKER_NAMES env vars
and parses CSV on every call across 6+ call sites. Since env vars don't
change during a session, cache the result per-thread using thread_local
OnceCell. Thread-local (vs process-global OnceLock) is used so that
tests which set different env vars per test thread continue to work.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… queue messages

Remove the 500-char truncation when re-queuing undelivered messages to
the supervisor. The full original message is now preserved in the
system-notice, so the supervisor can properly re-send or act on the
complete content instead of a truncated fragment.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add priority support to the prompt queue so important messages
(blockers, crashes) can jump ahead of routine status updates.

Changes:
- Add priority column to prompt_queue table (migration, DEFAULT 2)
- Add enqueue_full() method accepting optional NotificationPriority
- Change ORDER BY from created_at ASC to priority ASC, id ASC
- Wire priority param through coordination message action
- Add QueuedPrompt.priority field using existing NotificationPriority
- Add 3 tests: priority ordering, default priority, peek_for_targets

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add an ack-back mechanism so senders can verify message delivery:

- Schema: add `acked_at` column to prompt_queue table (safe migration)
- Store: add `ack()`, `unacked()`, `message_status()` to PromptQueueStore trait
- MessageStatus enum: Pending → Delivered → Confirmed lifecycle
- Coordination: add `message_ack` and `message_status` actions
- Tests: 5 new tests covering ack, idempotency, nonexistent, timeout, status

Messages now have a 3-state lifecycle:
1. Pending - queued but not yet injected
2. Delivered - injected (processed_at set) but unconfirmed
3. Confirmed - target agent acked receipt (acked_at set)

Agents can ack via: coordination action=message_ack notification_id=<id>
Senders can check via: coordination action=message_status notification_id=<id>
Unacked messages can be queried via store.unacked(timeout_secs, limit).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ool permissions

- CLAUDE.md: replace abbreviated docs with comprehensive architecture guide
  covering workspace layout, key patterns, testing, and migration details
- settings.json: add CAS MCP tool permissions to allow list

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ugh PtyConfig::claude

test-first gate: new tests verify that:
- custom effort value (e.g. "low") is used when provided
- supervisor defaults to "high" when effort=None
- worker defaults to "medium" when effort=None

These tests require the new `effort: Option<&str>` parameter on
PtyConfig::claude which does not exist yet — compilation failure
is the expected failing state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… factory spawn

Reads `llm.supervisor.reasoning_effort` / `llm.worker.reasoning_effort`
from `.cas/config.yaml` and threads the value all the way to the `--effort`
flag passed to Claude CLI at spawn time.

Changes:
- cas-pty: add `effort: Option<&str>` to PtyConfig::claude and PtyConfig::codex;
  replace hardcoded high/medium with role-based fallback that respects the config
  value when set (backward-compatible: None → "high" for supervisor, "medium" for worker)
- cas-mux: add `effort` param to Pane::worker() and Pane::supervisor();
  add supervisor_effort/worker_effort to MuxConfig and Mux, wire through
  factory() and add_worker()
- cas-factory: add supervisor_effort/worker_effort fields to FactoryConfig;
  wire into FactoryCore::new() (set_worker_effort) and spawn_supervisor()
- cas-cli: populate the new FactoryConfig fields via
  llm.reasoning_effort_for_role() in both factory entry points (mod.rs + daemon.rs)
  and propagate through the two MuxConfig construction sites (init.rs + fork_first.rs)

Also fixes pre-existing incorrect test assertion in test_pty_config_claude_with_teams
which claimed workers should NOT have --session-id (they always do).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FactoryCore::new was calling set_worker_cli and set_worker_effort but
missing set_worker_model, leaving worker model config silently ignored
when using the dynamic-spawn path.

Detected and applied by cas-code-review autofix loop (round 1).
Refs: cas-63d9

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 2, 2026

Thank you for your interest in CAS! We appreciate you taking the time to contribute.

CAS is source-available but does not accept pull requests at this time. Instead, please:

See CONTRIBUTING.md for more details on how to participate.

@github-actions github-actions Bot closed this May 2, 2026
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