openhermes 4.11.2 → 4.13.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.
Files changed (74) hide show
  1. package/CONTEXT.md +1 -1
  2. package/ETHOS.md +1 -1
  3. package/README.md +12 -18
  4. package/bootstrap.ts +73 -148
  5. package/docs/HOW-IT-WORKS.md +162 -0
  6. package/docs/adr/ADR-0001-rebuild-vs-increment.md +30 -0
  7. package/docs/adr/ADR-0002-routing-graph-vs-linear-chain.md +36 -0
  8. package/docs/adr/ADR-0003-per-directory-plan-storage.md +34 -0
  9. package/docs/adr/ADR-0004-composer-fragment-architecture.md +42 -0
  10. package/docs/adr/ADR-0005-hook-system-design.md +42 -0
  11. package/docs/adr/README.md +9 -0
  12. package/harness/codex/AUTOPILOT.md +30 -23
  13. package/harness/codex/CHARTER.md +3 -3
  14. package/harness/lib/composer/compose.test.ts +11 -0
  15. package/harness/lib/composer/fragments/02-delegation.md +2 -1
  16. package/harness/lib/composer/fragments/04-task-flow.md +42 -2
  17. package/harness/lib/composer/fragments/08-routing.md +1 -1
  18. package/harness/lib/composer/fragments/09-guardrails.md +17 -4
  19. package/harness/lib/composer/index.ts +1 -1
  20. package/harness/lib/guards/guard-config.ts +72 -0
  21. package/harness/lib/hooks/builtins/confidence-gate-hook.ts +2 -4
  22. package/harness/lib/hooks/builtins/delegation-depth-hook.ts +23 -4
  23. package/harness/lib/hooks/builtins/dynamic-route-hook.ts +99 -0
  24. package/harness/lib/hooks/builtins/next-route-hook.ts +24 -0
  25. package/harness/lib/hooks/builtins/plan-check-hook.ts +2 -2
  26. package/harness/lib/hooks/builtins/route-tracking-hook.ts +79 -25
  27. package/harness/lib/hooks/hooks.test.ts +117 -205
  28. package/harness/lib/hooks/index.ts +38 -30
  29. package/harness/lib/hooks/registry.ts +309 -416
  30. package/harness/lib/hooks/types.ts +116 -71
  31. package/harness/lib/plans/plan-location.ts +134 -0
  32. package/harness/lib/routing/index.ts +21 -0
  33. package/harness/lib/routing/route-guidance.ts +147 -0
  34. package/harness/lib/routing/route-resolver.ts +58 -0
  35. package/harness/lib/routing/routing.test.ts +195 -0
  36. package/harness/lib/routing/skill-frontmatter.ts +125 -0
  37. package/harness/lib/routing/types.ts +52 -0
  38. package/harness/skills/oh-ascii/SKILL.md +1 -1
  39. package/harness/skills/oh-fusion/DEEP.md +56 -33
  40. package/harness/skills/oh-fusion/SKILL.md +30 -16
  41. package/harness/skills/oh-init/DEEP.md +2 -2
  42. package/harness/skills/oh-manifest/SKILL.md +1 -0
  43. package/harness/skills/oh-plan-review/DEEP.md +1 -1
  44. package/harness/skills/oh-planner/DEEP.md +3 -3
  45. package/harness/skills/oh-review/DEEP.md +2 -0
  46. package/harness/skills/oh-review/SKILL.md +1 -0
  47. package/package.json +56 -55
  48. package/harness/lib/background/background.test.ts +0 -197
  49. package/harness/lib/background/index.ts +0 -7
  50. package/harness/lib/background/interfaces.ts +0 -31
  51. package/harness/lib/background/manager.ts +0 -320
  52. package/harness/lib/hooks/builtins/error-recovery-hook.ts +0 -107
  53. package/harness/lib/hooks/builtins/memory-sync-hook.ts +0 -73
  54. package/harness/lib/hooks/builtins/sanity-check-hook.ts +0 -52
  55. package/harness/lib/memory/index.ts +0 -18
  56. package/harness/lib/memory/interfaces.ts +0 -53
  57. package/harness/lib/memory/memory-manager.ts +0 -205
  58. package/harness/lib/memory/memory.test.ts +0 -491
  59. package/harness/lib/memory/plan-store.ts +0 -366
  60. package/harness/lib/recovery/handler.ts +0 -243
  61. package/harness/lib/recovery/index.ts +0 -14
  62. package/harness/lib/recovery/interfaces.ts +0 -48
  63. package/harness/lib/recovery/patterns.ts +0 -149
  64. package/harness/lib/recovery/recovery.test.ts +0 -312
  65. package/harness/lib/sanity/anomaly-tracker.ts +0 -127
  66. package/harness/lib/sanity/checker.ts +0 -178
  67. package/harness/lib/sanity/index.ts +0 -13
  68. package/harness/lib/sanity/interfaces.ts +0 -24
  69. package/harness/lib/sanity/sanity.test.ts +0 -472
  70. package/harness/lib/sync/file-watcher.ts +0 -174
  71. package/harness/lib/sync/index.ts +0 -11
  72. package/harness/lib/sync/interfaces.ts +0 -27
  73. package/harness/lib/sync/plan-sync.ts +0 -536
  74. package/harness/lib/sync/sync.test.ts +0 -832
@@ -0,0 +1,99 @@
1
+ import path from "node:path";
2
+ import { HookPhase, HookResult } from "../types.ts";
3
+ import type { HookContext, PostToolUseHook } from "../types.ts";
4
+ import { readSkillFrontmatter, resolveRoute } from "../../routing/index.ts";
5
+ import type { RouteEvidence } from "../../routing/index.ts";
6
+ import { ROUTE_GUIDANCE_PREFIX } from "../../routing/index.ts";
7
+ import {
8
+ ROUTE_ACTIONS,
9
+ ROUTE_OUTCOMES,
10
+ ROUTE_VERIFICATIONS,
11
+ ROUTE_WORK_TYPES,
12
+ } from "../../routing/types.ts";
13
+
14
+ const ROUTE_EVIDENCE_PREFIX = "ROUTE_EVIDENCE:";
15
+
16
+ function isRouteOutcome(value: unknown): value is RouteEvidence["outcome"] {
17
+ return typeof value === "string" && ROUTE_OUTCOMES.includes(value as RouteEvidence["outcome"]);
18
+ }
19
+
20
+ function isRouteVerification(value: unknown): value is NonNullable<RouteEvidence["verification"]> {
21
+ return typeof value === "string" && ROUTE_VERIFICATIONS.includes(value as NonNullable<RouteEvidence["verification"]>);
22
+ }
23
+
24
+ function isRouteAction(value: unknown): value is NonNullable<RouteEvidence["action"]> {
25
+ return typeof value === "string" && ROUTE_ACTIONS.includes(value as NonNullable<RouteEvidence["action"]>);
26
+ }
27
+
28
+ function isRouteWork(value: unknown): value is NonNullable<RouteEvidence["work"]> {
29
+ return typeof value === "string" && ROUTE_WORK_TYPES.includes(value as NonNullable<RouteEvidence["work"]>);
30
+ }
31
+
32
+ function parseRouteEvidence(output: string): RouteEvidence | null {
33
+ const evidenceLine = output
34
+ .split(/\r?\n/)
35
+ .map((line) => line.trim())
36
+ .find((line) => line.startsWith(ROUTE_EVIDENCE_PREFIX));
37
+
38
+ if (!evidenceLine) return null;
39
+
40
+ const raw = evidenceLine.slice(ROUTE_EVIDENCE_PREFIX.length).trim();
41
+ if (!raw) return null;
42
+
43
+ try {
44
+ const parsed = JSON.parse(raw) as Partial<RouteEvidence>;
45
+ if (!isRouteOutcome(parsed.outcome)) return null;
46
+ if (parsed.verification !== undefined && !isRouteVerification(parsed.verification)) return null;
47
+ if (parsed.action !== undefined && !isRouteAction(parsed.action)) return null;
48
+ if (parsed.work !== undefined && !isRouteWork(parsed.work)) return null;
49
+ if (parsed.target !== undefined && typeof parsed.target !== "string") return null;
50
+ if (parsed.reason !== undefined && typeof parsed.reason !== "string") return null;
51
+
52
+ return {
53
+ outcome: parsed.outcome,
54
+ ...(parsed.verification ? { verification: parsed.verification } : {}),
55
+ ...(parsed.action ? { action: parsed.action } : {}),
56
+ ...(parsed.work ? { work: parsed.work } : {}),
57
+ ...(parsed.target ? { target: parsed.target } : {}),
58
+ ...(parsed.reason ? { reason: parsed.reason } : {}),
59
+ };
60
+ } catch {
61
+ return null;
62
+ }
63
+ }
64
+
65
+ export const dynamicRouteHook: PostToolUseHook = {
66
+ metadata: {
67
+ name: "dynamic-route",
68
+ priority: 20,
69
+ phase: HookPhase.LATE,
70
+ dependencies: [],
71
+ errorHandling: "isolate",
72
+ },
73
+
74
+ async execute(context: HookContext, output: string) {
75
+ const evidence = parseRouteEvidence(output);
76
+ const skillsDir = typeof context._routingSkillsDir === "string" ? context._routingSkillsDir : undefined;
77
+
78
+ if (!evidence || !skillsDir || !context.agent) {
79
+ return { result: HookResult.CONTINUE };
80
+ }
81
+
82
+ const skillFilePath = path.join(skillsDir, context.agent, "SKILL.md");
83
+ const frontmatter = readSkillFrontmatter(skillFilePath);
84
+ if (!frontmatter) {
85
+ return { result: HookResult.CONTINUE };
86
+ }
87
+
88
+ const resolution = resolveRoute(frontmatter.route, evidence);
89
+ const guidance = `${ROUTE_GUIDANCE_PREFIX} ${JSON.stringify(resolution)}`;
90
+ const modifiedOutput = output.includes(ROUTE_GUIDANCE_PREFIX)
91
+ ? output
92
+ : `${output.trimEnd()}\n${guidance}`.trim();
93
+
94
+ return {
95
+ result: HookResult.INJECT,
96
+ modifiedOutput,
97
+ };
98
+ },
99
+ };
@@ -0,0 +1,24 @@
1
+ import { HookPhase, HookResult } from "../types.ts";
2
+ import type { HookContext, RouteHook } from "../types.ts";
3
+
4
+ export const nextRouteHook: RouteHook = {
5
+ metadata: {
6
+ name: "next-route",
7
+ priority: 90,
8
+ phase: HookPhase.EARLY,
9
+ dependencies: [],
10
+ errorHandling: "isolate",
11
+ },
12
+
13
+ async execute(context: HookContext, route: string) {
14
+ const nextRoute = context._nextRoute?.selected;
15
+ if (!nextRoute || nextRoute === route) {
16
+ return { result: HookResult.CONTINUE, modifiedRoute: route };
17
+ }
18
+
19
+ return {
20
+ result: HookResult.CONTINUE,
21
+ modifiedRoute: nextRoute,
22
+ };
23
+ },
24
+ };
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { HookPhase, HookResult } from "../types.ts";
9
9
  import type { HookContext, PreToolUseHook } from "../types.ts";
10
- import { findLatestPlanFile } from "../../../../bootstrap.ts";
10
+ import { resolvePlanAccess } from "../../plans/plan-location.ts";
11
11
 
12
12
  export const planCheckHook: PreToolUseHook = {
13
13
  metadata: {
@@ -19,7 +19,7 @@ export const planCheckHook: PreToolUseHook = {
19
19
  },
20
20
 
21
21
  async execute(context: HookContext) {
22
- const planFile = findLatestPlanFile(context.directory);
22
+ const planFile = resolvePlanAccess(context.directory)?.path ?? null;
23
23
 
24
24
  if (!planFile) {
25
25
  return {
@@ -2,12 +2,17 @@
2
2
  // RouteTrackingHook — RouteHook, priority=55, phase=LATE
3
3
  //
4
4
  // Loop guard — mechanically enforce two limits:
5
- // 1. Same skill visited 5+ times in one chain
6
- // 2. 8+ consecutive unproductive hops
5
+ // 1. Same skill visited N+ times in one chain (default 5)
6
+ // 2. N+ consecutive unproductive hops (default 8)
7
+ //
8
+ // Config from _guardConfig (centralized) with fallback to _routeTrackingConfig
9
+ // for backward compatibility. Progressive warning at thresholds before hard stop.
7
10
  // ---------------------------------------------------------------------------
8
11
 
9
12
  import { HookPhase, HookResult } from "../types.ts";
10
13
  import type { HookContext, RouteHook } from "../types.ts";
14
+ import type { GuardConfig, GuardProgression } from "../../guards/guard-config.ts";
15
+ import { checkGuardProgression, DEFAULT_GUARD_CONFIG } from "../../guards/guard-config.ts";
11
16
 
12
17
  // ---------------------------------------------------------------------------
13
18
  // Types
@@ -55,6 +60,53 @@ export function getHopHistory(sessionId: string): HopRecord[] {
55
60
 
56
61
  const defaultArtifactCheck: (route: string) => boolean = () => false;
57
62
 
63
+ // ---------------------------------------------------------------------------
64
+ // Resolve max values from guard config with fallbacks
65
+ // ---------------------------------------------------------------------------
66
+
67
+ function resolveMaxValues(context: HookContext): {
68
+ maxSkillRepeats: number;
69
+ maxUnproductiveHops: number;
70
+ artifactCheck: (route: string) => boolean | Promise<boolean>;
71
+ } {
72
+ // Primary: _guardConfig (centralized)
73
+ const gc: GuardConfig = context._guardConfig ?? DEFAULT_GUARD_CONFIG;
74
+ const maxSkillRepeats = gc.maxSkillRepeats;
75
+ const maxUnproductiveHops = gc.maxUnproductiveHops;
76
+
77
+ // Backward compat: _routeTrackingConfig overrides if present
78
+ const legacy = context._routeTrackingConfig as Partial<RouteTrackingConfig> | undefined;
79
+ const artifactCheck = legacy?.artifactCheck ?? defaultArtifactCheck;
80
+ const legacySkillRepeats = legacy?.maxSkillRepeats;
81
+ const legacyUnproductiveHops = legacy?.maxUnproductiveHops;
82
+
83
+ return {
84
+ maxSkillRepeats: legacySkillRepeats ?? maxSkillRepeats,
85
+ maxUnproductiveHops: legacyUnproductiveHops ?? maxUnproductiveHops,
86
+ artifactCheck,
87
+ };
88
+ }
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // Build optiRoute report helper
92
+ // ---------------------------------------------------------------------------
93
+
94
+ function buildOptiRouteReport(
95
+ state: RouteTrackingState,
96
+ reason: string,
97
+ maxSkillRepeats: number,
98
+ maxUnproductiveHops: number,
99
+ ) {
100
+ return {
101
+ reason,
102
+ chain: [...state.hops],
103
+ skillCounts: Object.fromEntries(state.skillCounts),
104
+ unproductiveCount: state.unproductiveCount,
105
+ maxSkillRepeats,
106
+ maxUnproductiveHops,
107
+ };
108
+ }
109
+
58
110
  // ---------------------------------------------------------------------------
59
111
  // Hook
60
112
  // ---------------------------------------------------------------------------
@@ -88,13 +140,9 @@ export const routeTrackingHook: RouteHook = {
88
140
  sessionStates.set(sessionId, state);
89
141
  }
90
142
 
91
- // Read config from context (or use defaults)
92
- // Support both `_routeTrackingConfig` and `hooks.route_tracking.*` conventions
93
- const config =
94
- (context._routeTrackingConfig ?? {}) as RouteTrackingConfig;
95
- const maxSkillRepeats = config.maxSkillRepeats ?? 5;
96
- const maxUnproductiveHops = config.maxUnproductiveHops ?? 8;
97
- const artifactCheck = config.artifactCheck ?? defaultArtifactCheck;
143
+ // Resolve config values
144
+ const { maxSkillRepeats, maxUnproductiveHops, artifactCheck } = resolveMaxValues(context);
145
+ const gc: GuardConfig = context._guardConfig ?? DEFAULT_GUARD_CONFIG;
98
146
 
99
147
  // Record the hop
100
148
  const producedArtifact = await artifactCheck(route);
@@ -116,29 +164,35 @@ export const routeTrackingHook: RouteHook = {
116
164
  state.unproductiveCount += 1;
117
165
  }
118
166
 
119
- // Check 1: Same skill repeated too many times
120
- if (currentSkillCount >= maxSkillRepeats) {
121
- context._optiRoute = {
122
- reason: `Same skill "${route}" visited ${currentSkillCount} times (max ${maxSkillRepeats})`,
123
- chain: [...state.hops],
124
- skillCounts: Object.fromEntries(state.skillCounts),
125
- unproductiveCount: state.unproductiveCount,
167
+ // Check 1: Same skill repeated too many times — with progressive warning
168
+ let progression = checkGuardProgression(currentSkillCount, maxSkillRepeats, gc);
169
+ if (progression.level === "warn" || progression.level === "escalate") {
170
+ // Progressive warning annotate context but don't stop
171
+ context._guardProgression = progression;
172
+ }
173
+ if (progression.level === "stop") {
174
+ context._optiRoute = buildOptiRouteReport(
175
+ state,
176
+ `Same skill "${route}" visited ${currentSkillCount} times (max ${maxSkillRepeats})`,
126
177
  maxSkillRepeats,
127
178
  maxUnproductiveHops,
128
- };
179
+ );
129
180
  return { result: HookResult.STOP };
130
181
  }
131
182
 
132
- // Check 2: Too many consecutive unproductive hops
133
- if (state.unproductiveCount >= maxUnproductiveHops) {
134
- context._optiRoute = {
135
- reason: `${state.unproductiveCount} consecutive unproductive hops (max ${maxUnproductiveHops})`,
136
- chain: [...state.hops],
137
- skillCounts: Object.fromEntries(state.skillCounts),
138
- unproductiveCount: state.unproductiveCount,
183
+ // Check 2: Too many consecutive unproductive hops — with progressive warning
184
+ progression = checkGuardProgression(state.unproductiveCount, maxUnproductiveHops, gc);
185
+ if (progression.level === "warn" || progression.level === "escalate") {
186
+ // Progressive warning annotate context but don't stop
187
+ context._guardProgression = progression;
188
+ }
189
+ if (progression.level === "stop") {
190
+ context._optiRoute = buildOptiRouteReport(
191
+ state,
192
+ `${state.unproductiveCount} consecutive unproductive hops (max ${maxUnproductiveHops})`,
139
193
  maxSkillRepeats,
140
194
  maxUnproductiveHops,
141
- };
195
+ );
142
196
  return { result: HookResult.STOP };
143
197
  }
144
198