@ours.network/fleet 1.1.0-nightly.21 → 1.1.0-nightly.23
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 +12 -2
- package/dist/build-info.json +4 -4
- package/dist/owner-channel/channel.d.ts +2 -0
- package/dist/owner-channel/channel.js +41 -23
- package/dist/preset-migration.d.ts +1 -1
- package/dist/preset-migration.js +23 -6
- package/dist/runner.js +45 -17
- 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
|
@@ -517,6 +517,15 @@ ours-fleet task create --title "Reviewed change" --template pair
|
|
|
517
517
|
ours-fleet task create --title "Phased delivery" --template team
|
|
518
518
|
```
|
|
519
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
|
+
|
|
520
529
|
`ours-fleet init -c /path/custom.yaml` reviews the literal resolved manifest and
|
|
521
530
|
`/path/custom/` split directory before doing anything. It requires a TTY and two
|
|
522
531
|
default-No confirmations. You select Codex, Claude, or both; then either one explicit
|
|
@@ -547,11 +556,12 @@ task-local `LocalCoordinator`, `Developer`, and `Critic`. The persistent
|
|
|
547
556
|
`FleetCoordinator` uses the separate packaged `Coordinator` contract and coordination
|
|
548
557
|
model.
|
|
549
558
|
|
|
550
|
-
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
|
|
551
561
|
`ours-fleet migrate-role-defaults -c FILE` for a zero-write plan, review every removal,
|
|
552
562
|
replacement, addition, preserved custom file, staging path, and recovery path, then rerun
|
|
553
563
|
with `--write`. Only exact semantic matches for packaged-bootstrap and generated
|
|
554
|
-
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
|
|
555
565
|
dangling custom reference refuses publication. Rerunning the migration is a no-op.
|
|
556
566
|
|
|
557
567
|
For manual adoption of a single newer packaged file, copy the reported source beside the
|
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.23",
|
|
3
|
+
"buildId": "de853a9ef3d8",
|
|
4
|
+
"commit": "f970d01c44d8a5cad2627c1bdde833e412508613",
|
|
5
5
|
"dirty": true,
|
|
6
|
-
"builtAt": "2026-09-
|
|
6
|
+
"builtAt": "2026-09-02T07:30:34.670Z",
|
|
7
7
|
"capabilities": [
|
|
8
8
|
"monitor.interrupt.after_tool"
|
|
9
9
|
]
|
|
@@ -225,6 +225,8 @@ export declare class OwnerChannel implements OwnerChannelHandle {
|
|
|
225
225
|
resourceIds?: Record<string, string>;
|
|
226
226
|
presentations?: FleetAuditPresentation[];
|
|
227
227
|
}): Promise<FleetAuditAttempt>;
|
|
228
|
+
private flushPendingFleetCommandAudits;
|
|
229
|
+
private deliverFleetCommandOutcome;
|
|
228
230
|
recover(epoch: string): Promise<void>;
|
|
229
231
|
private recoveryStage;
|
|
230
232
|
private writeShutdownState;
|
|
@@ -199,6 +199,7 @@ export class OwnerChannel {
|
|
|
199
199
|
}
|
|
200
200
|
this.startedOnce = true;
|
|
201
201
|
this.ready = true;
|
|
202
|
+
await this.flushPendingFleetCommandAudits();
|
|
202
203
|
// Do not make role startup wait for an old owner request to finish a turn.
|
|
203
204
|
// watchLoop itself drains before every establishment, including this first
|
|
204
205
|
// one, so there is no drain-to-tip race.
|
|
@@ -301,33 +302,50 @@ export class OwnerChannel {
|
|
|
301
302
|
return attempt;
|
|
302
303
|
if (attempt.outcome?.delivery === 'uncertain')
|
|
303
304
|
return attempt;
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
this.options.log(`[${this.options.role}] duplicate Fleet lifecycle event suppressed`);
|
|
315
|
-
continue;
|
|
316
|
-
}
|
|
317
|
-
throw error;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
catch (error) {
|
|
322
|
-
uncertain = true;
|
|
323
|
-
this.logError(`fleet lifecycle delivery uncertain correlation=${attempt.correlationId}`, error);
|
|
324
|
-
}
|
|
325
|
-
attempt = this.commandAudits.outcome(attempt.correlationId, this.options.role, uncertain ? 'uncertain' : 'delivered');
|
|
326
|
-
return attempt;
|
|
305
|
+
// Before the Owner sink is attached, ordinary commands have nothing to
|
|
306
|
+
// deliver. Lifecycle presentations remain in the never-attempted
|
|
307
|
+
// `sending` state and are flushed exactly once when start() makes the
|
|
308
|
+
// authenticated sink ready. A restart converts them to `uncertain`, so
|
|
309
|
+
// effects are never replayed after an unproven delivery boundary.
|
|
310
|
+
if (!attempt.outcome?.presentations?.length)
|
|
311
|
+
return this.commandAudits.outcome(attempt.correlationId, this.options.role, 'delivered');
|
|
312
|
+
if (!this.ready)
|
|
313
|
+
return attempt;
|
|
314
|
+
return this.deliverFleetCommandOutcome(attempt);
|
|
327
315
|
});
|
|
328
316
|
this.managementTail = run.then(() => undefined, () => undefined);
|
|
329
317
|
return run;
|
|
330
318
|
}
|
|
319
|
+
async flushPendingFleetCommandAudits() {
|
|
320
|
+
for (const attempt of this.commandAudits.list())
|
|
321
|
+
if (attempt.outcome?.delivery === 'sending')
|
|
322
|
+
await this.deliverFleetCommandOutcome(attempt);
|
|
323
|
+
}
|
|
324
|
+
async deliverFleetCommandOutcome(attempt) {
|
|
325
|
+
let uncertain = false;
|
|
326
|
+
try {
|
|
327
|
+
for (const event of attempt.outcome?.presentations ?? []) {
|
|
328
|
+
try {
|
|
329
|
+
await this.sendProactiveMessage(renderFleetLifecycleEvent(event), `fleet-lifecycle\0${lifecycleEventDigestBasis(event)}`, 0);
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
if (error instanceof DuplicateSendError) {
|
|
333
|
+
if (error.status !== 'delivered')
|
|
334
|
+
uncertain = true;
|
|
335
|
+
this.options.log(`[${this.options.role}] duplicate Fleet lifecycle event suppressed`);
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
throw error;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
uncertain = true;
|
|
344
|
+
this.logError(`fleet lifecycle delivery uncertain correlation=${attempt.correlationId}`, error);
|
|
345
|
+
}
|
|
346
|
+
attempt = this.commandAudits.outcome(attempt.correlationId, this.options.role, uncertain ? 'uncertain' : 'delivered');
|
|
347
|
+
return attempt;
|
|
348
|
+
}
|
|
331
349
|
recover(epoch) {
|
|
332
350
|
if (this.stopping)
|
|
333
351
|
return Promise.reject(new Error('owner channel is stopping'));
|
|
@@ -31,7 +31,7 @@ interface MigrationHooks {
|
|
|
31
31
|
beforeLockClaim?: () => void;
|
|
32
32
|
beforeRoleDefaultPublish?: () => void;
|
|
33
33
|
}
|
|
34
|
-
/** Explicit adoption of exact packaged revision-3 role defaults; customized files are never changed. */
|
|
34
|
+
/** Explicit adoption of exact packaged revision-3/4 role defaults; customized files are never changed. */
|
|
35
35
|
export declare function migratePackagedRoleDefaults(configuration: string, options?: {
|
|
36
36
|
write?: boolean;
|
|
37
37
|
}, hooks?: MigrationHooks): RoleDefaultMigrationResult;
|
package/dist/preset-migration.js
CHANGED
|
@@ -44,7 +44,14 @@ const V3_GENERATED_ROLE_DEFAULT_FINGERPRINTS = Object.freeze({
|
|
|
44
44
|
'agent_templates/Tester.yaml': '3733db187951353f855dc998f1dd1f66d883eb556a3e6431a7abf4e267f63071',
|
|
45
45
|
'agents/FleetCoordinator.yaml': '70f90797899828c3669c3914e8f873314e48cb9502aff187d2f1fbc9177f6df9',
|
|
46
46
|
});
|
|
47
|
-
const
|
|
47
|
+
const V4_ROLE_DEFAULT_FINGERPRINTS = Object.freeze({
|
|
48
|
+
'agent_templates/LocalCoordinator.yaml': 'f04a88b541e655e0039e4dc9834293b5180f6d488a473a6c51b50d0b72dae83b',
|
|
49
|
+
'room_templates/team.yaml': 'accaa3569781f7acde3f15c89f447f4132f580f936513379c704aad27481e13f',
|
|
50
|
+
});
|
|
51
|
+
const V4_GENERATED_ROLE_DEFAULT_FINGERPRINTS = Object.freeze({
|
|
52
|
+
'agent_templates/LocalCoordinator.yaml': 'cb7c83a835dde9d45bac42cadce2f196f561d2460321c690cdef435f7076f97f',
|
|
53
|
+
});
|
|
54
|
+
const V5_ROLE_DEFAULTS = new Set([
|
|
48
55
|
'roles/Coordinator.yaml', 'roles/LocalCoordinator.yaml', 'roles/Developer.yaml', 'roles/Critic.yaml',
|
|
49
56
|
'agent_templates/LocalCoordinator.yaml', 'agent_templates/Developer.yaml', 'agent_templates/Critic.yaml',
|
|
50
57
|
'room_templates/single.yaml', 'room_templates/pair.yaml', 'room_templates/team.yaml',
|
|
@@ -145,7 +152,7 @@ function validateRoleDefaultMigration(stageManifest) {
|
|
|
145
152
|
throw new Error(`Room Template '${template.name}' references missing Agent Template '${member.agent_template}'`);
|
|
146
153
|
}
|
|
147
154
|
}
|
|
148
|
-
/** Explicit adoption of exact packaged revision-3 role defaults; customized files are never changed. */
|
|
155
|
+
/** Explicit adoption of exact packaged revision-3/4 role defaults; customized files are never changed. */
|
|
149
156
|
export function migratePackagedRoleDefaults(configuration, options = {}, hooks = {}) {
|
|
150
157
|
const configPath = resolve(configuration);
|
|
151
158
|
const root = splitRootFor(configPath);
|
|
@@ -166,18 +173,28 @@ export function migratePackagedRoleDefaults(configuration, options = {}, hooks =
|
|
|
166
173
|
const removals = [];
|
|
167
174
|
const replacements = [];
|
|
168
175
|
const preserved = [];
|
|
169
|
-
|
|
176
|
+
const known = new Set([
|
|
177
|
+
...Object.keys(V3_ROLE_DEFAULT_FINGERPRINTS),
|
|
178
|
+
...Object.keys(V4_ROLE_DEFAULT_FINGERPRINTS),
|
|
179
|
+
]);
|
|
180
|
+
for (const relative of known) {
|
|
170
181
|
const path = join(root, relative);
|
|
171
182
|
if (!existsSync(path))
|
|
172
183
|
continue;
|
|
173
184
|
const actual = fingerprint(path);
|
|
174
|
-
|
|
175
|
-
|
|
185
|
+
const recognized = [
|
|
186
|
+
V3_ROLE_DEFAULT_FINGERPRINTS[relative],
|
|
187
|
+
V3_GENERATED_ROLE_DEFAULT_FINGERPRINTS[relative],
|
|
188
|
+
V4_ROLE_DEFAULT_FINGERPRINTS[relative],
|
|
189
|
+
V4_GENERATED_ROLE_DEFAULT_FINGERPRINTS[relative],
|
|
190
|
+
].some(expected => expected !== undefined && actual === expected);
|
|
191
|
+
if (recognized)
|
|
192
|
+
(V5_ROLE_DEFAULTS.has(relative) ? replacements : removals).push(path);
|
|
176
193
|
else
|
|
177
194
|
preserved.push(path);
|
|
178
195
|
}
|
|
179
196
|
const additions = [];
|
|
180
|
-
for (const relative of
|
|
197
|
+
for (const relative of V5_ROLE_DEFAULTS) {
|
|
181
198
|
const path = join(root, relative);
|
|
182
199
|
if (!existsSync(path))
|
|
183
200
|
additions.push(path);
|
package/dist/runner.js
CHANGED
|
@@ -26,6 +26,7 @@ import { FLEET_PROXY_CALLER_ENV, FLEET_PROXY_STATE_DIR_ENV, } from './fleet-prox
|
|
|
26
26
|
import { effectivePermissionMode } from './permissions.js';
|
|
27
27
|
import { assertModelPinReachesChild, effectiveRoleModel, repinModelEnv } from './model-env.js';
|
|
28
28
|
import { archiveTempState, markTempSupervisorActive, requestedTempStopReason, } from './temp-lifecycle.js';
|
|
29
|
+
import { FleetCommandAuditStore } from './fleet-command-audit.js';
|
|
29
30
|
export const SUPERVISOR_RECYCLE_REQUIRED = 'OWNER_CHANNEL_SUPERVISOR_RECYCLE_REQUIRED';
|
|
30
31
|
export class SupervisorRecycleRequiredError extends Error {
|
|
31
32
|
code = SUPERVISOR_RECYCLE_REQUIRED;
|
|
@@ -70,6 +71,31 @@ const defaultDeps = () => ({
|
|
|
70
71
|
});
|
|
71
72
|
const MONITOR_OWNER_FILE = '.monitor-owner';
|
|
72
73
|
const OBSOLETE_OURS_AUTOSTART_ENV = 'OURS_AUTOSTART';
|
|
74
|
+
function localFleetAuditor(stateDir, caller, log) {
|
|
75
|
+
const store = new FleetCommandAuditStore(join(stateDir, '.fleet-command-audit.json'));
|
|
76
|
+
return {
|
|
77
|
+
async begin(requestId, argv) {
|
|
78
|
+
let attempt = store.begin(requestId, caller, argv);
|
|
79
|
+
if (attempt.invocation === 'sending') {
|
|
80
|
+
log(`[${caller}] fleet proxy command ${attempt.correlationId} `
|
|
81
|
+
+ `route=${attempt.classification.route} decision=${attempt.classification.decision}`);
|
|
82
|
+
attempt = store.invocation(attempt.correlationId, caller, 'delivered');
|
|
83
|
+
}
|
|
84
|
+
return attempt;
|
|
85
|
+
},
|
|
86
|
+
async finish(input) {
|
|
87
|
+
let attempt = store.finish(input.correlationId, caller, {
|
|
88
|
+
class: input.class, effect: input.effect,
|
|
89
|
+
...(input.exitCode === undefined ? {} : { exitCode: input.exitCode }),
|
|
90
|
+
...(input.resourceIds ? { resourceIds: input.resourceIds } : {}),
|
|
91
|
+
...(input.presentations ? { presentations: input.presentations } : {}),
|
|
92
|
+
});
|
|
93
|
+
if (attempt.outcome?.delivery === 'sending')
|
|
94
|
+
attempt = store.outcome(attempt.correlationId, caller, 'delivered');
|
|
95
|
+
return attempt;
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
73
99
|
/** Environment injected only into the managed harness process. */
|
|
74
100
|
export function managedFleetProxyEnv(role, stateDir) {
|
|
75
101
|
const roleEnv = { ...(role.env ?? {}) };
|
|
@@ -612,11 +638,28 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
612
638
|
+ `${error?.message ?? String(error)}`);
|
|
613
639
|
}
|
|
614
640
|
}
|
|
615
|
-
control = deps.createControlServer(dir, arbiter, deps.log);
|
|
616
641
|
try {
|
|
642
|
+
if (role.owner_channel)
|
|
643
|
+
ownerChannel = deps.createOwnerChannel({
|
|
644
|
+
role: name,
|
|
645
|
+
harness: role.harness,
|
|
646
|
+
config: role.owner_channel,
|
|
647
|
+
session: arbiter,
|
|
648
|
+
stateDir: dir,
|
|
649
|
+
env: role.env,
|
|
650
|
+
log: deps.log,
|
|
651
|
+
...(ownerBinder ? { binderLease: ownerBinder } : {}),
|
|
652
|
+
...(configPath ? { configPath } : {}),
|
|
653
|
+
});
|
|
654
|
+
control = deps.createControlServer(dir, arbiter, deps.log);
|
|
655
|
+
control.setFleetAuditor(ownerChannel ? {
|
|
656
|
+
begin: (requestId, argv) => ownerChannel.beginFleetCommandAudit(requestId, argv),
|
|
657
|
+
finish: input => ownerChannel.finishFleetCommandAudit(input),
|
|
658
|
+
} : localFleetAuditor(dir, name, deps.log));
|
|
617
659
|
await control.start();
|
|
618
660
|
}
|
|
619
661
|
catch (error) {
|
|
662
|
+
await ownerChannel?.close().catch(() => undefined);
|
|
620
663
|
ownerBinder?.release();
|
|
621
664
|
await agentSession.close();
|
|
622
665
|
unsubscribeRecovery?.();
|
|
@@ -731,18 +774,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
731
774
|
deps.log(`[${name}] ${sessionLabel} startup prompt cancelled by ${started.cancellationSource}; `
|
|
732
775
|
+ 'keeping temporary supervisor alive');
|
|
733
776
|
sessionStartupComplete = true;
|
|
734
|
-
if (
|
|
735
|
-
ownerChannel = deps.createOwnerChannel({
|
|
736
|
-
role: name,
|
|
737
|
-
harness: role.harness,
|
|
738
|
-
config: role.owner_channel,
|
|
739
|
-
session: arbiter,
|
|
740
|
-
stateDir: dir,
|
|
741
|
-
env: role.env,
|
|
742
|
-
log: deps.log,
|
|
743
|
-
...(ownerBinder ? { binderLease: ownerBinder } : {}),
|
|
744
|
-
...(configPath ? { configPath } : {}),
|
|
745
|
-
});
|
|
777
|
+
if (ownerChannel) {
|
|
746
778
|
try {
|
|
747
779
|
await ownerChannel.start();
|
|
748
780
|
}
|
|
@@ -761,10 +793,6 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
761
793
|
}
|
|
762
794
|
if (ownerChannel) {
|
|
763
795
|
control.setOwnerChannel(ownerChannel);
|
|
764
|
-
control.setFleetAuditor({
|
|
765
|
-
begin: (requestId, argv) => ownerChannel.beginFleetCommandAudit(requestId, argv),
|
|
766
|
-
finish: input => ownerChannel.finishFleetCommandAudit(input),
|
|
767
|
-
});
|
|
768
796
|
}
|
|
769
797
|
reloadLoopConfig = async () => {
|
|
770
798
|
const nextRole = findRole(loadConfig(configPath), name);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ours.network/fleet",
|
|
3
|
-
"version": "1.1.0-nightly.
|
|
3
|
+
"version": "1.1.0-nightly.23",
|
|
4
4
|
"description": "Harness-agnostic fleet of persistent, identity-bound AI agents. Declarative fleet.yaml, managed native/ACP sessions, supervision, and ours.network messaging.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "FSL-1.1-Apache-2.0",
|
|
@@ -2,3 +2,48 @@ role: { ref: LocalCoordinator }
|
|
|
2
2
|
brain: { ref: claude-default }
|
|
3
3
|
coordinator: FleetCoordinator
|
|
4
4
|
permissions: { approval: ask, filesystem: workspace, unattended: deny }
|
|
5
|
+
loops:
|
|
6
|
+
continuity:
|
|
7
|
+
interval: 15m
|
|
8
|
+
prompt: |
|
|
9
|
+
Perform one bounded, observation-only continuity pass for the assigned task room. Do not modify
|
|
10
|
+
task or project state; the only permitted writes are the room action and secret-free WORKLOG
|
|
11
|
+
checkpoint defined below. Anchor the preceding interval to scheduled_at minus 15 minutes
|
|
12
|
+
through scheduled_at. A late start or missed-loop metadata describes this loop's outage, not
|
|
13
|
+
evidence that development froze.
|
|
14
|
+
|
|
15
|
+
Re-read ROUTINES.md when present and the continuity checkpoint in WORKLOG.md. Use bounded
|
|
16
|
+
`list_history` reads filtered to the assigned room CID and direction as needed. Do not drain
|
|
17
|
+
unread mail or mark unrelated mail read. Ignore unrelated contacts and private messages.
|
|
18
|
+
Use only attributable room/task evidence. Exclude loop-authored `CONTINUITY *` messages from
|
|
19
|
+
development evidence and fingerprint inputs; retain their request/report IDs separately for
|
|
20
|
+
dedupe. Lifecycle state `active`, elapsed time, silence, task difficulty, and filesystem
|
|
21
|
+
dirtiness alone do not prove development is active, meaningful progress, or a stall. Meaningful
|
|
22
|
+
progress is a concrete reported change, test result, decision, handoff, blocker delta, active
|
|
23
|
+
step, or unexpired ETA.
|
|
24
|
+
|
|
25
|
+
After a mandatory final history recheck, choose exactly one mutually exclusive branch:
|
|
26
|
+
- Terminal: if attributable evidence says the task is terminal, do nothing.
|
|
27
|
+
- Recent update: if the room already has a meaningful update in the interval, do nothing.
|
|
28
|
+
- Ongoing: if attributable development evidence is ongoing but the room has no meaningful
|
|
29
|
+
update in the interval, send at most one concise
|
|
30
|
+
`CONTINUITY ACTIVE [start..end]: <observed facts>; next=<known step or ETA>` update.
|
|
31
|
+
- Unobservable/no evidence: never invent activity. If no qualifying outstanding continuity
|
|
32
|
+
request exists, send one direct `CONTINUITY CHECK` request for status, next step, and ETA.
|
|
33
|
+
If the prior request is less than 10 minutes old or its ETA is unexpired, do nothing. If it
|
|
34
|
+
is at least 10 minutes old, no later meaningful evidence exists, and the ETA is absent or
|
|
35
|
+
elapsed, send at most one clear `CONTINUITY STALLED` report.
|
|
36
|
+
- Explicit stop: if attributable evidence says work stopped or is blocked with no active next
|
|
37
|
+
step, send at most one clear `CONTINUITY STALLED` report.
|
|
38
|
+
|
|
39
|
+
Before any send, suppress a semantic duplicate found by the final history recheck or checkpoint;
|
|
40
|
+
room history wins if they disagree. Build the stable fingerprint from classification and
|
|
41
|
+
non-loop evidence IDs/timestamps plus requested/reported state, excluding loop-authored
|
|
42
|
+
continuity messages, prose, and time-of-check so unchanged classification and evidence state
|
|
43
|
+
stay silent. Store continuity request/report IDs separately. Later meaningful progress resets
|
|
44
|
+
a prior request or stall. Append a secret-free WORKLOG checkpoint with interval, classification,
|
|
45
|
+
evidence IDs/times, fingerprint, and any separate request/report ID.
|
|
46
|
+
|
|
47
|
+
Do not edit project files, run implementation or tests, interrupt peers, spawn or manage agents,
|
|
48
|
+
invoke task/room lifecycle actions, infer from unrelated messages, or exceed LocalCoordinator
|
|
49
|
+
authority. Complete silently when no room action is required.
|
|
@@ -3,6 +3,7 @@ description: "Locally coordinated team: LocalCoordinator sequences, Developer ex
|
|
|
3
3
|
room: { quiet_membership: false, anonymous: false }
|
|
4
4
|
contract: |
|
|
5
5
|
LocalCoordinator coordinates sequencing, handoffs, context, and blockers among existing members.
|
|
6
|
+
LocalCoordinator performs a bounded 15-minute continuity check without taking over implementation.
|
|
6
7
|
Developer owns implementation and verification evidence.
|
|
7
8
|
Critic independently reviews and withholds sign-off on material failures.
|
|
8
9
|
LocalCoordinator has no Fleet lifecycle or agent-management authority.
|