Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions __tests__/claude-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,39 @@ describe('buildClaudeArgs', () => {
expect(args).not.toContain('--bare');
expect(args).not.toContain('--resume');
expect(args).not.toContain('--add-dir');
expect(args).not.toContain('--effort'); // unset by default
});

it('adds --effort when a role sets an effort level', () => {
const args = buildClaudeArgs({
sessionId: '00000000-0000-4000-8000-000000000002',
systemPrompt: 'role prompt',
model: 'claude-sonnet-4-6',
mcpConfigPath: null,
bare: false,
addDir: null,
resumeFrom: null,
title: undefined,
effort: 'high',
env: baseEnv,
});
expect(args).toContain('--effort');
expect(args[args.indexOf('--effort') + 1]).toBe('high');
});

it('omits --effort when effort is unset', () => {
const args = buildClaudeArgs({
sessionId: '00000000-0000-4000-8000-000000000003',
systemPrompt: 'role prompt',
model: 'claude-sonnet-4-6',
mcpConfigPath: null,
bare: false,
addDir: null,
resumeFrom: null,
title: undefined,
env: baseEnv,
});
expect(args).not.toContain('--effort');
});

it('adds --bare and drops --setting-sources when bare mode is on', () => {
Expand Down
2 changes: 1 addition & 1 deletion docs/roster.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,4 @@ Every role declares a `model` in `src/team/<phase>/<area>.ts`. The current sprea
3. Grade the output — the merge gate + `external-reviewer` calibration for factory roles; a manual read (or `external-reviewer`) for firm roles.
4. Promote (edit the role's `model` in `src/team/*`) only if quality holds; otherwise `fab model clear <role>` to roll back.

**The `effort` parameter** (GA on the Messages API for Opus 4.6+) is deferred: it would need an `AgentCreateParams` shape change and — like context compaction and the Tool Search tool — is not currently exposed on the Managed Agents agent-create surface fab's default transport uses. Revisit once the Managed Agents API carries it.
**The `effort` parameter** (GA on the Messages API for Opus 4.6+) is wired as an optional per-role `effort` on `TeamMember` (`low | medium | high | xhigh | max`), unset by default. It applies only on the transports that expose it — the `sdk` runtime (`query()` `effort`) and `claude-cli` (`--effort`); the Managed Agents agent-create surface does not carry it (like compaction and Tool Search), so it's a no-op on the default transport. Assign per role the same way as model tiering: set `effort` in `src/team/*`, pilot in `sdk`/`claude-cli` mode, keep or revert.
11 changes: 10 additions & 1 deletion src/runtimes/claude-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { join } from 'node:path';
import { createInterface } from 'node:readline';

import type { AgentRuntime, AgentSession, RunRoleOptions } from '../runtime.js';
import type { AgentEvent, FabState, TeamMember, TeamRole, UserEvent } from '../types.js';
import type { AgentEvent, EffortLevel, FabState, TeamMember, TeamRole, UserEvent } from '../types.js';
import { TEAM } from '../team.js';
import { buildSystemPrompt } from '../prompts.js';
import { loadState, getPrimaryRepo } from '../state.js';
Expand Down Expand Up @@ -55,6 +55,7 @@ export class ClaudeCliRuntime implements AgentRuntime {
addDir: repo ? `/workspace/${repo.repo}` : null,
resumeFrom: null,
title: options?.title,
effort: member.effort ?? null,
env: process.env,
});

Expand Down Expand Up @@ -269,6 +270,7 @@ class ResumedClaudeCliSession implements AgentSession {
addDir: repo ? `/workspace/${repo.repo}` : null,
resumeFrom: this.id,
title: undefined,
effort: null, // resume inherits the original session's effort
env: process.env,
});
this.liveSession = new ClaudeCliSession({
Expand Down Expand Up @@ -307,6 +309,7 @@ export interface BuildClaudeArgsOptions {
addDir: string | null;
resumeFrom: string | null;
title: string | undefined;
effort?: EffortLevel | null;
env: NodeJS.ProcessEnv;
}

Expand Down Expand Up @@ -344,6 +347,12 @@ export function buildClaudeArgs(opts: BuildClaudeArgsOptions): string[] {
args.push('--model', opts.model);
}

// Reasoning effort (claude `--effort`). Unset for most roles; set per role to
// trade latency/cost for depth. managed-agents has no equivalent on agent-create.
if (opts.effort) {
args.push('--effort', opts.effort);
}

// System prompt. `--append-system-prompt` layers on Claude Code's own
// default; we keep the default so subprocess tooling stays intact and
// append the role's prompt + factory preamble on top.
Expand Down
15 changes: 13 additions & 2 deletions src/runtimes/sdk.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { AgentRuntime, AgentSession, RunRoleOptions } from '../runtime.js';
import type { AgentEvent, FabState, TeamRole, UserEvent } from '../types.js';
import type { AgentEvent, EffortLevel, FabState, TeamRole, UserEvent } from '../types.js';
import { TEAM } from '../team.js';
import { buildSystemPrompt } from '../prompts.js';
import { loadState, getBudgetLimit } from '../state.js';
Expand Down Expand Up @@ -63,7 +63,16 @@ export class SdkRuntime implements AgentRuntime {
}

const sdk = await loadSdk();
const session = new SdkAgentSession(sdk, model, systemPrompt, options, backend, budgetUsd, mcpServers);
const session = new SdkAgentSession(
sdk,
model,
systemPrompt,
options,
backend,
budgetUsd,
mcpServers,
member.effort,
);
await session.start(message);
return session;
}
Expand Down Expand Up @@ -113,6 +122,7 @@ class SdkAgentSession implements AgentSession {
private readonly backend: InferenceBackend = 'api',
private readonly budgetUsd: number | null = null,
private readonly mcpServers: Record<string, HttpMcpServer> = {},
private readonly effort?: EffortLevel,
) {}

get id(): string {
Expand Down Expand Up @@ -148,6 +158,7 @@ class SdkAgentSession implements AgentSession {
// Role's MCP servers, scoped strictly to fab's set (not the user's
// ambient ~/.claude MCP config) — matches claude-cli's --strict-mcp-config.
...(Object.keys(this.mcpServers).length > 0 && { mcpServers: this.mcpServers, strictMcpConfig: true }),
...(this.effort && { effort: this.effort }),
...(backendEnv && { env: { ...process.env, ...backendEnv } }),
// Resources hint: the SDK uses cwd for filesystem-bound tools;
// workflows.ts pre-creates branches on the cloud-mounted repos
Expand Down
11 changes: 11 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,13 @@ export type TeamRole =
| 'prompt-optimizer'
| 'learner';

/**
* Reasoning-effort level. GA on the Messages API (Opus 4.6+); reachable on the
* sdk (`query()` `effort`) and claude-cli (`--effort`) transports, but NOT the
* managed-agents agent-create surface.
*/
export type EffortLevel = 'low' | 'medium' | 'high' | 'xhigh' | 'max';

export interface TeamMember {
role: TeamRole;
group?: TeamGroup;
Expand All @@ -508,6 +515,10 @@ export interface TeamMember {
system: string;
mcpServers: string[]; // server names from mcp.ts registry
briefTemplate?: string; // nanohype brief template name for skill generation
// Optional per-role reasoning effort. Unset = the model's default. Applied
// only on the sdk / claude-cli transports (managed-agents doesn't expose it);
// assign per role via the same pilot path as model tiering (see docs/roster.md).
effort?: EffortLevel;
}

// ── Git Resources ───────────────────────────────────────────────────
Expand Down
Loading