diff --git a/src/conductor/engine/workflow.py b/src/conductor/engine/workflow.py index 484192b..89f812a 100644 --- a/src/conductor/engine/workflow.py +++ b/src/conductor/engine/workflow.py @@ -1448,6 +1448,23 @@ async def _execute_loop(self, current_agent_name: str) -> dict[str, Any]: "stderr": script_output.stderr, "exit_code": script_output.exit_code, } + # Auto-parse JSON stdout: if stdout is valid JSON + # object, merge its fields into output so they're + # accessible as output.field_name in templates and + # route conditions (like LLM structured outputs). + try: + parsed = json.loads(script_output.stdout) + if isinstance(parsed, dict): + shadowed = set(parsed.keys()) & set(output_content.keys()) + if shadowed: + logger.debug( + "Script '%s' JSON output shadows built-in fields: %s", + agent.name, + ", ".join(sorted(shadowed)), + ) + output_content.update(parsed) + except (json.JSONDecodeError, ValueError): + pass self.context.store(agent.name, output_content) self.limits.record_execution(agent.name) self.limits.check_timeout() diff --git a/tests/test_engine/test_workflow.py b/tests/test_engine/test_workflow.py index 14843a4..589e1b6 100644 --- a/tests/test_engine/test_workflow.py +++ b/tests/test_engine/test_workflow.py @@ -298,6 +298,46 @@ def mock_handler(agent, prompt, context): # Workflow.input.goal should not be in agent2's context since it's not in input list assert "other" not in agent2_context.get("workflow", {}).get("input", {}) + @pytest.mark.asyncio + async def test_script_json_stdout_parsed_into_output(self) -> None: + """Test that script stdout containing JSON is auto-parsed into output fields.""" + config = WorkflowConfig( + workflow=WorkflowDef(name="json-script", entry_point="detector"), + agents=[ + AgentDef( + name="detector", + type="script", + command="pwsh", + args=[ + "-Command", + 'Write-Output \'{"plan_exists": true, "route": "planning"}\'; exit 0', + ], + routes=[ + RouteDef(to="planner", when="route == 'planning'"), + RouteDef(to="$end"), + ], + ), + AgentDef( + name="planner", + type="script", + command="pwsh", + args=["-Command", "Write-Output 'done'; exit 0"], + routes=[RouteDef(to="$end")], + ), + ], + ) + + provider = CopilotProvider(mock_handler=lambda a, p, c: {}) + engine = WorkflowEngine(config, provider) + await engine.run({}) + + det = engine.context.agent_outputs["detector"] + assert det["plan_exists"] is True + assert det["route"] == "planning" + assert "stdout" in det + assert det["exit_code"] == 0 + assert "planner" in engine.context.agent_outputs + class TestWorkflowEngineRouting: """Tests for workflow routing."""