Skip to main content

AG-UI Protocol Reference

This page describes the HTTP/SSE surface exposed by iac-code agui and the iac-code extension fields carried in standard AG-UI envelopes. See the overview and getting started pages first.

HTTP endpoints

Method and pathPurpose
GET /healthHealth and protocol version information
POST /Submit RunAgentInput and receive an SSE event stream
POST /extensions/iac-code/v1/executions/{executionId}/cancelNamespaced cancellation extension

The POST / body must use JSON, and clients should request SSE:

Content-Type: application/json
Accept: text/event-stream

When IAC_CODE_AGUI_AUTH_TOKEN is configured, protected requests also require:

Authorization: Bearer <token>

Use the standard Accept-Language header as an error-message fallback. forwardedProps.iacCode.preferredLanguage takes precedence and is also forwarded to the A2A runtime.

RunAgentInput

Minimal normal-run example:

{
"threadId": "8473547e-c8ed-4aef-a84c-603a6a8d42da",
"runId": "32c263f2-b0b0-42ac-905c-524a0a9bb652",
"state": {},
"messages": [
{"id": "message-1", "role": "user", "content": "Create a VPC template"}
],
"tools": [],
"context": [],
"forwardedProps": {
"iacCode": {
"schemaVersion": 1,
"rosInvocationId": "invocation-1",
"cwd": "/workspace/session-1",
"runMode": "normal"
}
}
}

Standard fields

FieldRequirementiac-code behavior
threadIdRequired non-empty stringStable conversation identity mapped to one A2A context and iac-code session
runIdRequired non-empty stringOne HTTP/SSE run; cannot be reused within the thread
parentRunIdOptionalCopied to RUN_STARTED
stateRequiredKept in the standard envelope; not used as iac-code runtime state
messagesRequiredA new run uses the latest user message; a resume need not add one
toolsRequired and emptyClient-defined tools are not supported
contextRequiredKept in the envelope; not currently converted into prompt context
forwardedPropsRequiredMust contain the iacCode extension
resumeFor resumeOne response for every pending interrupt

User messages support strings, text parts, and image parts with inline base64 data sources. Remote image URLs, audio, video, document, and generic binary parts are not supported. A decoded image is limited to 8 MiB, all images to 10 MiB, and the full HTTP request to 12 MiB.

forwardedProps.iacCode

This object uses a strict schema; unknown fields are rejected.

FieldTypeRequiredMeaning
schemaVersion1Yesiac-code extension version
rosInvocationIdstringYesCurrent execution caller identity, up to 256 characters
cwdstringYesAbsolute workspace path
modelstringNoPer-request model override
llmApiKeystringNoPer-request LLM provider key
thinking.enabledbooleanNoRequest thinking output
thinking.effortstringNoProvider-specific thinking effort
thinking.budgetpositive integerNoProvider-specific thinking budget
userIdstringNoTelemetry and caller-binding identity
channelstringNoCaller channel metadata
preferredLanguagestringNoRequest-local user-facing language, such as en
candidatePresentationstandard or richNoPipeline candidate presentation
runModenormal or pipelineNoExecution mode; otherwise chosen by A2A
pipelineNamestringNoPipeline name, for example selling
cleanupOnlybooleanNoRequest a Pipeline cleanup-only path
alibabaCloud.accessKeyIdstringNoRequest-local AccessKey ID
alibabaCloud.accessKeySecretstringNoRequest-local AccessKey secret
alibabaCloud.securityTokenstringNoRequest-local STS token
alibabaCloud.regionIdstringNoRequest-local default region

The initial run and its interrupt resumes must retain the same rosInvocationId. A later normal turn may use a new value. Cancellation must use the current execution's value.

A threadId is bound to the first request's cwd and userId; later requests cannot move the same thread to another workspace or caller.

SSE and heartbeat

Each AG-UI event is emitted as an SSE data: record. After 15 seconds without an event, the server emits:

: heartbeat

This is an SSE comment, not an AG-UI CUSTOM event. Conforming clients ignore it while it keeps the HTTP connection active.

Standard event mapping

A2A/iac-code signalAG-UI output
Accepted requestRUN_STARTED
Agent textTEXT_MESSAGE_START/CONTENT/END
Raw thinkingREASONING_START, REASONING_MESSAGE_*, REASONING_END
Tool start and argumentsTOOL_CALL_START/ARGS/END
Tool resultTOOL_CALL_RESULT
Pipeline step lifecycleSTEP_STARTED/STEP_FINISHED
Pipeline recovery snapshotACTIVITY_SNAPSHOT
Normal completionRUN_FINISHED with outcome.type = "success"
User input requiredRUN_FINISHED with outcome.type = "interrupt"
Adapter or A2A errorRUN_ERROR

RUN_FINISHED ends one AG-UI run, not necessarily the whole Pipeline. A Pipeline interrupted several times has several runs, each with its own RUN_STARTED and RUN_FINISHED. Pipeline business completion is represented by pipeline_completed, pipeline_error, and related Pipeline events.

To keep AG-UI spans balanced, the adapter closes open message, reasoning, tool, and step spans before an interrupt ends a run. The resume run reopens any durable Pipeline step still active. Raw event review may therefore show the same business step closing in one run and reopening in the next; this is not reversed execution.

iac-code custom events

iac-code.session.v1

Exposes the current adapter-to-A2A mapping, including threadId, aguiRunId, executionId, contextId, taskId, rosInvocationId, and sessionId. Use executionId with the cancellation extension. Generic clients may safely ignore this event.

iac-code.artifact.v1

Carries a structured projection of an A2A task artifact for optional preview, download, or diagnostics.

iac-code.tool-progress.v1

Carries intermediate tool progress without a standard equivalent. Tool start, arguments, and final result remain standard TOOL_CALL_* events and are not duplicated here.

iac-code.pipeline.v1

Only useful Pipeline information without a complete standard equivalent is emitted. Current eventType values are:

  • Pipeline: pipeline_started, pipeline_resumed, pipeline_completed, pipeline_error, pipeline_warning, backup_blocked;
  • candidates: candidate_started, candidate_completed, candidate_failed, candidate_interrupted, candidate_restart_requested, candidate_selected, candidate_detail_shown, candidate_step_failed;
  • sub-pipelines and step errors: sub_pipeline_started, sub_pipeline_completed, sub_step_failed, step_failed;
  • stacks and cleanup: stack_progress, stack_instances_progress, stack_current_changed, cleanup_started, cleanup_progress, cleanup_completed, cleanup_failed;
  • rollback: rollback_triggered, rollback_completed;
  • context: context_compaction_started, context_compacted, context_compaction_failed, fields_marked_stale;
  • presentation and tools: diagram_shown, mcp_status, tool_progress.

Signals with standard mappings are not duplicated as CUSTOM: text_delta becomes TEXT_MESSAGE_*, thinking_delta becomes REASONING_*, tool_started/tool_result become TOOL_CALL_*, usage becomes RUN_FINISHED.usage, and step lifecycles become STEP_*.

Clients should deduplicate replayed Pipeline events with (name, value.eventId) or the Pipeline sequence and tolerate unknown namespaced custom events.

Interrupt

An input-required run ends with RUN_FINISHED.outcome.type = "interrupt". Each interrupt includes:

  • id and reason;
  • a user-facing message;
  • an optional toolCallId;
  • a JSON responseSchema;
  • expiresAt;
  • metadata such as title, purpose, safeSummary, options, and toolName.

For a permission request, the response schema typically accepts:

{"decision": "allow_once"}

or:

{"decision": "deny"}

Render message, responseSchema, and descriptive metadata instead of inferring the UI from reason alone. Questions and option selection may use different schemas.

Resume

A resume is a new POST / with the same threadId, a new runId, the same rosInvocationId, and one entry per pending interrupt:

{
"resume": [
{
"interruptId": "permission-1",
"status": "resolved",
"payload": {"decision": "allow_once"}
}
]
}

Rules:

  • every pending interrupt must be answered exactly once;
  • duplicate and unknown IDs are rejected;
  • resolved requires a payload matching the corresponding schema;
  • cancelled stops that interrupt, and maps to deny for permissions;
  • durable pending state is removed only after A2A accepts the response;
  • schema errors produce RUN_ERROR while leaving the interrupt retryable;
  • repeated accepted responses do not execute the tool again.

Before applying a resume, the adapter can ask A2A to restore the iac-code session, verifies the A2A task/context identity, and catches up missing Pipeline events.

Turns and identities

threadId (stable conversation)
├─ runId-1 (user turn)
├─ runId-2 (interrupt resume)
├─ runId-3 (another resume)
└─ runId-4 (next normal message)

Every HTTP/SSE request uses a unique runId. Interrupt resume is a new run. After a normal turn completes, the next message creates a new execution while reusing the thread's iac-code session. Run idempotency is scoped to (threadId, runId).

Cancellation extension

POST /extensions/iac-code/v1/executions/<executionId>/cancel
Content-Type: application/json
{"threadId": "thread-1", "rosInvocationId": "invocation-1"}

Possible outcomes are cancelled, already_terminal, or HTTP 404 with EXECUTION_NOT_FOUND. Cancellation clears pending interrupts and does not change standard AG-UI event formats.

Persistence and recovery

Adapter state defaults to:

<config-dir>/agui/threads/<thread-key>.json

Each file contains thread/context/workspace binding, session and task identity, execution identity, Pipeline recovery positions, pending interrupts, and run/resume idempotency data. The adapter lazily loads one requested thread and atomically replaces only that thread's small file.

It never stores LLM keys, AccessKey secrets, or STS tokens. This is an adapter mapping directory, not a store for conversation text or execution artifacts. A2A manages its own session and task persistence; see the A2A documentation.

An expired interrupt is rejected on the next access, its pending state is cleared, and the adapter attempts to cancel the matching A2A task.

Disconnections

  • A run safely finished with an interrupt no longer depends on its SSE connection.
  • Resume creates a new SSE connection.
  • Disconnecting an ordinary active run causes the adapter to cancel the A2A task.
  • Disconnecting after an interrupt does not delete its persisted recovery state.

Errors

Errors before SSE begins use an HTTP JSON envelope. Errors during execution use standard RUN_ERROR events. Common codes include:

CodeMeaning
INVALID_INPUTInvalid envelope, extension fields, message content, or workspace
DUPLICATE_RUN_IDThe same request digest used an existing run ID
RUN_ID_CONFLICTA different request reused an existing run ID
THREAD_BUSYThe thread already has an active run
THREAD_BINDING_CONFLICTThe thread's workspace or caller conflicts with its binding
RESUME_REQUIREDThe thread is waiting for interrupt responses
INCOMPLETE_RESUMEMissing pending interrupts or duplicate IDs
UNKNOWN_INTERRUPTResume references an unknown interrupt
RESUME_PAYLOAD_INVALIDMissing payload or schema mismatch
RESUME_ALREADY_APPLIEDThe response was already applied or conflicts with it
EXECUTION_EXPIREDThe interrupt expired
EXECUTION_LOSTAdapter, A2A task, or iac-code session could not be recovered
STATE_PERSISTENCE_FAILEDRecovery-critical state could not be committed
A2A_UNAVAILABLEThe local A2A execution service is unavailable
A2A_PROTOCOL_ERRORA2A task/context/session identity conflicts with the mapping
A2A_EXECUTION_FAILEDThe A2A task ended in failure
CANCELLEDThe execution was cancelled

Recovery-critical writes fail closed. The adapter does not announce a recoverable task, session, or interrupt before its mapping is durable, and cancels the matching A2A task when necessary.