@deepstrike/sdk 0.2.17 → 0.2.19

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.
@@ -84,7 +84,6 @@ export class HarnessLoop {
84
84
  }
85
85
  async *stream(request) {
86
86
  const kernel = getKernel();
87
- const pipeline = new kernel.EvalPipeline({ extractSkillOnPass: true });
88
87
  const criteria = request.criteria ?? [];
89
88
  let currentGoal = request.goal;
90
89
  let lastIterations = 0;
@@ -126,11 +125,10 @@ export class HarnessLoop {
126
125
  }
127
126
  }
128
127
  yield { type: "supervising" };
129
- const evalAction = pipeline.feedOutcome(request.goal, criteria, lastResult, attempt);
130
- if (evalAction.kind !== "evaluate")
131
- break;
128
+ // #6 (0.5.0): the eval/verdict compute is the kernel's stateless free functions (was the
129
+ // EvalPipeline state machine). Build the eval prompt, call the eval LLM, parse the verdict.
130
+ const evalMsgs = kernel.buildEvalMessages(request.goal, criteria, lastResult, attempt, true);
132
131
  let evalText = "";
133
- const evalMsgs = evalAction.messages ?? [];
134
132
  const evalContext = {
135
133
  systemText: evalMsgs.filter((m) => m.role === "system").map((m) => m.content).join("\n\n"),
136
134
  turns: evalMsgs.filter((m) => m.role !== "system"),
@@ -139,18 +137,16 @@ export class HarnessLoop {
139
137
  if (evt.type === "text_delta")
140
138
  evalText += evt.delta;
141
139
  }
142
- const doneAction = pipeline.feedEvalResult(evalText);
143
- if (doneAction.kind !== "done")
144
- break;
140
+ const parsed = kernel.parseVerdict(evalText);
145
141
  const verdict = {
146
- passed: doneAction.passed ?? false,
147
- overallScore: doneAction.overallScore ?? 0,
148
- feedback: doneAction.feedback ?? "",
149
- details: doneAction.details ?? [],
142
+ passed: parsed.passed,
143
+ overallScore: parsed.overallScore,
144
+ feedback: parsed.feedback,
145
+ details: parsed.details ?? [],
150
146
  };
151
147
  if (verdict.passed) {
152
- if (doneAction.skillCandidate && this.skillDir) {
153
- const { name, description, whenToUse, content } = doneAction.skillCandidate;
148
+ if (parsed.skillCandidate && this.skillDir) {
149
+ const { name, description, whenToUse, content } = parsed.skillCandidate;
154
150
  const fm = ["---", `name: ${name}`, `description: ${description}`,
155
151
  whenToUse ? `when_to_use: ${whenToUse}` : null, "---", ""]
156
152
  .filter(Boolean).join("\n");
@@ -162,7 +158,6 @@ export class HarnessLoop {
162
158
  yield { type: "revising", verdict };
163
159
  currentGoal = `${request.goal}\n\n[Attempt ${attempt} feedback: ${verdict.feedback}]`;
164
160
  lastResult = "";
165
- pipeline.reset();
166
161
  }
167
162
  yield { type: "max_attempts_reached" };
168
163
  }
package/dist/index.d.ts CHANGED
@@ -68,7 +68,7 @@ export { SinglePassHarness, EvalLoopHarness, HarnessLoop } from "./harness/harne
68
68
  export type { HarnessRequest, HarnessOutcome, HarnessLoopOptions, QualityGate } from "./harness/harness.js";
69
69
  export type { Message, ToolCall, ToolResult, ToolSchema, ContentPart, TextPart, ImagePart, AudioPart, StreamEvent, TextDelta, ThinkingDelta, ToolCallEvent, ToolChunk, ToolDeltaEvent, ToolSuspendEvent, ToolResultEvent, DoneEvent, ErrorEvent, PermissionRequestEvent, PermissionResolvedEvent, PermissionResponse, LLMProvider, RetryConfig, TokenUsage, ProviderToolSpec, ProviderRunState, ProviderReplay, RenderedContext, ReplayabilityAssessment, } from "./types.js";
70
70
  export type { AgentCapabilityFilter, AgentIdentity, AgentIsolation, AgentRunSpec, AgentProcessChangedObservation, ContextInheritance, KernelAgentRole, LoopResult, MilestoneCheckResult, MilestoneContract, MilestonePhase, MilestonePolicy, SubAgentResult, TerminationReason, WorkflowSpec, WorkflowNodeSpec, WorkflowTaskSpec, WorkflowSpawnInfo, } from "./types/agent.js";
71
- export { agentIdentitySub, agentRunSpecToKernel, milestoneCheckFail, milestoneCheckPass, milestoneCheckResultToKernel, subAgentResultToKernel, workflowSpecToKernel, workflowNodeSpecToKernel, submitWorkflowNodesToKernel, submitWorkflowToKernel, submitWorkflowNodesTool, startWorkflowTool, fanoutSynthesize, generateAndFilter, verifyRules, } from "./types/agent.js";
71
+ export { agentIdentitySub, agentRunSpecToKernel, milestoneCheckFail, milestoneCheckPass, milestoneCheckResultToKernel, subAgentResultToKernel, workflowSpecToKernel, workflowNodeSpecToKernel, submitWorkflowNodesToKernel, submitWorkflowToKernel, submitWorkflowNodesTool, startWorkflowTool, fanoutSynthesize, generateAndFilter, verifyRules, genEval, } from "./types/agent.js";
72
72
  export type { AcceptanceCriterion, VerificationContract, ContractCheckResult, } from "./collaboration/contract.js";
73
73
  export { ContractBuilder, formatContractForSystemPrompt, contractToCriteriaStrings, } from "./collaboration/contract.js";
74
74
  export { AgentPool } from "./collaboration/pool.js";
package/dist/index.js CHANGED
@@ -46,7 +46,7 @@ export { PermissionManager, PermissionMode } from "./safety/permissions.js";
46
46
  export { Governance, governancePolicyToKernelEvent } from "./governance.js";
47
47
  // ── Harness ────────────────────────────────────────────────────────────────
48
48
  export { SinglePassHarness, EvalLoopHarness, HarnessLoop } from "./harness/harness.js";
49
- export { agentIdentitySub, agentRunSpecToKernel, milestoneCheckFail, milestoneCheckPass, milestoneCheckResultToKernel, subAgentResultToKernel, workflowSpecToKernel, workflowNodeSpecToKernel, submitWorkflowNodesToKernel, submitWorkflowToKernel, submitWorkflowNodesTool, startWorkflowTool, fanoutSynthesize, generateAndFilter, verifyRules, } from "./types/agent.js";
49
+ export { agentIdentitySub, agentRunSpecToKernel, milestoneCheckFail, milestoneCheckPass, milestoneCheckResultToKernel, subAgentResultToKernel, workflowSpecToKernel, workflowNodeSpecToKernel, submitWorkflowNodesToKernel, submitWorkflowToKernel, submitWorkflowNodesTool, startWorkflowTool, fanoutSynthesize, generateAndFilter, verifyRules, genEval, } from "./types/agent.js";
50
50
  export { ContractBuilder, formatContractForSystemPrompt, contractToCriteriaStrings, } from "./collaboration/contract.js";
51
51
  export { AgentPool } from "./collaboration/pool.js";
52
52
  export { KERNEL_ROLE_MAP } from "./collaboration/pool.js";
package/dist/kernel.d.ts CHANGED
@@ -77,13 +77,11 @@ interface NativeCriterion {
77
77
  required: boolean;
78
78
  weight?: number;
79
79
  }
80
- interface EvalPipelineAction {
81
- kind: "evaluate" | "done";
82
- messages?: Message[];
83
- passed?: boolean;
84
- overallScore?: number;
85
- feedback?: string;
86
- details?: Array<{
80
+ export interface Verdict {
81
+ passed: boolean;
82
+ overallScore: number;
83
+ feedback: string;
84
+ details: Array<{
87
85
  criterion: string;
88
86
  passed: boolean;
89
87
  score: number;
@@ -96,11 +94,6 @@ interface EvalPipelineAction {
96
94
  content: string;
97
95
  };
98
96
  }
99
- interface EvalPipelineInstance {
100
- feedOutcome(goal: string, criteria: NativeCriterion[], result: string, attempt: number): EvalPipelineAction;
101
- feedEvalResult(content: string): EvalPipelineAction;
102
- reset(): void;
103
- }
104
97
  interface IdlePipelineAction {
105
98
  kind: "synthesize_insights" | "commit_memories" | "noop" | "aborted";
106
99
  messages?: Message[];
@@ -156,9 +149,9 @@ interface KernelModule {
156
149
  timeoutMs?: bigint;
157
150
  }) => KernelRuntimeInstance;
158
151
  SignalRouter: new (maxQueueSize: number) => SignalRouterInstance;
159
- EvalPipeline: new (options?: {
160
- extractSkillOnPass?: boolean;
161
- }) => EvalPipelineInstance;
152
+ buildEvalMessages(goal: string, criteria: NativeCriterion[], result: string, attempt: number, extractSkillOnPass: boolean): Message[];
153
+ parseVerdict(content: string): Verdict;
154
+ verdictOutputSchema(extractSkillOnPass: boolean): string;
162
155
  IdlePipeline: new (agentId: string) => IdlePipelineInstance;
163
156
  }
164
157
  export declare function getKernel(): KernelModule;
@@ -24,6 +24,9 @@ export function skillMetadataToKernel(skill) {
24
24
  out.when_to_use = skill.whenToUse;
25
25
  if (skill.effort !== undefined)
26
26
  out.effort = skill.effort;
27
+ // P1-B: forward declared tool ids (additive; omitted when empty so existing skills' wire is unchanged).
28
+ if (skill.allowedTools?.length)
29
+ out.allowed_tools = skill.allowedTools;
27
30
  return out;
28
31
  }
29
32
  export function messageToKernelMessage(message) {
@@ -15,6 +15,30 @@ import { LargeResultSpool } from "./large-result-spool.js";
15
15
  export interface SchedulerBudget {
16
16
  maxWallMs?: number;
17
17
  }
18
+ /** P0-C tool-gating telemetry: per-LLM-turn metrics, emitted via `RuntimeOptions.onTurnMetrics`.
19
+ * Pure observation — no behavior change. Feeds the go/no-go analysis for epoch skill gating (P1-B):
20
+ * - `toolsExposed` vs `toolsCalled` quantifies over-exposure.
21
+ * - `activeSkill` across consecutive turns yields the skill *dwell* `D` (how long a skill stays
22
+ * loaded) — the break-even input that decides whether dynamic gating beats the cache-bust cost.
23
+ * - `cacheReadTokens` / `cacheCreationTokens` give the prompt-cache hit baseline to compare against
24
+ * after B/D ship. */
25
+ export interface TurnMetrics {
26
+ /** 1-based kernel turn this LLM call belongs to. */
27
+ turn: number;
28
+ /** Number of tool schemas exposed to the model this turn (base + meta, after run-profile gating). */
29
+ toolsExposed: number;
30
+ /** Number of tool calls the model emitted this turn. */
31
+ toolsCalled: number;
32
+ /** The skill loaded and in effect going into this turn (the most recent `skill` tool call's name),
33
+ * or undefined if none is active. Consecutive equal values measure dwell. */
34
+ activeSkill?: string;
35
+ /** Full prompt size the provider reported (uncached + cache read + cache creation). */
36
+ inputTokens: number;
37
+ /** Tokens served from the prompt cache this turn (Anthropic `cache_read_input_tokens`). */
38
+ cacheReadTokens: number;
39
+ /** Tokens written to the prompt cache this turn (Anthropic `cache_creation_input_tokens`). */
40
+ cacheCreationTokens: number;
41
+ }
18
42
  export interface RuntimeOptions {
19
43
  provider: LLMProvider;
20
44
  /** M4/G5: cumulative token cap for this run (the kernel's `max_total_tokens`). A workflow node's
@@ -98,6 +122,20 @@ export interface RuntimeOptions {
98
122
  }) => Promise<MilestoneCheckResult> | MilestoneCheckResult;
99
123
  /** Passed to kernel start_run for role/isolation metadata. */
100
124
  runSpec?: AgentRunSpec;
125
+ /** P0-A tool gating: a static per-run tool profile — only these tool ids (plus the
126
+ * skill/memory/knowledge/update_plan meta-tools) are exposed to the model each turn.
127
+ * Sugar that lowers to the same `capability_filter` sub-agents use; byte-stable across
128
+ * the run, so it never busts the prompt-cache prefix. Augments `runSpec`'s filter when
129
+ * both are set; synthesizes a minimal run spec when `runSpec` is absent. Omitted/empty
130
+ * ⇒ all registered tools exposed (no gating). */
131
+ allowedToolIds?: string[];
132
+ /** P0-C: optional per-turn metrics sink for tool-gating telemetry (see `TurnMetrics`). Pure
133
+ * observation; invoked once per LLM turn. Never throws into the run loop (errors are swallowed). */
134
+ onTurnMetrics?: (metrics: TurnMetrics) => void;
135
+ /** P1-B/D stable-core: tool ids that stay exposed even when an active skill narrows the toolset
136
+ * (read/search/bash etc.). Empty/absent ⇒ skills narrow to exactly their declared `allowed_tools`
137
+ * + meta-tools. Opt-in: with no skill declaring `allowed_tools`, gating never engages. */
138
+ stableCoreToolIds?: string[];
101
139
  /** Loaded via load_milestone_contract before run start. */
102
140
  milestoneContract?: MilestoneContract;
103
141
  /** Custom sub-agent host driver; defaults to SubAgentOrchestrator. */
@@ -819,15 +819,19 @@ export class RuntimeRunner {
819
819
  if (this.opts.skillDir) {
820
820
  const { scanSkillDir } = await import("../skills/loader.js");
821
821
  const metas = await scanSkillDir(this.opts.skillDir);
822
+ // P1-B: pass the full SkillMetadata (incl. `allowedTools`) straight through — re-mapping it
823
+ // field-by-field previously dropped `allowedTools`.
822
824
  kernelApply(runtime, this.pendingObservations, {
823
825
  kind: "set_available_skills",
824
- skills: metas.map((m) => skillMetadataToKernel({
825
- name: m.name,
826
- description: m.description,
827
- whenToUse: m.whenToUse,
828
- effort: m.effort,
829
- estimatedTokens: m.estimatedTokens ?? 0,
830
- })),
826
+ skills: metas.map(m => skillMetadataToKernel(m)),
827
+ });
828
+ }
829
+ // P1-B/D: configure the stable-core tool ids (always exposed under skill gating). Empty/absent
830
+ // ⇒ skills narrow to exactly their declared tools + meta-tools.
831
+ if (this.opts.stableCoreToolIds?.length) {
832
+ kernelApply(runtime, this.pendingObservations, {
833
+ kind: "set_stable_core_tools",
834
+ tool_ids: this.opts.stableCoreToolIds,
831
835
  });
832
836
  }
833
837
  if (this.opts.dreamStore && this.opts.agentId) {
@@ -876,14 +880,43 @@ export class RuntimeRunner {
876
880
  kind: "preload_history",
877
881
  messages: replayed.map(messageToKernelMessage),
878
882
  });
883
+ // P1-B B3: rebuild active-skill gating after a wake by re-emitting SkillActivated for each
884
+ // `skill` tool call in the replayed history (active_skills is not snapshotted — graceful).
885
+ // The catalog (set_available_skills) was already fed above, so allowed_tools resolves.
886
+ for (const m of replayed) {
887
+ for (const tc of m.toolCalls ?? []) {
888
+ if (tc.name !== "skill")
889
+ continue;
890
+ try {
891
+ const name = JSON.parse(tc.arguments || "{}").name;
892
+ if (name)
893
+ kernelApply(runtime, this.pendingObservations, { kind: "skill_activated", name });
894
+ }
895
+ catch { /* malformed skill args — skip */ }
896
+ }
897
+ }
879
898
  }
880
899
  const sessionStart = Date.now();
881
900
  const startPayload = {
882
901
  kind: "start_run",
883
902
  task: { goal, criteria },
884
903
  };
885
- if (this.opts.runSpec) {
886
- startPayload.run_spec = agentRunSpecToKernel(this.opts.runSpec);
904
+ // P0-A: lower an explicit `runSpec` and/or the `allowedToolIds` profile to the kernel's
905
+ // `capability_filter`. `allowedToolIds` augments an explicit spec's filter, else synthesizes
906
+ // a minimal top-level spec carrying just the filter (reuses the existing run_spec wire — no
907
+ // new ABI). Unset on both ⇒ no run_spec ⇒ no gating (铁律: no config = old behavior).
908
+ const allowedToolIds = this.opts.allowedToolIds;
909
+ const hasProfile = allowedToolIds !== undefined && allowedToolIds.length > 0;
910
+ if (this.opts.runSpec || hasProfile) {
911
+ const baseSpec = this.opts.runSpec ?? {
912
+ identity: { agentId: this.opts.agentId ?? "root", sessionId, isSubAgent: false },
913
+ role: "custom",
914
+ goal,
915
+ };
916
+ const spec = hasProfile
917
+ ? { ...baseSpec, capabilityFilter: { ...baseSpec.capabilityFilter, allowedIds: allowedToolIds } }
918
+ : baseSpec;
919
+ startPayload.run_spec = agentRunSpecToKernel(spec);
887
920
  }
888
921
  const osProfile = assertNativeProfile(this.opts.osProfile ?? "native");
889
922
  const attentionPolicy = this.opts.attentionPolicy ?? osProfile.attentionPolicy;
@@ -944,6 +977,9 @@ export class RuntimeRunner {
944
977
  ? kernelAction(runtime, this.pendingObservations, { kind: "resume" })
945
978
  : kernelAction(runtime, this.pendingObservations, startPayload);
946
979
  let hasAttemptedReactiveCompact = false;
980
+ // P0-C: the skill loaded and in effect going into the current turn (updated when the model's
981
+ // `skill` tool call resolves). Drives the per-turn `activeSkill` metric → dwell measurement.
982
+ let activeSkill;
947
983
  while (!runtime.isTerminal()) {
948
984
  // Page-in must run before appendObservations drains pending kernel observations.
949
985
  if (action.kind === "execute_tool") {
@@ -985,6 +1021,8 @@ export class RuntimeRunner {
985
1021
  let turnTokens = 0;
986
1022
  let turnInputTokens = 0;
987
1023
  let turnOutputTokens = 0;
1024
+ let turnCacheReadTokens = 0;
1025
+ let turnCacheCreationTokens = 0;
988
1026
  let shouldRetry = false;
989
1027
  const abortSignal = this.abortController?.signal;
990
1028
  try {
@@ -999,6 +1037,9 @@ export class RuntimeRunner {
999
1037
  turnTokens = usageEvt.totalTokens;
1000
1038
  turnInputTokens = usageEvt.inputTokens ?? 0;
1001
1039
  turnOutputTokens = usageEvt.outputTokens ?? 0;
1040
+ // P0-C: capture the prompt-cache split for the tool-gating hit-rate baseline.
1041
+ turnCacheReadTokens = usageEvt.cacheReadInputTokens ?? 0;
1042
+ turnCacheCreationTokens = usageEvt.cacheCreationInputTokens ?? 0;
1002
1043
  continue;
1003
1044
  }
1004
1045
  yield evt;
@@ -1079,6 +1120,32 @@ export class RuntimeRunner {
1079
1120
  toolCalls: finalToolCalls,
1080
1121
  providerReplay,
1081
1122
  }));
1123
+ // P0-C: emit per-turn tool-gating telemetry. `activeSkill` reflects the skill in effect
1124
+ // GOING INTO this turn; a `skill` call here only takes effect next turn, so emit first, then
1125
+ // advance. Wrapped so a faulty sink can never break the run (pure observation).
1126
+ if (this.opts.onTurnMetrics) {
1127
+ try {
1128
+ this.opts.onTurnMetrics({
1129
+ turn: runtime.turn(),
1130
+ toolsExposed: tools.length,
1131
+ toolsCalled: finalToolCalls.length,
1132
+ activeSkill,
1133
+ inputTokens: turnInputTokens,
1134
+ cacheReadTokens: turnCacheReadTokens,
1135
+ cacheCreationTokens: turnCacheCreationTokens,
1136
+ });
1137
+ }
1138
+ catch { /* metrics must never break the run */ }
1139
+ }
1140
+ const skillCall = finalToolCalls.find(c => c.name === "skill");
1141
+ if (skillCall) {
1142
+ try {
1143
+ const name = JSON.parse(skillCall.arguments || "{}").name;
1144
+ if (name)
1145
+ activeSkill = name;
1146
+ }
1147
+ catch { /* malformed skill args — leave activeSkill unchanged */ }
1148
+ }
1082
1149
  }
1083
1150
  else if (action.kind === "execute_tool") {
1084
1151
  const allCalls = action.calls;
@@ -1214,6 +1281,22 @@ export class RuntimeRunner {
1214
1281
  this.pendingSpoolOutputs.set(call.id, { tool: call.name, output: result.output });
1215
1282
  }
1216
1283
  }
1284
+ // P1-B B3: a `skill` call that resolved successfully activates that skill in the kernel, so
1285
+ // the next `call_provider` narrows the toolset to its declared tools. Fed before `tool_results`
1286
+ // (which computes the next action). Errs-open: a failed/missing skill load doesn't activate.
1287
+ for (const call of allCalls) {
1288
+ if (call.name !== "skill")
1289
+ continue;
1290
+ const res = toolResults.find(r => r.callId === call.id);
1291
+ if (!res || res.isError)
1292
+ continue;
1293
+ try {
1294
+ const name = JSON.parse(call.arguments || "{}").name;
1295
+ if (name)
1296
+ kernelApply(runtime, this.pendingObservations, { kind: "skill_activated", name });
1297
+ }
1298
+ catch { /* malformed skill args — skip activation */ }
1299
+ }
1217
1300
  action = kernelAction(runtime, this.pendingObservations, {
1218
1301
  kind: "tool_results",
1219
1302
  results: toolResults.map(toolResultToKernel),
@@ -4,6 +4,10 @@ export interface SkillMetadata {
4
4
  whenToUse?: string;
5
5
  effort?: number;
6
6
  estimatedTokens?: number;
7
+ /** P1-B tool gating: tool ids this skill needs. When the skill is active, the kernel narrows the
8
+ * exposed toolset to `stable-core ∪ allowedTools`. Parsed from `allowed_tools:` frontmatter
9
+ * (comma-separated or `[a, b]`). Absent ⇒ the skill does not narrow (back-compat). */
10
+ allowedTools?: string[];
7
11
  }
8
12
  /** Read one skill file and return its body (frontmatter stripped). */
9
13
  export declare function readSkillFile(skillDir: string, name: string): Promise<string | null>;
@@ -1,5 +1,13 @@
1
1
  import { readFile, readdir } from "fs/promises";
2
2
  import path from "path";
3
+ /** Parse a frontmatter tool list: `read, write` or `[read, write]` → ["read","write"]. */
4
+ function parseToolList(v) {
5
+ if (v == null || v === "")
6
+ return undefined;
7
+ const ids = String(v).trim().replace(/^\[|\]$/g, "").split(",")
8
+ .map(x => x.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
9
+ return ids.length ? ids : undefined;
10
+ }
3
11
  function parseFrontmatter(content) {
4
12
  const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
5
13
  if (!match)
@@ -38,6 +46,7 @@ export async function scanSkillDir(skillDir) {
38
46
  whenToUse: meta.when_to_use ? String(meta.when_to_use) : undefined,
39
47
  effort: meta.effort ? Number(meta.effort) : undefined,
40
48
  estimatedTokens: meta.estimated_tokens ? Number(meta.estimated_tokens) : undefined,
49
+ allowedTools: parseToolList(meta.allowed_tools),
41
50
  });
42
51
  }
43
52
  return results;
@@ -225,6 +225,13 @@ export declare function workflowNodeToManifest(node: WorkflowSpawnInfo, parentSe
225
225
  export declare function fanoutSynthesize(workers: WorkflowTaskSpec[], synthesize: WorkflowTaskSpec): WorkflowSpec;
226
226
  /** N parallel Implement generators feeding a single Verify filter/dedupe step (barrier). */
227
227
  export declare function generateAndFilter(generators: WorkflowTaskSpec[], filter: WorkflowTaskSpec): WorkflowSpec;
228
+ /**
229
+ * Generate→evaluate quality gate (the EvalPipeline successor, #6): a `loop` worker node (re-run up
230
+ * to `maxIters`, stopping early on a `loop_continue=false` self-signal) + a bias-resistant `verify`
231
+ * eval node gated on it, carrying the kernel's verdict `outputSchema`. Mirrors the kernel `gen_eval`
232
+ * template. For the iterative retry-with-feedback variant, drive it with `HarnessLoop`.
233
+ */
234
+ export declare function genEval(worker: WorkflowTaskSpec, evaluate: WorkflowTaskSpec, maxIters?: number, extractSkillOnPass?: boolean): WorkflowSpec;
228
235
  /**
229
236
  * One fresh-context verifier per rule/claim (parallel) + optional skeptic that depends on all and
230
237
  * re-checks flags. Verifiers run read-only with no inherited author context (bias-resistant).
@@ -1,3 +1,4 @@
1
+ import { getKernel } from "../kernel.js";
1
2
  /** Map kernel spawn observation → host manifest. */
2
3
  export function spawnObservationToManifest(obs, spec, parentSessionId) {
3
4
  const o = obs;
@@ -409,6 +410,34 @@ export function generateAndFilter(generators, filter) {
409
410
  });
410
411
  return { nodes };
411
412
  }
413
+ /**
414
+ * Generate→evaluate quality gate (the EvalPipeline successor, #6): a `loop` worker node (re-run up
415
+ * to `maxIters`, stopping early on a `loop_continue=false` self-signal) + a bias-resistant `verify`
416
+ * eval node gated on it, carrying the kernel's verdict `outputSchema`. Mirrors the kernel `gen_eval`
417
+ * template. For the iterative retry-with-feedback variant, drive it with `HarnessLoop`.
418
+ */
419
+ export function genEval(worker, evaluate, maxIters = 3, extractSkillOnPass = true) {
420
+ const schema = JSON.parse(getKernel().verdictOutputSchema(extractSkillOnPass));
421
+ return {
422
+ nodes: [
423
+ {
424
+ task: asTask(worker),
425
+ role: "implement",
426
+ isolation: "worktree",
427
+ contextInheritance: "full",
428
+ loop: { maxIters: Math.max(1, maxIters) },
429
+ },
430
+ {
431
+ task: asTask(evaluate),
432
+ role: "verify",
433
+ isolation: "read_only",
434
+ contextInheritance: "none",
435
+ dependsOn: [0],
436
+ outputSchema: schema,
437
+ },
438
+ ],
439
+ };
440
+ }
412
441
  /**
413
442
  * One fresh-context verifier per rule/claim (parallel) + optional skeptic that depends on all and
414
443
  * re-checks flags. Verifiers run read-only with no inherited author context (bias-resistant).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstrike/sdk",
3
- "version": "0.2.17",
3
+ "version": "0.2.19",
4
4
  "description": "DeepStrike Node.js SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@anthropic-ai/sdk": "^0.99.0",
23
- "@deepstrike/core": "0.2.17",
23
+ "@deepstrike/core": "0.2.19",
24
24
  "@google/generative-ai": "^0.24.1",
25
25
  "openai": "^5.23.2"
26
26
  },