agents-pod 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/CHANGELOG.md +28 -0
- package/README.md +209 -22
- package/package.json +1 -1
- package/src/cli.ts +399 -47
- package/src/inbox.ts +75 -9
- package/src/listeners.ts +11 -5
- package/src/outbox.ts +294 -0
- package/src/paths.ts +10 -0
- package/src/sources.ts +278 -0
- package/src/workspace.ts +11 -4
- package/src/wrapper.ts +71 -22
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,34 @@ All notable changes to agents-pod. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project
|
|
5
5
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.2.0] — 2026-11-04
|
|
8
|
+
|
|
9
|
+
Work that arrives in a chat can now be picked up there, and answered there,
|
|
10
|
+
without a resident process holding a socket: a **Source** drains a conversation
|
|
11
|
+
into a pod once per pass, a **Deliverer** posts a pod's **Outbox** back, and a
|
|
12
|
+
**Delivery Address** rides the Goal so a Worker can reply without asking anyone.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **IM intake and outbound — Source, Deliverer, Outbox, and Delivery Address** —
|
|
17
|
+
a pull-based intake and file-based reply path, with platform credentials and
|
|
18
|
+
API behavior kept in operator commands.
|
|
19
|
+
- **Sources and drains** — `source add|list|rm`, cursor-based JSONL drains with
|
|
20
|
+
platform ids and routing fields, bounded execution, claim-before-cursor
|
|
21
|
+
commits, and two-generation claim retention.
|
|
22
|
+
- **Source Listeners and standing Pod intake** — `listener add --source`, batched
|
|
23
|
+
`TASKS.md` Inbox delivery with Waiting state, the `{{reply_to}}` template
|
|
24
|
+
placeholder, and `--project` for template-materialized Pods.
|
|
25
|
+
- **Outbox delivery** — `deliver add|list|rm`, one operator deliverer, named
|
|
26
|
+
per-wake-up entry files carrying `{to, text}`, stable delivery uuids, bounded
|
|
27
|
+
wrapper and reconcile flushes, same-uuid retries, one-hour dead-lettering,
|
|
28
|
+
and retained `*.dead` entries for expired or invalid input.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- Wake-up outcomes are recorded under the registry lock, so concurrent passes
|
|
33
|
+
and wrappers no longer overwrite each other's registry updates.
|
|
34
|
+
|
|
7
35
|
## [0.1.0] — 2026-11-03
|
|
8
36
|
|
|
9
37
|
First release: one pod, many wake-ups, and the machinery that keeps a standing
|
package/README.md
CHANGED
|
@@ -4,9 +4,11 @@ Supervision for long-running coding agents: spawn a **pod** — a standing
|
|
|
4
4
|
unit that outlives every context window — and wake **agents** inside it,
|
|
5
5
|
again and again, each wake-up fresh, documents carrying the work across.
|
|
6
6
|
|
|
7
|
-
> **Status: `0.
|
|
8
|
-
> logs, reconcile, listeners, notifications) and the
|
|
9
|
-
> planes.
|
|
7
|
+
> **Status: `0.2.0` — IM intake and outbound.** The CLI runs (spawn, wake,
|
|
8
|
+
> cancel, logs, reconcile, listeners, sources, deliverers, notifications) and the
|
|
9
|
+
> Pi extension ships its two planes. A pod can now be fed from a chat and answer
|
|
10
|
+
> into it, through operator commands that keep credentials and sockets out of
|
|
11
|
+
> core. Wake-ups are **Pi only** in this release. Interfaces may still move
|
|
10
12
|
> before `1.0`. The design lives in `CONTEXT.md` and `docs/adr/`; a reader's
|
|
11
13
|
> companion — the design principles and an explained vocabulary — in
|
|
12
14
|
> [docs/DESIGN.md](docs/DESIGN.md).
|
|
@@ -73,11 +75,11 @@ agent (planner, coder, reviewer; pi, claude, codex) across its life
|
|
|
73
75
|
|
|
74
76
|
**Wake-ups are the context unit; the pod isolates them.** One **wake-up** =
|
|
75
77
|
one agent, one fresh context window: read the pod documents first, work,
|
|
76
|
-
record (handoff, decisions, task state), exit with an outcome.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
record (handoff, decisions, task state), exit with an outcome. Agents in a
|
|
79
|
+
pod share its documents, never one another's windows; pending Outbox entries
|
|
80
|
+
are files, not context. Context exhaustion ends a wake-up, never the pod;
|
|
81
|
+
waking again is routine, not recovery. The store's contract is three slots,
|
|
82
|
+
two hard lines ([ADR 0008](docs/adr/0008-document-store-contract.md)).
|
|
81
83
|
|
|
82
84
|
**A wrapper owns each wake-up's liveness.** Every wake-up gets a tiny
|
|
83
85
|
**wrapper** — detached, session and process-group leader — that spawns the
|
|
@@ -148,10 +150,16 @@ agents-pod agent add <id> --type <pi|claude|codex> [--model <id>] [--instruction
|
|
|
148
150
|
agents-pod agent edit <id> # $EDITOR on the stored definition
|
|
149
151
|
agents-pod agent rm <id> # pods left referencing it park at reconcile (ADR 0012)
|
|
150
152
|
agents-pod agent list [--json] # named agents: type, model, role
|
|
151
|
-
agents-pod listener add --inbox <dir|file>
|
|
152
|
-
[--match <substr>] [--max-concurrent <n>]
|
|
153
|
+
agents-pod listener add (--inbox <dir|file> | --source <name>) (--pod <podId> | --template <file>)
|
|
154
|
+
[--match <substr>] [--max-concurrent <n>] [--project <dir>]
|
|
153
155
|
agents-pod listener list [--json]
|
|
154
156
|
agents-pod listener rm <listenerId>
|
|
157
|
+
agents-pod source add <name> --cmd <path> [--timeout <s>]
|
|
158
|
+
agents-pod source list [--json]
|
|
159
|
+
agents-pod source rm <name>
|
|
160
|
+
agents-pod deliver add <name> --cmd <path> [--timeout <s>]
|
|
161
|
+
agents-pod deliver list [--json]
|
|
162
|
+
agents-pod deliver rm <name>
|
|
155
163
|
```
|
|
156
164
|
|
|
157
165
|
`spawn` claims a pod id, writes the pod record, wakes the first agent, and
|
|
@@ -222,21 +230,28 @@ policy, per record:
|
|
|
222
230
|
- terminal → no-op (terminal is terminal: `wake` refuses; continuation is
|
|
223
231
|
a new pod).
|
|
224
232
|
|
|
225
|
-
After pods, the same pass materializes listeners:
|
|
226
|
-
items by id (registry-first, so a lost tick
|
|
227
|
-
either spawn a pod from the listener's template
|
|
228
|
-
|
|
233
|
+
After pods, the same pass materializes listeners: use each Source's drained
|
|
234
|
+
items or scan each inbox, claim new items by id (registry-first, so a lost tick
|
|
235
|
+
cannot double-claim), then either spawn a pod from the listener's template —
|
|
236
|
+
bounded by `--max-concurrent` and the per-project pod cap — or append a batch
|
|
237
|
+
to the bound standing pod and use the ordinary capacity-checked wake path.
|
|
229
238
|
|
|
230
239
|
An inbox is a directory (one item per top-level non-dot file; claimable once
|
|
231
240
|
its mtime and size hold across two passes) or one append-only JSONL file
|
|
232
|
-
(`body` required; malformed lines skipped with a warning).
|
|
233
|
-
|
|
234
|
-
|
|
241
|
+
(`body` required, `title` optional; malformed lines skipped with a warning).
|
|
242
|
+
A JSONL line may also carry the routing fields `chat_id`, `root_id` and
|
|
243
|
+
`thread_id`; any other key is ignored. Item ids are filenames or line
|
|
244
|
+
numbers — never content — claimed per inbox; templates bind five
|
|
245
|
+
placeholders: `{{id}}` `{{title}}` `{{body}}` `{{path}}` and `{{reply_to}}`.
|
|
246
|
+
The last placeholder carries the Delivery Address consumed by the outbound
|
|
247
|
+
contract below (ADRs 0013, 0026).
|
|
235
248
|
|
|
236
249
|
For a standing pod, claimed items land in its `TASKS.md` `## Inbox`
|
|
237
|
-
section —
|
|
238
|
-
on the ordinary wake path; the
|
|
239
|
-
checkboxes
|
|
250
|
+
section — below the agent-owned `## Waiting`, and appended only while no
|
|
251
|
+
wake-up is live, then one batch, one wake on the ordinary wake path; the
|
|
252
|
+
agent serves entries and flips their checkboxes. A block with a Delivery
|
|
253
|
+
Address carries ` — to: <Delivery Address>` on its checkbox line, never in
|
|
254
|
+
the copied body (ADRs 0014, 0026).
|
|
240
255
|
|
|
241
256
|
Overlapping passes: one TTL'd single-flight lock (`reconcile.lock`, stale
|
|
242
257
|
after 5 minutes by wall clock — never PID liveness) skips fresh-held ticks
|
|
@@ -249,6 +264,164 @@ and done — through the copied channels (webhook/email/system) in
|
|
|
249
264
|
`~/.agents/pods/config.json`; once per episode; individual outcomes
|
|
250
265
|
never notify (ADR 0021).
|
|
251
266
|
|
|
267
|
+
## IM intake and outbound
|
|
268
|
+
|
|
269
|
+
The operator owns platform I/O. A **Source** is an executable pull drain; the
|
|
270
|
+
single **deliverer** is an executable push command. Source definitions and
|
|
271
|
+
their committed cursors live in `~/.agents/pods/sources.json`; Listener claims
|
|
272
|
+
live in `~/.agents/pods/listeners.json`; the deliverer lives in
|
|
273
|
+
`~/.agents/pods/deliverer.json`. `source add` and `deliver add` resolve `--cmd`
|
|
274
|
+
against the add command's cwd and require an executable regular file.
|
|
275
|
+
`--timeout` is a positive number of seconds, default `10`. Their add/list/rm
|
|
276
|
+
commands return `0` on success; invalid arguments, an unknown or duplicate
|
|
277
|
+
name, removing an in-use Source, or adding a second deliverer returns `2`.
|
|
278
|
+
|
|
279
|
+
`listener add --source <name>` takes either `--template <file>` or
|
|
280
|
+
`--pod <podId>`. With `--template`, `--project <dir>` selects the new Pods'
|
|
281
|
+
project and defaults to the reconcile invocation's cwd; with `--pod` it is a
|
|
282
|
+
configuration error (exit `2`), because the bound Pod keeps its own project.
|
|
283
|
+
|
|
284
|
+
### Writing a Source drain
|
|
285
|
+
|
|
286
|
+
Each reconcile pass invokes every distinct Source consumed by a Listener once,
|
|
287
|
+
before any Pod work and before taking the registry lock. The command receives
|
|
288
|
+
no arguments. Its cwd is the reconcile invocation's project (its cwd, not a
|
|
289
|
+
Listener's `--project`), and it inherits that invocation's environment plus:
|
|
290
|
+
|
|
291
|
+
- `AGENTS_POD_SOURCE_ID` — the registered Source name;
|
|
292
|
+
- `AGENTS_POD_CURSOR` — the last committed cursor, or the empty string when no
|
|
293
|
+
cursor has been committed.
|
|
294
|
+
|
|
295
|
+
The command writes JSONL to stdout; stdin is closed and stderr is ignored. A
|
|
296
|
+
successful stream contains zero or more items followed by an optional cursor
|
|
297
|
+
as the last non-empty line:
|
|
298
|
+
|
|
299
|
+
```jsonl
|
|
300
|
+
{"id":"message-123","body":"Please review this","title":"Review request","chat_id":"chat-1","root_id":"root-1","thread_id":"thread-1"}
|
|
301
|
+
{"cursor":"next-page-token"}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
- `id` is a non-empty string and `body` is a string; both are required. The id
|
|
305
|
+
is the platform's stable deduplication key.
|
|
306
|
+
- `title` is optional. A missing or blank title defaults to the trimmed first
|
|
307
|
+
line of `body`.
|
|
308
|
+
- `chat_id`, `root_id` and `thread_id` are optional routing strings. Non-empty
|
|
309
|
+
values are trimmed; blank or non-string values are ignored. They feed
|
|
310
|
+
`{{reply_to}}`. Source items have no filesystem path, so `{{path}}` is empty.
|
|
311
|
+
Unknown keys are ignored.
|
|
312
|
+
- The final `{"cursor":"…"}` line takes a string. Omitting it leaves the
|
|
313
|
+
stored cursor unchanged. Blank lines are ignored. A malformed JSON line, an
|
|
314
|
+
invalid item, or an invalid final cursor is skipped with one
|
|
315
|
+
`~/.agents/pods/cron.log` warning and is not claimed; other valid lines still
|
|
316
|
+
count.
|
|
317
|
+
|
|
318
|
+
Exit `0` commits the successful drain. Claims use `source:<name>` as their
|
|
319
|
+
inbox namespace and the item's platform id, shared by every Listener on that
|
|
320
|
+
Source. Claims are written to `listeners.json` before the Source's cursor is
|
|
321
|
+
atomically written to `sources.json`, so a crash between those writes replays
|
|
322
|
+
into id deduplication. A committed drain also advances the record's
|
|
323
|
+
`lastDrainAt`; claims from the current and previous successful drain
|
|
324
|
+
generations are retained and older timestamped claims are pruned. Claims with
|
|
325
|
+
no generation timestamp are retained. For a Source bound to a standing Pod,
|
|
326
|
+
the claimed batch must also be appended to `TASKS.md` before cursor and
|
|
327
|
+
`lastDrainAt` commit; a live wake-up holds that append, so the next pass drains
|
|
328
|
+
from the unchanged cursor again.
|
|
329
|
+
|
|
330
|
+
A non-zero exit, termination by a signal, spawn failure, timeout, or total
|
|
331
|
+
stdout larger than **1 MiB** abandons the whole drain: no items are claimed,
|
|
332
|
+
neither cursor nor `lastDrainAt` moves, no claims are pruned, and exactly one
|
|
333
|
+
`cron.log` line records the failure. Reconcile still exits `0`; the next pass
|
|
334
|
+
retries the whole drain from the same cursor (ADR 0024).
|
|
335
|
+
|
|
336
|
+
### Writing the deliverer
|
|
337
|
+
|
|
338
|
+
Exactly one deliverer may be configured; remove it before adding a replacement.
|
|
339
|
+
A Wake-up writes one JSON file per outgoing message in its own Pod:
|
|
340
|
+
|
|
341
|
+
```text
|
|
342
|
+
<project>/.agents/pods/<podId>/outbox/<wakeup>-<seq>-<name>.json
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
`<wakeup>` is the positive integer wake-up number, `<seq>` is a zero-padded
|
|
346
|
+
increasing sequence such as `001`, `002`, and `<name>` is a filename-safe slug.
|
|
347
|
+
Files are delivered serially in filename order. Each file contains a JSON
|
|
348
|
+
object with a non-empty string `to` and a string `text`:
|
|
349
|
+
|
|
350
|
+
```json
|
|
351
|
+
{"to":"<Delivery Address>","text":"The reply text"}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
`to` is the **Delivery Address** rendered by `{{reply_to}}` or carried on a
|
|
355
|
+
standing Pod's Inbox checkbox line. The worker copies it verbatim; the
|
|
356
|
+
deliverer parses it; core never reads it back. Its shape is:
|
|
357
|
+
|
|
358
|
+
```text
|
|
359
|
+
chat=<chat_id>;affinity=<root_id ?? item id>[;thread=<thread_id>]
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
An item with no `chat_id` renders the empty string, which is not a valid `to`
|
|
363
|
+
and cannot be delivered. When `thread` is present, the deliverer can use it to
|
|
364
|
+
reply in that thread.
|
|
365
|
+
|
|
366
|
+
The Wrapper invokes the deliverer once per current-wake-up entry after the
|
|
367
|
+
agent exits and before the Pod becomes idle. Reconcile retries entries for
|
|
368
|
+
every non-active Pod, outside the registry lock. Each invocation receives the
|
|
369
|
+
entry file's bytes verbatim on stdin, takes no arguments, runs with the Pod's
|
|
370
|
+
project as cwd, and ignores stdout and stderr. It inherits the operator
|
|
371
|
+
environment plus exactly these core-provided values:
|
|
372
|
+
|
|
373
|
+
- `AGENTS_POD_POD_ID` — the owning Pod id;
|
|
374
|
+
- `AGENTS_POD_WAKEUP_N` — the wake-up number parsed from the filename;
|
|
375
|
+
- `AGENTS_POD_OUTBOX_ENTRY` — the entry filename, not its full path;
|
|
376
|
+
- `AGENTS_POD_OUTBOX_UUID` — the 50-character lowercase hexadecimal idempotency
|
|
377
|
+
key derived from Pod id, wake-up number, and filename. Pass this same value
|
|
378
|
+
to the platform on every retry.
|
|
379
|
+
|
|
380
|
+
One flush per Pod makes at most **10 delivery attempts** within a shared
|
|
381
|
+
**10-second budget**, in filename order. The configured per-invocation timeout
|
|
382
|
+
defaults to 10 seconds and is additionally capped by the flush's remaining
|
|
383
|
+
budget. Entries left beyond either bound wait for a later pass.
|
|
384
|
+
|
|
385
|
+
Exit `0` is the only acknowledgement: core deletes the entry and writes no
|
|
386
|
+
completion marker. A non-zero exit, signal, spawn failure, or timeout leaves
|
|
387
|
+
the file unchanged, writes one `cron.log` line, and lets the next pass retry
|
|
388
|
+
with the same uuid. Delivery failure does not change the Wake-up outcome,
|
|
389
|
+
failure streak, or notifications.
|
|
390
|
+
|
|
391
|
+
An entry whose mtime is more than **one hour** old is not delivered: core
|
|
392
|
+
renames it by appending `.dead`, logs one line, and keeps its contents
|
|
393
|
+
unchanged. A malformed entry, a filename with no valid leading wake-up number,
|
|
394
|
+
or a future-wake-up entry seen by a Wrapper is likewise dead-lettered on first
|
|
395
|
+
sight. `*.dead` files are never scanned again or automatically removed. With
|
|
396
|
+
no deliverer configured, the Wrapper flush is a no-op; each reconcile pass
|
|
397
|
+
writes one `cron.log` line per Pod with waiting entries, and the same one-hour
|
|
398
|
+
aging still applies (ADR 0025).
|
|
399
|
+
|
|
400
|
+
### Role Instructions for replying agents
|
|
401
|
+
|
|
402
|
+
Save the following as the replying Agent's instructions file and pass it to
|
|
403
|
+
`agent add --instructions <file>`:
|
|
404
|
+
|
|
405
|
+
```text
|
|
406
|
+
When you reply to a Conversation, write one JSON file per reply into your own
|
|
407
|
+
Pod's outbox at .agents/pods/$AGENTS_POD_POD_ID/outbox/ under the Pod project.
|
|
408
|
+
Name each file $AGENTS_POD_WAKEUP_N-<seq>-<name>.json, using a zero-padded
|
|
409
|
+
increasing <seq> (001, 002, ...) and a filename-safe <name>. Write
|
|
410
|
+
{"to":"<Delivery Address>","text":"<reply>"}; copy the address verbatim from
|
|
411
|
+
Reply-To in the Goal or from "to:" on the item's TASKS.md Inbox checkbox line.
|
|
412
|
+
Do not write an entry when that address is empty.
|
|
413
|
+
|
|
414
|
+
If you ask a question in a thread, reply in that same thread by reusing its
|
|
415
|
+
Delivery Address, including the thread component. Ask at most two clarification
|
|
416
|
+
rounds per uncertainty; then proceed on a stated assumption and record that
|
|
417
|
+
assumption in HANDOFF.md.
|
|
418
|
+
|
|
419
|
+
Core deletes an entry only after the deliverer exits 0. A failed delivery keeps
|
|
420
|
+
the file, logs one line, and is retried on the next pass with the same uuid; an
|
|
421
|
+
entry older than one hour is renamed with a .dead suffix and kept. Do not
|
|
422
|
+
rename, delete, or rewrite pending or dead entries yourself.
|
|
423
|
+
```
|
|
424
|
+
|
|
252
425
|
## Storage
|
|
253
426
|
|
|
254
427
|
```text
|
|
@@ -256,12 +429,15 @@ never notify (ADR 0021).
|
|
|
256
429
|
├── pods.json # global pod registry: definitions + state, 0600
|
|
257
430
|
├── agents.json # named agents: adapter type, model, role instructions, 0600
|
|
258
431
|
├── listeners.json # listeners, bounds, claimed item ids, 0600
|
|
432
|
+
├── sources.json # named drain commands, committed cursors, lastDrainAt
|
|
433
|
+
├── deliverer.json # the single outbound delivery command
|
|
259
434
|
├── inboxes/<id>/ # default listener inboxes; any dir/file may serve instead
|
|
260
435
|
└── registry.lock # short-lived registry write lock, 0600
|
|
261
436
|
|
|
262
437
|
<project>/.agents/pods/
|
|
263
438
|
└── <podId>/
|
|
264
|
-
├── docs/ # HANDOFF.md · DECISIONS.md · TASKS.md —
|
|
439
|
+
├── docs/ # HANDOFF.md · DECISIONS.md · TASKS.md — cross-wake work memory (ADR 0008)
|
|
440
|
+
├── outbox/ # pending <wakeup>-<seq>-<name>.json replies and retained *.dead entries
|
|
265
441
|
├── output.log # every wake-up, append-only, wake-up delimited; trimmed idle by
|
|
266
442
|
│ # reconcile: cap 10 MB, keep 2 MB, oldest sections dropped (ADR 0016), 0600
|
|
267
443
|
├── heartbeat # touched by the live wake-up's wrapper every 30s; mtime = last beat;
|
|
@@ -317,7 +493,18 @@ adapted from agents-cron); listeners — inbox directory and JSONL file
|
|
|
317
493
|
sources, per-item pods from templates, bound standing pods; wake admission
|
|
318
494
|
honouring `--after` dependencies and the per-project cap; the Pi extension's
|
|
319
495
|
two planes (ADR 0010) — `/pod` commands, `spawn_pod`, and `pod_handoff`.
|
|
320
|
-
No default timeout
|
|
496
|
+
No default Wake-up timeout — liveness is heartbeat-based or it is nothing.
|
|
497
|
+
|
|
498
|
+
**Shipped in `0.2.0`.** Chat as an intake and an outlet, both as operator
|
|
499
|
+
commands rather than a daemon: sources (`source add|list|rm`) drained once per
|
|
500
|
+
pass with a committed cursor and claim-before-cursor ordering; source-fed
|
|
501
|
+
Listeners, batched `## Inbox` delivery to a standing pod, and `## Waiting` above
|
|
502
|
+
it; outbox delivery (`deliver add|list|rm`) at wake-up exit and in every pass,
|
|
503
|
+
with stable uuids, one-hour dead-lettering and retained `*.dead` entries; the
|
|
504
|
+
Affinity Key and the `{{reply_to}}` Delivery Address riding the Goal or an Inbox
|
|
505
|
+
line; `listener add --project` for tuning intake and workers apart. A wrapper's
|
|
506
|
+
outcome is now recorded under the registry lock, so a concurrent pass or wrapper
|
|
507
|
+
cannot overwrite it and force the same wake-up to run twice (ADR 0018).
|
|
321
508
|
|
|
322
509
|
**Next.** The Claude Code and Codex adapters (`--type` is refused today, not
|
|
323
510
|
silently downgraded). Default pod expiry and guarded fan-out — the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agents-pod",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Supervision for long-running coding agents: detached pods, heartbeat liveness, cron-ticked reconcile, and a Pi extension for spawning and handoff",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|