@ours.network/fleet 0.12.0 → 0.13.1
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 +184 -0
- package/dist/briefing.js +10 -0
- package/dist/cli.js +404 -1
- package/dist/config.d.ts +18 -2
- package/dist/config.js +67 -3
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +111 -0
- package/dist/duration.js +7 -3
- package/dist/harness/claude-code.js +5 -0
- package/dist/harness/types.d.ts +6 -0
- package/dist/loops/config.d.ts +30 -0
- package/dist/loops/config.js +135 -0
- package/dist/loops/manager.d.ts +48 -0
- package/dist/loops/manager.js +237 -0
- package/dist/loops/state.d.ts +54 -0
- package/dist/loops/state.js +148 -0
- package/dist/monitor.js +26 -2
- package/dist/owner-channel/attachments.d.ts +74 -0
- package/dist/owner-channel/attachments.js +378 -0
- package/dist/owner-channel/channel.d.ts +114 -2
- package/dist/owner-channel/channel.js +622 -43
- package/dist/owner-channel/notices.d.ts +21 -0
- package/dist/owner-channel/notices.js +66 -0
- package/dist/owner-channel/state.d.ts +34 -0
- package/dist/owner-channel/state.js +148 -1
- package/dist/owner-channel/tasks.d.ts +62 -0
- package/dist/owner-channel/tasks.js +246 -0
- package/dist/resolved-plan.js +11 -0
- package/dist/runner.js +87 -7
- package/dist/session/acp.d.ts +5 -2
- package/dist/session/acp.js +83 -25
- package/dist/session/arbiter.d.ts +42 -0
- package/dist/session/arbiter.js +72 -0
- package/dist/session/control.d.ts +12 -1
- package/dist/session/control.js +56 -3
- package/dist/session/types.d.ts +26 -2
- package/dist/session/types.js +5 -2
- package/dist/spawn.js +1 -0
- package/dist/supervisor/systemd.js +12 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -236,6 +236,9 @@ ours-fleet config [-c FILE] validate + print merged plan
|
|
|
236
236
|
ours-fleet ls | attach | peek | logs [-f] | status <Name>
|
|
237
237
|
ours-fleet send <Name> "text" | --key <K>
|
|
238
238
|
ours-fleet spawn [--temp] <Name> [--harness --session --mission --model --approval ...]
|
|
239
|
+
ours-fleet loops validate|list|status
|
|
240
|
+
ours-fleet loops reload <Role>
|
|
241
|
+
ours-fleet loops run-now|disable|enable <Role> <Loop>
|
|
239
242
|
ours-fleet rm <Name>
|
|
240
243
|
ours-fleet doctor [--harness H]
|
|
241
244
|
ours-fleet init
|
|
@@ -285,6 +288,13 @@ roles:
|
|
|
285
288
|
owners: [owner-contact-cid] # authenticated ours contact IDs, never display names
|
|
286
289
|
interrupt: false # false queues; true cancels current work first
|
|
287
290
|
progress_interval_ms: 30000 # fleet-generated progress notices; 0 disables
|
|
291
|
+
attachments: # secure inbound documents, images, and voice
|
|
292
|
+
enabled: true
|
|
293
|
+
max_files_per_request: 4 # 1..32; rejected from metadata before retrieval
|
|
294
|
+
max_file_bytes: 10485760 # 10 MiB
|
|
295
|
+
max_request_bytes: 20971520 # 20 MiB total, and >= max_file_bytes
|
|
296
|
+
retention_ms: 86400000 # stale crash cleanup; 1 minute..30 days
|
|
297
|
+
allowed_mime: [application/pdf, text/plain, image/png, audio/ogg]
|
|
288
298
|
model: claude-fable-5 # launch on a specific model (pass-through id; default: launcher default)
|
|
289
299
|
mission: one line
|
|
290
300
|
persona: | # operating contract (published as persona)
|
|
@@ -316,6 +326,17 @@ roles:
|
|
|
316
326
|
fs: { read: [/opt/toolchains], write: [] } # extra binds (state dir + cwd always included)
|
|
317
327
|
resources: { mem: 2G, cpu: "1.5", pids: 512 }
|
|
318
328
|
secrets: ["/host/tok:/run/secrets/tok"] # host:container, mounted read-only
|
|
329
|
+
|
|
330
|
+
loops: # trusted local scheduled ACP turns
|
|
331
|
+
coordinator_pass:
|
|
332
|
+
roles: [FleetCoordinator] # or ["*"] for permanent roles only
|
|
333
|
+
interval: 10m # 1m..30d
|
|
334
|
+
initial_delay: 10m # default: one full interval; explicit 0s is immediate
|
|
335
|
+
jitter: 30s # default 0; less than interval and at most 1h
|
|
336
|
+
enabled: true
|
|
337
|
+
prompt: |
|
|
338
|
+
Review current fleet state once. Unstick only actionable work.
|
|
339
|
+
If nothing material changed, complete silently without an owner report.
|
|
319
340
|
```
|
|
320
341
|
|
|
321
342
|
Merge order: `fleet.yaml` ← `fleet.d/*.yaml`; a duplicate role name is a hard
|
|
@@ -327,6 +348,36 @@ role's `harness_options`, so a fleet can set common Codex permission/profile def
|
|
|
327
348
|
and override individual keys per role. `monitor` merges the same way — a role block
|
|
328
349
|
overrides `defaults.monitor` key-by-key.
|
|
329
350
|
|
|
351
|
+
### Scheduled agent loops
|
|
352
|
+
|
|
353
|
+
Top-level `loops` schedule literal prompts from trusted local YAML. Enabled targets
|
|
354
|
+
must use `session: acp`; temporary roles never inherit loops, including `roles:
|
|
355
|
+
["*"]`. Fleet rejects an enabled loop when its explicitly selected base config is
|
|
356
|
+
a symlink, is owned by another user, or is group/world writable. Prompts are
|
|
357
|
+
bounded and normalized at validation time, but only their size and SHA-256 appear
|
|
358
|
+
in `config`, `list`, logs, or durable state.
|
|
359
|
+
|
|
360
|
+
Each occurrence is idle-only. An owner, console, monitor, or earlier turn already
|
|
361
|
+
using the role causes that occurrence to be recorded as `skipped_busy` and
|
|
362
|
+
discarded. Missed ticks, races, and failures are likewise recorded once: there is
|
|
363
|
+
no backlog, coalescing, catch-up turn, retry-on-idle, or cadence drift. The default
|
|
364
|
+
first run is one full interval after startup; set `initial_delay: 0s` explicitly
|
|
365
|
+
for an immediate first attempt. Restart recovery marks an in-flight run abandoned,
|
|
366
|
+
skips overdue ticks, and resumes the fixed nominal cadence.
|
|
367
|
+
|
|
368
|
+
Operational state is a mode-0600 `.scheduled-loops.json` in the permanent role's
|
|
369
|
+
state directory. `disable` persists across restarts; `enable` cannot override
|
|
370
|
+
`enabled: false` in YAML. `reload` re-reads the remembered trusted config through
|
|
371
|
+
the authenticated private control socket. Prompt-only edits retain cadence;
|
|
372
|
+
schedule or selector changes reset that loop to its configured initial delay.
|
|
373
|
+
`run-now` still obeys idle-only admission and returns exit 3 when busy; an
|
|
374
|
+
unavailable or uncertain control plane returns exit 2 and is never retried.
|
|
375
|
+
|
|
376
|
+
A scheduled turn has typed internal provenance and no owner authority. It cannot
|
|
377
|
+
cancel owner work and its ordinary completion is local only. Material proactive
|
|
378
|
+
owner reporting remains possible solely through an already-open authenticated
|
|
379
|
+
owner-channel task route; a no-op Coordinator pass should complete silently.
|
|
380
|
+
|
|
330
381
|
### Never-prompt failure
|
|
331
382
|
|
|
332
383
|
An unattended role has no console, so a permission request has nobody to answer
|
|
@@ -457,6 +508,12 @@ Set `monitor.interrupt: true` on roles where every configured wake should cancel
|
|
|
457
508
|
the active turn before the notification is delivered. This is intentionally
|
|
458
509
|
content-blind: the supervisor cannot inspect encrypted message bodies, so all
|
|
459
510
|
events selected by `wake_sources` receive the same interrupt policy.
|
|
511
|
+
The default is `false`: a role that must begin a post-readiness mission
|
|
512
|
+
immediately, including second-and-later mail received while it is working, must
|
|
513
|
+
set `monitor.mode: fleet` and `monitor.interrupt: true` explicitly. Readiness and
|
|
514
|
+
mission delivery still use ordinary ours mail: the role announces readiness,
|
|
515
|
+
waits for a body-free `[fleet-monitor]` wake, then calls `get_messages`; fleet
|
|
516
|
+
does not inject the mission body through ACP.
|
|
460
517
|
It primes the notification cursor *before* the session launches
|
|
461
518
|
(no missed arrivals), cannot be orphaned or left deaf-but-armed, and writes its
|
|
462
519
|
health to `<agentDir>/.monitor-status` (`armed | degraded | failed`), surfaced in
|
|
@@ -492,6 +549,105 @@ to each owner/controller identity, and put the owners' immutable contact CIDs in
|
|
|
492
549
|
channel identity. Add it to the control plane just like another contact, then
|
|
493
550
|
message it directly.
|
|
494
551
|
|
|
552
|
+
The running supervisor remains the only process which binds that identity.
|
|
553
|
+
Operators manage it, and an active agent turn emits bounded updates, through the
|
|
554
|
+
role's authenticated Unix control socket:
|
|
555
|
+
|
|
556
|
+
```sh
|
|
557
|
+
ours-fleet owner-channel contact list Coordinator
|
|
558
|
+
ours-fleet owner-channel contact invite Coordinator --name Mobile
|
|
559
|
+
ours-fleet owner-channel contact add Coordinator --invite-file ./invite.txt --name Mobile
|
|
560
|
+
# Or keep invite material out of both argv and a file:
|
|
561
|
+
ours-fleet owner-channel contact add Coordinator --invite-stdin
|
|
562
|
+
|
|
563
|
+
ours-fleet owner-channel owner list Coordinator
|
|
564
|
+
ours-fleet owner-channel owner authorize Coordinator <exact-64-hex-contact-cid>
|
|
565
|
+
ours-fleet owner-channel owner revoke Coordinator <exact-64-hex-contact-cid>
|
|
566
|
+
|
|
567
|
+
# Used only from the active [fleet-owner] turn; body is stdin, never argv:
|
|
568
|
+
ours-fleet owner-channel update Coordinator <request-id> --phase working --message-stdin
|
|
569
|
+
|
|
570
|
+
# Register background work while that request is active, then report after its final:
|
|
571
|
+
ours-fleet owner-channel task open Coordinator <active-request-id>
|
|
572
|
+
ours-fleet owner-channel task report Coordinator <task-id> --phase done --message-stdin
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
Pairing is deliberately two-step. `contact add` accepts an invite and reports a
|
|
576
|
+
pending contact handshake; it never grants authority. After peer verification,
|
|
577
|
+
use `contact list` to obtain the immutable CID, then explicitly `owner authorize`
|
|
578
|
+
that exact CID. Invite generation prints the invite only to stdout. Acceptance
|
|
579
|
+
reads it from stdin or a file, never a process argument.
|
|
580
|
+
|
|
581
|
+
Configured `owners` are the declared baseline. Authorize/revoke operations add
|
|
582
|
+
a bounded dynamic overlay stored beside the role state in mode 0600 and applied
|
|
583
|
+
immediately by the already-bound channel. `owner list` shows each CID's
|
|
584
|
+
`baseline`/`dynamic` source and whether it is effective. The overlay survives
|
|
585
|
+
session/supervisor restart; it stores only CIDs and a bounded action audit—never
|
|
586
|
+
invites, message bodies, credentials, or keys. A corrupt overlay fails closed
|
|
587
|
+
(no effective owners and no mutation), and the last effective owner cannot be
|
|
588
|
+
revoked.
|
|
589
|
+
|
|
590
|
+
These commands require a running ACP role with `owner_channel` enabled. Missing,
|
|
591
|
+
stopped, tmux, disabled, draining, and unavailable-MCP targets fail without
|
|
592
|
+
starting a second client, binding an identity, or opening a network listener.
|
|
593
|
+
|
|
594
|
+
An owner request follows one ordered lifecycle on its authenticated source wire:
|
|
595
|
+
|
|
596
|
+
1. Fleet sends an immediate receipt describing started, queued, or interrupting state.
|
|
597
|
+
2. Periodic fleet-generated summaries may report allowlisted ACP activity shapes.
|
|
598
|
+
3. The agent may explicitly send multiple high-level updates with the per-request
|
|
599
|
+
ID injected into its prompt. Phases are `working`, `approval`, and `blocked`,
|
|
600
|
+
rendered as precise `🔄`, `🔐`, and `🚧` notices. The one-line body is limited
|
|
601
|
+
to 280 characters/1024 bytes, deduplicated, capped at 20, and rate-limited to
|
|
602
|
+
one every five seconds. Reasoning, secret-like material, raw logs/tool output,
|
|
603
|
+
control characters, and late or unknown request IDs are rejected.
|
|
604
|
+
4. Fleet waits for accepted intermediate sends, then emits exactly one final ACP
|
|
605
|
+
response (or a sanitized terminal outcome). Successful turns send regular files
|
|
606
|
+
from the request outbox afterward, correlated to the same source wire.
|
|
607
|
+
|
|
608
|
+
Fleet chooses the stored authenticated sender for every update; neither the CLI
|
|
609
|
+
caller nor model supplies a recipient. Update audit logs contain only the hashed
|
|
610
|
+
request ID prefix, phase, character count, sequence, and delivery result. Bodies
|
|
611
|
+
remain memory-only and never enter the wire-ID state, authorization overlay, or
|
|
612
|
+
logs. A crash therefore replays the deferred owner request instead of persisting
|
|
613
|
+
an unfinished update body. `/interrupt` remains responsive and makes later
|
|
614
|
+
updates for the cancelled request fail closed.
|
|
615
|
+
|
|
616
|
+
Background work must not keep an ACP turn open. During the active authenticated
|
|
617
|
+
request, `task open` accepts only its injected request ID and emits no owner
|
|
618
|
+
message. Fleet creates a random opaque task ID and durably stores only the exact
|
|
619
|
+
originating CID/wire route, expiry, counters, and content hashes. The agent may
|
|
620
|
+
then tell the owner that a specialist is working, finalize, and idle. After a
|
|
621
|
+
later fleet-mail wake it verifies the result and uses `task report` with
|
|
622
|
+
`progress`, `done`, or `blocked`; fleet sends a new proactive follow-up from the
|
|
623
|
+
already-bound channel identity, correlated to the original wire. The CLI has no
|
|
624
|
+
recipient option and never broadcasts. `done` and `blocked` close the task only
|
|
625
|
+
after a successful send.
|
|
626
|
+
|
|
627
|
+
Tasks expire after seven days and are capped at 32 open tasks per role and eight
|
|
628
|
+
per originating owner. Each allows at most 20 reports, one every five seconds.
|
|
629
|
+
Reports reuse the one-sentence 280-character/1024-byte safety checks and body
|
|
630
|
+
deduplication. Authorization is rechecked at send time; revocation deletes that
|
|
631
|
+
owner's pending routes. The mode-0600 task file is bounded and contains no
|
|
632
|
+
message/report bodies. Corruption fails closed. A durable `sending` marker is
|
|
633
|
+
written before transport: if delivery fails, the response is lost, or the
|
|
634
|
+
supervisor crashes mid-send, the task becomes `uncertain` and refuses automatic
|
|
635
|
+
retry or later reordering. This at-most-once retry policy avoids double delivery
|
|
636
|
+
when ours-mcp cannot prove whether a send crossed the boundary; an operator must
|
|
637
|
+
resolve an uncertain task out of band.
|
|
638
|
+
|
|
639
|
+
Coordinator workflow: spawn the specialist, run `task open` before the active
|
|
640
|
+
owner turn ends, tell the owner work is continuing and finalize, then idle. On
|
|
641
|
+
the fleet-monitor wake, inspect and verify the specialist's result before using
|
|
642
|
+
`task report ... --phase done|blocked`; do not keep the ACP turn alive or poll.
|
|
643
|
+
|
|
644
|
+
For mobile onboarding, create or accept the contact first, wait until `contact
|
|
645
|
+
list` reports it established, then authorize that exact CID. Authorization and
|
|
646
|
+
revocation take effect immediately and the bounded mode-0600 CID overlay survives
|
|
647
|
+
role/supervisor restarts; update bodies do not. After restart, the supervisor is
|
|
648
|
+
still the sole channel binder and deferred unfinished requests retain the normal
|
|
649
|
+
at-least-once replay contract.
|
|
650
|
+
|
|
495
651
|
The two paths are deliberately simultaneous and have different authority:
|
|
496
652
|
|
|
497
653
|
- Mail to the role's normal `identity` remains peer mail. The content-blind
|
|
@@ -512,6 +668,34 @@ across a crash (the bridge requeues fetched input before starting a turn); true
|
|
|
512
668
|
exactly-once processing would require a leased claim/idempotency primitive in
|
|
513
669
|
ours-mcp.
|
|
514
670
|
|
|
671
|
+
Inbound owner attachments use the same authenticated-CID and exact-wire routing
|
|
672
|
+
boundary. Fleet first calls the metadata-only `list_incoming_files`, groups a
|
|
673
|
+
file-only wake or a same-sender reply-linked text caption, and checks the enabled,
|
|
674
|
+
count, declared MIME, per-file size, and total-size policy before retrieving any
|
|
675
|
+
bytes. It then calls selective `get_files` only for the admitted wire IDs. An
|
|
676
|
+
unauthorized sender is ignored without retrieval or reply. A rejected authorized
|
|
677
|
+
request receives a bounded reason correlated to its file wire.
|
|
678
|
+
|
|
679
|
+
Retrieved files must be regular, non-symlink paths whose byte count and SHA-256
|
|
680
|
+
match ours-mcp metadata. Fleet additionally checks content signatures against the
|
|
681
|
+
declared MIME, sanitizes traversal/control characters from names, and copies each
|
|
682
|
+
file into a random request-scoped directory at mode 0700 with files at mode 0600.
|
|
683
|
+
The `[fleet-owner]` turn receives only bounded metadata, the private local paths,
|
|
684
|
+
and an explicit daemon transcription result for voice messages. Successful
|
|
685
|
+
transcripts are included; `failed` and `unavailable` states are stated plainly so
|
|
686
|
+
the agent must use the audio path rather than inventing text.
|
|
687
|
+
|
|
688
|
+
Request files are removed after final delivery and stale directories are removed
|
|
689
|
+
after `retention_ms`. A bounded mode-0600 recovery journal stores only owner CID
|
|
690
|
+
and wire routing metadata—never filenames, paths, captions, transcripts, or file
|
|
691
|
+
bytes. If ours-mcp already marked a selected file processed when fleet restarts,
|
|
692
|
+
fleet resumes only that journaled wire with `save_file`; recovered voice is
|
|
693
|
+
explicitly marked transcript-unavailable. Corrupt recovery state disables
|
|
694
|
+
attachment admission. The host must run an ours-mcp version whose
|
|
695
|
+
`list_incoming_files`, selective `get_files`, and `save_file` schemas support
|
|
696
|
+
these guarantees; `ours-mcp voice-status --json` reports whether transcription
|
|
697
|
+
is currently configured.
|
|
698
|
+
|
|
515
699
|
Owner channels currently require `session: acp`. Fleet needs structured,
|
|
516
700
|
turn-correlated assistant output for automatic replies; scraping a tmux pane
|
|
517
701
|
cannot reliably distinguish the final answer from thoughts, tool output, or
|
package/dist/briefing.js
CHANGED
|
@@ -70,6 +70,16 @@ export function generateBriefing(role, v, opts) {
|
|
|
70
70
|
L.push(` appropriate, and reply explicitly with **${v.sendTool}** to that peer.`);
|
|
71
71
|
L.push('System acceptance, queue, progress, interruption, failure, and final-delivery notices');
|
|
72
72
|
L.push('on the owner channel are fleet-generated; do not imitate or resend them.');
|
|
73
|
+
L.push('', '### Background specialist follow-ups');
|
|
74
|
+
L.push('If an authenticated owner request starts work that will finish after your current turn:');
|
|
75
|
+
L.push(`1. During that active turn run \`ours-fleet owner-channel task open ${role.name} <active-request-id>\`.`);
|
|
76
|
+
L.push('2. Keep the returned opaque task ID, tell the owner the specialist is working, and');
|
|
77
|
+
L.push(' finalize normally. Do not hold the ACP turn open and do not poll.');
|
|
78
|
+
L.push('3. When fleet mail later wakes you, verify the specialist result, then run');
|
|
79
|
+
L.push(` \`ours-fleet owner-channel task report ${role.name} <task-id> `
|
|
80
|
+
+ '--phase <progress|done|blocked> --message-stdin`.');
|
|
81
|
+
L.push('Fleet sends the bounded follow-up only to the exact authenticated originating owner;');
|
|
82
|
+
L.push('`done` and `blocked` close the task. Never supply or infer a recipient yourself.');
|
|
73
83
|
}
|
|
74
84
|
if (role.coordinator) {
|
|
75
85
|
L.push(`7. ANNOUNCE yourself: call **${v.sendTool}** to contact "${role.coordinator}" with text:`);
|