@ours.network/fleet 1.1.0-nightly.20 → 1.1.0-nightly.22
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 +48 -4
- package/dist/application/task-room-service.js +16 -8
- package/dist/build-info.json +4 -4
- package/dist/cli.js +15 -4
- package/dist/config.d.ts +8 -1
- package/dist/config.js +17 -7
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +3 -2
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +21 -0
- package/dist/fleet-command-audit.js +14 -2
- package/dist/lifecycle-summary.d.ts +16 -0
- package/dist/lifecycle-summary.js +18 -0
- package/dist/loops/config.d.ts +10 -1
- package/dist/loops/config.js +35 -3
- package/dist/preset-migration.d.ts +1 -1
- package/dist/preset-migration.js +23 -6
- package/dist/resolved-plan.js +9 -1
- package/dist/rooms-tasks/cli.js +14 -3
- package/dist/rooms-tasks/launch-snapshot.js +5 -0
- package/dist/rooms-tasks/member-overrides.d.ts +3 -0
- package/dist/rooms-tasks/member-overrides.js +39 -5
- package/dist/rooms-tasks/provision.js +5 -0
- package/dist/rooms-tasks/types.d.ts +2 -0
- package/dist/runner.d.ts +3 -0
- package/dist/runner.js +17 -3
- package/dist/spawn.d.ts +9 -0
- package/dist/spawn.js +65 -8
- package/package.json +1 -1
- package/presets/fleet/agent_templates/LocalCoordinator.yaml +45 -0
- package/presets/fleet/room_templates/team.yaml +1 -0
- package/presets/manifest.json +1 -1
package/README.md
CHANGED
|
@@ -365,8 +365,41 @@ Agent Template document (\`~/fleet/agent_templates/Worker.yaml\`) is inert and r
|
|
|
365
365
|
role: { ref: developer }
|
|
366
366
|
brain: { ref: claude-default }
|
|
367
367
|
permissions: { approval: ask, filesystem: workspace, unattended: deny }
|
|
368
|
+
loops: # optional; temporary launches only
|
|
369
|
+
progress:
|
|
370
|
+
interval: 10m # required; 1m..30d
|
|
371
|
+
initial_delay: 10m # default: interval; 0s..30d
|
|
372
|
+
jitter: 30s # default: 0s; < interval and <=1h
|
|
373
|
+
enabled: true # default true
|
|
374
|
+
prompt: Continue the assigned work and report only material progress.
|
|
368
375
|
```
|
|
369
376
|
|
|
377
|
+
Agent Template-local `loops` are limited to 64 entries, implicitly scoped to the temporary agent, and
|
|
378
|
+
reuse Fleet's scheduled-turn semantics: a busy occurrence is skipped, ordinary
|
|
379
|
+
missed occurrences are not backlogged or replayed, and restart recovery may
|
|
380
|
+
preserve at most one recent late occurrence. The trusted authoring file and
|
|
381
|
+
private sealed role snapshot retain exact prompt text; resolved launch, task,
|
|
382
|
+
room, provenance, and audit presentation surfaces show only bytes and SHA-256.
|
|
383
|
+
|
|
384
|
+
Direct temporary spawn can set the same whole block from an owner-only regular
|
|
385
|
+
file containing exactly a top-level `loops:` mapping, or explicitly disable it:
|
|
386
|
+
|
|
387
|
+
```sh
|
|
388
|
+
ours-fleet spawn --temp Scout --role 'inline:{mission: inspect}' --brain 'inline:{harness: codex}' --loops-file ./scout-loops.yaml
|
|
389
|
+
ours-fleet spawn --temp Scout --role 'inline:{mission: inspect}' --brain 'inline:{harness: codex}' --no-loops
|
|
390
|
+
ours-fleet task start TASK --member developer --loops-file ./developer-loops.yaml
|
|
391
|
+
ours-fleet task start TASK --member critic --no-loops
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
`--loops-file` and `--no-loops` are mutually exclusive and temporary-only.
|
|
395
|
+
For room members they override the selected Agent Template as a whole; the
|
|
396
|
+
Agent Template overrides legacy omission. Omission preserves the historical
|
|
397
|
+
no-loop temporary behavior and never inherits manifest `roles: ["*"]` loops.
|
|
398
|
+
The file must be owned by the current user, mode 0600, non-symlink, no larger
|
|
399
|
+
than 1 MB, and contain a non-empty map. Validation completes before identity,
|
|
400
|
+
Cowork, snapshot, or supervisor side effects. Recovery and replacement reuse
|
|
401
|
+
the private sealed normalized definition even if mutable templates later change.
|
|
402
|
+
|
|
370
403
|
Persistent Agent instance (\`~/fleet/agents/Name.yaml\`) either remains a canonical
|
|
371
404
|
Role/Brain composition or explicitly reuses a template:
|
|
372
405
|
|
|
@@ -484,6 +517,15 @@ ours-fleet task create --title "Reviewed change" --template pair
|
|
|
484
517
|
ours-fleet task create --title "Phased delivery" --template team
|
|
485
518
|
```
|
|
486
519
|
|
|
520
|
+
The default `team` gives only its LocalCoordinator Agent Template a 15-minute
|
|
521
|
+
continuity loop. Each idle-only pass uses authenticated assigned-room evidence,
|
|
522
|
+
posts at most one missing interval update or status request, and reports `STALLED`
|
|
523
|
+
only after concrete stopped/blocked evidence or the role's documented request/ETA
|
|
524
|
+
window. Stable history/checkpoint fingerprints suppress duplicates; `single` and
|
|
525
|
+
`pair` remain loop-free. This preset does not widen permissions: task planning and
|
|
526
|
+
launch still fail closed when the selected harness cannot supply the configured
|
|
527
|
+
unattended messaging and checkpoint capabilities.
|
|
528
|
+
|
|
487
529
|
`ours-fleet init -c /path/custom.yaml` reviews the literal resolved manifest and
|
|
488
530
|
`/path/custom/` split directory before doing anything. It requires a TTY and two
|
|
489
531
|
default-No confirmations. You select Codex, Claude, or both; then either one explicit
|
|
@@ -514,11 +556,12 @@ task-local `LocalCoordinator`, `Developer`, and `Critic`. The persistent
|
|
|
514
556
|
`FleetCoordinator` uses the separate packaged `Coordinator` contract and coordination
|
|
515
557
|
model.
|
|
516
558
|
|
|
517
|
-
Revision-3 role defaults
|
|
559
|
+
Revision-3 role defaults and exact revision-4 LocalCoordinator/team defaults have an
|
|
560
|
+
explicit fail-closed adoption command: first run
|
|
518
561
|
`ours-fleet migrate-role-defaults -c FILE` for a zero-write plan, review every removal,
|
|
519
562
|
replacement, addition, preserved custom file, staging path, and recovery path, then rerun
|
|
520
563
|
with `--write`. Only exact semantic matches for packaged-bootstrap and generated
|
|
521
|
-
revision-3 forms are changed; same-named customized files remain byte-identical, and a
|
|
564
|
+
revision-3/4 forms are changed; same-named customized files remain byte-identical, and a
|
|
522
565
|
dangling custom reference refuses publication. Rerunning the migration is a no-op.
|
|
523
566
|
|
|
524
567
|
For manual adoption of a single newer packaged file, copy the reported source beside the
|
|
@@ -582,8 +625,9 @@ optional `rooms.owner.provider` setting is separate and defaults to
|
|
|
582
625
|
### Scheduled agent loops
|
|
583
626
|
|
|
584
627
|
Top-level `loops` schedule literal prompts from trusted local YAML. Enabled targets
|
|
585
|
-
must use a managed session (`acp` or `codex-app-server`); temporary roles never
|
|
586
|
-
["*"]`.
|
|
628
|
+
must use a managed session (`acp` or `codex-app-server`); temporary roles never
|
|
629
|
+
inherit these manifest loops, including `roles: ["*"]`. Only their sealed Agent
|
|
630
|
+
Template/CLI-local `loops` apply. Fleet rejects an enabled loop when its explicitly selected base config is
|
|
587
631
|
a symlink, is owned by another user, or is group/world writable. Prompts are
|
|
588
632
|
bounded and normalized at validation time, but only their size and SHA-256 appear
|
|
589
633
|
in `config`, `list`, logs, or durable state.
|
|
@@ -625,15 +625,24 @@ export class TaskRoomApplicationService {
|
|
|
625
625
|
throw new TaskRoomApplicationError('template_mismatch', 'anonymous override does not match the existing Room launch policy', { room: task.room_id });
|
|
626
626
|
if (TASK_TERMINAL_STATES.includes(task.state))
|
|
627
627
|
throw new TaskRoomApplicationError('task_terminal', 'task terminal', { task: input.taskId, state: task.state });
|
|
628
|
+
const durableTemplate = task.room_id
|
|
629
|
+
? getRoomRecord(task.room_id)?.template_snapshot ?? task.execution_plan?.snapshot
|
|
630
|
+
: task.execution_plan?.snapshot;
|
|
631
|
+
const templateRequestMatches = !input.template || (durableTemplate !== undefined
|
|
632
|
+
&& (input.template === durableTemplate.name
|
|
633
|
+
|| input.template === `${durableTemplate.name}@${durableTemplate.version}`));
|
|
634
|
+
const memberRequestMatches = input.members === undefined || (task.execution_plan !== undefined
|
|
635
|
+
&& task.execution_plan.overrides_hash === hashMemberOverrides(input.members));
|
|
636
|
+
const durableRequestMatches = durableTemplate !== undefined
|
|
637
|
+
&& templateRequestMatches && memberRequestMatches;
|
|
628
638
|
if (task.state === 'active' && task.room_id) {
|
|
629
639
|
if (input.template || input.members) {
|
|
630
|
-
const pinned =
|
|
640
|
+
const pinned = durableTemplate;
|
|
631
641
|
if (!pinned)
|
|
632
642
|
throw new TaskRoomApplicationError('template_mismatch', 'active task has no durable execution plan', { room: task.room_id });
|
|
633
|
-
|
|
634
|
-
if ((!input.template || input.template === pinned.name) && input.members
|
|
635
|
-
&& task.execution_plan?.overrides_hash === hashMemberOverrides(input.members))
|
|
643
|
+
if (durableRequestMatches)
|
|
636
644
|
return { task, status: 'already_active' };
|
|
645
|
+
const templateName = input.template ?? pinned.name;
|
|
637
646
|
const definition = resolveTemplate(templateName, cfg.roomTemplates ?? {});
|
|
638
647
|
if (!definition)
|
|
639
648
|
throw new TaskRoomApplicationError('template_not_found', 'template not found', { template: templateName });
|
|
@@ -654,11 +663,10 @@ export class TaskRoomApplicationService {
|
|
|
654
663
|
throw new Error(`task ${task.task_id} template reference does not match its durable execution snapshot`);
|
|
655
664
|
const templateName = input.template ?? durable?.name ?? task.template?.name
|
|
656
665
|
?? cfg.tasks?.default_room_template ?? cfg.rooms?.defaults?.template ?? 'single';
|
|
657
|
-
const storedOverridesMatch = !!(durable &&
|
|
658
|
-
|
|
659
|
-
const definition = durable && !input.template && (!input.members || storedOverridesMatch) ? undefined
|
|
666
|
+
const storedOverridesMatch = !!(durable && memberRequestMatches);
|
|
667
|
+
const definition = durable && durableRequestMatches ? undefined
|
|
660
668
|
: resolveTemplate(templateName, cfg.roomTemplates ?? {});
|
|
661
|
-
if (input.template && !definition)
|
|
669
|
+
if (input.template && !definition && !durableRequestMatches)
|
|
662
670
|
throw new TaskRoomApplicationError('template_not_found', 'template not found', { template: templateName });
|
|
663
671
|
if (!durable && !definition)
|
|
664
672
|
throw new TaskRoomApplicationError('template_not_found', 'template not found', { template: templateName });
|
package/dist/build-info.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.1.0-nightly.
|
|
3
|
-
"buildId": "
|
|
4
|
-
"commit": "
|
|
2
|
+
"version": "1.1.0-nightly.22",
|
|
3
|
+
"buildId": "bfb51e6ced00",
|
|
4
|
+
"commit": "5447c29d81481c7880fa9cf5bfbbcc2ff294950f",
|
|
5
5
|
"dirty": true,
|
|
6
|
-
"builtAt": "2026-09-
|
|
6
|
+
"builtAt": "2026-09-01T19:41:56.761Z",
|
|
7
7
|
"capabilities": [
|
|
8
8
|
"monitor.interrupt.after_tool"
|
|
9
9
|
]
|
package/dist/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ import { analyzeInstalls, buildInfo, buildLabel, discoverInstalls, runningLabel,
|
|
|
14
14
|
import { agentDir, agentsRoot, tmpRoot, logsRoot, deriveXdgRuntimeDir, defaultConfigPath } from './paths.js';
|
|
15
15
|
import { findRole, loadConfig, ROLE_NAME_RE } from './config.js';
|
|
16
16
|
import { formatDuration } from './duration.js';
|
|
17
|
-
import { redactSensitive, resolvedPlan } from './resolved-plan.js';
|
|
17
|
+
import { redactSensitive, resolvedPlan, resolvedRolePlan } from './resolved-plan.js';
|
|
18
18
|
import { pickBackend } from './supervisor/index.js';
|
|
19
19
|
import { up, down } from './ops.js';
|
|
20
20
|
import { readRestartLedger, runSupervised, runTemp } from './runner.js';
|
|
@@ -26,11 +26,11 @@ import { acquireRunLock, latestReport, listRuns, readReport, releaseRunLock, rep
|
|
|
26
26
|
import { watchdogAddressable } from './watchdog/query.js';
|
|
27
27
|
import { lastProvenance } from './spawn.js';
|
|
28
28
|
import { stringify } from 'yaml';
|
|
29
|
-
import { resolvedRolePlan } from './resolved-plan.js';
|
|
30
29
|
import { creationBuildNote, formatProvenance, readProvenance } from './creation.js';
|
|
31
30
|
import { doctor } from './doctor.js';
|
|
32
31
|
import { allWarnings, analyzeFleetPermissions, effectivePermissionMode, formatNative, } from './permissions.js';
|
|
33
32
|
import { AI_DOCS } from './docs.js';
|
|
33
|
+
import { renderAgentConfiguration, selectionOrigin, summarizeResolvedLaunch } from './lifecycle-summary.js';
|
|
34
34
|
import { classifyActivity, describeSessionState } from './session/activity.js';
|
|
35
35
|
import { controlRequest, controlSocketPath, followControl, livenessNote, } from './session/control.js';
|
|
36
36
|
import { SessionControlError } from './session/types.js';
|
|
@@ -1147,6 +1147,8 @@ cOpt(program.command('spawn [name]').description('spawn a new agent (permanent b
|
|
|
1147
1147
|
.option('--filesystem <mode>', 'common filesystem intent: read-only|workspace|unrestricted')
|
|
1148
1148
|
.option('--unattended <mode>', 'permission behavior without a console: deny|wait')
|
|
1149
1149
|
.option('--isolation-file <path>', 'file holding an isolation: mapping (same schema as fleet.yaml)')
|
|
1150
|
+
.option('--loops-file <path>', 'owner-only YAML containing exactly loops: for this temporary agent')
|
|
1151
|
+
.option('--no-loops', 'explicitly disable temporary-agent loops')
|
|
1150
1152
|
.option('--dry-run', 'validate and print without reserving or creating anything')
|
|
1151
1153
|
.option('--json', 'with --dry-run, emit a stable secret-safe JSON result')
|
|
1152
1154
|
.action(async (name, opts) => {
|
|
@@ -1163,6 +1165,8 @@ cOpt(program.command('spawn [name]').description('spawn a new agent (permanent b
|
|
|
1163
1165
|
approval: opts.approval,
|
|
1164
1166
|
filesystem: opts.filesystem, unattended: opts.unattended,
|
|
1165
1167
|
isolationFile: opts.isolationFile, configPath: opts.configuration,
|
|
1168
|
+
loopsFile: opts.loopsFile, noLoops: opts.loops === false,
|
|
1169
|
+
loopSource: opts.loopsFile || opts.loops === false ? 'cli' : 'omitted',
|
|
1166
1170
|
dryRun: opts.dryRun, json: opts.json,
|
|
1167
1171
|
};
|
|
1168
1172
|
if (o.json && !o.dryRun)
|
|
@@ -1176,7 +1180,7 @@ cOpt(program.command('spawn [name]').description('spawn a new agent (permanent b
|
|
|
1176
1180
|
process.stdout.write(`${JSON.stringify({
|
|
1177
1181
|
schemaVersion: result.schemaVersion,
|
|
1178
1182
|
warning: result.warning,
|
|
1179
|
-
roleDocument: result.roleDocument,
|
|
1183
|
+
roleDocument: redactSensitive(result.roleDocument),
|
|
1180
1184
|
resolvedRole: resolvedRolePlan(result.resolvedRole),
|
|
1181
1185
|
}, null, 2)}\n`);
|
|
1182
1186
|
}
|
|
@@ -1190,7 +1194,7 @@ cOpt(program.command('spawn [name]').description('spawn a new agent (permanent b
|
|
|
1190
1194
|
if (proxyStateDir) {
|
|
1191
1195
|
// Paths entered in the agent shell belong to that shell's cwd, not the
|
|
1192
1196
|
// supervisor process. Normalize before crossing the control boundary.
|
|
1193
|
-
for (const key of ['isolationFile']) {
|
|
1197
|
+
for (const key of ['isolationFile', 'loopsFile']) {
|
|
1194
1198
|
if (o[key])
|
|
1195
1199
|
o[key] = resolvePath(o[key]);
|
|
1196
1200
|
}
|
|
@@ -1211,6 +1215,8 @@ cOpt(program.command('spawn [name]').description('spawn a new agent (permanent b
|
|
|
1211
1215
|
+ `monitor=${result.monitor.mode} interrupt=${result.monitor.interrupt}`);
|
|
1212
1216
|
if (result.inherited.length)
|
|
1213
1217
|
console.log(` inherited omitted defaults from ${result.caller}: ${result.inherited.join(', ')}`);
|
|
1218
|
+
if (result.configuration)
|
|
1219
|
+
console.log(` ${renderAgentConfiguration(result.configuration)}`);
|
|
1214
1220
|
console.log(`→ watch it: ours-fleet peek ${result.role} | attach: ours-fleet attach ${result.role}`);
|
|
1215
1221
|
return;
|
|
1216
1222
|
}
|
|
@@ -1235,6 +1241,11 @@ cOpt(program.command('spawn [name]').description('spawn a new agent (permanent b
|
|
|
1235
1241
|
}
|
|
1236
1242
|
console.log(` permission=${plannedPermissionMode.fleetMode} `
|
|
1237
1243
|
+ `native=${plannedPermissionMode.nativeMode}`);
|
|
1244
|
+
console.log(` ${renderAgentConfiguration(summarizeResolvedLaunch(created.plan.preview.resolvedRole, {
|
|
1245
|
+
role: selectionOrigin(o.role ?? o.agentDefinition?.role),
|
|
1246
|
+
brain: selectionOrigin(o.brain ?? o.agentDefinition?.brain),
|
|
1247
|
+
permissionMode: plannedPermissionMode,
|
|
1248
|
+
}))}`);
|
|
1238
1249
|
console.log(`→ watch it: ours-fleet peek ${roleName} | attach: ours-fleet attach ${roleName}`);
|
|
1239
1250
|
}
|
|
1240
1251
|
catch (e) {
|
package/dist/config.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ConfigDiagnostic, type YamlMode } from './config-yaml.js';
|
|
2
2
|
import type { IsolationConfig, WrapContext } from './isolation/types.js';
|
|
3
3
|
import type { ResolvedWatchdog } from './watchdog/config.js';
|
|
4
|
-
import type { ResolvedLoop, ResolvedRoleLoop } from './loops/config.js';
|
|
4
|
+
import type { AgentLoopsConfig, ResolvedLoop, ResolvedRoleLoop } from './loops/config.js';
|
|
5
5
|
export interface OverseeEntry {
|
|
6
6
|
agent: string;
|
|
7
7
|
interval: string;
|
|
@@ -162,6 +162,8 @@ export interface AgentDefinition {
|
|
|
162
162
|
owner_channel?: OwnerChannelConfigInput;
|
|
163
163
|
worklog?: WorklogPolicyInput;
|
|
164
164
|
auth_proxy?: Partial<AuthProxyConfig>;
|
|
165
|
+
/** Scheduled turns scoped only to temporary launches; persistent Agents reject this field. */
|
|
166
|
+
loops?: AgentLoopsConfig;
|
|
165
167
|
}
|
|
166
168
|
/** Inert, reusable launch definition. It never owns an identity or runtime state. */
|
|
167
169
|
export type AgentTemplateDefinition = Omit<AgentDefinition, 'identity'>;
|
|
@@ -202,6 +204,10 @@ export interface ResolvedRole extends Omit<RoleConfig, 'model' | 'owner_channel'
|
|
|
202
204
|
worklog?: WorklogPolicy;
|
|
203
205
|
auth_proxy?: AuthProxyConfig;
|
|
204
206
|
loops?: ResolvedRoleLoop[];
|
|
207
|
+
/** Agent Template-local loops, kept separate from manifest loops until a temp launch. */
|
|
208
|
+
temporaryLoops?: ResolvedRoleLoop[];
|
|
209
|
+
/** Durable source of the temporary loop policy; internal and never user-authored. */
|
|
210
|
+
temporaryLoopSource?: 'agent-template' | 'cli' | 'omitted';
|
|
205
211
|
provenance?: Record<string, FieldProvenance>;
|
|
206
212
|
/** Internal/transient: never accepted as a user-authored RoleConfig key. */
|
|
207
213
|
roomMemberStartup?: RoomMemberStartup;
|
|
@@ -294,6 +300,7 @@ export declare function loadConfig(configPath?: string, options?: {
|
|
|
294
300
|
additionalAgent?: {
|
|
295
301
|
id: string;
|
|
296
302
|
definition: AgentDefinition;
|
|
303
|
+
temporary?: boolean;
|
|
297
304
|
};
|
|
298
305
|
skipWatchdogs?: boolean;
|
|
299
306
|
}): FleetConfig;
|
package/dist/config.js
CHANGED
|
@@ -6,7 +6,7 @@ import { harnessRuntimeDir, resolveIsolation, validateIsolationConfig, } from '.
|
|
|
6
6
|
import { getAdapter } from './harness/registry.js';
|
|
7
7
|
import { resolveRoleModelEnv } from './model-env.js';
|
|
8
8
|
import { resolveWatchdogs } from './watchdog/config.js';
|
|
9
|
-
import { resolveLoops } from './loops/config.js';
|
|
9
|
+
import { resolveAgentLoops, resolveLoops } from './loops/config.js';
|
|
10
10
|
import { validateRoomsConfig, validateTasksConfig, validateRoomTemplatesConfig, } from './rooms-tasks/config.js';
|
|
11
11
|
import { CAPABILITIES, CAP_MONITOR_INTERRUPT_AFTER_TOOL } from './capabilities.js';
|
|
12
12
|
import { runningLabel } from './provenance.js';
|
|
@@ -142,12 +142,12 @@ export const BRAIN_PRESET_KEYS = [
|
|
|
142
142
|
];
|
|
143
143
|
export const AGENT_KEYS = [
|
|
144
144
|
'role', 'brain', 'permissions', 'identity', 'cwd', 'coordinator', 'env', 'oversee',
|
|
145
|
-
'isolation', 'monitor', 'owner_channel', 'worklog', 'auth_proxy',
|
|
145
|
+
'isolation', 'monitor', 'owner_channel', 'worklog', 'auth_proxy', 'loops',
|
|
146
146
|
];
|
|
147
147
|
const AGENT_INSTANCE_KEYS = ['template', 'overrides'];
|
|
148
148
|
const TEMPLATE_FORBIDDEN_KEYS = ['identity'];
|
|
149
149
|
const MERGED_MAP_FIELDS = new Set([
|
|
150
|
-
'permissions', 'env', 'isolation', 'monitor', 'owner_channel', 'worklog', 'auth_proxy',
|
|
150
|
+
'permissions', 'env', 'isolation', 'monitor', 'owner_channel', 'worklog', 'auth_proxy', 'loops',
|
|
151
151
|
]);
|
|
152
152
|
const V2_API_VERSION = 'ours.network/fleet/v2';
|
|
153
153
|
const utf8Bytes = (value) => Buffer.byteLength(value, 'utf8');
|
|
@@ -221,6 +221,10 @@ export function validateEffectiveAgentTemplate(definition, id = 'room-member') {
|
|
|
221
221
|
return value;
|
|
222
222
|
}
|
|
223
223
|
function validateAgentScalars(value, file, id) {
|
|
224
|
+
if (value.loops !== undefined)
|
|
225
|
+
resolveAgentLoops(value.loops, {
|
|
226
|
+
name: id, harness: 'codex', session: 'acp', permissions: {},
|
|
227
|
+
}, file);
|
|
224
228
|
for (const key of ['identity', 'cwd']) {
|
|
225
229
|
const current = value[key];
|
|
226
230
|
if (current !== undefined && (typeof current !== 'string' || !current.trim()))
|
|
@@ -471,6 +475,8 @@ function composeSplitRoles(base, baseDoc, yamlMode, diagnostics, files, addition
|
|
|
471
475
|
}
|
|
472
476
|
if (!('role' in a) || !('brain' in a))
|
|
473
477
|
throw new ConfigError(`${agent.file}: agent '${id}' requires role and brain`);
|
|
478
|
+
if (Object.hasOwn(a, 'loops') && !(additionalAgent?.temporary && additionalAgent.id === id))
|
|
479
|
+
schemaError(agent.file, `/agents/${id}/loops`, 'is supported only for temporary Agent launches and inert Agent Templates');
|
|
474
480
|
const role = selection(a.role, 'role', agent.file, roles, ROLE_PRESET_KEYS);
|
|
475
481
|
const brain = selection(a.brain, 'brain', agent.file, brains, BRAIN_PRESET_KEYS);
|
|
476
482
|
const operational = deepSubStrict(Object.fromEntries(Object.entries(a).filter(([key]) => key !== 'role' && key !== 'brain')), vars, agent.file, `/agents/${id}`);
|
|
@@ -545,8 +551,9 @@ function composeSplitRoles(base, baseDoc, yamlMode, diagnostics, files, addition
|
|
|
545
551
|
brain: deepSubStrict(a.brain, vars, agent.file, `/agents/${id}/brain`),
|
|
546
552
|
...operational,
|
|
547
553
|
};
|
|
554
|
+
const { loops: _temporaryLoops, ...runtimeOperational } = operational;
|
|
548
555
|
documents.push({ file: agent.file,
|
|
549
|
-
doc: { roles: { [id]: { ...brainValue, ...roleValue, ...
|
|
556
|
+
doc: { roles: { [id]: { ...brainValue, ...roleValue, ...runtimeOperational } } }, provenance,
|
|
550
557
|
agentSelections: structuredClone({ role: canonicalDefinition.role, brain: canonicalDefinition.brain }),
|
|
551
558
|
agentDefinition: canonicalDefinition,
|
|
552
559
|
...(agentTemplate ? { agentTemplate } : {}) });
|
|
@@ -677,7 +684,7 @@ export function loadConfig(configPath, options = {}) {
|
|
|
677
684
|
defaulted('identity', r.identity !== undefined, r.identity === undefined);
|
|
678
685
|
defaulted('permissions', defaults.permissions !== undefined, defaults.permissions === undefined);
|
|
679
686
|
defaulted('monitor', defaults.monitor !== undefined, defaults.monitor === undefined);
|
|
680
|
-
|
|
687
|
+
const resolvedRole = {
|
|
681
688
|
...r,
|
|
682
689
|
name,
|
|
683
690
|
sourceFile: file,
|
|
@@ -701,7 +708,10 @@ export function loadConfig(configPath, options = {}) {
|
|
|
701
708
|
loops: [],
|
|
702
709
|
...(agentSelections ? { agentSelections } : {}),
|
|
703
710
|
...(agentTemplate ? { agentTemplate } : {}),
|
|
704
|
-
}
|
|
711
|
+
};
|
|
712
|
+
if (agentDefinition?.loops !== undefined)
|
|
713
|
+
resolvedRole.temporaryLoops = resolveAgentLoops(agentDefinition.loops, resolvedRole, file);
|
|
714
|
+
roles.push(resolvedRole);
|
|
705
715
|
// Forbidden-path enforcement: a mount that would breach the policy
|
|
706
716
|
// is a configuration error, caught by `config` rather than at launch.
|
|
707
717
|
if (isolation !== undefined) {
|
|
@@ -806,7 +816,7 @@ export function loadConfig(configPath, options = {}) {
|
|
|
806
816
|
/** Full read-only Agent resolution used by launch dry-runs and room plan validation. */
|
|
807
817
|
export function resolveAgentDefinitionDryRun(configPath, id, definition) {
|
|
808
818
|
return findRole(loadConfig(configPath, {
|
|
809
|
-
additionalAgent: { id, definition }, skipWatchdogs: true,
|
|
819
|
+
additionalAgent: { id, definition, temporary: true }, skipWatchdogs: true,
|
|
810
820
|
}), id);
|
|
811
821
|
}
|
|
812
822
|
/**
|
package/dist/creation.d.ts
CHANGED
|
@@ -156,7 +156,7 @@ export declare function daemonIdentityInventoryProvisioner(env?: NodeJS.ProcessE
|
|
|
156
156
|
/** Atomically write a bare Agent file, journalling it for rollback. */
|
|
157
157
|
export declare function writeRoleFile(tx: CreationTransaction, file: string, contents: string): void;
|
|
158
158
|
/** Where a setting's effective value came from. */
|
|
159
|
-
export type ProvenanceSource = 'cli' | 'fleet-default' | 'caller-role' | 'built-in';
|
|
159
|
+
export type ProvenanceSource = 'cli' | 'agent-template' | 'fleet-default' | 'caller-role' | 'built-in';
|
|
160
160
|
export interface ProvenanceEntry {
|
|
161
161
|
value: unknown;
|
|
162
162
|
source: ProvenanceSource;
|
package/dist/creation.js
CHANGED
|
@@ -374,11 +374,12 @@ export function creationBuildNote(p) {
|
|
|
374
374
|
export function formatProvenance(p) {
|
|
375
375
|
const mark = {
|
|
376
376
|
cli: 'explicit', 'fleet-default': 'fleet default', 'caller-role': 'caller role',
|
|
377
|
-
'built-in': 'built-in',
|
|
377
|
+
'agent-template': 'Agent Template', 'built-in': 'built-in',
|
|
378
378
|
};
|
|
379
379
|
return Object.entries(p.settings)
|
|
380
380
|
.filter(([, e]) => e.value !== undefined)
|
|
381
|
-
.map(([k, e]) => ` ${k.padEnd(12)} ${
|
|
381
|
+
.map(([k, e]) => ` ${k.padEnd(12)} ${e.value && typeof e.value === 'object'
|
|
382
|
+
? JSON.stringify(e.value) : String(e.value)} (${mark[e.source]})`);
|
|
382
383
|
}
|
|
383
384
|
/** Classify one setting: an explicit CLI value, a fleet default, or built-in. */
|
|
384
385
|
export function provenanceOf(cliValue, fleetDefault, builtIn) {
|