harness-everything 0.30.4 → 0.30.6
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +38 -0
- package/README.md +17 -7
- package/bin/cli.js +5 -0
- package/ci/fable-mode-test.js +112 -51
- package/ci/mechanism-2t-workspace-identity.test.js +3 -1
- package/ci/mechanism-2w-platform-compatibility.test.js +39 -0
- package/ci/mechanism-2z-claude-plugin-manifest.test.js +12 -0
- package/ci/router-workflow-plan.test.js +92 -7
- package/docs/platform-capabilities.md +14 -1
- package/docs/troubleshooting.md +31 -1
- package/docs/workflows/fable-mode.md +10 -9
- package/evals/fable-mode/tasks/negative-tier2-no-model.yaml +3 -3
- package/evals/fable-mode/tasks/positive-haiku-mode.yaml +1 -1
- package/evals/fable-mode/tasks/positive-opus-mode.yaml +1 -1
- package/evals/fable-mode/tasks/positive-sonnet-mode.yaml +1 -1
- package/evals/fable-mode/tasks/positive-trigger.yaml +1 -1
- package/fable-mode/SKILL.md +18 -12
- package/fable-mode/agents/fable-orchestrator.md +7 -6
- package/fable-mode/agents/fable-verifier.md +1 -1
- package/fable-mode/agents/fable-worker-haiku.md +5 -4
- package/fable-mode/agents/fable-worker-sonnet.md +5 -4
- package/fable-mode/behavior-profile-matrix.json +36 -0
- package/fable-mode/execution-guardrails/SKILL.md +3 -6
- package/fable-mode/execution-guardrails/references/guardrail-rules.md +8 -9
- package/fable-mode/fable-haiku/SKILL.md +7 -5
- package/fable-mode/fable-haiku/references/routing-notes.md +2 -1
- package/fable-mode/fable-opus/SKILL.md +8 -6
- package/fable-mode/fable-sonnet/SKILL.md +8 -6
- package/fable-mode/model-matrix.json +7 -30
- package/fable-mode/references/model-matrix.md +35 -30
- package/fable-mode/references/profile-lookup.md +19 -0
- package/fable-mode/runtime-model-floor-matrix.json +47 -0
- package/fable-mode/scripts/model-selector.js +106 -60
- package/fable-mode/scripts/workflow-plan-consumer.js +1 -1
- package/harness-everything/SKILL.md +11 -14
- package/harness-everything/references/router-workflow-plan.md +2 -2
- package/harness-everything/schemas/router-task-shape.schema.json +3 -2
- package/harness-everything/schemas/router-workflow-plan.schema.json +6 -5
- package/harness-everything/scripts/router-contract-core.js +19 -12
- package/harness-everything/scripts/tier-router.js +100 -12
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/plugin.json +1 -1
- package/plugins/harness-everything/skills/fable-mode/SKILL.md +18 -12
- package/plugins/harness-everything/skills/fable-mode/agents/fable-orchestrator.md +7 -6
- package/plugins/harness-everything/skills/fable-mode/agents/fable-verifier.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/agents/fable-worker-haiku.md +5 -4
- package/plugins/harness-everything/skills/fable-mode/agents/fable-worker-sonnet.md +5 -4
- package/plugins/harness-everything/skills/fable-mode/behavior-profile-matrix.json +36 -0
- package/plugins/harness-everything/skills/fable-mode/execution-guardrails/SKILL.md +3 -6
- package/plugins/harness-everything/skills/fable-mode/execution-guardrails/references/guardrail-rules.md +8 -9
- package/plugins/harness-everything/skills/fable-mode/fable-haiku/SKILL.md +7 -5
- package/plugins/harness-everything/skills/fable-mode/fable-haiku/references/routing-notes.md +2 -1
- package/plugins/harness-everything/skills/fable-mode/fable-opus/SKILL.md +8 -6
- package/plugins/harness-everything/skills/fable-mode/fable-sonnet/SKILL.md +8 -6
- package/plugins/harness-everything/skills/fable-mode/model-matrix.json +7 -30
- package/plugins/harness-everything/skills/fable-mode/references/model-matrix.md +35 -30
- package/plugins/harness-everything/skills/fable-mode/references/profile-lookup.md +19 -0
- package/plugins/harness-everything/skills/fable-mode/runtime-model-floor-matrix.json +47 -0
- package/plugins/harness-everything/skills/fable-mode/scripts/model-selector.js +106 -60
- package/plugins/harness-everything/skills/fable-mode/scripts/workflow-plan-consumer.js +1 -1
- package/plugins/harness-everything/skills/harness-everything/SKILL.md +11 -14
- package/plugins/harness-everything/skills/harness-everything/references/router-workflow-plan.md +2 -2
- package/plugins/harness-everything/skills/harness-everything/schemas/router-task-shape.schema.json +3 -2
- package/plugins/harness-everything/skills/harness-everything/schemas/router-workflow-plan.schema.json +6 -5
- package/plugins/harness-everything/skills/harness-everything/scripts/router-contract-core.js +19 -12
- package/plugins/harness-everything/skills/harness-everything/scripts/tier-router.js +100 -12
- package/sbom.cdx.json +7 -7
|
@@ -1,41 +1,46 @@
|
|
|
1
|
-
# Fable
|
|
1
|
+
# Fable behavior profiles and runtime model floors
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
machine-readable gate; this document is the human-facing contract.
|
|
3
|
+
Fable profiles define **how work is performed and which role owns it**. They do not select or require a branded runtime model. The historical names `haiku`, `sonnet`/`sonnect`, and `opus` remain accepted aliases so existing prompts keep working.
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
|
9
|
-
|
|
10
|
-
|
|
|
5
|
+
## Behavior Profile Matrix
|
|
6
|
+
|
|
7
|
+
| Canonical profile | Legacy alias | Role | Typical work | Named agent |
|
|
8
|
+
|---|---|---|---|---|
|
|
9
|
+
| `mechanical` | `haiku` | Mechanical worker | Bulk edits, format conversion, boilerplate, structured extraction | `fable-worker-haiku` |
|
|
10
|
+
| `reasoning` | `sonnet` / `sonnect` | Reasoning worker | Non-trivial implementation, research synthesis, analysis, bounded design | `fable-worker-sonnet` |
|
|
11
|
+
| `orchestrator` | `opus` | Orchestrator | Stage decomposition, dependency management, cross-stage synthesis, architecture/final decisions | `fable-orchestrator` |
|
|
12
|
+
| verifier | — | Cold reviewer | Spec-versus-artifact checks and independent re-verification | `fable-verifier` |
|
|
13
|
+
|
|
14
|
+
The machine-readable source is `fable-mode/behavior-profile-matrix.json`.
|
|
15
|
+
|
|
16
|
+
## Runtime Model Floor Matrix
|
|
11
17
|
|
|
12
|
-
|
|
13
|
-
The accepted input `sonnect` normalizes to Sonnet. A normal Tier 2 prompt does
|
|
14
|
-
not select a fable model.
|
|
18
|
+
Runtime floors are **host-specific recommendations**, not Fable identity and not an execution gate. A newer compatible model/effort may be used. If the active runtime is below or cannot be compared to the floor, Fable continues with the same behavior profile and records the status visibly.
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
| Fable profile | Claude recommended floor | Codex recommended floor |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| `orchestrator` | Opus 5.5+ / medium+ | GPT-6 Sol+ / xhigh+ |
|
|
23
|
+
| `reasoning` | Sonnet 5.5+ / medium+ | GPT-6 Sol+ / medium+ |
|
|
24
|
+
| `mechanical` | Haiku 5.5+ | GPT-6 Luna 6+ / xhigh+ |
|
|
25
|
+
|
|
26
|
+
The machine-readable source is `fable-mode/runtime-model-floor-matrix.json`. These are Harness support baselines, not claims that every host currently exposes every listed model.
|
|
17
27
|
|
|
18
|
-
|
|
19
|
-
mode, the runtime's available modes, the host model, and the stage contract:
|
|
28
|
+
## Resolution
|
|
20
29
|
|
|
21
30
|
```text
|
|
22
|
-
|
|
31
|
+
task
|
|
32
|
+
-> behavior profile
|
|
33
|
+
-> named role / agent
|
|
34
|
+
-> host adapter
|
|
35
|
+
-> advisory runtime floor
|
|
36
|
+
-> actual host-selected model
|
|
37
|
+
-> execute the SAME behavior contract
|
|
23
38
|
```
|
|
24
39
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
`
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
`--audit-file`).
|
|
31
|
-
|
|
32
|
-
If the requested model is unavailable, `--fallback inline` records the declared
|
|
33
|
-
host model and a visible reason. `--fallback stop` emits `status: blocked`, a
|
|
34
|
-
null effective model, and exit code 2. Neither path silently downgrades.
|
|
35
|
-
|
|
36
|
-
The orchestrator still owns scope lock, at most two full replans, stage
|
|
37
|
-
contracts, worker non-recursion, cold verification, and escalation. The native
|
|
38
|
-
host TODO tracker or a Markdown checklist records stage progress; no CLI TODO
|
|
39
|
-
state machine is part of this contract.
|
|
40
|
+
`fable on haiku` normalizes to `mechanical`; `fable on sonnect` normalizes to `reasoning`; `fable on opus` normalizes to `orchestrator`. None of these aliases means switching the host to that branded model.
|
|
41
|
+
|
|
42
|
+
The audit record uses `requestedProfile`, `effectiveProfile`, `profileAlias`, `assignedRole`, `runtimeModel`, `runtimeEffort`, `recommendedRuntimeFloor`, `runtimeFloorStatus`, and `runtimeFloorReason`, plus stage verification fields.
|
|
43
|
+
|
|
44
|
+
A `below-recommended` or `unknown` runtime-floor result is advisory and does not block Fable. Missing named-agent capability is separate: the existing `inline` or `stop` fallback policy decides whether the same profile runs inline or the stage is blocked.
|
|
40
45
|
|
|
41
46
|
See [execution phases](execution-phases.md) for the staged run contract.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Profile lookup
|
|
2
|
+
|
|
3
|
+
Use for explanation or a profile-only JSON record. No stage, audit-log mutation, delegation, or model switch is implied. Execution requests retain their selected workflow obligations.
|
|
4
|
+
|
|
5
|
+
| Alias | Profile | Role | Named agent |
|
|
6
|
+
|---|---|---|---|
|
|
7
|
+
| haiku | mechanical | mechanical-worker | fable-worker-haiku |
|
|
8
|
+
| sonnet / sonnect | reasoning | reasoning-worker | fable-worker-sonnet |
|
|
9
|
+
| opus | orchestrator | orchestrator | fable-orchestrator |
|
|
10
|
+
|
|
11
|
+
Preserve the input alias. Profiles describe behavior; the host chooses the actual runtime model. Report runtime fields as unknown when they were not supplied; never infer a model from an alias. Runtime floors are advisory, including unknown or below-recommended results.
|
|
12
|
+
|
|
13
|
+
For a lookup, these fields suffice: `requestedProfile`, `effectiveProfile`, `profileAlias`, `assignedRole`, `runtimeModel`, `runtimeEffort`. If the user requests a computed floor comparison, read the relevant entry in `runtime-model-floor-matrix.json` and the resolution rules in [runtime-floor rules](model-matrix.md); use `scripts/model-selector.js` for a stage audit, not by reading its implementation for a lookup.
|
|
14
|
+
|
|
15
|
+
Example for `fable on sonnect` with no supplied runtime:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{"requestedProfile":"reasoning","effectiveProfile":"reasoning","profileAlias":"sonnect","assignedRole":"reasoning-worker","runtimeModel":null,"runtimeEffort":null}
|
|
19
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"policy": "advisory-floor",
|
|
4
|
+
"belowFloorDisposition": "continue-with-visible-status",
|
|
5
|
+
"hosts": {
|
|
6
|
+
"claude": {
|
|
7
|
+
"orchestrator": {
|
|
8
|
+
"family": "opus",
|
|
9
|
+
"minVersion": "5.5",
|
|
10
|
+
"minEffort": "medium",
|
|
11
|
+
"display": "Opus 5.5+ / medium+"
|
|
12
|
+
},
|
|
13
|
+
"reasoning": {
|
|
14
|
+
"family": "sonnet",
|
|
15
|
+
"minVersion": "5.5",
|
|
16
|
+
"minEffort": "medium",
|
|
17
|
+
"display": "Sonnet 5.5+ / medium+"
|
|
18
|
+
},
|
|
19
|
+
"mechanical": {
|
|
20
|
+
"family": "haiku",
|
|
21
|
+
"minVersion": "5.5",
|
|
22
|
+
"minEffort": null,
|
|
23
|
+
"display": "Haiku 5.5+"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"codex": {
|
|
27
|
+
"orchestrator": {
|
|
28
|
+
"family": "sol",
|
|
29
|
+
"minGeneration": 6,
|
|
30
|
+
"minEffort": "xhigh",
|
|
31
|
+
"display": "GPT-6 Sol+ / xhigh+"
|
|
32
|
+
},
|
|
33
|
+
"reasoning": {
|
|
34
|
+
"family": "sol",
|
|
35
|
+
"minGeneration": 6,
|
|
36
|
+
"minEffort": "medium",
|
|
37
|
+
"display": "GPT-6 Sol+ / medium+"
|
|
38
|
+
},
|
|
39
|
+
"mechanical": {
|
|
40
|
+
"family": "luna",
|
|
41
|
+
"minGeneration": 6,
|
|
42
|
+
"minEffort": "xhigh",
|
|
43
|
+
"display": "GPT-6 Luna 6+ / xhigh+"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -5,7 +5,6 @@ const os = require('os');
|
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const crypto = require('crypto');
|
|
7
7
|
|
|
8
|
-
const MATRIX_PATH = path.join(__dirname, '..', 'model-matrix.json');
|
|
9
8
|
const SESSION_REGISTRY_DIR = 'session-workspaces';
|
|
10
9
|
const SESSION_REGISTRY_VERSION = 1;
|
|
11
10
|
const UNBOUND_WORKSPACE_KEY = 'unbound-workspace';
|
|
@@ -136,89 +135,140 @@ function getWorkspaceStateDir(root) {
|
|
|
136
135
|
}
|
|
137
136
|
const REQUIRED = ['stageBrief', 'passCondition', 'verificationCommand', 'verifierResult'];
|
|
138
137
|
const VALID_VERIFIER_RESULTS = new Set(['pending', 'pass', 'fail', 'not-run', 'blocked']);
|
|
138
|
+
const PROFILE_MATRIX_PATH = path.join(__dirname, '..', 'behavior-profile-matrix.json');
|
|
139
|
+
const RUNTIME_FLOOR_PATH = path.join(__dirname, '..', 'runtime-model-floor-matrix.json');
|
|
140
|
+
const EFFORT_ORDER = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
139
141
|
|
|
140
|
-
function
|
|
141
|
-
return JSON.parse(fs.readFileSync(
|
|
142
|
+
function loadBehaviorMatrix() {
|
|
143
|
+
return JSON.parse(fs.readFileSync(PROFILE_MATRIX_PATH, 'utf8'));
|
|
142
144
|
}
|
|
143
145
|
|
|
144
|
-
function
|
|
146
|
+
function loadRuntimeFloors() {
|
|
147
|
+
return JSON.parse(fs.readFileSync(RUNTIME_FLOOR_PATH, 'utf8'));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function normalizeProfile(value, matrix = loadBehaviorMatrix()) {
|
|
145
151
|
const input = String(value || '').trim().toLowerCase();
|
|
146
|
-
for (const [
|
|
147
|
-
if (definition.aliases.includes(input)) return
|
|
152
|
+
for (const [profile, definition] of Object.entries(matrix.profiles)) {
|
|
153
|
+
if (profile === input || definition.aliases.includes(input)) return { profile, alias: input, definition };
|
|
148
154
|
}
|
|
149
155
|
return null;
|
|
150
156
|
}
|
|
151
157
|
|
|
158
|
+
function normalizeHost(value) {
|
|
159
|
+
const input = String(value || '').trim().toLowerCase();
|
|
160
|
+
if (input === 'claude' || input === 'claude-code' || input === 'claude code') return 'claude';
|
|
161
|
+
if (input === 'codex' || input === 'openai-codex' || input === 'openai codex') return 'codex';
|
|
162
|
+
return input || 'unknown';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function normalizeEffort(value) {
|
|
166
|
+
const input = String(value || '').trim().toLowerCase().replace(/[_ -]/g, '');
|
|
167
|
+
if (!input) return null;
|
|
168
|
+
if (input === 'extrahigh' || input === 'xhigh') return 'xhigh';
|
|
169
|
+
return input;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function claudeVersion(model, family) {
|
|
173
|
+
const text = String(model || '').toLowerCase();
|
|
174
|
+
const match = text.match(new RegExp(family + '[^0-9]*(\\d+)(?:[.-](\\d+))?'));
|
|
175
|
+
if (!match) return null;
|
|
176
|
+
return Number(match[1] + '.' + (match[2] || '0'));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function codexGeneration(model) {
|
|
180
|
+
const match = String(model || '').toLowerCase().match(/\bgpt-(\d+(?:\.\d+)?)/);
|
|
181
|
+
return match ? Number(match[1]) : null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function evaluateRuntimeFloor(profile, input, floors = loadRuntimeFloors()) {
|
|
185
|
+
const host = normalizeHost(input.host);
|
|
186
|
+
const floor = floors.hosts[host] && floors.hosts[host][profile] ? floors.hosts[host][profile] : null;
|
|
187
|
+
const runtimeModel = String(input.runtimeModel || input.hostModel || '').trim() || null;
|
|
188
|
+
const runtimeEffort = normalizeEffort(input.runtimeEffort || input.effort);
|
|
189
|
+
if (!floor) return { host, runtimeModel, runtimeEffort, floor: null, status: 'not-configured', reason: 'no runtime floor configured for host ' + host };
|
|
190
|
+
if (!runtimeModel) return { host, runtimeModel, runtimeEffort, floor, status: 'unknown', reason: 'runtime model was not reported by the host' };
|
|
191
|
+
|
|
192
|
+
const lower = runtimeModel.toLowerCase();
|
|
193
|
+
if (!lower.includes(floor.family)) return { host, runtimeModel, runtimeEffort, floor, status: 'unknown', reason: 'runtime model family is not comparable to recommended ' + floor.family };
|
|
194
|
+
|
|
195
|
+
if (floor.minVersion) {
|
|
196
|
+
const actual = claudeVersion(runtimeModel, floor.family);
|
|
197
|
+
if (actual === null) return { host, runtimeModel, runtimeEffort, floor, status: 'unknown', reason: 'runtime model version could not be parsed' };
|
|
198
|
+
if (actual < Number(floor.minVersion)) return { host, runtimeModel, runtimeEffort, floor, status: 'below-recommended', reason: 'runtime model version ' + actual + ' is below ' + floor.minVersion };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (floor.minGeneration) {
|
|
202
|
+
const actual = codexGeneration(runtimeModel);
|
|
203
|
+
if (actual === null) return { host, runtimeModel, runtimeEffort, floor, status: 'unknown', reason: 'runtime model generation could not be parsed' };
|
|
204
|
+
if (actual < Number(floor.minGeneration)) return { host, runtimeModel, runtimeEffort, floor, status: 'below-recommended', reason: 'runtime model generation ' + actual + ' is below ' + floor.minGeneration };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (floor.minEffort) {
|
|
208
|
+
if (!runtimeEffort) return { host, runtimeModel, runtimeEffort, floor, status: 'unknown', reason: 'runtime reasoning effort was not reported by the host' };
|
|
209
|
+
const actualRank = EFFORT_ORDER.indexOf(runtimeEffort);
|
|
210
|
+
const floorRank = EFFORT_ORDER.indexOf(normalizeEffort(floor.minEffort));
|
|
211
|
+
if (actualRank < 0 || floorRank < 0) return { host, runtimeModel, runtimeEffort, floor, status: 'unknown', reason: 'runtime effort is not comparable to configured floor' };
|
|
212
|
+
if (actualRank < floorRank) return { host, runtimeModel, runtimeEffort, floor, status: 'below-recommended', reason: 'runtime effort ' + runtimeEffort + ' is below ' + floor.minEffort };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return { host, runtimeModel, runtimeEffort, floor, status: 'meets-recommended', reason: 'runtime meets the configured advisory floor' };
|
|
216
|
+
}
|
|
217
|
+
|
|
152
218
|
function parseArgs(argv) {
|
|
153
219
|
const args = {};
|
|
154
220
|
for (let i = 0; i < argv.length; i += 1) {
|
|
155
221
|
const token = argv[i];
|
|
156
222
|
if (token === '--help' || token === '-h') return { help: true };
|
|
157
|
-
if (!token.startsWith('--')) throw new Error(
|
|
223
|
+
if (!token.startsWith('--')) throw new Error('unknown argument: ' + token);
|
|
158
224
|
const key = token.slice(2).replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
159
225
|
const value = argv[i + 1];
|
|
160
|
-
if (typeof value === 'undefined' || value.startsWith('--')) throw new Error(
|
|
226
|
+
if (typeof value === 'undefined' || value.startsWith('--')) throw new Error('missing value for --' + key);
|
|
161
227
|
args[key] = value;
|
|
162
228
|
i += 1;
|
|
163
229
|
}
|
|
164
230
|
return args;
|
|
165
231
|
}
|
|
166
232
|
|
|
167
|
-
function resolveMode(input, matrix =
|
|
168
|
-
const
|
|
169
|
-
if (!
|
|
170
|
-
|
|
171
|
-
const availableModels = String(input.available || '')
|
|
172
|
-
.split(/[\s,]+/)
|
|
173
|
-
.map(model => normalizeModel(model, matrix))
|
|
174
|
-
.filter(Boolean);
|
|
175
|
-
const availableAgents = String(input.availableAgents || '')
|
|
176
|
-
.split(/[\s,]+/)
|
|
177
|
-
.map(agent => agent.trim())
|
|
178
|
-
.filter(Boolean);
|
|
179
|
-
const fallback = input.fallback || 'stop';
|
|
180
|
-
if (!Object.prototype.hasOwnProperty.call(matrix.fallbacks, fallback)) {
|
|
181
|
-
throw new Error('fallback must be inline or stop');
|
|
182
|
-
}
|
|
233
|
+
function resolveMode(input, matrix = loadBehaviorMatrix(), floors = loadRuntimeFloors()) {
|
|
234
|
+
const normalized = normalizeProfile(input.requested, matrix);
|
|
235
|
+
if (!normalized) throw new Error('requested profile must be one of mechanical, reasoning, orchestrator, haiku, sonnet, sonnect, or opus');
|
|
183
236
|
|
|
237
|
+
const availableAgents = String(input.availableAgents || '').split(/[\s,]+/).map(agent => agent.trim()).filter(Boolean);
|
|
238
|
+
const fallback = input.fallback || 'stop';
|
|
239
|
+
if (!['inline', 'stop'].includes(fallback)) throw new Error('fallback must be inline or stop');
|
|
184
240
|
for (const field of REQUIRED) {
|
|
185
|
-
if (!String(input[field] || '').trim()) throw new Error(
|
|
186
|
-
}
|
|
187
|
-
if (!VALID_VERIFIER_RESULTS.has(input.verifierResult)) {
|
|
188
|
-
throw new Error(`verifierResult must be one of ${[...VALID_VERIFIER_RESULTS].join(', ')}`);
|
|
241
|
+
if (!String(input[field] || '').trim()) throw new Error(field + ' is required for an auditable stage');
|
|
189
242
|
}
|
|
243
|
+
if (!VALID_VERIFIER_RESULTS.has(input.verifierResult)) throw new Error('verifierResult must be one of ' + [...VALID_VERIFIER_RESULTS].join(', '));
|
|
190
244
|
|
|
191
|
-
const
|
|
192
|
-
const
|
|
245
|
+
const requestedProfile = normalized.profile;
|
|
246
|
+
const requestedAgent = normalized.definition.agent;
|
|
193
247
|
const agentAvailable = availableAgents.includes(requestedAgent);
|
|
194
|
-
const
|
|
195
|
-
const
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
? requestedModel
|
|
200
|
-
: fallback === 'inline'
|
|
201
|
-
? hostModel
|
|
202
|
-
: fallbackDefinition.effectiveModel;
|
|
203
|
-
const missing = [];
|
|
204
|
-
if (!modelAvailable) missing.push(`${requestedModel} is not in availableModels`);
|
|
205
|
-
if (!agentAvailable) missing.push(`${requestedAgent} is not in availableAgents`);
|
|
206
|
-
const fallbackReason = selected
|
|
207
|
-
? 'none'
|
|
208
|
-
: `${fallbackDefinition.reason}: ${missing.join('; ')}`;
|
|
248
|
+
const status = agentAvailable ? 'selected' : fallback === 'inline' ? 'fallback' : 'blocked';
|
|
249
|
+
const runtime = evaluateRuntimeFloor(requestedProfile, input, floors);
|
|
250
|
+
const fallbackReason = agentAvailable ? 'none' : fallback === 'inline'
|
|
251
|
+
? requestedAgent + ' is not in availableAgents; execute the same ' + requestedProfile + ' behavior profile inline'
|
|
252
|
+
: requestedAgent + ' is not in availableAgents and inline fallback was not authorized';
|
|
209
253
|
|
|
210
254
|
return {
|
|
211
255
|
schemaVersion: matrix.schemaVersion,
|
|
212
256
|
status,
|
|
213
|
-
|
|
257
|
+
requestedProfile,
|
|
258
|
+
effectiveProfile: status === 'blocked' ? null : requestedProfile,
|
|
259
|
+
profileAlias: normalized.alias,
|
|
214
260
|
requestedInput: String(input.requested).trim(),
|
|
215
|
-
|
|
216
|
-
|
|
261
|
+
assignedRole: normalized.definition.role,
|
|
262
|
+
agent: agentAvailable ? requestedAgent : null,
|
|
217
263
|
availableAgents: [...new Set(availableAgents)],
|
|
218
264
|
fallbackPolicy: fallback,
|
|
219
265
|
fallbackReason,
|
|
220
|
-
|
|
221
|
-
|
|
266
|
+
host: runtime.host,
|
|
267
|
+
runtimeModel: runtime.runtimeModel,
|
|
268
|
+
runtimeEffort: runtime.runtimeEffort,
|
|
269
|
+
recommendedRuntimeFloor: runtime.floor,
|
|
270
|
+
runtimeFloorStatus: runtime.status,
|
|
271
|
+
runtimeFloorReason: runtime.reason,
|
|
222
272
|
stageBrief: String(input.stageBrief).trim(),
|
|
223
273
|
passCondition: String(input.passCondition).trim(),
|
|
224
274
|
verificationCommand: String(input.verificationCommand).trim(),
|
|
@@ -228,33 +278,29 @@ function resolveMode(input, matrix = loadMatrix()) {
|
|
|
228
278
|
}
|
|
229
279
|
|
|
230
280
|
function printHelp() {
|
|
231
|
-
console.log('Usage: node fable-mode/scripts/model-selector.js --requested <haiku|sonnet|sonnect|opus> --available
|
|
281
|
+
console.log('Usage: node fable-mode/scripts/model-selector.js --requested <mechanical|reasoning|orchestrator|haiku|sonnet|sonnect|opus> --available-agents <agent names> --stage-brief <text> --pass-condition <text> --verification-command <command> --verifier-result <pending|pass|fail|not-run|blocked> [--host <claude|codex>] [--runtime-model <model>] [--runtime-effort <effort>] [--fallback <inline|stop>] [--audit-file <path>]');
|
|
232
282
|
}
|
|
233
283
|
|
|
234
284
|
function appendAuditRecord(record, auditFile, context) {
|
|
235
|
-
const target = auditFile || process.env.FABLE_AUDIT_FILE ||
|
|
236
|
-
path.join(getWorkspaceStateDir(getWorkspaceRoot(context)), 'state', 'fable-mode', 'audit.jsonl');
|
|
285
|
+
const target = auditFile || process.env.FABLE_AUDIT_FILE || path.join(getWorkspaceStateDir(getWorkspaceRoot(context)), 'state', 'fable-mode', 'audit.jsonl');
|
|
237
286
|
fs.mkdirSync(path.dirname(path.resolve(target)), { recursive: true });
|
|
238
|
-
fs.appendFileSync(target,
|
|
287
|
+
fs.appendFileSync(target, JSON.stringify(record) + '\n', 'utf8');
|
|
239
288
|
}
|
|
240
289
|
|
|
241
290
|
function main(argv = process.argv.slice(2)) {
|
|
242
291
|
try {
|
|
243
292
|
const input = parseArgs(argv);
|
|
244
|
-
if (input.help) {
|
|
245
|
-
printHelp();
|
|
246
|
-
return 0;
|
|
247
|
-
}
|
|
293
|
+
if (input.help) { printHelp(); return 0; }
|
|
248
294
|
const record = resolveMode(input);
|
|
249
295
|
appendAuditRecord(record, input.auditFile, input);
|
|
250
296
|
console.log(JSON.stringify(record, null, 2));
|
|
251
297
|
return record.status === 'blocked' ? 2 : 0;
|
|
252
298
|
} catch (error) {
|
|
253
|
-
console.error(
|
|
299
|
+
console.error('[fable-mode/model-selector] ' + error.message);
|
|
254
300
|
return 2;
|
|
255
301
|
}
|
|
256
302
|
}
|
|
257
303
|
|
|
258
304
|
if (require.main === module) process.exitCode = main();
|
|
259
305
|
|
|
260
|
-
module.exports = { loadMatrix, normalizeModel, parseArgs, resolveMode, main };
|
|
306
|
+
module.exports = { loadBehaviorMatrix, loadMatrix: loadBehaviorMatrix, loadRuntimeFloors, normalizeProfile, normalizeModel: normalizeProfile, parseArgs, evaluateRuntimeFloor, resolveMode, main };
|
|
@@ -272,7 +272,7 @@ function prepareRun({ routerContract, stages, workspaceRoot, runId, sessionId =
|
|
|
272
272
|
verification: plan.verification,
|
|
273
273
|
workspace: plan.workspace,
|
|
274
274
|
memory: plan.memory,
|
|
275
|
-
|
|
275
|
+
profileSelection: plan.profileSelection,
|
|
276
276
|
limits: plan.limits,
|
|
277
277
|
},
|
|
278
278
|
stageIds: normalizedStages.map(stage => stage.stageId),
|
|
@@ -4,35 +4,32 @@ description: "Route software/project work through Harness: classify tier/topolog
|
|
|
4
4
|
license: Apache-2.0
|
|
5
5
|
metadata:
|
|
6
6
|
author: Miya Daniel
|
|
7
|
-
version: 0.30.
|
|
7
|
+
version: 0.30.6
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# Harness Everything
|
|
11
11
|
|
|
12
|
-
Kernel routing entry.
|
|
13
|
-
|
|
14
12
|
## USE FOR:
|
|
15
13
|
- Software/project triage, routing, re-routing, workflow selection.
|
|
16
|
-
- Work
|
|
14
|
+
- Work naming or matching another skill.
|
|
17
15
|
|
|
18
16
|
## DO NOT USE FOR:
|
|
19
17
|
- General Q&A or non-software writing.
|
|
20
18
|
|
|
21
19
|
## Invariants
|
|
22
|
-
1. **Route (MUST)** —
|
|
23
|
-
2. **Verify (MUST)** —
|
|
20
|
+
1. **Route (MUST)** — scope/tier plus selected/deferred topology.
|
|
21
|
+
2. **Verify (MUST)** — objective evidence via `verify-gate.js --json`.
|
|
24
22
|
3. **Zoom out (MUST)** — after 3 same-signature failures, use `zoom-out`.
|
|
25
|
-
4. **Resolve suggestions (MUST)** —
|
|
26
|
-
5. **Surface status (MUST)** — non-trivial work renders `### 🚦 Harness Status` with bold `Current`, `Read / Evidence`, `Next`;
|
|
27
|
-
6. **Keep agency** — required obligations are MUST; tactics MAY adapt. Numeric planning
|
|
23
|
+
4. **Resolve suggestions (MUST)** — evaluate each suggested `SKILL.md`; run applicable core contracts or retain a flow-grounded reason.
|
|
24
|
+
5. **Surface status (MUST)** — non-trivial work renders `### 🚦 Harness Status` with bold `Current`, `Read / Evidence`, `Next`; `Risk / Blocked` optional.
|
|
25
|
+
6. **Keep agency** — required obligations are MUST; tactics MAY adapt. Numeric planning MAY guide, never hard-stop; only Rule-of-3 reflection and explicit user/host permission boundaries may block.
|
|
26
|
+
7. **Windows PowerShell (MUST)** — `Get-Content -Encoding UTF8 -Raw`; npm `.cmd` shims (`npx.cmd`); never weaken `ExecutionPolicy`.
|
|
28
27
|
|
|
29
28
|
## Workflow
|
|
30
29
|
1. Reuse kernel output or run `node "<this-skill-dir>/scripts/kernel-router.js" "<prompt summary>"`.
|
|
31
30
|
2. Keep the routing checkpoint internal.
|
|
32
|
-
3. Resolve
|
|
33
|
-
4.
|
|
34
|
-
5. Tier-3/Fable broad mutation MUST
|
|
35
|
-
|
|
36
|
-
**Rule:** semantic MUST is not hard blocking; only Rule-of-3 reflection and explicit user/host permission boundaries may block.
|
|
31
|
+
3. Resolve suggestions as `use`, `not-applicable`, or `unresolved`.
|
|
32
|
+
4. Complete selected-topology invariants/stages/checks.
|
|
33
|
+
5. Tier-3/Fable broad mutation MUST use a linked worktree or explicit degraded fallback.
|
|
37
34
|
|
|
38
35
|
Deep dive: <this-skill-dir>/references/triage-and-tiers.md
|
package/plugins/harness-everything/skills/harness-everything/references/router-workflow-plan.md
CHANGED
|
@@ -68,7 +68,7 @@ The Phase 2 `iterative-single` plan exposes **8 iterations** as advisory plannin
|
|
|
68
68
|
The task-shape contract records:
|
|
69
69
|
|
|
70
70
|
- an explicit requested strategy, when named;
|
|
71
|
-
- an explicit Fable
|
|
71
|
+
- an explicit Fable behavior-profile request; legacy aliases `haiku`/`sonnet`/`opus` are delegated to `fable-mode/scripts/model-selector.js`;
|
|
72
72
|
- prohibitions such as no Fable, no subagents, no parallel execution, or no workspace.
|
|
73
73
|
|
|
74
74
|
Explicit choices override derived topology. Contradictory choice + prohibition is visible as a blocked fallback rather than silently guessing which instruction to ignore.
|
|
@@ -82,7 +82,7 @@ Examples:
|
|
|
82
82
|
- missing parallel calls or `concurrency: 1` serializes `fable-parallel` to `fable-staged` with a `reduced/serialized` fallback;
|
|
83
83
|
- unavailable subagents may reduce staged execution to visible inline execution;
|
|
84
84
|
- missing durable state for a required multi-agent workspace is `blocked`, not silently downgraded;
|
|
85
|
-
-
|
|
85
|
+
- Fable profile choice is independent of runtime-model availability; host-specific model floors are advisory and recorded by Fable rather than blocking routing;
|
|
86
86
|
- a required `actionGate` with known unavailable hook capability is `blocked`.
|
|
87
87
|
|
|
88
88
|
Fallbacks carry deterministic reason codes.
|
package/plugins/harness-everything/skills/harness-everything/schemas/router-task-shape.schema.json
CHANGED
|
@@ -56,10 +56,11 @@
|
|
|
56
56
|
},
|
|
57
57
|
"explicitRequest": {
|
|
58
58
|
"type": "object",
|
|
59
|
-
"required": ["
|
|
59
|
+
"required": ["fableProfile", "fableAlias", "strategy", "prohibitions"],
|
|
60
60
|
"additionalProperties": false,
|
|
61
61
|
"properties": {
|
|
62
|
-
"
|
|
62
|
+
"fableProfile": { "enum": ["mechanical", "reasoning", "orchestrator", null] },
|
|
63
|
+
"fableAlias": { "enum": ["mechanical", "reasoning", "orchestrator", "haiku", "sonnet", "sonnect", "opus", null] },
|
|
63
64
|
"strategy": {
|
|
64
65
|
"enum": ["direct-single", "iterative-single", "fable-staged", "fable-parallel", "fable-multi-agent-workspace", null]
|
|
65
66
|
},
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"memory",
|
|
20
20
|
"verification",
|
|
21
21
|
"ensemble",
|
|
22
|
-
"
|
|
22
|
+
"profileSelection",
|
|
23
23
|
"fallback",
|
|
24
24
|
"reasonCodes"
|
|
25
25
|
],
|
|
@@ -120,13 +120,14 @@
|
|
|
120
120
|
}
|
|
121
121
|
]
|
|
122
122
|
},
|
|
123
|
-
"
|
|
123
|
+
"profileSelection": {
|
|
124
124
|
"type": "object",
|
|
125
|
-
"required": ["requested", "policy"],
|
|
125
|
+
"required": ["requested", "alias", "policy"],
|
|
126
126
|
"additionalProperties": false,
|
|
127
127
|
"properties": {
|
|
128
|
-
"requested": { "enum": ["
|
|
129
|
-
"
|
|
128
|
+
"requested": { "enum": ["mechanical", "reasoning", "orchestrator", null] },
|
|
129
|
+
"alias": { "enum": ["mechanical", "reasoning", "orchestrator", "haiku", "sonnet", "sonnect", "opus", null] },
|
|
130
|
+
"policy": { "enum": ["behavior-profile-with-advisory-runtime-floor"] }
|
|
130
131
|
}
|
|
131
132
|
},
|
|
132
133
|
"fallback": {
|
package/plugins/harness-everything/skills/harness-everything/scripts/router-contract-core.js
CHANGED
|
@@ -59,13 +59,23 @@ function normalizeConstraints(raw = {}) {
|
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
function normalizeFableProfile(value) {
|
|
63
|
+
const input = String(value || '').trim().toLowerCase();
|
|
64
|
+
if (input === 'mechanical' || input === 'haiku') return { profile: 'mechanical', alias: input };
|
|
65
|
+
if (input === 'reasoning' || input === 'sonnet' || input === 'sonnect') return { profile: 'reasoning', alias: input };
|
|
66
|
+
if (input === 'orchestrator' || input === 'opus') return { profile: 'orchestrator', alias: input };
|
|
67
|
+
return { profile: null, alias: null };
|
|
68
|
+
}
|
|
69
|
+
|
|
62
70
|
function normalizeExplicitRequest(raw = {}) {
|
|
63
71
|
const requestedStrategy = STRATEGIES.has(raw.strategy) ? raw.strategy : null;
|
|
64
72
|
const prohibitions = Array.isArray(raw.prohibitions)
|
|
65
73
|
? raw.prohibitions.filter(value => PROHIBITIONS.has(value))
|
|
66
74
|
: [];
|
|
75
|
+
const fable = normalizeFableProfile(raw.fableAlias || raw.fableProfile || raw.fableModel);
|
|
67
76
|
return {
|
|
68
|
-
|
|
77
|
+
fableProfile: fable.profile,
|
|
78
|
+
fableAlias: fable.alias,
|
|
69
79
|
strategy: requestedStrategy,
|
|
70
80
|
prohibitions: Array.from(new Set(prohibitions)),
|
|
71
81
|
};
|
|
@@ -77,7 +87,7 @@ function buildTaskShape(input = {}) {
|
|
|
77
87
|
const hostCapabilities = normalizeHostCapabilities(input.hostCapabilities);
|
|
78
88
|
const constraints = normalizeConstraints(input.constraints);
|
|
79
89
|
const explicitRequest = normalizeExplicitRequest(input.explicitRequest || {
|
|
80
|
-
|
|
90
|
+
fableProfile: input.requestedFableProfile || input.requestedFableModel,
|
|
81
91
|
});
|
|
82
92
|
|
|
83
93
|
let scopeSize = 'unknown';
|
|
@@ -299,11 +309,6 @@ function selectWorkflowStrategy(input = {}) {
|
|
|
299
309
|
fallback.reasonCodes.push(prohibitions.includes('subagents') ? 'user-prohibited-subagents' : 'subagents-unavailable');
|
|
300
310
|
}
|
|
301
311
|
|
|
302
|
-
if (explicit.fableModel && taskShape.hostCapabilities.modelAvailability === 'unavailable') {
|
|
303
|
-
fallback.disposition = 'blocked';
|
|
304
|
-
fallback.mode = 'blocked';
|
|
305
|
-
fallback.reasonCodes.push('requested-model-capability-unavailable');
|
|
306
|
-
}
|
|
307
312
|
}
|
|
308
313
|
|
|
309
314
|
return {
|
|
@@ -494,9 +499,10 @@ function buildWorkflowPlan(input = {}) {
|
|
|
494
499
|
independent: meta.verificationIndependent,
|
|
495
500
|
},
|
|
496
501
|
ensemble: null,
|
|
497
|
-
|
|
498
|
-
requested: taskShape.explicitRequest.
|
|
499
|
-
|
|
502
|
+
profileSelection: {
|
|
503
|
+
requested: taskShape.explicitRequest.fableProfile,
|
|
504
|
+
alias: taskShape.explicitRequest.fableAlias,
|
|
505
|
+
policy: 'behavior-profile-with-advisory-runtime-floor',
|
|
500
506
|
},
|
|
501
507
|
fallback,
|
|
502
508
|
reasonCodes,
|
|
@@ -507,7 +513,7 @@ function buildRouterContract(input = {}) {
|
|
|
507
513
|
const tier = normalizeTierLabel(input.recommendedTier);
|
|
508
514
|
const reasonCodes = uniqueReasonCodes(input.reasonCodes);
|
|
509
515
|
const explicitRequest = normalizeExplicitRequest(input.explicitRequest || {
|
|
510
|
-
|
|
516
|
+
fableProfile: input.requestedFableProfile || input.requestedFableModel,
|
|
511
517
|
});
|
|
512
518
|
const taskShape = buildTaskShape({
|
|
513
519
|
tier,
|
|
@@ -546,7 +552,7 @@ function createDegradedRouterContract(reasonCode, message) {
|
|
|
546
552
|
rationale: message || 'Structured router contract unavailable.',
|
|
547
553
|
reasonCodes: [reasonCode || 'router-contract-unavailable'],
|
|
548
554
|
signals: {},
|
|
549
|
-
|
|
555
|
+
requestedFableProfile: null,
|
|
550
556
|
});
|
|
551
557
|
}
|
|
552
558
|
|
|
@@ -624,6 +630,7 @@ module.exports = {
|
|
|
624
630
|
createDegradedRouterContract,
|
|
625
631
|
normalizeConstraints,
|
|
626
632
|
normalizeExplicitRequest,
|
|
633
|
+
normalizeFableProfile,
|
|
627
634
|
normalizeHostCapabilities,
|
|
628
635
|
normalizeTierLabel,
|
|
629
636
|
selectWorkflowStrategy,
|