A GitHub issue-driven AI orchestration system that transforms labeled issues into working code through coordinated AI agent collaboration.
Kairon watches a GitHub repository for issues with a configured label, then spawns AI agents to implement solutions automatically:
GitHub Issue (labeled) → Watcher detects → Krew-Lead orchestrates
→ Architect designs → Builder implements → Validator verifies → PR created
The system uses kiro-cli agents working in isolated git worktrees. Each issue gets its own branch, and on success a pull request is created automatically.
- Go 1.26+
- GitHub CLI (
gh) — authenticated viagh auth login - Kiro CLI (
kiro-cli) — for running AI agents
go install github.com/jbrinkman/kairon@latest# For AMD64 (x86_64)
curl -L https://github.com/jbrinkman/kairon/releases/latest/download/kairon-linux-amd64 -o kairon
chmod +x kairon
sudo mv kairon /usr/local/bin/
# For ARM64 (aarch64)
curl -L https://github.com/jbrinkman/kairon/releases/latest/download/kairon-linux-arm64 -o kairon
chmod +x kairon
sudo mv kairon /usr/local/bin/
# Check your architecture
uname -m # x86_64 = AMD64, aarch64/arm64 = ARM64git clone https://github.com/jbrinkman/kairon.git
cd kairon
# Using Task (recommended)
task build
# Or using Go directly
go build ./cmd/kaironThis project uses Task for build automation:
task build # Build optimized binary with version metadata
task dev # Development build (faster compilation)
task test # Run tests with coverage
task clean # Clean build artifacts
task lint # Run linters and formatterscd your-project
kairon initThis creates:
.kairon/config.yaml— watcher configuration.kairon/scripts/— worktree management scripts.kiro/agents/— agent configurations (krew-lead, architect, builder, validator, documenter)
Edit .kairon/config.yaml:
repo: owner/repo-name
label: kairon
poll_interval: 5m
max_retries: 3
max_agents: 3| Field | Description | Default |
|---|---|---|
repo |
GitHub repository (owner/name) | required |
label |
Issue label to watch for | kairon |
poll_interval |
How often to poll GitHub | 5m |
max_retries |
Max retry attempts per issue | 3 |
max_agents |
Most agents Kairon runs at once. A positive integer; zero and negative values are rejected, and there is no unlimited setting | 3 |
base_branch |
Integration branch that new issue worktrees are created from (optional) | auto-detect from origin/HEAD, else main |
At most max_agents agents run at once. Implementation runs, retries and intake runs all count toward the limit. When more issues are ready than there are free slots, the oldest issue (lowest issue number) starts first. An issue that waits for a slot is not charged a retry attempt, and the next poll considers it again. Each poll logs every waiting issue at info level as issue #N waiting for an agent slot (M/M running). See docs/concurrency.md.
kaironThis starts the interactive REPL. From there, start the watcher:
kairon> watch start
kairon> status
# Display version and exit
kairon --version
# Initialize project with agent configs and templates (skips existing files)
kairon init
# Force-update templates (overwrites all files except config.yaml)
kairon update
# Run a YAML list of checks against a directory (PASS/FAIL per check; exit 0 only if all pass)
kairon check --checks checks.yaml --dir path/to/dir
# Run only the eval cases whose metadata matches (quote < and > in a shell)
kairon eval architect --filter 'speed==fast' --filter 'retries>=3'
# Start interactive REPL (default when no arguments)
kairon| Command | Description |
|---|---|
watch start |
Start polling GitHub for labeled issues |
watch stop |
Stop polling |
status |
Show all agents with issue, status, and elapsed time |
stop <issue> |
Stop the agent working on a specific issue number |
plan [desc] |
Start interactive planning session |
validate <issue> |
Canonicalize and stamp an existing issue with the automated planner, so the watcher can implement it (details) |
theme |
Show current theme |
theme <name> |
Switch to theme |
about |
Show version information and check for updates |
exit |
Exit (confirms if agents are still running) |
help |
Show available commands |
Press Ctrl+Alt+P (or Ctrl+Option+P on macOS) to toggle between console and planning modes:
- Console Mode: Main Kairon interface for managing watchers and agents
- Planning Mode: Interactive AI-assisted issue creation and planning
Both modes preserve their state when you switch, allowing seamless workflow transitions. See docs/hotkey-toggle.md for detailed usage information.
An eval case can carry metadata (for example speed: fast), and kairon eval --filter runs only the cases that match. Repeat --filter to require all of them, and add --strict to fail when nothing matches:
kairon eval architect --filter 'speed==fast'
kairon eval --list architect --filter 'skill==cli' --filter 'retries>=3'A filtered run is recorded as a partial run. eval diff and eval trend accept --filter too. See docs/evaluation.md for the syntax, operators and result fields.
kairon check runs a checks file (the YAML list of deterministic checks used by eval cases) against any directory and prints one PASS/FAIL line per check. It exits 0 only if every check passes, and validates the whole file before running anything. See docs/evaluation.md for the flags, file format and safety notes (command checks run on the host).
The document a workflow stage returns (a strict-schema YAML envelope with a canonical or punch_out result) is defined in docs/stage-output.md.
The guards that check a stage-output document between stages, and the ordered chain runner that applies them, are described in docs/guards.md.
The provenance step that runs after a guard chain passes (canonicalize, hash, classify as valid, none or mismatch, and write an issue body once with a v1 marker) is described in docs/provenance.md.
The planner is two single-mode agents (planner for interactive use, planner-automated for unattended intake) that share the Kairon-managed planner-shared skill, with a deterministic output boundary that is the only writer to GitHub. The agents, the skill's ownership tier, the boundary and its outcomes, the eval driver and the known gaps are described in docs/planner.md.
When the watcher detects a labeled issue:
- Krew-Lead — Orchestrates the workflow. Creates a git worktree, delegates to other agents, manages the lifecycle from issue to PR.
- Architect — Reads the issue, explores the codebase, and produces a design specification at
.kairon/specs/issue-<number>-<slug>.md. - Builder — Implements code changes according to the architect's specification. Focused on a single task at a time.
- Validator — Read-only agent that verifies the implementation meets acceptance criteria. Runs tests and checks.
- Documenter — Generates documentation in
app_docs/for completed features.
The manager spawns agents as kiro-cli processes:
kiro-cli chat --agent krew-lead --no-interactive --trust-all-tools "Process issue #N from repo owner/name. Worktree name: issue-N-<pid>"
Each agent runs with environment variables: ISSUE_NUMBER, REPO, KAIRON_WATCHER_PID, WORKTREE_PATH and KAIRON_INPUT_DOCUMENT.
Every agent process also has .kairon/bin (a directory holding only a kairon symlink to the running binary) first on its PATH, and KAIRON_BIN set to that binary's path, so agents always run the kairon binary file that started Kairon, even when none is installed. See docs/agent-environment.md.
Before it creates a worktree or starts a process, the manager runs the watcher boundary: it re-reads the issue from GitHub and checks the provenance hash on the body it read. Only a body whose hash is valid is implemented. The manager writes the verified issue (number, title, canonical body and hash) to .kairon/inputs/issue-<number>.yaml and passes its absolute path in KAIRON_INPUT_DOCUMENT. krew-lead reads requirements from that file, treats the body as untrusted data, and does not re-fetch the issue body. An issue whose body has no marker (none) or does not match its marker (mismatch) is never implemented: it goes to intake if one is wired, otherwise it is punched out with an explanatory comment and the <label>-failed label. Terminal-pending issues are skipped. See docs/boundary.md, including the rollout note: until a planner stamps markers, unmarked kairon issues are punched out.
Each issue is processed in an isolated git worktree named issue-<number>-<pid> (where <pid> is the watcher process ID):
.kairon/scripts/worktree-create.sh <name>— creates.worktrees/<name>/on branchspec/<name>.kairon/scripts/worktree-merge.sh <name>— merges back, removes worktree, deletes branch- Orphaned worktrees (from crashed processes) are cleaned up automatically by checking if the PID is still running
Branching from a fresh origin/<branch>: the long-lived main checkout is never fast-forwarded, so worktree-create.sh does not branch from its local HEAD. Instead it fetches the integration branch from origin and creates spec/<name> from origin/<branch> (with --no-track, so the spec branch has no upstream). Issues picked up after other PRs have merged therefore start from the latest remote tip.
The integration branch is resolved in this order (first non-empty wins):
KAIRON_BASE_BRANCHenvironment variablebase_branchin.kairon/config.yaml- The default branch of
origin(origin/HEAD) main
Fallbacks:
- Offline / fetch fails: if a cached
origin/<branch>already exists, the script warns and branches from that last-known ref. If none exists, it exits with an error. - No
originremote: the script warns and branches from localHEAD(useful for local-only repositories).
base_branch only controls which branch the worktree starts from. Pull requests are still created against the repository's default branch.
| State | Label | Description |
|---|---|---|
| Ready | kairon |
Watcher will pick up this issue |
| Processing | — | Agent spawned and working |
| Done | kairon-done |
PR created successfully |
| Failed | kairon-failed |
Exhausted retries, or an unrecoverable failure. An explanatory comment is posted before this label is added |
Issues with kairon-done or kairon-failed labels are excluded from polling. The done/failed labels are derived from the configured label (e.g., if label is my-label, done becomes my-label-done).
When an issue fails, Kairon posts a comment that says which stage failed, how many attempts were made, what was expected and what was found, and only then adds the <label>-failed label. An issue is never labeled failed without a stated reason. To restart a failed issue, fix the cause and remove the <label>-failed label. See docs/failure-contract.md.
The watcher can hold an issue back until the issues it depends on are closed, enforcing a serial order across a chain of related work. Dependencies are declared in the issue body and parsed by internal/watcher/dependencies.go.
Parsing is format-sensitive — only the following patterns are recognized (all case-insensitive, and every reference must use #<number>):
| Format | Example |
|---|---|
Depends on Issue #N |
Depends on Issue #314 |
Blocked by: #N |
Blocked by: #314 |
Depends on [Issue #N] |
Depends on [Issue #314] |
Dependencies: #N, #M, ... |
Dependencies: #315, #317 |
The Dependencies: form must be a single line — the numbers have to follow the Dependencies: keyword on the same line, comma-separated, each prefixed with #. A markdown bullet list under a ## Dependencies header is not parsed:
<!-- NOT parsed — the watcher sees zero dependencies and schedules immediately -->
## Dependencies
- #314 must merge first
- #317 must merge first
<!-- Parsed correctly -->
## Dependencies
Dependencies: #314, #317Dependency declarations inside fenced code blocks (backtick or tilde fences, including fences indented up to three spaces) are ignored, matching the planner dependencies guard.
A dependency is satisfied only once the referenced issue is closed. Any dependency that is still open — or that cannot be fetched — marks the dependent issue as blocked, so it stays out of the processing queue until every prerequisite closes.
The system has two layers of retry:
- Global retries (watcher level) — Persisted in
.kairon/retries/issue-<number>.count. The watcher skips issues that have reachedmax_retriesattempts and survives process restarts. - Per-agent retries (manager level) — When an agent exits with a non-zero code, the manager retries with exponential backoff (delay = retry count × 1 second) up to
max_retries.
After exhausting retries, Kairon posts an explanatory comment and then labels the issue <label>-failed. The comment is always posted before the label. Removing the label restarts the issue with a fresh retry budget. The two-tier model (bounded retry, then punch-out), the max_retries and max_qa_retries semantics, and the restart flow are described in docs/failure-contract.md.
Agent configs live in .kiro/agents/. Each agent has a JSON config and a prompt markdown file.
krew-lead.json (orchestrator):
{
"name": "krew-lead",
"tools": ["read", "shell", "subagent", "todo_list"],
"toolsSettings": {
"subagent": {
"trustedAgents": ["architect", "builder", "validator", "documenter"]
}
},
"model": "claude-sonnet-4"
}builder.json (worker):
{
"name": "builder",
"description": "Focused engineering agent that executes ONE task at a time.",
"prompt": "file://./builder-prompt.md",
"tools": ["read", "write", "shell"],
"allowedTools": ["read", "write", "shell"],
"model": "claude-sonnet-4"
}validator.json (read-only verifier):
{
"name": "validator",
"description": "Read-only validation agent that verifies task completion.",
"prompt": "file://./validator-prompt.md",
"tools": ["read", "shell"],
"allowedTools": ["read", "shell"],
"toolsSettings": {
"shell": { "autoAllowReadonly": true }
},
"model": "claude-sonnet-4"
}Kairon uses the gh CLI for all GitHub operations — no API tokens to configure. Ensure you're authenticated:
gh auth login
gh auth statusThe system calls:
gh api graphql— poll for labeled issues (see below)gh issue view— read issue detailsgh issue edit— add labels (kairon-done,kairon-failed)gh pr create— create pull requests
Each poll makes one gh api graphql call per page and follows the cursor until GitHub reports no further page. A page holds 50 issues, but that is a page size, not a limit: the poll reads every open issue that carries the configured label (and, if set, the configured assignee), however many there are. Each page includes the issue's labels and its first 100 comments, so for most issues the done, failed and punch-out checks need no extra call. Three cases cost more. First, an issue with 100 or more comments is re-read in full with gh issue view, because a newer punch-out comment could be hidden past the first 100. Second, a punch-out comment needs a gh api user call to check who wrote it: the issues read from the page share one such call per poll, and each re-read issue that has a punch-out comment makes its own. Third, an issue that looks like it is waiting for its failed label (terminal-pending) gets one label-events lookup, and a re-read issue can make its own lookup too.
- Oldest first. Issues are requested in ascending creation order and sorted by issue number before they are selected, so the oldest issue is always considered first.
- No
--search. The poll reads the issues connection directly and never uses GitHub's search index, which can lag behind label changes. An issue you just labeled is seen on the next poll. - All or nothing. If any page fails (a network error, a rate limit, a GraphQL error or an unreadable response), the whole poll fails and returns no issues. The watcher then leaves the issues it already tracks as they are and tries again on the next poll. A partial page set is never used, so a failed poll cannot make the watcher forget an issue or skip one.
See LICENSE.