@mariocairone/paperclip-kiro-adapter
v0.3.0
Published
ACP-first Paperclip adapter for Kiro CLI
Maintainers
Readme
Paperclip Kiro ACP adapter
ACP-first Paperclip adapter for Kiro CLI.
- Adapter type:
kiro_acp - Display name: Kiro ACP
- Package:
@mariocairone/paperclip-kiro-adapter0.3.0(public) - Execution: local Kiro CLI only; remote execution targets and legacy remote transports fail closed before host probes or filesystem/process work
Requirements
- Node.js
>=24.11.0(required by pinned@paperclipai/adapter-utils2026.831.1);.nvmrcselects the Node 26 line used for local validation - Paperclip external-adapter support
kiro-cliavailable to the Paperclip server user- A completed Kiro login for that user
- Kiro CLI with
kiro-cli acpfor the ACP lane
kiro-cli login --use-device-flow
kiro-cli acp --helpBuild a local checkout with:
npm ci
npm run buildThis package is published publicly on npm as @mariocairone/paperclip-kiro-adapter. A local checkout can also be installed through Paperclip's local-adapter path for development and validation.
Architecture
execute() resolves one of two lanes:
- ACP lane (default):
@paperclipai/adapter-utils/acpx-enginestartskiro-cli acpover stdio. Session identity and tool events come from typed ACP/acpx metadata. No terminal transcript parsing and nochat --list-sessionsheuristic are used. - CLI compatibility lane: the original
kiro-cli chat --no-interactivewrapper remains available. It preserves prompt assembly, profile handling, output sanitization, timeout behavior, session lookup/resume, loopback API access, runtime MCP, skills, and base-agent inheritance. Its per-run profile uses the shared atomic mode-0600writer and is removed after every post-write success or failure.
Both lanes are local-only. An executionTarget with kind: "remote" or a legacy remote execution transport is rejected before capability probing, profile creation, filesystem access, or process launch. Environment tests return the same restriction as a structured failure rather than probing the Paperclip host.
Engine resolution
| engine | Behavior |
|---|---|
| auto (default) | Use ACP. Fall back to CLI only when kiro-cli acp --help explicitly reports that the acp subcommand is unavailable. Missing binaries, auth failures, timeouts, and ambiguous probe failures do not trigger fallback. |
| acp | Require ACP. A missing subcommand fails closed with no CLI fallback. |
| cli | Always use the compatibility wrapper. |
The capability probe is read-only, cached for five minutes, and starts no ACP session.
ACP profile lifecycle
Kiro receives trusted system context through an agent profile. The ACP profile:
- has a deterministic name derived from the Paperclip agent ID;
- is written into the worktree's
.kiro/agentswhen the repository ignores that path, so the profile shares the lifetime of the worktree Paperclip created; otherwise into$KIRO_HOME/agents. The check queries the file the adapter would write, because a.kiro/directory pattern does not match the bare path before the directory exists. A profile carries the full system prompt, so it is never written where it would appear as untracked work; - is written atomically with mode
0600; - is rewritten per heartbeat and fingerprinted into the acpx session identity, so persona, instructions, skills, MCP, model, or tool-policy changes start a compatible new ACP session;
- is retained because a warm ACP server outlives one heartbeat;
- is cleaned only by a safe sweep restricted to regular, adapter-owned
paperclip-*.jsonfiles. Other profiles of the same agent are collected after 30 minutes, other agents' profiles after 24 hours. Neither window is zero: a concurrent run of the same agent with a different effective configuration owns its own profile path and rewrites it every heartbeat, so a recent file is treated as possibly in use. Foreign files and symlinks are untouched.
The trusted system prompt is base persona + managed instructions. Assigned skills are delivered as skill:// resources instead, and per-wake Paperclip context remains in the acpx turn prompt.
Where the Kiro profile comes from
The profile is resolved in this order:
kiro-agent.json(orkiro-agent.md) in the agent's Paperclip instructions bundle.baseAgenton disk, for installations configured that way.- A minimal isolated default, modelled on Kiro's own
agent_config.json.examplewith one deliberate difference:includeMcpJsonisfalse. An agent with no configuration must not inherit the machine's global MCP servers.
A malformed bundled profile fails the run instead of silently falling back, so a typo is visible instead of quietly changing which tools an agent has.
The bundled profile
Paperclip's instructions bundle accepts arbitrary files next to AGENTS.md and exports them with the company, so the complete Kiro profile can live with the agent record: per-role, editable where the instructions are edited, and versioned in a company export. There is no file picker and no path to configure — the adapter reads the file from the bundle directory Paperclip already passes as instructionsRootPath:
<paperclip-data>/companies/<companyId>/agents/<agentId>/instructions/
├── AGENTS.md # role instructions (bundle entry file)
└── kiro-agent.json # this agent's Kiro profileThe file is a Kiro agent profile, so it takes Kiro's own fields rather than adapter-specific ones — prompt, model, tools, allowedTools, mcpServers, resources, toolAliases, toolsSettings, includeMcpJson, requireMcpStartup. A new Kiro field works without an adapter change. Two fields are read but not used at run time: name, because the adapter derives the run profile name from the agent id and content fingerprint, and description, which is useful to say what the profile is for.
{
"description": "Kiro tool substrate for this role",
"prompt": "Shared operating context for the role.",
"tools": ["*", "delegate", "@engineering-memory"],
"allowedTools": ["*", "delegate", "@engineering-memory"],
"mcpServers": {
"engineering-memory": {
"command": "/usr/local/bin/basic-memory",
"args": ["mcp", "--project", "my-project"]
}
},
"resources": ["file://AGENTS.md", "file://README*"],
"includeMcpJson": false,
"requireMcpStartup": true
}Write or replace it through the Paperclip API or CLI, the same way AGENTS.md is managed:
# read
curl "$PAPERCLIP/api/agents/$AGENT_ID/instructions-bundle/file?path=kiro-agent.json"
# write
curl -X PUT "$PAPERCLIP/api/agents/$AGENT_ID/instructions-bundle/file" \
-H 'Content-Type: application/json' \
-d "$(jq -n --arg c "$(cat kiro-agent.json)" '{path:"kiro-agent.json", content:$c}')"
# remove, falling back to baseAgent or the minimal default
curl -X DELETE "$PAPERCLIP/api/agents/$AGENT_ID/instructions-bundle/file?path=kiro-agent.json"The effective run prompt is bundled profile prompt + Paperclip role instructions. Assigned skills are not part of it; they arrive as resources.
Skills as resources
Assigned Paperclip skills become one skill://<runtime path>/SKILL.md resource each, appended to the profile's own resources. Kiro loads a skill:// resource on demand — name and description enter the prompt, the body only on use — so a role can carry long skills without paying for them on every run, and a skill's bundled references/ stay reachable because the agent knows the directory. Selection remains per-agent: the runtime paths come from Paperclip's paperclipRuntimeSkills filtered by that agent's desiredSkills. An assigned skill that Paperclip did not materialize is reported as a run warning. The CLI compatibility lane keeps inlining skills, since it builds a single prompt string and has no resource list.
Base agents
baseAgent is the on-disk fallback, used only when the agent has no bundled profile. It resolves, in order:
<cwd>/.kiro/agents/<name>.mdor.json$KIRO_HOME/agents/<name>.mdor.json~/.kiro/agents/<name>.mdor.jsonwhenKIRO_HOMEis unset
Markdown frontmatter and JSON profiles inherit:
promptmodeltoolsandallowedToolsmcpServersresourcestoolAliasestoolsSettingsincludeMcpJsonrequireMcpStartup
With Paperclip model: auto, the base model wins. An explicit Paperclip model wins over the base model.
MCP
MCP servers are combined from Kiro's global/workspace mcp.json, the base agent, and Paperclip runtime connections. Every server is added to the Kiro tools/allowedTools list as @name; "*" alone covers only built-in tools. Runtime names are sanitized. Base-agent MCP definitions win collisions, followed by existing Kiro MCP definitions; runtime connections cannot replace either. Bearer tokens are written only to the mode-0600 profile and are never logged.
requireMcpStartup: true fails before launch if no MCP server is configured and is copied to the Kiro profile for Kiro versions that support that field.
Configuration
Paperclip already renders command, model, standard thinking effort, cwd, instructions, prompt template, and extraArgs. Adapter-specific fields are:
| Field | Default | Description |
|---|---:|---|
| engine | auto | auto, acp, or cli; semantics above |
| baseAgent | — | Kiro agent on disk, used only when the agent has no kiro-agent.json in its Paperclip instructions bundle |
| kiroHome | — | KIRO_HOME for this agent. Kiro resolves agents, skills, steering and settings against it, so a dedicated directory keeps the operator's personal skill library out of agent runs and generated profiles out of the personal home. Wins over an env.KIRO_HOME binding; falls back to it, then to the user's home |
| effort | — | Standard Paperclip thinking-effort field. The adapter accepts low, medium, high, xhigh, and max at runtime; see the UI compatibility note below. |
| trustTools | unset | Comma-separated string or string array for --trust-tools; unset uses --trust-all-tools; an explicit empty list emits no Kiro trust flag |
| requireMcpStartup | false | Require configured MCP and request Kiro profile startup enforcement |
| preferConfiguredCwd | false | Make configured cwd override Paperclip's workspace |
| permissionMode | approve-all | acpx client policy: approve-all, approve-reads, or deny-all |
| nonInteractivePermissions | deny | deny or fail |
| timeoutSec | 0 | ACP/CLI run timeout; 0 means no adapter timeout |
| graceSec | 20 | CLI fallback SIGTERM grace period |
| logLineLimit | 2000 | CLI fallback log-line truncation; 0 disables it |
Model and effort are session-fixed
Kiro CLI does not support the model/effort configuration methods used by generic ACP clients. The adapter therefore passes both on the process command:
kiro-cli acp --agent <profile> --model <model> --effort <effort>It removes all model/effort keys from the acpx-engine config, preventing session/set_config_option. A model or effort change changes the command/session fingerprint and applies to a new ACP session.
Effort precedence is explicit:
effort(standard Paperclip field)thinkingEffortreasoningEffortmodelReasoningEffort- legacy
kiroEffort(read-only migration compatibility)
Values outside low, medium, high, xhigh, max fail the environment check and are omitted with a run warning.
Thinking effort UI compatibility
The adapter accepts all five values in saved/API configuration, including xhigh and max. With pinned @paperclipai/adapter-utils 2026.831.1, an external adapter cannot extend Paperclip's host-owned Thinking effort picker: AdapterConfigSchema only defines independent adapter fields, while CreateConfigValues.thinkingEffort is an unenumerated host form value. To offer xhigh and max in that standard picker, Paperclip must add both values to its host UI option enumeration or extend the external-adapter contract with an adapter-supplied option extension for thinkingEffort. Until then, use saved/API configuration for those two Kiro values; do not add a duplicate adapter field.
Reserved extraArgs
The adapter owns these flags and rejects both --flag value and --flag=value forms in extraArgs:
--agent--agent-engine--model--effort--resume-id--output-format--no-interactive
This prevents caller arguments from breaking profile, session, or typed-output guarantees.
--agent-engine is reserved because the adapter targets the Kiro v2 agent engine. The v3 engine rejects --agent, --model, --effort, --trust-all-tools, and --trust-tools on kiro-cli acp, so an extraArgs attempt to switch engines produces a command Kiro refuses to start rather than a working v3 session. Selecting v3 requires adapter support for its own configuration surface (ACP session modes instead of --agent, config options instead of --model), which this version does not implement.
Network and Paperclip instructions
The agent receives Paperclip runtime environment, managed instructions, selected skills, runtime MCP, and the loopback API URL. Public Paperclip origins are replaced for the ACP worker by setting both PAPERCLIP_API_URL and PAPERCLIP_RUNTIME_API_URL to loopback (or PAPERCLIP_LOCAL_API_URL) in the run-scoped config and ACP child command, while NO_PROXY/no_proxy include localhost, 127.0.0.1, and ::1. The host process.env is never mutated.
Endpoint selection is trusted only from the Paperclip server's own environment. Per-agent env configuration may still set ordinary process variables, but it cannot move the API endpoint that receives the run token off this machine: a configured PAPERCLIP_LOCAL_API_URL, PAPERCLIP_LISTEN_HOST, or related setting is honoured only when it resolves to a loopback address. Anything else is ignored with a run warning, the host-resolved endpoint is used, and the rejected PAPERCLIP_LOCAL_API_URL is not forwarded to the child. Only an operator's host-level PAPERCLIP_LOCAL_API_URL may name a non-local bridge. Both lanes apply the same rule; the reserved run-identity variables (PAPERCLIP_API_KEY, PAPERCLIP_RUN_ID, PAPERCLIP_TASK_ID, and related fields) are likewise always taken from the current run, never from configuration.
The server subpath still exports the deprecated forceLocalPaperclipApiUrlForEngine(env?) compatibility helper. New callers should use buildKiroAcpRunEnvironment; the deprecated helper mutates only an explicitly supplied environment object, and no-argument calls now operate on an isolated object instead of global process state.
Paperclip capability surface
The server module declares the following optional ServerAdapterModule capabilities from @paperclipai/adapter-utils 2026.831.1:
| Capability | Status | Notes |
|---|---|---|
| listModels / refreshModels | Supported | Live catalog from kiro-cli chat --list-models -f json, cached per executable/cwd/environment hash for 60 seconds in a bounded LRU cache (32 contexts). Static ids are the fallback only when the live query fails. |
| sessionManagement | Supported | supportsSessionResume: true, nativeContextManagement: "unknown", Paperclip threshold compaction (maxSessionRuns: 200, maxRawInputTokens: 2_000_000, maxSessionAgeHours: 72). No native Kiro compaction is claimed. |
| getQuotaWindows | Supported | One credit window from the strict read-only /usage parser against the host-default kiro-cli. Unavailable or malformed output returns ok: false with no windows; credits are never reported as tokens or as zero usage. |
| detectModel | Not supported | Kiro exposes the available catalog, not a locally configured current model. |
| modelProfiles | Not supported | The live catalog carries no cost class, so no model is labelled cheap. |
| loginCapability | Not supported | Login stays manual (kiro-cli login --use-device-flow); no device-flow output grammar is parsed or stored. |
Sessions and output
ACP session parameters are serialized by the pinned acpx-engine session codec and retain sessionKey, acpSessionId, agentSessionId, runtime session name, cwd, mode, and config fingerprint. CLI session params remain { sessionId, cwd }. The combined codec round-trips both formats.
ACP emits JSON log events such as acpx.session, acpx.text_delta, acpx.tool_call, acpx.status, and acpx.result. The CLI fallback keeps the existing ANSI/redraw sanitizer and UI parser. The shipped UI module exports only the stateful createStdoutParser factory so Paperclip preserves CLI continuation and tool call/result pairing across lines.
For compatibility with Paperclip 2026.831.1, the parser also repairs one narrowly identified framing defect: a missing JSON quote escape immediately after Paperclip's literal ***REDACTED*** marker inside an ACPX event. Recovery runs only after normal JSON parsing fails, only for acpx.* input, and only when the repaired payload becomes valid JSON. It never reconstructs redacted data; every other malformed line remains visible as untrusted stdout. Because recovery happens at render time, previously stored affected runs can render cleanly after the updated parser is loaded.
Credit and cost accounting
Kiro reports usage in credits, not tokens. The ACP lane wraps kiro-cli acp with the packaged acp-metering-proxy.cjs process. The proxy forwards ACP stdio unchanged and persists only a sanitized projection of _kiro.dev/metadata.meteringUsage (sessionId, credits, duration, timestamp) in a mode-0600 per-task journal. It never stores prompts, responses, tool payloads, or arbitrary private metadata.
Before and after a metered turn, the adapter runs the read-only kiro-cli chat --no-interactive "/usage" command and strictly parses:
- plan tier and monthly plan price;
- credits consumed and credits included;
- utilization percentage and reset date.
Accounting rules:
- credits are returned as
resultJson.kiroUsage; they are never converted to token fields; KIRO FREEis recorded with a$0monthly plan cost and never produces overage cost;- paid-plan usage ending at or below 100% is
subscription_includedwith incrementalcostUsd: 0; - paid-plan usage above 100% is
subscription_overageat$0.04per billable credit; - a turn crossing the boundary bills only the over-plan portion; when the pre/post global delta reveals unrelated concurrent account usage, the project allocation is marked estimated and is not published as billed
costUsd; - if
/usageis unavailable or malformed, credits remain attributed to the run/project butcostUsdstaysnullandbillingTypeisunknown.
The fixed monthly plan price is recorded under resultJson.kiroPlan.planMonthlyCostUsd for reconciliation. It is not repeatedly charged to every run. Paperclip's token usageJson remains null unless the ACP runtime independently provides real token usage.
Migration from kiro_local
This adapter intentionally uses the new type kiro_acp; it does not claim the baseline kiro_local type.
- Install/build this adapter as a separate local package.
- Change each intended Paperclip agent's adapter type from
kiro_localtokiro_acp. - Keep existing
command,model,cwd,baseAgent, skills, instructions, MCP, timeout, and environment configuration. - Start with
engine: auto. Useengine: acpafter confirmingkiro-cli acp --helpworks and fail-closed behavior is desired. - Use the standard Paperclip
Thinking effortfield; legacykiroEffortis read only for migration compatibility. - Remove reserved flags from
extraArgs.
There is no Git-history import, live adapter installation, remote creation, or Paperclip system mutation in this repository.
Limitations
- Local execution only; remote/sandbox Kiro provisioning is not implemented.
- Kiro login is manual.
- Model and effort cannot change inside a live ACP session.
requireMcpStartupdepends on Kiro profile support; the adapter can guarantee the local “at least one MCP configured” precondition, not behavior in older Kiro builds that ignore the profile field.- Credit attribution is exact per metered ACP turn. The included-versus-overage split for the single turn that crosses 100% is estimated when unrelated concurrent account usage lands in the same interval;
/usageis an account-wide snapshot. - Base-agent-scoped MCP is visible only when present in the inherited profile; unrelated agent profiles are not scanned.
- Reloading an already imported Node ESM package may require a Paperclip server restart.
Development and validation
npm run typecheck
npm test
npm run build
npm pack --dry-runA read-only smoke test can use kiro-cli acp --help and the bundled acpx client. Do not install the adapter into a live Paperclip instance merely to validate the package.
Attribution and license
MIT licensed. The initial CLI baseline was adapted from Schapat/paperclip-kiro-adapter, and its public feat-acp-engine work was consulted as a technical reference. Copyright and attribution details are in LICENSE and NOTICE. This repository has an independent Git history and is not affiliated with Paperclip Labs or Kiro.
