kilo-loop/README.md
2026-09-13 22:38:01 +03:00

33 KiB
Raw Blame History

kilocode-loop

Goal-driven autonomous loop runner for the Kilo CLI (kilo run), with a live Express dashboard, per-iteration reports (tokens / cost / context / topic / % / next stage), streamed section headings for every action, terminal controls and human-in-the-loop between sessions.

It also enforces its own safety net: independent verification gates (a stage is not done until the loop's --verify command exits 0), a destructive-command guard, per-iteration git checkpoints with revert, budget/stall limits, secret redaction, notifications and an optional reviewer pass.

By default every iteration runs in its own fresh session (small context, cheaper, more targeted): the loop carries only a curated handoff — goal, current stage, previous summary, operator answers and a handoff packet the agent prepares. Turn on Use shared context between iterations (dashboard checkbox, --shared-context, or S in the terminal) to keep one growing session instead.

It is a self-contained mini-app: one Node process spawns kilo run --format json for every iteration, parses the event stream, and turns the agent's own progress.json into the operator report.

┌─ loop ───────────────────────────────────────────────────────────────────┐
│ for i in 1..N:                                                           │
│   ┌─ section: Iteration i/N — <stage> ───────────────────────────────┐   │
│   │  ▸ Read src/foo.vue        (tool → section heading)              │   │
│   │  ▸ Edit src/foo.vue                                              │   │
│   │  ▸ Run pnpm typecheck                                            │   │
│   └──────────────────────────────────────────────────────────────────┘   │
│   report: tokens · cost · context · topic · % · next stage               │
│   human-in-the-loop: ask the operator if the agent needs a decision      │
└──────────────────────────────────────────────────────────────────────────┘

Requirements

  • Node.js ≥ 20 (developed on Node 24)
  • The Kilo CLI on PATH (kilo / kilocode), authenticated
  • A git repository to work in (the agent never commits)
npm install

Quick start

The launcher resolves the git root of the current directory and installs node_modules on first use. Run it with no --goal to open the dashboard launcher and pick a plan in the UI:

# from anywhere inside the repo (or a submodule — it scopes to that repo)
tools/kilocode-loop/kilo-loop                 # open the dashboard launcher (pick a plan in the UI)
tools/kilocode-loop/kilo-loop --list-goals    # print discovered plans + state, then exit
tools/kilocode-loop/kilo-loop --goal .kilocode-loop/goal.json -n 5   # run one directly
tools/kilocode-loop/kilo-loop --dry-run --goal examples/goal.example.json  # offline rehearsal

# same command from anywhere, once the launcher is on PATH
kilo-loop
kilo-loop --goal plans/another-goal.md -n 5

Per-repo defaults live in <repo>/.kilocode-loop/config.json (agent, iterations, session mode, HITL, port, context threshold, default verify). No goal is injected by default — pass --goal to run one directly, or pick it in the dashboard.

Standalone

npm install

# Real run: 5 iterations of the code-design agent against ./your-repo
node bin/kilocode-loop.mjs \
  --goal /path/to/your-repo/.kilocode-loop/goal.json \
  -n 5 \
  --project /path/to/your-repo \
  --agent code-design \
  --port 7999

# Offline rehearsal: no API calls, exercises the loop, reports, dashboard and HITL
node bin/kilocode-loop.mjs --dry-run \
  --goal examples/goal.example.json -n 5 --project /tmp/demo --port 7999

Then open the dashboard at http://127.0.0.1:7999.

Pre-flight confirmation

On an interactive terminal a direct run (kilo-loop --goal …) does not start immediately: it prints a summary of the resolved scenario and waits for confirmation. Launcher mode (no --goal) skips this — nothing runs until you press Start run in the dashboard.

╭─ About to run ───────────────────────────────────────────────────────╮
│ Goal         Workflows module — review, simplification & hardening   │
│ Source       /home/.../.kilocode-loop/goal.json                      │
│ Project      /home/.../wiz4apps                                      │
│ Agent        code-design                                             │
│ Iterations   5                                                       │
│ Context      fresh session per iteration (handoff)                   │
│ HITL         on-question                                             │
│ Auto-approve yes                                                     │
│ Dashboard    http://127.0.0.1:7999                                   │
│ Mode         live                                                    │
╰──────────────────────────────────────────────────────────────────────╯

  1. [ ] Close Phase 1 residuals
  2. [ ] Phase 2 — architecture convergence residuals
  ...

  Start this run? [Y/n]

Enter/y starts; anything else cancels without starting anything (no run dir, no dashboard, no sessions). A missing/broken goal file fails here, before anything is created.

  • --no-confirm / --yes — skip the prompt and start immediately.
  • Non-interactive stdin/stdout (pipes, CI, --dry-run harness) always starts immediately.
  • Set "confirm": false in .kilocode-loop/config.json to make auto-start the default for a repo.

Controls

Where Key / button Effect
Terminal X Abort the loop after the current session finishes
Terminal P Pause / resume before the next iteration
Terminal S Toggle shared context (fresh sessions ⇄ one growing session)
Terminal Q Stop now (kills the current kilo session)
Terminal ? Print shortcut help
Terminal typing + Enter Answer a pending question (number keys pick an option)
Terminal Ctrl+C (SIGINT) Stop the run and exit. It works whether or not the dashboard is open; a second Ctrl+C force-exits immediately
Dashboard Pause / Abort after session / Stop now Same controls from the browser
Dashboard Use shared context between iterations Off (default) = fresh session + handoff per iteration; on = one growing session
Dashboard Revert before #N Restore the checkpoint taken before iteration N (confirmation required)
Dashboard Report Download report.md for the active run
Dashboard question modal Answer the pending questions (option buttons become editable answers); answers go into the next session

Top-bar activity indicator

The header doubles as a status light, driven purely by CSS animations (JS only toggles a topbar--<state> class):

  • running / starting — a glowing blue→green segment sweeps along the bottom edge over a faint travelling sheen, so it is obvious work is in progress;
  • waiting-answer — pulsing purple, to draw attention to a pending question;
  • paused — a static amber line (deliberately not animating);
  • error / done-with-errors / stale — the bar is tinted light red with a pulsing red line;
  • aborted / stopped-budget / stalled / stopped-guard — a light amber tint (warning, not failure);
  • done — a static green line; idle — plain header.

All movement is disabled under prefers-reduced-motion: reduce (the state colour stays). See public/styles.css (topbar--*) and topbarState() in public/app.js.

Fresh sessions vs shared context

Default (sharedContext: false / checkbox off) — each iteration is an independent kilo run with no session history. To keep the work coherent, the runner carries forward only:

  • the goal, its stages and the current stage;
  • the previous iteration's progress.json (topic, summary, next stage, percent);
  • all operator answers collected so far;
  • the list of files changed so far this run;
  • the previous iteration's handoff packet.

The handoff packet is a bounded markdown file (.kilocode-loop/handoff.md, ~120 lines max) that the agent writes at the end of every iteration: current state, decisions & constraints, next steps, prepared data, open questions. The runner embeds it into the next prompt and then deletes the file, so a forgotten packet can never leak into a later iteration. Older packets are archived per iteration in runs/<runId>/iterations/<NN>-handoff-in.md.

Shared context (sharedContext: true / checkbox on) — one growing session is continued across iterations (--session <id>), so the whole history is available but the context (and cost) grows every iteration. The handoff sections are then skipped because the session already has the history.

The toggle is live: the dashboard checkbox (POST /api/control { "action": "set-shared-context", "value": true|false }) and S in the terminal apply from the next iteration onward.

How a report is produced

Each iteration the runner writes a prompt that embeds a contract: at the end of the session the agent must write <project>/.kilocode-loop/progress.json:

{
  "iteration": 2,
  "topic": "Rework SettingsByokSection",
  "stageId": "stage-2",
  "stageStatus": "in_progress",
  "percent": 40,
  "nextStage": "Verify layout and dark theme",
  "summary": "Replaced the card grid with a q-select + config panel. Typecheck passes.",
  "filesChanged": ["src/components/settings/SettingsByokSection.vue"],
  "needsInput": false,
  "questions": []
}

From that + the CLI event stream the runner derives:

  • iteration number — loop counter
  • tokens / cost — summed from every step_finish event
  • context sizetokens.total of the last step (how full the window is)
  • topicprogress.topic
  • % completeprogress.percent, or the ratio of completed goal stages
  • next stageprogress.nextStage, or the first not-done stage
  • files changed — git delta since the iteration started, plus filesChanged

If the agent forgets the contract, the runner falls back to the goal stages and scrapes question-looking lines out of the agent's text.

Reports on disk

<project>/.kilocode-loop/
  progress.json                 # the agent's machine-readable report
  answers.json                  # operator answers from HITL
  handoff.md                    # context packet for the next fresh iteration
  review.json                   # reviewer verdict (when --review is on)
  runs/<runId>/
    state.json                  # full run state (for the dashboard)
    events.jsonl                # every log line (secrets redacted)
    report.md                   # end-of-run summary
    checkpoints/
      iter-01.json              # HEAD + untracked list for iteration 1
      iter-01.patch             # `git diff HEAD` at the checkpoint
    iterations/
      01-prompt.md              # exact prompt sent
      01-review-prompt.md       # reviewer prompt (when --review is on)
      02-handoff-in.md          # handoff packet consumed by iteration 2
      01-report.json
      01-report.md

Verification gates, guards, budgets and checkpoints

Since v0.2 the loop no longer trusts the agent's own "done". Every safety feature is opt-out, and all of them apply to the loop, not the agent's prompt.

Independent verification (the loop runs the check itself)

A stage is not marked done until a verify command the loop itself runs exits 0. Set it globally, per stage, or both (the stage wins):

{
  "title": "Ship the parser",
  "stages": [
    { "id": "s1", "title": "Rebuild the parser", "verify": "pnpm test", "acceptance": "no failures" }
  ]
}

Markdown goals take directives after :::

- [ ] Rebuild the parser :: verify: pnpm test :: acceptance: no failures

When verify fails, the stage is recorded as blocked, the failing output is fed back into the next iteration's prompt, and completedStages is not advanced. Set --no-sync-goal to stop the loop from ticking [x] in a markdown goal file.

Destructive-command guard

Two layers:

  1. Pre-execution — the loop injects deny rules for destructive bash patterns (git commit, git push, git reset --hard, git clean -f, npm|pnpm publish, history rewrites, raw disk writes, recursive deletes of system/home paths, …) into the child kilo run via KILO_CONFIG_CONTENT, so --auto still denies them. Skipped when the project already sets a scalar permission.bash.
  2. Runtime — every tool_use is scanned; a match aborts that session immediately (damage control) and, by default, the run continues with the next iteration: the still-incomplete stage is retried and the safety note is fed back into the next prompt (## Loop safety feedback), so the agent can reach the same result a safer way. In shared-context mode the aborted session is replaced by a fresh one with a synthesized handoff.

Use --guard-stop to restore the old behaviour (stop the whole run on the first hit, status stopped-guard) and --max-guard-blocks <n> (default 3) to cap how many consecutive guard-blocked iterations are tolerated before the loop stops.

rm -rf is path-aware and cd-aware in both layers: recursive deletes whose targets are all strictly inside a temp directory (os.tmpdir(), /tmp, /var/tmp) are allowed, so routine scratch cleanup no longer blocks a run — including the common agent pattern cd /tmp && rm -rf <scratch>, where the relative target is resolved against the temp cwd. Deleting the temp root itself, a relative path with no known temp cwd, or any non-temp absolute path is still blocked; cd /repo && rm -rf src stays blocked. Mixed commands (one temp target plus one unsafe target) are blocked. Pre-execution denies only the catastrophic recursive targets (~, $HOME, /home/*, /etc*, /usr*, /opt*, /root*, /boot*, /srv*, .git/), while the runtime scanner remains the catch-all for everything else.

Checkpoints and revert

Before each iteration the loop captures a checkpoint under runs/<runId>/checkpoints/: HEAD, git diff HEAD and the untracked-file list. Nothing is committed. Revert before #N on the dashboard (or POST /api/revert { iteration }) restores tracked files to HEAD, re-applies the checkpoint's own diff and deletes untracked files created afterwards. Revert is destructive and always behind a confirmation; it does not touch submodule contents or committed history. Disable with --no-checkpoint.

Budgets and stall detection

  • --max-cost <usd> / --max-tokens <n> — stop with status stopped-budget.
  • --max-stale-iterations <n> — stop with status stalled after N iterations that change no files and advance no stage.
  • --max-iteration-minutes <n> — per-iteration hard timeout (already existed).

Auto-recovery from model/network errors

If kilo run dies because the provider connection is reset, the iteration is retried instead of failing and losing the turn. Detection covers Connection reset by [peer|server], ECONNRESET/ETIMEDOUT/EPIPE, socket hang up, fetch failed, premature close, HTTP 429/5xx, and rate limit/overloaded/timed out.

  • Each retry continues the interrupted session (--session <id>), so work already done in the turn is preserved (in shared mode the run session; in fresh mode the session the failed attempt created).
  • Exponential backoff with jitter: --retry-delay (default 2000 ms), doubling per attempt up to --retry-max-delay (default 60000 ms).
  • --max-retries <n> (default 3); --no-retry disables it entirely.
  • Timeouts (--max-iteration-minutes), a command-guard hit, or a user Stop are not retried as transient errors. A guard hit instead aborts the session and the loop continues with the next iteration (see --guard-stop / --max-guard-blocks).
  • The dashboard marks the iteration retrying (auto-retry ×N) and the report records the attempt count and the last transient reason.
  • Optionally notify on retries with --notify … (event model-retry).

Resuming a stopped run

When a run does not reach done (guard, error, stall, budget, abort), the CLI prints the exact command to continue it:

  Resume this run: kilo-loop --goal <goal> --resume <runId>

--resume <runId> reloads that run's saved state: stages it already reported as done are skipped, lastPercent is carried over, and — when the previous run was interrupted by the guard — the safety note is injected into the first iteration again. --resume last (or latest) resolves to the most recent saved run, so the everyday recovery command is simply:

kilo-loop --goal .kilo/plans/<plan>.md --resume last

Saved runs live under .kilocode-loop/runs/<runId>/; kilo-loop report --last shows the most recent one without starting anything.

Secret redaction

Every log line, state.json, per-iteration report and the run report is scrubbed before it is written: bearer/Basic headers, sk-/ghp_/xox/AWS/JWT tokens, token=/password: values and URL userinfo. Disable with --no-redact.

Notifications

--notify <targets> (space/comma separated) sends out-of-band alerts for question, verify-failed, guard, budget, stalled and run-done:

Target Behaviour
https://hooks.slack.com/... Slack incoming webhook ({ text })
https://ntfy.sh/<topic> or ntfy://<host>/<topic> ntfy POST + Title header
telegram://<botToken>/<chatId> Telegram sendMessage
any https:// URL generic JSON payload { event, title, text, sentAt }

Reviewer pass

--review runs one extra reviewer session (agent --review-agent, default the run agent) after a stage passes verify. The reviewer inspects the working tree and writes .kilocode-loop/review.json with { verdict: "pass"|"block", … }; a block un-advances the stage and raises the reviewer's questions. Skipped in --dry-run.

Hybrid context

In shared-context mode, --auto-fresh-tokens <n> switches to a fresh session (with a synthesized handoff) once the context passes that size, instead of only warning.

Report subcommand & exit codes

kilo-loop report                 # most recent run's report.md
kilo-loop report <runId> --json  # redacted run state
kilo-loop report --last -C /repo

report is read-only: it starts nothing and makes no API calls.

Exit codes: 0 done, 1 done-with-errors, 2 aborted, 3 budget, 4 stalled, 5 stopped-guard.

Kilo vs opencode

The loop drives a CLI, not a library: it spawns <bin> run --format json and parses the line-delimited JSON event stream. opencode is the same lineage as the Kilo CLI (near-identical run surface), so the wire format is compatible:

Shared with opencode
/run --format json NDJSON events tool_use, step_finish, text, reasoning, error; part.tool, part.state.input, part.tokens{cache}, part.cost, sessionID
Flags --agent, --model, --variant, --thinking, --session, --continue, --title, --format json

Two things differ, and the tool adapts to them automatically:

kilo opencode
Auto-approve flag --auto --dangerously-skip-permissions
Inline config env (guard) KILO_CONFIG_CONTENT OPENCODE_CONFIG_CONTENT
Project config for permission.bash kilo.json, .kilo/kilo.json opencode.json, .opencode/opencode.json

Usage:

# point the loop at opencode (runtime auto-detected from the binary name)
KILO_BIN=opencode kilo-loop --goal goal.md -n 5 \
    --agent build --model anthropic/claude-sonnet

# or be explicit
kilo-loop --runtime opencode --agent build ...

--runtime defaults to auto, which detects opencode from the binary name. Agent names and model IDs are CLI-specific: set --agent/--model to something defined in your opencode config (the Kilo default code-design is not an opencode agent). Everything the loop itself provides — verification gates, checkpoints, budgets, redaction, notifications, the dashboard and report — is runtime-independent.

Goal files

--goal-text "..." — a single implicit stage.

--goal file.json:

{
  "title": "Stabilise the workflows module",
  "description": "…",
  "stages": [
    {
      "id": "stage-1",
      "title": "Remove legacy libs",
      "details": "…",
      "verify": "pnpm test",
      "acceptance": "test suite is green",
      "model": "provider/fast-model",
      "agent": "code",
      "variant": "high"
    }
  ]
}

Stage fields: id, title, details, done, and optional verify, acceptance, model, agent, variant (per-stage routing overrides the run defaults). --goal file.md — the first # heading is the title, each - [ ] / - [x] item is a stage, and :: verify: … :: acceptance: … adds directives:

# Stabilise the workflows module

- [ ] Remove legacy libs :: verify: pnpm test
- [x] Rewire the builder

Dashboard launcher (pick a plan in the UI)

Run kilo-loop without --goal and the dashboard opens in launcher mode: no run is started until you pick one.

kilo-loop                 # launcher only
kilo-loop --list-goals    # print every discovered plan + its state, then exit

The launcher lets you:

  • pick a plan from a dropdown — plans are discovered under .kilocode-loop/goals/**, the legacy .kilocode-loop/goal.{json,md}, and any markdown plan under plans/ or .kilo/plans/ that contains - [ ] items;
  • see each plan's state (done/total, %, type, last run status/iterations/cost);
  • set the verify command per stage (kept in the goal file; - [ ] Title :: verify: cmd for markdown, stage.verify for JSON) or once for all plans (stored in .kilocode-loop/config.json);
  • Mark 100% all stages done (- [x] for markdown, done: true for JSON) for a plan that was finished outside the loop, or Clear done to reopen it;
  • Delete plan — moves the plan file to the gitignored .kilocode-loop/trash/<timestamp>-<name> (recoverable; nothing is committed). The UI asks for confirmation first;
  • set iterations / agent / HITL / dry-run and Start run;
  • write an optional custom instruction (textarea). It is added to the top of every iteration prompt (under ## Operator instructions for this run, before the goal) and the run header shows custom instruction while it is active. It maps to the same promptExtra as the CLI --prompt-extra; leaving the field empty keeps any instruction coming from the CLI or config.

After a run finishes, New run returns to the launcher (the plan list is refreshed with the new completion state). POST /api/start refuses to start a second run while one is active.

Reaching the dashboard from a phone (same Wi-Fi)

By default the dashboard binds loopback (127.0.0.1), so only this machine can open it. --lan binds every interface (0.0.0.0) and the console prints the machine's real LAN address to open on another device:

kilo-loop --lan                       # or set "lan": true in .kilocode-loop/config.json
#   Dashboard http://192.168.1.106:7999  (Ctrl+Click to open)

Then open that URL on the phone (same Wi-Fi/router). A wildcard bind is never shown as a URL; the tool resolves the primary non-virtual interface (docker/vpn bridges are filtered out). An explicit --host <ip> still wins, and --local forces loopback back on.

Security: the dashboard has no authentication and exposes control endpoints (start/stop/pause, revert which deletes untracked files). Only use --lan on a trusted network, and prefer --local (or --host 127.0.0.1) on shared/public Wi-Fi.

Scaffolding a plan (new-plan)

Plans must contain - [ ] checkbox stages to be discovered at all. Generate one from the project's template instead of hand-writing the format:

kilo-loop new-plan "My plan title"          # writes .kilo/plans/<epoch-ms>-<slug>.md
kilo-loop new-plan "My plan title" --print  # preview without writing

The template is <project>/.kilo/templates/plan.md (a sensible default is used if the project has none). Optional flags: --slug <s>, --epoch <ms>, --dir <d>, --project <p>, --print, --force. The scaffolder is also available directly at tools/kilocode-loop/new-plan.mjs.

CLI reference

Goal
  --goal <file>              .json goal or .md checklist
  --goal-text "<text>"       inline goal
  -n, --iterations <N>       loop count (default 5)

Execution
  -C, --project <dir>        repository the agent works in (default cwd)
  -a, --agent <name>         Kilo agent (default code-design)
  -m, --model <id>           model override (provider/model)
      --variant <name>       reasoning variant (e.g. high, max)
      --thinking             capture reasoning parts
      --auto / --no-auto     auto-approve permissions (default: auto)
      --shared-context       one growing session across iterations (costly)
      --no-shared-context    fresh session per iteration + handoff (default)
      --session-mode <mode>  legacy alias: continue = shared, fresh = no shared
      --hitl <mode>          always | on-question | off
      --runtime <mode>       auto | kilo | opencode (auto-detects from the CLI binary)
      --max-iteration-minutes <n>
      --context-warn-tokens <n>
      --prompt-extra <file>  extra instructions for every iteration

Verification & safety
      --verify "<cmd>"       loop-run check; a stage is not done until it exits 0
      --verify-timeout-minutes <n>
      --checkpoint / --no-checkpoint
      --guard / --no-guard   deny destructive bash; a hit aborts the session
      --guard-stop           stop the run on the first guard hit (default: continue)
      --max-guard-blocks <n> consecutive guard hits tolerated (default 3)
      --redact / --no-redact
      --max-cost <usd>
      --max-tokens <n>
      --max-stale-iterations <n>
      --retry / --no-retry   retry an iteration on a model connection reset (default on)
      --max-retries <n>      retry attempts per iteration (default 3)
      --retry-delay <ms>     first backoff delay (default 2000, doubles)
      --retry-max-delay <ms> backoff cap (default 60000)
      --review / --review-agent <name>
      --auto-fresh-tokens <n>
      --sync-goal / --no-sync-goal
      --force                run even when every stage is marked done
      --list-goals           list discovered plans + state, then exit
      --notify <targets>

Dashboard
  -p, --port <n>             default 7999
      --host <host>          default 127.0.0.1
      --lan                  bind 0.0.0.0 so a phone on the same Wi-Fi can open it
      --local                force loopback only (overrides a config "lan": true)
      --keep-open            keep the dashboard after the loop

Utility
  new-plan "<title>"         scaffold .kilo/plans/<epoch-ms>-<slug>.md from .kilo/templates/plan.md
      --dry-run              simulate the agent (no API calls)
      --resume <runId|last>  reuse a previous run's session + stage progress
      --run-id <id>
      --quiet                suppress the live console stream
      --no-color
      --no-confirm, --yes    start immediately (skip the pre-flight confirmation)

Config can also live in <project>/.kilocode-loop/config.json (CLI wins).

Dashboard API

Method Path Purpose
GET /api/state Current run snapshot ({status:"idle", idle:true} when no run is active)
GET /api/events SSE stream (state + log messages)
GET /api/goals Discoverable plans/goals with per-stage completion and last run
POST /api/goals/verify { "file": "…", "stageIndex": N, "verify": "cmd" } — set a stage verify (stageIndex omitted = global, JSON goals only)
POST /api/goals/verify-global { "verify": "cmd" } — default verify for all runs (.kilocode-loop/config.json)
POST /api/start { "file": "…", "iterations": N, "agent": "…", "hitl": "…", "dryRun": bool } — start a run from the launcher
GET /api/runs List saved runs
GET /api/runs/:id Load a saved run (with its log)
GET /api/runs/:id/report/:iter Iteration report (markdown)
GET /api/runs/:id/export Full run report (markdown)
GET /api/runs/:id/diff/:iter Checkpoint patch captured before iteration N
GET /api/checkpoints Active run's checkpoints (?run=<id> for a saved run)
GET /api/runs/:id/checkpoints Saved run's checkpoints
POST /api/control { "action": "pause"|"resume"|"abort-after-current"|"stop" } and { "action": "set-shared-context", "value": true|false }
POST /api/revert { "iteration": N } — restore the checkpoint before iteration N
POST /api/answer { "answers": [{ "question": "…", "answer": "…" }] }

How it drives the Kilo CLI

kilo run --format json emits one JSON object per line:

{"type":"step_start","timestamp":…,"sessionID":"…","part":{…}}
{"type":"tool_use","part":{"tool":"edit","state":{"status":"completed","input":{…}}}}
{"type":"text","part":{"type":"text","text":"…"}}
{"type":"step_finish","part":{"cost":0.0021,"tokens":{"total":14411,"input":14025,"output":2,"reasoning":0,"cache":{"read":384,"write":0}}}}
{"type":"error","error":…}
  • The runner captures sessionID from the first event and passes --session <id> on later iterations, so --session-mode continue keeps one growing context (and --session-mode fresh starts a new one each time).
  • Unattended runs need --auto: without it, non-interactive permission requests are auto-rejected by the CLI ("pass --auto for autonomous use"). --auto is on by default; deny destructive permissions in the project kilo.json if needed.
  • --thinking is required for reasoning events to be emitted.

Safety

  • The runner never commits or pushes; the agent is instructed not to either.
  • --auto approves permissions the project does not explicitly deny — review kilo.json / .kilo permissions before unattended runs. The command guard adds deny rules for destructive bash patterns on top of them.
  • --dry-run performs no API calls and no file changes (beyond its own progress.json); it also skips verify commands and the reviewer pass.
  • Logs, state and reports are redacted before they are persisted; use --no-redact only when you trust the target. --no-guard and --no-checkpoint trade safety for speed.
  • report and --json are read-only.

Adding it to the main project as a submodule

This repo is standalone and has its own history. To pull it into another repo:

# 1. create an empty repo on Forgejo (e.g. w4c-agent/kilocode-loop), then:
cd /path/to/main-repo
git submodule add ssh://git@localhost:13022/w4c-agent/kilocode-loop.git tools/kilocode-loop
git commit -m "chore: add kilocode-loop submodule"

Because it is a submodule, node_modules/ stays local and is never committed.

Layout

bin/kilocode-loop.mjs   entry point (wires config → orchestrator → dashboard)
src/config.mjs          CLI/config parsing
src/orchestrator.mjs    the loop, controls, HITL, report assembly
src/kilocode.mjs        `kilo run` driver + JSONL event parser
src/goal.mjs            goal files, agent contract, progress/percent/questions
src/state.mjs           run state, persistence, log stream
src/reports.mjs         console/markdown reports, formatting
src/console.mjs         live console renderer
src/terminal.mjs        TTY controls
src/server.mjs          Express + SSE dashboard API
src/verify.mjs          independent verify command runner
src/goals.mjs           goal/plan registry, summaries, verify editing
src/controller.mjs      run lifecycle (start multiple plans from the dashboard)
src/guard.mjs           destructive-command guard (pre-execution + runtime)
src/checkpoint.mjs      per-iteration git checkpoints + revert
src/redact.mjs          secret redaction for persisted logs/state/reports
src/notify.mjs          webhook/ntfy/telegram notifications
src/reportcli.mjs       `kilo-loop report` subcommand
src/simulator.mjs       --dry-run agent
public/                 dashboard UI
examples/               sample goals
test/                   node:test unit tests (npm test)