feat: programmable dynamic workflows (facade, execute_loop, shared budget) + parallel-write safety fix#64
Merged
Merged
Conversation
The parallel write fast path executed tools directly via the ToolExecutor,
bypassing ToolSafetyGate entirely — so with multiple write calls in one turn,
permission checks and skill restrictions were not enforced.
Make can_run_parallel_write_batch consult the gate itself (single source of
truth): only fast-path when, for every call, no active skill restriction forbids
the tool AND the permission checker explicitly Allows it. A missing checker
resolves to Ask (a Deny without a confirmation manager), so it correctly refuses.
This preserves the optimization for the explicit-allow case while closing the
bypass. Exposes ToolSafetyGate::{check_skill_restrictions,permission_decision}
as pub(crate).
…ed budget
Compose the existing combinators into a runtime-driven, Claude-Code-style
dynamic workflow mechanism, reusing the AgentExecutor / SessionStore /
WorkflowCheckpoint / BudgetGuard seams rather than inventing parallel ones.
- Workflow facade (orchestration/workflow.rs): a cheaply-clonable handle whose
verbs agent/parallel/phase/pipeline each delegate to one combinator; phase is
a named, resumable barrier ({root_id}/{index}:{name}) emitting WorkflowEvent
milestones. Control flow lives in the host language.
- execute_loop + LoopDecision (combinators.rs): bounded loop-until-dry with a
mandatory max_iterations hard cap.
- WorkflowBudget (orchestration/workflow_budget.rs): an aggregating BudgetGuard
that sums token spend from every step into one shared ledger (soft cap).
- Wire BudgetGuard through ChildRunContext so a single guard spans a fan-out
(closes a gap: budget was per-session_id only).
- AgentSession::workflow()/workflow_with_token_budget(): pre-wire executor,
store, per-step events, session-derived root id, and the shared budget. Also
fix agent_executor() to install parent_context (security/skill/workspace), so
orchestrated steps are neither more nor less privileged than delegated ones.
- README: document the Workflow facade, loop, and shared budget.
Tests: full lib suite green; new unit + e2e coverage for every verb, resume,
loop caps, budget aggregation, and a real child-agent workflow step.
…allel)
Expose the shared workflow budget through both SDKs via a session method built
on AgentSession::workflow_with_token_budget(): run a fan-out where every child
agent feeds ONE token ledger and, once the cap is reached, further child LLM
calls are denied (a soft cap; the in-flight fan-out is never force-killed).
Returns the per-step outcomes plus the ledger snapshot.
- Node (napi): session.workflowParallel(specs, budgetTokens?) -> { outcomes, budget }
- Python (pyo3): session.workflow_parallel(specs, budget_tokens=None) -> dict
- Regenerate node generated.d.ts; document both in the README SDK examples.
Both native modules build (napi build / maturin) and pass an offline smoke test
(empty fan-out takes no LLM path; correct ledger snapshot). Full orchestration
behavior is covered by the core crate's test suite.
- core/tests/test_workflow_facade_real_llm.rs (#[ignore], real-LLM gated like test_orchestration_real_llm.rs): the Workflow facade phase + milestones, execute_loop's max_iterations hard cap, session.workflow() running a real child agent with the shared ledger accumulating spend, and sequential budget enforcement (a step started after the cap is denied). - sdk/node/test.mjs: offline workflowParallel shape check (empty fan-out, ledger snapshot) in the existing smoke run. - sdk/python/tests/test_workflow_parallel.py: the same offline check for Python. Verified: core suite green; the integration target compiles and registers its 4 ignored cases; node 'npm test' and the python smoke both pass offline.
…okens?)
Drop the awkwardly-named workflowParallel / workflow_parallel methods. The only
difference from parallel was a shared token budget, so make it an optional
argument on parallel itself instead of leaking the internal 'workflow' facade
name into the public API.
- Node: parallel(specs, budgetTokens?) -> Array<StepOutcomeObject> |
WorkflowParallelResult (napi Either). No budget => the plain outcomes array
(unchanged, non-breaking); a budget => { outcomes, budget } (ledger snapshot).
- Python: parallel(specs, budget_tokens=None) -> list | dict, same split.
- Update README SDK examples; node test.mjs and the renamed
test_parallel_budget.py cover both the array and budgeted shapes offline.
Rebuilt both native modules (napi build / maturin); npm test and the python
smoke pass; clippy/fmt clean.
ZhiXiao-Lin
pushed a commit
that referenced
this pull request
Jun 7, 2026
- Rust (core + node/python SDK crates), Node package.json + lockfiles, Python SDK + bootstrap shim, and Cargo.lock all aligned to 3.5.0. - 3.5.0 ships the programmable dynamic workflows merged in #64: Workflow facade, execute_loop, shared WorkflowBudget, parallel budget overload, plus the parallel-write safety-gate fix.
ZhiXiao-Lin
added a commit
that referenced
this pull request
Jun 7, 2026
- Rust (core + node/python SDK crates), Node package.json + lockfiles, Python SDK + bootstrap shim, and Cargo.lock all aligned to 3.5.0. - 3.5.0 ships the programmable dynamic workflows merged in #64: Workflow facade, execute_loop, shared WorkflowBudget, parallel budget overload, plus the parallel-write safety-gate fix. Co-authored-by: Claude <claude@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Compose a Claude-Code-style dynamic workflow mechanism from the existing seams (
AgentExecutor,WorkflowCheckpoint,BudgetGuard) — no new subsystem — plus two safety/correctness fixes.Commits
ToolSafetyGate(permission + skill restrictions). Now consults the gate itself (single source of truth); only fast-paths when every call is explicitly Allowed and unrestricted.Workflowfacade (agent/parallel/phase/pipeline/log),execute_loop+LoopDecision(mandatorymax_iterations),WorkflowBudgetaggregating ledger; wiresBudgetGuardthroughChildRunContext;AgentSession::workflow()/workflow_with_token_budget(); also fixesagent_executor()to installparent_context(security/skill/workspace parity).#[ignore]real-LLM tests for the facade/phase/loop/budget (all 4 pass against a live provider).parallel(specs, budgetTokens?)— Node (napiEither) & Python (list|dict) return shapes; non-breaking optional budget arg.Tests
cargo test -p a3s-code-core --lib→ 1755 passed / 0 failed--test test_workflow_facade_real_llm -- --ignored) → 4 passednpm test+ Python smoke pass; clippy/fmt clean.Docs are in a companion PR on
A3S-Lab/a3s(apps/docs).