@ours.network/fleet 0.2.0 → 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
 
@@ -167,6 +176,8 @@ roles:
167
176
  harness_options: # adapter-owned, adapter-validated
168
177
  plugins: { "name@marketplace": false } # claude-code: plugin overrides
169
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
170
181
  ```
171
182
 
172
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.`);
@@ -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,9 @@ export function makeClaudeCodeAdapter(exec = realExec) {
78
89
  },
79
90
  buildLaunch(role, mode, s, prep) {
80
91
  const stateDir = roleStateDir(role);
92
+ const pm = permissionMode(role);
81
93
  const base = ['claude', ...(role.model ? ['--model', role.model] : []),
94
+ ...(pm ? ['--permission-mode', pm] : []),
82
95
  ...prep.argv, '--remote-control', role.name];
83
96
  const argv = mode === 'fresh'
84
97
  ? [...base, '--session-id', s.sessionId, `Read and follow ${join(stateDir, 'briefing.md')} now.`]
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/fleet",
3
- "version": "0.2.0",
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",