@loops-adk/core 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -13
- package/assets/logo.png +0 -0
- package/bin/loops.mjs +5 -5
- package/dist/{agent-sdk-RF5VJZAT.js → agent-sdk-4QJDWM7N.js} +3 -3
- package/dist/{agent-sdk-RF5VJZAT.js.map → agent-sdk-4QJDWM7N.js.map} +1 -1
- package/dist/api.d.ts +177 -3
- package/dist/api.js +26 -10
- package/dist/api.js.map +1 -1
- package/dist/{chunk-XC46B4FD.js → chunk-MA6NDQMO.js} +2 -2
- package/dist/chunk-MA6NDQMO.js.map +1 -0
- package/dist/{chunk-3BPU34DE.js → chunk-WM5QVHM2.js} +789 -46
- package/dist/chunk-WM5QVHM2.js.map +1 -0
- package/dist/{claude-cli-U7WEVAOL.js → claude-cli-75AOQUKG.js} +3 -3
- package/dist/{claude-cli-U7WEVAOL.js.map → claude-cli-75AOQUKG.js.map} +1 -1
- package/dist/{codex-6I5UZ2HM.js → codex-LYZF52WL.js} +25 -13
- package/dist/codex-LYZF52WL.js.map +1 -0
- package/dist/env/command.d.ts +1 -1
- package/dist/env/docker.d.ts +1 -1
- package/dist/env/sst.d.ts +1 -1
- package/dist/index.js +249 -11
- package/dist/index.js.map +1 -1
- package/dist/{types-B4wGVpqo.d.ts → types-Cv_3ymr9.d.ts} +118 -37
- package/package.json +10 -1
- package/skills/author-loop/SKILL.md +25 -14
- package/skills/design-agent-team/SKILL.md +108 -0
- package/skills/supervise-loop-run/SKILL.md +64 -0
- package/dist/chunk-3BPU34DE.js.map +0 -1
- package/dist/chunk-XC46B4FD.js.map +0 -1
- package/dist/codex-6I5UZ2HM.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loops-adk/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Jonny Neill",
|
|
6
6
|
"description": "Run an agent in a convergence loop with an honest done-gate. A small, nestable loop and DAG primitive: deterministic plus agent-judge conditions, git as memory, review-restart, budgets, and a live TUI.",
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"dist",
|
|
53
53
|
"bin",
|
|
54
54
|
"skills",
|
|
55
|
+
"assets",
|
|
55
56
|
"README.md",
|
|
56
57
|
"LICENSE"
|
|
57
58
|
],
|
|
@@ -64,6 +65,14 @@
|
|
|
64
65
|
"typecheck": "tsc --noEmit",
|
|
65
66
|
"test": "vitest run",
|
|
66
67
|
"test:watch": "vitest",
|
|
68
|
+
"bench:ab": "tsx bench/ab.ts",
|
|
69
|
+
"bench:graph": "tsx bench/graph.ts",
|
|
70
|
+
"bench:signal": "BENCH_GRAPH_TASK=graph-tasks/stable-store-contract BENCH_OUT=bench/results-signal.json tsx bench/graph.ts",
|
|
71
|
+
"bench:compare": "tsx bench/compare.ts",
|
|
72
|
+
"bench:report": "tsx bench/report.ts",
|
|
73
|
+
"bench:report:sample": "tsx bench/report.ts bench/results.sample.json",
|
|
74
|
+
"bench:context:dry": "BENCH_DRY=1 BENCH_CB_GROUPS=bench/contextbench/groups.dry.json tsx bench/swecontextbench.ts",
|
|
75
|
+
"bench:mechanism": "tsx bench/mechanism.ts",
|
|
67
76
|
"example:poll": "tsx src/index.ts run examples/simple-poll.loop.ts --no-tui",
|
|
68
77
|
"example:gate": "tsx src/index.ts run examples/confidence-gate.loop.ts",
|
|
69
78
|
"prepack": "npm run build",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: author-loop
|
|
3
|
-
description: Use when writing, running, or validating a loops `.loop.ts
|
|
3
|
+
description: Use when writing, running, or validating a loops `.loop.ts`: the mental model, the honest-convergence gate, the git-memory tiers, the loop archetypes, and copy-paste recipes for authoring convergence loops with the `loops` library. Load this before composing a loop.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Authoring loops
|
|
@@ -11,9 +11,9 @@ description: Use when writing, running, or validating a loops `.loop.ts` — the
|
|
|
11
11
|
|
|
12
12
|
There is one unit of work and two supporting types:
|
|
13
13
|
|
|
14
|
-
- `Job = (ctx) => Promise<Outcome
|
|
15
|
-
- `Condition = (ctx, last) => Promise<{ met, reason, confidence? }
|
|
16
|
-
- `Engine
|
|
14
|
+
- `Job = (ctx) => Promise<Outcome>`: a unit of work of any size.
|
|
15
|
+
- `Condition = (ctx, last) => Promise<{ met, reason, confidence? }>`: a yes/no gate.
|
|
16
|
+
- `Engine`: where an agent turn runs (a model backend).
|
|
17
17
|
|
|
18
18
|
`loop()` returns a `Job`. `dag()` returns a `Job`. So loops and DAGs **nest both ways**: a DAG node can be a loop, a loop body can be a DAG. Nesting is the absence of a special case. Author with that freedom; do not reach for a node type that only works in one position.
|
|
19
19
|
|
|
@@ -45,9 +45,9 @@ export default defineJob(
|
|
|
45
45
|
|
|
46
46
|
## The gate is the whole point
|
|
47
47
|
|
|
48
|
-
The trap this library exists to avoid is "ask the model if it is done"
|
|
48
|
+
The trap this library exists to avoid is "ask the model if it is done": the model grades its own homework and always says yes. Make the gate **honest**:
|
|
49
49
|
|
|
50
|
-
- Combine a **deterministic** signal (`commandSucceeds('npm', ['test'])
|
|
50
|
+
- Combine a **deterministic** signal (`commandSucceeds('npm', ['test'])`: the tests really pass) with a **separate judge** (`agentCheck`). Prefer this mixed form over a lone judge.
|
|
51
51
|
- `until`/`start`/`stopOn` take one item or many. Arrays are `all` by default; wrap in `any(...)` for or.
|
|
52
52
|
- Harden the judge: `quorum(2, judgeA, judgeB, judgeC)` is a k-of-n jury. `agentCheck({ dimensions: [...] })` opens on the geometric mean, so one weak dimension drags the verdict down.
|
|
53
53
|
- A missing confidence scores 0 (fail-closed). Never lean on the model's self-report alone.
|
|
@@ -67,16 +67,16 @@ until: [
|
|
|
67
67
|
Progress accumulates on disk, so each iteration starts with a clean context but not a blank one.
|
|
68
68
|
|
|
69
69
|
- `ground: true` on an `agentJob` reads the recent commit log + this run's scratch files into the next prompt, so a fresh turn knows what was already tried.
|
|
70
|
-
- `commit: { subject }` (or `commit: true`) writes one structured milestone commit on convergence
|
|
70
|
+
- `commit: { subject }` (or `commit: true`) writes one structured milestone commit on convergence: the reasoning welded to the diff. Later turns ground on it.
|
|
71
71
|
- For long, noisy histories use `ground: { retrieve: true }` (select relevant commits, not recent-N); for indefinite processes add `consolidateJob` to fold history into a bounded, decision-preserving record.
|
|
72
72
|
|
|
73
73
|
## Three archetypes
|
|
74
74
|
|
|
75
75
|
A loop is not one shape. Pick the one that matches the work:
|
|
76
76
|
|
|
77
|
-
- **Converge
|
|
78
|
-
- **Sweep
|
|
79
|
-
- **Tend
|
|
77
|
+
- **Converge**: one hard target, retried until a gate passes: `loop({ until: gate, max })`.
|
|
78
|
+
- **Sweep**: a known worklist, one fresh task each: a `loop`/`dag` over the list.
|
|
79
|
+
- **Tend**: an unbounded process picking the next unit: `loop({ until: dynamicCondition, max })`, body dispatches to a sub-loop (wrap in `isolated(...)` for its own worktree).
|
|
80
80
|
|
|
81
81
|
They nest: triage is Tend ∘ Converge; a research sweep is Sweep ∘ Converge.
|
|
82
82
|
|
|
@@ -97,20 +97,31 @@ dag({
|
|
|
97
97
|
|
|
98
98
|
`needs` are dependencies; `optional` nodes never block; an unmet `when` skips a node; `isolation: 'worktree'` (on the dag) or `isolate: true` (per node) runs writers in parallel worktrees that land back on pass. `sequence` and `parallel` are sugar over `dag`.
|
|
99
99
|
|
|
100
|
+
## Agents and feedback
|
|
101
|
+
|
|
102
|
+
A node can be a named specialist instead of an inline prompt. Define it once with `defineAgent` (persona in markdown via `fromFile`, structure in TS) and hand it to `agentJob({ agent })`; `defineSkill` folds a methodology into its system. The contract fields (`tier`, `outputs`, `failureModes`, …) are metadata for `describe` and validation, not scheduling power: the `dag` orchestrates, agents stay workers.
|
|
103
|
+
|
|
104
|
+
Review feedback is a structured revision request that flows back to the worker on one channel. In a loop, a failing `review` is threaded into the next turn as `ctx.lastReview`; set `consumeFeedback: true` and `agentJob` folds it into the prompt. Aggregate several reviewers with `reviewPanel`; route a fix back to an earlier dag node with a targeted `revisionRequest({ target, findings })` (or the terse `kickback(to, reason)`) when the dag's `maxKickbacks` allows it.
|
|
105
|
+
|
|
106
|
+
Composing a team of specialists, gates, and routed feedback is its own skill: see `skills/design-agent-team/SKILL.md`.
|
|
107
|
+
|
|
100
108
|
## Author → validate → run
|
|
101
109
|
|
|
102
110
|
```bash
|
|
103
|
-
loops validate path/to/feature.loop.ts
|
|
104
|
-
loops describe path/to/feature.loop.ts
|
|
105
|
-
loops
|
|
111
|
+
loops validate path/to/feature.loop.ts # offline pre-flight: loads + prints the shape, no model calls, no spend
|
|
112
|
+
loops describe path/to/feature.loop.ts # print the loop's shape (gate, body, nodes) without running
|
|
113
|
+
loops describe path/to/feature.loop.ts --json # the same shape as JSON (incl. each agent node's contract)
|
|
114
|
+
loops run path/to/feature.loop.ts # live Ink TUI
|
|
106
115
|
loops run path/to/feature.loop.ts --no-tui # plain streamed logs
|
|
107
|
-
loops run path/to/feature.loop.ts --json # NDJSON event
|
|
116
|
+
loops run path/to/feature.loop.ts --json # raw NDJSON event firehose (to supervise a run, prefer --supervise + records, below)
|
|
108
117
|
```
|
|
109
118
|
|
|
110
119
|
Always `loops validate` first. It imports and constructs the loop (catching syntax, import, and bad-export errors) without running it, so you fix authoring mistakes for free before spending a single agent turn. It also prints the loop's shape (its gate, body, and dag nodes), so you can confirm you built what you intended. `loops describe` prints that shape on its own.
|
|
111
120
|
|
|
112
121
|
`loops run` works from any repo, including one that uses `loops` as a submodule or dependency. The recipe's folder must be an ES module scope (a `package.json` with `{"type":"module"}`); repos that consume `loops` already have this. If a load fails with an ES-module error, that scope is what is missing.
|
|
113
122
|
|
|
123
|
+
Add `--supervise` to make a run observable from another process: it registers under `~/.loops/runs/`. From an agent, the primary read API is `loops records <runId>`, the semantic decision stream (dispatch / completion / surfacing / revision), filterable with `--kind`, `--path`, `--last`, `--json`, rather than the raw `run --json` firehose. `loops tail <runId>` streams live events, `loops status <runId>` reports terminal state, and `loops list` enumerates runs. Watching a long run or supervising several at once is its own skill: see `skills/supervise-loop-run/SKILL.md`.
|
|
124
|
+
|
|
114
125
|
## Gotchas
|
|
115
126
|
|
|
116
127
|
- **Test offline first.** Use the `mock` engine, or an engine-free `fnJob`/`predicate` body, to prove the loop's shape with zero network. A change to convergence logic deserves a deterministic check, not a live model call.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: design-agent-team
|
|
3
|
+
description: Use when composing a team of specialist agents in a loops `dag`: defining an `AgentDef`, folding in `defineSkill` methodologies, wiring review feedback (`reviewPanel`/`consumeFeedback`/`revisionRequest`), and gating nodes so the graph orchestrates and the agents stay workers, never dispatchers. Load this before turning a loop into a multi-agent team.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Designing an agent team
|
|
7
|
+
|
|
8
|
+
A `dag` of specialist agents is a team. The load-bearing rule that keeps it a team and not a swarm:
|
|
9
|
+
|
|
10
|
+
**The graph orchestrates; agents do not.** The `dag` is the manager (toposort + dispatch), `Condition`/`quorum` are the gates, `Outcome` is the result channel. An `AgentDef` is only the *contract*: who the agent is, what it may touch, how it works. It carries no scheduling authority. An agent produces an `Outcome`; the graph decides what runs next. Never build an agent whose job is to dispatch other agents; make the graph do it.
|
|
11
|
+
|
|
12
|
+
**REQUIRED BACKGROUND:** you compose these agents into a loop/dag. Read `skills/author-loop/SKILL.md` for the loop mental model, the honest gate, and git-memory first.
|
|
13
|
+
|
|
14
|
+
## Two builders: a skill is a method, an agent is a worker
|
|
15
|
+
|
|
16
|
+
- `defineSkill({ name, instructions })` is a **methodology** (how to work: TDD, writing-plans). Prose only. A skill never dispatches an agent.
|
|
17
|
+
- `defineAgent({ ... })` is a **worker**: a persona plus its contract. It *composes* skills; the skills' instructions fold into its system prompt.
|
|
18
|
+
|
|
19
|
+
Persona and methodology live in editable markdown (`fromFile`); structure and types live in TS. The `.ts` is the typed wrapper around the `.md`.
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { defineAgent, defineSkill, fromFile, agentJob } from '@loops-adk/core';
|
|
23
|
+
|
|
24
|
+
const tdd = defineSkill({
|
|
25
|
+
name: 'tdd',
|
|
26
|
+
instructions: fromFile(new URL('./skills/tdd.md', import.meta.url)),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const storeEngineer = defineAgent({
|
|
30
|
+
name: 'store-engineer',
|
|
31
|
+
system: fromFile(new URL('./agents/store-engineer.md', import.meta.url)), // persona, as markdown
|
|
32
|
+
model: 'sonnet',
|
|
33
|
+
tools: ['edit', 'bash'], // the permission boundary
|
|
34
|
+
leaf: true, // may not spawn sub-agents; bottoms the branch out here
|
|
35
|
+
tier: 'worker', // contract metadata (no scheduling power)
|
|
36
|
+
capabilities: ['storage engine', 'id stability'],
|
|
37
|
+
outputs: [{ name: 'patch' }, { name: 'test-report' }],
|
|
38
|
+
skills: [tdd], // methodologies fold into the system
|
|
39
|
+
requiresSkills: ['contract-first'], // metadata unless also in `skills`
|
|
40
|
+
usesSkills: ['small-diff'],
|
|
41
|
+
humanGates: [{ name: 'prod-approval', when: 'deploying production changes' }],
|
|
42
|
+
failureModes: [{ mode: 'tests-flaky', recovery: 'isolate the flake, retry once', severity: 'should-fix' }],
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`agentJob({ agent: storeEngineer, prompt, ground: true })` resolves the def into the engine request (`system` = persona + folded skills, plus `model`/`tools`). Inline `system`/`model`/`tools`/`allowedTools` on the `agentJob` still override the def. The contract fields beyond `system`/`model`/`tools` are **optional metadata** for validation, `loops describe`, docs, and future discovery. They change nothing at runtime; they do not grant dispatch authority.
|
|
47
|
+
|
|
48
|
+
**`leaf` is the fan-out brake.** A leaf agent cannot spawn sub-agents (the engine withholds the sub-agent tool). Use it to stop a thorough worker from quietly expanding into a slow, expensive swarm. The team's shape stays the graph you drew, not one the agent invents.
|
|
49
|
+
|
|
50
|
+
## Wire the team as a graph
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { dag, loop, agentJob, gateJob, quorum, agentCheck, commandSucceeds } from '@loops-adk/core';
|
|
54
|
+
|
|
55
|
+
dag({
|
|
56
|
+
name: 'ship',
|
|
57
|
+
nodes: {
|
|
58
|
+
store: loop({ name: 'store', body: agentJob({ agent: storeEngineer, prompt: 'Build the store to its tests.', ground: true }), until: commandSucceeds('npm', ['test']) }),
|
|
59
|
+
api: { needs: ['store'], job: loop({ /* apiEngineer, same shape */ }) },
|
|
60
|
+
review: { needs: ['api'], job: gateJob('review', quorum(2,
|
|
61
|
+
agentCheck({ agent: securityReviewer, question: 'Is it safe?' }),
|
|
62
|
+
agentCheck({ agent: correctnessReviewer, question: 'Is it correct?' }),
|
|
63
|
+
)) },
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Each engineer is a Converge loop (build to a `test` gate); reviewers are gates. `quorum(k, ...)` is a k-of-n jury; `gateJob(name, condition)` turns a `Condition` into a `Job` so it can be a node. Because a reviewer is just an agent and `agentCheck` takes an `engine`/`model`, any reviewer runs on any model, so put the adversarial lens on a second model for a genuinely independent signal.
|
|
69
|
+
|
|
70
|
+
## Feedback is a loop boundary, not a back-edge
|
|
71
|
+
|
|
72
|
+
Review findings are structured, and they flow back to the worker on the same channel whether they come from a loop's `review` slot or a dag kickback.
|
|
73
|
+
|
|
74
|
+
**In a loop:** a failing `review` outcome is threaded into the next body turn as `ctx.lastReview`. Set `consumeFeedback: true` so the worker reads it without you hand-writing "address the feedback" into every prompt:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
const implement = agentJob({ agent: implementationAgent, prompt: brief, consumeFeedback: true });
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Aggregate several reviewers** with `reviewPanel`. Every reviewer is a gate: the panel passes when all of them clear, or `pass: N` of them (k-of-n). An empty panel is a construction error. Give each reviewer real evidence with `reviewContext`:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { reviewPanel, reviewContext, agentCheck } from '@loops-adk/core';
|
|
84
|
+
|
|
85
|
+
const review = reviewPanel({
|
|
86
|
+
pass: 2, // optional: k-of-n instead of all
|
|
87
|
+
reviewers: [
|
|
88
|
+
{ name: 'security', review: agentCheck({ question: 'Is it safe?', context: reviewContext({ diff: true, ledger: true }) }) },
|
|
89
|
+
{ name: 'correctness', review: agentCheck({ question: 'Is it correct?', context: reviewContext({ tests: { command: 'npm', args: ['test'] } }) }) },
|
|
90
|
+
{ name: 'simplicity', review: agentCheck({ question: 'Is it simple?', context: reviewContext({ files: ['src/**'] }) }) },
|
|
91
|
+
],
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
A failing panel emits a `revisionRequest` carrying each failing reviewer's concern as a finding, threaded into the next pass.
|
|
96
|
+
|
|
97
|
+
**Route feedback across a DAG** with a targeted revision. When `DagConfig.maxKickbacks > 0`, a `revisionRequest({ target, findings })` (or the terse `kickback(to, reason)`) re-runs the target node and its transitive dependents, threading the reason in as their `lastReview`. Constrain valid targets with `DagNode.acceptsKickbackTo`. Because every cycle is a bounded re-run, not a graph edge, it always terminates.
|
|
98
|
+
|
|
99
|
+
Give a worker just enough map to act on routed feedback without seeing the whole orchestration, with `graphContext: true`, which appends a small block naming this node, its direct dependencies, and its direct dependents.
|
|
100
|
+
|
|
101
|
+
## Verify the contract before spending a turn
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
loops validate team.loop.ts # loads + constructs, no model calls
|
|
105
|
+
loops describe team.loop.ts --json # the shape, incl. each agent node's contract (tier, outputs, failure modes)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`describe --json` reflects the contract you declared back at you, so you confirm the team you built is the team you meant. To watch or supervise the team once it runs, see `skills/supervise-loop-run/SKILL.md`.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: supervise-loop-run
|
|
3
|
+
description: Use when an agent needs to observe, monitor, or supervise a running loops job from another process: discover live runs, read a run's state and shape, stream its events, inspect the decisions it made (dispatch/completion/surfacing/revision), or decide whether to intervene. Load this when watching a long run or supervising several at once. Requires the run to have been started with `--supervise`.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Supervising a loop run
|
|
7
|
+
|
|
8
|
+
A run started with `loops run <file> --supervise` registers itself under `~/.loops/runs/<runId>/` and writes its live state, raw events (`events.jsonl`), and semantic decisions (`semantic.jsonl`) there as it goes. Another process reads those files with no daemon and no socket: the filesystem is the channel. Every command below is read-only; supervising never touches the run.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
loops run build.loop.ts --supervise # in one terminal (or backgrounded)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## The loop: list → status → tail → records → decide
|
|
15
|
+
|
|
16
|
+
**`loops list`** (alias `ls`) discovers runs. Each line is the runId, state (`running` / `dead` / a terminal status like `pass`/`fail`/`paused`), current iteration, age, and title. A run whose process is gone is marked `dead`.
|
|
17
|
+
|
|
18
|
+
**`loops status <runId>`** prints a point-in-time snapshot: terminal-or-live state, the loop's shape, the last gate verdict (which gate, met, confidence, reason), the last outcome, and token usage. Use this to answer where a run stands and whether it is healthy.
|
|
19
|
+
|
|
20
|
+
**`loops tail <runId>`** streams the raw event log live (Ctrl-C to stop). It ends on its own when the run reaches a terminal status or its process disappears. Use this to watch a turn unfold.
|
|
21
|
+
|
|
22
|
+
**`loops records <runId>`** is the **primary agent API**: the semantic decision stream, one line per meaningful thing the run decided. This is what an agent reads to reason about a run, not the raw `--json` event firehose. Five kinds:
|
|
23
|
+
|
|
24
|
+
| kind | meaning |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| `dispatch` | a job or dag-node started |
|
|
27
|
+
| `completion` | a job / loop / dag finished (carries the outcome status + summary) |
|
|
28
|
+
| `surfacing` | a review or kickback raised feedback (carries severity + reason) |
|
|
29
|
+
| `revision-emitted` | an outcome asked for another pass |
|
|
30
|
+
| `revision-routed` | that revision was routed to a target (accepted/rejected) |
|
|
31
|
+
|
|
32
|
+
Filter it down for a machine-readable slice:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
loops records <runId> --json # everything, as JSONL
|
|
36
|
+
loops records <runId> --kind completion # just what finished
|
|
37
|
+
loops records <runId> --kind revision # both revision kinds (emitted + routed)
|
|
38
|
+
loops records <runId> --path ship/implementation --json # only this subtree of the loop tree
|
|
39
|
+
loops records <runId> --kind surfacing --since 2026-07-01T09:00:00Z
|
|
40
|
+
loops records <runId> --last 20 # the most recent 20 matching records
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`--path` is a slash-separated prefix over the record's position in the loop tree. `--kind revision` is the convenience union of `revision-emitted` and `revision-routed`.
|
|
44
|
+
|
|
45
|
+
## Deciding what to do next
|
|
46
|
+
|
|
47
|
+
Read `records` (and `status` for tokens/gate) to choose an action, since loops does not act for you:
|
|
48
|
+
|
|
49
|
+
- **Converged**: a top-level `completion` with `status: pass`. Done; nothing to do.
|
|
50
|
+
- **Stuck in review**: repeated `surfacing` / `revision-routed` on the same node with a `block`/`should-fix` severity and the iteration climbing toward its cap. The gate is doing its job or the worker cannot satisfy it; inspect the reason and decide whether to let it run, abort, or (if you drive the run) route different feedback.
|
|
51
|
+
- **Dead**: `list` shows `dead`, or `status` says the process is gone with no terminal outcome. The run crashed or was killed; investigate its last `completion`/event.
|
|
52
|
+
- **Budget-bound**: `status` shows tokens near the run's budget; expect a `paused` outcome next.
|
|
53
|
+
|
|
54
|
+
## Build your own supervisor
|
|
55
|
+
|
|
56
|
+
The read side is on the public surface, so an agent supervising a fleet (killing the ones that drift, watching the ones mid-revision) reads the same files programmatically:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { listRuns, readRunStatus, runEventsPath, runSemanticRecordsPath } from '@loops-adk/core';
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`listRuns()` and `readRunStatus(runId)` mirror `list`/`status`; `runEventsPath`/`runSemanticRecordsPath` locate the two JSONL streams to read directly. `semanticRecordsFromEvent(event)` derives the semantic records from a raw event if you tail the event stream yourself.
|
|
63
|
+
|
|
64
|
+
To author or shape the run you are supervising, see `skills/author-loop/SKILL.md`; to compose the agent team inside it, see `skills/design-agent-team/SKILL.md`.
|