@ours.network/fleet 1.0.5 → 1.1.0-nightly.2
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 +90 -112
- package/dist/application/capabilities.d.ts +0 -1
- package/dist/application/capabilities.js +3 -13
- package/dist/application/fleet-query-service.d.ts +0 -3
- package/dist/application/fleet-query-service.js +1 -24
- package/dist/application/role-creation-service.d.ts +5 -5
- package/dist/application/role-creation-service.js +14 -9
- package/dist/application/role-removal-service.js +6 -3
- package/dist/application/role-repository.d.ts +1 -2
- package/dist/application/role-repository.js +7 -7
- package/dist/application/session-control.d.ts +0 -12
- package/dist/application/session-control.js +0 -42
- package/dist/application/session-mutations.d.ts +5 -5
- package/dist/application/types.d.ts +1 -8
- package/dist/briefing.js +3 -6
- package/dist/build-info.json +5 -5
- package/dist/cli.js +17 -28
- package/dist/config.d.ts +29 -6
- package/dist/config.js +320 -40
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +2 -2
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +51 -95
- package/dist/doctor.js +1 -8
- package/dist/fleet-proxy.d.ts +1 -1
- package/dist/generated-agent-source.d.ts +9 -0
- package/dist/generated-agent-source.js +53 -0
- package/dist/harness/acp-session-transport.d.ts +8 -0
- package/dist/harness/acp-session-transport.js +3 -0
- package/dist/harness/agent-session.d.ts +46 -0
- package/dist/harness/agent-session.js +1 -0
- package/dist/harness/claude-code-session.d.ts +22 -0
- package/dist/harness/claude-code-session.js +32 -0
- package/dist/harness/claude-code.d.ts +2 -1
- package/dist/harness/claude-code.js +39 -81
- package/dist/harness/codex-session.d.ts +22 -0
- package/dist/harness/codex-session.js +32 -0
- package/dist/harness/codex.d.ts +2 -1
- package/dist/harness/codex.js +49 -45
- package/dist/harness/registry.js +4 -0
- package/dist/harness/types.d.ts +6 -38
- package/dist/index.d.ts +3 -5
- package/dist/index.js +1 -3
- package/dist/init-guidance.d.ts +1 -0
- package/dist/init-guidance.js +5 -0
- package/dist/isolation/resources.d.ts +3 -7
- package/dist/isolation/resources.js +3 -7
- package/dist/model-env.d.ts +2 -4
- package/dist/model-env.js +4 -7
- package/dist/monitor.d.ts +1 -24
- package/dist/monitor.js +5 -140
- package/dist/ops.js +4 -2
- package/dist/owner-channel/channel.d.ts +2 -2
- package/dist/resolved-plan.d.ts +3 -2
- package/dist/resolved-plan.js +21 -5
- package/dist/runner.d.ts +5 -15
- package/dist/runner.js +26 -102
- package/dist/sensitive-config.d.ts +2 -0
- package/dist/sensitive-config.js +4 -0
- package/dist/session/acp.d.ts +2 -2
- package/dist/session/acp.js +2 -3
- package/dist/session/activity.d.ts +2 -3
- package/dist/session/activity.js +2 -3
- package/dist/session/arbiter.d.ts +4 -4
- package/dist/session/control.d.ts +3 -3
- package/dist/session/types.d.ts +11 -5
- package/dist/spawn.d.ts +4 -4
- package/dist/spawn.js +60 -32
- package/dist/supervisor/none.d.ts +1 -1
- package/dist/supervisor/none.js +2 -2
- package/dist/tmux.d.ts +1 -14
- package/dist/tmux.js +1 -51
- package/dist/watchdog/config.js +6 -4
- package/dist/watchdog/run.js +8 -23
- package/dist/web/auth.d.ts +1 -1
- package/dist/web/fleet-config-service.d.ts +11 -8
- package/dist/web/fleet-config-service.js +383 -183
- package/dist/web/runtime.js +4 -15
- package/dist/web/server.d.ts +1 -3
- package/dist/web/server.js +3 -14
- package/dist/web/topology-promote.js +18 -18
- package/dist/web/topology.js +2 -2
- package/dist/web/yaml-document-edit.js +2 -0
- package/dist/web-app/assets/index-59GF-gsZ.css +1 -0
- package/dist/web-app/assets/index-BbE9EX6n.js +10 -0
- package/dist/web-app/index.html +2 -2
- package/examples/fleet/agents/Alice.yaml +11 -0
- package/examples/fleet/agents/FleetCoordinator.yaml +7 -0
- package/examples/fleet/brains/claude-default.yaml +5 -0
- package/examples/fleet/roles/coordinator.yaml +5 -0
- package/examples/fleet/roles/developer.yaml +3 -0
- package/examples/fleet.yaml +74 -0
- package/package.json +4 -8
- package/dist/session/tmux.d.ts +0 -28
- package/dist/session/tmux.js +0 -80
- package/dist/web/terminal/bridge.d.ts +0 -27
- package/dist/web/terminal/bridge.js +0 -317
- package/dist/web-app/assets/TerminalView-C_G1ID2P.js +0 -9
- package/dist/web-app/assets/index-BCBK78hw.js +0 -10
- package/dist/web-app/assets/index-DuC-xnX4.css +0 -1
package/README.md
CHANGED
|
@@ -8,8 +8,8 @@ harnesses — from one declarative file.**
|
|
|
8
8
|
An AI coding agent in a terminal dies when you close the laptop. `ours-fleet`
|
|
9
9
|
turns such sessions into **roles**: long-lived agents that
|
|
10
10
|
|
|
11
|
-
- **run through
|
|
12
|
-
|
|
11
|
+
- **run through structured ACP sessions** behind a shared Codex/Claude Code
|
|
12
|
+
agent-session interface, which you can inspect or prompt,
|
|
13
13
|
- are **supervised** — systemd (Linux) or launchd (macOS) restarts them on crash
|
|
14
14
|
and brings them back after a reboot,
|
|
15
15
|
- **resume their context** across restarts (when the harness supports it),
|
|
@@ -19,10 +19,10 @@ turns such sessions into **roles**: long-lived agents that
|
|
|
19
19
|
- can **spawn subagents** (permanent or temporary) and **oversee** them: peek into
|
|
20
20
|
a ward's console, answer a stuck prompt, nudge it back to work.
|
|
21
21
|
|
|
22
|
-
The
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
reality match
|
|
22
|
+
The fleet uses a v2 manifest plus typed documents: `~/fleet.yaml` holds
|
|
23
|
+
fleet-wide operational policy and automation, while `~/fleet/agents`,
|
|
24
|
+
`~/fleet/roles`, and `~/fleet/brains` hold bare Agent, Role, and Brain documents.
|
|
25
|
+
`ours-fleet up` makes reality match that trusted split source set.
|
|
26
26
|
|
|
27
27
|
**Harness-agnostic by design.** The core never assumes a specific agent CLI; each
|
|
28
28
|
harness is a small adapter (how to launch, how to resume, how to wire config).
|
|
@@ -31,21 +31,22 @@ public — each additional harness (Gemini CLI, OpenCode, …) is a small adapte
|
|
|
31
31
|
A single fleet can mix harnesses per role:
|
|
32
32
|
|
|
33
33
|
```yaml
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
# ~/fleet/agents/Reviewer.yaml
|
|
35
|
+
role: { ref: reviewer }
|
|
36
|
+
brain: { inline: { harness: claude-code, session: acp } }
|
|
37
|
+
|
|
38
|
+
# ~/fleet/agents/Prototyper.yaml
|
|
39
|
+
role: { inline: { mission: Build prototypes } }
|
|
40
|
+
brain: { inline: { harness: codex, session: acp } }
|
|
39
41
|
```
|
|
40
42
|
|
|
41
43
|
## How it works
|
|
42
44
|
|
|
43
45
|
```
|
|
44
|
-
~/fleet.yaml + ~/fleet
|
|
46
|
+
~/fleet.yaml + ~/fleet/{agents,roles,brains}/*.yaml your declaration
|
|
45
47
|
│ ours-fleet up
|
|
46
48
|
▼
|
|
47
|
-
briefing.md per role ──►
|
|
48
|
-
└─► ACP client ──► ACP agent (codex-acp …)
|
|
49
|
+
briefing.md per role ──► agent session adapter ──► ACP client/session ──► ACP agent
|
|
49
50
|
▲ │
|
|
50
51
|
systemd --user / launchd ───────┘ restart on crash, start at boot/login
|
|
51
52
|
```
|
|
@@ -78,7 +79,6 @@ The state dir contract:
|
|
|
78
79
|
| What | Why | Install |
|
|
79
80
|
|---|---|---|
|
|
80
81
|
| Node ≥ 20 | runs `ours-fleet` itself | nodejs.org, `apt`, or `brew` |
|
|
81
|
-
| tmux | roles using `session: tmux` (the default) | `apt install tmux` / `brew install tmux` |
|
|
82
82
|
| Node ≥ 22 | Claude roles using `session: acp` | required by the maintained Claude ACP adapter |
|
|
83
83
|
| a harness CLI, logged in | the agent itself | e.g. Claude Code (`claude`) or Codex CLI (`codex`) |
|
|
84
84
|
| `ours` CLI + shared daemon | identity + agent-to-agent messaging | `npm i -g @ours.network/cli && ours daemon start` |
|
|
@@ -98,8 +98,8 @@ ours-fleet doctor # verifies everything above, with actionable messages
|
|
|
98
98
|
The maintained Codex and Claude ACP adapters install as optional dependencies of
|
|
99
99
|
`ours-fleet` and are resolved internally; users do not install adapter commands
|
|
100
100
|
or add them to `PATH`. An explicit `session_options.acp.command` remains
|
|
101
|
-
available for custom adapters. On Node 20–21,
|
|
102
|
-
|
|
101
|
+
available for custom adapters. On Node 20–21, Codex ACP remains available,
|
|
102
|
+
while maintained Claude ACP requires upgrading to Node 22.
|
|
103
103
|
|
|
104
104
|
Each OS user manages their own fleet — to host roles under a sandboxed account,
|
|
105
105
|
become that account and repeat.
|
|
@@ -107,8 +107,9 @@ become that account and repeat.
|
|
|
107
107
|
## Quickstart
|
|
108
108
|
|
|
109
109
|
```sh
|
|
110
|
+
cp -R "$(npm root -g)/@ours.network/fleet/examples/fleet" ~/fleet
|
|
110
111
|
cp "$(npm root -g)/@ours.network/fleet/examples/fleet.yaml" ~/fleet.yaml
|
|
111
|
-
$EDITOR ~/fleet
|
|
112
|
+
$EDITOR ~/fleet/agents/*.yaml # compose Role + Brain and operational settings
|
|
112
113
|
ours-fleet up # boot the fleet (staggered)
|
|
113
114
|
ours-fleet ls # running consoles
|
|
114
115
|
ours-fleet attach Alice # watch one live (Ctrl-b d to leave)
|
|
@@ -139,8 +140,8 @@ fleet monitor policy, and same-harness model inherit from the caller. Explicit
|
|
|
139
140
|
flags win; changing harness without a model lets the selected harness/fleet
|
|
140
141
|
defaults choose one. After creation succeeds, fleet can deterministically notify
|
|
141
142
|
the caller's owner channel with the caller and spawned-role details. This is an
|
|
142
|
-
honest-actor convenience and attribution path, not a security boundary;
|
|
143
|
-
|
|
143
|
+
honest-actor convenience and attribution path, not a security boundary; host
|
|
144
|
+
shells and deliberately bypassed absolute binaries retain direct behavior.
|
|
144
145
|
|
|
145
146
|
## Local web console
|
|
146
147
|
|
|
@@ -209,8 +210,8 @@ offline shell and no stale fleet state.
|
|
|
209
210
|
|
|
210
211
|
The console provides evidence-separated inventory and status, ACP activity and
|
|
211
212
|
permission controls, redacted logs, typed text send, confirmed lifecycle
|
|
212
|
-
actions, transactional permanent/temporary creation
|
|
213
|
-
|
|
213
|
+
actions, and transactional permanent/temporary creation. Identity is fixed to
|
|
214
|
+
the role name. Creation uses the authenticated
|
|
214
215
|
daemon inventory and reports verified, missing, or unknown evidence. A missing
|
|
215
216
|
permanent role identity is created deterministically before launch with local
|
|
216
217
|
discovery and auto-accept enabled, then the provisioning lease is released so
|
|
@@ -243,41 +244,30 @@ never force-adopts or deletes identity state. Permanent roles are
|
|
|
243
244
|
provisioned by fleet before launch and never delegate normal identity creation
|
|
244
245
|
to the harness.
|
|
245
246
|
|
|
246
|
-
|
|
247
|
-
non-terminal features remain available and tmux Terminal is disabled with a
|
|
248
|
-
diagnostic.
|
|
247
|
+
The web console uses structured ACP activity rather than exposing a raw agent terminal.
|
|
249
248
|
|
|
250
249
|
Security boundaries:
|
|
251
250
|
|
|
252
251
|
- configured `Host` and `Origin`, CSRF, one-time WebSocket tickets, and explicit
|
|
253
252
|
bind/origin policy are enforced server-side;
|
|
254
253
|
- cwd values must resolve beneath configured local roots;
|
|
255
|
-
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
hand-written
|
|
262
|
-
|
|
263
|
-
The web
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
comments, spacing and quoting. One bounded exception: changing the *length* of a
|
|
273
|
-
block sequence — adding or removing an entry under `watch:`, `oversee:`, `roles:`
|
|
274
|
-
or `wake_sources:` — rewrites that one collection as a whole, which drops inline
|
|
275
|
-
comments written on its individual items. The loss is confined to the collection
|
|
276
|
-
you edited, is shown in the diff before anything is written, and can be declined
|
|
277
|
-
by not saving. Each write is revision-guarded, reviewed as a diff of the real
|
|
278
|
-
file, validated with the real loader, and preceded by a timestamped backup.
|
|
279
|
-
Values under `env:` — and the `vars:` entries they interpolate — are masked in
|
|
280
|
-
the diff and never leave the host.
|
|
254
|
+
- normal logs are bounded and redacted;
|
|
255
|
+
- tests use temporary fleet homes and fake supervisors/identity providers—never
|
|
256
|
+
active role state.
|
|
257
|
+
|
|
258
|
+
Permanent spawns write a bare Agent document to
|
|
259
|
+
`~/fleet/agents/<Name>.yaml`; generated-source ownership is recorded so removal
|
|
260
|
+
never deletes an unproven hand-written neighbor. `ours-fleet rm <Name>` unspawns.
|
|
261
|
+
|
|
262
|
+
The web editor reads and writes the split document model explicitly as
|
|
263
|
+
`{manifest, agents}`. Role and Brain presets are visible through Agent refs but
|
|
264
|
+
remain read-only. Saves use one aggregate revision over the manifest and all
|
|
265
|
+
Agent/Role/Brain sources, validate an exact-stem private staging tree, show a
|
|
266
|
+
deterministic redacted diff per document, and hold one manifest-root lock.
|
|
267
|
+
Changed/deleted files are backed up together; any partial failure restores every
|
|
268
|
+
document or reports the private recovery directory. An unchanged save is a
|
|
269
|
+
byte-for-byte no-op and creates no backup. Nested environment, authentication,
|
|
270
|
+
invite, and isolation secrets never leave the host.
|
|
281
271
|
|
|
282
272
|
From inside Claude Code, Codex, or Hermes with the core `ours` plugin installed,
|
|
283
273
|
say **"spawn an ours agent …"**. The core skill checks for `ours-fleet`, installs
|
|
@@ -292,20 +282,19 @@ them and unsticks them:
|
|
|
292
282
|
|
|
293
283
|
```sh
|
|
294
284
|
ours-fleet peek Worker # what is it doing?
|
|
295
|
-
ours-fleet send Worker --key 1 # answer the menu it's stuck on
|
|
296
285
|
ours-fleet send Worker "continue with the tests, then report"
|
|
297
286
|
```
|
|
298
287
|
|
|
299
|
-
Declare standing assignments in
|
|
288
|
+
Declare standing assignments in the overseer's bare Agent document (rendered into its
|
|
300
289
|
briefing) — or just write "keep an eye on Alice and Bob every 5 minutes" in a
|
|
301
290
|
persona; the bundled `oversee-agents` skill defines what that means operationally:
|
|
302
291
|
|
|
303
292
|
```yaml
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
293
|
+
role: { ref: coordinator }
|
|
294
|
+
brain: { ref: claude-default }
|
|
295
|
+
oversee:
|
|
296
|
+
- { agent: Alice, interval: 5m }
|
|
297
|
+
- { agent: Bob, interval: 5m }
|
|
309
298
|
```
|
|
310
299
|
|
|
311
300
|
## Command reference
|
|
@@ -315,7 +304,7 @@ ours-fleet docs | man AI-friendly complete reference
|
|
|
315
304
|
ours-fleet up|down|restart|force-restart [-c FILE] [Name...]
|
|
316
305
|
ours-fleet config [-c FILE] validate + print merged plan
|
|
317
306
|
ours-fleet ls | attach | peek | logs [-f] | status <Name>
|
|
318
|
-
ours-fleet send <Name> "text"
|
|
307
|
+
ours-fleet send <Name> "text"
|
|
319
308
|
ours-fleet spawn [--temp] [<Name> | --role <Name>] [--harness --session --mission --model --approval ...]
|
|
320
309
|
ours-fleet loops validate|list|status
|
|
321
310
|
ours-fleet loops reload <Role>
|
|
@@ -348,30 +337,32 @@ in its state dir) across supervisor-triggered restarts — systemd/launchd re-in
|
|
|
348
337
|
agent process with no arguments, so without this the role would silently fall back to
|
|
349
338
|
the default `~/fleet.yaml` on its very first crash-restart and fail to resolve.
|
|
350
339
|
|
|
351
|
-
##
|
|
340
|
+
## split configuration field reference
|
|
352
341
|
|
|
353
|
-
|
|
342
|
+
The following is a schematic field inventory, not a single YAML document. Host
|
|
343
|
+
settings belong in `~/fleet.yaml`; reusable behavior and harness settings belong
|
|
344
|
+
in `~/fleet/roles/<id>.yaml` and `~/fleet/brains/<id>.yaml`; operational fields
|
|
345
|
+
belong in `~/fleet/agents/<id>.yaml`.
|
|
346
|
+
|
|
347
|
+
```text
|
|
354
348
|
vars: { work_root: /home/me/work } # ${var} substitution anywhere below
|
|
349
|
+
api_version: ours.network/fleet/v2
|
|
355
350
|
start_stagger_ms: 0 # delay between agent LAUNCHES (host-wide, ms); 0 = no stagger
|
|
356
351
|
defaults:
|
|
357
|
-
harness: claude-code # for roles that don't set one
|
|
358
|
-
session: tmux # tmux (default) | acp
|
|
359
352
|
permissions: # common intent, translated by each harness/backend
|
|
360
353
|
approval: ask # ask | auto | allow (`deny` is a deprecated alias)
|
|
361
354
|
filesystem: workspace # read-only | workspace | unrestricted
|
|
362
355
|
unattended: deny # deny | wait
|
|
363
|
-
model: claude-fable-5 # default model for roles that don't set one (per-role model / --model wins)
|
|
364
|
-
max_tokens: 500000 # session cap (harness-interpreted)
|
|
365
356
|
monitor:
|
|
366
357
|
mode: fleet # fleet (default) | native
|
|
367
358
|
worklog: # built-ins shown; set false to opt out
|
|
368
359
|
max_kb: 1024 # rotate only above this active-log size
|
|
369
360
|
keep_tail_kb: 256 # UTF-8 tail; line-aligned when one fits
|
|
370
361
|
max_archives: 12 # recent beside log; older preserved cold
|
|
371
|
-
|
|
372
|
-
Name: # [A-Za-z0-9_-]+
|
|
362
|
+
Agent document:
|
|
363
|
+
Name: # filename stem; [A-Za-z0-9_-]+
|
|
373
364
|
harness: claude-code
|
|
374
|
-
session: acp #
|
|
365
|
+
session: acp # optional; ACP is the only supported session
|
|
375
366
|
session_options:
|
|
376
367
|
acp:
|
|
377
368
|
command: claude-agent-acp # optional advanced override
|
|
@@ -406,7 +397,7 @@ roles:
|
|
|
406
397
|
bio: | # public card (published as bio)
|
|
407
398
|
briefing_file: curated.md # replaces the generated narrative
|
|
408
399
|
env: { KEY: value } # extra session env
|
|
409
|
-
oversee: [{
|
|
400
|
+
oversee: [{ agent: X, interval: 5m }]
|
|
410
401
|
harness_options: # adapter-owned, adapter-validated
|
|
411
402
|
plugins: { "name@marketplace": false } # claude-code: plugin overrides
|
|
412
403
|
# mem_palace: false # claude-code: disable memory plugin
|
|
@@ -444,17 +435,15 @@ loops: # trusted local scheduled ACP turns
|
|
|
444
435
|
If nothing material changed, complete silently without an owner report.
|
|
445
436
|
```
|
|
446
437
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
and override individual keys per role. `monitor` merges the same way — a role block
|
|
454
|
-
overrides `defaults.monitor` key-by-key.
|
|
438
|
+
The v2 loader reads the manifest, then sorted bare Role, Brain, and Agent
|
|
439
|
+
documents from its exact stem directory. Duplicate filename-derived IDs are a
|
|
440
|
+
hard error. Identities and Agents are decoupled—removing an Agent never deletes
|
|
441
|
+
an identity. Role, Brain, and operational ownership are composed explicitly;
|
|
442
|
+
there is no generic cross-kind merge. Manifest operational defaults such as
|
|
443
|
+
`monitor` and `permissions` merge only within their owning Agent fields.
|
|
455
444
|
|
|
456
445
|
Every supervised role is a client of the operator-configured ours daemon. Fleet strips the
|
|
457
|
-
obsolete, presence-sensitive `OURS_AUTOSTART` variable from
|
|
446
|
+
obsolete, presence-sensitive `OURS_AUTOSTART` variable from ACP harness
|
|
458
447
|
processes. `ours-mcp proxy` is client-only and never starts a daemon; operators and explicit
|
|
459
448
|
installer/setup flows remain responsible for starting it.
|
|
460
449
|
|
|
@@ -597,9 +586,7 @@ since a human can still attach a console and answer.
|
|
|
597
586
|
`auto` selects Codex ACP `agent` (`on-request` + `workspace-write`) / Claude
|
|
598
587
|
`acceptEdits`. `allow` selects Codex ACP's fully non-interactive yolo mode,
|
|
599
588
|
reported by the adapter as `agent-full-access` (`never` +
|
|
600
|
-
`danger-full-access`); Claude uses `bypassPermissions`.
|
|
601
|
-
approval and sandbox flags remain independent, `auto` is `on-request` and
|
|
602
|
-
`allow` is `never` while `filesystem` still chooses the sandbox.
|
|
589
|
+
`danger-full-access`); Claude uses `bypassPermissions`.
|
|
603
590
|
`dontAsk` suppresses only the *prompt*, not the denial, which is why an
|
|
604
591
|
`allow` role previously ran unable to do its job. Legacy `deny` is accepted
|
|
605
592
|
only for compatibility and retains its conservative Codex `on-request` /
|
|
@@ -689,8 +676,8 @@ their boots ~4 s apart instead of firing all seven at once.
|
|
|
689
676
|
With `monitor.mode: fleet` (the default), the **ours-fleet supervisor** delivers
|
|
690
677
|
a role's mail wakes: the per-role runner long-polls the ours daemon's notification API and
|
|
691
678
|
submits a single `[fleet-monitor] N new messages from … — run get_messages` prompt
|
|
692
|
-
through the
|
|
693
|
-
and falls back to structured `session/prompt
|
|
679
|
+
through the shared agent session. ACP uses live steering when its adapter supports it
|
|
680
|
+
and falls back to structured `session/prompt`.
|
|
694
681
|
Set `monitor.interrupt: true` on roles where every configured wake should cancel
|
|
695
682
|
the active turn before the notification is delivered. This is intentionally
|
|
696
683
|
content-blind: the supervisor cannot inspect encrypted message bodies, so all
|
|
@@ -700,8 +687,7 @@ tool result or pending permission. Fleet waits for terminal ACP tool/update
|
|
|
700
687
|
evidence, then steers the wake without calling `session.cancel`. If the tool is
|
|
701
688
|
still active after 120 seconds, or the adapter cannot expose authenticated tool
|
|
702
689
|
boundaries/steering, fleet visibly degrades to non-cancelling steering or queued
|
|
703
|
-
delivery.
|
|
704
|
-
interrupts remain immediate.
|
|
690
|
+
delivery. Explicit human and control interrupts remain immediate.
|
|
705
691
|
The default is `false`: a role that must begin a post-readiness mission
|
|
706
692
|
immediately, including second-and-later mail received while it is working, must
|
|
707
693
|
set `monitor.mode: fleet` and `monitor.interrupt: true` explicitly. Readiness and
|
|
@@ -794,8 +780,8 @@ invites, message bodies, credentials, or keys. A corrupt overlay fails closed
|
|
|
794
780
|
(no effective owners and no mutation), and the last effective owner cannot be
|
|
795
781
|
revoked.
|
|
796
782
|
|
|
797
|
-
These commands require a running
|
|
798
|
-
stopped,
|
|
783
|
+
These commands require a running role with `owner_channel` enabled. Missing,
|
|
784
|
+
stopped, disabled, draining, and unavailable-MCP targets fail without
|
|
799
785
|
starting a second client, binding an identity, or opening a network listener.
|
|
800
786
|
|
|
801
787
|
The managed agent can use its ordinary ours `send_message` or `send_file` tool to
|
|
@@ -1015,11 +1001,8 @@ gets a durable pre-send marker: a failed/ambiguous send becomes `uncertain` and
|
|
|
1015
1001
|
not blindly retried, while files not yet attempted remain recoverable after restart.
|
|
1016
1002
|
Logs contain counts and byte totals, never filenames or raw bytes.
|
|
1017
1003
|
|
|
1018
|
-
Owner channels
|
|
1019
|
-
|
|
1020
|
-
cannot reliably distinguish the final answer from thoughts, tool output, or
|
|
1021
|
-
unrelated concurrent work. The config rejects tmux instead of silently offering
|
|
1022
|
-
weaker semantics.
|
|
1004
|
+
Owner channels use the structured agent-session interface for turn-correlated
|
|
1005
|
+
assistant output and reliable automatic replies.
|
|
1023
1006
|
|
|
1024
1007
|
## Codex roles
|
|
1025
1008
|
|
|
@@ -1050,27 +1033,22 @@ the structured CLI watcher, because a detached process cannot wake a Codex turn.
|
|
|
1050
1033
|
wait is re-entered after each handled message; `ours-codex` instead wakes the idle
|
|
1051
1034
|
session through its App Server integration.
|
|
1052
1035
|
|
|
1053
|
-
The main Codex controls
|
|
1036
|
+
The main Codex controls are Brain-owned and also available when spawning:
|
|
1054
1037
|
|
|
1055
1038
|
```yaml
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
roles:
|
|
1071
|
-
Reviewer:
|
|
1072
|
-
harness_options:
|
|
1073
|
-
sandbox: read-only # overrides just this default key
|
|
1039
|
+
# ~/fleet/brains/codex-review.yaml
|
|
1040
|
+
harness: codex
|
|
1041
|
+
session: acp
|
|
1042
|
+
model: gpt-5.4
|
|
1043
|
+
harness_options:
|
|
1044
|
+
launcher: auto
|
|
1045
|
+
profile: fleet
|
|
1046
|
+
sandbox: read-only
|
|
1047
|
+
approval: on-request
|
|
1048
|
+
monitor: true
|
|
1049
|
+
search: true
|
|
1050
|
+
add_dirs: [/data/shared]
|
|
1051
|
+
config: { model_reasoning_effort: high }
|
|
1074
1052
|
```
|
|
1075
1053
|
|
|
1076
1054
|
Equivalent one-off/permanent spawn controls include `--model`, `--permission-mode`,
|
|
@@ -1083,7 +1061,7 @@ role-specific ours daemon, or configure the host default in `~/.ours/config.json
|
|
|
1083
1061
|
|
|
1084
1062
|
Each role can be sandboxed at the environment level via an `isolation:` block —
|
|
1085
1063
|
**fully additive: a role with no block behaves exactly as before.** The agent's
|
|
1086
|
-
|
|
1064
|
+
agent process is wrapped in [bubblewrap](https://github.com/containers/bubblewrap)
|
|
1087
1065
|
(rootless, no setuid), resource-limited by `systemd-run --user --scope`.
|
|
1088
1066
|
|
|
1089
1067
|
An empty `isolation: {}` gives a sensible default posture: filesystem-confined to
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { RoleRecord, RoleCapabilities, RoleStatus } from './types.js';
|
|
2
2
|
export interface CapabilityContext {
|
|
3
|
-
terminalPtyAvailable?: boolean;
|
|
4
3
|
controlProtocolVersion?: number;
|
|
5
4
|
}
|
|
6
5
|
export declare function roleCapabilities(role: RoleRecord, status: RoleStatus, context?: CapabilityContext): RoleCapabilities;
|
|
@@ -1,30 +1,20 @@
|
|
|
1
1
|
export function roleCapabilities(role, status, context = {}) {
|
|
2
|
-
const backend = role.configuredBackend ?? (role.detectedBackend === 'acp'
|
|
2
|
+
const backend = role.configuredBackend ?? (role.detectedBackend === 'acp' ? 'acp' : undefined);
|
|
3
3
|
const online = status.session.reachability === 'online';
|
|
4
4
|
const acp = backend === 'acp';
|
|
5
|
-
const tmux = backend === 'tmux';
|
|
6
5
|
const protocolVersion = context.controlProtocolVersion ?? 1;
|
|
7
6
|
const inactive = status.overall === 'offline'
|
|
8
7
|
|| (!online && status.supervisor.liveness === 'stopped');
|
|
9
|
-
const terminalAvailable = tmux && online && context.terminalPtyAvailable === true;
|
|
10
|
-
const terminalReason = terminalAvailable ? undefined
|
|
11
|
-
: !tmux ? 'wrong_backend'
|
|
12
|
-
: !online ? 'offline'
|
|
13
|
-
: 'pty_unavailable';
|
|
14
8
|
return {
|
|
15
9
|
protocolVersion,
|
|
16
10
|
inventory: true,
|
|
17
11
|
status: true,
|
|
18
12
|
output: {
|
|
19
|
-
recent: acp
|
|
13
|
+
recent: acp, stream: acp && protocolVersion >= 2,
|
|
20
14
|
structured: acp, replayCursor: acp && protocolVersion >= 2,
|
|
21
15
|
},
|
|
22
|
-
input: { text: online &&
|
|
16
|
+
input: { text: online && acp, interrupt: online && acp, steering: false },
|
|
23
17
|
permissions: { observe: acp, respond: acp && online },
|
|
24
|
-
terminal: {
|
|
25
|
-
available: terminalAvailable, reason: terminalReason,
|
|
26
|
-
multiViewer: terminalAvailable, writerLease: terminalAvailable,
|
|
27
|
-
},
|
|
28
18
|
lifecycle: {
|
|
29
19
|
start: role.lifetime === 'permanent' && status.supervisor.liveness !== 'running',
|
|
30
20
|
stop: role.lifetime === 'permanent' && !inactive && status.supervisor.liveness !== 'stopped',
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import type { SupervisorBackend } from '../supervisor/types.js';
|
|
2
2
|
import { controlRequest } from '../session/control.js';
|
|
3
|
-
import { Tmux } from '../tmux.js';
|
|
4
3
|
import { type CapabilityContext } from './capabilities.js';
|
|
5
4
|
import { RoleRepository } from './role-repository.js';
|
|
6
5
|
import type { RoleCapabilities, RoleRecord, RoleStatus } from './types.js';
|
|
7
6
|
export interface FleetQueryOptions {
|
|
8
7
|
repository: RoleRepository;
|
|
9
8
|
supervisor: SupervisorBackend;
|
|
10
|
-
tmux?: Tmux;
|
|
11
9
|
control?: typeof controlRequest;
|
|
12
10
|
capabilityContext?: CapabilityContext;
|
|
13
11
|
/**
|
|
@@ -24,7 +22,6 @@ export interface FleetQueryOptions {
|
|
|
24
22
|
}
|
|
25
23
|
export declare class FleetQueryService {
|
|
26
24
|
private readonly options;
|
|
27
|
-
private readonly tmux;
|
|
28
25
|
private readonly control;
|
|
29
26
|
constructor(options: FleetQueryOptions);
|
|
30
27
|
list(): Promise<Array<{
|
|
@@ -4,7 +4,6 @@ import { classifyActivity } from '../session/activity.js';
|
|
|
4
4
|
import { controlRequest } from '../session/control.js';
|
|
5
5
|
import { SessionControlError } from '../session/types.js';
|
|
6
6
|
import { readExitRecord, readRestartLedger } from '../runner.js';
|
|
7
|
-
import { Tmux } from '../tmux.js';
|
|
8
7
|
import { roleCapabilities } from './capabilities.js';
|
|
9
8
|
import { FleetError } from './errors.js';
|
|
10
9
|
const clean = (value, max = 512) => value.replace(/[\0-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '').trim().slice(0, max);
|
|
@@ -50,7 +49,7 @@ function sessionOverall(supervisor, session, restart, monitor, isolation, proble
|
|
|
50
49
|
|| problems.some(problem => problem.severity === 'error' || problem.source === 'watchdog')
|
|
51
50
|
|| session.readiness === 'failed')
|
|
52
51
|
return 'attention';
|
|
53
|
-
// A reachable
|
|
52
|
+
// A reachable agent session is the user's live interaction surface. Its
|
|
54
53
|
// evidence remains authoritative even when a service manager no longer owns
|
|
55
54
|
// the process (for example, a detached ACP session with an inactive unit).
|
|
56
55
|
if (session.reachability === 'online'
|
|
@@ -69,11 +68,9 @@ function sessionOverall(supervisor, session, restart, monitor, isolation, proble
|
|
|
69
68
|
}
|
|
70
69
|
export class FleetQueryService {
|
|
71
70
|
options;
|
|
72
|
-
tmux;
|
|
73
71
|
control;
|
|
74
72
|
constructor(options) {
|
|
75
73
|
this.options = options;
|
|
76
|
-
this.tmux = options.tmux ?? new Tmux();
|
|
77
74
|
this.control = options.control ?? controlRequest;
|
|
78
75
|
}
|
|
79
76
|
async list() {
|
|
@@ -174,26 +171,6 @@ export class FleetQueryService {
|
|
|
174
171
|
};
|
|
175
172
|
}
|
|
176
173
|
}
|
|
177
|
-
if (intended === 'tmux' || role.detectedBackend === 'tmux') {
|
|
178
|
-
try {
|
|
179
|
-
const has = await this.tmux.has(role.id);
|
|
180
|
-
return {
|
|
181
|
-
backend: 'tmux', reachability: has ? 'online' : supervisor === 'stopped' ? 'offline' : 'unknown',
|
|
182
|
-
readiness: has ? 'idle' : supervisor === 'running' ? 'starting' : 'failed',
|
|
183
|
-
evidence: 'inferred',
|
|
184
|
-
// tmux exposes no agent-side evidence at all, and `readiness: idle`
|
|
185
|
-
// here is a pane-liveness inference, not an activity claim.
|
|
186
|
-
activity: { state: 'unobservable' },
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
catch (error) {
|
|
190
|
-
return {
|
|
191
|
-
backend: 'tmux', reachability: 'unknown', readiness: 'unknown',
|
|
192
|
-
evidence: 'inferred', lastError: clean(error.message),
|
|
193
|
-
activity: { state: 'unobservable' },
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
174
|
return {
|
|
198
175
|
backend: 'unknown', reachability: supervisor === 'stopped' ? 'offline' : 'unknown',
|
|
199
176
|
readiness: supervisor === 'stopped' ? 'failed' : 'unknown', evidence: 'inferred',
|
|
@@ -12,7 +12,7 @@ export interface CreateRoleSessionRequest {
|
|
|
12
12
|
/** null means the selected harness's own default; web blank fields send null. */
|
|
13
13
|
model?: string | null;
|
|
14
14
|
reasoningEffort?: string | null;
|
|
15
|
-
session: 'acp'
|
|
15
|
+
session: 'acp';
|
|
16
16
|
cwd?: string;
|
|
17
17
|
lifetime: 'permanent' | 'temporary';
|
|
18
18
|
mission?: string;
|
|
@@ -41,7 +41,7 @@ export interface CreationCapabilities {
|
|
|
41
41
|
harnesses: Array<{
|
|
42
42
|
id: 'codex' | 'claude-code';
|
|
43
43
|
available: boolean;
|
|
44
|
-
sessions: Array<'acp'
|
|
44
|
+
sessions: Array<'acp'>;
|
|
45
45
|
defaultModel?: string;
|
|
46
46
|
models: string[];
|
|
47
47
|
modelOptions: HarnessModelOption[];
|
|
@@ -71,7 +71,7 @@ export interface CreationPreview {
|
|
|
71
71
|
name: string;
|
|
72
72
|
identity: string;
|
|
73
73
|
harness: string;
|
|
74
|
-
session: 'acp'
|
|
74
|
+
session: 'acp';
|
|
75
75
|
model?: string;
|
|
76
76
|
reasoningEffort?: string;
|
|
77
77
|
cwd?: string;
|
|
@@ -95,7 +95,7 @@ export interface CreationAction {
|
|
|
95
95
|
actionId: string;
|
|
96
96
|
requestHash: string;
|
|
97
97
|
roleId: string;
|
|
98
|
-
session: 'acp'
|
|
98
|
+
session: 'acp';
|
|
99
99
|
lifetime: 'permanent' | 'temporary';
|
|
100
100
|
state: CreationStage;
|
|
101
101
|
stages: Array<{
|
|
@@ -121,7 +121,7 @@ export interface RoleCreationServiceOptions {
|
|
|
121
121
|
tempLauncher?: SupervisorLauncher;
|
|
122
122
|
allowedCwdRoots?: string[];
|
|
123
123
|
journalDir?: string;
|
|
124
|
-
probeReady?: (name: string, session: 'acp'
|
|
124
|
+
probeReady?: (name: string, session: 'acp') => Promise<'ready' | 'attention' | 'unknown'>;
|
|
125
125
|
onProgress?: (action: CreationAction) => void;
|
|
126
126
|
modelCatalogs?: Partial<Record<'codex' | 'claude-code', () => HarnessModelCatalog>>;
|
|
127
127
|
/** Direct/managed callers must not create or restore the web action journal. */
|
|
@@ -10,6 +10,7 @@ import { FleetError, normalizeError } from './errors.js';
|
|
|
10
10
|
import { inheritCallerSpawnDefaults } from '../fleet-proxy.js';
|
|
11
11
|
import { effectivePermissionMode } from '../permissions.js';
|
|
12
12
|
import { effectiveRoleModel } from '../model-env.js';
|
|
13
|
+
import { getAdapter } from '../harness/registry.js';
|
|
13
14
|
import { claudeModelCatalog, codexModelCatalog, } from './model-catalog.js';
|
|
14
15
|
const canonical = (value) => {
|
|
15
16
|
if (Array.isArray(value))
|
|
@@ -109,13 +110,13 @@ export class RoleCreationService {
|
|
|
109
110
|
reasons,
|
|
110
111
|
harnesses: [
|
|
111
112
|
{
|
|
112
|
-
id: 'codex', available: true, sessions: ['acp'
|
|
113
|
+
id: 'codex', available: true, sessions: ['acp'],
|
|
113
114
|
defaultModel: resolveRoleModel(undefined, 'codex', defaults),
|
|
114
115
|
...codexModels, catalogSource: 'codex-runtime-catalog', customModelAllowed: true,
|
|
115
116
|
warnings: codexCatalog.warnings,
|
|
116
117
|
},
|
|
117
118
|
{
|
|
118
|
-
id: 'claude-code', available: true, sessions: ['acp'
|
|
119
|
+
id: 'claude-code', available: true, sessions: ['acp'],
|
|
119
120
|
defaultModel: resolveRoleModel(undefined, 'claude-code', defaults),
|
|
120
121
|
...claudeModels, catalogSource: 'claude-adapter-2.1', customModelAllowed: true,
|
|
121
122
|
warnings: claudeCatalog.warnings,
|
|
@@ -149,7 +150,7 @@ export class RoleCreationService {
|
|
|
149
150
|
const effective = {
|
|
150
151
|
name: request.name, identity: request.name,
|
|
151
152
|
harness: request.harness ?? defaults.harness ?? 'claude-code',
|
|
152
|
-
session: request.session ?? defaults.session ?? '
|
|
153
|
+
session: request.session ?? defaults.session ?? 'acp',
|
|
153
154
|
model: resolveRoleModel(request.model, request.harness, defaults),
|
|
154
155
|
reasoningEffort: request.reasoningEffort ?? undefined,
|
|
155
156
|
cwd, lifetime: request.lifetime,
|
|
@@ -264,9 +265,7 @@ export class RoleCreationService {
|
|
|
264
265
|
this.stage(action, 'waiting_for_session');
|
|
265
266
|
const ready = await this.waitForReady(action.roleId, action.session);
|
|
266
267
|
if (ready === 'ready') {
|
|
267
|
-
action.openPath = action.
|
|
268
|
-
? `/roles/${encodeURIComponent(action.roleId)}/activity`
|
|
269
|
-
: `/roles/${encodeURIComponent(action.roleId)}/terminal`;
|
|
268
|
+
action.openPath = `/roles/${encodeURIComponent(action.roleId)}/activity`;
|
|
270
269
|
this.stage(action, 'session_reachable', 'session is reachable; identity binding has no structured evidence in this version');
|
|
271
270
|
}
|
|
272
271
|
else if (ready === 'attention') {
|
|
@@ -311,14 +310,20 @@ export class RoleCreationService {
|
|
|
311
310
|
throw new FleetError('invalid_request', 'invalid role name');
|
|
312
311
|
if (!['codex', 'claude-code'].includes(input.harness))
|
|
313
312
|
throw new FleetError('invalid_request', 'unsupported harness');
|
|
314
|
-
if (
|
|
313
|
+
if (input.session !== 'acp')
|
|
315
314
|
throw new FleetError('invalid_request', 'unsupported session backend');
|
|
316
315
|
if (!['permanent', 'temporary'].includes(input.lifetime))
|
|
317
316
|
throw new FleetError('invalid_request', 'unsupported lifetime');
|
|
318
317
|
bounded(input.model ?? undefined, 'model', 128);
|
|
319
318
|
bounded(input.reasoningEffort ?? undefined, 'reasoning effort', 16);
|
|
320
|
-
if (input.reasoningEffort != null
|
|
321
|
-
|
|
319
|
+
if (input.reasoningEffort != null) {
|
|
320
|
+
try {
|
|
321
|
+
getAdapter(input.harness).agentSession.resolveBrain({ effort: input.reasoningEffort });
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
throw new FleetError('invalid_request', error.message);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
322
327
|
bounded(input.mission, 'mission', 4_096);
|
|
323
328
|
bounded(input.coordinator, 'coordinator', 128);
|
|
324
329
|
bounded(input.bio, 'bio', 8_192);
|