@ours.network/fleet 0.6.0 → 0.7.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 +22 -5
- package/dist/cli.js +3 -3
- package/dist/ops.d.ts +3 -2
- package/dist/ops.js +11 -4
- package/dist/runner.js +20 -2
- package/dist/spawn.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ The state dir contract:
|
|
|
62
62
|
| `briefing.md` | generated | rewritten on every `up`/`restart`; never hand-edit |
|
|
63
63
|
| `WORKLOG.md` | the agent | seeded empty, agent-appended; survives restarts |
|
|
64
64
|
| `ROUTINES.md` | operator / agent | **optional** recurring-work instructions; re-read at the start of every wake, hot-editable **without a restart**; absence means "no routines" |
|
|
65
|
-
| `.identity`, `.cwd`, `.session-id`, `.booted`, `.exit-status` | supervisor | dot-marker state — session resume and boot bookkeeping |
|
|
65
|
+
| `.identity`, `.cwd`, `.session-id`, `.booted`, `.exit-status`, `.config-path` | supervisor | dot-marker state — session resume and boot bookkeeping |
|
|
66
66
|
|
|
67
67
|
## Prerequisites
|
|
68
68
|
|
|
@@ -122,6 +122,17 @@ From inside Claude Code: install the `ours-fleet` plugin (ships in this repo und
|
|
|
122
122
|
temp-vs-permanent, co-drafts the bio and persona with you, spawns, and arms
|
|
123
123
|
oversight.
|
|
124
124
|
|
|
125
|
+
From inside Codex, install the native fleet plugin:
|
|
126
|
+
|
|
127
|
+
```sh
|
|
128
|
+
npm i -g @ours.network/fleet-codex
|
|
129
|
+
ours-fleet-codex-install
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Start a new Codex session and say **"spawn an ours agent …"**. The bundled skill
|
|
133
|
+
walks through lifetime, model, sandbox, approval policy, profile, launcher, and
|
|
134
|
+
mail-monitor consent, then verifies the real tmux session and offers oversight.
|
|
135
|
+
|
|
125
136
|
## Oversight ("keep an eye")
|
|
126
137
|
|
|
127
138
|
Spawned agents are subagents; their spawner (or any assigned overseer) checks on
|
|
@@ -158,6 +169,11 @@ ours-fleet doctor [--harness H]
|
|
|
158
169
|
ours-fleet init
|
|
159
170
|
```
|
|
160
171
|
|
|
172
|
+
A permanent role brought up via `-c custom.yaml` remembers that file (`.config-path`
|
|
173
|
+
in its state dir) across supervisor-triggered restarts — systemd/launchd re-invoke the
|
|
174
|
+
agent process with no arguments, so without this the role would silently fall back to
|
|
175
|
+
the default `~/fleet.yaml` on its very first crash-restart and fail to resolve.
|
|
176
|
+
|
|
161
177
|
## fleet.yaml reference
|
|
162
178
|
|
|
163
179
|
```yaml
|
|
@@ -213,11 +229,11 @@ and override individual keys per role.
|
|
|
213
229
|
|
|
214
230
|
## Codex roles
|
|
215
231
|
|
|
216
|
-
Install Codex
|
|
232
|
+
Install Codex, the native ours plugin, and the fleet skills once on the fleet host:
|
|
217
233
|
|
|
218
234
|
```sh
|
|
219
|
-
npm i -g @ours.network/codex
|
|
220
|
-
ours-codex-install
|
|
235
|
+
npm i -g @ours.network/fleet-codex
|
|
236
|
+
ours-fleet-codex-install
|
|
221
237
|
ours-fleet doctor --harness codex
|
|
222
238
|
```
|
|
223
239
|
|
|
@@ -319,7 +335,8 @@ cleanly as long as each role has its own `cwd` (the common case); two roles
|
|
|
319
335
|
sharing an identical `cwd` could have their resumes cross — give them distinct
|
|
320
336
|
working directories if that matters. MCP and monitor wiring is provided by
|
|
321
337
|
[`@ours.network/codex`](https://github.com/adapt-toolkit/ours-mcp/tree/main/packages/codex);
|
|
322
|
-
|
|
338
|
+
the native spawn/oversight skills are provided by `@ours.network/fleet-codex`.
|
|
339
|
+
`ours-fleet doctor --harness codex` verifies the CLI, ours plugin, and enhanced launcher/fallback.
|
|
323
340
|
|
|
324
341
|
## Learn more
|
|
325
342
|
|
package/dist/cli.js
CHANGED
|
@@ -101,7 +101,7 @@ cOpt(program.command('config').description('validate + print the merged plan (no
|
|
|
101
101
|
cOpt(program.command('up [names...]').description('create/start every role (or just the named ones)'))
|
|
102
102
|
.action(async (names, opts) => {
|
|
103
103
|
try {
|
|
104
|
-
await up(loadConfig(opts.configuration), names, deps());
|
|
104
|
+
await up(loadConfig(opts.configuration), names, deps(), opts.configuration);
|
|
105
105
|
}
|
|
106
106
|
catch (e) {
|
|
107
107
|
die(e);
|
|
@@ -119,7 +119,7 @@ cOpt(program.command('down [names...]').description('stop roles'))
|
|
|
119
119
|
cOpt(program.command('restart [names...]').description('re-sync config + bounce, RESUMING context'))
|
|
120
120
|
.action(async (names, opts) => {
|
|
121
121
|
try {
|
|
122
|
-
await restartRoles(loadConfig(opts.configuration), names, deps(), 'keep');
|
|
122
|
+
await restartRoles(loadConfig(opts.configuration), names, deps(), 'keep', opts.configuration);
|
|
123
123
|
}
|
|
124
124
|
catch (e) {
|
|
125
125
|
die(e);
|
|
@@ -128,7 +128,7 @@ cOpt(program.command('restart [names...]').description('re-sync config + bounce,
|
|
|
128
128
|
cOpt(program.command('force-restart [names...]').description('re-sync + bounce FRESH (context wiped)'))
|
|
129
129
|
.action(async (names, opts) => {
|
|
130
130
|
try {
|
|
131
|
-
await restartRoles(loadConfig(opts.configuration), names, deps(), 'fresh');
|
|
131
|
+
await restartRoles(loadConfig(opts.configuration), names, deps(), 'fresh', opts.configuration);
|
|
132
132
|
}
|
|
133
133
|
catch (e) {
|
|
134
134
|
die(e);
|
package/dist/ops.d.ts
CHANGED
|
@@ -10,11 +10,12 @@ export interface OpsDeps {
|
|
|
10
10
|
export declare function applyRole(role: ResolvedRole, opts?: {
|
|
11
11
|
fresh?: boolean;
|
|
12
12
|
temp?: boolean;
|
|
13
|
+
configPath?: string;
|
|
13
14
|
}): string;
|
|
14
15
|
/** Create/start roles declaratively. Idempotent; active roles keep their context. */
|
|
15
|
-
export declare function up(cfg: FleetConfig, names: string[], deps: OpsDeps): Promise<void>;
|
|
16
|
+
export declare function up(cfg: FleetConfig, names: string[], deps: OpsDeps, configPath?: string): Promise<void>;
|
|
16
17
|
export declare function down(cfg: FleetConfig, names: string[], deps: OpsDeps): Promise<void>;
|
|
17
18
|
/** Re-sync from config + bounce. mode 'keep' resumes context; 'fresh' wipes it. */
|
|
18
|
-
export declare function restartRoles(cfg: FleetConfig, names: string[], deps: OpsDeps, mode: 'keep' | 'fresh'): Promise<void>;
|
|
19
|
+
export declare function restartRoles(cfg: FleetConfig, names: string[], deps: OpsDeps, mode: 'keep' | 'fresh', configPath?: string): Promise<void>;
|
|
19
20
|
/** Stop + forget a role: unit, state dir, and its fleet.d file when spawned. */
|
|
20
21
|
export declare function rmRole(cfg: FleetConfig, name: string, deps: OpsDeps): Promise<void>;
|
package/dist/ops.js
CHANGED
|
@@ -14,6 +14,13 @@ export function applyRole(role, opts = {}) {
|
|
|
14
14
|
throw new Error(`role '${role.name}': ` + errs.map(e => `${e.path}: ${e.message}`).join('; '));
|
|
15
15
|
const dir = agentDir(role.name, opts.temp === true);
|
|
16
16
|
mkdirSync(dir, { recursive: true });
|
|
17
|
+
// systemd's shared unit template invokes `_run <name>` on every restart with no
|
|
18
|
+
// -c (see supervisor/systemd.ts) — record which config this permanent role was
|
|
19
|
+
// brought up from so the supervised process can reload the SAME file instead of
|
|
20
|
+
// silently falling back to the default ~/fleet.yaml. Temp roles snapshot their
|
|
21
|
+
// whole resolved role into role.yaml instead and don't need this.
|
|
22
|
+
if (!opts.temp)
|
|
23
|
+
writeFileSync(join(dir, '.config-path'), (opts.configPath ?? '') + '\n');
|
|
17
24
|
writeFileSync(join(dir, '.identity'), role.identity + '\n');
|
|
18
25
|
if (role.cwd)
|
|
19
26
|
writeFileSync(join(dir, '.cwd'), role.cwd + '\n');
|
|
@@ -35,13 +42,13 @@ function selectRoles(cfg, names) {
|
|
|
35
42
|
return names.length ? names.map(n => findRole(cfg, n)) : cfg.roles;
|
|
36
43
|
}
|
|
37
44
|
/** Create/start roles declaratively. Idempotent; active roles keep their context. */
|
|
38
|
-
export async function up(cfg, names, deps) {
|
|
45
|
+
export async function up(cfg, names, deps, configPath) {
|
|
39
46
|
let first = true;
|
|
40
47
|
for (const role of selectRoles(cfg, names)) {
|
|
41
48
|
if (!first)
|
|
42
49
|
await deps.sleep(STAGGER_MS());
|
|
43
50
|
first = false;
|
|
44
|
-
const dir = applyRole(role);
|
|
51
|
+
const dir = applyRole(role, { configPath });
|
|
45
52
|
// If the role isn't running, boot fresh so it reads the briefing we just wrote.
|
|
46
53
|
const status = await deps.backend.status(role.name).catch(() => '');
|
|
47
54
|
if (!/running|active \(/.test(status))
|
|
@@ -62,13 +69,13 @@ export async function down(cfg, names, deps) {
|
|
|
62
69
|
}
|
|
63
70
|
}
|
|
64
71
|
/** Re-sync from config + bounce. mode 'keep' resumes context; 'fresh' wipes it. */
|
|
65
|
-
export async function restartRoles(cfg, names, deps, mode) {
|
|
72
|
+
export async function restartRoles(cfg, names, deps, mode, configPath) {
|
|
66
73
|
let first = true;
|
|
67
74
|
for (const role of selectRoles(cfg, names)) {
|
|
68
75
|
if (!first)
|
|
69
76
|
await deps.sleep(STAGGER_MS());
|
|
70
77
|
first = false;
|
|
71
|
-
applyRole(role, { fresh: mode === 'fresh' });
|
|
78
|
+
applyRole(role, { fresh: mode === 'fresh', configPath });
|
|
72
79
|
await deps.backend.restart(role.name);
|
|
73
80
|
deps.log(mode === 'fresh'
|
|
74
81
|
? `↻ ${role.name} — force-restarted (FRESH — context cleared, briefing reloaded)`
|
package/dist/runner.js
CHANGED
|
@@ -47,13 +47,31 @@ export function loadTempRole(name) {
|
|
|
47
47
|
role.__temp = true;
|
|
48
48
|
return role;
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Fall back to the config path applyRole() recorded at the last up/restart/spawn
|
|
52
|
+
* for this role. systemd's shared unit template (`ours-fleet-agent@.service`)
|
|
53
|
+
* execs `_run <name>` with no -c on every restart, so a role brought up from a
|
|
54
|
+
* non-default config (`ours-fleet up -c custom.yaml`) would otherwise silently
|
|
55
|
+
* resolve against the default ~/fleet.yaml on its very first restart and fail
|
|
56
|
+
* with "no such role". An empty/missing marker means "use the default", same as
|
|
57
|
+
* no -c was ever given.
|
|
58
|
+
*/
|
|
59
|
+
function resolveConfigPath(dir, explicit) {
|
|
60
|
+
if (explicit)
|
|
61
|
+
return explicit;
|
|
62
|
+
const marker = join(dir, '.config-path');
|
|
63
|
+
if (!existsSync(marker))
|
|
64
|
+
return undefined;
|
|
65
|
+
return readFileSync(marker, 'utf8').trim() || undefined;
|
|
66
|
+
}
|
|
50
67
|
/** One supervised session lifecycle. The supervisor re-invokes us after we return. */
|
|
51
68
|
export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
52
69
|
const deps = { ...defaultDeps(), ...partialDeps };
|
|
53
70
|
const temp = opts.temp === true;
|
|
54
|
-
const role = temp ? loadTempRole(name) : findRole(loadConfig(opts.configPath), name);
|
|
55
|
-
const adapter = getAdapter(role.harness);
|
|
56
71
|
const dir = agentDir(name, temp);
|
|
72
|
+
const configPath = temp ? opts.configPath : resolveConfigPath(dir, opts.configPath);
|
|
73
|
+
const role = temp ? loadTempRole(name) : findRole(loadConfig(configPath), name);
|
|
74
|
+
const adapter = getAdapter(role.harness);
|
|
57
75
|
mkdirSync(dir, { recursive: true });
|
|
58
76
|
const sidFile = join(dir, '.session-id');
|
|
59
77
|
if (!existsSync(sidFile))
|
package/dist/spawn.js
CHANGED
|
@@ -61,7 +61,7 @@ export async function spawnPermanent(o, deps) {
|
|
|
61
61
|
writeFileSync(file, stringify({
|
|
62
62
|
roles: { [o.name]: roleFromOpts(o, cfg.defaults.harness) },
|
|
63
63
|
}));
|
|
64
|
-
await up(loadConfig(o.configPath), [o.name], deps);
|
|
64
|
+
await up(loadConfig(o.configPath), [o.name], deps, o.configPath);
|
|
65
65
|
return file;
|
|
66
66
|
}
|
|
67
67
|
const detachedSupervisor = (binPath, args, dir) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ours.network/fleet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Harness-agnostic fleet of persistent, identity-bound AI agents. Declarative fleet.yaml, tmux consoles, systemd/launchd supervision, ours.network messaging.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "FSL-1.1-Apache-2.0",
|