Full ~/.config/opencode/kiro-auth-plugin/kiro.json example and every supported option. See the
root README for the short version.
{
"auto_sync_kiro_cli": false,
"account_selection_strategy": "lowest-usage",
"distribute_across_processes": true,
"per_request_spread": false,
"quota_avoidance_enabled": true,
"quota_reserve_threshold": 0.95,
"stop_on_overage": true,
"overage_threshold": 0,
"default_region": "us-east-1",
"idc_start_url": "https://your-company.awsapps.com/start",
"idc_region": "us-east-1",
"rate_limit_retry_delay_ms": 5000,
"rate_limit_max_retries": 3,
"max_request_iterations": 20,
"sdk_response_timeout_enabled": false,
"sdk_response_timeout_ms": 300000,
"sdk_http_keep_alive": false,
"stream_event_timeout_enabled": false,
"request_timeout_ms": 120000,
"stream_buffer_until_complete": false,
"compaction_buffer_until_complete": true,
"stream_max_attempts": 3,
"stream_recovery_mode": "off",
"stream_recovery_reuse_conversation_id_across_accounts": false,
"token_expiry_buffer_ms": 300000,
"token_keepalive_enabled": false,
"token_keepalive_interval_ms": 600000,
"usage_sync_max_retries": 3,
"usage_tracking_enabled": true,
"auto_effort_mapping": true,
"enable_log_api_request": false,
"diagnostic_log_level": "off",
"log_retention_days": 7,
"log_max_total_size_mb": 512,
"log_compress_after_days": 1,
"log_segment_size_mb": 16
}New default keys are backfilled into an existing
kiro.jsonautomatically on load: when the plugin adds an option in a new version, it is appended to your file with its default value the next time the plugin loads. Backfill is additive only — it never changes, reorders, or removes keys you already set, never rewrites a file that is already complete, and leaves an unparseable file untouched.
The plugin config, logs, and refresh/keep-alive locks live under
~/.config/opencode/kiro-auth-plugin/ (%APPDATA%\opencode\kiro-auth-plugin\
on Windows). Existing safe files are migrated automatically once, so no manual
action is needed. The SQLite database remains at ~/.config/opencode/kiro.db
because moving a live database during an upgrade is unsafe.
auto_sync_kiro_cli: Automatically sync sessions from Kiro CLI (default:false).kiro-clistores only one token per auth method, so its auto-sync cannot represent multiple accounts and can overwrite a freshly-rotated plugin token with a stale one. Manualopencode auth loginper account is the supported multi-account path; enable this only if you rely onkiro-cli.account_selection_strategy: Account rotation strategy (default:lowest-usage). See the strategy table below.distribute_across_processes: Spread simultaneously-started OpenCode processes across different accounts using a DB-backed atomic counter (default:true). Set tofalseto restore the old behavior where every process starts from the first account. See Account distribution across processes below.per_request_spread: Re-pick the lowest-usage account on every single request instead of pinning to the process's assigned account (default:false). Overrides sticky pinning fromaccount_selection_strategy. See Account distribution across processes below.quota_avoidance_enabled: Softly avoid near-exhausted accounts when multiple accounts are registered (default:true). See Quota-aware account avoidance below.quota_reserve_threshold: Usage-ratio cutoff (0-1, default:0.95) above which an account is considered near-exhausted and gets soft-avoided. Only meaningful whenquota_avoidance_enabledistrue.stop_on_overage: Stop selecting accounts that have entered AWS paid overage (default:true). See Overage protection below.overage_threshold: Paid-overage invocations tolerated before stopping an account (default:0, meaning stop on any overage). Only meaningful whenstop_on_overageistrue.default_region: AWS region (us-east-1,us-west-2).idc_start_url: Default IAM Identity Center Start URL (e.g.https://your-company.awsapps.com/start). Leave unset/blank to default to AWS Builder ID.idc_region: IAM Identity Center (SSO OIDC) region (sso_region). Defaults tous-east-1.rate_limit_retry_delay_ms: Delay between rate limit retries (1000-60000ms).rate_limit_max_retries: Maximum retry attempts for rate limits (0-10).max_request_iterations: Maximum loop iterations to prevent hangs (10-1000).sdk_response_timeout_enabled: Opt into a fixed initial-response deadline coveringclient.send()and the first upstream stream event (default:false). It is disabled because a pending request is ambiguous: high-effort models can legitimately spend several minutes generating before their first event, and Windows sleep or network transitions can consume a wall-clock deadline. Caller cancellation still aborts the SDK request and releases the request queue. Override withKIRO_SDK_RESPONSE_TIMEOUT_ENABLED.sdk_response_timeout_ms: Fixed SDK response deadline whensdk_response_timeout_enabledistrue(30000-600000ms, default:300000). Override withKIRO_SDK_RESPONSE_TIMEOUT_MS.sdk_http_keep_alive: Reuse a completed SDK HTTP connection for a later request (default:false). The default gives every request a fresh socket to avoid Bun reusing a stale pooled connection. This does not serialize or cap active streams: the SDK still permits up to 50 concurrent sockets per client, and multiple OpenCode processes remain independent. The tradeoff is one additional TCP/TLS handshake per request. Set this totrueonly when connection reuse has proven stable in your runtime. Override withKIRO_SDK_HTTP_KEEP_ALIVE.stream_event_timeout_enabled: Opt into a fixed inactivity deadline between upstream stream events (default:false). It is disabled because high-effort models can legitimately compute for several minutes between events, so event silence alone cannot distinguish generation from a stalled connection. Caller cancellation and SDK transport errors remain active. Override withKIRO_STREAM_EVENT_TIMEOUT_ENABLED.request_timeout_ms: Stream-event inactivity deadline whenstream_event_timeout_enabledistrue(30000-600000ms, default:120000). It starts only after the first raw event and is paused while the downstream consumer is not pulling. Override withKIRO_REQUEST_TIMEOUT_MS.stream_buffer_until_complete: Consume and validate the complete Kiro event stream before exposing any output to OpenCode (default:false). Enable this when long-running agent tasks are more important than live token display. If the upstream connection resets after producing partial content or a partial tool call, the failed attempt remains private to the plugin and can be safely retried without duplicating downstream content or executing a tool twice. Successful responses are still returned as OpenAI-compatible SSE, but their chunks arrive only after Kiro finishes the complete response. Each failed upstream attempt may still consume Kiro quota. Because the response is validated beforeRequestHandler.handle()returns, this mode also holds the process-local Kiro request queue until the upstream response completes. Override withKIRO_STREAM_BUFFER_UNTIL_COMPLETE.compaction_buffer_until_complete: Atomically buffer only requests that OpenCode marks withagent: "compaction"(default:true). The plugin consumes its private request marker before constructing the AWS request. Failed stream attempts expose no partial summary bytes, while ordinary chat remains live unlessstream_buffer_until_completeis also enabled. Set this tofalseonly to restore streaming compaction behavior. Override withKIRO_COMPACTION_BUFFER_UNTIL_COMPLETE.stream_max_attempts: Maximum complete event-stream attempts (1-10, default:3). This caps the total SDK sends for one inbound provider request — the initial send plus any pre-output stream retries — so3means at most threegenerateAssistantResponsecalls for that request. It is distinct frommax_request_iterations, which bounds the overall per-request loop that also covers HTTP-error retries and account switches. Both budgets apply at the same time: a stream retry is refused oncestream_max_attemptsis reached even if loop iterations remain, and the loop still stops atmax_request_iterationsregardless of remaining stream attempts. In normal live-stream mode, retries remain limited to failures before semantic output. With either global buffering or atomic compaction buffering enabled, this limit also covers failures after upstream output because none of that attempt has reached OpenCode yet. Override withKIRO_STREAM_MAX_ATTEMPTS.stream_recovery_mode: Controls live recovery after semantic output (default:off).reasoning_restartis eligible only when no visible text, tool call, or raw tool intent reached OpenCode;exact_replayadditionally permits visible text or tool calls but byte-exactly matches reasoning, visible text, and normalized tool calls. A replay publishes nothing until the entire delivered prefix matches, and divergence or early end fails closed. Every recovery attempt is a real SDK send and consumes quota. Override withKIRO_STREAM_RECOVERY_MODE.stream_recovery_reuse_conversation_id_across_accounts: Recovery attempts on the same account always reuse one transformed semantic request. After an account switch, the defaultfalseperforms a fresh transform and uses a new KiroconversationId; unchanged normalized semantics remain correlated by the same recovery group. Set this experimental option totrueonly to reuse the originalconversationIdwith a different account envelope. This has not been validated by a real Kiro cross-account A/B and does not guarantee deterministic model output. Override withKIRO_STREAM_RECOVERY_REUSE_CONVERSATION_ID_ACROSS_ACCOUNTS.token_expiry_buffer_ms: Token refresh buffer time (30000-300000ms, default:300000). An access token within this window of expiry is treated as expired and refreshed on next use.token_keepalive_enabled: Opt-in background keep-alive that proactively rotates idle accounts' tokens before they expire (default:false). Recommended for multi-account setups or accounts left idle for long stretches, so a rarely-used account's token stays fresh instead of only refreshing on its next request. It only runs while OpenCode is running (it is an in-process timer, not an OS daemon) and cannot extend past the AWS IAM Identity Center session ceiling — an expired IdC session still requires a fullopencode auth login. See Token keep-alive below.token_keepalive_interval_ms: How often the keep-alive scan runs (60000-3600000ms, default:600000= 10 minutes). Only meaningful whentoken_keepalive_enabledistrue.usage_sync_max_retries: Retry attempts for usage sync (0-5, default:3).auth_server_port_start: Legacy/ignored (no local auth server).auth_server_port_range: Legacy/ignored (no local auth server).usage_tracking_enabled: Enable usage tracking and toast notifications (default:true). When enabled, the plugin refreshes each account's used quota intokiro.dbafter requests (60s cooldown per account) and shows the auth-menu label, startup toast, and ≥90% warning toast described in Reading usage below.auto_effort_mapping: Automatically map OpenCode thinking budgets to Kiro effort levels for supported models (default:true). See docs/MODELS.md for the budget-to-effort table.enable_log_api_request: Enable detailed API request logging (default:false). Keep this off unless you are actively diagnosing a request because records may contain prompt and tool payloads.diagnostic_log_level: Emit privacy-safe request-shape, correlation, and stream-terminal diagnostics (off|basic|verbose, default:off). This is independent fromenable_log_api_request: evenverboserecords only counts, bounded role sequences, enums, booleans, UUIDs, and truncated SHA-256 hashes. It never records prompt/reasoning text, tool names, arguments/results, signatures, account data, email, ARN, or raw session/message IDs. Override withKIRO_DIAGNOSTIC_LOG_LEVEL.log_retention_days: Delete archived and detailed logs older than this many days (1-365, default:7).log_max_total_size_mb: Maximum combined size of managed logs (16-102400 MiB, default:512). The oldest closed logs are removed first; active files are protected and bounded by rotation.log_compress_after_days: Gzip an inactive API log segment after this many days (1-30, default:1). Segments closed by size or date rotation are compressed immediately.log_segment_size_mb: Rotateplugin.logand detailed API log segments at this size (1-256 MiB, default:16).enable_log_effort_debug: Log each request's inbound body shape (top-level keys and reasoning-related fields only, no message content) and the resolved Kiro effort (default:false). Independent fromenable_log_api_request.
Detailed API logging no longer creates a request and response JSON file for
every call. Records are appended as compact NDJSON to a process-specific
segment. Closed segments and rotated plugin.log files are gzip-compressed in
the background. Maintenance normally runs after startup/log activity and then
at most once every 15 minutes; a large legacy backlog is drained through
bounded follow-up batches.
Three independent limits prevent unbounded growth:
- closed logs older than
log_retention_daysare deleted; - oldest closed logs are deleted when the directory exceeds
log_max_total_size_mb; - a hard 1000-file safety limit gradually removes legacy per-request JSON files in batches, so upgrading a directory with hundreds of thousands of files does not block streaming.
enable_log_api_request remains off by default. When it is off, routine
successful request payloads are not recorded. Failed upstream requests retain
only a sanitized request/response pair: the request body is null, the account
is a process-local alias, and no prompt, tool payload, email, raw account ID,
token, or profileArn is written. Enabling the option records the original
diagnostic request shape and may include prompt/tool payloads and account email.
All log settings can also be overridden with KIRO_LOG_RETENTION_DAYS,
KIRO_LOG_MAX_TOTAL_SIZE_MB, KIRO_LOG_COMPRESS_AFTER_DAYS, and
KIRO_LOG_SEGMENT_SIZE_MB.
diagnostic_log_level is intended for the specific failure class where an
assistant says it will perform another action but the persisted turn ends
without a tool call. It changes logging only; it does not change request
conversion, tool parsing, recovery, buffering, retries, account selection, or
stream concurrency.
| Level | Additional records |
|---|---|
off |
No diagnostic correlation or request-shape records. Existing stream health records remain unchanged. |
basic |
Per-request trace, hashed OpenCode identities, role/tool counts, current-turn kind, and terminal provenance. |
verbose |
Everything in basic, plus bounded role sequences, marker/repair counts, tool-set hashes, and image/reasoning-envelope counts. |
When enabled, the chat.headers hook adds a random diagnosticTraceId and
one-way 16-hex SHA-256 prefixes for the OpenCode session, agent, and message
identities. The plugin validates and consumes those private headers at its
request boundary; they are not copied into the CodeWhisperer SDK request.
Hashes support cross-log correlation without writing raw identities, but they
are still linkable metadata. Return the level to off after collecting a
reproduction.
Kiro request shape diagnostics is written once after the inbound body has
been transformed to the Kiro wire shape. basic fields compare input message,
role, tool-use/result counts with history/current-message counts on the wire.
verbose adds:
- the last 64 role codes for input and wire messages;
- whether wire history alternates user/assistant roles;
- counts for empty assistant turns, synthetic marker hits, orphan repairs, flattened orphan results, and reasoning envelopes;
- hashes of input and wire tool-name sets, never the names themselves;
- inferred-tool and current-image counts.
The record is an observation of the actual transformed request. It does not infer intent from assistant prose and cannot prove that a model should have called a tool.
Stream health is tracked in plugin.log independently from
enable_log_api_request, so you can measure upstream stream failures without
recording prompt or tool payloads. A start record and a terminal record bound
each inbound streaming request; attempt-level records carry the details needed
to explain recovery.
Kiro stream request started (INFO) is written exactly once per inbound
streaming request, unconditionally — it does not depend on
enable_log_api_request, and non-streaming requests are not recorded. Fields:
recoveryGroupId, semanticFingerprint, wireConversationId,
conversationId, requestKind, sameSemanticAsInitial,
sameConversationIdAsInitial, model, effectiveModel, effort,
streamDeliveryMode, and processId. This is the denominator every
stream-failure rate is measured against, so an account switch or HTTP-error
retry inside the same inbound request still produces only one record. The
string is a grep target for log-analysis scripts; it is exported as
STREAM_REQUEST_STARTED_LOG from src/core/request/request-handler.ts and is
treated as a stable contract.
With diagnostic_log_level enabled, start, attempt, warning, failure, and
terminal records also carry diagnosticTraceId, sessionHash, agentHash,
messageHash, and diagnosticLogLevel. Kiro stream attempt started is
then emitted once for every actual SDK stream attempt, including recovery
attempts. This distinguishes “the model said it would retry” from a real second
SDK send.
Kiro stream ended without completion metadata (WARN, exported as
STREAM_MISSING_COMPLETION_LOG, outcome: 'clean_eof_without_completion_metadata') fires when the upstream event stream
ends cleanly but never sent completion metadata. The response still completes
normally, exactly as before. This endpoint commonly omits completion metadata,
so the warning is transport-shape telemetry, not evidence of truncation and
never triggers recovery by itself.
Attempt-level stream records — the clean-EOF warning above and each stream
failure outcome (retrying, exhausted, terminated_after_output,
ignored_after_completion_metadata, recovered) — carry these shared fields:
| Field | Meaning |
|---|---|
recoveryGroupId, semanticFingerprint, wireConversationId |
Stable recovery correlation, normalized semantics, and actual Kiro wire ID |
requestKind, sameSemanticAsInitial |
Request classification and whether the transformed semantics drifted |
sameConversationIdAsInitial |
Whether this attempt reused the first attempt's Kiro conversation ID |
model, effectiveModel, effort, region |
Requested and resolved model settings and region |
accountAlias |
Process-local alias; raw account IDs and email are omitted |
streamAttempt, maxStreamAttempts |
Attempt number and the stream_max_attempts cap |
streamDeliveryMode |
buffered or live, including compaction-only atomic buffering |
sdkHttpKeepAlive |
The effective sdk_http_keep_alive value |
processId, bunVersion |
OS process id and the Bun runtime version |
upstreamEventCount, streamElapsedMs |
Raw upstream events and wall time for this attempt |
emittedReasoningChars, emittedVisibleChars |
Character counts already observed in the two text channels |
emittedToolCount, sawToolIntent |
Published tool count and any complete, partial, or discarded upstream tool intent |
Kiro stream request terminal (INFO, exported as
STREAM_TERMINAL_LOG) is written exactly once for every started stream,
including success, failure, cancellation, and recovery. Recovery terminals add
attemptsUsed, accountsTried, accountAliases, initialFailure,
finalFailure, recovered, quotaRelevant, and terminalSource. Correlate it
with the start and attempt records using recoveryGroupId; do not infer a root
cause from the UI's generic error text alone.
At basic or verbose, terminal records add:
terminalProvenance:clean_eof,completion_metadata,semantic_truncation,caller_abort,recovery_exhausted,upstream_error,processing_error, orunknown;downstreamFinishReason:stoportool-callsonly for a clean end, otherwisenull;downstreamFinishReasonProvenance:synthesized_from_tool_countfor a clean end, otherwisenull.
Kiro commonly ends cleanly without completion metadata. In that case the
plugin's OpenAI-compatible terminal chunk is derived from the transformed tool
count: zero tools becomes stop, one or more becomes tool-calls. This field
makes that provenance explicit; it is not evidence that Kiro sent a native
stop decision and must not be used to guess tool intent from natural-language
text.
The last four are lengths, counts, and a boolean only. No reasoning text, reply text, or tool arguments are ever written to these records — a character count cannot reconstruct content. They exist so a failed attempt can be classified after the fact: an attempt with zero emitted characters, zero tool calls, and no tool intent is safe to reason about differently from one that already put output in front of you.
If you run several OpenCode processes at once (multiple terminals, editor sessions, or CI jobs), each process previously started with its own process-local selection cursor at index 0 — so with no cross-process coordination, every process's first pick landed on the same account, piling all their traffic onto it while other registered accounts sat idle.
distribute_across_processes (default true) fixes this with an atomic
counter stored in the plugin_meta table of kiro.db. On startup, each
process claims the next counter value and uses it as its starting offset, so
concurrently-launched processes begin on different accounts instead of
converging on the first one. Each strategy applies that offset differently:
sticky does a circular scan over the stable, id-sorted account list
starting at the offset, picking the first selectable account at or after it
(wrapping around without collapsing back to index 0); lowest-usage uses the
offset only as a deterministic tie-breaker over that same id-sorted order,
never overriding an account with genuinely lower usage; and round-robin
starts its rotation cursor at the offset and cycles through the current pool
of available accounts in their existing order. If the counter can't be read
for any reason, the process falls back to offset 0 — startup is never blocked
by this. Set it to false to restore the old single-offset behavior.
per_request_spread (default false) is a separate, opt-in trade-off: with
it on, every request re-picks whichever account currently has the lowest
usage, rather than staying pinned to the account the process was assigned.
This maximizes spread across accounts but gives up the conversation affinity
that a pinned/sticky account provides. Leave it off unless you specifically
want per-request rebalancing over sticky conversation pinning.
An internal benchmark (4 concurrent workers × 5 requests each, real
generateAssistantResponse calls, us-east-1, claude-opus-4-8) puts the
trade-off in numbers. Both distribution modes reach the same throughput —
roughly 1.6× the single-account baseline (~34 s → ~21 s wall time) — because
that gain comes from spreading load off one account, not from re-picking per
request. What differs is the tail: leaving per_request_spread off (the
default distribute_across_processes-only mode) kept p95 latency lower
(~3.8 s vs ~5.7 s), while turning it on spread quota consumption more evenly
across accounts (a 10/10 split vs 15/5). So: keep it off if you care about
stable tail latency and conversation pinning; turn it on if even quota
draw-down across accounts matters more to you than p95. These are small-sample,
directional figures, not an SLA.
Both keys are additive — see the automatic backfill note above; if you
already have a ~/.config/opencode/kiro-auth-plugin/kiro.json, these are
added with their default values the next time the plugin loads. No manual
edit is required.
Kiro access tokens last ~1 hour and are normally refreshed on demand — the next request that needs an expired token triggers a refresh (the same model Kiro's own CLI uses). For a single active account that is enough. But with multiple accounts the rotation strategy may leave some accounts idle for long stretches, so an idle account's token only gets refreshed the next time it happens to be picked — which can be much later.
Set token_keepalive_enabled: true to run a lightweight background scan every
token_keepalive_interval_ms (default 10 minutes) that proactively refreshes any
healthy account whose token is within token_expiry_buffer_ms of expiry. This
keeps idle-account tokens rotating so they are ready when selected.
Important properties:
- In-process only. The scan runs while OpenCode is running. There is no
OS-level daemon; nothing refreshes while OpenCode is closed. This matches how
kiro-cliand the Kiro IDE behave (on-demand / while-open, no background daemon). - Leader-elected. If several OpenCode processes are open, a file lock ensures only one runs the scan, so accounts are not double-refreshed.
- Bounded by the IdC session. AWS IAM Identity Center caps the session
(commonly 8 hours, up to 90 days for Kiro). Once that ceiling is hit even a
valid refresh token fails and you must run
opencode auth loginagain — no keep-alive can extend past it. - Default off. Enable it explicitly; recommended for multi-account or long-idle setups.
Kiro's reasoning depth is controlled by a global effort setting in
kiro.json, not by OpenCode's per-agent thinking level. Read the
limitation below before
trying to give different agents different reasoning depths.
effort(low | medium | high | xhigh | max, optional): sets Kiro's reasoning effort for every request sent through the plugin. Leave it unset to fall back to automatic budget-based mapping (below), ormediumfor thinking-enabled requests with no budget set.auto_effort_mapping(defaulttrue): wheneffortisn't set, this maps OpenCode's thinking budget (thinkingConfig.thinkingBudgeton a-thinkingmodel variant) to a Kiro effort level automatically. See the budget table in docs/MODELS.md. Set tofalseto disable the mapping and always fall back tomediumunlesseffortis explicit.
Effort only changes behavior on effort-capable models:
claude-opus-4-5,claude-opus-4-6,claude-opus-4-6-1m,claude-opus-4-7,claude-opus-4-8,claude-opus-5claude-sonnet-4-5,claude-sonnet-4-5-1m,claude-sonnet-4-6,claude-sonnet-4-6-1m,claude-sonnet-5gpt-5.6-sol,gpt-5.6-terra,gpt-5.6-luna
xhigh is honored on claude-opus-4-7, claude-opus-4-8,
claude-opus-5, claude-sonnet-5, and the GPT 5.6 family; on other
effort-capable models it's clamped down to max. Any model outside this list
(Haiku, the open-weight models, etc.) ignores effort entirely.
Model ids ending in -thinking (e.g. claude-opus-4-8-thinking) trigger
Kiro's reasoning mode directly, independent of the global effort key.
Their thinkingConfig.thinkingBudget variant maps to an effort level
through the same budget table (default medium when no budget is set). See
docs/MODELS.md for the full variant catalog.
Set enable_log_effort_debug (default false) to log each request's
inbound body shape (top-level keys and reasoning-related fields only, never
message content) plus the effort level the plugin resolved, to
~/.config/opencode/kiro-auth-plugin/logs/plugin.log. This is independent from
enable_log_api_request.
OpenCode lets you set a thinking/reasoning level per agent, either with
--variant high|low|max on the CLI or a per-agent variant in
oh-my-openagent.json. This plugin cannot see that setting. Capturing
real inbound request bodies with --variant high and --variant low
produced byte-identical payloads: a plain OpenAI-style
{model, max_tokens, messages, ...} body with no reasoningEffort,
reasoning, or thinkingConfig field anywhere. OpenCode's orchestration
layer consumes the variant upstream and never forwards it to this plugin's
custom fetch.
In practice:
- You can't give different agents different Kiro effort levels through OpenCode's per-agent variant mechanism.
- Use the global
effortkey inkiro.json(applies to every request across every agent), or pin an agent to a-thinkingmodel id with an explicit budget inprovider.kiro-auth.models, to control effort. - Genuinely per-agent effort would need to be implemented at the OpenCode/omo orchestration layer, for example by mapping each agent to a distinct model id (model choice is forwarded), not inside this plugin.
Reasoning-capable Kiro models (Claude Opus 4.x, and other reasoning-capable
Kiro models) stream their chain-of-thought through a distinct event,
separate from the final answer text. The plugin picks that event up and
surfaces it as OpenCode's own reasoning block (reasoning_content), shown
as "Thought: <duration>" plus a collapsible reasoning section above the
final reply, instead of dropping it or merging it into the visible answer.
This is separate from the effort setting above:
- Reasoning is emitted by default for reasoning models, regardless of
effortorauto_effort_mapping. effortonly scales reasoning depth (how much the model thinks), not whether reasoning is shown. Even atloweffort, any reasoning the model produces still streams into the reasoning block.- No config needed. There's no toggle for this — reasoning display is automatic for any model/request that has reasoning content to stream.
account_selection_strategy controls how the plugin picks which stored Kiro
account handles the next request. It only matters once you have more than
one account registered (see the root README
for how accounts are added).
| Value | Behavior | Default |
|---|---|---|
lowest-usage |
Picks the healthy account with the lowest used quota on every request. Maximizes combined quota across accounts and keeps usage balanced. | ✅ |
round-robin |
Cycles through healthy accounts in order, one request each. | |
sticky |
Always uses the first account; only switches away when that account becomes unhealthy (rate-limited/403). |
Regardless of strategy, failover is automatic: an unhealthy account is skipped in favor of the next healthy one. If all accounts are rate-limited, the plugin waits the minimum reset time and retries. A circuit breaker trips after 10 consecutive selection failures.
{
"account_selection_strategy": "round-robin",
"token_keepalive_enabled": true,
"usage_tracking_enabled": true,
"usage_sync_max_retries": 3,
"default_region": "us-east-1"
}With this config, add two or more accounts via opencode auth login per
account, and the plugin cycles through them on each request instead of always
favoring the account with the most quota left. token_keepalive_enabled is
included here because round-robin can leave individual accounts idle between
turns; see Token keep-alive.
quota_avoidance_enabled and quota_reserve_threshold add a soft layer on
top of account_selection_strategy. It only kicks in once you have two or
more accounts registered; with a single account it's bypassed entirely and
that account is used normally (only the usual health/rate-limit checks
apply).
With multiple accounts, the plugin splits them into two tiers on each
request: accounts whose usage ratio (usedCount / limitCount) is below
quota_reserve_threshold ("ample" tier) and accounts at or above it
("near-exhausted" tier). Whatever account_selection_strategy you've
configured (sticky, round-robin, or lowest-usage) then runs within the
ample tier first, so accounts with room are preferred before ones sitting
near their limit. Accounts with an unknown quota (limitCount of 0, e.g.
FEATURE_NOT_SUPPORTED) are treated as having a 0 ratio and are never
avoided.
Drain fallback: if every account is at or above the threshold, avoidance
doesn't block requests. The plugin falls back to using the near-exhausted
tier and keeps draining remaining quota normally until an account actually
returns a real 402 Quota error, at which point the existing hard
account-switch takes over. There's no starvation.
This is a soft, proactive layer only. It does not change any of the existing hard behaviors:
- A real
402 Quotaerror still hard-switches to the next account. - A
429rate-limit response still triggers the existing rate-limit handling and account switch. - The ≥90%-usage warning toast (see Reading usage) still fires the same as before.
quota_reserve_threshold just tells the account selector to prefer accounts
with more headroom before any of those hard limits are hit.
Env overrides: KIRO_QUOTA_AVOIDANCE_ENABLED (boolean),
KIRO_QUOTA_RESERVE_THRESHOLD (number, 0-1).
AWS Kiro allows paid overage after the free quota is exhausted: the usage API
can return HTTP 200 with currentOverages > 0, and AWS bills those extra
invocations at $0.04 per invocation. By default this plugin treats that
signal as a hard selection stop so it does not keep spending money silently.
With stop_on_overage: true and overage_threshold: 0, any account whose
latest usage sync reports paid overage is excluded from selection. If every
otherwise-usable account is blocked by overage, the plugin throws a hard-stop
error instead of sleeping or retrying. A clean account that is merely
rate-limited still takes precedence and follows the normal wait path.
To intentionally continue with paid overage, set:
{
"stop_on_overage": false
}Recovery is automatic after AWS resets the monthly quota: the next successful
usage sync records currentOverages: 0, and the account becomes selectable
again.
Removing an account (opencode auth login → kiro-auth → "Remove a Kiro
account (N stored)" → pick one) deletes it from kiro.db and records its
account id in a removed_accounts table — a tombstone.
That tombstone matters because of auto_sync_kiro_cli. With auto-sync on
(the default), the plugin re-scans your local kiro-cli database on every
auth init and imports whatever sessions it finds there. Before the
tombstone existed, this meant a removed account would simply get re-imported
on the next startup if it was still present in kiro-cli's own store — the
exact bug reported with recurring idc-placeholder+...@awsapps.local
accounts. Now syncFromKiroCli checks the tombstone first and skips any
account id it lists, so:
auto_sync_kiro_cli: truewill not revive a removed account. Once removed, it stays removed across restarts and every subsequent sync, no matter how many times auto-sync runs.- Re-login clears the tombstone. If you deliberately want that account
back, run
opencode auth loginand log in with it again (Builder ID or IAM Identity Center, matching the identity you removed). That login clears the tombstone entry and the account is stored normally again — auto-sync will also pick it up fromkiro-cliafterward if it's still there. - This only affects this plugin's own
kiro.db. Removing an account here does not touchkiro-cli's own credential store (data.sqlite3). If you also wantkiro-cliitself to forget that identity, log it out there directly (kiro-cli logoutor equivalent) — that's a separate, unrelated store from this plugin's tombstone.
The plugin surfaces live usage (usedCount/limitCount per account) in the
opencode auth login menu label, a startup toast, and a ≥90%-usage warning
toast — see the root README for details. There
is no persistent status-bar widget. To check usage at any time without
restarting OpenCode, query kiro.db directly:
python3 -c "import sqlite3;r=sqlite3.connect('$HOME/.config/opencode/kiro.db').execute('SELECT email,used_count,limit_count FROM accounts').fetchall();[print(f'{e}: {u}/{l} (left {l-u})') for e,u,l in r]"On Windows, replace the path with %APPDATA%\opencode\kiro.db.