Rules for AI Assistants
September 7, 2026 · View on GitHub
This file is a ROUTER, not a manual. It carries only the rules whose violation
causes damage before a pointer could be read. Everything else is a link you MUST
open before touching that subsystem: see
Read before you touch. The frontend has its own router,
website/AGENTS.md.
What this is
Kiro Crew is an open-source personal AI agent: chat from the web dashboard, the
CLI, or a messaging channel like Slack and Discord; run multi-step tasks
unattended; schedule cron jobs; keep memory across sessions. It drives an LLM
through the KiroACP provider (the ACP adapter running kiro-cli over ACP
JSON-RPC) plus MCP tools.
- Backend: Python package
kiro_crewinsrc/kiro_crew/. Frontend: React- TS + Vite SPA in
website/, built intosrc/kiro_crew/static/dist/and served by the backend.
- TS + Vite SPA in
- Data home:
~/.kiro/crew, overridden withKIROCREW_HOME. - Distribution: public GitHub, plain setuptools, public PyPI / public npm.
Full map: overview. This repo is the de-Amazoned
public fork of an internal package; what must never come back is
oss-fork-boundaries, gated by
the internal-content-scan check (which today reports on pushes to main, not on
pull requests -- see oss-fork-boundaries.md) and the
no-new-builtin-apps rule in AUTOSDE.yaml.
Read before you touch
Load the doc for the row you are working in before you change code. Update it in the same commit when you change what it documents.
| If you are touching… | Read first |
|---|---|
platform/, editions, CPP seam, governance | platform-context + governance |
security.py, hooks.py, denied commands, sensitive paths | security + sel |
| the security model as a whole, threat boundaries | security-deep-dive |
computer_use/ | computer-use |
acp/, kiro-cli transport, providers | acp-client + providers |
| picking or defaulting a model anywhere | model-selection + model-fallback |
| adding or adapting an agent harness (BYO, KAS, claude) | harness-parity (invariants) + harness-parity-gate (CI) |
| the publicly selectable Claude backend | claude-code-provider |
| sessions, slots, session keys, PIDs | session + history |
| session summaries, the chat summary panel, intent extraction | session-summary |
| memory, embeddings, vectors, lessons, skills, hooks | memory-skills-hooks |
| MCP servers or tools (adding, changing, statelessness) | mcp |
| apps, App Kit, manifests, app agents | app-kit-platform + app-kit/ |
| artifacts, companion chat | artifacts |
stt/, transcribe.py, voice_reply.py, the mic, dictation, TTS | stt-streaming + voice-streaming |
| cron, learn, dashboard handlers | learn-cron-dashboard |
| Slack, Discord, any channel, messaging, approvals | messaging + slack-gateway |
| subagents, spawn, orphan recovery | subagent |
crews, select_crew, crew bindings, Crew Mode slots | crew-mode |
| the pipeline conductor agent or its skill | pipeline-conductor |
| task runner | task + taskrunner |
workflows/ (the dynamic-workflow engine) | workflows |
| themes | themes + theming-contract |
anything under website/ | website/AGENTS.md |
| user-facing strings, dates, numbers, sort order | i18n-catalog (authoring) + i18n-gates (CI) |
| tests: flakes, hangs, speed, memory, fixtures, sharding, side effects, conftest isolation, Windows | testing-conventions + the writing-tests skill; frontend and Electron tests: website/docs/testing.md |
| browser E2E | e2e-gate |
| proving a worktree change against an isolated running gateway | worktree-verification-recipes |
| CI, PR flow, review gates, commit messages | ci-and-reviews + CONTRIBUTING.md |
| constants, comments, lint, code style, the brand name | code-style |
| connections, connectors, an external account link | connections |
| a POSIX call: locks, signals, PIDs, chmod, RSS | platform-compat + windows-install |
injected [Cron notification] / [Subagent completion event] | injected-messages |
| build, install, dev mode | CONTRIBUTING.md + install |
| cutting a release | release |
CHANGELOG.md | changelog |
| errors, retries, user-facing failure text | error-handling |
| what this public fork must never re-introduce | oss-fork-boundaries |
| any doc: moving, renaming, indexing it | docs/README.md |
The whole doc tree is indexed from docs/README.md. User-facing
docs that ship in the package live in src/kiro_crew/docs/ and are indexed by
its README.
Security invariants (do NOT weaken)
Detail and rationale: security, governance, computer-use.
- Keystone.
security_policy.json,profiles/,admission_policy.jsonandcomputer_use.jsonunder the data home are the ceiling the agent is governed by. The enforcement point is the OS layer insandbox.py, not a text matcher. Every crew-home leaf carries one of three dispositions, and which one it has IS the statement of what is guaranteed:HIDDEN(bind-masked in every mode — the credential homes,.env,live_target.json),READONLY(in-sandbox code reads it and a write would let the agent choose its own ceiling — the four leaves above), orVISIBLE(in-sandbox code needs read and write, so no OS rule applies and it rests on the tool gate alone). Precisely, then: the agent cannot write its own ceiling in any sandbox mode, and it can read it, deliberately — masking a policy file makes it resolve to the permissive standalone default, so hiding a ceiling REMOVES it instead of protecting it. Do not restore a read block by pattern-matching command text: a spawned shell reaches a file through anopen()that never routes through the tool gate, so a path fenced only there is readable in any sandbox mode whatever the matcher recognises, and each spelling closed (/./, a glued redirect, a variable,cd,pushd) narrows an unbounded set by exactly one.test_sandbox_governance_mask.pypins the union of the three dispositions equal to the crew-home half ofsecurity.sensitive_home_dirs(), so a new leaf cannot land in none of them — that pin, not a regex, is what keeps the ceiling un-disableable. One residual worth carrying:sel_hmac.keyisVISIBLE, so the SEL audit key has no OS fence; closing that means moving its in-sandbox reader behind the gateway, never another matcher. - Governance is
POLICY ∩ PROFILE, tightest-wins, enforced at Kiro Crew's OWN PreToolUse gate even when the kiro agent config granted the call. The evaluator is scope-name-agnostic, so adding a scope is aSCOPE_CATALOGdata change, never an evaluator edit. CONTRACT_VERSIONstays pinned at 1 pre-launch.- Never restate the denied-rule count in prose.
test/test_denied_commands_security.pypins it, and a restated count goes stale silently. - A cron script body is never a shell-gate subject.
is_sensitive_bash_commandandis_deniedread a SHELL COMMAND LINE;mcp_cron._vet_script_contentsscans a Python source body with whole-body, source-aware detectors only (credential path, secret env name, exfil URL) and the sandbox is the runtime control. Handing the body to the shell gate was tried (#4243 → #8811) and every shell-grammar pass produced a permanent false denial on ordinary scripts (#7912, #8563, #8643, #8812), each patched with another AST layer that still could not stopopen(a + b). Do not add asubject_is_*flag, a_traversal_subjectsre-pointing, or anis_sensitive_source_bodyback. A new script detector is a whole-body match in_vet_script_contents, or a sandbox mask. Pinned bytest_the_shell_gate_has_no_source_body_entry_pointandtest_script_body_is_never_a_shell_gate_subject. - A regex spelling-chase is a review smell, not a fix. When a security review
finds "X also reaches the fence via spelling Y", ask first whether the SUBJECT is
wrong (a document handed to a command-line matcher) or whether the sandbox
already covers it. Add a table entry only when the subject is genuinely a shell
command line and the OS sandbox does not hold the path (#7441 went four rounds
of
command/exec -a/nice/env -i/timeout/busyboxbefore restructuring). - Computer use is deliberately NOT governed: it is one operator opt-in on the
keystone
computer_use.json. Never addcomputer_use.*scopes, capability rows, approval ordinals or pointer permits. Its refusals run in band ontools._dispatch, never at the fail-OPENhooksgate, because a pre-authorized tool can skip that gate.click_method: "auto"must NEVER resolve onto"global": that is the only thing between an ordinary click and the operator's real cursor.
Never hardcode a model id
claude-*, opus*, sonnet*, haiku*, gpt-* or fable* as a default or
fallback fails at runtime for anyone not entitled to it, silently until the first
prompt. The default is "auto"; resolve a substitute choice through
acp.client.resolve_usable_model; pin a cheaper model only via
agent.role_models.<role>. code-review.yml fails on a newly added hardcoded
literal. Rules and the one exception:
model-selection.
Harness parity
Never express "this is the Kiro harness" as the ABSENCE of another one. A negative
test fails toward the permissive answer, so nothing goes red until an operator who
never opted into that harness pays for it — and every id in
acp_backends.BASELINE_SELECTABLE_BACKENDS is selectable on a plain public build,
so not is_claude_backend is already wrong on the non-Claude ones. Identity is
positive: is_kiro_backend, == ACP_BACKEND_KIRO, or membership in a named
ACP_BACKENDS_* set.
An added harness ADAPTS to the seams the Kiro path already runs through; it never
moves, widens or generalizes them, and it is selected at agent.acp_backend —
agent.provider stays enum=["acp"]. Invariant ids (cite them bare, H7),
capability sets and the CI half:
harness-parity. Run the added-line
gate locally with
HARNESS_BASE_REF=origin/main python3 scripts/check_harness_parity.py.
Specs and docs
- MUST read the owning spec under
docs/system-specs/before changing the code it covers, and MUST update it in the SAME commit. - MUST NOT create additional markdown files unless explicitly instructed.
- Everything else about adding, moving, indexing and linting a doc — including
scripts/docs-lint.sh— is docs/README.md. Treatdocs/task-specs/as an archive, never as current context.
Git
- Do NOT proactively
git commit. Commit only when asked. - Do NOT
git pushunless the user explicitly says to push. Being asked to commit is NOT permission to push. mainis the default branch; changes land through a GitHub PR. The full flow: CONTRIBUTING.md.
<type>: <summary — max 72 chars, imperative, lowercase, no period>
<body — what and why, not how; wrapped at 72>
Types the PR-title gate in code-review.yml accepts: feat, fix, docs,
style, refactor, perf, test, chore, ci, build, revert. One
logical change per commit, and at most two commits per PR.
CHANGELOG.md
- Your feature PR does not touch
CHANGELOG.md. The release PR writes the section covering everything that shipped. - Never delete or edit a shipped section. A release PR prepends one section and leaves every earlier one byte-identical. This has already cost 322 lines of released history once, which no test caught.
- A stable release must never ship a version carrying a prerelease suffix.
When, how, the heading shape and the format budget: changelog. Cutting a release, and the one escape hatch from the suffix rule: release.
The gate before you commit
python3 scripts/check_black_formatting.py && python3 scripts/check_subprocess_encoding.py && isort src/kiro_crew test
flake8 src/kiro_crew test && mypy src/kiro_crew
python -m pytest
- On macOS, run
mypy --platform linux src/kiro_crew. Without it a local run reports errors you did not cause and MISSES the Linux-only errors CI fails on, so a clean local run is a false green. - Never run bare
black src/kiro_crew test. It reformats every baselined file and buries your diff. Format only what you touched:black --target-version py310 <the files you changed>. - Frontend:
cd website && npm run build && npm run test. - A multi-test
--override-iniMUST keep-n auto --dist loadgroup --max-worker-restart=2; a bare override silently drops--dist loadgroupand scatters@pytest.mark.xdist_grouptests into flaky races.
Gates, the six flake classes, the conftest isolation floor and the traps that are
invisible when reading a test: code-style +
testing-conventions. A test that
can block forever is a lost RUN, not a failed test: on Windows pytest-timeout kills
the xdist worker, and with --max-worker-restart=0 one unbounded await aborts the
whole job (class 6). Frontend and Electron: website/docs/testing.md.
Cross-platform
Route POSIX calls through platform_compat: fcntl, termios, resource and
pty do not exist on Windows, and os.kill(pid, 0) TERMINATES the target
there — it is not a liveness probe. The full helper-per-call table:
platform-compat. Verify process,
signal, file-lock and metrics changes on macOS + Linux.
LLM-facing capabilities
A new LLM-facing CLI command MUST also ship as an MCP tool, MCP tools MUST be
stateless (no module global holds per-caller data), and a skill any shipped
feature, tool or doc references MUST live in src/kiro_crew/builtin_skills/ — the
only tree bundled into the package. Why, plus the session-key gate:
mcp +
memory-skills-hooks.
Injected messages are not the user
[Cron notification from "job"], [Subagent completion event] and
[auto-nudge cycle N] arrive from automation. Process them; do NOT answer them as
if a human typed them — the user may not be present. Envelopes:
injected-messages.
Harness safety
kirocrew gateway --approval yolo auto-approves ALL tools and refuses to start
unless KIROCREW_HOME is explicitly set to a non-default path. Never point it at
~/.kiro/crew. All harness flags: cli.