Agent Action Nodes
agent.invoke, agent.dispatch, agent.dispatch_stage, file management, and session state nodes.
The layered execution model - how agents are packaged, what kits and credentials they receive, and how the runner processes work items - is documented at donmai.dev/docs/layered-execution-model. This page covers the platform workflow nodes that dispatch and manage agents.
Agent action nodes bridge the workflow engine and the donmai runtime. They dispatch work to agents, manage concurrent session caps, track session state, and reserve files across parallel agents.
Node summary
| Node ID | Purpose |
|---|---|
agent.invoke | Dispatch work and wait for session completion (synchronous) |
agent.dispatch | Explicit A2A dispatch to a registered remote agent by card + skill |
agent.dispatch_stage | SDLC stage dispatch - reads canonical stage CloudEvent (deprecated; use agent.invoke) |
agent.dispatch_count.increment | Increment the per-issue dispatch counter |
agent.file.reserve | Acquire pessimistic file locks for a session |
agent.file.release | Release file locks held by a session |
agent.session.get_state | Read current session status and metadata |
agent.invoke
The primary dispatch node. Accepts agent composition refs - either a pre-composed AgentDispatchRef from an upstream agent foundational node, or individual slot refs wired directly - dispatches work, and waits for the session to complete before routing to success or fail.
agent.invoke accepts two composition paths:
- Path 2 - full composition: Wire a foundational
agentnode'soutputport to thecompositioninput. Theagentnode fan-ins all refs upstream. - Path 3 - individual slots: Wire
agent-definition,llm-model,kit-provider,credential-provider,capacity-pool, and capability nodes directly to the matching slot ports onagent.invoke.
Input ports
| Port | Type | Required | Description |
|---|---|---|---|
agentRef | string | yes | Agent card $ref URI |
stageEvent | object | no | Auto-bound from trigger output; passed into the agent as initial context |
partials | array | no | Skill partial refs to attach ([{ id, condition?, order? }]) |
modelProfileId | string | no | LLM model profile override |
budgetOverride | object | no | { maxDurationSeconds, maxSubAgents, maxTokens } |
composition | object | no | Full AgentDispatchRef from an upstream agent node (Path 2) |
definition | object | no | AgentDefinitionRef (Path 3 slot) |
model | object | no | ModelRef (Path 3 slot) |
kits | array | no | KitRef[] (Path 3 slot) |
credentials | object | no | CredentialRef (Path 3 slot) |
sandbox | object | no | Sandbox ref (Path 3 slot) |
capacityPool | object | no | CapacityPoolRef (Path 3 slot) |
capabilities | array | no | CapabilityRef[] (Path 3 slot) |
organizationId | string | no | Override org context |
projectName | string | no | Override project context |
sessionId | string | no | Parent session ID for sub-agent tracking |
agentId | string | no | Pin to a specific registered agent ID |
repoPath | string | no | Working repository path |
userIntent | string | no | Free-text intent passed to the agent |
priority | number | no | Queue priority for this dispatch |
Output ports
| Port | Type | Description |
|---|---|---|
success | InvokeResult | Session completed successfully |
fail | { error: string } | Session failed or could not be dispatched |
InvokeResult shape
{
sessionId: string
stageId: string
queuePosition?: number
dispatched: boolean
parked?: boolean
replaced?: boolean
agentCardId?: string
budget?: {
maxDurationSeconds: number
maxSubAgents: number
maxTokens: number
}
}Dynamic contract ports (B4)
When you select an agent card in the config panel and that card declares completion_contract.outputs, the editor projects additional named output ports onto the node. For example, a card with { outputs: { summary: "string", pr_number: "number" } } produces summary and pr_number output ports that downstream nodes can wire to typed inputs.
agent.dispatch
Explicit A2A dispatch node. Sends a work item to a registered remote agent by targeting either an exact agent card + skill (targetAgentRef) or the best-matching registered agent for a given skill description (skillMatcher). Unlike agent.invoke, this node does not wait for the session to complete - it routes to output immediately after queuing.
Use agent.dispatch when routing work to a remote registered agent at a known address. For dispatching work to a local-pool agent within your own SDLC, prefer agent.invoke.
Input ports
| Port | Type | Required | Description |
|---|---|---|---|
orgId | string | yes | Org owning the registered remote agents |
targetAgentRef | object | no | Explicit pointer: { agentCardId: string, skillId: string }. When set, skill-matching is skipped. |
skillMatcher | object | no | Skill-based matcher: { workType?: string, capabilities?: string[] }. The highest-scoring registered agent receives the dispatch. |
args | object | no | Payload forwarded to the remote agent's skill |
sessionId | string | no | Optional session ID for correlation and dedup |
timeoutMs | number | no | Override the outbound message timeout (default: 30,000 ms) |
Output port
| Port | Type | Description |
|---|---|---|
output | DispatchResult | Dispatch result including agentCardId, skillId, sessionId, capabilityScore, and latencyMs |
Example: dispatch to an explicit agent
- id: dispatch_reviewer
type: action
nodeId: agent.dispatch
config:
orgId: "{{ $workflow.orgId }}"
targetAgentRef:
agentCardId: "abc123"
skillId: "code-review"
args:
issueId: "{{ $trigger.data.issueId }}"
prUrl: "{{ nodes.create_pr.success.htmlUrl }}"agent.dispatch_stage
agent.dispatch_stage is deprecated. Use agent.invoke for new workflows. Existing workflows using dispatch_stage continue to function, but the canvas displays a deprecation banner. The canonical migration path is to wire an agent-definition foundational node into agent.invoke's definition slot and move inline promptArtifact content there.
Dispatches a named SDLC stage. Reads the canonical stage CloudEvent payload, renders the stage prompt with issue context, and queues work with budget metadata. Used by legacy SDLC templates to dispatch individual phases (research, backlog-creation, development, QA, acceptance) while preserving stage-level context.
Input ports
| Port | Type | Required | Description |
|---|---|---|---|
stageEvent | object | yes | Canonical stage CloudEvent data payload from the trigger normalizer |
stageId | string | no | Stage ID (e.g. "research", "development"). Falls back to the inbound CloudEvent's stageId when omitted. |
budgetOverride | object | no | { maxDurationSeconds?, maxSubAgents?, maxTokens? } - absent fields use stage defaults |
sessionId | string | no | Optional session ID; generated if omitted |
organizationId | string | no | Linear org ID for tenant isolation |
projectName | string | no | Linear project name - drives model cascade and worker routing |
model | string | no | Model identifier override |
subAgentModel | string | no | Model override for sub-agents spawned by the thinking agent |
priority | number | no | Queue priority (lower = higher priority) |
repoPath | string | no | Repository path hint |
agentId | string | no | Linear agent ID that owns the session |
Output port - output ({ sessionId, stageId, queuePosition?, dispatched, parked?, replaced?, budget? })
agent.dispatch_count.increment
Increments the per-issue dispatch counter. Pair with the agent.dispatch_count.under_cap condition node to enforce a maximum number of agent sessions per Linear issue.
Input ports
| Port | Type | Required | Description |
|---|---|---|---|
issueId | string | yes | Linear issue ID |
Output port - output ({ count: number }): the new counter value after increment.
agent.file.reserve
Acquires pessimistic locks on one or more file paths for the duration of an agent session. Prevents concurrent agents from editing the same files simultaneously. Returns both successfully reserved paths and any conflicts (files already held by another session, with holder details).
Input ports
| Port | Type | Required | Description |
|---|---|---|---|
sessionId | string | yes | Session that will hold the locks |
filePaths | string[] | yes | Paths relative to the repo root to reserve |
reason | string | no | Optional human-readable reason stored on the reservation |
Output port - output ({ reserved: string[], conflicts: FileConflict[] })
FileConflict shape: { filePath: string, heldBy: { sessionId, repoId, filePath, reservedAt, reason? } }
Conflicts do not cause the node to route to fail - the output always carries both reserved and conflicts arrays. Wire a downstream condition to check conflicts.length > 0 if you need to branch on conflicts.
agent.file.release
Releases all file locks held by a session. Call this at the end of every branch - including error paths - to unblock downstream agents.
Input ports
| Port | Type | Required | Description |
|---|---|---|---|
sessionId | string | yes | Session whose reservations to release |
Output port - output ({ released: number }): count of reservations released.
agent.file.release always releases all reservations for the session. There is no per-file release - to release a subset, use agent.file.reserve on a new session with only the files you want to continue holding.
agent.session.get_state
Reads the current state of an agent session. Returns status, work type, model, branch, PR info, cost, and token counts. Useful in polling loops or condition checks that need current session metadata.
Input ports
| Port | Type | Required | Description |
|---|---|---|---|
sessionId | string | yes | Agent session ID |
Output port - output (SessionState)
SessionState shape
{
status: 'queued' | 'running' | 'completed' | 'failed' | 'stopped'
workType?: string
provider?: string
model?: string
branch?: string
prUrl?: string
prNumber?: number
cost?: number
tokenCount?: number
startedAt?: string // ISO-8601 timestamp
completedAt?: string // ISO-8601 timestamp
}Usage pattern: SDLC stage dispatch with agent.invoke
The SDLC v2 template wires agent.invoke with a composed agent foundational node:
linear.agent_session.created
→ linear.agent_session.acknowledge
→ agent.dispatch_count.under_cap (condition: issueId)
→ true → agent.dispatch_count.increment
→ agent.invoke (composition from agent foundational node)
→ success → linear.agent_session.close
→ fail → linear.comment.create ("Stage failed")
→ false → linear.comment.create ("Dispatch cap reached")Usage pattern: parallel file reservation
Reserve files before dispatching concurrent agents to prevent edit conflicts:
steps:
- id: reserve
type: action
nodeId: agent.file.reserve
config:
sessionId: "{{ $trigger.data.sessionId }}"
filePaths:
- src/lib/core.ts
- src/lib/utils.ts
reason: "Development stage"
- id: invoke_agent
type: action
nodeId: agent.invoke
config:
agentRef: "agent://my-org/dev-agent"
stageEvent: "{{ $trigger.data }}"
- id: release
type: action
nodeId: agent.file.release
config:
sessionId: "{{ $trigger.data.sessionId }}"Related pages
- Foundational Nodes -
agent,agent-definition,llm-model,kit-provider, and other composition sources - Trigger Nodes -
agent.exit- re-entry point after a dispatched agent session completes - Condition Nodes -
agent.dispatch_count.under_cap,agent.session.has_pr,agent.session.status - Linear Action Nodes -
linear.agent_session.close,linear.agent_session.acknowledge - Parallel Nodes - fan-out multiple agent dispatches concurrently