crewhaus 0.2.1 → 0.2.2
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/dist/advise-rules.d.ts +55 -1
- package/dist/advise-rules.js +165 -1
- package/dist/index.js +40 -7
- package/dist/route.d.ts +22 -2
- package/dist/route.js +93 -15
- package/package.json +89 -89
package/dist/advise-rules.d.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* context slice for each missing kind and simply produce no findings — the
|
|
16
16
|
* rules only fire where the data now exists.
|
|
17
17
|
*/
|
|
18
|
+
import type { ArmStats } from "@crewhaus/routing-store";
|
|
18
19
|
import type { Spec } from "@crewhaus/spec";
|
|
19
20
|
import { type SpecPatch } from "@crewhaus/spec-patch";
|
|
20
21
|
export type AdviceSeverity = "info" | "warn";
|
|
@@ -108,6 +109,13 @@ export type AdviceContext = {
|
|
|
108
109
|
readonly totalToolBytes: number;
|
|
109
110
|
/** `.crewhaus/audit` record kinds → counts (report context + future rules). */
|
|
110
111
|
readonly auditKindCounts: ReadonlyMap<string, number>;
|
|
112
|
+
/**
|
|
113
|
+
* Adaptive model routing — the `model_pool` reward scoreboard arms from
|
|
114
|
+
* `.crewhaus/routing/arms.jsonl` (empty when the harness has never routed a
|
|
115
|
+
* pool). The routing rules mine these into pool-POLICY suggestions; the
|
|
116
|
+
* candidate roster itself stays human-owned.
|
|
117
|
+
*/
|
|
118
|
+
readonly routingArms: ReadonlyArray<ArmStats>;
|
|
111
119
|
};
|
|
112
120
|
export type SessionEvents = {
|
|
113
121
|
readonly sessionId: string;
|
|
@@ -140,7 +148,7 @@ export declare function payloadOf(obj: unknown, kind: string): Record<string, un
|
|
|
140
148
|
* old-vintage logs (no advisor kinds) and future kinds both pass through
|
|
141
149
|
* cleanly.
|
|
142
150
|
*/
|
|
143
|
-
export declare function buildAdviceContext(sessions: ReadonlyArray<SessionEvents>, auditObjects?: ReadonlyArray<unknown>): AdviceContext;
|
|
151
|
+
export declare function buildAdviceContext(sessions: ReadonlyArray<SessionEvents>, auditObjects?: ReadonlyArray<unknown>, routingArms?: ReadonlyArray<ArmStats>): AdviceContext;
|
|
144
152
|
export type AdviceThresholds = {
|
|
145
153
|
/** Minimum calls before a tool's error rate is judged at all. */
|
|
146
154
|
toolFailureMinCalls: number;
|
|
@@ -171,6 +179,13 @@ export type AdviceThresholds = {
|
|
|
171
179
|
/** Item 21 — minimum total tool bytes before dominance is judged at all
|
|
172
180
|
* (a handful of tiny calls shouldn't trip a restructure). */
|
|
173
181
|
toolByteMinTotal: number;
|
|
182
|
+
/** Adaptive routing — samples an arm needs before the routing rules judge
|
|
183
|
+
* it, when the spec doesn't set `model_pool.learning.minSamplesPerArm`.
|
|
184
|
+
* Mirrors the PolicyRouter's own default floor. */
|
|
185
|
+
poolMinSamples: number;
|
|
186
|
+
/** Adaptive routing — mean-reward gap below the band best at/above which a
|
|
187
|
+
* candidate is called consistently losing (demotion advice). */
|
|
188
|
+
poolDemotionGap: number;
|
|
174
189
|
};
|
|
175
190
|
/**
|
|
176
191
|
* Canonical rule thresholds. runtime-core's in-run digest
|
|
@@ -187,6 +202,16 @@ export type RuleOptions = {
|
|
|
187
202
|
/** The cwd spec, when one exists — enables patch suggestions. */
|
|
188
203
|
readonly spec?: Spec;
|
|
189
204
|
readonly thresholds?: Partial<AdviceThresholds>;
|
|
205
|
+
/**
|
|
206
|
+
* Whether the RAW spec YAML textually carries a key at `path` (the driver
|
|
207
|
+
* builds this from `specHasPath` in `@crewhaus/spec-patch`). Zod-defaulted
|
|
208
|
+
* fields are always present on the parsed `spec`, but `applySpecPatch`
|
|
209
|
+
* requires `add` for absent keys and `replace` for present ones — a rule
|
|
210
|
+
* proposing a patch on a defaultable field needs this to pick the op.
|
|
211
|
+
* Absent → such rules assume the key is absent (the common case for
|
|
212
|
+
* defaulted fields) and emit `add`.
|
|
213
|
+
*/
|
|
214
|
+
readonly specHasPath?: (path: ReadonlyArray<string>) => boolean;
|
|
190
215
|
};
|
|
191
216
|
export type AdviceRule = (ctx: AdviceContext, opts?: RuleOptions) => AdviceFinding[];
|
|
192
217
|
/**
|
|
@@ -315,6 +340,35 @@ export declare function subAgentFragment(toolName: string): string;
|
|
|
315
340
|
* rule stays silent, since there's nowhere to paste it.
|
|
316
341
|
*/
|
|
317
342
|
export declare const ruleSubAgentSplit: AdviceRule;
|
|
343
|
+
/**
|
|
344
|
+
* Pool policy upgrade: the spec declares a `model_pool` that is NOT yet
|
|
345
|
+
* `learned`, and the reward scoreboard already has full-coverage data (every
|
|
346
|
+
* candidate past the sample floor in ≥1 difficulty band) — the harness has
|
|
347
|
+
* earned the right to exploit what it measured. Proposes flipping
|
|
348
|
+
* `model_pool.policy` to `learned` (whitelisted; `optimize --from-advice`
|
|
349
|
+
* eval-gates the flip). The candidate ROSTER is never touched.
|
|
350
|
+
*/
|
|
351
|
+
export declare const rulePoolPolicyUpgrade: AdviceRule;
|
|
352
|
+
/**
|
|
353
|
+
* Pool candidate demotion: a candidate that is past the sample floor and
|
|
354
|
+
* loses to the band best by ≥ `poolDemotionGap` mean reward in EVERY band
|
|
355
|
+
* with full coverage is consistently wasted spend. ADVICE-ONLY by design:
|
|
356
|
+
* the candidate roster is human-owned (mirroring the `["agent","model"]`
|
|
357
|
+
* OPTIMIZABLE_PATHS exclusion), so this never emits a patch — it names the
|
|
358
|
+
* loser and lets a human edit `model_pool.candidates`.
|
|
359
|
+
*/
|
|
360
|
+
export declare const rulePoolCandidateDemotion: AdviceRule;
|
|
361
|
+
/**
|
|
362
|
+
* Stale exploitation: a `learned` pool whose scoreboard has full coverage but
|
|
363
|
+
* whose config never explores again — `explorationRate` unset/0 and the
|
|
364
|
+
* bandit is not `thompson` (which self-explores via posterior sampling). Once
|
|
365
|
+
* every arm clears the floor, such a pool hard-commits to the argmax forever
|
|
366
|
+
* and can never notice model drift or a candidate that improved. Proposes a
|
|
367
|
+
* small ε (0.05) via a whole-`learning`-block replace that PRESERVES the
|
|
368
|
+
* spec's other learning fields (whitelisted path; `optimize --from-advice`
|
|
369
|
+
* eval-gates it).
|
|
370
|
+
*/
|
|
371
|
+
export declare const rulePoolStaleExploitation: AdviceRule;
|
|
318
372
|
export declare const ADVICE_RULES: ReadonlyArray<AdviceRule>;
|
|
319
373
|
/**
|
|
320
374
|
* Run every rule and rank the findings: warn before info, then by the
|
package/dist/advise-rules.js
CHANGED
|
@@ -64,7 +64,7 @@ export function payloadOf(obj, kind) {
|
|
|
64
64
|
* old-vintage logs (no advisor kinds) and future kinds both pass through
|
|
65
65
|
* cleanly.
|
|
66
66
|
*/
|
|
67
|
-
export function buildAdviceContext(sessions, auditObjects = []) {
|
|
67
|
+
export function buildAdviceContext(sessions, auditObjects = [], routingArms = []) {
|
|
68
68
|
const toolStats = new Map();
|
|
69
69
|
const recoveriesByAction = new Map();
|
|
70
70
|
const recoveriesByErrorName = new Map();
|
|
@@ -197,6 +197,7 @@ export function buildAdviceContext(sessions, auditObjects = []) {
|
|
|
197
197
|
perToolBytes,
|
|
198
198
|
totalToolBytes,
|
|
199
199
|
auditKindCounts,
|
|
200
|
+
routingArms,
|
|
200
201
|
};
|
|
201
202
|
}
|
|
202
203
|
/**
|
|
@@ -218,6 +219,8 @@ export const DEFAULT_ADVICE_THRESHOLDS = {
|
|
|
218
219
|
chronicCompactionSessions: 2,
|
|
219
220
|
toolByteDominance: 0.6,
|
|
220
221
|
toolByteMinTotal: 50_000,
|
|
222
|
+
poolMinSamples: 25,
|
|
223
|
+
poolDemotionGap: 0.3,
|
|
221
224
|
};
|
|
222
225
|
/** Stop reasons that are part of healthy operation; everything else
|
|
223
226
|
* (max_tokens, refusal, pause_turn, …) counts as anomalous. Mirrored by
|
|
@@ -798,6 +801,164 @@ export const ruleSubAgentSplit = (ctx, opts) => {
|
|
|
798
801
|
},
|
|
799
802
|
];
|
|
800
803
|
};
|
|
804
|
+
function agentModelPool(spec) {
|
|
805
|
+
const agent = specField(spec, "agent");
|
|
806
|
+
const pool = agent?.["model_pool"];
|
|
807
|
+
if (pool === undefined || pool === null || typeof pool !== "object")
|
|
808
|
+
return undefined;
|
|
809
|
+
return pool;
|
|
810
|
+
}
|
|
811
|
+
/** The bands (routeKeys) where EVERY pool candidate has ≥ `floor` samples. */
|
|
812
|
+
function bandsWithFullCoverage(arms, candidates, floor) {
|
|
813
|
+
const bands = [...new Set(arms.map((a) => a.routeKey))];
|
|
814
|
+
return bands.filter((band) => candidates.every((c) => arms.some((a) => a.routeKey === band && a.model === c.model && a.n >= floor)));
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Pool policy upgrade: the spec declares a `model_pool` that is NOT yet
|
|
818
|
+
* `learned`, and the reward scoreboard already has full-coverage data (every
|
|
819
|
+
* candidate past the sample floor in ≥1 difficulty band) — the harness has
|
|
820
|
+
* earned the right to exploit what it measured. Proposes flipping
|
|
821
|
+
* `model_pool.policy` to `learned` (whitelisted; `optimize --from-advice`
|
|
822
|
+
* eval-gates the flip). The candidate ROSTER is never touched.
|
|
823
|
+
*/
|
|
824
|
+
export const rulePoolPolicyUpgrade = (ctx, opts) => {
|
|
825
|
+
const t = resolveThresholds(opts);
|
|
826
|
+
const spec = opts?.spec;
|
|
827
|
+
const pool = agentModelPool(spec);
|
|
828
|
+
if (pool === undefined || pool.policy === "learned")
|
|
829
|
+
return [];
|
|
830
|
+
if (ctx.routingArms.length === 0 || pool.candidates.length < 2)
|
|
831
|
+
return [];
|
|
832
|
+
const floor = pool.learning?.minSamplesPerArm ?? t.poolMinSamples;
|
|
833
|
+
const ready = bandsWithFullCoverage(ctx.routingArms, pool.candidates, floor);
|
|
834
|
+
if (ready.length === 0)
|
|
835
|
+
return [];
|
|
836
|
+
const totalSamples = ctx.routingArms.reduce((acc, a) => acc + a.n, 0);
|
|
837
|
+
const adviceText = `The model_pool reward scoreboard has ≥${floor} samples for every candidate in band(s) ${ready.join(", ")} — set model_pool.policy: learned so routing exploits the measured per-band winners instead of the fixed heuristic.`;
|
|
838
|
+
const policyKeyPresent = opts?.specHasPath?.(["agent", "model_pool", "policy"]) ?? false;
|
|
839
|
+
const patch = {
|
|
840
|
+
target: spec?.target ?? "cli",
|
|
841
|
+
path: ["agent", "model_pool", "policy"],
|
|
842
|
+
op: policyKeyPresent ? "replace" : "add",
|
|
843
|
+
value: "learned",
|
|
844
|
+
rationale: `advise: scoreboard full coverage (every candidate ≥${floor} samples) in band(s) ${ready.join(", ")} across ${totalSamples} routed decisions`,
|
|
845
|
+
};
|
|
846
|
+
return [
|
|
847
|
+
{
|
|
848
|
+
id: "pool-policy-upgrade",
|
|
849
|
+
severity: "info",
|
|
850
|
+
summary: "model_pool scoreboard is ready — flip policy to learned",
|
|
851
|
+
evidence: [
|
|
852
|
+
`bands with every candidate ≥${floor} samples: ${ready.join(", ")}`,
|
|
853
|
+
`${totalSamples} routed decisions recorded across ${ctx.routingArms.length} arm(s)`,
|
|
854
|
+
`current policy: ${pool.policy}`,
|
|
855
|
+
],
|
|
856
|
+
counts: { readyBands: ready.length, totalSamples },
|
|
857
|
+
suggestion: patchOrAdvice(spec, patch, adviceText),
|
|
858
|
+
},
|
|
859
|
+
];
|
|
860
|
+
};
|
|
861
|
+
/**
|
|
862
|
+
* Pool candidate demotion: a candidate that is past the sample floor and
|
|
863
|
+
* loses to the band best by ≥ `poolDemotionGap` mean reward in EVERY band
|
|
864
|
+
* with full coverage is consistently wasted spend. ADVICE-ONLY by design:
|
|
865
|
+
* the candidate roster is human-owned (mirroring the `["agent","model"]`
|
|
866
|
+
* OPTIMIZABLE_PATHS exclusion), so this never emits a patch — it names the
|
|
867
|
+
* loser and lets a human edit `model_pool.candidates`.
|
|
868
|
+
*/
|
|
869
|
+
export const rulePoolCandidateDemotion = (ctx, opts) => {
|
|
870
|
+
const t = resolveThresholds(opts);
|
|
871
|
+
const pool = agentModelPool(opts?.spec);
|
|
872
|
+
if (pool === undefined || ctx.routingArms.length === 0 || pool.candidates.length < 3) {
|
|
873
|
+
// With only 2 candidates, "demote the loser" would collapse the pool —
|
|
874
|
+
// routing a 1-model pool is meaningless; leave that call entirely human.
|
|
875
|
+
return [];
|
|
876
|
+
}
|
|
877
|
+
const floor = pool.learning?.minSamplesPerArm ?? t.poolMinSamples;
|
|
878
|
+
const fullBands = bandsWithFullCoverage(ctx.routingArms, pool.candidates, floor);
|
|
879
|
+
if (fullBands.length === 0)
|
|
880
|
+
return [];
|
|
881
|
+
const losers = pool.candidates.filter((c) => fullBands.every((band) => {
|
|
882
|
+
const bandArms = ctx.routingArms.filter((a) => a.routeKey === band);
|
|
883
|
+
const best = Math.max(...bandArms.map((a) => a.meanReward));
|
|
884
|
+
const own = bandArms.find((a) => a.model === c.model);
|
|
885
|
+
return own !== undefined && own.meanReward <= best - t.poolDemotionGap;
|
|
886
|
+
}));
|
|
887
|
+
return losers.map((loser) => {
|
|
888
|
+
const detail = fullBands
|
|
889
|
+
.map((band) => {
|
|
890
|
+
const bandArms = ctx.routingArms.filter((a) => a.routeKey === band);
|
|
891
|
+
const best = Math.max(...bandArms.map((a) => a.meanReward));
|
|
892
|
+
const own = bandArms.find((a) => a.model === loser.model);
|
|
893
|
+
return `${band}: ${own?.meanReward.toFixed(3)} vs best ${best.toFixed(3)}`;
|
|
894
|
+
})
|
|
895
|
+
.join("; ");
|
|
896
|
+
return {
|
|
897
|
+
id: `pool-candidate-demotion:${loser.model}`,
|
|
898
|
+
severity: "info",
|
|
899
|
+
summary: `model_pool candidate ${loser.model} consistently loses`,
|
|
900
|
+
evidence: [
|
|
901
|
+
`mean reward trails the band best by ≥${t.poolDemotionGap} in every full-coverage band (${detail})`,
|
|
902
|
+
"the candidate roster is human-owned — remove it from model_pool.candidates by hand if the trend holds",
|
|
903
|
+
],
|
|
904
|
+
counts: { fullBands: fullBands.length },
|
|
905
|
+
suggestion: {
|
|
906
|
+
kind: "advice",
|
|
907
|
+
text: `Candidate ${loser.model} has enough samples and still trails the best arm by ≥${t.poolDemotionGap} mean reward in every measured band (${detail}). Consider removing it from model_pool.candidates — fewer arms also means less exploration spend. This is a roster change, so it is never auto-applied.`,
|
|
908
|
+
},
|
|
909
|
+
};
|
|
910
|
+
});
|
|
911
|
+
};
|
|
912
|
+
/**
|
|
913
|
+
* Stale exploitation: a `learned` pool whose scoreboard has full coverage but
|
|
914
|
+
* whose config never explores again — `explorationRate` unset/0 and the
|
|
915
|
+
* bandit is not `thompson` (which self-explores via posterior sampling). Once
|
|
916
|
+
* every arm clears the floor, such a pool hard-commits to the argmax forever
|
|
917
|
+
* and can never notice model drift or a candidate that improved. Proposes a
|
|
918
|
+
* small ε (0.05) via a whole-`learning`-block replace that PRESERVES the
|
|
919
|
+
* spec's other learning fields (whitelisted path; `optimize --from-advice`
|
|
920
|
+
* eval-gates it).
|
|
921
|
+
*/
|
|
922
|
+
export const rulePoolStaleExploitation = (ctx, opts) => {
|
|
923
|
+
const t = resolveThresholds(opts);
|
|
924
|
+
const spec = opts?.spec;
|
|
925
|
+
const pool = agentModelPool(spec);
|
|
926
|
+
if (pool === undefined || pool.policy !== "learned")
|
|
927
|
+
return [];
|
|
928
|
+
if (ctx.routingArms.length === 0 || pool.candidates.length < 2)
|
|
929
|
+
return [];
|
|
930
|
+
const learning = pool.learning ?? {};
|
|
931
|
+
// Already exploring (ε > 0) or self-exploring (thompson) → healthy.
|
|
932
|
+
if ((learning.explorationRate ?? 0) > 0 || learning.bandit === "thompson")
|
|
933
|
+
return [];
|
|
934
|
+
const floor = learning.minSamplesPerArm ?? t.poolMinSamples;
|
|
935
|
+
const ready = bandsWithFullCoverage(ctx.routingArms, pool.candidates, floor);
|
|
936
|
+
if (ready.length === 0)
|
|
937
|
+
return []; // still warming up — the floor explores for it
|
|
938
|
+
const proposedLearning = { ...learning, explorationRate: 0.05 };
|
|
939
|
+
const learningKeyPresent = opts?.specHasPath?.(["agent", "model_pool", "learning"]) ?? false;
|
|
940
|
+
const adviceText = "This learned model_pool has cleared its sample floor in every measured band and now exploits the argmax forever — it can no longer notice model drift or an improved candidate. Set model_pool.learning.explorationRate (e.g. 0.05) to keep sampling occasionally, or switch learning.bandit to thompson, which self-balances exploration.";
|
|
941
|
+
const patch = {
|
|
942
|
+
target: spec?.target ?? "cli",
|
|
943
|
+
path: ["agent", "model_pool", "learning"],
|
|
944
|
+
op: learningKeyPresent ? "replace" : "add",
|
|
945
|
+
value: proposedLearning,
|
|
946
|
+
rationale: `advise: learned pool converged (full coverage in band(s) ${ready.join(", ")}) with explorationRate 0 and a non-thompson bandit — pure exploitation goes stale`,
|
|
947
|
+
};
|
|
948
|
+
return [
|
|
949
|
+
{
|
|
950
|
+
id: "pool-stale-exploitation",
|
|
951
|
+
severity: "info",
|
|
952
|
+
summary: "converged learned model_pool never explores — add explorationRate",
|
|
953
|
+
evidence: [
|
|
954
|
+
`full-coverage band(s) at floor ${floor}: ${ready.join(", ")}`,
|
|
955
|
+
`learning.explorationRate is ${learning.explorationRate ?? "unset"} and bandit is ${learning.bandit ?? "epsilon-greedy"}`,
|
|
956
|
+
],
|
|
957
|
+
counts: { readyBands: ready.length },
|
|
958
|
+
suggestion: patchOrAdvice(spec, patch, adviceText),
|
|
959
|
+
},
|
|
960
|
+
];
|
|
961
|
+
};
|
|
801
962
|
export const ADVICE_RULES = [
|
|
802
963
|
ruleRepeatedToolFailures,
|
|
803
964
|
ruleTruncationPressure,
|
|
@@ -807,6 +968,9 @@ export const ADVICE_RULES = [
|
|
|
807
968
|
ruleFailureTaxonomy,
|
|
808
969
|
ruleLoopBreak,
|
|
809
970
|
ruleSubAgentSplit,
|
|
971
|
+
rulePoolPolicyUpgrade,
|
|
972
|
+
rulePoolCandidateDemotion,
|
|
973
|
+
rulePoolStaleExploitation,
|
|
810
974
|
];
|
|
811
975
|
/**
|
|
812
976
|
* Run every rule and rank the findings: warn before info, then by the
|
package/dist/index.js
CHANGED
|
@@ -23,11 +23,16 @@ import { createLogger } from "@crewhaus/logging";
|
|
|
23
23
|
import { McpHost } from "@crewhaus/mcp-host";
|
|
24
24
|
import { captureFacts, createMemoryStore, deriveMemoryDecision, summarizeDurableFacts, } from "@crewhaus/memory-store";
|
|
25
25
|
import { BUILTIN_DEFAULT_RULES, PermissionConfigError, parsePermissionsConfig, tagRules, } from "@crewhaus/permission-engine";
|
|
26
|
+
// Adaptive model routing — `crewhaus route status|reset` inspects/clears the
|
|
27
|
+
// per-(routeKey, model) reward scoreboard behind `agent.model_pool`; advise
|
|
28
|
+
// mines the same scoreboard into pool-policy suggestions.
|
|
29
|
+
import { openScoreboard } from "@crewhaus/routing-store";
|
|
26
30
|
import { runChatLoop } from "@crewhaus/runtime-core";
|
|
27
31
|
import { createSessionStore, evictExpiredSessions } from "@crewhaus/session-store";
|
|
28
32
|
import { createSkillTool, discoverSkills } from "@crewhaus/skills-registry";
|
|
29
33
|
import { loadCommands } from "@crewhaus/slash-commands";
|
|
30
34
|
import { parseSpec } from "@crewhaus/spec";
|
|
35
|
+
import { specHasPath } from "@crewhaus/spec-patch";
|
|
31
36
|
import { spawnSubAgent } from "@crewhaus/sub-agent-spawner";
|
|
32
37
|
import { ToolCatalog } from "@crewhaus/tool-catalog";
|
|
33
38
|
import { registerMcpServer } from "@crewhaus/tool-mcp";
|
|
@@ -260,8 +265,6 @@ import { RetireError, buildRetirementPlan, formatPlan, formatRetirementResult, r
|
|
|
260
265
|
// Item 25 — model right-sizing downshift search core (pure enumeration + cost
|
|
261
266
|
// projection + $/score ranking); side-effect-free so it is unit-testable.
|
|
262
267
|
import { buildRightSizeReport, enumerateSlotCandidates, } from "./right-size";
|
|
263
|
-
// Adaptive model routing — `crewhaus route status|reset` inspects/clears the
|
|
264
|
-
// per-(routeKey, model) reward scoreboard behind `agent.model_pool`.
|
|
265
268
|
import { runRoute } from "./route";
|
|
266
269
|
// Item 13 — `crewhaus scaffold-evals` + `init --with-evals`: day-one eval
|
|
267
270
|
// assets generated from the spec (deterministic template / one-shot model
|
|
@@ -1425,6 +1428,7 @@ function usageText() {
|
|
|
1425
1428
|
" [--factory-root <p>] [--docs-root <p>] [--demos-root <p>]",
|
|
1426
1429
|
" cost-summary --session <id> summarize cost_accrual events for a session",
|
|
1427
1430
|
" route status [--dir <root>] show the adaptive model_pool reward scoreboard",
|
|
1431
|
+
" route explain <session> [--dir <r>] replay a run's per-turn model_route decisions",
|
|
1428
1432
|
" route reset [--dir <root>] wipe the scoreboard (default root .crewhaus)",
|
|
1429
1433
|
" advise [--session <id> | --all] mine session logs for spec advice (item 14)",
|
|
1430
1434
|
" [--json] [-o <dir>] writes suggestions.json + report.html (default .crewhaus/advice)",
|
|
@@ -3442,6 +3446,16 @@ async function runRunCli(args, ir, specPath) {
|
|
|
3442
3446
|
ir.agent.modelTiers !== undefined
|
|
3443
3447
|
? { modelTiers: ir.agent.modelTiers }
|
|
3444
3448
|
: {}),
|
|
3449
|
+
// Adaptive model routing — thread `agent.model_pool` so `crewhaus run`
|
|
3450
|
+
// routes (and learns) exactly like the compiled cli bundle, not just the
|
|
3451
|
+
// single primary. Mutually exclusive with tiers/fallbacks in the spec;
|
|
3452
|
+
// a `--model` override forces a single model, so the pool applies only
|
|
3453
|
+
// without an override.
|
|
3454
|
+
...(ir.target === "cli" &&
|
|
3455
|
+
typeof modelOverride !== "string" &&
|
|
3456
|
+
ir.agent.modelPool !== undefined
|
|
3457
|
+
? { modelPool: ir.agent.modelPool }
|
|
3458
|
+
: {}),
|
|
3445
3459
|
// Section 55 / item 23 — thread the spec's failure_taxonomy so
|
|
3446
3460
|
// recovery-engine consults the user's named error classes (including
|
|
3447
3461
|
// the `switch-model` verdict) before its built-in flow.
|
|
@@ -7668,11 +7682,13 @@ async function runAdvise(args) {
|
|
|
7668
7682
|
if (args.flags["help"]) {
|
|
7669
7683
|
process.stdout.write("usage: crewhaus advise [--session <id> | --all] [--json] [-o <dir>]\n" +
|
|
7670
7684
|
"\n" +
|
|
7671
|
-
"mines .crewhaus/sessions (+ .crewhaus/audit) for spec advice:\n" +
|
|
7685
|
+
"mines .crewhaus/sessions (+ .crewhaus/audit + .crewhaus/routing) for spec advice:\n" +
|
|
7672
7686
|
" repeated tool failures, max_tokens truncation pressure, compaction\n" +
|
|
7673
7687
|
" thrash, permission-ask churn, stop-reason anomalies, learned\n" +
|
|
7674
7688
|
" failure_taxonomy + loop-break rules, sub-agent splits under\n" +
|
|
7675
|
-
" chronic context pressure\n" +
|
|
7689
|
+
" chronic context pressure, and model_pool scoreboard mining\n" +
|
|
7690
|
+
" (flip policy to learned once every candidate has enough samples;\n" +
|
|
7691
|
+
" name consistently-losing candidates — roster edits stay human)\n" +
|
|
7676
7692
|
"\n" +
|
|
7677
7693
|
" --session <id> mine one session (default: all sessions)\n" +
|
|
7678
7694
|
" --json print machine-readable findings to stdout\n" +
|
|
@@ -7722,17 +7738,34 @@ async function runAdvise(args) {
|
|
|
7722
7738
|
// The cwd spec enables patch suggestions; without one (or with a broken
|
|
7723
7739
|
// one) rules fall back to advice text — mining must not block on it.
|
|
7724
7740
|
let spec;
|
|
7741
|
+
let specText;
|
|
7725
7742
|
const specPath = join(process.cwd(), "crewhaus.yaml");
|
|
7726
7743
|
if (existsSync(specPath)) {
|
|
7727
7744
|
try {
|
|
7728
|
-
|
|
7745
|
+
specText = readFileSync(specPath, "utf-8");
|
|
7746
|
+
spec = parseSpec(specText);
|
|
7729
7747
|
}
|
|
7730
7748
|
catch (err) {
|
|
7731
7749
|
process.stderr.write(`[advise] crewhaus.yaml did not parse (${err.message}) — patch suggestions downgraded to advice\n`);
|
|
7732
7750
|
}
|
|
7733
7751
|
}
|
|
7734
|
-
|
|
7735
|
-
|
|
7752
|
+
// Adaptive model routing — the pool reward scoreboard, when the harness has
|
|
7753
|
+
// one. A missing/empty store simply produces no routing findings.
|
|
7754
|
+
let routingArms = [];
|
|
7755
|
+
try {
|
|
7756
|
+
routingArms = openScoreboard(join(process.cwd(), ".crewhaus")).snapshot();
|
|
7757
|
+
}
|
|
7758
|
+
catch {
|
|
7759
|
+
// Corrupt store must not block mining the sessions.
|
|
7760
|
+
}
|
|
7761
|
+
const ctx = buildAdviceContext(sessions, auditObjects, routingArms);
|
|
7762
|
+
const specTextForOps = specText;
|
|
7763
|
+
const findings = runAdviceRules(ctx, {
|
|
7764
|
+
...(spec !== undefined ? { spec } : {}),
|
|
7765
|
+
...(spec !== undefined && specTextForOps !== undefined
|
|
7766
|
+
? { specHasPath: (path) => specHasPath(specTextForOps, path) }
|
|
7767
|
+
: {}),
|
|
7768
|
+
});
|
|
7736
7769
|
const generatedAt = new Date().toISOString();
|
|
7737
7770
|
const suggestions = buildSuggestionsFile(findings, ctx.sessionIds, generatedAt);
|
|
7738
7771
|
const outDir = strFlag(args, "out") ?? join(process.cwd(), ".crewhaus", "advice");
|
package/dist/route.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { type ArmStats } from "@crewhaus/routing-store";
|
|
2
2
|
export type RouteArgs = {
|
|
3
|
-
readonly sub: "status" | "reset";
|
|
3
|
+
readonly sub: "status" | "reset" | "explain";
|
|
4
4
|
readonly dir: string;
|
|
5
|
+
/** Session id — required for `explain`. */
|
|
6
|
+
readonly session?: string;
|
|
5
7
|
};
|
|
6
|
-
/** Parse `route <sub> [--dir <root>]` argv (everything after `route`). */
|
|
8
|
+
/** Parse `route <sub> [<session>] [--dir <root>]` argv (everything after `route`). */
|
|
7
9
|
export declare function parseRouteArgs(argv: readonly string[]): RouteArgs;
|
|
8
10
|
/** Snapshot the scoreboard arms (sorted routeKey then model). */
|
|
9
11
|
export declare function loadArms(rootDir: string): ArmStats[];
|
|
@@ -15,5 +17,23 @@ export declare function loadArms(rootDir: string): ArmStats[];
|
|
|
15
17
|
export declare function formatRouteStatus(arms: readonly ArmStats[]): string;
|
|
16
18
|
/** Wipe the whole scoreboard. Returns the number of arms removed. */
|
|
17
19
|
export declare function resetRouting(rootDir: string): number;
|
|
20
|
+
/** One persisted `model_route` decision from a session log. */
|
|
21
|
+
export type RouteDecision = {
|
|
22
|
+
readonly turnNumber?: number;
|
|
23
|
+
readonly routeKey: string;
|
|
24
|
+
readonly model: string;
|
|
25
|
+
readonly policy: string;
|
|
26
|
+
readonly reason: string;
|
|
27
|
+
readonly explored?: boolean;
|
|
28
|
+
readonly policyVersion?: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Read the `model_route` decisions persisted in a session's event log at
|
|
32
|
+
* `<rootDir>/sessions/<sessionId>.jsonl`, in turn order. A missing log yields
|
|
33
|
+
* `[]`; a malformed line is skipped (the log is best-effort observability).
|
|
34
|
+
*/
|
|
35
|
+
export declare function readRouteDecisions(rootDir: string, sessionId: string): RouteDecision[];
|
|
36
|
+
/** Render a run's per-turn routing decisions as a table (turn order). */
|
|
37
|
+
export declare function formatRouteExplain(sessionId: string, decisions: readonly RouteDecision[]): string;
|
|
18
38
|
/** Run `crewhaus route …`, returning the text to print. */
|
|
19
39
|
export declare function runRoute(argv: readonly string[]): string;
|
package/dist/route.js
CHANGED
|
@@ -1,25 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `crewhaus route` — inspect and reset
|
|
3
|
-
* (the
|
|
2
|
+
* `crewhaus route` — inspect, explain, and reset adaptive model routing
|
|
3
|
+
* (the `agent.model_pool` reward scoreboard + per-turn routing decisions).
|
|
4
4
|
*
|
|
5
|
-
* route status
|
|
6
|
-
* route
|
|
5
|
+
* route status [--dir <root>] show the learned arms, best-per-bucket first
|
|
6
|
+
* route explain <session> [--dir …] replay a run's per-turn model_route decisions
|
|
7
|
+
* route reset [--dir <root>] wipe the scoreboard (kill switch)
|
|
7
8
|
*
|
|
8
|
-
* `--dir` points at the `.crewhaus` root (default `.crewhaus`); the
|
|
9
|
-
* at `<root>/routing/arms.jsonl
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* `--dir` points at the `.crewhaus` root (default `.crewhaus`); the scoreboard
|
|
10
|
+
* lives at `<root>/routing/arms.jsonl` and session logs at
|
|
11
|
+
* `<root>/sessions/<id>.jsonl`. `status` surfaces the ACCUMULATED learning a
|
|
12
|
+
* `learned` policy exploits; `explain` shows WHY each turn of one run picked
|
|
13
|
+
* the model it did (including ε-greedy exploration draws).
|
|
12
14
|
*/
|
|
13
|
-
import { existsSync, rmSync } from "node:fs";
|
|
15
|
+
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
14
16
|
import { join } from "node:path";
|
|
15
17
|
import { openScoreboard } from "@crewhaus/routing-store";
|
|
16
18
|
const DEFAULT_ROOT = ".crewhaus";
|
|
17
|
-
/** Parse `route <sub> [--dir <root>]` argv (everything after `route`). */
|
|
19
|
+
/** Parse `route <sub> [<session>] [--dir <root>]` argv (everything after `route`). */
|
|
18
20
|
export function parseRouteArgs(argv) {
|
|
19
21
|
let dir = DEFAULT_ROOT;
|
|
20
22
|
let sub;
|
|
23
|
+
let session;
|
|
21
24
|
for (let i = 0; i < argv.length; i++) {
|
|
22
25
|
const a = argv[i];
|
|
26
|
+
if (a === undefined)
|
|
27
|
+
continue;
|
|
23
28
|
if (a === "--dir") {
|
|
24
29
|
const v = argv[i + 1];
|
|
25
30
|
if (v === undefined)
|
|
@@ -27,16 +32,26 @@ export function parseRouteArgs(argv) {
|
|
|
27
32
|
dir = v;
|
|
28
33
|
i++;
|
|
29
34
|
}
|
|
30
|
-
else if (a === "status" || a === "reset") {
|
|
35
|
+
else if (sub === undefined && (a === "status" || a === "reset" || a === "explain")) {
|
|
36
|
+
// A subcommand keyword is only the subcommand when it comes FIRST; after
|
|
37
|
+
// that the same word is a plain positional (so `route explain status`
|
|
38
|
+
// explains a session literally named "status", not runs `status`).
|
|
31
39
|
sub = a;
|
|
32
40
|
}
|
|
41
|
+
else if (sub === "explain" && session === undefined && !a.startsWith("--")) {
|
|
42
|
+
session = a; // the session id positional (only `explain` takes one)
|
|
43
|
+
}
|
|
33
44
|
else {
|
|
34
|
-
throw new Error(`route: unknown argument "${a}" (expected: status | reset [--dir <root>])`);
|
|
45
|
+
throw new Error(`route: unknown argument "${a}" (expected: status | reset | explain <session> [--dir <root>])`);
|
|
35
46
|
}
|
|
36
47
|
}
|
|
37
|
-
if (sub === undefined)
|
|
38
|
-
throw new Error("route: expected a subcommand (status | reset)");
|
|
39
|
-
|
|
48
|
+
if (sub === undefined) {
|
|
49
|
+
throw new Error("route: expected a subcommand (status | explain <session> | reset)");
|
|
50
|
+
}
|
|
51
|
+
if (sub === "explain" && session === undefined) {
|
|
52
|
+
throw new Error("route explain: a <session> id is required");
|
|
53
|
+
}
|
|
54
|
+
return { sub, dir, ...(session !== undefined ? { session } : {}) };
|
|
40
55
|
}
|
|
41
56
|
/** Snapshot the scoreboard arms (sorted routeKey then model). */
|
|
42
57
|
export function loadArms(rootDir) {
|
|
@@ -80,11 +95,74 @@ export function resetRouting(rootDir) {
|
|
|
80
95
|
rmSync(path, { force: true });
|
|
81
96
|
return removed;
|
|
82
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Read the `model_route` decisions persisted in a session's event log at
|
|
100
|
+
* `<rootDir>/sessions/<sessionId>.jsonl`, in turn order. A missing log yields
|
|
101
|
+
* `[]`; a malformed line is skipped (the log is best-effort observability).
|
|
102
|
+
*/
|
|
103
|
+
export function readRouteDecisions(rootDir, sessionId) {
|
|
104
|
+
// Guard against a hostile session id escaping the sessions dir (e.g.
|
|
105
|
+
// `../../etc/passwd`). Real ids are `sess_<hex>`; anything with a path
|
|
106
|
+
// separator or `..` is rejected outright.
|
|
107
|
+
if (/[/\\]/.test(sessionId) || sessionId.includes("..") || sessionId.length === 0) {
|
|
108
|
+
throw new Error(`route explain: invalid session id "${sessionId}"`);
|
|
109
|
+
}
|
|
110
|
+
const path = join(rootDir, "sessions", `${sessionId}.jsonl`);
|
|
111
|
+
if (!existsSync(path))
|
|
112
|
+
return [];
|
|
113
|
+
const out = [];
|
|
114
|
+
for (const line of readFileSync(path, "utf8").split("\n")) {
|
|
115
|
+
const trimmed = line.trim();
|
|
116
|
+
if (trimmed.length === 0)
|
|
117
|
+
continue;
|
|
118
|
+
let rec;
|
|
119
|
+
try {
|
|
120
|
+
rec = JSON.parse(trimmed);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (rec.kind !== "model_route" || typeof rec.payload !== "object" || rec.payload === null) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const p = rec.payload;
|
|
129
|
+
if (typeof p["routeKey"] !== "string" || typeof p["model"] !== "string")
|
|
130
|
+
continue;
|
|
131
|
+
out.push({
|
|
132
|
+
...(typeof p["turnNumber"] === "number" ? { turnNumber: p["turnNumber"] } : {}),
|
|
133
|
+
routeKey: p["routeKey"],
|
|
134
|
+
model: p["model"],
|
|
135
|
+
policy: typeof p["policy"] === "string" ? p["policy"] : "?",
|
|
136
|
+
reason: typeof p["reason"] === "string" ? p["reason"] : "",
|
|
137
|
+
...(typeof p["explored"] === "boolean" ? { explored: p["explored"] } : {}),
|
|
138
|
+
...(typeof p["policyVersion"] === "string" ? { policyVersion: p["policyVersion"] } : {}),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
/** Render a run's per-turn routing decisions as a table (turn order). */
|
|
144
|
+
export function formatRouteExplain(sessionId, decisions) {
|
|
145
|
+
if (decisions.length === 0) {
|
|
146
|
+
return `No model_route decisions recorded for session ${sessionId}. (Only runs whose spec declares \`agent.model_pool\` persist routing decisions.)`;
|
|
147
|
+
}
|
|
148
|
+
const lines = [`session ${sessionId} — ${decisions.length} routing decision(s):`, ""];
|
|
149
|
+
lines.push(`${"turn".padStart(4)} ${"band".padEnd(5)} ${"model".padEnd(28)} ${"policy".padEnd(10)} ${"pick".padEnd(9)} reason`);
|
|
150
|
+
for (const d of decisions) {
|
|
151
|
+
const turn = d.turnNumber !== undefined ? String(d.turnNumber) : "-";
|
|
152
|
+
const pick = d.explored ? "explore" : "exploit";
|
|
153
|
+
lines.push(`${turn.padStart(4)} ${d.routeKey.padEnd(5)} ${d.model.padEnd(28)} ${d.policy.padEnd(10)} ${pick.padEnd(9)} ${d.reason}`);
|
|
154
|
+
}
|
|
155
|
+
return lines.join("\n");
|
|
156
|
+
}
|
|
83
157
|
/** Run `crewhaus route …`, returning the text to print. */
|
|
84
158
|
export function runRoute(argv) {
|
|
85
159
|
const args = parseRouteArgs(argv);
|
|
86
160
|
if (args.sub === "status")
|
|
87
161
|
return formatRouteStatus(loadArms(args.dir));
|
|
162
|
+
if (args.sub === "explain") {
|
|
163
|
+
const session = args.session; // parseRouteArgs guarantees it for explain
|
|
164
|
+
return formatRouteExplain(session, readRouteDecisions(args.dir, session));
|
|
165
|
+
}
|
|
88
166
|
const removed = resetRouting(args.dir);
|
|
89
167
|
return `Reset routing scoreboard at ${join(args.dir, "routing", "arms.jsonl")} (${removed} arm${removed === 1 ? "" : "s"} removed).`;
|
|
90
168
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crewhaus",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CrewHaus — the meta-harness compiler for AI agents. Compile one crewhaus.yaml spec into a CLI agent, channel bot, RAG pipeline, multi-agent crew, eval harness, voice or browser agent, and more.",
|
|
6
6
|
"keywords": [
|
|
@@ -31,94 +31,94 @@
|
|
|
31
31
|
"test": "bun test src"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@crewhaus/adapter-anthropic": "0.2.
|
|
35
|
-
"@crewhaus/agent-context-isolation": "0.2.
|
|
36
|
-
"@crewhaus/audit-log": "0.2.
|
|
37
|
-
"@crewhaus/eval-optimizer-orchestrator": "0.2.
|
|
38
|
-
"@crewhaus/prompt-optimizer": "0.2.
|
|
39
|
-
"@crewhaus/prompt-optimizer-claude": "0.2.
|
|
40
|
-
"@crewhaus/justification-judge-claude": "0.2.
|
|
41
|
-
"@crewhaus/spec-patch": "0.2.
|
|
42
|
-
"@crewhaus/canary-controller": "0.2.
|
|
43
|
-
"@crewhaus/compiler": "0.2.
|
|
44
|
-
"@crewhaus/computer-use-driver": "0.2.
|
|
45
|
-
"@crewhaus/context-bundle": "0.2.
|
|
46
|
-
"@crewhaus/cost-tracker": "0.2.
|
|
47
|
-
"@crewhaus/data-retention-engine": "0.2.
|
|
48
|
-
"@crewhaus/dataset-registry": "0.2.
|
|
49
|
-
"@crewhaus/deployment-controller": "0.2.
|
|
50
|
-
"@crewhaus/egress-classifier": "0.2.
|
|
51
|
-
"@crewhaus/egress-matcher-semantic": "0.2.
|
|
52
|
-
"@crewhaus/embedder": "0.2.
|
|
53
|
-
"@crewhaus/errors": "0.2.
|
|
54
|
-
"@crewhaus/eval-dataset": "0.2.
|
|
55
|
-
"@crewhaus/eval-grader": "0.2.
|
|
56
|
-
"@crewhaus/eval-judge": "0.2.
|
|
57
|
-
"@crewhaus/eval-report": "0.2.
|
|
58
|
-
"@crewhaus/eval-runner": "0.2.
|
|
59
|
-
"@crewhaus/event-log": "0.2.
|
|
60
|
-
"@crewhaus/gateway-server": "0.2.
|
|
61
|
-
"@crewhaus/grader-safety-classifiers": "0.2.
|
|
62
|
-
"@crewhaus/hooks-engine": "0.2.
|
|
63
|
-
"@crewhaus/infra-utils": "0.2.
|
|
64
|
-
"@crewhaus/ir": "0.2.
|
|
65
|
-
"@crewhaus/ir-passes": "0.2.
|
|
66
|
-
"@crewhaus/logging": "0.2.
|
|
67
|
-
"@crewhaus/mcp-host": "0.2.
|
|
68
|
-
"@crewhaus/memory-store": "0.2.
|
|
69
|
-
"@crewhaus/model-router": "0.2.
|
|
70
|
-
"@crewhaus/module-marketplace-client": "0.2.
|
|
71
|
-
"@crewhaus/migration-engine": "0.2.
|
|
72
|
-
"@crewhaus/migration-runner": "0.2.
|
|
73
|
-
"@crewhaus/plugin-registry": "0.2.
|
|
74
|
-
"@crewhaus/plugin-sdk": "0.2.
|
|
75
|
-
"@crewhaus/permission-engine": "0.2.
|
|
76
|
-
"@crewhaus/pii-redactor": "0.2.
|
|
77
|
-
"@crewhaus/prompt-injection-detector": "0.2.
|
|
78
|
-
"@crewhaus/regression-runner": "0.2.
|
|
79
|
-
"@crewhaus/routing-store": "0.2.
|
|
80
|
-
"@crewhaus/run-context": "0.2.
|
|
81
|
-
"@crewhaus/runtime-core": "0.2.
|
|
82
|
-
"@crewhaus/secrets-manager": "0.2.
|
|
83
|
-
"@crewhaus/session-store": "0.2.
|
|
84
|
-
"@crewhaus/spec-registry": "0.2.
|
|
85
|
-
"@crewhaus/skills-registry": "0.2.
|
|
86
|
-
"@crewhaus/slash-commands": "0.2.
|
|
87
|
-
"@crewhaus/smoke-harness": "0.2.
|
|
88
|
-
"@crewhaus/spec": "0.2.
|
|
89
|
-
"@crewhaus/sub-agent-spawner": "0.2.
|
|
90
|
-
"@crewhaus/template-marketplace-client": "0.2.
|
|
91
|
-
"@crewhaus/template-registry": "0.2.
|
|
92
|
-
"@crewhaus/tenancy": "0.2.
|
|
93
|
-
"@crewhaus/trace-event-bus": "0.2.
|
|
94
|
-
"@crewhaus/tool-bash": "0.2.
|
|
95
|
-
"@crewhaus/tool-builder": "0.2.
|
|
96
|
-
"@crewhaus/target-cli": "0.2.
|
|
97
|
-
"@crewhaus/target-eval-bundle": "0.2.
|
|
98
|
-
"@crewhaus/tool-catalog": "0.2.
|
|
99
|
-
"@crewhaus/tool-code-execution": "0.2.
|
|
100
|
-
"@crewhaus/tool-codegraph": "0.2.
|
|
101
|
-
"@crewhaus/tool-fetch": "0.2.
|
|
102
|
-
"@crewhaus/tool-fs": "0.2.
|
|
103
|
-
"@crewhaus/tool-image": "0.2.
|
|
104
|
-
"@crewhaus/tool-image-generation": "0.2.
|
|
105
|
-
"@crewhaus/tool-document-ingest": "0.2.
|
|
106
|
-
"@crewhaus/tool-mcp": "0.2.
|
|
107
|
-
"@crewhaus/tool-memory": "0.2.
|
|
108
|
-
"@crewhaus/tool-mouse-keyboard": "0.2.
|
|
109
|
-
"@crewhaus/tool-navigate": "0.2.
|
|
110
|
-
"@crewhaus/tool-permission-matcher": "0.2.
|
|
111
|
-
"@crewhaus/tool-screen-capture": "0.2.
|
|
112
|
-
"@crewhaus/tool-task": "0.2.
|
|
113
|
-
"@crewhaus/tool-todo": "0.2.
|
|
114
|
-
"@crewhaus/tool-vision-grounding": "0.2.
|
|
115
|
-
"@crewhaus/tool-web": "0.2.
|
|
116
|
-
"@crewhaus/docker-images": "0.2.
|
|
117
|
-
"@crewhaus/crewhaus-cloud": "0.2.
|
|
118
|
-
"@crewhaus/federation-discovery": "0.2.
|
|
119
|
-
"@crewhaus/sandbox": "0.2.
|
|
120
|
-
"@crewhaus/sandbox-image-registry": "0.2.
|
|
121
|
-
"@crewhaus/compliance-controls": "0.2.
|
|
34
|
+
"@crewhaus/adapter-anthropic": "0.2.2",
|
|
35
|
+
"@crewhaus/agent-context-isolation": "0.2.2",
|
|
36
|
+
"@crewhaus/audit-log": "0.2.2",
|
|
37
|
+
"@crewhaus/eval-optimizer-orchestrator": "0.2.2",
|
|
38
|
+
"@crewhaus/prompt-optimizer": "0.2.2",
|
|
39
|
+
"@crewhaus/prompt-optimizer-claude": "0.2.2",
|
|
40
|
+
"@crewhaus/justification-judge-claude": "0.2.2",
|
|
41
|
+
"@crewhaus/spec-patch": "0.2.2",
|
|
42
|
+
"@crewhaus/canary-controller": "0.2.2",
|
|
43
|
+
"@crewhaus/compiler": "0.2.2",
|
|
44
|
+
"@crewhaus/computer-use-driver": "0.2.2",
|
|
45
|
+
"@crewhaus/context-bundle": "0.2.2",
|
|
46
|
+
"@crewhaus/cost-tracker": "0.2.2",
|
|
47
|
+
"@crewhaus/data-retention-engine": "0.2.2",
|
|
48
|
+
"@crewhaus/dataset-registry": "0.2.2",
|
|
49
|
+
"@crewhaus/deployment-controller": "0.2.2",
|
|
50
|
+
"@crewhaus/egress-classifier": "0.2.2",
|
|
51
|
+
"@crewhaus/egress-matcher-semantic": "0.2.2",
|
|
52
|
+
"@crewhaus/embedder": "0.2.2",
|
|
53
|
+
"@crewhaus/errors": "0.2.2",
|
|
54
|
+
"@crewhaus/eval-dataset": "0.2.2",
|
|
55
|
+
"@crewhaus/eval-grader": "0.2.2",
|
|
56
|
+
"@crewhaus/eval-judge": "0.2.2",
|
|
57
|
+
"@crewhaus/eval-report": "0.2.2",
|
|
58
|
+
"@crewhaus/eval-runner": "0.2.2",
|
|
59
|
+
"@crewhaus/event-log": "0.2.2",
|
|
60
|
+
"@crewhaus/gateway-server": "0.2.2",
|
|
61
|
+
"@crewhaus/grader-safety-classifiers": "0.2.2",
|
|
62
|
+
"@crewhaus/hooks-engine": "0.2.2",
|
|
63
|
+
"@crewhaus/infra-utils": "0.2.2",
|
|
64
|
+
"@crewhaus/ir": "0.2.2",
|
|
65
|
+
"@crewhaus/ir-passes": "0.2.2",
|
|
66
|
+
"@crewhaus/logging": "0.2.2",
|
|
67
|
+
"@crewhaus/mcp-host": "0.2.2",
|
|
68
|
+
"@crewhaus/memory-store": "0.2.2",
|
|
69
|
+
"@crewhaus/model-router": "0.2.2",
|
|
70
|
+
"@crewhaus/module-marketplace-client": "0.2.2",
|
|
71
|
+
"@crewhaus/migration-engine": "0.2.2",
|
|
72
|
+
"@crewhaus/migration-runner": "0.2.2",
|
|
73
|
+
"@crewhaus/plugin-registry": "0.2.2",
|
|
74
|
+
"@crewhaus/plugin-sdk": "0.2.2",
|
|
75
|
+
"@crewhaus/permission-engine": "0.2.2",
|
|
76
|
+
"@crewhaus/pii-redactor": "0.2.2",
|
|
77
|
+
"@crewhaus/prompt-injection-detector": "0.2.2",
|
|
78
|
+
"@crewhaus/regression-runner": "0.2.2",
|
|
79
|
+
"@crewhaus/routing-store": "0.2.2",
|
|
80
|
+
"@crewhaus/run-context": "0.2.2",
|
|
81
|
+
"@crewhaus/runtime-core": "0.2.2",
|
|
82
|
+
"@crewhaus/secrets-manager": "0.2.2",
|
|
83
|
+
"@crewhaus/session-store": "0.2.2",
|
|
84
|
+
"@crewhaus/spec-registry": "0.2.2",
|
|
85
|
+
"@crewhaus/skills-registry": "0.2.2",
|
|
86
|
+
"@crewhaus/slash-commands": "0.2.2",
|
|
87
|
+
"@crewhaus/smoke-harness": "0.2.2",
|
|
88
|
+
"@crewhaus/spec": "0.2.2",
|
|
89
|
+
"@crewhaus/sub-agent-spawner": "0.2.2",
|
|
90
|
+
"@crewhaus/template-marketplace-client": "0.2.2",
|
|
91
|
+
"@crewhaus/template-registry": "0.2.2",
|
|
92
|
+
"@crewhaus/tenancy": "0.2.2",
|
|
93
|
+
"@crewhaus/trace-event-bus": "0.2.2",
|
|
94
|
+
"@crewhaus/tool-bash": "0.2.2",
|
|
95
|
+
"@crewhaus/tool-builder": "0.2.2",
|
|
96
|
+
"@crewhaus/target-cli": "0.2.2",
|
|
97
|
+
"@crewhaus/target-eval-bundle": "0.2.2",
|
|
98
|
+
"@crewhaus/tool-catalog": "0.2.2",
|
|
99
|
+
"@crewhaus/tool-code-execution": "0.2.2",
|
|
100
|
+
"@crewhaus/tool-codegraph": "0.2.2",
|
|
101
|
+
"@crewhaus/tool-fetch": "0.2.2",
|
|
102
|
+
"@crewhaus/tool-fs": "0.2.2",
|
|
103
|
+
"@crewhaus/tool-image": "0.2.2",
|
|
104
|
+
"@crewhaus/tool-image-generation": "0.2.2",
|
|
105
|
+
"@crewhaus/tool-document-ingest": "0.2.2",
|
|
106
|
+
"@crewhaus/tool-mcp": "0.2.2",
|
|
107
|
+
"@crewhaus/tool-memory": "0.2.2",
|
|
108
|
+
"@crewhaus/tool-mouse-keyboard": "0.2.2",
|
|
109
|
+
"@crewhaus/tool-navigate": "0.2.2",
|
|
110
|
+
"@crewhaus/tool-permission-matcher": "0.2.2",
|
|
111
|
+
"@crewhaus/tool-screen-capture": "0.2.2",
|
|
112
|
+
"@crewhaus/tool-task": "0.2.2",
|
|
113
|
+
"@crewhaus/tool-todo": "0.2.2",
|
|
114
|
+
"@crewhaus/tool-vision-grounding": "0.2.2",
|
|
115
|
+
"@crewhaus/tool-web": "0.2.2",
|
|
116
|
+
"@crewhaus/docker-images": "0.2.2",
|
|
117
|
+
"@crewhaus/crewhaus-cloud": "0.2.2",
|
|
118
|
+
"@crewhaus/federation-discovery": "0.2.2",
|
|
119
|
+
"@crewhaus/sandbox": "0.2.2",
|
|
120
|
+
"@crewhaus/sandbox-image-registry": "0.2.2",
|
|
121
|
+
"@crewhaus/compliance-controls": "0.2.2",
|
|
122
122
|
"yaml": "^2.6.0"
|
|
123
123
|
},
|
|
124
124
|
"devDependencies": {
|