Skip to content

feat(workflow): add Workflow tool for dynamic multi-agent orchestration - #186

Merged
elkaix merged 14 commits into
mainfrom
feat/dynamic-workflows
Jun 30, 2026
Merged

feat(workflow): add Workflow tool for dynamic multi-agent orchestration#186
elkaix merged 14 commits into
mainfrom
feat/dynamic-workflows

Conversation

@elkaix

@elkaix elkaix commented Jun 30, 2026

Copy link
Copy Markdown
Member

Summary

  • Add a built-in Workflow tool: the model writes a small Python "workflow script" that orchestrates many subagents via agent(), parallel(), pipeline(), phase(), log(), and an optional budget, AST-validated for determinism and executed in a restricted namespace (ported from the JS reference under blackbox/pi-dynamic-workflows-main, redesigned Python-native since the CLI ships no Node runtime).
  • Every agent() call routes through the existing AgentTool (not a lower-level runner), so it inherits root-only/no-nesting enforcement, type/policy/capacity checks, and the established RunAgents orchestration-approval pattern (one approval per distinct script).
  • Live progress streams via wire_send(ProgressNote(...)); structured output supports JSON-schema validation with one retry; cancellation propagates asyncio.CancelledError through to real child teardown (AgentToolForegroundSubagentRunner), proven by both an engine-level test and a real-spawn-layer integration test.
  • Registered only in the default root agent spec — never in child subagent specs.
  • A genuine concurrency bug was found and fixed mid-implementation: the token-budget check originally ran before acquiring the concurrency semaphore, letting every dispatched agent in a batch bypass the budget regardless of concurrency; fixed by gating the check inside the semaphore (now bounded to one concurrency batch's worth of overshoot).
  • token_budget is exposed as a settable, optional per-call tool parameter (mirroring the existing AgentTool.Params.budget_seconds precedent) so the documented budget primitive actually enforces when set, rather than being inert in production.

Test plan

  • make check-pythinker-code (ruff check + format + pyright + ty) — clean
  • Full unit suite (tests/) — 6475 passed, 7 skipped, 1 xfailed
  • tests_e2e — 65 passed, 4 skipped
  • Focused workflow suite (parser/engine/display/tool/registration/abort-integration) — 31 passed
  • Manual interactive test against a real LLM/provider: fan-out to multiple subagents, live progress, and synthesis all confirmed working end-to-end
  • CHANGELOG.md updated under ## Unreleased

Summary by CodeRabbit

  • New Features

    • Added a built-in Workflow tool for deterministic, scripted multi-step orchestration across multiple agents (sequential, parallel, and pipelined flows) with live phase/progress rendering and structured JSON results.
    • Extended default tool availability to include Workflow, alongside workflow specification guidance.
  • Bug Fixes

    • Improved cancellation behavior so aborted workflows tear down active child work reliably.
    • Added stricter workflow-script validation and safer structured-result handling, including schema-based retry, better error messaging, and support for token-budget limits.

elkaix added 12 commits June 30, 2026 13:35
agent() checked budget.remaining() before acquiring the concurrency
semaphore, so every agent() call dispatched together (e.g. via
parallel()) saw the same stale state["spent"] and passed regardless of
the configured concurrency, only catching the overrun after the fact.
Move the check inside async with semaphore so it re-evaluates spend
once a slot is actually granted, bounding worst-case overshoot to one
concurrency batch instead of the whole dispatched set.
…pdates

Adds the Unreleased changelog bullet for the Workflow tool and a real
abort-teardown integration test proving cancellation propagates through
AgentTool -> ForegroundSubagentRunner and marks the child instance
"killed" (not just the engine's own bookkeeping).

Registering Workflow in agent.yaml grew the default agent spec's tools
list, which moved pinned tool-list snapshots in test_agent_spec.py (root
spec + 5 inheriting child specs) and test_default_agent.py (the loaded
toolset's tool names) -- all fixed as a single additive line each.

test_workflow_parser.py/test_workflow_registration.py/test_workflow_tool.py
also picked up ruff-format reflow from the full-repo gate.
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5542013a-b35f-40ee-b502-e036ca2a074a

📥 Commits

Reviewing files that changed from the base of the PR and between 44b7cff and 9553cb2.

📒 Files selected for processing (7)
  • src/pythinker_code/tools/workflow/__init__.py
  • src/pythinker_code/tools/workflow/display.py
  • src/pythinker_code/tools/workflow/engine.py
  • tests/tools/test_workflow_abort_integration.py
  • tests/tools/test_workflow_display.py
  • tests/tools/test_workflow_engine.py
  • tests/tools/test_workflow_tool.py

📝 Walkthrough

Walkthrough

This PR adds a built-in Workflow tool for deterministic Python orchestration, with a sandboxed engine, progress rendering, approval gating, schema-validated subagent retries, default-agent registration, and coverage for parsing, execution, cancellation, and packaging.

Changes

Workflow Tool Implementation

Layer / File(s) Summary
Workflow spec and registration
src/pythinker_code/tools/workflow/description.md, src/pythinker_code/agents/default/agent.yaml, CHANGELOG.md, tests/utils/test_pyinstaller_utils.py
Adds the workflow usage spec, registers the tool in the default agent allowlist, updates the changelog, and includes workflow assets/modules in packaging expectations.
Progress snapshot and rendering
src/pythinker_code/tools/workflow/display.py, tests/tools/test_workflow_display.py
Adds agent/workflow snapshots, phase and status tracking, compact progress rendering, and tests for lifecycle, skipped-state, duplicate-label handling, and log truncation.
Script parsing and workflow runtime
src/pythinker_code/tools/workflow/engine.py
Adds AST-based parsing, runtime types, deterministic execution primitives, concurrency and token-budget handling, coroutine validation, and the async workflow execution wrapper.
Workflow tool execution and child handling
src/pythinker_code/tools/workflow/__init__.py
Adds the public tool, approval gating, snapshot wiring, run_workflow invocation, child-agent execution, schema validation retry, and helper functions for script/output handling.
Workflow parser, engine, tool, and integration tests
tests/tools/test_workflow_*.py, tests/core/test_agent_spec.py, tests/core/test_default_agent.py
Covers parser validation, runtime semantics, tool behavior, registration snapshots, cancellation teardown, and default-agent tool lists.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant Workflow
  participant ApprovalSystem
  participant run_workflow
  participant AgentTool

  Caller->>Workflow: __call__(params: script, args, token_budget)
  Workflow->>Workflow: enforce root-only role
  Workflow->>Workflow: compute script fingerprint
  Workflow->>ApprovalSystem: request orchestration approval
  ApprovalSystem-->>Workflow: approved or rejected
  alt rejected
    Workflow-->>Caller: ToolError
  else approved
    Workflow->>run_workflow: run_workflow(script, agent_runner, concurrency, token_budget, hooks)
    loop per agent() call
      run_workflow->>AgentTool: dispatch child prompt
      AgentTool-->>run_workflow: result or schema retry error
    end
    run_workflow-->>Workflow: WorkflowRunResult(meta, result, logs, phases)
    Workflow-->>Caller: ToolOk(JSON result)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

enhancement

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description has useful detail, but it misses the required Related Issue and Checklist sections and doesn't follow the repo template. Add the template sections: Related Issue with Resolve #(issue_number), Description, and the full Checklist with checked items where applicable.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 70.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title follows conventional commits and accurately summarizes the workflow orchestration change.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dynamic-workflows

Comment @coderabbitai help to get the list of available commands.

Comment thread tests/tools/test_workflow_abort_integration.py
Comment thread tests/tools/test_workflow_engine.py
Comment thread tests/tools/test_workflow_tool.py Fixed
Comment thread tests/tools/test_workflow_tool.py Fixed
Comment thread tests/tools/test_workflow_tool.py Fixed
Comment thread tests/tools/test_workflow_abort_integration.py Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/pythinker_code/tools/workflow/__init__.py`:
- Around line 101-123: The workflow progress hook is dropping `log()` output
because `RunWorkflowHooks.on_log` ignores the log message and only calls
`emit()`, so the text never reaches `WorkflowSnapshot` or the final `ToolOk`
result. Update the workflow event handling around `emit`, `on_log`, and
`render_progress` so log messages are either stored in `WorkflowSnapshot` and
rendered in `ProgressNote`, or remove/disable `log()` from the workflow API if
logs are not meant to be surfaced. Ensure the visible progress/result path
includes the actual log text, not just a rerender trigger.

In `@src/pythinker_code/tools/workflow/display.py`:
- Around line 40-50: Track agent completion by stable id rather than label in
the workflow display state. Update the AgentSnapshot lifecycle in display.py so
start_agent() returns and stores a unique identifier for each agent, and
end_agent() uses that identifier instead of walking the agents list by label.
Keep the display label for rendering only, and make sure the running/error/done
status update targets the exact snapshot created by start_agent().

In `@src/pythinker_code/tools/workflow/engine.py`:
- Around line 273-285: _validate_options() currently passes through arbitrary
dict values, which lets bad types reach AgentOptions consumers like the strip()
call on opts.label in agent() and causes internal AttributeError failures.
Tighten normalization in _normalize_options() by validating that label, phase,
model, and agent_type are either strings or None, and that schema is an allowed
schema shape (or None), raising WorkflowRuntimeError with a clear message when
any field is malformed. Keep the existing dict check and use AgentOptions as the
central place to reject invalid agent() option values early so scripts like
agent("x", {"label": 1}) fail with a tool error instead of an internal
exception.

In `@tests/tools/test_workflow_tool.py`:
- Around line 132-176: The current tests in Workflow.__call__ only spy on
run_workflow kwargs, which ties them to an internal implementation detail
instead of observable tool behavior. Update these tests to exercise the public
tool surface by invoking the tool with a script that is sensitive to token
limits, then assert the resulting ToolOk or ToolError outcome for a small
token_budget and the default unbounded case when token_budget is omitted. Keep
the focus on the Workflow tool contract rather than monkeypatching
pythinker_code.tools.workflow.run_workflow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 65907d0d-9e7f-4b03-90fb-34e78cfe76c9

📥 Commits

Reviewing files that changed from the base of the PR and between a12d6f7 and 44b7cff.

📒 Files selected for processing (15)
  • CHANGELOG.md
  • src/pythinker_code/agents/default/agent.yaml
  • src/pythinker_code/tools/workflow/__init__.py
  • src/pythinker_code/tools/workflow/description.md
  • src/pythinker_code/tools/workflow/display.py
  • src/pythinker_code/tools/workflow/engine.py
  • tests/core/test_agent_spec.py
  • tests/core/test_default_agent.py
  • tests/tools/test_workflow_abort_integration.py
  • tests/tools/test_workflow_display.py
  • tests/tools/test_workflow_engine.py
  • tests/tools/test_workflow_parser.py
  • tests/tools/test_workflow_registration.py
  • tests/tools/test_workflow_tool.py
  • tests/utils/test_pyinstaller_utils.py

Comment thread src/pythinker_code/tools/workflow/__init__.py
Comment thread src/pythinker_code/tools/workflow/display.py Outdated
Comment thread src/pythinker_code/tools/workflow/engine.py
Comment thread tests/tools/test_workflow_tool.py Outdated
@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.72566% with 45 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/pythinker_code/tools/workflow/engine.py 84.79% 29 Missing and 11 partials ⚠️
src/pythinker_code/tools/workflow/display.py 93.42% 2 Missing and 3 partials ⚠️

📢 Thoughts on this report? Let us know!

@elkaix
elkaix merged commit a2e9f45 into main Jun 30, 2026
56 checks passed
@elkaix
elkaix deleted the feat/dynamic-workflows branch June 30, 2026 22:10
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