harnery 0.17.0 → 0.18.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.
@@ -30,6 +30,8 @@ export const SUPERVISOR_INTENT_SCHEMA_VERSION = 1 as const;
30
30
 
31
31
  const GOAL_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
32
32
  const MAX_TITLE = 200;
33
+ const MAX_OBJECTIVE = 4_000;
34
+ const MAX_ACCEPTANCE = 50;
33
35
  const MAX_INTENT_BYTES = 256 * 1024;
34
36
  const MAX_TEMPLATES = 20;
35
37
  const TEMPLATE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
@@ -50,6 +52,18 @@ export interface SupervisorAutomationPolicy {
50
52
  retry_blocked: boolean;
51
53
  }
52
54
 
55
+ export interface SupervisorMission {
56
+ objective: string;
57
+ acceptance: string[];
58
+ max_milestones: number;
59
+ }
60
+
61
+ export interface CreateSupervisorMissionInput {
62
+ objective: string;
63
+ acceptance: readonly string[];
64
+ maxMilestones?: number;
65
+ }
66
+
53
67
  export interface SupervisorIntent {
54
68
  schema_version: typeof SUPERVISOR_INTENT_SCHEMA_VERSION;
55
69
  id: string;
@@ -58,6 +72,7 @@ export interface SupervisorIntent {
58
72
  specialists: Record<string, WorkflowSpecialistProfile>;
59
73
  limits: SupervisorLimits;
60
74
  automation: SupervisorAutomationPolicy;
75
+ mission?: SupervisorMission;
61
76
  replanning?: SupervisorReplanningPolicy;
62
77
  created_at: string;
63
78
  }
@@ -77,6 +92,8 @@ export type SupervisorNextAction =
77
92
  | "review"
78
93
  | "retry"
79
94
  | "replan"
95
+ | "plan_initial"
96
+ | "plan_milestone"
80
97
  | "review_plan"
81
98
  | "none";
82
99
 
@@ -84,6 +101,7 @@ export interface SupervisorProjection {
84
101
  id: string;
85
102
  title: string;
86
103
  root_work_id: string;
104
+ root_materialized: boolean;
87
105
  state: SupervisorState;
88
106
  reason: string;
89
107
  next_action: SupervisorNextAction;
@@ -98,6 +116,8 @@ export interface SupervisorProjection {
98
116
  plan_generation: number;
99
117
  replans_used: number;
100
118
  replans_remaining: number;
119
+ milestones_completed: number;
120
+ milestones_remaining: number;
101
121
  pending_plan_id?: string;
102
122
  latest_plan_status?: SupervisorPlanRecord["status"];
103
123
  governed_work_ids: string[];
@@ -112,32 +132,57 @@ export interface SupervisorRecord {
112
132
 
113
133
  export interface CreateSupervisorInput {
114
134
  coordRoot: string;
115
- rootWorkId: string;
135
+ rootWorkId?: string;
116
136
  specialists: Readonly<Record<string, WorkflowSpecialistProfile>>;
117
137
  title?: string;
118
138
  id?: string;
119
139
  limits?: Partial<SupervisorLimits>;
120
140
  automation?: Partial<SupervisorAutomationPolicy>;
141
+ mission?: CreateSupervisorMissionInput;
121
142
  replanning?: CreateSupervisorReplanningInput;
122
143
  }
123
144
 
124
145
  export function createSupervisor(input: CreateSupervisorInput): SupervisorRecord {
125
146
  const coordRoot = resolve(input.coordRoot);
126
- assertWorkId(input.rootWorkId);
127
- const root = readWorkItem(coordRoot, input.rootWorkId);
128
147
  const id = input.id ?? newSupervisorId();
129
148
  assertSupervisorId(id);
149
+ const mission = input.mission ? normalizeMission(input.mission) : undefined;
150
+ if (mission && !input.replanning) {
151
+ throw new Error("supervisor mission requires a replanning policy");
152
+ }
153
+ if (!input.rootWorkId && !mission) {
154
+ throw new Error(
155
+ "supervisor creation without root work requires a mission and replanning policy",
156
+ );
157
+ }
158
+ if (input.rootWorkId) assertWorkId(input.rootWorkId);
159
+ const root = input.rootWorkId ? readWorkItem(coordRoot, input.rootWorkId) : undefined;
130
160
  const replanning = input.replanning
131
161
  ? normalizeReplanning(input.replanning, input.specialists)
132
162
  : undefined;
163
+ if (
164
+ mission &&
165
+ !input.rootWorkId &&
166
+ replanning &&
167
+ replanning.max_replans <= mission.max_milestones
168
+ ) {
169
+ throw new Error(
170
+ "objective-first mission max_replans must exceed max_milestones to reserve completion review",
171
+ );
172
+ }
133
173
  const intent: SupervisorIntent = {
134
174
  schema_version: SUPERVISOR_INTENT_SCHEMA_VERSION,
135
175
  id,
136
- title: bounded(input.title ?? root.intent.title, "supervisor title", MAX_TITLE),
137
- root_work_id: input.rootWorkId,
176
+ title: bounded(
177
+ input.title ?? root?.intent.title ?? missionTitle(mission!),
178
+ "supervisor title",
179
+ MAX_TITLE,
180
+ ),
181
+ root_work_id: input.rootWorkId ?? missionRootId(id),
138
182
  specialists: normalizeWorkflowSpecialists(input.specialists),
139
183
  limits: normalizeLimits(input.limits),
140
184
  automation: normalizeAutomation(input.automation),
185
+ ...(mission ? { mission } : {}),
141
186
  ...(replanning ? { replanning } : {}),
142
187
  created_at: new Date().toISOString(),
143
188
  };
@@ -248,7 +293,11 @@ function readSupervisorInternal(
248
293
  const coordRoot = resolve(coordRootRaw);
249
294
  const intent = readSupervisorIntent(coordRoot, goalId);
250
295
  const plans = readSupervisorPlans(coordRoot, intent.id, intent.root_work_id);
251
- const work = collectSupervisorWork(coordRoot, plans.active_root_work_id);
296
+ const activeRootExists = workIntentExists(coordRoot, plans.active_root_work_id);
297
+ if (!activeRootExists && (!intent.mission || plans.generation > 0)) {
298
+ throw new Error(`supervisor ${intent.id} root work is missing`);
299
+ }
300
+ const work = activeRootExists ? collectSupervisorWork(coordRoot, plans.active_root_work_id) : [];
252
301
  const governed = collectGovernedWork(coordRoot, intent.root_work_id, plans, work);
253
302
  return {
254
303
  intent,
@@ -267,7 +316,6 @@ function deriveProjection(
267
316
  ignoreLease: boolean,
268
317
  ): SupervisorProjection {
269
318
  const root = work.find((record) => record.intent.id === plans.active_root_work_id);
270
- if (!root) throw new Error(`supervisor ${intent.id} root work is missing`);
271
319
  const attemptsUsed = governed.reduce((sum, record) => sum + record.projection.attempts_used, 0);
272
320
  const readyWork = work
273
321
  .filter((record) => record.projection.state === "ready")
@@ -311,10 +359,18 @@ function deriveProjection(
311
359
  ...cancelled,
312
360
  ...terminalBlocked,
313
361
  ]);
362
+ const initialRootAccepted =
363
+ intent.mission !== undefined &&
364
+ governed.some(
365
+ (record) =>
366
+ record.intent.id === intent.root_work_id && record.projection.state === "succeeded",
367
+ );
368
+ const milestonesCompleted = plans.milestones_completed + (initialRootAccepted ? 1 : 0);
314
369
  const base = {
315
370
  id: intent.id,
316
371
  title: intent.title,
317
372
  root_work_id: plans.active_root_work_id,
373
+ root_materialized: root !== undefined,
318
374
  work_ids: work.map((record) => record.intent.id),
319
375
  ready_work: readyWork,
320
376
  resumable_work: resumableWork,
@@ -326,6 +382,8 @@ function deriveProjection(
326
382
  plan_generation: plans.generation,
327
383
  replans_used: plans.plans.length,
328
384
  replans_remaining: Math.max(0, (intent.replanning?.max_replans ?? 0) - plans.plans.length),
385
+ milestones_completed: milestonesCompleted,
386
+ milestones_remaining: Math.max(0, (intent.mission?.max_milestones ?? 0) - milestonesCompleted),
329
387
  pending_plan_id: plans.latest?.status === "proposed" ? plans.latest.request.id : undefined,
330
388
  latest_plan_status: plans.latest?.status,
331
389
  governed_work_ids: governed.map((record) => record.intent.id),
@@ -338,11 +396,11 @@ function deriveProjection(
338
396
  next_action: "wait_for_run",
339
397
  };
340
398
  }
341
- if (root.projection.state === "succeeded") {
399
+ if (intent.mission && plans.completed) {
342
400
  return {
343
401
  ...base,
344
402
  state: "succeeded",
345
- reason: "root work was explicitly accepted",
403
+ reason: "mission completion was explicitly accepted",
346
404
  next_action: "none",
347
405
  };
348
406
  }
@@ -370,6 +428,64 @@ function deriveProjection(
370
428
  next_action: "replan",
371
429
  };
372
430
  }
431
+ const triggerFingerprint = supervisorGraphFingerprint({
432
+ rootWorkId: plans.active_root_work_id,
433
+ generation: plans.generation,
434
+ work,
435
+ });
436
+ const latestHandledSameGraph =
437
+ plans.latest?.request.trigger_fingerprint === triggerFingerprint &&
438
+ plans.latest.request.prior_root_work_id === plans.active_root_work_id &&
439
+ plans.latest.status === "attention";
440
+ if (latestHandledSameGraph && plans.latest) {
441
+ return {
442
+ ...base,
443
+ state: "awaiting_attention",
444
+ reason: plans.latest.reason ?? `plan ${plans.latest.request.id} requires attention`,
445
+ next_action: "none",
446
+ };
447
+ }
448
+ if (!root && intent.mission) {
449
+ if (!intent.replanning || plans.plans.length >= intent.replanning.max_replans) {
450
+ return {
451
+ ...base,
452
+ state: "budget_exhausted",
453
+ reason: "mission exhausted its planning budget before creating an initial milestone",
454
+ next_action: "none",
455
+ };
456
+ }
457
+ return {
458
+ ...base,
459
+ state: "ready",
460
+ reason: "mission is ready for its initial bounded milestone plan",
461
+ next_action: "plan_initial",
462
+ };
463
+ }
464
+ if (!root) throw new Error(`supervisor ${intent.id} root work is missing`);
465
+ if (root.projection.state === "succeeded" && intent.mission) {
466
+ if (!intent.replanning || plans.plans.length >= intent.replanning.max_replans) {
467
+ return {
468
+ ...base,
469
+ state: "budget_exhausted",
470
+ reason: "mission exhausted its planning budget at a milestone boundary",
471
+ next_action: "none",
472
+ };
473
+ }
474
+ return {
475
+ ...base,
476
+ state: "ready",
477
+ reason: `milestone ${plans.milestones_completed} is accepted and requires mission reassessment`,
478
+ next_action: "plan_milestone",
479
+ };
480
+ }
481
+ if (root.projection.state === "succeeded") {
482
+ return {
483
+ ...base,
484
+ state: "succeeded",
485
+ reason: "root work was explicitly accepted",
486
+ next_action: "none",
487
+ };
488
+ }
373
489
  const resumableDispatchable = intent.automation.resume_approved ? resumableWork : [];
374
490
  const attemptDispatchable = [
375
491
  ...readyWork,
@@ -448,15 +564,6 @@ function deriveProjection(
448
564
  next_action: "none",
449
565
  };
450
566
  }
451
- const triggerFingerprint = supervisorGraphFingerprint({
452
- rootWorkId: plans.active_root_work_id,
453
- generation: plans.generation,
454
- work,
455
- });
456
- const latestHandledSameGraph =
457
- plans.latest?.request.trigger_fingerprint === triggerFingerprint &&
458
- plans.latest.request.prior_root_work_id === plans.active_root_work_id &&
459
- plans.latest.status === "attention";
460
567
  const canReplan =
461
568
  intent.replanning !== undefined &&
462
569
  plans.plans.length < intent.replanning.max_replans &&
@@ -484,14 +591,6 @@ function deriveProjection(
484
591
  next_action: "none",
485
592
  };
486
593
  }
487
- if (latestHandledSameGraph && plans.latest) {
488
- return {
489
- ...base,
490
- state: "awaiting_attention",
491
- reason: plans.latest.reason ?? `plan ${plans.latest.request.id} requires attention`,
492
- next_action: "none",
493
- };
494
- }
495
594
  return {
496
595
  ...base,
497
596
  state: "blocked",
@@ -531,6 +630,19 @@ function validateIntent(intent: SupervisorIntent, goalId: string): void {
531
630
  }
532
631
  bounded(intent.title, "supervisor title", MAX_TITLE);
533
632
  assertWorkId(intent.root_work_id);
633
+ if (intent.mission) {
634
+ const normalizedMission = normalizeMission({
635
+ objective: intent.mission.objective,
636
+ acceptance: intent.mission.acceptance,
637
+ maxMilestones: intent.mission.max_milestones,
638
+ });
639
+ if (JSON.stringify(normalizedMission) !== JSON.stringify(intent.mission)) {
640
+ throw new Error(`supervisor intent ${goalId} mission is not canonical`);
641
+ }
642
+ if (!intent.replanning) {
643
+ throw new Error(`supervisor intent ${goalId} mission requires replanning`);
644
+ }
645
+ }
534
646
  const specialists = normalizeWorkflowSpecialists(intent.specialists);
535
647
  if (JSON.stringify(specialists) !== JSON.stringify(intent.specialists)) {
536
648
  throw new Error(`supervisor intent ${goalId} specialists are not canonical`);
@@ -546,6 +658,32 @@ function validateIntent(intent: SupervisorIntent, goalId: string): void {
546
658
  if (intent.replanning) validateReplanning(intent.replanning, specialists, goalId);
547
659
  }
548
660
 
661
+ function normalizeMission(input: CreateSupervisorMissionInput): SupervisorMission {
662
+ if (!Array.isArray(input.acceptance)) {
663
+ throw new Error("supervisor mission acceptance must be an array");
664
+ }
665
+ if (input.acceptance.length < 1 || input.acceptance.length > MAX_ACCEPTANCE) {
666
+ throw new Error(`supervisor mission acceptance must contain 1 to ${MAX_ACCEPTANCE} criteria`);
667
+ }
668
+ const acceptance = input.acceptance.map((criterion, index) =>
669
+ bounded(criterion, `supervisor mission acceptance[${index}]`, 500),
670
+ );
671
+ if (new Set(acceptance).size !== acceptance.length) {
672
+ throw new Error("supervisor mission acceptance criteria must be unique");
673
+ }
674
+ return {
675
+ objective: bounded(input.objective, "supervisor mission objective", MAX_OBJECTIVE),
676
+ acceptance,
677
+ max_milestones: positive(input.maxMilestones ?? 4, "supervisor mission max_milestones", 20),
678
+ };
679
+ }
680
+
681
+ function missionTitle(mission: SupervisorMission): string {
682
+ return mission.objective.length <= MAX_TITLE
683
+ ? mission.objective
684
+ : `${mission.objective.slice(0, MAX_TITLE - 1).trimEnd()}…`;
685
+ }
686
+
549
687
  function normalizeLimits(input: Partial<SupervisorLimits> | undefined): SupervisorLimits {
550
688
  return {
551
689
  max_cycles: positive(input?.max_cycles ?? 50, "supervisor max_cycles", 1_000),
@@ -685,8 +823,10 @@ function collectGovernedWork(
685
823
  active: WorkRecord[],
686
824
  ): WorkRecord[] {
687
825
  const governed = new Map<string, WorkRecord>();
688
- for (const record of collectSupervisorWork(coordRoot, originalRootWorkId)) {
689
- governed.set(record.intent.id, record);
826
+ if (workIntentExists(coordRoot, originalRootWorkId)) {
827
+ for (const record of collectSupervisorWork(coordRoot, originalRootWorkId)) {
828
+ governed.set(record.intent.id, record);
829
+ }
690
830
  }
691
831
  for (const workId of plans.applied_work_ids)
692
832
  governed.set(workId, readWorkItem(coordRoot, workId));
@@ -694,6 +834,14 @@ function collectGovernedWork(
694
834
  return [...governed.values()];
695
835
  }
696
836
 
837
+ function workIntentExists(coordRoot: string, workId: string): boolean {
838
+ return existsSync(join(resolve(coordRoot), ".harnery", "work", workId, "intent.json"));
839
+ }
840
+
841
+ function missionRootId(goalId: string): string {
842
+ return `${goalId.slice(0, 87)}-mission-root`;
843
+ }
844
+
697
845
  function supervisorDir(coordRoot: string, goalId: string): string {
698
846
  assertSupervisorId(goalId);
699
847
  return join(resolve(coordRoot), ".harnery", "supervisors", goalId);