@ours.network/fleet 0.1.2 → 0.4.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/README.md CHANGED
@@ -49,10 +49,19 @@ roles:
49
49
  ```
50
50
 
51
51
  Each role gets a state dir (`~/.ours-fleet/agents/<Name>/`) holding its briefing,
52
- WORKLOG, and session markers. On boot the agent reads its briefing: bind identity,
53
- publish bio/persona, arm a mail monitor (`ours-mcp watch`), announce to its
54
- coordinator, work. On crash the supervisor relaunches it and the harness resumes
55
- the same session.
52
+ logs, routines, and session markers. On boot the agent reads its briefing: bind
53
+ identity, publish bio/persona, arm a mail monitor (`ours-mcp watch`), announce to
54
+ its coordinator, work. On crash the supervisor relaunches it and the harness
55
+ resumes the same session.
56
+
57
+ The state dir contract:
58
+
59
+ | File | Owner | Lifecycle |
60
+ |---|---|---|
61
+ | `briefing.md` | generated | rewritten on every `up`/`restart`; never hand-edit |
62
+ | `WORKLOG.md` | the agent | seeded empty, agent-appended; survives restarts |
63
+ | `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" |
64
+ | `.identity`, `.cwd`, `.session-id`, `.booted`, `.exit-status` | supervisor | dot-marker state — session resume and boot bookkeeping |
56
65
 
57
66
  ## Prerequisites
58
67
 
@@ -137,7 +146,7 @@ ours-fleet up|down|restart|force-restart [-c FILE] [Name...]
137
146
  ours-fleet config [-c FILE] validate + print merged plan
138
147
  ours-fleet ls | attach | peek | logs [-f] | status <Name>
139
148
  ours-fleet send <Name> "text" | --key <K>
140
- ours-fleet spawn [--temp] <Name> [--mission --bio-file --persona-file ...]
149
+ ours-fleet spawn [--temp] <Name> [--mission --model --bio-file --persona-file ...]
141
150
  ours-fleet rm <Name>
142
151
  ours-fleet doctor [--harness H]
143
152
  ours-fleet init
@@ -149,6 +158,7 @@ ours-fleet init
149
158
  vars: { work_root: /home/me/work } # ${var} substitution anywhere below
150
159
  defaults:
151
160
  harness: claude-code # for roles that don't set one
161
+ model: claude-fable-5 # default model for roles that don't set one (per-role model / --model wins)
152
162
  max_tokens: 500000 # session cap (harness-interpreted)
153
163
  roles:
154
164
  Name: # [A-Za-z0-9_-]+
@@ -156,6 +166,7 @@ roles:
156
166
  identity: "Display Name" # ours identity to bind (default: Name)
157
167
  cwd: ${work_root}/repo # where the harness process runs
158
168
  coordinator: FleetCoordinator # announce target on boot
169
+ model: claude-fable-5 # launch on a specific model (pass-through id; default: launcher default)
159
170
  mission: one line
160
171
  persona: | # operating contract (published as persona)
161
172
  bio: | # public card (published as bio)
@@ -165,6 +176,8 @@ roles:
165
176
  harness_options: # adapter-owned, adapter-validated
166
177
  plugins: { "name@marketplace": false } # claude-code: plugin overrides
167
178
  # mem_palace: false # claude-code: disable memory plugin
179
+ # permission_mode: dontAsk # claude-code: launch permission mode —
180
+ # one of default | acceptEdits | plan | dontAsk | bypassPermissions
168
181
  ```
169
182
 
170
183
  Merge order: `fleet.yaml` ← `fleet.d/*.yaml`; a duplicate role name is a hard
@@ -3,6 +3,7 @@ import type { BriefingVocab } from './harness/types.js';
3
3
  export interface BriefingOpts {
4
4
  stateDir: string;
5
5
  worklogPath: string;
6
+ routinesPath: string;
6
7
  /** Curated body (from briefing_file) replacing the narrative sections. */
7
8
  briefingBody?: string;
8
9
  }
package/dist/briefing.js CHANGED
@@ -62,6 +62,10 @@ export function generateBriefing(role, v, opts) {
62
62
  L.push('', '## Durable log');
63
63
  L.push(`Append important commands / decisions / results to \`${opts.worklogPath}\` as you go —`);
64
64
  L.push('it survives restarts.');
65
+ L.push('', '## Routines');
66
+ L.push(`If \`${opts.routinesPath}\` exists, re-read it at the START of every wake — before acting`);
67
+ L.push('on messages, timers, or prompts — and follow it for recurring or scheduled work. It may');
68
+ L.push('change between wakes without a restart; treat the file, not your memory of it, as current.');
65
69
  L.push('', '## On restart (you run under a supervised launcher)');
66
70
  L.push(`On restart, WITHOUT asking: re-bind (**${v.bindTool}** name "${id}" force=true),`);
67
71
  L.push(`re-arm the \`${v.watchCommand(id)}\` monitor, then continue from your WORKLOG.`);
package/dist/cli.js CHANGED
@@ -48,6 +48,8 @@ cOpt(program.command('config').description('validate + print the merged plan (no
48
48
  console.log(` source: ${r.sourceFile}`);
49
49
  if (r.cwd)
50
50
  console.log(` cwd: ${r.cwd}`);
51
+ if (r.model)
52
+ console.log(` model: ${r.model}`);
51
53
  if (r.coordinator)
52
54
  console.log(` coordinator: ${r.coordinator}`);
53
55
  if (r.mission)
@@ -148,12 +150,14 @@ cOpt(program.command('spawn <name>').description('spawn a new agent (permanent b
148
150
  .option('--identity <name>', 'ours identity to bind (default: role name)')
149
151
  .option('--cwd <dir>', 'working directory')
150
152
  .option('--coordinator <name>', 'announce target')
153
+ .option('--model <id>', 'model id to launch on (e.g. claude-fable-5); default: launcher default')
151
154
  .option('--bio-file <file>', 'public bio (file)')
152
155
  .option('--persona-file <file>', 'persona / operating contract (file)')
153
156
  .action(async (name, opts) => {
154
157
  const o = {
155
158
  name, temp: opts.temp, harness: opts.harness, mission: opts.mission,
156
159
  identity: opts.identity, cwd: opts.cwd, coordinator: opts.coordinator,
160
+ model: opts.model,
157
161
  bioFile: opts.bioFile, personaFile: opts.personaFile, configPath: opts.configuration,
158
162
  };
159
163
  try {
package/dist/config.d.ts CHANGED
@@ -11,6 +11,7 @@ export interface RoleConfig {
11
11
  persona?: string;
12
12
  bio?: string;
13
13
  briefing_file?: string;
14
+ model?: string;
14
15
  max_tokens?: number;
15
16
  autocompact_pct?: number;
16
17
  env?: Record<string, string>;
package/dist/config.js CHANGED
@@ -7,7 +7,7 @@ export class ConfigError extends Error {
7
7
  const NAME_RE = /^[A-Za-z0-9_-]+$/;
8
8
  const ROLE_KEYS = [
9
9
  'harness', 'identity', 'cwd', 'coordinator', 'mission', 'persona', 'bio',
10
- 'briefing_file', 'max_tokens', 'autocompact_pct', 'env', 'oversee', 'harness_options',
10
+ 'briefing_file', 'model', 'max_tokens', 'autocompact_pct', 'env', 'oversee', 'harness_options',
11
11
  ];
12
12
  function deepSub(v, vars) {
13
13
  if (typeof v === 'string')
@@ -65,6 +65,7 @@ export function loadConfig(configPath) {
65
65
  sourceFile: file,
66
66
  harness: r.harness ?? defaults.harness ?? 'claude-code',
67
67
  identity: r.identity ?? name,
68
+ model: r.model ?? defaults.model,
68
69
  max_tokens: r.max_tokens ?? defaults.max_tokens,
69
70
  });
70
71
  }
@@ -3,7 +3,18 @@ import { join } from 'node:path';
3
3
  import { home } from '../paths.js';
4
4
  import { realExec } from '../exec.js';
5
5
  import { registerAdapter } from './registry.js';
6
- const OPTION_KEYS = ['plugins', 'mem_palace', 'mem_palace_midsession_autosave'];
6
+ const OPTION_KEYS = ['plugins', 'mem_palace', 'mem_palace_midsession_autosave', 'permission_mode'];
7
+ /** Claude Code's accepted --permission-mode values. */
8
+ const PERMISSION_MODES = ['default', 'acceptEdits', 'plan', 'dontAsk', 'bypassPermissions'];
9
+ /** Resolve & validate the per-role permission mode, throwing on an unknown value. */
10
+ function permissionMode(role) {
11
+ const pm = role.harness_options?.permission_mode;
12
+ if (pm == null)
13
+ return undefined;
14
+ if (!PERMISSION_MODES.includes(pm))
15
+ throw new Error(`invalid harness_options.permission_mode "${pm}"; allowed: ${PERMISSION_MODES.join(', ')}`);
16
+ return pm;
17
+ }
7
18
  /** Context window of the fleet model (1M); max_tokens → % of this. */
8
19
  const WINDOW = 1_000_000;
9
20
  export function autocompactPct(role) {
@@ -78,7 +89,10 @@ export function makeClaudeCodeAdapter(exec = realExec) {
78
89
  },
79
90
  buildLaunch(role, mode, s, prep) {
80
91
  const stateDir = roleStateDir(role);
81
- const base = ['claude', ...prep.argv, '--remote-control', role.name];
92
+ const pm = permissionMode(role);
93
+ const base = ['claude', ...(role.model ? ['--model', role.model] : []),
94
+ ...(pm ? ['--permission-mode', pm] : []),
95
+ ...prep.argv, '--remote-control', role.name];
82
96
  const argv = mode === 'fresh'
83
97
  ? [...base, '--session-id', s.sessionId, `Read and follow ${join(stateDir, 'briefing.md')} now.`]
84
98
  : [...base, '--resume', s.sessionId,
package/dist/ops.js CHANGED
@@ -23,7 +23,8 @@ export function applyRole(role, opts = {}) {
23
23
  writeFileSync(join(dir, 'WORKLOG.md'), '');
24
24
  const briefingBody = role.briefing_file ? readFileSync(role.briefing_file, 'utf8') : undefined;
25
25
  writeFileSync(join(dir, 'briefing.md'), generateBriefing(role, adapter.vocabulary, {
26
- stateDir: dir, worklogPath: join(dir, 'WORKLOG.md'), briefingBody,
26
+ stateDir: dir, worklogPath: join(dir, 'WORKLOG.md'),
27
+ routinesPath: join(dir, 'ROUTINES.md'), briefingBody,
27
28
  }));
28
29
  if (opts.fresh)
29
30
  for (const f of ['.booted', '.session-id', '.exit-status'])
package/dist/spawn.d.ts CHANGED
@@ -7,6 +7,7 @@ export interface SpawnOpts {
7
7
  identity?: string;
8
8
  cwd?: string;
9
9
  coordinator?: string;
10
+ model?: string;
10
11
  bioFile?: string;
11
12
  personaFile?: string;
12
13
  overseeInterval?: string;
package/dist/spawn.js CHANGED
@@ -17,6 +17,8 @@ function roleFromOpts(o) {
17
17
  r.coordinator = o.coordinator;
18
18
  if (o.mission)
19
19
  r.mission = o.mission;
20
+ if (o.model?.trim())
21
+ r.model = o.model.trim();
20
22
  if (o.bioFile)
21
23
  r.bio = readFileSync(o.bioFile, 'utf8').trim();
22
24
  if (o.personaFile)
@@ -57,6 +59,7 @@ export async function spawnTemp(o, binPath, launch = detachedSupervisor) {
57
59
  name: o.name,
58
60
  harness: o.harness ?? cfg.defaults.harness ?? 'claude-code',
59
61
  identity: o.identity ?? o.name,
62
+ model: o.model?.trim() || cfg.defaults.model,
60
63
  sourceFile: '(temp)',
61
64
  };
62
65
  const dir = applyRole(role, { temp: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/fleet",
3
- "version": "0.1.2",
3
+ "version": "0.4.0",
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",