@deepstrike/sdk 0.2.70 → 0.2.72
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/README.md +57 -41
- package/dist/advanced/public.d.ts +24 -0
- package/dist/advanced/public.js +18 -0
- package/dist/agent-facade.d.ts +96 -0
- package/dist/agent-facade.js +317 -0
- package/dist/agent-ir.d.ts +11 -5
- package/dist/agent-ir.js +42 -26
- package/dist/canonical-prefix-allowlist.d.ts +6 -0
- package/dist/canonical-prefix-allowlist.js +30 -0
- package/dist/evals/public.d.ts +50 -0
- package/dist/evals/public.js +25 -0
- package/dist/guardrail.d.ts +4 -1
- package/dist/handoff-target.d.ts +2 -0
- package/dist/handoff-target.js +7 -1
- package/dist/index.d.ts +14 -30
- package/dist/index.js +7 -16
- package/dist/kernel.d.ts +2 -2
- package/dist/knowledge/public.d.ts +2 -0
- package/dist/knowledge/public.js +1 -1
- package/dist/knowledge/source.d.ts +7 -0
- package/dist/knowledge/source.js +20 -1
- package/dist/memory/protocols.d.ts +2 -2
- package/dist/projection-pairs.d.ts +43 -0
- package/dist/projection-pairs.js +9 -0
- package/dist/providers/anthropic-adapter.d.ts +2 -2
- package/dist/providers/anthropic.d.ts +4 -4
- package/dist/providers/base.d.ts +5 -5
- package/dist/providers/content-normalization.d.ts +4 -4
- package/dist/providers/gemini-adapter.d.ts +2 -2
- package/dist/providers/gemini.d.ts +3 -3
- package/dist/providers/ollama-adapter.d.ts +2 -2
- package/dist/providers/ollama.d.ts +2 -2
- package/dist/providers/openai-chat.d.ts +4 -4
- package/dist/providers/openai-responses-adapter.d.ts +2 -2
- package/dist/providers/openai-responses.d.ts +2 -2
- package/dist/providers/openai.d.ts +4 -4
- package/dist/providers/protocol-adapter.d.ts +2 -2
- package/dist/providers/protocol-capabilities.d.ts +1 -0
- package/dist/providers/protocol-capabilities.js +3 -0
- package/dist/providers/public.d.ts +4 -2
- package/dist/providers/public.js +2 -1
- package/dist/providers/replay-validator.d.ts +3 -3
- package/dist/runtime/archive.d.ts +7 -7
- package/dist/runtime/canonical-kernel-step.d.ts +2 -2
- package/dist/runtime/context-manager.d.ts +56 -0
- package/dist/runtime/context-manager.js +112 -0
- package/dist/runtime/eval.d.ts +2 -2
- package/dist/runtime/kernel-step.d.ts +5 -5
- package/dist/runtime/provider-replay.d.ts +2 -2
- package/dist/runtime/public.d.ts +22 -0
- package/dist/runtime/public.js +11 -0
- package/dist/runtime/replay-fixture.d.ts +3 -3
- package/dist/runtime/replay-fixture.js +1 -1
- package/dist/runtime/replay-provider.d.ts +4 -4
- package/dist/runtime/replay-provider.js +1 -1
- package/dist/runtime/runner.d.ts +17 -5
- package/dist/runtime/runner.js +110 -37
- package/dist/runtime/session-log.d.ts +1 -1
- package/dist/runtime/session-repair.d.ts +2 -2
- package/dist/runtime/workflow-control-flow.d.ts +1 -1
- package/dist/runtime/workflow-control-flow.js +16 -2
- package/dist/runtime-classification.d.ts +161 -0
- package/dist/runtime-classification.js +66 -0
- package/dist/runtime-language.d.ts +32 -0
- package/dist/runtime-language.js +51 -0
- package/dist/skill.d.ts +31 -5
- package/dist/types/agent.d.ts +17 -4
- package/dist/types.d.ts +22 -12
- package/dist/workflow/definition.d.ts +19 -0
- package/dist/workflow/definition.js +29 -0
- package/dist/workflow/public.d.ts +3 -1
- package/dist/workflow/public.js +1 -0
- package/package.json +23 -2
- package/dist/compat/anthropic/mcp.d.ts +0 -15
- package/dist/compat/anthropic/mcp.js +0 -10
- package/dist/compat/openai/agent.d.ts +0 -34
- package/dist/compat/openai/agent.js +0 -24
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
* from the original run). That's the point of replay-for-benchmarking: prompt may differ across
|
|
18
18
|
* variants, response is pinned, so a cost Δ purely reflects the prompt change.
|
|
19
19
|
* - `outputTokens` is estimated from `message.content.length / 4`; provider usage belongs to
|
|
20
|
-
* the session measurement plane, never to the public
|
|
20
|
+
* the session measurement plane, never to the public ModelMessage mirror.
|
|
21
21
|
* - `cacheReadInputTokens` / `cacheCreationInputTokens` are emitted as 0 — replay has no real
|
|
22
22
|
* cache state. Mechanisms whose Δ depends on cache behavior must validate with a live A/B too.
|
|
23
23
|
*
|
|
24
24
|
* Tokenizer: by default a `chars/4` estimator (±20% for English; worse for code/JSON). For tighter
|
|
25
25
|
* numbers plug `opts.tokenizer = tiktokenEncoder` or similar.
|
|
26
26
|
*/
|
|
27
|
-
import type { LLMProvider,
|
|
27
|
+
import type { LLMProvider, ModelMessage, ProviderDescriptor, ProviderRunState, RenderedContext, StreamEvent, ToolSchema } from "../types.js";
|
|
28
28
|
export interface ReplayProviderOpts {
|
|
29
29
|
/**
|
|
30
30
|
* Maps a rendered-context text payload to a token count. Defaults to `chars / 4`.
|
|
@@ -54,7 +54,7 @@ export declare class ReplayProvider implements LLMProvider {
|
|
|
54
54
|
* @param messages Ordered list of assistant messages to replay (one per LLM call).
|
|
55
55
|
* @param opts Optional tokenizer / descriptor / wrap-around behavior.
|
|
56
56
|
*/
|
|
57
|
-
constructor(messages: ReadonlyArray<
|
|
57
|
+
constructor(messages: ReadonlyArray<ModelMessage>, opts?: ReplayProviderOpts);
|
|
58
58
|
descriptor(): ProviderDescriptor;
|
|
59
59
|
/** Number of messages consumed so far. */
|
|
60
60
|
consumed(): number;
|
|
@@ -62,7 +62,7 @@ export declare class ReplayProvider implements LLMProvider {
|
|
|
62
62
|
remaining(): number;
|
|
63
63
|
/** Reset the cursor — useful for re-running the same fixture in a fresh session. */
|
|
64
64
|
reset(): void;
|
|
65
|
-
complete(_context: RenderedContext, _tools: ToolSchema[]): Promise<
|
|
65
|
+
complete(_context: RenderedContext, _tools: ToolSchema[]): Promise<ModelMessage>;
|
|
66
66
|
stream(context: RenderedContext, tools: ToolSchema[], _extensions?: Record<string, unknown>, _state?: ProviderRunState, _signal?: AbortSignal): AsyncIterable<StreamEvent>;
|
|
67
67
|
private pull;
|
|
68
68
|
private estimateInputTokens;
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* from the original run). That's the point of replay-for-benchmarking: prompt may differ across
|
|
18
18
|
* variants, response is pinned, so a cost Δ purely reflects the prompt change.
|
|
19
19
|
* - `outputTokens` is estimated from `message.content.length / 4`; provider usage belongs to
|
|
20
|
-
* the session measurement plane, never to the public
|
|
20
|
+
* the session measurement plane, never to the public ModelMessage mirror.
|
|
21
21
|
* - `cacheReadInputTokens` / `cacheCreationInputTokens` are emitted as 0 — replay has no real
|
|
22
22
|
* cache state. Mechanisms whose Δ depends on cache behavior must validate with a live A/B too.
|
|
23
23
|
*
|
package/dist/runtime/runner.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type { LLMProvider,
|
|
1
|
+
import type { LLMProvider, ModelMessage, ContentPart, ToolSchema, StreamEvent, ToolSuspendEvent, PermissionRequestEvent, PermissionResponse, AsyncSummarizer, MemorySummarizer, EntropySample, EntropyWatchOptions } from "../types.js";
|
|
2
2
|
import type { MemoryStore, MemoryRecord, MemoryRecall, MemoryScope, MemoryQuery } from "../memory/protocols.js";
|
|
3
3
|
import type { KnowledgeSource } from "../knowledge/source.js";
|
|
4
|
+
import type { Skill } from "../skill.js";
|
|
5
|
+
import type { ContextManager } from "./context-manager.js";
|
|
4
6
|
import type { RuntimeSignalUrgency, SignalSource } from "../signals/types.js";
|
|
5
7
|
import type { SessionLog, SessionEvent } from "./session-log.js";
|
|
6
8
|
import type { KernelJournal } from "./kernel-journal.js";
|
|
@@ -9,6 +11,7 @@ import type { ExecutionPlane } from "./execution-plane.js";
|
|
|
9
11
|
import type { RunGroup } from "./run-group.js";
|
|
10
12
|
import { type MemoryPolicy, type ResourceQuota } from "../kernel.js";
|
|
11
13
|
import type { AgentRunSpec, MilestoneCheckResult, MilestoneContract, MilestonePolicy, WorkflowSpec, WorkflowOutcome } from "../types/agent.js";
|
|
14
|
+
import type { AgentCapabilityFilter } from "../types/agent.js";
|
|
12
15
|
export declare function stableSemanticArchiveName(effectId: string): string;
|
|
13
16
|
import { type SubAgentOrchestrator } from "./sub-agent-orchestrator.js";
|
|
14
17
|
import { type ReducerRegistry } from "./reducers.js";
|
|
@@ -95,6 +98,8 @@ export interface KernelReliabilityOptions {
|
|
|
95
98
|
export type OperationCancellationReason = "user" | "deadline" | "lease_lost" | "host_shutdown";
|
|
96
99
|
export interface RuntimeOptions {
|
|
97
100
|
provider: LLMProvider;
|
|
101
|
+
/** Host-owned capability ceiling applied to the root run before skills or run profiles narrow it further. */
|
|
102
|
+
capabilityFilter?: AgentCapabilityFilter;
|
|
98
103
|
/** Host-owned artifact set identity captured in operation genesis. */
|
|
99
104
|
artifactSetDigest?: string;
|
|
100
105
|
/** M4/G5: cumulative token cap for this run (the kernel's `max_total_tokens`). A workflow node's
|
|
@@ -150,7 +155,11 @@ export interface RuntimeOptions {
|
|
|
150
155
|
* behavior difference. */
|
|
151
156
|
nudges?: NudgeRule[];
|
|
152
157
|
initialMemory?: string[];
|
|
158
|
+
/** Optional host ledger that admits dynamic context before kernel insertion. */
|
|
159
|
+
contextManager?: ContextManager;
|
|
153
160
|
skillDir?: string;
|
|
161
|
+
/** Inline skill catalog. Metadata is exposed at run start; content is loaded only on activation. */
|
|
162
|
+
skillCatalog?: Skill[];
|
|
154
163
|
/** Host-layer allowlist over the `skillDir` catalog by skill NAME. When set, only scanned skills
|
|
155
164
|
* whose name is listed are fed to the kernel via `set_available_skills` (the manifest layer
|
|
156
165
|
* intersects onto this host baseline in `applyManifest`). Absent ⇒ zero behavior difference (all
|
|
@@ -398,6 +407,8 @@ export declare class RuntimeRunner {
|
|
|
398
407
|
* run — guards against re-pushing a duplicate entry if the model calls `skill(name)` again for
|
|
399
408
|
* an already-active skill (loading is idempotent; the knowledge push should be too). */
|
|
400
409
|
private knowledgePushedSkills;
|
|
410
|
+
/** Host mirror of kernel skill lease expiry, used only to clear ContextManager overlays. */
|
|
411
|
+
private skillLeaseExpirations;
|
|
401
412
|
private nextArchiveStart;
|
|
402
413
|
private pendingPageOutArchives;
|
|
403
414
|
private activePageOutArchive;
|
|
@@ -488,7 +499,7 @@ export declare class RuntimeRunner {
|
|
|
488
499
|
* K1: `opts.key` gives the entry identity — a same-key push upserts (applied at the next
|
|
489
500
|
* compaction/renewal boundary, where the cached system[1] block is rewritten anyway) instead
|
|
490
501
|
* of appending a duplicate. `opts.pinned` exempts the entry from the knowledge-budget sweep. */
|
|
491
|
-
pushKnowledge(message:
|
|
502
|
+
pushKnowledge(message: ModelMessage, tokens?: number, opts?: {
|
|
492
503
|
key?: string;
|
|
493
504
|
pinned?: boolean;
|
|
494
505
|
}): Promise<void>;
|
|
@@ -500,6 +511,7 @@ export declare class RuntimeRunner {
|
|
|
500
511
|
* drops at the next compaction/renewal boundary. A later `skill(name)` call re-activates and
|
|
501
512
|
* re-pins fresh content. Errs-open: not-active is a kernel-side no-op. */
|
|
502
513
|
deactivateSkill(name: string): Promise<void>;
|
|
514
|
+
private expireSkillContext;
|
|
503
515
|
/**
|
|
504
516
|
* G3: run one workflow node, enforcing its `output_schema` (if any). Without a schema this is a
|
|
505
517
|
* plain `orchestrator.run`. With one, the node's agent is instructed to emit conforming JSON, its
|
|
@@ -603,14 +615,14 @@ export declare class RuntimeRunner {
|
|
|
603
615
|
* message exists. A tail assistant tool_call with nothing after it is a genuinely PENDING tool the
|
|
604
616
|
* run stopped in front of (the wake/recovery case), which must stay unpaired so wake executes it.
|
|
605
617
|
* Pure. */
|
|
606
|
-
export declare function pairOrphanToolCalls(messages:
|
|
618
|
+
export declare function pairOrphanToolCalls(messages: ModelMessage[]): ModelMessage[];
|
|
607
619
|
export declare function replayMessages(events: Array<{
|
|
608
620
|
seq: number;
|
|
609
621
|
event: SessionEvent;
|
|
610
|
-
}>, maxBytes?: number):
|
|
622
|
+
}>, maxBytes?: number): ModelMessage[];
|
|
611
623
|
export declare function replayMessagesAsync(events: Array<{
|
|
612
624
|
seq: number;
|
|
613
625
|
event: SessionEvent;
|
|
614
|
-
}>, maxBytes?: number, loadArchive?: (archiveRef: string) => Promise<
|
|
626
|
+
}>, maxBytes?: number, loadArchive?: (archiveRef: string) => Promise<ModelMessage[]>): Promise<ModelMessage[]>;
|
|
615
627
|
/** Collect all text_delta events from a run into a single string. */
|
|
616
628
|
export declare function collectText(stream: AsyncIterable<StreamEvent>): Promise<string>;
|
package/dist/runtime/runner.js
CHANGED
|
@@ -11,6 +11,23 @@ import { buildLlmCompletedEvent, buildRunTerminalEvent, buildWorkflowNodeComplet
|
|
|
11
11
|
import { KernelPrimitivesDashboard } from "./kernel-primitives-dashboard.js";
|
|
12
12
|
import { capabilityMarker, capabilitySkill, capabilityTool, capabilityCommandMount, capabilityCommandUnmount, archivePresentationFromObservations, entropySampleFromObservation, messageToKernelMessage, skillMetadataToKernel, taskUpdateToKernel, toolResultToKernel, toolSchemaToKernel, } from "./kernel-step.js";
|
|
13
13
|
import { CanonicalKernelRejectedError, CanonicalRunnerRuntime, canonicalKernelAction, canonicalKernelApply, canonicalKernelMaybeAction, canonicalStartAgent, canonicalStartWorkflow, } from "./canonical-kernel-step.js";
|
|
14
|
+
function intersectCapabilityFilters(a, b) {
|
|
15
|
+
if (!a && !b)
|
|
16
|
+
return undefined;
|
|
17
|
+
const intersect = (left, right) => {
|
|
18
|
+
if (!left?.length)
|
|
19
|
+
return right?.length ? [...right] : undefined;
|
|
20
|
+
if (!right?.length)
|
|
21
|
+
return [...left];
|
|
22
|
+
return left.filter(value => right.includes(value));
|
|
23
|
+
};
|
|
24
|
+
const allowedKinds = intersect(a?.allowedKinds, b?.allowedKinds);
|
|
25
|
+
const allowedIds = intersect(a?.allowedIds, b?.allowedIds);
|
|
26
|
+
return {
|
|
27
|
+
...(allowedKinds?.length ? { allowedKinds } : allowedKinds ? { allowedKinds: [] } : {}),
|
|
28
|
+
...(allowedIds?.length ? { allowedIds } : allowedIds ? { allowedIds: [] } : {}),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
14
31
|
export function stableSemanticArchiveName(effectId) {
|
|
15
32
|
const stableEffectId = effectId.replace(/[^a-zA-Z0-9._:-]/g, "_");
|
|
16
33
|
return `page-out-${stableEffectId || "unknown"}`;
|
|
@@ -153,6 +170,8 @@ export class RuntimeRunner {
|
|
|
153
170
|
* run — guards against re-pushing a duplicate entry if the model calls `skill(name)` again for
|
|
154
171
|
* an already-active skill (loading is idempotent; the knowledge push should be too). */
|
|
155
172
|
knowledgePushedSkills = new Set();
|
|
173
|
+
/** Host mirror of kernel skill lease expiry, used only to clear ContextManager overlays. */
|
|
174
|
+
skillLeaseExpirations = new Map();
|
|
156
175
|
nextArchiveStart = 0;
|
|
157
176
|
pendingPageOutArchives = [];
|
|
158
177
|
activePageOutArchive;
|
|
@@ -273,7 +292,7 @@ export class RuntimeRunner {
|
|
|
273
292
|
* agent syscall: the host selects records from its store, then the kernel owns the write
|
|
274
293
|
* of live semantic context. `seenRecordIds` is the prefetch's dedupe horizon.
|
|
275
294
|
*/
|
|
276
|
-
async prefetchMemoryIntoKnowledge(runtime, query, agentId, sessionId, seenRecordIds
|
|
295
|
+
async prefetchMemoryIntoKnowledge(runtime, query, agentId, sessionId, seenRecordIds) {
|
|
277
296
|
let hits = [];
|
|
278
297
|
try {
|
|
279
298
|
hits = await this.retrieveMemoryFromStore(query, query.top_k, agentId);
|
|
@@ -287,12 +306,10 @@ export class RuntimeRunner {
|
|
|
287
306
|
});
|
|
288
307
|
}
|
|
289
308
|
for (const hit of hits) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
tokens: Math.max(1, Math.ceil(hit.record.content.length / 4)),
|
|
295
|
-
}, sessionId);
|
|
309
|
+
// Route renewal recalls through the same host context admission path as every other
|
|
310
|
+
// dynamic knowledge entry. This keeps ContextManager budgets and ledger events in sync
|
|
311
|
+
// while preserving the canonical kernel knowledge command underneath.
|
|
312
|
+
await this.pushKnowledge({ role: "system", content: hit.record.content, toolCalls: [] }, Math.max(1, Math.ceil(hit.record.content.length / 4)), { key: `memory:${hit.record.record_id}` });
|
|
296
313
|
}
|
|
297
314
|
await this.applyHostMemoryRecallLifecycle(hits, agentId);
|
|
298
315
|
await this.logMemoryRetrievalResult(sessionId, hits);
|
|
@@ -598,10 +615,27 @@ export class RuntimeRunner {
|
|
|
598
615
|
async pushKnowledge(message, tokens, opts) {
|
|
599
616
|
if (!this.activeKernel)
|
|
600
617
|
return;
|
|
618
|
+
const content = message.content ?? "";
|
|
619
|
+
const itemId = opts?.key ?? `context:${createHash("sha256").update(content).digest("hex").slice(0, 16)}`;
|
|
620
|
+
if (this.opts.contextManager) {
|
|
621
|
+
this.opts.contextManager.upsert({
|
|
622
|
+
id: itemId,
|
|
623
|
+
kind: opts?.key?.startsWith("skill:") ? "skill" : opts?.key?.startsWith("memory:") ? "memory" : "knowledge",
|
|
624
|
+
content,
|
|
625
|
+
scope: opts?.key?.startsWith("skill:") ? "session" : "turn",
|
|
626
|
+
priority: opts?.pinned ? 100 : 50,
|
|
627
|
+
pinned: opts?.pinned,
|
|
628
|
+
source: { type: opts?.key?.split(":", 1)[0] ?? "context", ...(opts?.key ? { id: opts.key } : {}) },
|
|
629
|
+
});
|
|
630
|
+
if (!this.opts.contextManager.select().some(item => item.id === itemId)) {
|
|
631
|
+
this.opts.contextManager.remove(itemId);
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
601
635
|
await this.commitKernelApply(this.activeKernel, this.pendingObservations, {
|
|
602
636
|
kind: "add_knowledge_message",
|
|
603
|
-
content
|
|
604
|
-
tokens: tokens ?? Math.max(1, Math.ceil(
|
|
637
|
+
content,
|
|
638
|
+
tokens: tokens ?? Math.max(1, Math.ceil(content.length / 4)),
|
|
605
639
|
...(opts?.key !== undefined ? { key: opts.key } : {}),
|
|
606
640
|
...(opts?.pinned ? { pinned: true } : {}),
|
|
607
641
|
});
|
|
@@ -609,6 +643,7 @@ export class RuntimeRunner {
|
|
|
609
643
|
/** K1: mark a keyed knowledge entry for removal at the next compaction/renewal boundary.
|
|
610
644
|
* Errs-open: an unknown key is a kernel-side no-op. */
|
|
611
645
|
async removeKnowledge(key) {
|
|
646
|
+
this.opts.contextManager?.remove(key);
|
|
612
647
|
if (!this.activeKernel)
|
|
613
648
|
return;
|
|
614
649
|
await this.commitKernelApply(this.activeKernel, this.pendingObservations, { kind: "remove_knowledge", key });
|
|
@@ -618,11 +653,24 @@ export class RuntimeRunner {
|
|
|
618
653
|
* drops at the next compaction/renewal boundary. A later `skill(name)` call re-activates and
|
|
619
654
|
* re-pins fresh content. Errs-open: not-active is a kernel-side no-op. */
|
|
620
655
|
async deactivateSkill(name) {
|
|
621
|
-
if (
|
|
622
|
-
|
|
623
|
-
|
|
656
|
+
if (this.activeKernel) {
|
|
657
|
+
await this.commitKernelApply(this.activeKernel, this.pendingObservations, { kind: "skill_deactivated", name });
|
|
658
|
+
}
|
|
624
659
|
// Re-arm the SDK-side push guard so a re-activation re-pins the content.
|
|
625
660
|
this.knowledgePushedSkills.delete(name);
|
|
661
|
+
this.skillLeaseExpirations.delete(name);
|
|
662
|
+
this.opts.contextManager?.remove(`skill:${name}`);
|
|
663
|
+
}
|
|
664
|
+
expireSkillContext(currentTurn) {
|
|
665
|
+
if (this.opts.skillLeaseTurns === undefined)
|
|
666
|
+
return;
|
|
667
|
+
for (const [name, expiresAtTurn] of this.skillLeaseExpirations) {
|
|
668
|
+
if (currentTurn < expiresAtTurn)
|
|
669
|
+
continue;
|
|
670
|
+
this.skillLeaseExpirations.delete(name);
|
|
671
|
+
this.knowledgePushedSkills.delete(name);
|
|
672
|
+
this.opts.contextManager?.remove(`skill:${name}`);
|
|
673
|
+
}
|
|
626
674
|
}
|
|
627
675
|
/**
|
|
628
676
|
* G3: run one workflow node, enforcing its `output_schema` (if any). Without a schema this is a
|
|
@@ -632,7 +680,7 @@ export class RuntimeRunner {
|
|
|
632
680
|
* validation reason — a node that cannot meet its declared output contract starves its dependents,
|
|
633
681
|
* exactly as a denied spawn does.
|
|
634
682
|
*/
|
|
635
|
-
async runWorkflowNode(node, parentSessionId, orchestrator, budget, outputs, abortSignal) {
|
|
683
|
+
async runWorkflowNode(node, parentSessionId, orchestrator, budget, outputs, abortSignal, contextPolicies) {
|
|
636
684
|
// G2: a reduce node runs no LLM — execute the registered pure function over its dependency
|
|
637
685
|
// outputs and feed the result back as an ordinary completion. Deterministic; no agent burned.
|
|
638
686
|
if (node.reducer) {
|
|
@@ -645,7 +693,11 @@ export class RuntimeRunner {
|
|
|
645
693
|
const budgetNote = workflowBudgetNote(budget);
|
|
646
694
|
// W-N2: a DAG edge carries data — every dependent node sees its dependencies' outputs (the
|
|
647
695
|
// kernel sends `input_agent_ids` for all dependents; judges/reduce keep their special paths).
|
|
648
|
-
const
|
|
696
|
+
const policy = contextPolicies?.get(node.agent_id) ?? contextPolicies?.get(node.agent_id.replace(/-i\d+$/, ""));
|
|
697
|
+
const include = policy?.include ?? ["dependency_outputs"];
|
|
698
|
+
const depsNote = include.includes("dependency_outputs")
|
|
699
|
+
? dependencyOutputsNote(node.input_agent_ids, outputs, policy?.maxTokens !== undefined ? Math.max(256, policy.maxTokens * 4) : 8_000, policy?.dependencyMode ?? "full")
|
|
700
|
+
: "";
|
|
649
701
|
const withBudget = (goal) => [goal, depsNote, budgetNote].filter(Boolean).join("\n\n");
|
|
650
702
|
const mkCtx = (goal) => ({
|
|
651
703
|
parentOpts: this.opts,
|
|
@@ -806,7 +858,7 @@ export class RuntimeRunner {
|
|
|
806
858
|
};
|
|
807
859
|
}
|
|
808
860
|
const observations = this.pendingObservations.slice(observationStart);
|
|
809
|
-
const outcome = await this.driveWorkflow(initialAction, observations, parentSessionId, runtime, new Map());
|
|
861
|
+
const outcome = await this.driveWorkflow(initialAction, observations, parentSessionId, runtime, new Map(), new Map(spec.nodes.flatMap((node, index) => node.context ? [[`wf-node${index}`, node.context]] : [])));
|
|
810
862
|
if (bootstrapped) {
|
|
811
863
|
let terminal = runtime.resumeAction();
|
|
812
864
|
if (!terminal)
|
|
@@ -861,6 +913,8 @@ export class RuntimeRunner {
|
|
|
861
913
|
this.pendingObservations = [];
|
|
862
914
|
this.pendingPageOutArchives = [];
|
|
863
915
|
this.activePageOutArchive = undefined;
|
|
916
|
+
this.knowledgePushedSkills.clear();
|
|
917
|
+
this.skillLeaseExpirations.clear();
|
|
864
918
|
this.currentSessionId = sessionId;
|
|
865
919
|
const runtime = this.createCanonicalRuntime(runId, sessionId);
|
|
866
920
|
this.activeKernel = runtime;
|
|
@@ -965,7 +1019,7 @@ export class RuntimeRunner {
|
|
|
965
1019
|
* Drive a canonical root or provider-authored workflow from kernel effects only: run each
|
|
966
1020
|
* emitted batch, resolve its launch/completion/preemption effects, and stop at the kernel terminal.
|
|
967
1021
|
*/
|
|
968
|
-
async driveWorkflow(initialAction, initial, parentSessionId, runtime, seedOutputs) {
|
|
1022
|
+
async driveWorkflow(initialAction, initial, parentSessionId, runtime, seedOutputs, contextPolicies) {
|
|
969
1023
|
let observations = initial;
|
|
970
1024
|
const orchestrator = this.opts.subAgentOrchestrator ?? defaultSubAgentOrchestrator;
|
|
971
1025
|
const findDone = (obs) => obs.find(o => o.kind === "workflow_completed");
|
|
@@ -1027,7 +1081,7 @@ export class RuntimeRunner {
|
|
|
1027
1081
|
const controllers = new Map(nodes.map(n => [n.agent_id, new AbortController()]));
|
|
1028
1082
|
const batchState = { settled: false };
|
|
1029
1083
|
const monitor = this.monitorWorkflowPreemption(runtime, controllers, batchState);
|
|
1030
|
-
const results = await Promise.all(nodes.map(node => this.runWorkflowNode(node, parentSessionId, orchestrator, roundBudget, outputs, controllers.get(node.agent_id)?.signal)));
|
|
1084
|
+
const results = await Promise.all(nodes.map(node => this.runWorkflowNode(node, parentSessionId, orchestrator, roundBudget, outputs, controllers.get(node.agent_id)?.signal, contextPolicies)));
|
|
1031
1085
|
batchState.settled = true;
|
|
1032
1086
|
const preempted = await monitor;
|
|
1033
1087
|
if (preempted !== null) {
|
|
@@ -1352,6 +1406,8 @@ export class RuntimeRunner {
|
|
|
1352
1406
|
this.pendingObservations = [];
|
|
1353
1407
|
this.pendingPageOutArchives = [];
|
|
1354
1408
|
this.activePageOutArchive = undefined;
|
|
1409
|
+
this.knowledgePushedSkills.clear();
|
|
1410
|
+
this.skillLeaseExpirations.clear();
|
|
1355
1411
|
this.currentSessionId = sessionId;
|
|
1356
1412
|
this.activeProviderInvocationId = undefined;
|
|
1357
1413
|
this.providerRetryPending = false;
|
|
@@ -1413,17 +1469,23 @@ export class RuntimeRunner {
|
|
|
1413
1469
|
});
|
|
1414
1470
|
}
|
|
1415
1471
|
if (this.opts.initialMemory) {
|
|
1416
|
-
for (const mem of this.opts.initialMemory) {
|
|
1417
|
-
await this.
|
|
1418
|
-
kind: "add_knowledge_message",
|
|
1419
|
-
content: mem,
|
|
1420
|
-
tokens: Math.max(1, Math.ceil(mem.length / 4)),
|
|
1421
|
-
});
|
|
1472
|
+
for (const [index, mem] of this.opts.initialMemory.entries()) {
|
|
1473
|
+
await this.pushKnowledge({ role: "system", content: mem, toolCalls: [] }, undefined, { key: `initial:${index}`, pinned: true });
|
|
1422
1474
|
}
|
|
1423
1475
|
}
|
|
1424
|
-
if (this.opts.skillDir) {
|
|
1476
|
+
if (this.opts.skillDir || this.opts.skillCatalog?.length) {
|
|
1425
1477
|
const { scanSkillDir } = await import("../skills/loader.js");
|
|
1426
|
-
const metas =
|
|
1478
|
+
const metas = [
|
|
1479
|
+
...(this.opts.skillDir ? await scanSkillDir(this.opts.skillDir) : []),
|
|
1480
|
+
...(this.opts.skillCatalog ?? []).map(skill => ({
|
|
1481
|
+
name: skill.name,
|
|
1482
|
+
description: skill.description ?? "",
|
|
1483
|
+
...(skill.metadata?.whenToUse ? { whenToUse: String(skill.metadata.whenToUse) } : {}),
|
|
1484
|
+
...(skill.metadata?.effort !== undefined ? { effort: Number(skill.metadata.effort) } : {}),
|
|
1485
|
+
...(skill.metadata?.estimatedTokens !== undefined ? { estimatedTokens: Number(skill.metadata.estimatedTokens) } : {}),
|
|
1486
|
+
...(skill.tools?.length ? { allowedTools: skill.tools.map(tool => typeof tool === "string" ? tool : tool.name) } : {}),
|
|
1487
|
+
})),
|
|
1488
|
+
];
|
|
1427
1489
|
// S2 host-layer skill allowlist: keep only scanned skills named in `skillFilter` before feeding
|
|
1428
1490
|
// the catalog. Absent ⇒ feed all (identical to the pre-feature message); empty ⇒ feed none. The
|
|
1429
1491
|
// `set_available_skills` message is ALWAYS sent when a skillDir exists (shape preserved) — only
|
|
@@ -1505,9 +1567,16 @@ export class RuntimeRunner {
|
|
|
1505
1567
|
role: "custom",
|
|
1506
1568
|
goal,
|
|
1507
1569
|
};
|
|
1508
|
-
|
|
1509
|
-
|
|
1570
|
+
const filtered = intersectCapabilityFilters(baseSpec.capabilityFilter, this.opts.capabilityFilter);
|
|
1571
|
+
let spec = filtered
|
|
1572
|
+
? { ...baseSpec, capabilityFilter: filtered }
|
|
1510
1573
|
: baseSpec;
|
|
1574
|
+
if (hasProfile) {
|
|
1575
|
+
spec = {
|
|
1576
|
+
...spec,
|
|
1577
|
+
capabilityFilter: intersectCapabilityFilters(spec.capabilityFilter, { allowedIds: allowedToolIds }),
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1511
1580
|
spec = { ...spec, exposureBaseline: baselineToolIds };
|
|
1512
1581
|
if (hasMilestoneContract && !spec.verificationContractId) {
|
|
1513
1582
|
spec = { ...spec, verificationContractId: "node-default" };
|
|
@@ -1569,6 +1638,7 @@ export class RuntimeRunner {
|
|
|
1569
1638
|
while (!runtime.isTerminal()) {
|
|
1570
1639
|
nextCompressedArchiveStart = await this.appendObservations(sessionId, runtime, nextCompressedArchiveStart, taskScope);
|
|
1571
1640
|
this.nextArchiveStart = nextCompressedArchiveStart;
|
|
1641
|
+
this.expireSkillContext(runtime.turn());
|
|
1572
1642
|
if (this.interrupted) {
|
|
1573
1643
|
action = await this.commitKernelAction(runtime, this.pendingObservations, {
|
|
1574
1644
|
kind: "cancel_operation",
|
|
@@ -1921,7 +1991,7 @@ export class RuntimeRunner {
|
|
|
1921
1991
|
...(turnOutputTokens > 0 ? { observed_output_tokens: settlement?.observed_output_tokens ?? turnOutputTokens } : {}),
|
|
1922
1992
|
...(turnStopReason ? { stop_reason: turnStopReason } : {}),
|
|
1923
1993
|
};
|
|
1924
|
-
if (this.opts.skillDir) {
|
|
1994
|
+
if (this.opts.skillDir || this.opts.skillCatalog?.length) {
|
|
1925
1995
|
const skillCalls = finalToolCalls.filter(call => call.name === "skill");
|
|
1926
1996
|
if (skillCalls.length > 0) {
|
|
1927
1997
|
const { readSkillFile } = await import("../skills/loader.js");
|
|
@@ -1932,16 +2002,16 @@ export class RuntimeRunner {
|
|
|
1932
2002
|
continue;
|
|
1933
2003
|
if (this.opts.skillFilter && !this.opts.skillFilter.includes(name))
|
|
1934
2004
|
continue;
|
|
1935
|
-
const
|
|
2005
|
+
const inline = this.opts.skillCatalog?.find(skill => skill.name === name);
|
|
2006
|
+
const content = inline?.instructions
|
|
2007
|
+
?? (this.opts.skillDir ? await readSkillFile(this.opts.skillDir, name) : null);
|
|
1936
2008
|
if (!content)
|
|
1937
2009
|
continue;
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
pinned: true,
|
|
1944
|
-
});
|
|
2010
|
+
const knowledge = inline?.knowledge
|
|
2011
|
+
?.map(entry => typeof entry === "string" ? entry : entry.content)
|
|
2012
|
+
.filter((entry) => Boolean(entry)) ?? [];
|
|
2013
|
+
const fullContent = [content, ...knowledge].join("\n\n");
|
|
2014
|
+
await this.pushKnowledge({ role: "system", content: fullContent, toolCalls: [] }, Math.max(1, Math.ceil(fullContent.length / 4)), { key: `skill:${name}`, pinned: true });
|
|
1945
2015
|
}
|
|
1946
2016
|
catch {
|
|
1947
2017
|
// A missing or malformed skill stays a model-visible syscall rejection.
|
|
@@ -2372,6 +2442,9 @@ export class RuntimeRunner {
|
|
|
2372
2442
|
if (this.opts.skillLeaseTurns !== undefined || !this.knowledgePushedSkills.has(name)) {
|
|
2373
2443
|
this.knowledgePushedSkills.add(name);
|
|
2374
2444
|
await this.pushKnowledge({ role: "system", content: res.output, toolCalls: [] }, undefined, { key: `skill:${name}` });
|
|
2445
|
+
if (this.opts.skillLeaseTurns !== undefined) {
|
|
2446
|
+
this.skillLeaseExpirations.set(name, runtime.turn() + this.opts.skillLeaseTurns);
|
|
2447
|
+
}
|
|
2375
2448
|
}
|
|
2376
2449
|
}
|
|
2377
2450
|
catch { /* malformed skill args — skip the knowledge pin */ }
|
|
@@ -2614,7 +2687,7 @@ export class RuntimeRunner {
|
|
|
2614
2687
|
for (const q of queries ?? []) {
|
|
2615
2688
|
if (!q.query.trim())
|
|
2616
2689
|
continue;
|
|
2617
|
-
const { action } = await this.prefetchMemoryIntoKnowledge(runtime, q, this.opts.agentId, this.durableSessionId(this.currentSessionId), seenRecordIds
|
|
2690
|
+
const { action } = await this.prefetchMemoryIntoKnowledge(runtime, q, this.opts.agentId, this.durableSessionId(this.currentSessionId), seenRecordIds);
|
|
2618
2691
|
resumed = action ?? resumed;
|
|
2619
2692
|
}
|
|
2620
2693
|
// Every seed command leaves the pending provider action authoritative; use the last view.
|
|
@@ -279,7 +279,7 @@ export type SessionEvent = {
|
|
|
279
279
|
classify_branch?: string;
|
|
280
280
|
tournament_winner?: string;
|
|
281
281
|
loop_continue?: boolean;
|
|
282
|
-
output?: import("../types.js").
|
|
282
|
+
output?: import("../types.js").ModelMessage;
|
|
283
283
|
} | {
|
|
284
284
|
kind: "workflow_nodes_submitted";
|
|
285
285
|
turn: number;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ModelMessage, ProviderWireEvidence, ToolCall } from "../types.js";
|
|
2
2
|
import type { SessionEvent } from "./session-log.js";
|
|
3
3
|
import type { WorkflowNodeStatus } from "../types/agent.js";
|
|
4
4
|
export { REPLAY_CONTENT_MAX_BYTES as RECOVERY_CONTENT_MAX_BYTES } from "./replay-sanitize.js";
|
|
@@ -57,7 +57,7 @@ export declare function buildWorkflowNodeCompletedEvent(input: {
|
|
|
57
57
|
classifyBranch?: string;
|
|
58
58
|
tournamentWinner?: string;
|
|
59
59
|
loopContinue?: boolean;
|
|
60
|
-
output?:
|
|
60
|
+
output?: ModelMessage;
|
|
61
61
|
}): Extract<SessionEvent, {
|
|
62
62
|
kind: "workflow_node_completed";
|
|
63
63
|
}>;
|
|
@@ -7,7 +7,7 @@ export declare function loopInstruction(maxIters: number, iteration?: number): s
|
|
|
7
7
|
* just ordering (fan-out→synthesize was an uninformed synthesis without this). Each dependency's
|
|
8
8
|
* output is clipped so a chain of large nodes can't blow the child's context; empty/unknown
|
|
9
9
|
* outputs are skipped. Returns "" when the node has no dependencies. */
|
|
10
|
-
export declare function dependencyOutputsNote(inputAgentIds: string[] | undefined, outputs: Map<string, string> | undefined, maxPerDep?: number): string;
|
|
10
|
+
export declare function dependencyOutputsNote(inputAgentIds: string[] | undefined, outputs: Map<string, string> | undefined, maxPerDep?: number, mode?: "full" | "summary" | "reference"): string;
|
|
11
11
|
/** Instruction appended to a classify node's goal: pick exactly one of the kernel's branch labels. */
|
|
12
12
|
export declare function classifyInstruction(labels: string[]): string;
|
|
13
13
|
/** Build a tournament judge's goal: the controller's criterion + the two candidates to compare. */
|
|
@@ -20,20 +20,34 @@ export function loopInstruction(maxIters, iteration = 0) {
|
|
|
20
20
|
* just ordering (fan-out→synthesize was an uninformed synthesis without this). Each dependency's
|
|
21
21
|
* output is clipped so a chain of large nodes can't blow the child's context; empty/unknown
|
|
22
22
|
* outputs are skipped. Returns "" when the node has no dependencies. */
|
|
23
|
-
export function dependencyOutputsNote(inputAgentIds, outputs, maxPerDep = 8_000) {
|
|
23
|
+
export function dependencyOutputsNote(inputAgentIds, outputs, maxPerDep = 8_000, mode = "full") {
|
|
24
24
|
if (!inputAgentIds?.length || !outputs)
|
|
25
25
|
return "";
|
|
26
|
+
if (mode === "reference") {
|
|
27
|
+
return inputAgentIds.some(id => outputs.has(id))
|
|
28
|
+
? `[dependency references]\n${inputAgentIds.filter(id => outputs.has(id)).join(", ")}`
|
|
29
|
+
: "";
|
|
30
|
+
}
|
|
26
31
|
const blocks = inputAgentIds
|
|
27
32
|
.map(id => {
|
|
28
33
|
const out = outputs.get(id) ?? "";
|
|
29
34
|
if (!out)
|
|
30
35
|
return "";
|
|
31
|
-
const clipped =
|
|
36
|
+
const clipped = mode === "summary"
|
|
37
|
+
? summarizeDependency(out, maxPerDep)
|
|
38
|
+
: out.length > maxPerDep ? `${out.slice(0, maxPerDep)}\n…[truncated]` : out;
|
|
32
39
|
return `[dependency ${id} output]\n${clipped}`;
|
|
33
40
|
})
|
|
34
41
|
.filter(Boolean);
|
|
35
42
|
return blocks.join("\n\n");
|
|
36
43
|
}
|
|
44
|
+
function summarizeDependency(value, maxChars) {
|
|
45
|
+
if (value.length <= maxChars)
|
|
46
|
+
return value;
|
|
47
|
+
const head = Math.max(1, Math.floor(maxChars * 0.7));
|
|
48
|
+
const tail = Math.max(1, maxChars - head);
|
|
49
|
+
return `${value.slice(0, head)}\n…[summary truncated]…\n${value.slice(-tail)}`;
|
|
50
|
+
}
|
|
37
51
|
/** Instruction appended to a classify node's goal: pick exactly one of the kernel's branch labels. */
|
|
38
52
|
export function classifyInstruction(labels) {
|
|
39
53
|
return (`Classify the input and choose EXACTLY ONE label from: ${labels.map(l => JSON.stringify(l)).join(", ")}. ` +
|