From 8d72ee3b5e96b4a7c011e509f54cfff4bbabc777 Mon Sep 17 00:00:00 2001 From: Daniel Green Date: Fri, 24 Apr 2026 20:12:56 -0700 Subject: [PATCH 1/2] feat(engine): auto-parse script agent JSON stdout into output fields When a script agent's stdout is valid JSON, the parsed fields are now merged into the output dict alongside stdout/stderr/exit_code. This makes parsed fields accessible in route conditions and downstream templates as output.field_name. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/conductor/engine/workflow.py | 10 ++++++++ tests/test_engine/test_workflow.py | 40 ++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/src/conductor/engine/workflow.py b/src/conductor/engine/workflow.py index 484192b..e94b017 100644 --- a/src/conductor/engine/workflow.py +++ b/src/conductor/engine/workflow.py @@ -1448,6 +1448,16 @@ 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): + 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.""" From fee0dca4a127a5d1b22ede35e6b15b31d7aeb9a2 Mon Sep 17 00:00:00 2001 From: Daniel Green Date: Tue, 28 Apr 2026 09:56:14 -0700 Subject: [PATCH 2/2] fix(engine): log when script JSON output shadows built-in fields Add debug-level logging when a script's parsed JSON output contains keys that shadow the built-in stdout/stderr/exit_code fields. Makes the intentional shadowing behavior observable for debugging. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/conductor/engine/workflow.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/conductor/engine/workflow.py b/src/conductor/engine/workflow.py index e94b017..89f812a 100644 --- a/src/conductor/engine/workflow.py +++ b/src/conductor/engine/workflow.py @@ -1455,6 +1455,13 @@ async def _execute_loop(self, current_agent_name: str) -> dict[str, Any]: 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