@loops-adk/core 0.1.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/LICENSE +21 -0
- package/README.md +486 -0
- package/bin/loops.mjs +16 -0
- package/dist/App-3YQS6DXA.js +461 -0
- package/dist/App-3YQS6DXA.js.map +1 -0
- package/dist/agent-sdk-RF5VJZAT.js +95 -0
- package/dist/agent-sdk-RF5VJZAT.js.map +1 -0
- package/dist/anthropic-api-XJY6Y4T2.js +131 -0
- package/dist/anthropic-api-XJY6Y4T2.js.map +1 -0
- package/dist/api.d.ts +949 -0
- package/dist/api.js +898 -0
- package/dist/api.js.map +1 -0
- package/dist/chunk-33YIGWNU.js +63 -0
- package/dist/chunk-33YIGWNU.js.map +1 -0
- package/dist/chunk-3BPU34DE.js +2163 -0
- package/dist/chunk-3BPU34DE.js.map +1 -0
- package/dist/chunk-CXEPZHSR.js +86 -0
- package/dist/chunk-CXEPZHSR.js.map +1 -0
- package/dist/chunk-I3STY7U6.js +61 -0
- package/dist/chunk-I3STY7U6.js.map +1 -0
- package/dist/chunk-JFTXJ7I2.js +18 -0
- package/dist/chunk-JFTXJ7I2.js.map +1 -0
- package/dist/chunk-XC46B4FD.js +9 -0
- package/dist/chunk-XC46B4FD.js.map +1 -0
- package/dist/chunk-Y2SD7GBL.js +30 -0
- package/dist/chunk-Y2SD7GBL.js.map +1 -0
- package/dist/claude-cli-U7WEVAOL.js +124 -0
- package/dist/claude-cli-U7WEVAOL.js.map +1 -0
- package/dist/codex-6I5UZ2HM.js +60 -0
- package/dist/codex-6I5UZ2HM.js.map +1 -0
- package/dist/env/command.d.ts +53 -0
- package/dist/env/command.js +3 -0
- package/dist/env/command.js.map +1 -0
- package/dist/env/docker.d.ts +38 -0
- package/dist/env/docker.js +33 -0
- package/dist/env/docker.js.map +1 -0
- package/dist/env/sst.d.ts +39 -0
- package/dist/env/sst.js +20 -0
- package/dist/env/sst.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +620 -0
- package/dist/index.js.map +1 -0
- package/dist/types-B4wGVpqo.d.ts +898 -0
- package/package.json +100 -0
- package/skills/author-loop/SKILL.md +121 -0
package/package.json
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@loops-adk/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"author": "Jonny Neill",
|
|
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.",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"agent",
|
|
9
|
+
"agents",
|
|
10
|
+
"loop",
|
|
11
|
+
"loops",
|
|
12
|
+
"convergence",
|
|
13
|
+
"ai",
|
|
14
|
+
"llm",
|
|
15
|
+
"claude",
|
|
16
|
+
"orchestration"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/jonny981/loops.git"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/jonny981/loops#readme",
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/jonny981/loops/issues"
|
|
25
|
+
},
|
|
26
|
+
"type": "module",
|
|
27
|
+
"main": "./dist/api.js",
|
|
28
|
+
"types": "./dist/api.d.ts",
|
|
29
|
+
"bin": {
|
|
30
|
+
"loops": "./bin/loops.mjs"
|
|
31
|
+
},
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/api.d.ts",
|
|
35
|
+
"import": "./dist/api.js"
|
|
36
|
+
},
|
|
37
|
+
"./env/command": {
|
|
38
|
+
"types": "./dist/env/command.d.ts",
|
|
39
|
+
"import": "./dist/env/command.js"
|
|
40
|
+
},
|
|
41
|
+
"./env/sst": {
|
|
42
|
+
"types": "./dist/env/sst.d.ts",
|
|
43
|
+
"import": "./dist/env/sst.js"
|
|
44
|
+
},
|
|
45
|
+
"./env/docker": {
|
|
46
|
+
"types": "./dist/env/docker.d.ts",
|
|
47
|
+
"import": "./dist/env/docker.js"
|
|
48
|
+
},
|
|
49
|
+
"./package.json": "./package.json"
|
|
50
|
+
},
|
|
51
|
+
"files": [
|
|
52
|
+
"dist",
|
|
53
|
+
"bin",
|
|
54
|
+
"skills",
|
|
55
|
+
"README.md",
|
|
56
|
+
"LICENSE"
|
|
57
|
+
],
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public"
|
|
60
|
+
},
|
|
61
|
+
"scripts": {
|
|
62
|
+
"loops": "tsx src/index.ts",
|
|
63
|
+
"build": "tsup",
|
|
64
|
+
"typecheck": "tsc --noEmit",
|
|
65
|
+
"test": "vitest run",
|
|
66
|
+
"test:watch": "vitest",
|
|
67
|
+
"example:poll": "tsx src/index.ts run examples/simple-poll.loop.ts --no-tui",
|
|
68
|
+
"example:gate": "tsx src/index.ts run examples/confidence-gate.loop.ts",
|
|
69
|
+
"prepack": "npm run build",
|
|
70
|
+
"prepublishOnly": "npm run typecheck"
|
|
71
|
+
},
|
|
72
|
+
"engines": {
|
|
73
|
+
"node": ">=20"
|
|
74
|
+
},
|
|
75
|
+
"dependencies": {
|
|
76
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.185",
|
|
77
|
+
"@anthropic-ai/sdk": "^0.105.0",
|
|
78
|
+
"commander": "^15.0.0",
|
|
79
|
+
"execa": "^9.6.1",
|
|
80
|
+
"ink": "^7.1.0",
|
|
81
|
+
"ms": "^2.1.3",
|
|
82
|
+
"p-limit": "^7.3.0",
|
|
83
|
+
"p-retry": "^8.0.0",
|
|
84
|
+
"p-timeout": "^7.0.1",
|
|
85
|
+
"picocolors": "^1.1.1",
|
|
86
|
+
"react": "^19.2.7",
|
|
87
|
+
"toposort": "^2.0.2",
|
|
88
|
+
"tsx": "^4.22.4",
|
|
89
|
+
"zod": "^4.4.3"
|
|
90
|
+
},
|
|
91
|
+
"devDependencies": {
|
|
92
|
+
"@types/ms": "^2.1.0",
|
|
93
|
+
"@types/node": "^26.0.0",
|
|
94
|
+
"@types/react": "^19.2.17",
|
|
95
|
+
"@types/toposort": "^2.0.7",
|
|
96
|
+
"tsup": "^8.5.1",
|
|
97
|
+
"typescript": "^6.0.3",
|
|
98
|
+
"vitest": "^4.1.9"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: author-loop
|
|
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
|
+
---
|
|
5
|
+
|
|
6
|
+
# Authoring loops
|
|
7
|
+
|
|
8
|
+
`loops` runs an agent in a convergence loop: do a bit of work with a fresh context, check whether it is *actually* done against a gate you define, and if not, go again. You author a loop as a small TypeScript file, validate it offline, then run it.
|
|
9
|
+
|
|
10
|
+
## The one idea
|
|
11
|
+
|
|
12
|
+
There is one unit of work and two supporting types:
|
|
13
|
+
|
|
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
|
+
|
|
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
|
+
|
|
20
|
+
## A loop file
|
|
21
|
+
|
|
22
|
+
A `.loop.ts` `export default`s a `Job`. Wrap it in `defineJob(...)` to pin the type.
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { defineJob, loop, agentJob, commandSucceeds, agentCheck } from '@loops-adk/core';
|
|
26
|
+
|
|
27
|
+
export default defineJob(
|
|
28
|
+
loop({
|
|
29
|
+
name: 'build-feature',
|
|
30
|
+
max: 20,
|
|
31
|
+
body: agentJob({
|
|
32
|
+
prompt: (c) => `Iteration ${c.iteration}: make concrete progress on TASK.md.`,
|
|
33
|
+
ground: true, // read the commit log + scratch files before working
|
|
34
|
+
}),
|
|
35
|
+
until: [
|
|
36
|
+
commandSucceeds('npm', ['test']), // ground truth
|
|
37
|
+
agentCheck({ question: 'Does it match TASK.md?', threshold: 0.85 }), // intent
|
|
38
|
+
],
|
|
39
|
+
commit: { subject: 'feat: TASK.md' }, // one milestone commit on convergence
|
|
40
|
+
}),
|
|
41
|
+
);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`loop()` config worth knowing: `body` (the Job per iteration), `until` (stop gate), `start` (gate before iterating), `stopOn` (hard early-exit), `review` (runs when `until` is met; a failing review folds its findings back into the next iteration), `max` (iteration cap), `delayMs` (polling delay), `commit` (milestone commit on convergence).
|
|
45
|
+
|
|
46
|
+
## The gate is the whole point
|
|
47
|
+
|
|
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
|
+
|
|
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
|
+
- `until`/`start`/`stopOn` take one item or many. Arrays are `all` by default; wrap in `any(...)` for or.
|
|
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
|
+
- A missing confidence scores 0 (fail-closed). Never lean on the model's self-report alone.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
until: [
|
|
57
|
+
commandSucceeds('npm', ['test']),
|
|
58
|
+
quorum(2,
|
|
59
|
+
agentCheck({ question: 'Correct?', dimensions: ['intent match', 'evidence', 'no regressions'] }),
|
|
60
|
+
agentCheck({ question: 'Correct?', model: 'opus' }),
|
|
61
|
+
),
|
|
62
|
+
],
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Memory is git
|
|
66
|
+
|
|
67
|
+
Progress accumulates on disk, so each iteration starts with a clean context but not a blank one.
|
|
68
|
+
|
|
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 — the reasoning welded to the diff. Later turns ground on it.
|
|
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
|
+
|
|
73
|
+
## Three archetypes
|
|
74
|
+
|
|
75
|
+
A loop is not one shape. Pick the one that matches the work:
|
|
76
|
+
|
|
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
|
+
|
|
81
|
+
They nest: triage is Tend ∘ Converge; a research sweep is Sweep ∘ Converge.
|
|
82
|
+
|
|
83
|
+
## Compose
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { dag, sequence, parallel } from '@loops-adk/core';
|
|
87
|
+
|
|
88
|
+
dag({
|
|
89
|
+
name: 'ship',
|
|
90
|
+
nodes: {
|
|
91
|
+
research: agentJob({ label: 'research', prompt: '…' }),
|
|
92
|
+
implement: { needs: ['research'], job: loop({ /* a loop as a node */ }) },
|
|
93
|
+
review: { needs: ['implement'], job: gateJob('review', agentCheck({ /* … */ })) },
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
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
|
+
|
|
100
|
+
## Author → validate → run
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
loops validate path/to/feature.loop.ts # offline pre-flight: loads + prints the shape, no model calls, no spend
|
|
104
|
+
loops describe path/to/feature.loop.ts # print the loop's shape (gate, body, nodes) without running
|
|
105
|
+
loops run path/to/feature.loop.ts # live Ink TUI
|
|
106
|
+
loops run path/to/feature.loop.ts --no-tui # plain streamed logs
|
|
107
|
+
loops run path/to/feature.loop.ts --json # NDJSON event stream (parse this from an agent)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
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
|
+
|
|
112
|
+
`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
|
+
|
|
114
|
+
## Gotchas
|
|
115
|
+
|
|
116
|
+
- **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.
|
|
117
|
+
- **Conditions default to `all`.** A bare array of conditions must *all* hold. Wrap in `any(...)` when you mean or.
|
|
118
|
+
- **The body is a Job, so it can be another `loop`/`dag`.** Reach for nesting before inventing a new construct.
|
|
119
|
+
- **One milestone, not one commit per iteration.** `commit` fires on convergence. Want finer commits? Compose finer loops or nodes.
|
|
120
|
+
|
|
121
|
+
The full surface is the package's only export (`loops`); see the repo README for engines, environments, budgets, and the PR/forge jobs.
|