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
6 changes: 6 additions & 0 deletions src/conductor/engine/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -1582,6 +1582,7 @@ async def _execute_loop(self, current_agent_name: str) -> dict[str, Any]:
async with self.limits.timeout_context():
# Emit workflow_started before the execution loop
self._system_metadata = self._build_system_metadata()
default_effort = self.config.workflow.runtime.default_reasoning_effort
self._emit(
"workflow_started",
{
Expand All @@ -1593,6 +1594,11 @@ async def _execute_loop(self, current_agent_name: str) -> dict[str, Any]:
"name": a.name,
"type": a.type or "agent",
"model": a.model,
"reasoning_effort": (
a.reasoning.effort
if a.reasoning is not None
else default_effort
),
}
for a in self.config.agents
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export function AgentDetail({ node }: AgentDetailProps) {
output: node.output,
elapsed: node.elapsed,
model: node.model,
reasoning_effort: node.reasoning_effort,
tokens: node.tokens,
input_tokens: node.input_tokens,
output_tokens: node.output_tokens,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export function MetadataGrid({ items }: MetadataGridProps) {
export function buildAgentMetadata(nd: {
elapsed?: number;
model?: string;
reasoning_effort?: string;
tokens?: number;
input_tokens?: number;
output_tokens?: number;
Expand All @@ -41,6 +42,7 @@ export function buildAgentMetadata(nd: {

if (nd.elapsed != null) items.push({ label: 'Elapsed', value: formatElapsed(nd.elapsed) });
if (nd.model) items.push({ label: 'Model', value: nd.model });
if (nd.reasoning_effort) items.push({ label: 'Reasoning', value: nd.reasoning_effort });
if (nd.tokens != null) items.push({ label: 'Tokens', value: formatTokens(nd.tokens) });
if (nd.input_tokens != null && nd.output_tokens != null) {
items.push({ label: 'In / Out', value: `${formatTokens(nd.input_tokens)} / ${formatTokens(nd.output_tokens)}` });
Expand Down
6 changes: 6 additions & 0 deletions src/conductor/web/frontend/src/stores/workflow-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface IterationSnapshot {
output?: unknown;
elapsed?: number;
model?: string;
reasoning_effort?: string;
tokens?: number;
input_tokens?: number;
output_tokens?: number;
Expand Down Expand Up @@ -82,6 +83,7 @@ export interface NodeData {
type: NodeType;
elapsed?: number;
model?: string;
reasoning_effort?: string;
// Context window tracking
context_pct?: number;
context_window_used?: number;
Expand Down Expand Up @@ -142,6 +144,7 @@ export interface WorkflowAgent {
name: string;
type?: string;
model?: string;
reasoning_effort?: string | null;
}

export interface ParallelGroup {
Expand Down Expand Up @@ -936,6 +939,7 @@ const eventHandlers: Record<string, (state: MutableState, data: Record<string, u
const nodeType = (a.type || 'agent') as NodeType;
ensureNode(state.nodes, a.name, nodeType);
if (a.model) state.nodes[a.name]!.model = a.model;
if (a.reasoning_effort) state.nodes[a.name]!.reasoning_effort = a.reasoning_effort;
agentNames.add(a.name);
}
}
Expand Down Expand Up @@ -983,6 +987,7 @@ const eventHandlers: Record<string, (state: MutableState, data: Record<string, u
const nodeType = (a.type || 'agent') as NodeType;
ensureNode(ctx.nodes, a.name, nodeType);
if (a.model) ctx.nodes[a.name]!.model = a.model;
if (a.reasoning_effort) ctx.nodes[a.name]!.reasoning_effort = a.reasoning_effort;
agentNames.add(a.name);
}
}
Expand All @@ -1006,6 +1011,7 @@ const eventHandlers: Record<string, (state: MutableState, data: Record<string, u
output: nd.output,
elapsed: nd.elapsed,
model: nd.model,
reasoning_effort: nd.reasoning_effort,
tokens: nd.tokens,
input_tokens: nd.input_tokens,
output_tokens: nd.output_tokens,
Expand Down
2 changes: 1 addition & 1 deletion src/conductor/web/frontend/src/types/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export type EventType =
export interface WorkflowStartedData {
name: string;
entry_point?: string;
agents: Array<{ name: string; type?: string; model?: string }>;
agents: Array<{ name: string; type?: string; model?: string; reasoning_effort?: string | null }>;
routes: Array<{ from: string; to: string; when?: string }>;
parallel_groups?: Array<{ name: string; agents: string[] }>;
for_each_groups?: Array<{ name: string }>;
Expand Down

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/conductor/web/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Conductor Dashboard</title>
<script type="module" crossorigin src="/assets/index-C_kyLX-T.js"></script>
<script type="module" crossorigin src="/assets/index-BoZxUtZP.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Eux_9Dst.css">
</head>
<body>
Expand Down
79 changes: 79 additions & 0 deletions tests/test_engine/test_event_emission.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
LimitsConfig,
OutputField,
ParallelGroup,
ReasoningConfig,
RouteDef,
RuntimeConfig,
WorkflowConfig,
Expand Down Expand Up @@ -200,6 +201,84 @@ async def test_workflow_started_includes_routes(self) -> None:
assert any(r["from"] == "a" and r["to"] == "b" for r in routes)
assert any(r["from"] == "b" and r["to"] == "$end" for r in routes)

@pytest.mark.asyncio
async def test_workflow_started_includes_reasoning_effort(self) -> None:
"""workflow_started includes per-agent reasoning_effort.

- Agent with explicit reasoning.effort wins.
- Agent without explicit reasoning falls back to
runtime.default_reasoning_effort.
- When neither is set, the field is None.
"""
emitter, collector = _make_emitter_and_collector()
config = WorkflowConfig(
workflow=WorkflowDef(
name="reasoning-test",
entry_point="explicit",
runtime=RuntimeConfig(provider="copilot", default_reasoning_effort="medium"),
context=ContextConfig(mode="accumulate"),
limits=LimitsConfig(max_iterations=10),
),
agents=[
AgentDef(
name="explicit",
model="gpt-4",
prompt="explicit override",
reasoning=ReasoningConfig(effort="high"),
output={"x": OutputField(type="string")},
routes=[RouteDef(to="default")],
),
AgentDef(
name="default",
model="gpt-4",
prompt="uses workflow default",
output={"y": OutputField(type="string")},
routes=[RouteDef(to="$end")],
),
],
output={"y": "{{ default.output.y }}"},
)
provider = CopilotProvider(
mock_handler=lambda a, p, c: {"x": "1"} if a.name == "explicit" else {"y": "2"}
)
engine = WorkflowEngine(config, provider, event_emitter=emitter)
await engine.run({})

event = collector.first("workflow_started")
agents = {a["name"]: a for a in event.data["agents"]}
assert agents["explicit"]["reasoning_effort"] == "high"
assert agents["default"]["reasoning_effort"] == "medium"

@pytest.mark.asyncio
async def test_workflow_started_reasoning_effort_unset_is_none(self) -> None:
"""When neither agent nor workflow default sets effort, field is None."""
emitter, collector = _make_emitter_and_collector()
config = WorkflowConfig(
workflow=WorkflowDef(
name="no-reasoning",
entry_point="agent1",
runtime=RuntimeConfig(provider="copilot"),
context=ContextConfig(mode="accumulate"),
limits=LimitsConfig(max_iterations=10),
),
agents=[
AgentDef(
name="agent1",
model="gpt-4",
prompt="no reasoning anywhere",
output={"result": OutputField(type="string")},
routes=[RouteDef(to="$end")],
),
],
output={"result": "{{ agent1.output.result }}"},
)
provider = CopilotProvider(mock_handler=lambda a, p, c: {"result": "done"})
engine = WorkflowEngine(config, provider, event_emitter=emitter)
await engine.run({})

event = collector.first("workflow_started")
assert event.data["agents"][0]["reasoning_effort"] is None


class TestAgentEvents:
"""Tests for agent_started, agent_completed, and agent_failed events."""
Expand Down
Loading