Two related gaps in the DAG engine (station v0.68, workflow_engine.py) around agent nodes — the governed tool-use loops the airlock is meant to gate. I've tried to state each precisely rather than overstate; both were checked against the source before posting.
Finding 1 — plan_workflow has no agent branch, so an agent workflow's plan is minted but not approvable
plan_workflow()'s node loop branches on kind: transform / http / merge / subworkflow / wait / else (= effect). There is no agent branch, so a normal agent node (declares tools/budget/system, not action_id/provider) falls into the effect path, R.resolve_node(n) returns None, and it raises KeyError("unknown effect"). The broad except catches it and the node plans as {plan_error, policy: {decision: "block"}}, which promotes the aggregate blast-radius requires to block.
The plan object is still built and signed — but it is unapprovable: the agent node is a blocked plan_error. So plan_workflow cannot produce a usable (auto-approvable) pre-approval plan for a workflow containing agent nodes, even though run_workflow handles agent nodes fine at runtime (_run_agent_node). (Scope: a hybrid node carrying effect fields would resolve differently; this is about ordinary agent nodes.)
Repro: plan_workflow(wf) on a wf with a {"type":"agent","tools":[...]} node → that node's entry is {plan_hash: sha(plan_error...), policy:{decision:"block"}}.
Finding 2 — an agent action that needs approval aborts the whole invocation instead of durably suspending
Inside an agent node there is no in-loop interactive approval and no durable suspend/resume. When the gate returns pending, agent_loop returns outcome AWAITING_TEAM_APPROVAL and never executes the action; back in _run_agent_node, if res["outcome"] != "COMPLETED": raise RuntimeError(...) fires, so the saga rolls the node back. The code says so itself: "Clean suspend+resume for AWAITING_TEAM_APPROVAL is a later slice (§11 partial-progress)."
Consequence: a newly-pending agent action aborts the current workflow invocation (discarding intermediate progress) rather than suspending; approval only works via the re-fire model — re-running the whole workflow, where the action reaches proceed if the co-sign was satisfied out-of-band beforehand.
Precision (so this isn't overstated): pending is not the default for an external_send. The gate checks in order — it returns blocked if live execution is disallowed by the workspace policy, or if the spend estimate exceeds the cap (default max_spend_cents is 0, so any priced send is blocked), and only consults team approval afterward; pending arises only when a team-approval policy names that provider. With no team policy an external_send returns proceed and executes. And proceed does not mean "airlock off" — it may carry a satisfied approval block (a prior out-of-band co-sign). The defect is specifically the missing durable suspend/resume, not that approval is impossible.
Suggested fixes
plan_workflow: add anagentbranch that plans from the node's declared tools + disposition + budget (blast radius = union of the tools' action-classes;disposition=airlock⇒require_human; spend frombudget.max_spend_cents), mirroring how_run_agent_nodeis gated inrun_workflow— so an agent workflow yields an approvable plan.- agent runtime: implement durable suspend/resume for
AWAITING_TEAM_APPROVALso an agent node pauses at a pending action, surfaces the approval, and resumes+executes without discarding intermediate progress (rather thanRuntimeError→ rollback).
Found while building marcofgv/freelancer-daily-bidder and marcofgv/freelancer-com. Reviewed against the source before posting. Station v0.68.