AI & Agents

Agent Work Steps

The worker-side AI step types — ai_action for a single inference and ai_agent for an execution-bound agentic loop — that run inside a DAG workflow run.

The chat agent is the interactive face of Ductor's agent loop. This page is the headless one: two step types that run AI inside a DAG workflow run, driven by workers rather than a browser. Same underlying machinery, different host — a workflow step has no operator watching a stream, so it is execution-bound, resumable, and metered as part of the run.

Step typeWhat it does
ai_actionA single LLM inference through the inference proxy, with optional structured-output validation.
ai_agentA workflow host for the shared durable agent runtime, with preset resolution, governed tools, approvals, and execution bounds.

ai_action — one inference

ai_action (executed by AIActionExecutor) is the simple case: it parses the step args, builds an inference request, calls RouteInference on the AI service, and — when the step declares an output JSON Schema — validates the response against it before returning a structured result. Use it when you want the model to transform or classify some data as one deterministic step in a pipeline, not to run a multi-turn agent.

ai_agent — a durable runtime host

ai_agent is executed by DurableAgentExecutor. It maps a DAG node onto the shared durable agent runtime: the model proposes tool calls, the governed broker authorizes and executes them, results are journaled, and the runtime continues until completion, a durable input request, or an execution bound.

  • Preset resolution. The step can reference a tenant agent preset (by id, optionally version-pinned); the agent_preset_resolver loads and resolves it before the loop starts.
  • Governed tool execution. The canonical registry resolves a content-addressed tool definition; the policy receipt and argument hash are bound to the invocation before the connector action runs.
  • Resumable approval state. An approval-gated invocation becomes a workflow approval wait. The node persists the agent session, turn, request id, and expected journal sequence. Resume must match all four before the approved invocation runs.
  • Execution bounds. max_tool_calls / max_requests cap the loop, and crossing a bound is recorded distinctly.
  • Typed external input. Approval, question, budget extension, external-auth, and reconciliation pauses use the same actor-bound, expiring, single-use protocol.
  • Bounded delegation. Inline definitions may declare exact/versioned specialist presets, tool intersections, depth/fanout/concurrency ceilings, child budgets, and JSON output schemas. A model supplies only task ids and prompts.
  • Scoped memory. A pinned memory policy may recall only verified tenant/subject/entity records. Recalled text is always untrusted user-role context.
  • Typed model and completion policy. The same configured decision-policy profile used by chat is applied before the workflow definition's first pin. Existing persisted policy wins, and child specialists receive the same immutable overlay rules.

Model-only steps

An inline definition or tenant preset may have no tools. For example, a step can produce a concise summary in a single model turn:

Model-only AI_AGENT args
{
  "model": "<enabled model>",
  "user_prompt": "Return a concise summary of the supplied task.",
  "tools": [],
  "max_requests": 1,
  "max_tool_calls": 0
}

The host merges preset configuration before resolving omitted bounds. An omitted max_tool_calls becomes 0 for an effective definition without callable tools, or 50 when tools, skills, or specialists make calls available. An explicit JSON max_tool_calls: 0 remains zero even if the referenced preset has tools. A stored preset's zero tool bound means use that default; it does not override the explicit step bound.

An omitted max_requests inherits a positive preset bound or defaults to 25; explicit request bounds must be positive. Negative limits are refused. An empty manifest is still governed and persisted, and unsolicited tool calls are denied before execution.

DAG run step: ai_action step: ai_agent RouteInference Validate schema result Resolve preset Shared agent runtime(journaled, bounded, resumable) Typed decisionsmodel + completion result

Award-bound AI_AGENT execution

For held RFQ work, use an award reference instead of a caller-selected preset:

AI_AGENT args
{
  "award_ref": { "award_id": "${{ TRIGGER.award_id }}" },
  "max_requests": 1,
  "max_tool_calls": 1
}

The REST step type is STEP_TYPE_AI_AGENT; workflow DSL calls it ai_agent. award_ref accepts only award_id. Optional max_requests, max_tool_calls, and max_tokens tighten bounds. Model, provider, preset, instructions, prompt, tools, schema, sampling, delegation, and approval overrides are refused even when empty. Ordinary non-award ai_agent configurations retain their own preset/inline contract.

For a selected model-only worker, set max_tool_calls to 0 or omit it so the host resolves the source definition's bounds. Explicit zero is preserved on the award path too. The source preset identity and hash remain unchanged; governance records the separate execution definition and its empty manifest.

The resolver checks the tenant, pre-reserved workflow run id, award identity, immutable task snapshot, and original selected preset pin. The normal durable runtime records a governed execution definition separately from that source preset, with the actual session/turn, manifest, input hash, and run/step. It does not mutate the selected preset or substitute a current registration.

The RFQ evaluator independently reads journaled worker output and validates the published inline schema before capture. A successful buyer workflow alone is insufficient; absent selected execution is a buyer-attributed rejection. See Clear AI work for task schemas and settlement.

How it relates to the rest of the surface

  • Versus durable chat. Shared runtime, different host adapter. Chat exposes committed journal events over resumable SSE. A workflow maps the same outcomes to node completion, failure, or an approval wait in durable run state.
  • Versus agent presets. An ai_agent step resolves a tenant agent preset to get its model, instructions, tool set, and bounds — the same presets a chat session references. The compile-time preset templates are a separate thing: they run as ordinary connector actions, which an ai_action-style step invokes directly.
  • Versus the inference proxy. Both step types issue every LLM call through the one provider-agnostic proxy, so provider choice and cost guards are uniform with the rest of Ductor's AI surface.
  • Versus typed decisions. ai_action remains one generative inference. An ai_agent definition may additionally use the separate decision plane for first-pin model choice and close/continue/review classification.

Observability

The executors emit dedicated metric families — ai_action_* (request counts, token totals and per-request distribution, duration) and ai_agent_* (tool-call counts, LLM-iteration counts, bound-exceeded counts, total duration). Model, provider, and tool labels are normalized to a closed enum so a misconfigured workflow cannot inflate metric cardinality.

Inference usage is also written to the durable usage plane with workflow run and step correlation. When a step is part of an experiment, pass the experiment id and immutable exposure id as inference metadata so request, token, cost, latency, and error events can be attributed to the exact variant. See Durable usage and attribution and Experiments.

Enable the durable workflow host

configs/ductor.yaml
workflow_runtime:
  enabled: true

ai_inference:
  enabled: true
  providers:
    primary:
      type: gemini
      api_key_env: GEMINI_API_KEY
      models: [gemini-2.5-flash-lite]
      enabled: true

There is no legacy workflow-agent flag or fallback executor. When workflow inference and connector actions are enabled, startup wires the canonical journal, model-policy, processor, tool-policy, memory, and delegation services; missing required dependencies fail startup rather than selecting another loop.

Specialist definitions are resolved and pinned before parent admission. Child tools are the exact intersection of parent tools, child tools, and the declared allowlist. Children receive only their task prompt and content-addressed lineage—not the parent transcript or memory—and join in stable declared order even when they finish in parallel. Undeclared children, duplicate task ids, recursive depth overflow, schema failure, or aggregate budget escape fail before child admission.

An approval is not a fresh tool proposal

Resume uses the invocation that was already authorized and parked. A stale sequence, wrong request id, different session, or different turn is rejected. The executor never asks the model to recreate an approval-gated mutation after the operator acts.