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.
| Field | Type | Default | Description |
|---|---|---|---|
kind | string | - | Must be "kiro" to select this adapter. |
command | string | kiro-cli | Path or name of the Kiro CLI binary. Resolved via exec.LookPath at session start. |
max_turns | integer | 20 | Maximum Sortie turns per worker session. The orchestrator calls RunTurn up to this many times, re-checking tracker state after each turn. |
max_sessions | integer | 0 (unlimited) | Maximum completed worker sessions per issue before the orchestrator stops retrying. 0 disables the budget. |
max_concurrent_agents | integer | 10 | Global concurrency limit across all issues. |
max_concurrent_agents_by_state | map | {} | Per-state concurrency limits. Keys are state names, lowercased for matching. Non-positive or non-numeric entries are silently ignored. |
turn_timeout_ms | integer | 3600000 (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_ms | integer | 5000 (5 seconds) | Timeout for startup and synchronous operations. |
stall_timeout_ms | integer | 300000 (5 minutes) | Maximum time between consecutive events before the orchestrator treats the turn as stalled. 0 or negative disables stall detection. |
max_retry_backoff_ms | integer | 300000 (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: 300000kiro 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.
| Field | CLI flag | Type | Default | Description |
|---|---|---|---|---|
model | --model | string | (CLI default) | Model identifier passed on every turn. Pinned per turn because the /model slash command is unavailable headless. |
trust_all_tools | --trust-all-tools | boolean | false | Auto-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 | --agent | string | (none) | Named Kiro context profile (custom agent). |
kiro:
model: claude-sonnet-4.6
trust_tools:
- read
- grep
- globtrust_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:
| Configuration | Argument emitted | Effect |
|---|---|---|
trust_all_tools: true | --trust-all-tools | Auto-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.
- 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 resolvescommandviaexec.LookPath, defaulting tokiro-cli. In SSH mode, it resolves the localsshbinary instead and stores the remote command for later use. - Local mode: runs the credential preflight (
checkCredential). ConfirmsKIRO_API_KEYis set, then runs akiro-cli whoamicanary. See authentication. - SSH mode: skips the credential preflight and injects
KIRO_API_KEYinline into the remote command, shell-quoted. See SSH remote execution. - Initializes per-session state: launch target, agent config, pass-through config, logger, the
ResumeSessionIDvalue assessionID, and a fresh per-turn stdout accumulator. - Constructs the
agentcore.ForkPerTurnSessionthat owns the subprocess lifecycle for this session. - Returns a
SessionwithIDset to the resume session ID, an emptyAgentPID, and the opaque session state.
Errors:
| Condition | Error kind |
|---|---|
| Empty or non-existent workspace path | invalid_workspace_cwd |
| Workspace path is not a directory | invalid_workspace_cwd |
| Agent command is empty or whitespace-only | agent_not_found |
Local kiro-cli binary not found in PATH | agent_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.
- Panics if
OnEventis nil. - Recovers the session state from
Session.Internal. Returnsresponse_errorif the type assertion fails. - Resets the per-turn stdout accumulator so each turn starts clean.
- Calls
forkSession.RunTurnwith the rendered prompt and theOnEventcallback.
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 content | Meaning |
|---|---|
â–¸ Credits: trailer | The 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 evidence | Exit reason | Error kind | Decided by |
|---|---|---|---|
Exit 0 with a â–¸ Credits: trailer on stderr | turn_completed | (none) | OnFinalize. Sets the resume flag for subsequent turns. |
Exit 0, empty stdout, no credits trailer, Authentication failed. on stderr | turn_failed | response_error | OnFinalize. Message: kiro authentication failed. |
| Exit 0, no credits trailer (any other case) | turn_failed | turn_failed | OnFinalize. Message: kiro exited without a credits trailer. |
| Any other non-zero exit | turn_failed | port_exit | OnFinalize. Message: kiro exited with a non-zero status. |
| Exit 127 (binary not found) | turn_failed | agent_not_found | Shared skeleton. |
| SIGTERM (143) or SIGKILL (137) | turn_cancelled | turn_cancelled | Shared skeleton. |
| Turn context cancelled | turn_cancelled | turn_cancelled | Shared skeleton. |
| stdout scanner error | turn_failed | port_exit | Shared 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.
| Turn | Resume 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
StartSessionresolves the localsshbinary. The agent command is stored for remote execution rather than resolved locally.- The credential preflight is skipped.
buildSSHRemoteCmdprependsKIRO_API_KEYto the remote command and shell-quotes the value, because OpenSSH drops the orchestrator’s local environment. WhenKIRO_API_KEYis empty, no prefix is added. RunTurnbuilds the per-turn argument list and wraps it withsshutil.BuildSSHArgs.- 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:
| Option | Value | Purpose |
|---|---|---|
StrictHostKeyChecking | Configurable (default: accept-new) | Host key verification policy. Set via worker.ssh_strict_host_key_checking. Allowed values: accept-new, yes, no. |
BatchMode | yes | Disables interactive prompts. |
ConnectTimeout | 30 | Connection timeout in seconds. |
ServerAliveInterval | 15 | Keepalive interval in seconds. |
ServerAliveCountMax | 3 | Number 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):
- Confirms
KIRO_API_KEYis set and non-empty. A missing key returnsresponse_error. - Runs a
kiro-cli whoamicanary with a 5-second timeout. A timeout or non-zero exit returnsresponse_error. - Inspects the canary output. The key is accepted only when the output contains the success marker
Authenticated with API keyand does not containAuthentication failed.. Otherwise the preflight returnsresponse_errorfor an invalid or expired key.
The preflight defends against two distinct failure shapes:
| Failure | Symptom without the preflight |
|---|---|
| No credential | Headless chat enters an interactive device-login flow and blocks indefinitely, because --no-interactive does not suppress login. |
| Invalid key | Headless 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.
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:
| Variable | Required | Description |
|---|---|---|
KIRO_API_KEY | Yes (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:
| Property | Value |
|---|---|
RequiresCommand | true |
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
| Aspect | Claude Code | Copilot CLI | Codex | OpenCode | Kiro |
|---|---|---|---|---|---|
| Kind | claude-code | copilot-cli | codex | opencode | kiro |
| Default command | claude | copilot | codex app-server | opencode | kiro-cli |
| Subprocess model | New process per turn | New process per turn | Persistent process across turns | New process per turn, plus an export subprocess | New process per turn |
| Protocol | CLI flags + JSONL stdout | CLI flags + JSONL stdout | JSON-RPC 2.0 over stdin/stdout | CLI flags + newline-delimited stdout envelopes | CLI flags + plain-text stdout transcript |
| Headless output | Structured (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 json | JSON-RPC notifications | --format json | None |
| Session ID source | UUID generated by adapter | Discovered from result event | Thread ID from thread/start | Discovered from the first JSON envelope | None; carries ResumeSessionID only |
| Resume mechanism | --resume <UUID> | --resume <sessionId> or --continue | thread/resume or automatic within session | --session <sessionID> | --resume (cwd-scoped), after first success |
| Token accounting | Event stream, with result fallback | Output tokens only | turn/completed usage object | Separate export subprocess | None (credits only, not tokens) |
| Model reporting | From assistant events | Not available | Not available | Recovered from export providerID/modelID | Not available (AgentEvent.Model empty) |
| Permission control | --permission-mode or --dangerously-skip-permissions | --autopilot + --no-ask-user + tool scoping | approvalPolicy and sandbox policy | --dangerously-skip-permissions plus OPENCODE_PERMISSION | --trust-all-tools or --trust-tools=<csv> |
| Inner turn limit | claude-code.max_turns | copilot-cli.max_autopilot_continues | None | None exposed by the adapter | None exposed by the adapter |
| Exit-code reliability | Structured result event plus exit | Structured result.exitCode plus exit | JSON-RPC turn status | Terminal stdout error can still exit 0 | Exit 0 is ambiguous; success requires the credits trailer on stderr |
| Credential preflight | None | Env vars + gh auth status | account/read over JSON-RPC | None | kiro-cli whoami canary at session start |
| MCP availability | --mcp-config sidecar | --additional-mcp-config sidecar | dynamicTools on thread/start | None injected by the adapter | Inert under KIRO_API_KEY (profile gate disables) |
| Authentication | ANTHROPIC_API_KEY (+ Bedrock, Vertex) | COPILOT_GITHUB_TOKEN / GH_TOKEN / GITHUB_TOKEN / gh auth | CODEX_API_KEY or cached Codex auth | OpenCode-managed provider auth | KIRO_API_KEY (Kiro Pro, Pro+, or Power) |
External references
- Kiro CLI documentation - official command reference
- Kiro CLI headless mode - the
--no-interactivepath this adapter launches - Migrating from Amazon Q - the
qtokiro-clirename and the configuration move to~/.kiro - Kiro CLI exit codes - the documented exit-code surface
- Kiro CLI built-in tools - the tool catalog scoped by
--trust-tools aws/amazon-q-developer-clion GitHub - the CLI source of record for the rebranded binary
Related pages
- WORKFLOW.md configuration reference - full
agentschema andkiroextension block - Environment variables reference -
KIRO_API_KEYand runtime environment behavior - Error reference - all agent error kinds with retry behavior
- How to control agent costs - time-based budgeting and concurrency limits, which matter most for Kiro
- How to scale agents with SSH - remote execution setup and host pool configuration
- How to write a prompt template - template variables, conditionals, and built-in functions
- State machine reference - orchestration states, turn lifecycle, and stall detection
Was this page helpful?