@noetic-tools/core 0.0.1
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/LICENSE +201 -0
- package/README.md +38 -0
- package/dist/adapters/in-memory-fs-adapter.d.ts +3 -0
- package/dist/adapters/in-memory-fs-adapter.js +245 -0
- package/dist/adapters/in-memory-shell-adapter.d.ts +9 -0
- package/dist/adapters/in-memory-shell-adapter.js +20 -0
- package/dist/adapters/in-memory-subprocess-adapter.d.ts +5 -0
- package/dist/adapters/in-memory-subprocess-adapter.js +126 -0
- package/dist/adapters/local-fs-adapter.d.ts +3 -0
- package/dist/adapters/local-fs-adapter.js +43 -0
- package/dist/adapters/local-shell-adapter.d.ts +51 -0
- package/dist/adapters/local-shell-adapter.js +167 -0
- package/dist/adapters/local-subprocess-adapter.d.ts +16 -0
- package/dist/adapters/local-subprocess-adapter.js +187 -0
- package/dist/adapters/openrouter.d.ts +57 -0
- package/dist/adapters/openrouter.js +248 -0
- package/dist/builders/channel-builder.d.ts +36 -0
- package/dist/builders/channel-builder.js +15 -0
- package/dist/builders/control-flow-builders.d.ts +79 -0
- package/dist/builders/control-flow-builders.js +53 -0
- package/dist/builders/every.d.ts +45 -0
- package/dist/builders/every.js +60 -0
- package/dist/builders/layer-provides-builders.d.ts +32 -0
- package/dist/builders/layer-provides-builders.js +33 -0
- package/dist/builders/loop-builder.d.ts +23 -0
- package/dist/builders/loop-builder.js +46 -0
- package/dist/builders/memory-builder.d.ts +11 -0
- package/dist/builders/memory-builder.js +15 -0
- package/dist/builders/provide-builder.d.ts +20 -0
- package/dist/builders/provide-builder.js +34 -0
- package/dist/builders/spawn-builder.d.ts +20 -0
- package/dist/builders/spawn-builder.js +33 -0
- package/dist/builders/step-builders.d.ts +62 -0
- package/dist/builders/step-builders.js +99 -0
- package/dist/builders/tool-builder.d.ts +52 -0
- package/dist/builders/tool-builder.js +59 -0
- package/dist/conditions/conditions.d.ts +114 -0
- package/dist/conditions/conditions.js +271 -0
- package/dist/conditions/cosine-similarity.d.ts +11 -0
- package/dist/conditions/cosine-similarity.js +28 -0
- package/dist/conditions/index.d.ts +3 -0
- package/dist/conditions/index.js +2 -0
- package/dist/errors/noetic-config-error.d.ts +41 -0
- package/dist/errors/noetic-config-error.js +44 -0
- package/dist/errors/noetic-error.d.ts +18 -0
- package/dist/errors/noetic-error.js +52 -0
- package/dist/index.d.ts +152 -0
- package/dist/index.js +112 -0
- package/dist/internal-test.d.ts +11 -0
- package/dist/internal-test.js +11 -0
- package/dist/interpreter/clone-guard.d.ts +4 -0
- package/dist/interpreter/clone-guard.js +18 -0
- package/dist/interpreter/collect-tools.d.ts +12 -0
- package/dist/interpreter/collect-tools.js +70 -0
- package/dist/interpreter/execute-branch.d.ts +3 -0
- package/dist/interpreter/execute-branch.js +10 -0
- package/dist/interpreter/execute-every.d.ts +15 -0
- package/dist/interpreter/execute-every.js +143 -0
- package/dist/interpreter/execute-fork.d.ts +3 -0
- package/dist/interpreter/execute-fork.js +215 -0
- package/dist/interpreter/execute-llm.d.ts +4 -0
- package/dist/interpreter/execute-llm.js +217 -0
- package/dist/interpreter/execute-loop.d.ts +3 -0
- package/dist/interpreter/execute-loop.js +171 -0
- package/dist/interpreter/execute-provide.d.ts +10 -0
- package/dist/interpreter/execute-provide.js +64 -0
- package/dist/interpreter/execute-run.d.ts +3 -0
- package/dist/interpreter/execute-run.js +39 -0
- package/dist/interpreter/execute-spawn.d.ts +11 -0
- package/dist/interpreter/execute-spawn.js +113 -0
- package/dist/interpreter/execute-tool.d.ts +5 -0
- package/dist/interpreter/execute-tool.js +81 -0
- package/dist/interpreter/execute.d.ts +14 -0
- package/dist/interpreter/execute.js +119 -0
- package/dist/interpreter/framework-cast.d.ts +10 -0
- package/dist/interpreter/framework-cast.js +16 -0
- package/dist/interpreter/message-helpers.d.ts +9 -0
- package/dist/interpreter/message-helpers.js +42 -0
- package/dist/interpreter/typeguards.d.ts +25 -0
- package/dist/interpreter/typeguards.js +50 -0
- package/dist/memory/budget.d.ts +23 -0
- package/dist/memory/budget.js +117 -0
- package/dist/memory/function-call-utils.d.ts +6 -0
- package/dist/memory/function-call-utils.js +25 -0
- package/dist/memory/layer-api.d.ts +14 -0
- package/dist/memory/layer-api.js +91 -0
- package/dist/memory/layer-lifecycle.d.ts +160 -0
- package/dist/memory/layer-lifecycle.js +552 -0
- package/dist/memory/layer-usage.d.ts +15 -0
- package/dist/memory/layer-usage.js +48 -0
- package/dist/memory/layers/durable-task-state.d.ts +79 -0
- package/dist/memory/layers/durable-task-state.js +118 -0
- package/dist/memory/layers/file-reference.d.ts +55 -0
- package/dist/memory/layers/file-reference.js +538 -0
- package/dist/memory/layers/history-window.d.ts +29 -0
- package/dist/memory/layers/history-window.js +114 -0
- package/dist/memory/layers/observational-memory.d.ts +53 -0
- package/dist/memory/layers/observational-memory.js +110 -0
- package/dist/memory/layers/plan.d.ts +55 -0
- package/dist/memory/layers/plan.js +369 -0
- package/dist/memory/layers/static-content.d.ts +27 -0
- package/dist/memory/layers/static-content.js +35 -0
- package/dist/memory/layers/steering.d.ts +51 -0
- package/dist/memory/layers/steering.js +258 -0
- package/dist/memory/layers/tool-memory-layer.d.ts +16 -0
- package/dist/memory/layers/tool-memory-layer.js +43 -0
- package/dist/memory/layers/working-memory.d.ts +44 -0
- package/dist/memory/layers/working-memory.js +92 -0
- package/dist/memory/projector.d.ts +10 -0
- package/dist/memory/projector.js +19 -0
- package/dist/memory/scope.d.ts +3 -0
- package/dist/memory/scope.js +31 -0
- package/dist/memory/strip-unresolved.d.ts +13 -0
- package/dist/memory/strip-unresolved.js +35 -0
- package/dist/observability/genai-attributes.d.ts +11 -0
- package/dist/observability/genai-attributes.js +12 -0
- package/dist/observability/span-impl.d.ts +20 -0
- package/dist/observability/span-impl.js +33 -0
- package/dist/observability/trace-exporter.d.ts +15 -0
- package/dist/observability/trace-exporter.js +26 -0
- package/dist/patterns/flow.d.ts +52 -0
- package/dist/patterns/flow.js +103 -0
- package/dist/patterns/interview.d.ts +53 -0
- package/dist/patterns/interview.js +113 -0
- package/dist/patterns/plans.d.ts +45 -0
- package/dist/patterns/plans.js +125 -0
- package/dist/patterns/ralph-wiggum.d.ts +20 -0
- package/dist/patterns/ralph-wiggum.js +37 -0
- package/dist/patterns/react.d.ts +18 -0
- package/dist/patterns/react.js +39 -0
- package/dist/portable.d.ts +26 -0
- package/dist/portable.js +34 -0
- package/dist/runtime/agent-harness.d.ts +206 -0
- package/dist/runtime/agent-harness.js +1170 -0
- package/dist/runtime/broadcaster-utils.d.ts +30 -0
- package/dist/runtime/broadcaster-utils.js +53 -0
- package/dist/runtime/channel-store.d.ts +18 -0
- package/dist/runtime/channel-store.js +187 -0
- package/dist/runtime/context-impl.d.ts +80 -0
- package/dist/runtime/context-impl.js +140 -0
- package/dist/runtime/cwd-helpers.d.ts +42 -0
- package/dist/runtime/cwd-helpers.js +68 -0
- package/dist/runtime/detached-handle.d.ts +14 -0
- package/dist/runtime/detached-handle.js +56 -0
- package/dist/runtime/event-broadcaster.d.ts +61 -0
- package/dist/runtime/event-broadcaster.js +187 -0
- package/dist/runtime/exec-context-factory.d.ts +25 -0
- package/dist/runtime/exec-context-factory.js +30 -0
- package/dist/runtime/in-memory-storage.d.ts +9 -0
- package/dist/runtime/in-memory-storage.js +33 -0
- package/dist/runtime/item-log-impl.d.ts +11 -0
- package/dist/runtime/item-log-impl.js +25 -0
- package/dist/runtime/message-queue.d.ts +30 -0
- package/dist/runtime/message-queue.js +46 -0
- package/dist/runtime/session-runner.d.ts +77 -0
- package/dist/runtime/session-runner.js +253 -0
- package/dist/runtime/session-streams.d.ts +11 -0
- package/dist/runtime/session-streams.js +204 -0
- package/dist/runtime/tool-memory.d.ts +6 -0
- package/dist/runtime/tool-memory.js +22 -0
- package/dist/schemas/item.d.ts +35 -0
- package/dist/schemas/item.js +155 -0
- package/dist/types/channel.d.ts +29 -0
- package/dist/types/channel.js +1 -0
- package/dist/types/common.d.ts +111 -0
- package/dist/types/common.js +1 -0
- package/dist/types/context.d.ts +93 -0
- package/dist/types/context.js +1 -0
- package/dist/types/detached.d.ts +17 -0
- package/dist/types/detached.js +7 -0
- package/dist/types/embed.d.ts +5 -0
- package/dist/types/embed.js +1 -0
- package/dist/types/error.d.ts +51 -0
- package/dist/types/error.js +1 -0
- package/dist/types/fs-adapter.d.ts +51 -0
- package/dist/types/fs-adapter.js +3 -0
- package/dist/types/harness-result.d.ts +43 -0
- package/dist/types/harness-result.js +2 -0
- package/dist/types/items.d.ts +115 -0
- package/dist/types/items.js +2 -0
- package/dist/types/memory.d.ts +336 -0
- package/dist/types/memory.js +13 -0
- package/dist/types/mutable-context.d.ts +13 -0
- package/dist/types/mutable-context.js +1 -0
- package/dist/types/observability.d.ts +30 -0
- package/dist/types/observability.js +1 -0
- package/dist/types/runtime.d.ts +255 -0
- package/dist/types/runtime.js +1 -0
- package/dist/types/shell-adapter.d.ts +34 -0
- package/dist/types/shell-adapter.js +10 -0
- package/dist/types/steering.d.ts +89 -0
- package/dist/types/steering.js +14 -0
- package/dist/types/step.d.ts +206 -0
- package/dist/types/step.js +1 -0
- package/dist/types/subprocess-adapter.d.ts +41 -0
- package/dist/types/subprocess-adapter.js +1 -0
- package/dist/types/tool-context.d.ts +34 -0
- package/dist/types/tool-context.js +1 -0
- package/dist/unstable.d.ts +28 -0
- package/dist/unstable.js +24 -0
- package/dist/until/combinators.d.ts +17 -0
- package/dist/until/combinators.js +49 -0
- package/dist/until/never.d.ts +9 -0
- package/dist/until/never.js +12 -0
- package/dist/until/predicates.d.ts +43 -0
- package/dist/until/predicates.js +126 -0
- package/package.json +81 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { MemoryScope } from '../../types/memory';
|
|
2
|
+
import type { AfterModelCallParams, BeforeToolCallParams, LedgerEntry, SteeringConfig, SteeringDecision, SteeringState } from '../../types/steering';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a steering memory layer that evaluates rules before tool calls and after model calls.
|
|
5
|
+
*
|
|
6
|
+
* @public
|
|
7
|
+
* @param config - Steering configuration including rules, scope, and max ledger size.
|
|
8
|
+
* @returns A `MemoryLayer` that enforces steering rules via allow/deny/guide decisions.
|
|
9
|
+
*/
|
|
10
|
+
export declare function steering(config: SteeringConfig): {
|
|
11
|
+
id: "steering";
|
|
12
|
+
name: string;
|
|
13
|
+
slot: 90;
|
|
14
|
+
scope: MemoryScope;
|
|
15
|
+
budget: {
|
|
16
|
+
min: number;
|
|
17
|
+
max: number;
|
|
18
|
+
};
|
|
19
|
+
timeouts: {
|
|
20
|
+
beforeToolCall: number;
|
|
21
|
+
afterModelCall: number;
|
|
22
|
+
};
|
|
23
|
+
hooks: {
|
|
24
|
+
init({ storage }: import("../..").InitParams): Promise<{
|
|
25
|
+
state: SteeringState;
|
|
26
|
+
}>;
|
|
27
|
+
recall({ state }: import("../..").RecallParams<SteeringState>): Promise<{
|
|
28
|
+
items: import("../..").InputMessageItem[];
|
|
29
|
+
tokenCount: number;
|
|
30
|
+
state: SteeringState;
|
|
31
|
+
} | null>;
|
|
32
|
+
store(): Promise<undefined>;
|
|
33
|
+
beforeToolCall({ toolName, toolArgs, ctx, state }: BeforeToolCallParams<SteeringState>): Promise<{
|
|
34
|
+
decision: SteeringDecision;
|
|
35
|
+
state: SteeringState;
|
|
36
|
+
}>;
|
|
37
|
+
afterModelCall({ response, ctx, state }: AfterModelCallParams<SteeringState>): Promise<{
|
|
38
|
+
decision: SteeringDecision;
|
|
39
|
+
state: SteeringState;
|
|
40
|
+
}>;
|
|
41
|
+
onSpawn({ parentState }: import("../..").SpawnParams<SteeringState>): Promise<{
|
|
42
|
+
childState: {
|
|
43
|
+
ledger: LedgerEntry[];
|
|
44
|
+
pendingAsync: never[];
|
|
45
|
+
};
|
|
46
|
+
}>;
|
|
47
|
+
onComplete({ state, outcome }: import("../..").CompleteParams<SteeringState>): Promise<{
|
|
48
|
+
state: SteeringState;
|
|
49
|
+
}>;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { NoeticConfigError } from '../../errors/noetic-config-error';
|
|
2
|
+
import { createMessage, estimateTokens, extractAssistantText, } from '../../interpreter/message-helpers';
|
|
3
|
+
import { Slot } from '../../types/memory';
|
|
4
|
+
import { LedgerEntryKind, SteeringAction } from '../../types/steering';
|
|
5
|
+
import { mostRestrictive } from '../layer-lifecycle';
|
|
6
|
+
//#region Constants
|
|
7
|
+
const DEFAULT_MAX_LEDGER_ENTRIES = 100;
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region Rule Evaluation Helpers
|
|
10
|
+
function requireCallModel(params, ruleId) {
|
|
11
|
+
if (!params.ctx.callModel) {
|
|
12
|
+
throw new NoeticConfigError({
|
|
13
|
+
code: 'MISSING_CALL_MODEL',
|
|
14
|
+
message: `LLM-evaluated steering rule "${ruleId}" requires a callModel but none is available.`,
|
|
15
|
+
hint: 'Pass `llm: { provider: "openrouter", apiKey: "..." }` to AgentHarness or set OPENROUTER_API_KEY.',
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function evaluateProgrammaticRule(rule, params) {
|
|
20
|
+
if (!rule.predicate) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return rule.predicate(params);
|
|
24
|
+
}
|
|
25
|
+
async function evaluateLlmRuleSync(rule, params) {
|
|
26
|
+
if (!rule.llmEval) {
|
|
27
|
+
return {
|
|
28
|
+
action: SteeringAction.Allow,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const { callModel } = params.ctx;
|
|
32
|
+
if (!callModel) {
|
|
33
|
+
return {
|
|
34
|
+
action: SteeringAction.Allow,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const contextSummary = 'toolName' in params
|
|
38
|
+
? `Tool: ${params.toolName}, Args: ${JSON.stringify(params.toolArgs)}`
|
|
39
|
+
: `Response items: ${params.response.items.length}`;
|
|
40
|
+
const prompt = `${rule.llmEval.prompt}\n\nContext: ${contextSummary}\n\nRespond with exactly "ALLOW", "DENY", or "GUIDE: <guidance text>".`;
|
|
41
|
+
const userMessage = createMessage(prompt, 'user');
|
|
42
|
+
const model = rule.llmEval.model ?? 'openai/gpt-4o-mini';
|
|
43
|
+
const response = await callModel({
|
|
44
|
+
model,
|
|
45
|
+
items: [
|
|
46
|
+
userMessage,
|
|
47
|
+
],
|
|
48
|
+
});
|
|
49
|
+
const text = extractAssistantText(response.items).trim().toUpperCase();
|
|
50
|
+
if (text.startsWith('DENY')) {
|
|
51
|
+
return {
|
|
52
|
+
action: SteeringAction.Deny,
|
|
53
|
+
guidance: text.slice(5).trim() || undefined,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (text.startsWith('GUIDE:')) {
|
|
57
|
+
return {
|
|
58
|
+
action: SteeringAction.Guide,
|
|
59
|
+
guidance: text.slice(6).trim(),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
action: SteeringAction.Allow,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function fireLlmRuleAsync({ rule, params, pendingAsync }) {
|
|
67
|
+
evaluateLlmRuleSync(rule, params)
|
|
68
|
+
.then((decision) => {
|
|
69
|
+
if (decision.action !== SteeringAction.Allow) {
|
|
70
|
+
pendingAsync.push({
|
|
71
|
+
ruleId: rule.id,
|
|
72
|
+
guidance: decision.guidance ?? `Rule ${rule.id}: ${decision.action}`,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
.catch(() => {
|
|
77
|
+
// Async LLM eval failures are silently swallowed — they must not disrupt the agent
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function trimLedger(ledger, max) {
|
|
81
|
+
if (ledger.length <= max) {
|
|
82
|
+
return ledger;
|
|
83
|
+
}
|
|
84
|
+
return ledger.slice(ledger.length - max);
|
|
85
|
+
}
|
|
86
|
+
async function evaluateRules({ rules, hookName, params, state, }) {
|
|
87
|
+
const applicable = rules.filter((r) => r.appliesTo.includes(hookName));
|
|
88
|
+
const decisions = [];
|
|
89
|
+
for (const rule of applicable) {
|
|
90
|
+
// Programmatic predicate (fast)
|
|
91
|
+
const programmatic = evaluateProgrammaticRule(rule, params);
|
|
92
|
+
if (programmatic) {
|
|
93
|
+
decisions.push(programmatic);
|
|
94
|
+
if (programmatic.action === SteeringAction.Deny) {
|
|
95
|
+
return mostRestrictive(decisions);
|
|
96
|
+
}
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
// LLM eval — throws NoeticConfigError if callModel is missing
|
|
100
|
+
if (!rule.llmEval) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
requireCallModel(params, rule.id);
|
|
104
|
+
if (rule.llmEval.mode === 'sync') {
|
|
105
|
+
const decision = await evaluateLlmRuleSync(rule, params);
|
|
106
|
+
decisions.push(decision);
|
|
107
|
+
if (decision.action === SteeringAction.Deny) {
|
|
108
|
+
return mostRestrictive(decisions);
|
|
109
|
+
}
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
// Async — fire and forget
|
|
113
|
+
fireLlmRuleAsync({
|
|
114
|
+
rule,
|
|
115
|
+
params,
|
|
116
|
+
pendingAsync: state.pendingAsync,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return mostRestrictive(decisions);
|
|
120
|
+
}
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region Public API
|
|
123
|
+
/**
|
|
124
|
+
* Creates a steering memory layer that evaluates rules before tool calls and after model calls.
|
|
125
|
+
*
|
|
126
|
+
* @public
|
|
127
|
+
* @param config - Steering configuration including rules, scope, and max ledger size.
|
|
128
|
+
* @returns A `MemoryLayer` that enforces steering rules via allow/deny/guide decisions.
|
|
129
|
+
*/
|
|
130
|
+
export function steering(config) {
|
|
131
|
+
const maxLedger = config.maxLedgerEntries ?? DEFAULT_MAX_LEDGER_ENTRIES;
|
|
132
|
+
const scope = config.scope ?? 'execution';
|
|
133
|
+
return {
|
|
134
|
+
id: 'steering',
|
|
135
|
+
name: 'Steering',
|
|
136
|
+
slot: Slot.STEERING,
|
|
137
|
+
scope,
|
|
138
|
+
budget: {
|
|
139
|
+
min: 0,
|
|
140
|
+
max: 500,
|
|
141
|
+
},
|
|
142
|
+
timeouts: {
|
|
143
|
+
beforeToolCall: 5e3,
|
|
144
|
+
afterModelCall: 1e4,
|
|
145
|
+
},
|
|
146
|
+
hooks: {
|
|
147
|
+
async init({ storage }) {
|
|
148
|
+
const saved = await storage.get('state');
|
|
149
|
+
const state = saved ?? {
|
|
150
|
+
ledger: [],
|
|
151
|
+
pendingAsync: [],
|
|
152
|
+
};
|
|
153
|
+
return {
|
|
154
|
+
state,
|
|
155
|
+
};
|
|
156
|
+
},
|
|
157
|
+
async recall({ state }) {
|
|
158
|
+
if (state.pendingAsync.length === 0) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
const feedback = state.pendingAsync.map((p) => `[${p.ruleId}] ${p.guidance}`).join('\n');
|
|
162
|
+
state.pendingAsync = [];
|
|
163
|
+
const content = `<steering_feedback>\n${feedback}\n</steering_feedback>`;
|
|
164
|
+
return {
|
|
165
|
+
items: [
|
|
166
|
+
createMessage(content, 'developer'),
|
|
167
|
+
],
|
|
168
|
+
tokenCount: estimateTokens(content),
|
|
169
|
+
state,
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
async store() {
|
|
173
|
+
// Model turn ledger entries are recorded in afterModelCall to avoid duplicates
|
|
174
|
+
return undefined;
|
|
175
|
+
},
|
|
176
|
+
async beforeToolCall({ toolName, toolArgs, ctx, state }) {
|
|
177
|
+
const decision = await evaluateRules({
|
|
178
|
+
rules: config.rules,
|
|
179
|
+
hookName: 'beforeToolCall',
|
|
180
|
+
params: {
|
|
181
|
+
toolName,
|
|
182
|
+
toolArgs,
|
|
183
|
+
ctx,
|
|
184
|
+
state,
|
|
185
|
+
},
|
|
186
|
+
state,
|
|
187
|
+
});
|
|
188
|
+
const entry = {
|
|
189
|
+
kind: LedgerEntryKind.ToolCall,
|
|
190
|
+
timestamp: Date.now(),
|
|
191
|
+
toolName,
|
|
192
|
+
toolArgs,
|
|
193
|
+
ruleId: decision.action !== SteeringAction.Allow ? 'steering' : undefined,
|
|
194
|
+
action: decision.action,
|
|
195
|
+
guidance: decision.guidance,
|
|
196
|
+
};
|
|
197
|
+
state.ledger.push(entry);
|
|
198
|
+
state.ledger = trimLedger(state.ledger, maxLedger);
|
|
199
|
+
return {
|
|
200
|
+
decision,
|
|
201
|
+
state,
|
|
202
|
+
};
|
|
203
|
+
},
|
|
204
|
+
async afterModelCall({ response, ctx, state }) {
|
|
205
|
+
const decision = await evaluateRules({
|
|
206
|
+
rules: config.rules,
|
|
207
|
+
hookName: 'afterModelCall',
|
|
208
|
+
params: {
|
|
209
|
+
response,
|
|
210
|
+
ctx,
|
|
211
|
+
state,
|
|
212
|
+
},
|
|
213
|
+
state,
|
|
214
|
+
});
|
|
215
|
+
const entry = {
|
|
216
|
+
kind: LedgerEntryKind.ModelTurn,
|
|
217
|
+
timestamp: Date.now(),
|
|
218
|
+
tokenUsage: {
|
|
219
|
+
input: response.usage.inputTokens,
|
|
220
|
+
output: response.usage.outputTokens,
|
|
221
|
+
},
|
|
222
|
+
ruleId: decision.action !== SteeringAction.Allow ? 'steering' : undefined,
|
|
223
|
+
action: decision.action,
|
|
224
|
+
guidance: decision.guidance,
|
|
225
|
+
};
|
|
226
|
+
state.ledger.push(entry);
|
|
227
|
+
state.ledger = trimLedger(state.ledger, maxLedger);
|
|
228
|
+
return {
|
|
229
|
+
decision,
|
|
230
|
+
state,
|
|
231
|
+
};
|
|
232
|
+
},
|
|
233
|
+
async onSpawn({ parentState }) {
|
|
234
|
+
return {
|
|
235
|
+
childState: {
|
|
236
|
+
ledger: structuredClone(parentState.ledger),
|
|
237
|
+
pendingAsync: [],
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
},
|
|
241
|
+
async onComplete({ state, outcome }) {
|
|
242
|
+
const entry = {
|
|
243
|
+
kind: LedgerEntryKind.Custom,
|
|
244
|
+
timestamp: Date.now(),
|
|
245
|
+
custom: {
|
|
246
|
+
outcome,
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
state.ledger.push(entry);
|
|
250
|
+
state.ledger = trimLedger(state.ledger, maxLedger);
|
|
251
|
+
return {
|
|
252
|
+
state,
|
|
253
|
+
};
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
//#endregion
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Tool } from '../../types/common';
|
|
2
|
+
import type { MemoryLayer } from '../../types/memory';
|
|
3
|
+
interface ToolMemoryLayerOpts {
|
|
4
|
+
slot?: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Generates one MemoryLayer per unique memory id among the provided tools.
|
|
8
|
+
* Tools sharing the same `memory.id` share a single layer (and thus state).
|
|
9
|
+
*
|
|
10
|
+
* @public
|
|
11
|
+
* @param tools - Array of tools to extract memory declarations from.
|
|
12
|
+
* @param opts - Optional slot override for the generated layers.
|
|
13
|
+
* @returns An array of `MemoryLayer` instances, one per unique tool memory id.
|
|
14
|
+
*/
|
|
15
|
+
export declare function toolMemoryLayer(tools: ReadonlyArray<Tool>, opts?: ToolMemoryLayerOpts): MemoryLayer[];
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Slot } from '../../types/memory';
|
|
2
|
+
const EXECUTION_SCOPE = 'execution';
|
|
3
|
+
/**
|
|
4
|
+
* Generates one MemoryLayer per unique memory id among the provided tools.
|
|
5
|
+
* Tools sharing the same `memory.id` share a single layer (and thus state).
|
|
6
|
+
*
|
|
7
|
+
* @public
|
|
8
|
+
* @param tools - Array of tools to extract memory declarations from.
|
|
9
|
+
* @param opts - Optional slot override for the generated layers.
|
|
10
|
+
* @returns An array of `MemoryLayer` instances, one per unique tool memory id.
|
|
11
|
+
*/
|
|
12
|
+
export function toolMemoryLayer(tools, opts) {
|
|
13
|
+
const seen = new Map();
|
|
14
|
+
for (const t of tools) {
|
|
15
|
+
if (!t.memory) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const memId = t.memory.id ?? t.name;
|
|
19
|
+
if (seen.has(memId)) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
seen.set(memId, t.memory);
|
|
23
|
+
}
|
|
24
|
+
const slot = opts?.slot ?? Slot.WORKING_MEMORY + 10;
|
|
25
|
+
return [
|
|
26
|
+
...seen.entries(),
|
|
27
|
+
].map(([memId, decl]) => ({
|
|
28
|
+
id: memId,
|
|
29
|
+
name: memId,
|
|
30
|
+
slot,
|
|
31
|
+
scope: EXECUTION_SCOPE,
|
|
32
|
+
hooks: {
|
|
33
|
+
async init() {
|
|
34
|
+
return {
|
|
35
|
+
state: decl.init(),
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
async recall({ state }) {
|
|
39
|
+
return decl.recall(state);
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ZodType } from 'zod';
|
|
2
|
+
export type WorkingMemoryState = string | Record<string, unknown>;
|
|
3
|
+
export interface WorkingMemoryConfig {
|
|
4
|
+
scope?: 'thread' | 'resource';
|
|
5
|
+
schema?: ZodType;
|
|
6
|
+
template?: string;
|
|
7
|
+
readOnly?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Creates a mutable working memory layer that the model can update via the `working-memory/update` tool.
|
|
11
|
+
*
|
|
12
|
+
* @public
|
|
13
|
+
* @param config - Optional configuration for scope, Zod schema, template, and read-only mode.
|
|
14
|
+
* @returns A `MemoryLayer` providing scratchpad state the model can read and write.
|
|
15
|
+
*/
|
|
16
|
+
export declare function workingMemory(config?: WorkingMemoryConfig): {
|
|
17
|
+
id: "working-memory";
|
|
18
|
+
name: string;
|
|
19
|
+
slot: 100;
|
|
20
|
+
scope: "thread" | "resource";
|
|
21
|
+
budget: {
|
|
22
|
+
min: number;
|
|
23
|
+
max: number;
|
|
24
|
+
};
|
|
25
|
+
provides: {
|
|
26
|
+
snapshot: import("../..").LayerDataDecl<WorkingMemoryState, WorkingMemoryState>;
|
|
27
|
+
update: import("../..").LayerFunctionDecl<Record<string, unknown>, void, WorkingMemoryState>;
|
|
28
|
+
};
|
|
29
|
+
hooks: {
|
|
30
|
+
init({ storage }: import("../..").InitParams): Promise<{
|
|
31
|
+
state: WorkingMemoryState;
|
|
32
|
+
}>;
|
|
33
|
+
recall({ state }: import("../..").RecallParams<WorkingMemoryState>): Promise<{
|
|
34
|
+
items: import("../..").InputMessageItem[];
|
|
35
|
+
tokenCount: number;
|
|
36
|
+
} | null>;
|
|
37
|
+
store({ newItems, state }: import("../..").StoreParams<WorkingMemoryState>): Promise<{
|
|
38
|
+
state: WorkingMemoryState;
|
|
39
|
+
} | undefined>;
|
|
40
|
+
onSpawn({ parentState }: import("../..").SpawnParams<WorkingMemoryState>): Promise<{
|
|
41
|
+
childState: WorkingMemoryState;
|
|
42
|
+
} | null>;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { layerData, layerFn } from '../../builders/layer-provides-builders';
|
|
3
|
+
import { createMessage, estimateTokens } from '../../interpreter/message-helpers';
|
|
4
|
+
import { Slot } from '../../types/memory';
|
|
5
|
+
import { findFunctionCall } from '../function-call-utils';
|
|
6
|
+
function safeMerge(state, args) {
|
|
7
|
+
const { __proto__: _p, constructor: _c, ...safeArgs } = args;
|
|
8
|
+
if (typeof state === 'object' && state !== null) {
|
|
9
|
+
return {
|
|
10
|
+
...state,
|
|
11
|
+
...safeArgs,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
return safeArgs;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Creates a mutable working memory layer that the model can update via the `working-memory/update` tool.
|
|
18
|
+
*
|
|
19
|
+
* @public
|
|
20
|
+
* @param config - Optional configuration for scope, Zod schema, template, and read-only mode.
|
|
21
|
+
* @returns A `MemoryLayer` providing scratchpad state the model can read and write.
|
|
22
|
+
*/
|
|
23
|
+
export function workingMemory(config) {
|
|
24
|
+
const scope = config?.scope ?? 'thread';
|
|
25
|
+
return {
|
|
26
|
+
id: 'working-memory',
|
|
27
|
+
name: 'Working Memory',
|
|
28
|
+
slot: Slot.WORKING_MEMORY,
|
|
29
|
+
scope,
|
|
30
|
+
budget: {
|
|
31
|
+
min: 200,
|
|
32
|
+
max: 1_500,
|
|
33
|
+
},
|
|
34
|
+
provides: {
|
|
35
|
+
snapshot: layerData({
|
|
36
|
+
read: (state) => state,
|
|
37
|
+
}),
|
|
38
|
+
update: layerFn({
|
|
39
|
+
description: 'Update the agent working memory with new key-value pairs.',
|
|
40
|
+
input: z.record(z.string(), z.unknown()),
|
|
41
|
+
output: z.void(),
|
|
42
|
+
execute: async (args, state) => ({
|
|
43
|
+
result: undefined,
|
|
44
|
+
state: safeMerge(state, args),
|
|
45
|
+
}),
|
|
46
|
+
}),
|
|
47
|
+
},
|
|
48
|
+
hooks: {
|
|
49
|
+
async init({ storage }) {
|
|
50
|
+
const saved = await storage.get('state');
|
|
51
|
+
const state = saved ?? (config?.schema ? {} : '');
|
|
52
|
+
return {
|
|
53
|
+
state,
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
async recall({ state }) {
|
|
57
|
+
if (!state || (typeof state === 'object' && Object.keys(state).length === 0)) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const text = typeof state === 'string' ? state : JSON.stringify(state, null, 2);
|
|
61
|
+
const content = `<working_memory>\n${text}\n</working_memory>`;
|
|
62
|
+
return {
|
|
63
|
+
items: [
|
|
64
|
+
createMessage(content, 'developer'),
|
|
65
|
+
],
|
|
66
|
+
tokenCount: estimateTokens(content),
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
async store({ newItems, state }) {
|
|
70
|
+
if (config?.readOnly) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Backward compat: detect implicit updateWorkingMemory calls from LLMs
|
|
74
|
+
const args = findFunctionCall(newItems, 'updateWorkingMemory');
|
|
75
|
+
if (!args) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
state: safeMerge(state, args),
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
async onSpawn({ parentState }) {
|
|
83
|
+
if (scope === 'resource') {
|
|
84
|
+
return {
|
|
85
|
+
childState: structuredClone(parentState),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Item } from '../types/items';
|
|
2
|
+
import type { ProjectionPolicy } from '../types/memory';
|
|
3
|
+
interface AssembleViewParams {
|
|
4
|
+
systemPromptItems: Item[];
|
|
5
|
+
layerOutputItems: Item[];
|
|
6
|
+
historyItems: Item[];
|
|
7
|
+
policy?: ProjectionPolicy;
|
|
8
|
+
}
|
|
9
|
+
export declare function assembleView({ systemPromptItems, layerOutputItems, historyItems, policy, }: AssembleViewParams): Item[];
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#endregion
|
|
2
|
+
//#region Public API
|
|
3
|
+
export function assembleView({ systemPromptItems, layerOutputItems, historyItems, policy, }) {
|
|
4
|
+
const view = [];
|
|
5
|
+
// Add system prompt items
|
|
6
|
+
view.push(...systemPromptItems);
|
|
7
|
+
// Add layer output items (sorted by slot already from recall)
|
|
8
|
+
view.push(...layerOutputItems);
|
|
9
|
+
// Add history items (with overflow handling if policy specified)
|
|
10
|
+
if (policy?.overflow === 'sliding_window' && policy.windowSize) {
|
|
11
|
+
const windowItems = historyItems.slice(-policy.windowSize);
|
|
12
|
+
view.push(...windowItems);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
view.push(...historyItems);
|
|
16
|
+
}
|
|
17
|
+
return view;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ExecutionContext, MemoryScope, ScopedStorage, StorageAdapter } from '../types/memory';
|
|
2
|
+
export declare function resolveScopeKey(scope: MemoryScope, ctx: ExecutionContext): string;
|
|
3
|
+
export declare function createScopedStorage(storage: StorageAdapter, layerId: string, scopeKey: string): ScopedStorage;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function resolveScopeKey(scope, ctx) {
|
|
2
|
+
switch (scope) {
|
|
3
|
+
case 'thread':
|
|
4
|
+
return ctx.threadId;
|
|
5
|
+
case 'resource':
|
|
6
|
+
return ctx.resourceId ?? ctx.threadId;
|
|
7
|
+
case 'global':
|
|
8
|
+
return '__global__';
|
|
9
|
+
case 'execution':
|
|
10
|
+
return ctx.executionId;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function createScopedStorage(storage, layerId, scopeKey) {
|
|
14
|
+
const prefix = `layers/${layerId}/${scopeKey}/`;
|
|
15
|
+
return {
|
|
16
|
+
async get(key) {
|
|
17
|
+
return storage.get(`${prefix}${key}`);
|
|
18
|
+
},
|
|
19
|
+
async set(key, value) {
|
|
20
|
+
return storage.set(`${prefix}${key}`, value);
|
|
21
|
+
},
|
|
22
|
+
async delete(key) {
|
|
23
|
+
return storage.delete(`${prefix}${key}`);
|
|
24
|
+
},
|
|
25
|
+
async list(keyPrefix) {
|
|
26
|
+
const fullPrefix = keyPrefix ? `${prefix}${keyPrefix}` : prefix;
|
|
27
|
+
const keys = await storage.list(fullPrefix);
|
|
28
|
+
return keys.map((k) => (k.startsWith(prefix) ? k.slice(prefix.length) : k));
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drop dangling tool-call state from an item list. A `function_call` without a
|
|
3
|
+
* matching `function_call_output` (or vice-versa) wedges the model on the next
|
|
4
|
+
* round (it typically fabricates an output), so we filter both sides of any
|
|
5
|
+
* incomplete pair. Order-preserving and pure — safe to run after a slice.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors Claude Code's `filterUnresolvedToolUses`. Used by the CLI's
|
|
8
|
+
* session-resume path to drop dangling pairs left by a crash, and by the
|
|
9
|
+
* `historyWindow` memory layer to clean orphans at the slice boundary.
|
|
10
|
+
*/
|
|
11
|
+
import type { Item } from '../types/items';
|
|
12
|
+
/** @public */
|
|
13
|
+
export declare function stripUnresolvedToolCalls(items: ReadonlyArray<Item>): Item[];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drop dangling tool-call state from an item list. A `function_call` without a
|
|
3
|
+
* matching `function_call_output` (or vice-versa) wedges the model on the next
|
|
4
|
+
* round (it typically fabricates an output), so we filter both sides of any
|
|
5
|
+
* incomplete pair. Order-preserving and pure — safe to run after a slice.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors Claude Code's `filterUnresolvedToolUses`. Used by the CLI's
|
|
8
|
+
* session-resume path to drop dangling pairs left by a crash, and by the
|
|
9
|
+
* `historyWindow` memory layer to clean orphans at the slice boundary.
|
|
10
|
+
*/
|
|
11
|
+
/** @public */
|
|
12
|
+
export function stripUnresolvedToolCalls(items) {
|
|
13
|
+
const outputCallIds = new Set();
|
|
14
|
+
const callCallIds = new Set();
|
|
15
|
+
for (const item of items) {
|
|
16
|
+
if (item.type === 'function_call_output') {
|
|
17
|
+
outputCallIds.add(item.callId);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (item.type === 'function_call') {
|
|
21
|
+
callCallIds.add(item.callId);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const keep = [];
|
|
25
|
+
for (const item of items) {
|
|
26
|
+
if (item.type === 'function_call' && !outputCallIds.has(item.callId)) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (item.type === 'function_call_output' && !callCallIds.has(item.callId)) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
keep.push(item);
|
|
33
|
+
}
|
|
34
|
+
return keep;
|
|
35
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const GenAI: {
|
|
2
|
+
readonly SYSTEM: "gen_ai.system";
|
|
3
|
+
readonly REQUEST_MODEL: "gen_ai.request.model";
|
|
4
|
+
readonly USAGE_INPUT_TOKENS: "gen_ai.usage.input_tokens";
|
|
5
|
+
readonly USAGE_OUTPUT_TOKENS: "gen_ai.usage.output_tokens";
|
|
6
|
+
readonly COST: "gen_ai.cost";
|
|
7
|
+
};
|
|
8
|
+
export declare const ToolAttr: {
|
|
9
|
+
readonly NAME: "tool.name";
|
|
10
|
+
readonly NEEDS_APPROVAL: "tool.needs_approval";
|
|
11
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// GenAI Semantic Convention attributes
|
|
2
|
+
export const GenAI = {
|
|
3
|
+
SYSTEM: 'gen_ai.system',
|
|
4
|
+
REQUEST_MODEL: 'gen_ai.request.model',
|
|
5
|
+
USAGE_INPUT_TOKENS: 'gen_ai.usage.input_tokens',
|
|
6
|
+
USAGE_OUTPUT_TOKENS: 'gen_ai.usage.output_tokens',
|
|
7
|
+
COST: 'gen_ai.cost',
|
|
8
|
+
};
|
|
9
|
+
export const ToolAttr = {
|
|
10
|
+
NAME: 'tool.name',
|
|
11
|
+
NEEDS_APPROVAL: 'tool.needs_approval',
|
|
12
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Span } from '../types/observability';
|
|
2
|
+
export declare class SpanImpl implements Span {
|
|
3
|
+
readonly traceId: string;
|
|
4
|
+
readonly spanId: string;
|
|
5
|
+
readonly parentSpanId: string | null;
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly startTime: number;
|
|
8
|
+
endTime?: number;
|
|
9
|
+
readonly attributes: Map<string, string | number | boolean>;
|
|
10
|
+
readonly events: Array<{
|
|
11
|
+
name: string;
|
|
12
|
+
timestamp: number;
|
|
13
|
+
attributes?: Record<string, string | number | boolean>;
|
|
14
|
+
}>;
|
|
15
|
+
constructor(name: string, parent: Span | null, traceId?: string);
|
|
16
|
+
setAttribute(key: string, value: string | number | boolean): void;
|
|
17
|
+
addEvent(name: string, attributes?: Record<string, string | number | boolean>): void;
|
|
18
|
+
end(): void;
|
|
19
|
+
get duration(): number;
|
|
20
|
+
}
|