harness-everything 0.8.0 → 0.9.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 (102) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.github/workflows/ci.yml +12 -2
  4. package/AGENTS.md +36 -35
  5. package/CHANGELOG.md +51 -0
  6. package/README.md +2 -2
  7. package/benchmarks/results/reviews/pr142-verification.json +95 -0
  8. package/ci/action-gate.test.js +83 -3
  9. package/ci/doc-capability-consistency.test.js +3 -0
  10. package/ci/invariant-routing.test.js +87 -74
  11. package/ci/mechanism-2af-worktree-isolation.test.js +152 -0
  12. package/ci/mechanism-2ag-router-worktree-contract.test.js +44 -0
  13. package/ci/mechanism-2ah-workflow-lifecycle.test.js +140 -0
  14. package/ci/openai-plugin-package.test.js +9 -6
  15. package/ci/phase4-ensemble-memory.test.js +128 -10
  16. package/ci/router-workflow-plan.test.js +23 -0
  17. package/ci/workflow-execution-contract.test.js +133 -0
  18. package/ci/workflow-plan-consumers.test.js +18 -0
  19. package/docs/architecture.md +111 -114
  20. package/docs/mechanism-first-skill-mesh.md +92 -103
  21. package/docs/openai-plugin.md +1 -1
  22. package/docs/philosophy.md +40 -33
  23. package/docs/platform-capabilities.md +2 -2
  24. package/docs/reviews/pr-142-issue-131.md +89 -0
  25. package/docs/routing.md +61 -124
  26. package/docs/workflow-runtime.md +109 -0
  27. package/fable-mode/CONTRACT-FORMAT.md +3 -1
  28. package/fable-mode/SKILL.md +1 -1
  29. package/fable-mode/agents/fable-orchestrator.md +9 -7
  30. package/fable-mode/execution-guardrails/SKILL.md +1 -1
  31. package/fable-mode/fable-haiku/SKILL.md +1 -1
  32. package/fable-mode/fable-opus/SKILL.md +1 -1
  33. package/fable-mode/fable-sonnet/SKILL.md +1 -1
  34. package/fable-mode/scripts/workflow-plan-consumer.js +13 -8
  35. package/harness-everything/SKILL.md +11 -10
  36. package/harness-everything/references/triage-and-tiers.md +76 -107
  37. package/harness-everything/schemas/router-task-shape.schema.json +4 -2
  38. package/harness-everything/schemas/router-workflow-plan.schema.json +3 -2
  39. package/harness-everything/scripts/kernel-router-core.js +265 -0
  40. package/harness-everything/scripts/kernel-router.js +118 -223
  41. package/harness-everything/scripts/router-contract-core.js +633 -0
  42. package/harness-everything/scripts/router-contract.js +21 -581
  43. package/harness-everything/scripts/tier-router.js +19 -1
  44. package/hooks/hooks.json +25 -4
  45. package/hooks/scripts/action-gate.js +111 -2
  46. package/hooks/scripts/contract-test.js +27 -2
  47. package/hooks/scripts/lib/workflow-isolation.js +102 -0
  48. package/hooks/scripts/lib/workflow-runtime.js +254 -0
  49. package/hooks/scripts/state-persist.js +1 -0
  50. package/hooks/scripts/subagent-scope-guard.js +20 -3
  51. package/hooks/scripts/workflow-disposition.js +104 -0
  52. package/hooks/scripts/workflow-gate.js +56 -0
  53. package/hooks/scripts/workflow-stop-gate.js +44 -0
  54. package/multi-agent-workspace/SKILL.md +2 -2
  55. package/multi-agent-workspace/scripts/index_memory.js +107 -1
  56. package/opencode-plugin/plugin.json +1 -1
  57. package/package.json +2 -2
  58. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  59. package/plugins/harness-everything/hooks/hooks.json +44 -1
  60. package/plugins/harness-everything/hooks/scripts/action-gate.js +111 -2
  61. package/plugins/harness-everything/hooks/scripts/contract-test.js +27 -2
  62. package/plugins/harness-everything/hooks/scripts/lib/workflow-isolation.js +102 -0
  63. package/plugins/harness-everything/hooks/scripts/lib/workflow-runtime.js +254 -0
  64. package/plugins/harness-everything/hooks/scripts/state-persist.js +1 -0
  65. package/plugins/harness-everything/hooks/scripts/subagent-scope-guard.js +20 -3
  66. package/plugins/harness-everything/hooks/scripts/workflow-disposition.js +104 -0
  67. package/plugins/harness-everything/hooks/scripts/workflow-gate.js +56 -0
  68. package/plugins/harness-everything/hooks/scripts/workflow-stop-gate.js +44 -0
  69. package/plugins/harness-everything/plugin.json +1 -1
  70. package/plugins/harness-everything/scripts/lib/advisory-text.js +51 -54
  71. package/plugins/harness-everything/skills/fable-mode/CONTRACT-FORMAT.md +3 -1
  72. package/plugins/harness-everything/skills/fable-mode/SKILL.md +1 -1
  73. package/plugins/harness-everything/skills/fable-mode/agents/fable-orchestrator.md +9 -7
  74. package/plugins/harness-everything/skills/fable-mode/execution-guardrails/SKILL.md +1 -1
  75. package/plugins/harness-everything/skills/fable-mode/fable-haiku/SKILL.md +1 -1
  76. package/plugins/harness-everything/skills/fable-mode/fable-opus/SKILL.md +1 -1
  77. package/plugins/harness-everything/skills/fable-mode/fable-sonnet/SKILL.md +1 -1
  78. package/plugins/harness-everything/skills/fable-mode/scripts/workflow-plan-consumer.js +13 -8
  79. package/plugins/harness-everything/skills/harness-everything/SKILL.md +11 -10
  80. package/plugins/harness-everything/skills/harness-everything/references/triage-and-tiers.md +76 -107
  81. package/plugins/harness-everything/skills/harness-everything/schemas/router-task-shape.schema.json +4 -2
  82. package/plugins/harness-everything/skills/harness-everything/schemas/router-workflow-plan.schema.json +3 -2
  83. package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router-core.js +265 -0
  84. package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router.js +118 -223
  85. package/plugins/harness-everything/skills/harness-everything/scripts/router-contract-core.js +633 -0
  86. package/plugins/harness-everything/skills/harness-everything/scripts/router-contract.js +21 -581
  87. package/plugins/harness-everything/skills/harness-everything/scripts/tier-router.js +19 -1
  88. package/plugins/harness-everything/skills/multi-agent-workspace/SKILL.md +2 -2
  89. package/plugins/harness-everything/skills/multi-agent-workspace/scripts/index_memory.js +107 -1
  90. package/plugins/harness-everything/skills/self-evolve/SKILL.md +6 -6
  91. package/plugins/harness-everything/skills/self-evolve/references/memory-resolution.md +22 -33
  92. package/plugins/harness-everything/skills/self-evolve/scripts/persist-memory.js +336 -57
  93. package/plugins/harness-everything/skills/self-evolve/scripts/self-regression.js +1 -0
  94. package/plugins/harness-everything/skills/using-git-worktrees/SKILL.md +14 -16
  95. package/plugins/harness-everything/skills/using-git-worktrees/references/workflow-details.md +44 -85
  96. package/scripts/lib/advisory-text.js +51 -54
  97. package/self-evolve/SKILL.md +6 -6
  98. package/self-evolve/references/memory-resolution.md +22 -33
  99. package/self-evolve/scripts/persist-memory.js +336 -57
  100. package/self-evolve/scripts/self-regression.js +1 -0
  101. package/using-git-worktrees/SKILL.md +14 -16
  102. package/using-git-worktrees/references/workflow-details.md +44 -85
@@ -1,252 +1,147 @@
1
1
  #!/usr/bin/env node
2
+ 'use strict';
3
+
2
4
  const crypto = require('crypto');
3
5
  const fs = require('fs');
4
- const os = require('os');
5
6
  const path = require('path');
6
- const { spawnSync } = require('child_process');
7
- const {
8
- createDegradedRouterContract,
9
- validateRouterContract,
10
- } = require('./router-contract');
11
- const { applyEnsemblePolicy } = require('./ensemble-policy');
12
-
13
- /**
14
- * Harness Kernel Router
15
- *
16
- * tier-router remains the human-readable classifier/guide detector. The
17
- * kernel consumes its versioned side-channel JSON contract instead of
18
- * scraping human-readable stdout. Phase 2 selects an execution topology, but
19
- * the router still only emits a plan: executors retain responsibility for
20
- * spawning agents, creating workspaces, running tools, and enforcing gates.
21
- * Phase 4 attaches ensemble-review only as a post-selection modifier.
22
- */
23
-
24
- const INVARIANT_TEXT = {
25
- 'scope-lock': 'Route before execution: stay inside the authorized task/repository scope.',
26
- 'verify-before-claim': 'Verify before claim: completion claims require objective evidence appropriate to the change.',
27
- 'replan-after-repeated-failure': 'Re-plan on repetition: after 3 same-signature failures, stop micro-retrying and zoom out/re-diagnose.',
28
- 'evaluate-suggestions-before-skip': 'Evaluate before skip: read each suggested skill\'s complete SKILL.md entry/basic flow before omitting it.',
29
- 'loop-budget': 'Stop iterative-single when its explicit iteration budget is exhausted.',
30
- 'objective-verification': 'Use an objective check for iterative work; self-critique alone is not verification.',
31
- 'stage-contracts': 'Fable stages must have explicit stage contracts and pass conditions.',
32
- 'cold-verification': 'Fable delivery requires a cold/independent verifier where specified.',
33
- 'parallel-scope-contract': 'Parallel work requires declared independent scopes and non-overlapping/read-only writes.',
34
- 'synthesis-barrier': 'Do not claim parallel completion until all workstreams reach the synthesis barrier.',
35
- 'handoff-contracts': 'Multi-agent workspace work uses orchestrator-owned handoff contracts; workers do not form a peer mesh.',
36
- 'workspace-state': 'Durable workspace state remains scoped to the workspace and existing state conventions.',
37
- 'pre-action-approval': 'Irreversible/external side effects require approval before the exact payload executes.',
38
- 'preserve-disagreement': 'Ensemble synthesis must retain unresolved minority positions and evidence gaps.',
39
- 'independent-ensemble-verifier': 'Ensemble delivery requires a verifier independent from the candidate identities; agreement alone is not proof.',
40
- };
41
-
42
- const SKILL_TEXT = {
43
- 'tdd': 'tdd: useful for behavioral changes where executable tests can drive the implementation.',
44
- 'verification-loop': 'verification-loop: useful for systematic build/lint/test/diff evidence before delivery.',
45
- 'fable-mode': 'fable-mode / fable-discipline: useful for macro planning or deliberate multi-agent decomposition.',
46
- 'fable-discipline': null,
47
- 'multi-agent-workspace': 'multi-agent-workspace: useful when durable bounded delegation, handoffs, or workspace memory are required.',
48
- };
7
+ const core = require('./kernel-router-core');
49
8
 
50
- function sanitizeClassifierOutput(stdout) {
51
- const lines = String(stdout || '').split(/\r?\n/);
52
- const filtered = [];
53
- let skipLegacyContinuation = false;
54
-
55
- for (const line of lines) {
56
- if (line.includes('=> BASE EXECUTION LOOP:')) {
57
- skipLegacyContinuation = true;
58
- continue;
59
- }
60
- if (skipLegacyContinuation && line.includes('Track exactly ONE item in-progress')) {
61
- skipLegacyContinuation = false;
62
- continue;
63
- }
64
- skipLegacyContinuation = false;
65
- filtered.push(line);
9
+ function loadRuntime() {
10
+ for (const prefix of ['../..', '../../..']) {
11
+ const candidate = path.resolve(__dirname, prefix, 'hooks/scripts/lib/workflow-runtime.js');
12
+ if (fs.existsSync(candidate)) return { runtime: require(candidate), hooksRoot: path.dirname(path.dirname(candidate)) };
66
13
  }
67
-
68
- return filtered.join('\n').replace(/\n{3,}/g, '\n\n').trimEnd();
14
+ throw new Error('workflow runtime unavailable in this installation');
69
15
  }
70
16
 
71
- function readStructuredContract(contractPath) {
72
- try {
73
- const parsed = JSON.parse(fs.readFileSync(contractPath, 'utf8'));
74
- const validation = validateRouterContract(parsed);
75
- if (!validation.valid) {
76
- return createDegradedRouterContract(
77
- 'router-contract-invalid',
78
- `Structured router contract failed validation: ${validation.errors.join('; ')}`,
79
- );
80
- }
81
- return parsed;
82
- } catch (err) {
83
- return createDegradedRouterContract(
84
- 'router-contract-unavailable',
85
- `Structured router contract unavailable: ${err.message}`,
86
- );
17
+ function issueMemoryCapability(workflow) {
18
+ if (!workflow || !workflow.workflowPlan || workflow.workflowPlan.memory?.write === 'none') {
19
+ if (workflow) delete workflow.memoryAuthorization;
20
+ return null;
87
21
  }
22
+ const token = crypto.randomBytes(24).toString('base64url');
23
+ workflow.memoryAuthorization = {
24
+ schemaVersion: 1,
25
+ capabilityHash: crypto.createHash('sha256').update(token, 'utf8').digest('hex'),
26
+ writerRole: 'coordinator',
27
+ sessionId: workflow.sessionId,
28
+ workflowId: workflow.workflowId,
29
+ writeDisposition: workflow.workflowPlan.memory.write,
30
+ issuedAt: new Date().toISOString(),
31
+ usedAt: null,
32
+ };
33
+ return token;
88
34
  }
89
35
 
90
- function resolvePrompt(prompt, stdinPayload) {
91
- if (prompt) return prompt;
92
- if (!stdinPayload) return '';
93
- try {
94
- const payload = JSON.parse(stdinPayload);
95
- return typeof payload.prompt === 'string' ? payload.prompt : '';
96
- } catch (_) {
97
- return '';
36
+ function persistWorkflow(plan, payload, prompt) {
37
+ if (!payload || !(payload.session_id || payload.sessionId)) return null;
38
+ const { runtime, hooksRoot } = loadRuntime();
39
+ const context = runtime.loadWorkflow(payload);
40
+ const { writeCurrentSession } = require(path.join(hooksRoot, 'lib/harness-state'));
41
+ writeCurrentSession(context.root, context.sessionId);
42
+ if (context.workflow && runtime.OPEN_STATES.has(context.workflow.state)) {
43
+ if (!context.workflow.workflowPlan) throw new Error('legacy unresolved workflow needs explicit migration; refusing to replace its obligations');
44
+ // Steering/status prompts cannot drop an unresolved execution contract.
45
+ // Stronger new work is retained as a pending route until explicit replan.
46
+ if (runtime.isMajorWorkflow(plan) && !runtime.isMajorWorkflow(context.workflow)) {
47
+ context.workflow.state = 'blocked';
48
+ context.workflow.pendingPlan = plan;
49
+ context.workflow.blockReason = 'stronger-route-requires-replan';
50
+ context.workflow.mutationIsolation = plan.mutationIsolation;
51
+ }
52
+ context.workflow.lastPromptHash = crypto.createHash('sha256').update(prompt).digest('hex').slice(0, 24);
53
+ const memoryCapability = issueMemoryCapability(context.workflow);
54
+ runtime.saveWorkflow(context);
55
+ return { ...context, hooksRoot, retained: true, memoryCapability };
98
56
  }
99
- }
100
-
101
- function printWorkflowPlan(plan) {
102
- const visible = {
103
- schemaVersion: plan.schemaVersion,
104
- routingStatus: plan.routingStatus,
57
+ const selected = plan.strategySelection === 'selected' && Boolean(plan.strategy);
58
+ const now = Date.now();
59
+ context.workflow = {
60
+ schemaVersion: 2,
61
+ workflowId: crypto.randomUUID(),
62
+ createdAt: new Date(now).toISOString(),
63
+ createdAtMs: now,
64
+ sessionId: context.sessionId,
65
+ promptHash: crypto.createHash('sha256').update(prompt).digest('hex').slice(0, 24),
105
66
  tier: plan.tier,
106
67
  strategy: plan.strategy,
107
68
  strategySelection: plan.strategySelection,
108
- actionGate: plan.actionGate,
109
- limits: plan.limits,
110
- parallelism: plan.parallelism,
111
- workspace: plan.workspace,
112
- memory: plan.memory,
69
+ requiredInvariants: plan.requiredInvariants || [],
70
+ suggestedSkills: plan.suggestedSkills || [],
113
71
  verification: plan.verification,
114
- ensemble: plan.ensemble,
115
- fallback: plan.fallback,
116
- reasonCodes: plan.reasonCodes,
72
+ mutationIsolation: plan.mutationIsolation || { required: false },
73
+ workflowPlan: plan,
74
+ state: plan.fallback?.disposition === 'blocked' ? 'blocked' : selected ? 'pending' : 'deferred',
75
+ runId: null,
76
+ revision: 0,
77
+ escapes: [],
78
+ enforcement: {
79
+ mode: 'mandatory-applicable-workflow',
80
+ escapePolicy: 'scoped-evidence-only',
81
+ reasoningPolicy: 'model-controls-how',
82
+ },
117
83
  };
118
- console.log(`\n=> ROUTER WORKFLOW PLAN (JSON): ${JSON.stringify(visible)}`);
84
+ const memoryCapability = issueMemoryCapability(context.workflow);
85
+ runtime.saveWorkflow(context);
86
+ return { ...context, hooksRoot, retained: false, memoryCapability };
119
87
  }
120
88
 
121
- function uniqueSuggestedSkills(plan) {
122
- if (!Array.isArray(plan.suggestedSkills)) return [];
123
- return [...new Set(plan.suggestedSkills.filter(skill => typeof skill === 'string' && skill.trim()))];
124
- }
125
-
126
- function enforceSuggestionEvaluation(plan) {
127
- if (uniqueSuggestedSkills(plan).length === 0) return;
128
- if (!Array.isArray(plan.requiredInvariants)) plan.requiredInvariants = [];
129
- if (!plan.requiredInvariants.includes('evaluate-suggestions-before-skip')) {
130
- plan.requiredInvariants.push('evaluate-suggestions-before-skip');
89
+ function run(raw) {
90
+ const promptArg = process.argv.slice(2).join(' ');
91
+ let payload = null;
92
+ if (raw.trim()) {
93
+ try { payload = JSON.parse(raw); } catch (_) { console.error('[Workflow Router] Invalid hook payload; lifecycle persistence unavailable.'); }
131
94
  }
132
- }
133
-
134
- function displayTier(tier) {
135
- if (tier === 'tier1') return 'Tier 1';
136
- if (tier === 'tier2') return 'Tier 2';
137
- if (tier === 'tier3') return 'Tier 3';
138
- return tier || 'unclassified';
139
- }
140
-
141
- function printRoutingCheckpoint(plan) {
142
- const invariants = Array.isArray(plan.requiredInvariants) ? plan.requiredInvariants : [];
143
- const suggestions = uniqueSuggestedSkills(plan);
144
-
145
- console.log('\n=> HARNESS ROUTING CHECKPOINT (REQUIRED VISIBLE STATE):');
146
- console.log(` - Tier: ${displayTier(plan.tier)}`);
147
- console.log(` - Strategy: ${plan.strategy || 'deferred'}`);
148
- console.log(` - Required invariants: ${invariants.length ? invariants.join(', ') : 'none'}`);
149
- console.log(` - Suggested skills: ${suggestions.length ? suggestions.join(', ') : 'none'}`);
150
- if (suggestions.length > 0) {
151
- console.log(' - Suggestion evaluation: MANDATORY. Before skipping any listed skill, read its complete SKILL.md entry and evaluate USE FOR, DO NOT USE FOR, workflow/basic flow, and hard rules.');
152
- console.log(' - Skip evidence: do not reject from only the skill name, description, router summary, or a generic "routine/common task" judgement. If the entry cannot be resolved/read, mark it unresolved/unavailable rather than skipped.');
153
- console.log(' - Suggestion disposition: execution is advisory after evaluation. Using one suggestion does not waive read-before-skip for other omitted suggestions. If all are skipped after evaluation, state one brief reason grounded in the evaluated flows in the first visible progress/update message.');
154
- }
155
- }
156
-
157
- function printKernelContract(plan) {
158
- console.log('\n=> REQUIRED HARNESS INVARIANTS:');
159
- for (const invariant of plan.requiredInvariants || []) {
160
- console.log(` - ${invariant}: ${INVARIANT_TEXT[invariant] || 'Required by the selected workflow plan.'}`);
95
+ const prompt = promptArg || (typeof payload?.prompt === 'string' ? payload.prompt : '');
96
+ const result = core.route(promptArg, raw || null);
97
+ let plan = result.contract.workflowPlan;
98
+ let persisted = null;
99
+ let failure = null;
100
+ try {
101
+ persisted = persistWorkflow(plan, payload, prompt);
102
+ if (persisted?.retained) plan = persisted.workflow.workflowPlan;
103
+ } catch (error) { failure = error; }
104
+ if (result.sanitized) console.log(result.sanitized);
105
+ if (result.stderr) process.stderr.write(result.stderr);
106
+ if (persisted?.retained) console.log('\n=> Retaining unresolved workflow from earlier prompts; the checkpoint below is the active execution contract.');
107
+ core.printWorkflowPlan(plan);
108
+ core.printRoutingCheckpoint(plan);
109
+ core.printKernelContract(plan);
110
+ console.log('\n=> WORKFLOW EXECUTION CONTRACT (MANDATORY WHEN SELECTED):');
111
+ console.log(' - State: ' + (persisted?.workflow.state || (plan.strategy ? 'unpersisted' : 'deferred')));
112
+ console.log(' - Selected workflow: ' + (plan.strategy || 'deferred'));
113
+ if (plan.strategySelection === 'selected') {
114
+ console.log(' - Execute the selected workflow to resolution. Reasoning and implementation remain flexible inside it.');
115
+ console.log(' - Completion requires the workflow obligations and objective verification to resolve.');
116
+ console.log(' - Generic simple/routine/already-clear reasons cannot waive the selected workflow.');
117
+ console.log(' - Tier 3 / Fable source and artifact mutations require Git worktree isolation; unavailable isolation means BLOCKED.');
118
+ console.log(' - Escape is limited to workflow-uncovered-scope or host-capability-unavailable, with stage, scope and evidence; other obligations remain mandatory.');
161
119
  }
162
-
163
- console.log('\n=> SUGGESTED SKILLS (MANDATORY EVALUATION — ADVISORY EXECUTION):');
164
- if (Array.isArray(plan.suggestedSkills) && plan.suggestedSkills.length > 0) {
165
- const emitted = new Set();
166
- for (const skill of plan.suggestedSkills) {
167
- if (emitted.has(skill)) continue;
168
- const text = Object.prototype.hasOwnProperty.call(SKILL_TEXT, skill) ? SKILL_TEXT[skill] : `${skill}: advisory for the selected topology.`;
169
- if (text) console.log(` - ${text}`);
170
- emitted.add(skill);
171
- if (skill === 'fable-mode') emitted.add('fable-discipline');
172
- }
173
- console.log(' - Read-before-skip: evaluate every suggested skill entry before omission; adopting one suggestion does not waive evaluation of the others.');
174
- if (plan.strategy && plan.strategy.startsWith('fable-')) {
175
- console.log(' - Use these selectively after evaluation; Tier 3/Fable does not create a universal skill pipeline.');
176
- }
177
- } else if (plan.strategy === 'direct-single') {
178
- console.log(' - No domain skill was suggested. Prefer the bounded direct path and load a focused skill only when it adds value.');
179
- } else if (plan.strategySelection === 'deferred') {
180
- console.log(' - Strategy is deferred/unclassified. Do not infer triviality; choose the smallest justified approach from task evidence.');
120
+ if (persisted) {
121
+ const controller = path.join(persisted.hooksRoot, 'workflow-disposition.js');
122
+ console.log(' - Workflow id: ' + persisted.workflow.workflowId);
123
+ console.log(' - Memory write disposition: ' + (persisted.workflow.workflowPlan.memory?.write || 'none'));
124
+ if (persisted.memoryCapability) console.log(' - Memory capability (single-use, workflow/session-bound): ' + persisted.memoryCapability);
125
+ console.log(' - Stage specification: ' + path.join(persisted.sessionDir, 'workflow-stages.json'));
126
+ console.log(' - Enter/replan: node "' + controller + '" start --session-id "' + persisted.sessionId + '"');
127
+ console.log(' - Escape one declared stage: node "' + controller + '" escape --session-id "' + persisted.sessionId + '" --stage-id "<id>" --reason-code workflow-uncovered-scope --scope "<uncovered scope>" --evidence "<evidence>"');
181
128
  } else {
182
- console.log(' - No additional skill suggestion from the workflow plan.');
183
- }
184
-
185
- if (plan.ensemble) {
186
- console.log(`\n=> ENSEMBLE REVIEW: bounded to ${plan.ensemble.maxCandidates} candidates; synthesis=${plan.ensemble.synthesis}; verifier=${plan.ensemble.verifier}. Preserve minority positions and do not claim improvement without paired evidence.`);
187
- }
188
-
189
- if (plan.actionGate && plan.actionGate.required) {
190
- console.log(`\n=> ACTION GATE REQUIRED: ${plan.actionGate.reasonCodes.join(', ')}; disposition=${plan.actionGate.disposition}. The router only declares this requirement; execution must not bypass approval.`);
191
- }
192
-
193
- if (plan.fallback && plan.fallback.disposition !== 'none') {
194
- console.log(`\n=> ROUTING FALLBACK: ${plan.fallback.disposition}/${plan.fallback.mode}; ${plan.fallback.reasonCodes.join(', ')}.`);
195
- }
196
-
197
- if (plan.routingStatus === 'degraded') {
198
- console.log('\n=> ROUTING DEGRADATION: Structured routing is degraded. Keep the strategy deferred unless independent evidence supports a route; do not silently downgrade to Tier 1/direct execution.');
129
+ console.log(' - Runtime state was not persisted; host enforcement is unavailable/unknown, not proven hard enforcement.');
199
130
  }
200
-
201
- console.log('\n=> ORCHESTRATION POLICY: Do not enforce workflow order. Enforce workflow invariants from the plan. Suggested-skill evaluation is mandatory; execution remains advisory after evaluation. The router plans; execution components execute.');
202
- }
203
-
204
- function run(prompt, stdinPayload) {
205
- const classifierPath = path.join(__dirname, 'tier-router.js');
206
- const contractPath = path.join(os.tmpdir(), `harness-router-${process.pid}-${crypto.randomUUID()}.json`);
207
- const args = prompt ? [classifierPath, prompt] : [classifierPath];
208
- const result = spawnSync(process.execPath, args, {
209
- input: prompt ? undefined : stdinPayload,
210
- encoding: 'utf8',
211
- env: {
212
- ...process.env,
213
- HARNESS_ROUTER_CONTRACT_PATH: contractPath,
214
- },
215
- });
216
-
217
- if (result.error) {
218
- console.error(`[Harness Kernel] Failed to execute tier-router.js: ${result.error.message}`);
219
- process.exit(1);
220
- }
221
-
222
- const sanitized = sanitizeClassifierOutput(result.stdout);
223
- if (sanitized) console.log(sanitized);
224
- if (result.stderr) process.stderr.write(result.stderr);
225
-
226
- const contract = readStructuredContract(contractPath);
227
- applyEnsemblePolicy(contract, resolvePrompt(prompt, stdinPayload));
228
- enforceSuggestionEvaluation(contract.workflowPlan);
229
- try {
230
- fs.rmSync(contractPath, { force: true });
231
- } catch (err) {
232
- // Temporary contract cleanup is best effort only.
233
- }
234
-
235
- printWorkflowPlan(contract.workflowPlan);
236
- printRoutingCheckpoint(contract.workflowPlan);
237
- printKernelContract(contract.workflowPlan);
238
-
239
- if (result.status !== 0) process.exit(result.status === null ? 1 : result.status);
131
+ if (failure) {
132
+ console.error('[Workflow Router] ' + failure.message);
133
+ process.exitCode = 2;
134
+ } else if (result.status !== 0) process.exitCode = result.status || 1;
240
135
  }
241
136
 
242
- const prompt = process.argv.slice(2).join(' ');
243
- if (prompt) {
244
- run(prompt, null);
245
- } else if (process.stdin.isTTY) {
246
- run('', null);
247
- } else {
137
+ if (process.argv.length > 2 || process.stdin.isTTY) run('');
138
+ else {
248
139
  let input = '';
140
+ let finished = false;
141
+ const finish = () => { if (finished) return; finished = true; clearTimeout(timeout); run(input); };
142
+ const timeout = setTimeout(finish, 500);
249
143
  process.stdin.setEncoding('utf8');
250
144
  process.stdin.on('data', chunk => { input += chunk; });
251
- process.stdin.on('end', () => run('', input));
145
+ process.stdin.on('end', finish);
146
+ process.stdin.on('error', finish);
252
147
  }