kilo-loop/README.md
2026-09-13 13:51:43 +03:00

270 lines
10 KiB
Markdown

# 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 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)
```bash
npm install
```
## Quick start
### Installed as a submodule (recommended)
The launcher resolves the git root of the current directory, installs
`node_modules` on first use, and picks up `<repo>/.kilocode-loop/goal.json` when
no goal is passed:
```bash
# from anywhere inside the repo (or a submodule — it scopes to that repo)
tools/kilocode-loop/kilo-loop # uses .kilocode-loop/goal.json, 5 iterations
tools/kilocode-loop/kilo-loop -n 3 # override the iteration count
tools/kilocode-loop/kilo-loop --goal plans/another-goal.md
tools/kilocode-loop/kilo-loop --dry-run # offline rehearsal
# same command from anywhere, once the launcher is on PATH
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). The goal lives in
`<repo>/.kilocode-loop/goal.json` — edit it, or point `--goal` somewhere else.
### Standalone
```bash
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>.
## Controls
| Where | Key / button | Effect |
| --- | --- | --- |
| Terminal | `X` | Abort the loop **after** the current session finishes |
| Terminal | `P` | Pause / resume before the next iteration |
| 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) |
| Dashboard | Pause / Abort after session / Stop now | Same controls from the browser |
| Dashboard | question modal | Answer the pending questions; answers go into the next session |
## 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`:
```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 size** — `tokens.total` of the last step (how full the window is)
- **topic** — `progress.topic`
- **% complete** — `progress.percent`, or the ratio of completed goal stages
- **next stage** — `progress.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
runs/<runId>/
state.json # full run state (for the dashboard)
events.jsonl # every log line
report.md # end-of-run summary
iterations/
01-prompt.md # exact prompt sent
01-report.json
01-report.md
```
## Goal files
`--goal-text "..."` — a single implicit stage.
`--goal file.json`:
```json
{
"title": "Stabilise the workflows module",
"description": "…",
"stages": [{ "id": "stage-1", "title": "Remove legacy libs", "details": "…" }]
}
```
`--goal file.md` — the first `#` heading is the title, each `- [ ]` / `- [x]`
item is a stage:
```markdown
# Stabilise the workflows module
- [ ] Remove legacy libs
- [x] Rewire the builder
```
## 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)
--session-mode <mode> continue (one growing session) | fresh
--hitl <mode> always | on-question | off
--max-iteration-minutes <n>
--context-warn-tokens <n>
--prompt-extra <file> extra instructions for every iteration
Dashboard
-p, --port <n> default 7999
--host <host> default 127.0.0.1
--keep-open keep the dashboard after the loop
Utility
--dry-run simulate the agent (no API calls)
--resume <runId> reuse a previous run's session + stage progress
--run-id <id>
--quiet suppress the live console stream
--no-color
```
Config can also live in `<project>/.kilocode-loop/config.json` (CLI wins).
## Dashboard API
| Method | Path | Purpose |
| --- | --- | --- |
| `GET` | `/api/state` | Current run snapshot |
| `GET` | `/api/events` | SSE stream (`state` + `log` messages) |
| `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) |
| `POST` | `/api/control` | `{ "action": "pause"\|"resume"\|"abort-after-current"\|"stop" }` |
| `POST` | `/api/answer` | `{ "answers": [{ "question": "…", "answer": "…" }] }` |
## How it drives the Kilo CLI
`kilo run --format json` emits one JSON object per line:
```jsonc
{"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.
- `--dry-run` performs no API calls and no file changes (beyond its own
`progress.json`).
## Adding it to the main project as a submodule
This repo is standalone and has its own history. To pull it into another repo:
```bash
# 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/simulator.mjs --dry-run agent
public/ dashboard UI
examples/ sample goals
test/ node:test unit tests (npm test)
```