Inside Kiro Crew: How a Persistent AI Agent Actually Works


I’ve spent the better part of a week living inside Kiro Crew — not just using it, but reading its installed source to understand how it actually works. This post is what I pieced together: a layer-by-layer unpacking of the architecture, from the surfaces you type into all the way down to the kiro-cli subprocess that talks to the model. If you’ve ever wondered what “persistent, self-improving AI agent” means in engineering terms rather than marketing terms, this is for you.

The one-sentence mental model

Kiro Crew is one long-running Gateway process on hardware you control, which drives kiro-cli as its LLM agent over a protocol called ACP (Agent Client Protocol), wraps it in persistent memory and scheduling, and lets you reach the same agent from many surfaces. Everything else is detail hanging off that spine.

The cleanest way to hold it in your head: one Gateway × many surfaces × many trigger modes. You pick where the Gateway runs, then any mix of surfaces (dashboard, CLI, Slack…) and trigger modes (interactive, scheduled, reactive) on top.

The architecture, in six bands

Here’s the diagram I built while working through it. Read it top to bottom — a request enters at the top (a surface), flows down through the Gateway into an agent session, and the bottom bands (memory, persistence) are what make the whole thing survive beyond a single conversation.

Kiro Crew — System Architecture
1 · Surfaces — Desktop app · Web dashboard (:5476) · CLI · Slack / Discord / Telegram / Teams / Webex / WeCom / WeChat · Inbound webhooks
2 · Messaging Transport (channel-neutral) — Layer 1 Transport (receive → authorize → normalize) · Layer 2 TurnDriver (redact → approval ladder → events) · Layer 2b Renderer · Layer 3 session keys
3 · Gateway (the long-running process) — Session Manager · Context Builder · Approval Broker · Consolidator · App Kit · Cron Scheduler · Subagent Manager · Task Runner · AutoNudge · Research Lab · Webhook Router
4 · Agent Session Runtime (ACP) — Session Provider · kiro-cli (KiroACP, JSON-RPC over stdio) · MCP Tool Layer · Turn Loop · OS Sandbox
5 · Memory & Learning (local-first) — Preferences · Projects · Daily History · Lessons · Vector Memory (semantic + episodic) · Knowledge Library · Skills
6 · Persistence (~/.kiro/crew) — Session JSONL · config.json · memory/ · lessons · embedding model · subagent transcripts · audit logs · snapshots
Cross-cutting — Security (defense in depth): tool approvals · 137 deny patterns · OS sandbox · sensitive-path guards · credential redaction · signed dashboard tokens · governance policy ceiling

Band 1 — Surfaces: where you work

Every way you reach the agent funnels into the same Gateway: the desktop app (Electron with a bundled Gateway), the web dashboard at localhost:5476, the CLI (kirocrew chat / run / cron / spawn), seven messaging channels, and authenticated inbound webhooks. The key design decision is that the surface is decoupled from where the agent runs — start work in the dashboard, continue it from Slack, and it’s the same session, same memory, same state.

Band 2 — Messaging Transport: write a channel once

This is the most elegant part of the codebase. Rather than each channel re-implementing streaming, auth, and tool approvals, Kiro Crew extracts the channel-neutral parts into a messaging package with a strict one-way dependency (slack/dashboardmessaging, never the reverse). Three layers:

  • Layer 1 — MessagingTransport (one per channel): receive() → authorize() (deny-by-default) → normalize() into a neutral InboundMessage. A capabilities struct declares each channel’s limits (character cap, buttons, streaming) so upper layers degrade gracefully instead of branching on channel type.
  • Layer 2 — TurnDriver (shared by all channels): consumes the LLM event stream, redacts credentials and exfiltration URLs, runs the approval ladder, emits neutral output events, and audits every approval decision.
  • Layer 2b — Renderer (one per channel): maps those neutral events onto the channel’s real API.

The payoff: a new channel implements only Layer 1 + Layer 2b and inherits redaction, the approval ladder, session identity, and message chunking for free.

Band 3 — The Gateway: the brain

The Gateway is the long-running process that everything else orbits. It routes messages, persists session state, injects memory, starts scheduled work, coordinates subagents, brokers approvals, enforces policy, and exposes the dashboard. Its core services include the Session Manager (isolated concurrent sessions, warm pool, resume), the Context Builder (injects memory/skills/history into each turn), the Approval Broker, and the Consolidator (more on that below). Alongside those sit the execution modes — the different ways work can start:

ModeWhat it’s for
InteractiveYou typing in the dashboard, CLI, or a messaging channel
Scheduled (cron)Recurring jobs — “every weekday at 9, summarize my costs”
Task RunnerA bounded multi-step spec you hand over and walk away from
SubagentsParallel background workers for independent pieces
ProactiveAutoNudge / monitoring loops that re-inject a check on an interval
ReactiveWebhooks and messaging events that wake the agent

Band 4 — The agent runtime: kiro-cli over ACP

Here’s where a lot of my confusion cleared up. Kiro Crew is not the LLM agent — kiro-cli is. Kiro Crew is the client that drives it. They talk over ACP, the Agent Client Protocol (created by Zed, the same idea as the Language Server Protocol but for AI agents): JSON-RPC 2.0 over stdin/stdout, with a handshake of initialize → session/new → session/set_mode → session/set_model → session/prompt.

The division of labour is clean:

ConcernOwner
Model reasoning, token streaming, per-session conversation statekiro-cli (the agent)
Executing tool calls, connecting to MCP serverskiro-cli, using configs Kiro Crew forwards
Owning the subprocess, routing frames by session IDAcpRuntime
Memory injection, approvals, scheduling, redaction, persistenceKiro Crew Gateway (the client)

One neat detail: a single AcpRuntime owns one kiro-cli subprocess but multiplexes many sessions over it — a single-reader loop routes frames by sessionId to per-session queues. So your dashboard chat, a cron job, and a subagent can all ride the same process, each isolated. There’s no hard cap on sessions per process; growth is bounded by process recycling (age/RAM thresholds) rather than a counter.

What’s actually in the kirocrew-core MCP server

Tools reach the agent over the Model Context Protocol (MCP). The built-in kirocrew-core server is where Kiro Crew’s own agent-facing commands live. Reading the registrations, they group into families:

FamilyTools
Subagentsspawn_run, spawn_sub_agents, spawn_list, spawn_status, spawn_steer, spawn_continue, spawn_release
Learninglearn_add, learn_list, learn_remove
Skillsskill_search, skill_discover, skill_fetch
Tasks & flowtask_run, wait, register_hook, select_crew, resource_status
Messagingsend_message, send_notification, delete_message, read_slack_profile, file_send
Artifactsartifact_save, artifact_get, artifact_update, artifact_revert, artifact_list, artifact_versions, artifact_delete

(Scheduling — cron_add, cron_list, and friends — lives in a sibling kirocrew-cron server.) The design rule is that every LLM-facing command must be an MCP tool, which is why the agent’s capabilities are so legible: you can enumerate exactly what it can do by listing the server’s tools.

How agents work

An “agent” here is just a JSON config (in ~/.kiro/agents/) defining a model, a system prompt, a set of tools, MCP servers, and mapped skills. Each agent runs as its own ACP session. The built-ins are kirocrew (the full-featured default you talk to), plus internal workers like kirocrew-lite (a lighter, cheaper agent used for background housekeeping) and kirocrew-knowledge (document extraction). You can define your own custom agents by dropping a JSON file in that directory.

Two fields are worth distinguishing because they solve different problems:

  • tools — the availability gate. What the agent can reach at all.
  • allowedTools — the auto-approve gate. Which of those run without an approval prompt.

Agents don’t chat peer-to-peer. They collaborate two ways: delegation (a parent agent spawns named subagents and their results flow back to it) and shared workspace memory (asynchronous, indirect). That’s the “teammates, not tools” idea made concrete — an orchestrator with subagents plus a shared memory substrate, not an agent-to-agent chat bus.

How Kiro Crew spawns a subagent through kiro-cli

When the agent calls spawn_run, here’s the chain, end to end:

  1. The Subagent Manager registers a new subagent with its own session key (subagent:<id>).
  2. It acquires a session — either a new kiro-cli ACP process, or (more often) a fresh sessionId on an existing multiplexed AcpRuntime via a session/new call.
  3. The subagent runs under whichever agent it was spawned with — a named agent’s config, or the default agent if unnamed. It does not inherit the parent’s tool list; it uses its own agent config. (What does flow down is the parent’s injected context — memory, lessons, project — unless you switch those off with the include_* flags.)
  4. The subagent streams its work over ACP like any other session. When it finishes, its result is injected back into the parent conversation as a [Subagent completion event], and the full transcript is kept on disk for a grace window so the parent can read it on demand.
  5. The parent synthesizes the results.

Important guardrails: subagents cannot spawn their own subagents (no recursion), the concurrent cap auto-sizes from host memory and CPU, and a spawn is refused when free memory drops below a threshold. So the fan-out is bounded and hierarchical: one orchestrator, a flat layer of workers, results collected back up.

How memory actually works

This is the part that turns a stateless chatbot into something that remembers you. The crucial distinction I had to internalise: raw conversation transcripts are isolated per session, but memory bridges across them. One session can’t see another’s live messages — but a background process distills each session into shared memory that every future session reads.

That background process is the Consolidator. It runs on the Gateway’s event loop and fires on two triggers: preferences/projects/semantic memory after roughly every 30 messages, and daily history plus lessons after about 3 hours idle (or end of day). It runs a real, tool-free LLM turn on the lightweight background agent, feeds it the unconsolidated transcript tail plus current memory, and asks for structured JSON. The output fans out to two kinds of storage at once:

OutputLands inStorage type
history_entryhistory/{date}.mdText file
preferences / projectspreferences.md / projects.mdText file
semanticStructured key-value storeVector (embedded)
episodicConversation fragmentsVector (embedded)
lessonslessons.jsonlBoth file + embedded

The two vector types map to a real distinction from cognitive science: semantic memory is atomic facts (“this project’s region is X”), while episodic memory is events and decisions (“we debugged X on this date and concluded Y”). Semantic answers “what is X?”; episodic answers “what happened when we did X?”. Both are embedded with an in-process embedding model (Qwen3-Embedding-0.6B, ~610 MB, downloaded on first start), so they can be retrieved by meaning, not just keywords.

So how does the agent decide whether to use a text file or the vector store? It doesn’t really “choose” — the two are used at different moments. Small, consolidated text memory (preferences, projects, recent history) is always injected as baseline context at the start of every session. The vector store is queried on demand — automatically when something is semantically relevant, or explicitly via a search tool. Same knowledge, different retrieval timing.

One consequence worth knowing: deleting a session removes that conversation’s transcript, but not the memory already distilled from it. To truly forget something, you edit the memory files and lessons too. And for conversations that should never persist, Incognito mode reads memory but writes nothing, while Temporary mode neither reads nor writes.

There’s a second, separate store: the Knowledge Library. Where memory is distilled from your conversations, the Knowledge Library holds documents you give it — folders, uploads, artifacts. At ingest time each document is chunked, an LLM extracts entities and relationships into a graph, and the chunks are embedded. Search then fuses three signals — keyword (FTS5), graph traversal, and vector similarity. It degrades gracefully: without the embedding model it still works on keyword + graph.

Skills: on-demand knowledge, and self-evolution

Skills are markdown knowledge packs loaded only when relevant — a lightweight extension point that doesn’t touch the core runtime. A few I dug into show the range:

  • conductor — agent delegation. Routes a task to a specialist “crew” when its triggers clearly match, otherwise the default agent just handles it.
  • computer-use — drives native macOS desktop apps through the accessibility layer (snapshot the window as a numbered element tree, act by element index). Opt-in and off by default.
  • browser-recording — captures a web flow as an mp4/GIF for evidence, driving the project’s own Playwright plus ffmpeg.
  • crystallize — turns the current session into a reusable skill, staged for review by default.

That last one closes the loop on the “self-evolving” claim: corrections become lessons, and repeated patterns can be crystallized into new skills. The system is designed to get more tailored to your work the more you use it — memory, lessons, and skills all stay inspectable and editable.

Security is at the runtime boundary, not the prompt

Because this agent has real tool access, the controls are enforced where the tools run rather than by asking the model nicely. Even with tool approvals set to auto, a layered defense still applies: 137 bundled deny patterns block destructive commands, sensitive paths are guarded, credentials are redacted from output, an OS sandbox (Seatbelt on macOS, namespaces on Linux) confines the process, and a governance policy can impose a tightest-wins ceiling nothing below it can loosen. “Auto-approve” only removes the per-tool prompt — it does not switch any of that off.

Takeaways

  • Kiro Crew is the client; kiro-cli is the agent. They talk over ACP (Agent Client Protocol) — JSON-RPC over stdio — and one runtime multiplexes many sessions onto one process.
  • Capabilities are legible. Every LLM-facing command is an MCP tool, so you can enumerate exactly what the agent can do; kirocrew-core groups into subagents, learning, skills, tasks, messaging, and artifacts.
  • Memory is two-layered. Isolated transcripts, shared consolidated memory — distilled into both editable text files and an embedded vector store (semantic + episodic).
  • Subagents are hierarchical and bounded. A parent spawns a flat layer of workers (no recursion), results flow back, and the concurrency cap auto-sizes from host resources.
  • Deleting a session ≠ forgetting. The transcript goes; the distilled memory stays until you remove it.

What impressed me most, reading the source, is how much of the “magic” is just disciplined plumbing — a clean protocol boundary, a channel-neutral turn loop, a consolidation step that runs on a cheap model, and safety enforced at the point of execution. None of it is mysterious once you trace it. That’s a good sign for a tool you’re going to trust with real work.