Skip to content
Kiro CLI Adapter

Kiro CLI Adapter

The Kiro CLI adapter connects Sortie to the Kiro CLI, the rebranded Amazon Q Developer CLI, via subprocess management. It launches kiro-cli chat --no-interactive, reads a plain human transcript from stdout, and classifies the turn outcome from the process exit status and stderr. Headless Kiro emits no structured event stream, so the adapter parses no JSON. Registered under kind "kiro".

Each RunTurn call spawns a fresh subprocess (fork-per-turn). StartSession runs a credential preflight but starts no long-lived process. EventStream() returns nil; events arrive through the RunTurn OnEvent callback. The adapter is safe for concurrent use: one adapter instance serves all sessions, with per-session state held in an opaque internal handle.

See also: WORKFLOW.md configuration for the full agent schema, environment variables for KIRO_API_KEY, error reference for all agent error kinds, how to write a prompt template for template authoring.


Configuration

The adapter reads from two configuration sections in WORKFLOW.md front matter: the generic agent block (shared by all adapters) and the kiro extension block (pass-through to the Kiro CLI).

agent section

These fields control the orchestrator’s scheduling behavior. They are not passed to the Kiro CLI.

FieldTypeDefaultDescription
kindstring-Must be "kiro" to select this adapter.
commandstringkiro-cliPath or name of the Kiro CLI binary. Resolved via exec.LookPath at session start.
max_turnsinteger20Maximum Sortie turns per worker session. The orchestrator calls RunTurn up to this many times, re-checking tracker state after each turn.
max_sessionsinteger0 (unlimited)Maximum completed worker sessions per issue before the orchestrator stops retrying. 0 disables the budget.
max_concurrent_agentsinteger10Global concurrency limit across all issues.
max_concurrent_agents_by_statemap{}Per-state concurrency limits. Keys are state names, lowercased for matching. Non-positive or non-numeric entries are silently ignored.
turn_timeout_msinteger3600000 (1 hour)Total timeout for a single RunTurn call. The orchestrator cancels the turn context when exceeded. Kiro has no native per-turn timeout, so this is the only backstop on a stuck turn.
read_timeout_msinteger5000 (5 seconds)Timeout for startup and synchronous operations.
stall_timeout_msinteger300000 (5 minutes)Maximum time between consecutive events before the orchestrator treats the turn as stalled. 0 or negative disables stall detection.
max_retry_backoff_msinteger300000 (5 minutes)Maximum delay cap for exponential backoff between retry attempts.
agent:
  kind: kiro
  command: kiro-cli
  max_turns: 5
  max_concurrent_agents: 4
  turn_timeout_ms: 1800000
  stall_timeout_ms: 300000
  max_retry_backoff_ms: 300000

kiro extension section

These fields are adapter-specific. The orchestrator forwards them to the adapter without validation. Each field maps to a kiro-cli chat flag.

FieldCLI flagTypeDefaultDescription
model--modelstring(CLI default)Model identifier passed on every turn. Pinned per turn because the /model slash command is unavailable headless.
trust_all_tools--trust-all-toolsbooleanfalseAuto-approves every tool call. Mutually exclusive with trust_tools.
trust_tools--trust-tools=<csv>list of strings[]Comma-joined tool allowlist. An empty list trusts nothing. Mutually exclusive with trust_all_tools.
agent--agentstring(none)Named Kiro context profile (custom agent).
kiro:
  model: claude-sonnet-4.6
  trust_tools:
    - read
    - grep
    - glob

trust_all_tools: true together with a non-empty trust_tools list is rejected when the adapter is constructed. parsePassthroughConfig returns the error trust_all_tools and trust_tools are mutually exclusive, and the adapter fails to load.

Tool trust behavior

The adapter selects exactly one trust mode per turn and serializes it into buildArgs:

ConfigurationArgument emittedEffect
trust_all_tools: true--trust-all-toolsAuto-approves every tool call.
trust_tools non-empty--trust-tools=<comma-joined>Auto-approves only the listed tools.
Neither configured--trust-tools=Trusts nothing. The flag is always present.

--trust-tools is the default mode. When trust_all_tools is false, the adapter always passes --trust-tools=<csv>, with an empty value when trust_tools is unset. Kiro’s built-in tool catalog includes read, write, glob, grep, and shell, plus aliases such as fs_read, fs_write, and execute_bash, and the further tools aws, web_search, web_fetch, code, and report. A read-only profile (read, grep, glob) is the least-privilege starting point; add write and shell only when the workflow requires file edits or command execution, and only inside a sandbox. The full catalog and its default permissions are documented in the Kiro adapter research notes.


Session lifecycle

StartSession

Validates the workspace path, resolves the kiro-cli binary, verifies the credential, and initializes per-session state. No subprocess is spawned.

  1. Resolves the launch target via agentcore.ResolveLaunchTarget(params, "kiro-cli"). This validates that the workspace path is a non-empty absolute path pointing to an existing directory, and resolves command via exec.LookPath, defaulting to kiro-cli. In SSH mode, it resolves the local ssh binary instead and stores the remote command for later use.
  2. Local mode: runs the credential preflight (checkCredential). Confirms KIRO_API_KEY is set, then runs a kiro-cli whoami canary. See authentication.
  3. SSH mode: skips the credential preflight and injects KIRO_API_KEY inline into the remote command, shell-quoted. See SSH remote execution.
  4. Initializes per-session state: launch target, agent config, pass-through config, logger, the ResumeSessionID value as sessionID, and a fresh per-turn stdout accumulator.
  5. Constructs the agentcore.ForkPerTurnSession that owns the subprocess lifecycle for this session.
  6. Returns a Session with ID set to the resume session ID, an empty AgentPID, and the opaque session state.

Errors:

ConditionError kind
Empty or non-existent workspace pathinvalid_workspace_cwd
Workspace path is not a directoryinvalid_workspace_cwd
Agent command is empty or whitespace-onlyagent_not_found
Local kiro-cli binary not found in PATHagent_not_found
SSH binary not found (SSH mode)agent_not_found
KIRO_API_KEY not set (local mode)response_error
kiro-cli whoami canary times out or exits non-zero (local mode)response_error
Canary output shows an invalid or expired key (local mode)response_error

The credential errors return response_error rather than agent_not_found, because the binary is already resolved when the canary runs. A canary failure means the present binary could not confirm the credential, not that the agent is missing, so it is classified as a retryable credential problem.

RunTurn

Resets the per-turn stdout accumulator and delegates to the shared fork-per-turn session.

  1. Panics if OnEvent is nil.
  2. Recovers the session state from Session.Internal. Returns response_error if the type assertion fails.
  3. Resets the per-turn stdout accumulator so each turn starts clean.
  4. Calls forkSession.RunTurn with the rendered prompt and the OnEvent callback.

The fork-per-turn session builds the argument list, launches one kiro-cli subprocess, scans its stdout, drains stderr, waits for exit, and runs the adapter’s OnFinalize classifier. See headless output and error handling.

StopSession

Terminates a running subprocess by delegating to the fork-per-turn session. Returns nil when no subprocess is active and is safe to call after a failed RunTurn.

EventStream

Returns nil. The adapter delivers all events synchronously through the OnEvent callback in RunTurn.


Process shutdown

The adapter inherits the shared agentcore fork-per-turn shutdown. Each turn runs under exec.CommandContext. Before start, the subprocess is placed in its own process group via the shared procutil package. cmd.Cancel is set to send a graceful signal to the process group, and cmd.WaitDelay is set to 5 seconds.

On Unix, graceful shutdown is SIGTERM and force kill is SIGKILL to the process group. On Windows, graceful shutdown is CTRL_BREAK_EVENT to the process group, and the subprocess is assigned to a Job Object with KILL_ON_JOB_CLOSE so force termination kills the full descendant tree.

Shutdown is turn-scoped, because fork-per-turn means there is no process between turns. StopSession performs an explicit graceful-to-force sequence: it sends SIGTERM to the process group, waits up to 5 seconds for the turn to complete cleanup, then sends SIGKILL to the process group if the grace window elapses. If the StopSession context is cancelled first, the adapter force-kills the process group and returns ctx.Err(). After cmd.Wait returns, the session performs a best-effort group kill to clean up any surviving children.


Headless output

This is the defining section. Headless Kiro emits no structured stream. There is no JSON, no JSONL, and no machine-readable result envelope. The turn outcome is determined from process exit status and stderr, not from parsed stdout.

stdout is a human transcript. For a turn that invokes no tools, it carries the assistant answer with a colorized > marker and ANSI styling. A turn that invokes tools also prints tool-progress lines. The adapter launches with --wrap never to disable width-based line wrapping, strips ANSI color and style escapes from each line, and accumulates the cleaned text into a per-turn buffer.

Each non-empty cleaned line is surfaced as an EventNotification, with the message truncated to 500 runes. The accumulated buffer is not truncated; the OnFinalize classifier reads its length to distinguish an empty-stdout authentication failure from a turn that produced output. The notifications exist for observability; the adapter does not derive turn outcome from them.

stderr carries the signals the adapter classifies:

stderr contentMeaning
â–¸ Credits: trailerThe one positive proof a turn executed. The numeric credit and time values vary; the prefix is the stable contract.
Authentication failed.The credential is present but invalid.
Warnings (for example, Failed to retrieve MCP settings)Non-fatal diagnostics. Re-emitted at WARN level on failure paths.

There are no per-event timestamps in the transcript. The adapter cannot reconstruct tool-call durations, so it emits no EventToolResult events. These findings are documented in the Kiro adapter research notes. This section replaces the JSONL event stream, event type mapping, and result event field sections of the structured-output adapters, none of which apply to Kiro.


Token accounting

The headless path reports no token counts. The closing cost line on stderr (▸ Credits: 0.01 • Time: 1s) carries an abstract credits figure and elapsed time, never input or output token counts. The credits figure does not map onto domain.TokenUsage, which carries only token counters.

The adapter’s GetUsage hook returns the zero domain.TokenUsage value. The adapter emits no EventTokenUsage, and TurnResult.Usage is the zero value on every path. Token-based budget enforcement is therefore inert for this adapter.

Time-based budget enforcement is the only supported mechanism. Set agent.turn_timeout_ms to bound wall-clock time per turn. This replaces the token accumulation, model tracking, and API timing logic of the structured-output adapters.


Error handling

Outcome classification

The turn outcome is determined from the process exit status and the two stderr signals. The exit-0 branches and the generic non-zero branch are decided by the adapter’s OnFinalize callback. Exit 127, signal termination, and cancellation are decided by the shared fork-per-turn skeleton before OnFinalize runs.

Kiro evidenceExit reasonError kindDecided by
Exit 0 with a â–¸ Credits: trailer on stderrturn_completed(none)OnFinalize. Sets the resume flag for subsequent turns.
Exit 0, empty stdout, no credits trailer, Authentication failed. on stderrturn_failedresponse_errorOnFinalize. Message: kiro authentication failed.
Exit 0, no credits trailer (any other case)turn_failedturn_failedOnFinalize. Message: kiro exited without a credits trailer.
Any other non-zero exitturn_failedport_exitOnFinalize. Message: kiro exited with a non-zero status.
Exit 127 (binary not found)turn_failedagent_not_foundShared skeleton.
SIGTERM (143) or SIGKILL (137)turn_cancelledturn_cancelledShared skeleton.
Turn context cancelledturn_cancelledturn_cancelledShared skeleton.
stdout scanner errorturn_failedport_exitShared skeleton. Becomes turn_cancelled if the context is already cancelled.

Why exit 0 is not success

A successful turn and an invalid-credential turn both exit 0. Exit code alone cannot distinguish them. The reliable success signal is the â–¸ Credits: trailer on stderr, which a turn prints only after it actually executed. The adapter never maps a bare exit 0 to turn_completed. It requires the credits trailer as the positive success signal and classifies an exit-0 turn with no trailer as a failure.


Session resume

Continuation is cwd-scoped. The adapter does not track a session ID across turns.

TurnResume flag
First turn of a session(none)
Subsequent turns, after the first successful turn--resume

The resume flag is gated by a per-session resumeRequested state that OnFinalize sets to true after the first turn completes with a credits trailer. From that point, buildArgs appends --resume to every turn, which attaches to the most recent conversation in the workspace directory.

The adapter uses --resume, not --resume-id. The headless conversation’s session ID is not enumerable through the CLI: --list-sessions returns empty for a conversation created by a headless turn, and the headless turn output carries no session ID. Sortie runs one conversation per workspace, so cwd-scoped --resume continues the correct conversation without an ID.

The Kiro adapter research notes summary table lists --resume-id <id> as the preferred deterministic continuation. The shipped adapter uses cwd-scoped --resume instead, for the reasons above. This page documents the shipped adapter.


SSH remote execution

When the worker configuration includes ssh_hosts, the adapter launches kiro-cli on a remote host via SSH instead of locally. The process model stays fork-per-turn: each turn is a separate SSH invocation wrapping one remote subprocess.

How it works

  1. StartSession resolves the local ssh binary. The agent command is stored for remote execution rather than resolved locally.
  2. The credential preflight is skipped. buildSSHRemoteCmd prepends KIRO_API_KEY to the remote command and shell-quotes the value, because OpenSSH drops the orchestrator’s local environment. When KIRO_API_KEY is empty, no prefix is added.
  3. RunTurn builds the per-turn argument list and wraps it with sshutil.BuildSSHArgs.
  4. The remote command is cd -- '<workspace>' && <remoteCommand> '<arg>' ..., with each adapter-generated argument shell-quoted.

SSH options

The adapter uses the shared sshutil transport defaults:

OptionValuePurpose
StrictHostKeyCheckingConfigurable (default: accept-new)Host key verification policy. Set via worker.ssh_strict_host_key_checking. Allowed values: accept-new, yes, no.
BatchModeyesDisables interactive prompts.
ConnectTimeout30Connection timeout in seconds.
ServerAliveInterval15Keepalive interval in seconds.
ServerAliveCountMax3Number of missed keepalives before disconnect.

Shell quoting

The workspace path and the adapter-generated arguments are single-quoted with standard POSIX escaping before they are embedded in the remote shell command. The KIRO_API_KEY value is quoted with the same mechanism. The configured remote base command is treated as a pre-formed shell fragment; quoting inside agent.command is the operator’s responsibility.

Exit codes

SSH exit code 255 indicates a connection failure (refused, timeout, unreachable) and maps to port_exit through the generic non-zero branch. Exit code 127 means the remote kiro-cli binary is not in PATH and maps to agent_not_found.


Authentication

The adapter consumes KIRO_API_KEY. The headless path requires a Kiro Pro, Pro+, or Power subscription. Sortie does not manage the credential beyond the preflight; the subprocess inherits the full parent process environment, and kiro-cli reads the key directly.

StartSession runs a credential preflight in local mode (checkCredential):

  1. Confirms KIRO_API_KEY is set and non-empty. A missing key returns response_error.
  2. Runs a kiro-cli whoami canary with a 5-second timeout. A timeout or non-zero exit returns response_error.
  3. Inspects the canary output. The key is accepted only when the output contains the success marker Authenticated with API key and does not contain Authentication failed.. Otherwise the preflight returns response_error for an invalid or expired key.

The preflight defends against two distinct failure shapes:

FailureSymptom without the preflight
No credentialHeadless chat enters an interactive device-login flow and blocks indefinitely, because --no-interactive does not suppress login.
Invalid keyHeadless chat exits 0 with empty stdout and Authentication failed. on stderr, a silent failure that exit code alone cannot detect.

The presence check defends against the hang; the whoami canary defends against the silent exit-0 failure. The orchestrator turn timeout (agent.turn_timeout_ms) is the final backstop.

MCP is unavailable on the KIRO_API_KEY path. The backend profile gate returns 403 under API-key authentication and disables MCP. StartSessionParams.MCPConfigPath has no effect, and --require-mcp-startup is unreachable. See MCP.

Required environment variables:

VariableRequiredDescription
KIRO_API_KEYYes (local mode)Headless credential. Requires a Kiro Pro, Pro+, or Power subscription. In SSH mode, the orchestrator injects it inline into the remote command.

MCP

MCP is inert under KIRO_API_KEY authentication. The backend GetProfile call returns HTTP 403, the CLI logs that MCP configuration could not be checked and defaults to disabled, and it prints Failed to retrieve MCP settings; MCP functionality disabled to stderr on every invocation.

With MCP disabled, a workspace mcp.json is not loaded, StartSessionParams.MCPConfigPath has no effect, and --require-mcp-startup exit 3 is unreachable. There is no per-launch --mcp-config flag on chat. The adapter passes no MCP flag and does not depend on MCP injection. This behavior is documented in the Kiro adapter research notes, which trace it to a profile-entitlement gate rather than a missing configuration.


Concurrency safety

The adapter is safe for concurrent use. One KiroAdapter instance serves all sessions. Per-session state is isolated in the opaque Session.Internal handle, which owns the launch target, the pass-through config, the resume flag, the per-turn stdout accumulator, and the fork-per-turn session.

RunTurn is safe to call concurrently for different sessions. Turns for a single session must be serialized; the orchestrator guarantees this.


Adapter registration

The adapter registers itself under kind "kiro" via an init function in internal/agent/kiro. Registration metadata declares:

PropertyValue
RequiresCommandtrue

The orchestrator’s preflight validation uses this metadata to require a non-empty agent.command field for agent.kind: kiro. Binary lookup happens during StartSession via exec.LookPath, with kiro-cli as the default command.


Key differences from other adapters

AspectClaude CodeCopilot CLICodexOpenCodeKiro
Kindclaude-codecopilot-clicodexopencodekiro
Default commandclaudecopilotcodex app-serveropencodekiro-cli
Subprocess modelNew process per turnNew process per turnPersistent process across turnsNew process per turn, plus an export subprocessNew process per turn
ProtocolCLI flags + JSONL stdoutCLI flags + JSONL stdoutJSON-RPC 2.0 over stdin/stdoutCLI flags + newline-delimited stdout envelopesCLI flags + plain-text stdout transcript
Headless outputStructured (stream-json)Structured (json)Structured (JSON-RPC notifications)Structured (--format json)Plain transcript, no structured stream
Output format flag--output-format stream-json--output-format jsonJSON-RPC notifications--format jsonNone
Session ID sourceUUID generated by adapterDiscovered from result eventThread ID from thread/startDiscovered from the first JSON envelopeNone; carries ResumeSessionID only
Resume mechanism--resume <UUID>--resume <sessionId> or --continuethread/resume or automatic within session--session <sessionID>--resume (cwd-scoped), after first success
Token accountingEvent stream, with result fallbackOutput tokens onlyturn/completed usage objectSeparate export subprocessNone (credits only, not tokens)
Model reportingFrom assistant eventsNot availableNot availableRecovered from export providerID/modelIDNot available (AgentEvent.Model empty)
Permission control--permission-mode or --dangerously-skip-permissions--autopilot + --no-ask-user + tool scopingapprovalPolicy and sandbox policy--dangerously-skip-permissions plus OPENCODE_PERMISSION--trust-all-tools or --trust-tools=<csv>
Inner turn limitclaude-code.max_turnscopilot-cli.max_autopilot_continuesNoneNone exposed by the adapterNone exposed by the adapter
Exit-code reliabilityStructured result event plus exitStructured result.exitCode plus exitJSON-RPC turn statusTerminal stdout error can still exit 0Exit 0 is ambiguous; success requires the credits trailer on stderr
Credential preflightNoneEnv vars + gh auth statusaccount/read over JSON-RPCNonekiro-cli whoami canary at session start
MCP availability--mcp-config sidecar--additional-mcp-config sidecardynamicTools on thread/startNone injected by the adapterInert under KIRO_API_KEY (profile gate disables)
AuthenticationANTHROPIC_API_KEY (+ Bedrock, Vertex)COPILOT_GITHUB_TOKEN / GH_TOKEN / GITHUB_TOKEN / gh authCODEX_API_KEY or cached Codex authOpenCode-managed provider authKIRO_API_KEY (Kiro Pro, Pro+, or Power)

External references


Related pages

Was this page helpful?