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_crew in src/kiro_crew/. Frontend: React
    • TS + Vite SPA in website/, built into src/kiro_crew/static/dist/ and served by the backend.
  • Data home: ~/.kiro/crew, overridden with KIROCREW_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, governanceplatform-context + governance
security.py, hooks.py, denied commands, sensitive pathssecurity + sel
the security model as a whole, threat boundariessecurity-deep-dive
computer_use/computer-use
acp/, kiro-cli transport, providersacp-client + providers
picking or defaulting a model anywheremodel-selection + model-fallback
adding or adapting an agent harness (BYO, KAS, claude)harness-parity (invariants) + harness-parity-gate (CI)
the publicly selectable Claude backendclaude-code-provider
sessions, slots, session keys, PIDssession + history
session summaries, the chat summary panel, intent extractionsession-summary
memory, embeddings, vectors, lessons, skills, hooksmemory-skills-hooks
MCP servers or tools (adding, changing, statelessness)mcp
apps, App Kit, manifests, app agentsapp-kit-platform + app-kit/
artifacts, companion chatartifacts
stt/, transcribe.py, voice_reply.py, the mic, dictation, TTSstt-streaming + voice-streaming
cron, learn, dashboard handlerslearn-cron-dashboard
Slack, Discord, any channel, messaging, approvalsmessaging + slack-gateway
subagents, spawn, orphan recoverysubagent
crews, select_crew, crew bindings, Crew Mode slotscrew-mode
the pipeline conductor agent or its skillpipeline-conductor
task runnertask + taskrunner
workflows/ (the dynamic-workflow engine)workflows
themesthemes + theming-contract
anything under website/website/AGENTS.md
user-facing strings, dates, numbers, sort orderi18n-catalog (authoring) + i18n-gates (CI)
tests: flakes, hangs, speed, memory, fixtures, sharding, side effects, conftest isolation, Windowstesting-conventions + the writing-tests skill; frontend and Electron tests: website/docs/testing.md
browser E2Ee2e-gate
proving a worktree change against an isolated running gatewayworktree-verification-recipes
CI, PR flow, review gates, commit messagesci-and-reviews + CONTRIBUTING.md
constants, comments, lint, code style, the brand namecode-style
connections, connectors, an external account linkconnections
a POSIX call: locks, signals, PIDs, chmod, RSSplatform-compat + windows-install
injected [Cron notification] / [Subagent completion event]injected-messages
build, install, dev modeCONTRIBUTING.md + install
cutting a releaserelease
CHANGELOG.mdchangelog
errors, retries, user-facing failure texterror-handling
what this public fork must never re-introduceoss-fork-boundaries
any doc: moving, renaming, indexing itdocs/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.json and computer_use.json under the data home are the ceiling the agent is governed by. The enforcement point is the OS layer in sandbox.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), or VISIBLE (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 an open() 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.py pins the union of the three dispositions equal to the crew-home half of security.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.key is VISIBLE, 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 a SCOPE_CATALOG data change, never an evaluator edit.
  • CONTRACT_VERSION stays pinned at 1 pre-launch.
  • Never restate the denied-rule count in prose. test/test_denied_commands_security.py pins it, and a restated count goes stale silently.
  • A cron script body is never a shell-gate subject. is_sensitive_bash_command and is_denied read a SHELL COMMAND LINE; mcp_cron._vet_script_contents scans 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 stop open(a + b). Do not add a subject_is_* flag, a _traversal_subjects re-pointing, or an is_sensitive_source_body back. A new script detector is a whole-body match in _vet_script_contents, or a sandbox mask. Pinned by test_the_shell_gate_has_no_source_body_entry_point and test_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/busybox before restructuring).
  • Computer use is deliberately NOT governed: it is one operator opt-in on the keystone computer_use.json. Never add computer_use.* scopes, capability rows, approval ordinals or pointer permits. Its refusals run in band on tools._dispatch, never at the fail-OPEN hooks gate, 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_backendagent.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. Treat docs/task-specs/ as an archive, never as current context.

Git

  • Do NOT proactively git commit. Commit only when asked.
  • Do NOT git push unless the user explicitly says to push. Being asked to commit is NOT permission to push.
  • main is 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-ini MUST keep -n auto --dist loadgroup --max-worker-restart=2; a bare override silently drops --dist loadgroup and scatters @pytest.mark.xdist_group tests 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.