Skip to content

feat(adapter/kiro): first-class Kiro CLI skill runtime#768

Open
Alexhans wants to merge 1 commit into
apache:mainfrom
Alexhans:feat/kiro-skills-discovery
Open

feat(adapter/kiro): first-class Kiro CLI skill runtime#768
Alexhans wants to merge 1 commit into
apache:mainfrom
Alexhans:feat/kiro-skills-discovery

Conversation

@Alexhans

@Alexhans Alexhans commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Bring Kiro CLI (the rebranded Amazon Q Developer CLI — the subject of
    feat(adapter/amazon-q): Amazon Q Developer CLI skill runtime #320) to first-class parity with Claude Code and OpenCode as a skill runtime,
    following the docs/adapters/add-a-harness.md recipe (docs(adapters): add "add a new agent harness" step-by-step recipe #741).
  • Discovery: Kiro reads .kiro/skills/ (not .agents/skills/), so this adds
    a kiro row to the skills/setup/agents.md registry plus the per-skill
    relays. The registry-driven adopt/verify/worktree-init logic then wires Kiro
    automatically; .agents / .github / .kiro all carry the 69 magpie-*
    relays.
  • Enforcement (agent-harness score: Kiro now 5/5 substrate tools):
    agent-guard (--kiro preToolUse adapter), sandbox-lint (--kiro),
    permission-audit (audit-kiro), agent-isolation (kiro-iso), and spec-loop
    (headless kiro-cli chat --no-interactive) all support Kiro.

Context

#320 was filed for "Amazon Q Developer CLI"; Amazon Q was rebranded to Kiro
CLI
, and the issue's suggested ~/.aws/amazonq/agents/<name>.json path is
superseded by Kiro's .kiro/skills/ + agent-config model — this PR implements
current Kiro.

Kiro's permission model is an allowlist/denylist (toolsSettings.shell
allowedCommands / deniedCommands = \A..\z-anchored regex, deny-before-allow,
denyByDefault, autoAllowReadonly) rather than OpenCode's per-command decision
map; the sandbox-lint / permission-audit invariants resolve through that. The
agent-guard adapter shares the harness-agnostic dispatch() core, so verdicts
are byte-identical across harnesses.

This follows the path paved by the OpenCode harness-adapter PRs (#686, #687,
#688) — the same shared-dispatch() / thin-adapter pattern applied to a third
harness. This is a a single PR (rather than OpenCode's incremental slices)
because Kiro additionally needs discovery wiring — the skills/setup/agents.md
registry row plus the per-skill relays — that OpenCode did not require (OpenCode
reads .agents/skills/ for free).

Type of change

  • Python package (tools/*/ with pyproject.toml)
  • Tool / bridge contract (tools/<system>/*.md)
  • CI / dev loop (prek, validators)
  • Documentation (docs/, tool READMEs)

Test plan

  • prek run --all-files passes
  • uv run pytest for each touched package (agent-guard, sandbox-lint,
    permission-audit, agent-isolation, symlink-lint) passes; ruff + mypy clean
  • spec-loop tests/test_runner_fixtures.sh covers the Kiro launch argv
    (kiro-cli chat --no-interactive <prompt>, no --model)
  • Discovery proven: a fresh kiro-cli chat --no-interactive discovers
    all skills with the .kiro/skills/ relays present, and 0 without
    (negative control); symlink-lint verifies the relays are cycle-free and
    target-correct
  • agent-guard proven end-to-end: a real .kiro preToolUse hook
    blocks a Co-Authored-By commit (commit not created) and allows a
    clean one
  • vendor-neutrality-score regenerates with Kiro at 5/5 substrate tools
  • Real-adopter end-to-end: adopted this branch into a downstream repo (Apache
    Airflow fork) via /magpie-setup — it auto-wired .kiro/skills/, and Kiro
    loaded and invoked framework skills with no gotchas (default-agent auto-load)
  • Custom agents covered too: verified on kiro-cli 2.11.1 that skills auto-load
    for both default and custom agents — /context show shows the CLI injecting
    workspace .kiro/skills/*/SKILL.md + global ~/.kiro/skills/*/SKILL.md with no
    explicit skill:// resource. (The kiro.dev docs mention custom agents may need
    resources: [skill://…]; this CLI version does not require it.)

Known limitations

  • agent-isolation covers the env -i credential-strip launcher (unit-tested);
    the bubblewrap OS-sandbox layer is not exercised in CI (unprivileged user
    namespaces unavailable in the dev environment) — deferred to a bwrap-capable
    host, consistent with how OpenCode's OS-sandbox layer is treated.
  • The sandbox-lint / permission-audit --kiro config-posture checks ship
    alongside the agent-guard hook as defense-in-depth (OpenCode precedent). A
    general /magpie-setup auto-installer for non-Claude harness enforcement is
    out of scope here (Claude-only today; a follow-up would cover OpenCode + Kiro
    together).

Gen-AI disclosure: authored with Kiro CLI (Opus 4.8) and reviewed by me.
Commits carry a Generated-by: trailer; no AI Co-Authored-By.

Bring Kiro CLI (the rebranded Amazon Q Developer CLI — apache#320) to parity with
Claude Code and OpenCode as a skill runtime, following the
docs/adapters/add-a-harness.md recipe (apache#741).

Discovery: Kiro reads .kiro/skills/ (not .agents/skills/), so add a `kiro` row
to the skills/setup/agents.md registry plus the per-skill relays; the
registry-driven adopt/verify/worktree-init logic then wires it automatically.
.agents/.github/.kiro all carry the 69 magpie-* relays.

Enforcement (agent-harness score: Kiro now 5/5 substrate tools):
- agent-guard: a --kiro preToolUse adapter (kiro_main) over the shared
  dispatch() core — Kiro feeds {tool_name, tool_input.command, cwd} on stdin
  and blocks on exit 2 with the reason on stderr.
- sandbox-lint --kiro and permission-audit audit-kiro: encode Kiro's
  allowlist/denylist model (allowedCommands/deniedCommands = \A..\z regex,
  deny-before-allow, denyByDefault) and flag configs that auto-approve
  dangerous shell / exfil surfaces.
- agent-isolation kiro-iso: the env -i credential-strip launcher (the harness
  name `kiro` normalises to the `kiro-cli` binary).
- spec-loop: a headless `kiro-cli chat --no-interactive` runner profile in lib.sh.

Verified: full prek --all-files; per-tool pytest + ruff + mypy; the spec-loop
runner fixture (kiro argv); symlink-lint (relays cycle-free + target-correct);
discovery — a fresh kiro-cli discovers all skills with the relays present and 0
without (negative control); agent-guard end-to-end — a real .kiro preToolUse
hook blocks a Co-Authored-By commit and allows a clean one;
vendor-neutrality-score regenerates with Kiro at 5/5.

Known limitations: agent-isolation's bubblewrap OS-sandbox layer is unit-tested
for the launcher but its e2e is deferred to a bwrap-capable host (as OpenCode's
is); a general /magpie-setup auto-installer for non-Claude-only harness
enforcement is a follow-up (Claude-only today), so the Kiro hook uses
${MAGPIE_AGENT_GUARD:-.claude/hooks/agent-guard.py}.

Generated-by: Kiro CLI (Opus 4.8)
return 0
print(f"sandbox-lint: Kiro permission-policy violations in {config_path}:", file=sys.stderr)
for e in errors:
print(f" - {e}", file=sys.stderr)

import pytest

import agent_guard
@justinmclean

Copy link
Copy Markdown
Member

Nice, focused harness addition. The thin-adapter-over-shared-dispatch() pattern is
applied cleanly across all five substrate tools, the 69 .kiro relays match the existing
.agents/.claude/.github convention, and the test coverage is strong (including the
differential --kiro/--opencode verdict test). A couple of small observations, neither blocking.

Smaller observations

  • tools/permission-audit/src/permission_audit/cli.py — _cmd_audit_kiro reads the Kiro config through _read_opencode_config(...). It's functionally a generic JSON read, but the OpenCode-named helper on the Kiro path reads as a copy-paste artifact. A harness-neutral name (_read_json_config) would make audit-kiro self-documenting.

  • tools/sandbox-lint/src/sandbox_lint/kiro.py and
    tools/permission-audit/src/permission_audit/kiro.py duplicate effective_shell_decision,
    _regex_fullmatch_any, and the SHELL_TOOL_NAMES/WILDCARD_TOOL_NAMES/EXFIL_TOOLS
    constants near-verbatim. This tracks the existing per-tool duplication of the OpenCode
    logic, so it's consistent with keeping the tool packages independently installable —
    raising it only so the choice is deliberate rather than accidental.

This review was drafted by an AI-assisted tool and confirmed by a Magpie
maintainer. The findings below are observations, not blockers; a Magpie
maintainer — a real person — will take the next look at the PR. If you think a
finding is mis-applied, please reply on the PR and a maintainer will weigh in.

@potiuk potiuk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this, @Alexhans — a substantial and careful piece of work. The adapters faithfully mirror the established OpenCode thin-adapter / shared-dispatch() pattern, sandbox_lint/kiro.py models Kiro's deny-before-allow \A..\z-anchored regex semantics correctly (nice use of re.fullmatch + defensive handling of malformed config), and every adapter ships tests. The 69 relay symlinks + registry row are the bulk of the diff and look mechanical and correct.

The one thing between this and a green light is CI: the required CodeQL check is failing on two alerts, flagged inline below. One is a trivial test-only import fix; the other is a defensible false positive that still needs an explicit resolution to turn the check green. I've also left one non-blocking naming nit.

Once CI is green I'm happy to take another pass. Nice work bringing Kiro to parity.


This review was drafted by an AI-assisted tool and confirmed by a Magpie maintainer. The findings below are observations, not blockers; a Magpie maintainer — a real person — will take the next look at the PR. If you think a finding is mis-applied, please reply on the PR and a maintainer will weigh in.

More on how Magpie handles maintainer review: CONTRIBUTING.md.

Comment on lines +34 to +35
import agent_guard
from agent_guard import ALLOW_EXIT, DENY_EXIT, kiro_main

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI blocker (CodeQL alert #32). agent_guard is imported both as a module here and via from agent_guard import … on the next line — CodeQL flags the mixed style and it's failing the required CodeQL check. The bare import agent_guard is only used once (agent_guard.opencode_main() at line 106), so consolidating onto the from import clears it:

Suggested change
import agent_guard
from agent_guard import ALLOW_EXIT, DENY_EXIT, kiro_main
from agent_guard import ALLOW_EXIT, DENY_EXIT, kiro_main, opencode_main

…then update line 106 to call opencode_main() directly.

return 0
print(f"sandbox-lint: Kiro permission-policy violations in {config_path}:", file=sys.stderr)
for e in errors:
print(f" - {e}", file=sys.stderr)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI blocker (CodeQL alert #31 — clear-text logging of sensitive data). The invariant messages in e inline matched!r (the adopter's config regex), and CodeQL's taint tracker treats that config-derived value as a secret reaching a log sink. This is a defensible false positive — echoing which rule tripped the invariant is the linter's whole job — but CodeQL is a required check and it's red, so it needs resolving before this can land. Two clean options: (a) refactor so the message names the offending rule without inlining the raw pattern, or (b) add a reviewed inline CodeQL suppression comment with a one-line justification. Either is fine.


def _cmd_audit_kiro(args: argparse.Namespace) -> int:
config_path = Path(args.config_path).resolve()
config = _read_opencode_config(config_path)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit (non-blocking). _cmd_audit_kiro loads the Kiro config via _read_opencode_config(…). It works — both are plain JSON readers — but the opencode name is misleading on the Kiro path; a shared _read_json_config helper (or a Kiro-named wrapper) would read cleaner. Not a merge blocker.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants