@graphorin/memory 0.5.0
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/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +401 -0
- package/dist/conflict/index.d.ts +3 -0
- package/dist/conflict/index.js +7 -0
- package/dist/conflict/locale-packs/en.js +262 -0
- package/dist/conflict/locale-packs/en.js.map +1 -0
- package/dist/conflict/locale-packs/index.js +4 -0
- package/dist/conflict/locale-packs/types.d.ts +103 -0
- package/dist/conflict/locale-packs/types.d.ts.map +1 -0
- package/dist/conflict/locale-packs/types.js +55 -0
- package/dist/conflict/locale-packs/types.js.map +1 -0
- package/dist/conflict/pipeline.d.ts +35 -0
- package/dist/conflict/pipeline.d.ts.map +1 -0
- package/dist/conflict/pipeline.js +257 -0
- package/dist/conflict/pipeline.js.map +1 -0
- package/dist/conflict/stages/helpers.js +66 -0
- package/dist/conflict/stages/helpers.js.map +1 -0
- package/dist/conflict/stages/stage1-exact-dedup.js +21 -0
- package/dist/conflict/stages/stage1-exact-dedup.js.map +1 -0
- package/dist/conflict/stages/stage2-embedding-three-zone.js +37 -0
- package/dist/conflict/stages/stage2-embedding-three-zone.js.map +1 -0
- package/dist/conflict/stages/stage3-heuristic-regex.js +45 -0
- package/dist/conflict/stages/stage3-heuristic-regex.js.map +1 -0
- package/dist/conflict/stages/stage4-subject-predicate.js +31 -0
- package/dist/conflict/stages/stage4-subject-predicate.js.map +1 -0
- package/dist/conflict/stages/stage5-defer-to-deep.js +22 -0
- package/dist/conflict/stages/stage5-defer-to-deep.js.map +1 -0
- package/dist/conflict/types.d.ts +222 -0
- package/dist/conflict/types.d.ts.map +1 -0
- package/dist/conflict/types.js +50 -0
- package/dist/conflict/types.js.map +1 -0
- package/dist/consolidator/budget.d.ts +1 -0
- package/dist/consolidator/budget.js +214 -0
- package/dist/consolidator/budget.js.map +1 -0
- package/dist/consolidator/decay.d.ts +35 -0
- package/dist/consolidator/decay.d.ts.map +1 -0
- package/dist/consolidator/decay.js +91 -0
- package/dist/consolidator/decay.js.map +1 -0
- package/dist/consolidator/dlq.js +58 -0
- package/dist/consolidator/dlq.js.map +1 -0
- package/dist/consolidator/errors.d.ts +54 -0
- package/dist/consolidator/errors.d.ts.map +1 -0
- package/dist/consolidator/errors.js +67 -0
- package/dist/consolidator/errors.js.map +1 -0
- package/dist/consolidator/idempotency.d.ts +1 -0
- package/dist/consolidator/idempotency.js +22 -0
- package/dist/consolidator/idempotency.js.map +1 -0
- package/dist/consolidator/index.d.ts +34 -0
- package/dist/consolidator/index.d.ts.map +1 -0
- package/dist/consolidator/index.js +163 -0
- package/dist/consolidator/index.js.map +1 -0
- package/dist/consolidator/lock.d.ts +2 -0
- package/dist/consolidator/lock.js +89 -0
- package/dist/consolidator/lock.js.map +1 -0
- package/dist/consolidator/noise-filter.d.ts +2 -0
- package/dist/consolidator/noise-filter.js +173 -0
- package/dist/consolidator/noise-filter.js.map +1 -0
- package/dist/consolidator/phases/deep.d.ts +4 -0
- package/dist/consolidator/phases/deep.js +198 -0
- package/dist/consolidator/phases/deep.js.map +1 -0
- package/dist/consolidator/phases/induce.d.ts +154 -0
- package/dist/consolidator/phases/induce.d.ts.map +1 -0
- package/dist/consolidator/phases/induce.js +253 -0
- package/dist/consolidator/phases/induce.js.map +1 -0
- package/dist/consolidator/phases/light.d.ts +4 -0
- package/dist/consolidator/phases/light.js +99 -0
- package/dist/consolidator/phases/light.js.map +1 -0
- package/dist/consolidator/phases/reflect.js +287 -0
- package/dist/consolidator/phases/reflect.js.map +1 -0
- package/dist/consolidator/phases/standard.d.ts +8 -0
- package/dist/consolidator/phases/standard.js +499 -0
- package/dist/consolidator/phases/standard.js.map +1 -0
- package/dist/consolidator/reconcile.js +192 -0
- package/dist/consolidator/reconcile.js.map +1 -0
- package/dist/consolidator/runtime.d.ts +62 -0
- package/dist/consolidator/runtime.d.ts.map +1 -0
- package/dist/consolidator/runtime.js +580 -0
- package/dist/consolidator/runtime.js.map +1 -0
- package/dist/consolidator/scheduler.d.ts +111 -0
- package/dist/consolidator/scheduler.d.ts.map +1 -0
- package/dist/consolidator/scheduler.js +71 -0
- package/dist/consolidator/scheduler.js.map +1 -0
- package/dist/consolidator/triggers.d.ts +40 -0
- package/dist/consolidator/triggers.d.ts.map +1 -0
- package/dist/consolidator/triggers.js +94 -0
- package/dist/consolidator/triggers.js.map +1 -0
- package/dist/consolidator/types.d.ts +397 -0
- package/dist/consolidator/types.d.ts.map +1 -0
- package/dist/consolidator/types.js +117 -0
- package/dist/consolidator/types.js.map +1 -0
- package/dist/context-engine/annotations.d.ts +118 -0
- package/dist/context-engine/annotations.d.ts.map +1 -0
- package/dist/context-engine/annotations.js +102 -0
- package/dist/context-engine/annotations.js.map +1 -0
- package/dist/context-engine/auto-recall.d.ts +52 -0
- package/dist/context-engine/auto-recall.d.ts.map +1 -0
- package/dist/context-engine/auto-recall.js +41 -0
- package/dist/context-engine/auto-recall.js.map +1 -0
- package/dist/context-engine/compaction/clear-tool-results.d.ts +55 -0
- package/dist/context-engine/compaction/clear-tool-results.d.ts.map +1 -0
- package/dist/context-engine/compaction/clear-tool-results.js +97 -0
- package/dist/context-engine/compaction/clear-tool-results.js.map +1 -0
- package/dist/context-engine/compaction/compactor.d.ts +52 -0
- package/dist/context-engine/compaction/compactor.d.ts.map +1 -0
- package/dist/context-engine/compaction/compactor.js +196 -0
- package/dist/context-engine/compaction/compactor.js.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.d.ts +15 -0
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.d.ts.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.js +27 -0
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.js.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.d.ts +18 -0
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.d.ts.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.js +47 -0
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.js.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.d.ts +15 -0
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.d.ts.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.js +32 -0
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.js.map +1 -0
- package/dist/context-engine/compaction/hooks/types.d.ts +42 -0
- package/dist/context-engine/compaction/hooks/types.d.ts.map +1 -0
- package/dist/context-engine/compaction/index.d.ts +9 -0
- package/dist/context-engine/compaction/index.js +9 -0
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts +84 -0
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts.map +1 -0
- package/dist/context-engine/compaction/templates/summary-9-section.js +84 -0
- package/dist/context-engine/compaction/templates/summary-9-section.js.map +1 -0
- package/dist/context-engine/compaction/thresholds.d.ts +68 -0
- package/dist/context-engine/compaction/thresholds.d.ts.map +1 -0
- package/dist/context-engine/compaction/thresholds.js +63 -0
- package/dist/context-engine/compaction/thresholds.js.map +1 -0
- package/dist/context-engine/compaction/types.d.ts +202 -0
- package/dist/context-engine/compaction/types.d.ts.map +1 -0
- package/dist/context-engine/engine.d.ts +258 -0
- package/dist/context-engine/engine.d.ts.map +1 -0
- package/dist/context-engine/engine.js +424 -0
- package/dist/context-engine/engine.js.map +1 -0
- package/dist/context-engine/index.d.ts +68 -0
- package/dist/context-engine/index.d.ts.map +1 -0
- package/dist/context-engine/index.js +24 -0
- package/dist/context-engine/locale-packs/en.d.ts +14 -0
- package/dist/context-engine/locale-packs/en.d.ts.map +1 -0
- package/dist/context-engine/locale-packs/en.js +87 -0
- package/dist/context-engine/locale-packs/en.js.map +1 -0
- package/dist/context-engine/locale-packs/index.js +4 -0
- package/dist/context-engine/locale-packs/resolver.d.ts +41 -0
- package/dist/context-engine/locale-packs/resolver.d.ts.map +1 -0
- package/dist/context-engine/locale-packs/resolver.js +119 -0
- package/dist/context-engine/locale-packs/resolver.js.map +1 -0
- package/dist/context-engine/locale-packs/types.d.ts +119 -0
- package/dist/context-engine/locale-packs/types.d.ts.map +1 -0
- package/dist/context-engine/locale-packs/types.js +24 -0
- package/dist/context-engine/locale-packs/types.js.map +1 -0
- package/dist/context-engine/metadata.d.ts +40 -0
- package/dist/context-engine/metadata.d.ts.map +1 -0
- package/dist/context-engine/metadata.js +66 -0
- package/dist/context-engine/metadata.js.map +1 -0
- package/dist/context-engine/preambles/inbound-en.d.ts +20 -0
- package/dist/context-engine/preambles/inbound-en.d.ts.map +1 -0
- package/dist/context-engine/preambles/inbound-en.js +23 -0
- package/dist/context-engine/preambles/inbound-en.js.map +1 -0
- package/dist/context-engine/privacy-filter.d.ts +108 -0
- package/dist/context-engine/privacy-filter.d.ts.map +1 -0
- package/dist/context-engine/privacy-filter.js +113 -0
- package/dist/context-engine/privacy-filter.js.map +1 -0
- package/dist/context-engine/templates/base-en.d.ts +19 -0
- package/dist/context-engine/templates/base-en.d.ts.map +1 -0
- package/dist/context-engine/templates/base-en.js +22 -0
- package/dist/context-engine/templates/base-en.js.map +1 -0
- package/dist/context-engine/templates/composer.d.ts +55 -0
- package/dist/context-engine/templates/composer.d.ts.map +1 -0
- package/dist/context-engine/templates/composer.js +51 -0
- package/dist/context-engine/templates/composer.js.map +1 -0
- package/dist/context-engine/token-budget.d.ts +99 -0
- package/dist/context-engine/token-budget.d.ts.map +1 -0
- package/dist/context-engine/token-budget.js +211 -0
- package/dist/context-engine/token-budget.js.map +1 -0
- package/dist/context-engine/token-counter.d.ts +54 -0
- package/dist/context-engine/token-counter.d.ts.map +1 -0
- package/dist/context-engine/token-counter.js +81 -0
- package/dist/context-engine/token-counter.js.map +1 -0
- package/dist/errors/index.d.ts +171 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +206 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/facade.d.ts +278 -0
- package/dist/facade.d.ts.map +1 -0
- package/dist/facade.js +265 -0
- package/dist/facade.js.map +1 -0
- package/dist/graph/entity-resolver.d.ts +135 -0
- package/dist/graph/entity-resolver.d.ts.map +1 -0
- package/dist/graph/entity-resolver.js +202 -0
- package/dist/graph/entity-resolver.js.map +1 -0
- package/dist/graph/index.d.ts +1 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +95 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/contextualize.d.ts +14 -0
- package/dist/internal/contextualize.d.ts.map +1 -0
- package/dist/internal/contextualize.js +110 -0
- package/dist/internal/contextualize.js.map +1 -0
- package/dist/internal/embedder-binding.js +50 -0
- package/dist/internal/embedder-binding.js.map +1 -0
- package/dist/internal/id.js +24 -0
- package/dist/internal/id.js.map +1 -0
- package/dist/internal/injection-heuristics.js +64 -0
- package/dist/internal/injection-heuristics.js.map +1 -0
- package/dist/internal/spans.js +35 -0
- package/dist/internal/spans.js.map +1 -0
- package/dist/internal/storage-adapter.d.ts +742 -0
- package/dist/internal/storage-adapter.d.ts.map +1 -0
- package/dist/migration/embedder-migration.d.ts +124 -0
- package/dist/migration/embedder-migration.d.ts.map +1 -0
- package/dist/migration/embedder-migration.js +109 -0
- package/dist/migration/embedder-migration.js.map +1 -0
- package/dist/migration/index.d.ts +2 -0
- package/dist/migration/index.js +3 -0
- package/dist/search/explain.d.ts +74 -0
- package/dist/search/explain.d.ts.map +1 -0
- package/dist/search/explain.js +50 -0
- package/dist/search/explain.js.map +1 -0
- package/dist/search/index.d.ts +6 -0
- package/dist/search/index.js +6 -0
- package/dist/search/iterative.d.ts +223 -0
- package/dist/search/iterative.d.ts.map +1 -0
- package/dist/search/iterative.js +330 -0
- package/dist/search/iterative.js.map +1 -0
- package/dist/search/query-transform.d.ts +112 -0
- package/dist/search/query-transform.d.ts.map +1 -0
- package/dist/search/query-transform.js +184 -0
- package/dist/search/query-transform.js.map +1 -0
- package/dist/search/rrf.d.ts +95 -0
- package/dist/search/rrf.d.ts.map +1 -0
- package/dist/search/rrf.js +166 -0
- package/dist/search/rrf.js.map +1 -0
- package/dist/search/types.d.ts +50 -0
- package/dist/search/types.d.ts.map +1 -0
- package/dist/tiers/episodic-memory.d.ts +142 -0
- package/dist/tiers/episodic-memory.d.ts.map +1 -0
- package/dist/tiers/episodic-memory.js +254 -0
- package/dist/tiers/episodic-memory.js.map +1 -0
- package/dist/tiers/index.d.ts +8 -0
- package/dist/tiers/index.js +9 -0
- package/dist/tiers/insight-memory.d.ts +71 -0
- package/dist/tiers/insight-memory.d.ts.map +1 -0
- package/dist/tiers/insight-memory.js +133 -0
- package/dist/tiers/insight-memory.js.map +1 -0
- package/dist/tiers/procedural-memory.d.ts +147 -0
- package/dist/tiers/procedural-memory.d.ts.map +1 -0
- package/dist/tiers/procedural-memory.js +267 -0
- package/dist/tiers/procedural-memory.js.map +1 -0
- package/dist/tiers/semantic-memory.d.ts +461 -0
- package/dist/tiers/semantic-memory.d.ts.map +1 -0
- package/dist/tiers/semantic-memory.js +631 -0
- package/dist/tiers/semantic-memory.js.map +1 -0
- package/dist/tiers/session-memory.d.ts +117 -0
- package/dist/tiers/session-memory.d.ts.map +1 -0
- package/dist/tiers/session-memory.js +163 -0
- package/dist/tiers/session-memory.js.map +1 -0
- package/dist/tiers/shared-memory.d.ts +29 -0
- package/dist/tiers/shared-memory.d.ts.map +1 -0
- package/dist/tiers/shared-memory.js +58 -0
- package/dist/tiers/shared-memory.js.map +1 -0
- package/dist/tiers/working-memory.d.ts +129 -0
- package/dist/tiers/working-memory.d.ts.map +1 -0
- package/dist/tiers/working-memory.js +229 -0
- package/dist/tiers/working-memory.js.map +1 -0
- package/dist/tools/block-tools.d.ts +98 -0
- package/dist/tools/block-tools.d.ts.map +1 -0
- package/dist/tools/block-tools.js +114 -0
- package/dist/tools/block-tools.js.map +1 -0
- package/dist/tools/fact-tools.d.ts +294 -0
- package/dist/tools/fact-tools.d.ts.map +1 -0
- package/dist/tools/fact-tools.js +293 -0
- package/dist/tools/fact-tools.js.map +1 -0
- package/dist/tools/index.d.ts +35 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +36 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/recall-tools.d.ts +209 -0
- package/dist/tools/recall-tools.d.ts.map +1 -0
- package/dist/tools/recall-tools.js +170 -0
- package/dist/tools/recall-tools.js.map +1 -0
- package/dist/tools/types.d.ts +40 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/package.json +119 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
//#region src/consolidator/phases/induce.ts
|
|
2
|
+
/** Output-token ceiling for one induction call. */
|
|
3
|
+
const DEFAULT_INDUCTION_MAX_TOKENS = 512;
|
|
4
|
+
/** Hard cap on steps in an induced procedure (defends against runaway output). */
|
|
5
|
+
const MAX_PROCEDURE_STEPS = 50;
|
|
6
|
+
/** Upper bound on trajectory steps rendered into the induction prompt. */
|
|
7
|
+
const MAX_TRAJECTORY_STEPS_SHOWN = 60;
|
|
8
|
+
/** Matches `{snake_case}` / `{kebab-case}` variable placeholders in a step. */
|
|
9
|
+
const VARIABLE_PLACEHOLDER = /\{([a-zA-Z][\w-]*)\}/gu;
|
|
10
|
+
const INDUCTION_SYSTEM_PROMPT = [
|
|
11
|
+
"You are the workflow-induction step of a long-running agent memory. Given the goal and the",
|
|
12
|
+
"sequence of steps an agent took to SUCCEED at a task, distil a reusable procedure a future",
|
|
13
|
+
"agent could follow for the same kind of task. Abstract concrete values into {snake_case}",
|
|
14
|
+
"variables (e.g. \"dry cat food\" → {product}, \"Monday\" → {day}) so the procedure generalizes;",
|
|
15
|
+
"keep the steps short and imperative. Also state a few verifiable success criteria — concrete,",
|
|
16
|
+
"checkable conditions that indicate the task is done. Do NOT invent steps that did not occur.",
|
|
17
|
+
"Return strictly JSON: { \"title\": string, \"steps\": string[], \"variables\": string[],",
|
|
18
|
+
"\"successCriteria\": string[] }."
|
|
19
|
+
].join(" ");
|
|
20
|
+
/**
|
|
21
|
+
* Build the (pure) induction request. Renders the trajectory as numbered
|
|
22
|
+
* steps; caps the rendered steps to keep the prompt bounded.
|
|
23
|
+
*/
|
|
24
|
+
function buildInductionRequest(trajectory, options = {}) {
|
|
25
|
+
const stepLines = trajectory.steps.slice(0, MAX_TRAJECTORY_STEPS_SHOWN).map((step, i) => {
|
|
26
|
+
const head = step.tool !== void 0 && step.tool.length > 0 ? step.tool : "step";
|
|
27
|
+
return step.detail !== void 0 && step.detail.length > 0 ? `[${i + 1}] ${head}: ${step.detail}` : `[${i + 1}] ${head}`;
|
|
28
|
+
}).join("\n");
|
|
29
|
+
return {
|
|
30
|
+
messages: [{
|
|
31
|
+
role: "user",
|
|
32
|
+
content: [
|
|
33
|
+
`Goal: ${trajectory.goal.length > 0 ? trajectory.goal : "(unstated)"}`,
|
|
34
|
+
"",
|
|
35
|
+
"Trajectory (the steps the agent took to succeed):",
|
|
36
|
+
stepLines.length > 0 ? stepLines : "(none)",
|
|
37
|
+
"",
|
|
38
|
+
"Distil the reusable procedure."
|
|
39
|
+
].join("\n")
|
|
40
|
+
}],
|
|
41
|
+
systemMessage: INDUCTION_SYSTEM_PROMPT,
|
|
42
|
+
temperature: 0,
|
|
43
|
+
maxTokens: options.maxTokens ?? DEFAULT_INDUCTION_MAX_TOKENS,
|
|
44
|
+
...options.signal !== void 0 ? { signal: options.signal } : {},
|
|
45
|
+
outputType: { kind: "structured" }
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Parse the induction response into an {@link InducedProcedure}, tolerating
|
|
50
|
+
* chatty / fenced output. Returns `null` when absent / unparseable / it
|
|
51
|
+
* yields no steps. **Never throws on model output** — only normalizes.
|
|
52
|
+
*
|
|
53
|
+
* The returned `variables` are reconciled with the steps: every `{name}`
|
|
54
|
+
* placeholder actually present in the steps is guaranteed to appear, so the
|
|
55
|
+
* abstraction is grounded in the procedure rather than in a model-declared
|
|
56
|
+
* list that may drift from it.
|
|
57
|
+
*
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
function parseInducedProcedure(text) {
|
|
61
|
+
if (text === void 0 || text.length === 0) return null;
|
|
62
|
+
const parsed = tryParseObject(stripFence(text).trim());
|
|
63
|
+
if (!isRecord(parsed)) return null;
|
|
64
|
+
return normalizeInducedProcedure({
|
|
65
|
+
title: typeof parsed.title === "string" ? parsed.title : "",
|
|
66
|
+
steps: toStringArray(parsed.steps),
|
|
67
|
+
variables: toStringArray(parsed.variables),
|
|
68
|
+
successCriteria: toStringArray(parsed.successCriteria ?? parsed.success_criteria)
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Normalize a raw induced procedure: trim + drop empty entries, cap step
|
|
73
|
+
* count, and reconcile the variable list with the placeholders actually used
|
|
74
|
+
* in the steps. Returns `null` when no usable steps remain (nothing to
|
|
75
|
+
* induce).
|
|
76
|
+
*
|
|
77
|
+
* @internal
|
|
78
|
+
*/
|
|
79
|
+
function normalizeInducedProcedure(raw) {
|
|
80
|
+
const steps = raw.steps.map((s) => s.trim()).filter((s) => s.length > 0).slice(0, MAX_PROCEDURE_STEPS);
|
|
81
|
+
if (steps.length === 0) return null;
|
|
82
|
+
const fromSteps = extractPlaceholders(steps);
|
|
83
|
+
const declared = raw.variables.map((v) => normalizeVariableName(v)).filter((v) => v.length > 0);
|
|
84
|
+
const variables = dedupe([...fromSteps, ...declared]);
|
|
85
|
+
const successCriteria = dedupe(raw.successCriteria.map((c) => c.trim()).filter((c) => c.length > 0));
|
|
86
|
+
return {
|
|
87
|
+
title: raw.title.trim().length > 0 ? raw.title.trim() : steps[0] ?? "induced procedure",
|
|
88
|
+
steps,
|
|
89
|
+
variables,
|
|
90
|
+
successCriteria
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Resilient provider-backed inducer. A provider throw or unparseable output
|
|
95
|
+
* degrades to `null` (no procedure) — induction never breaks the write path.
|
|
96
|
+
*/
|
|
97
|
+
function createProviderWorkflowInducer(provider, options = {}) {
|
|
98
|
+
return { async induce(trajectory, callOptions = {}) {
|
|
99
|
+
const merged = {
|
|
100
|
+
maxTokens: callOptions.maxTokens ?? options.maxTokens ?? DEFAULT_INDUCTION_MAX_TOKENS,
|
|
101
|
+
...callOptions.signal !== void 0 ? { signal: callOptions.signal } : {}
|
|
102
|
+
};
|
|
103
|
+
try {
|
|
104
|
+
const res = await provider.generate(buildInductionRequest(trajectory, merged));
|
|
105
|
+
try {
|
|
106
|
+
options.onUsage?.({
|
|
107
|
+
promptTokens: res.usage.promptTokens,
|
|
108
|
+
completionTokens: res.usage.completionTokens
|
|
109
|
+
});
|
|
110
|
+
} catch {}
|
|
111
|
+
return parseInducedProcedure(res.text);
|
|
112
|
+
} catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
} };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Pure orchestrator: induce a procedure from a trajectory.
|
|
119
|
+
*
|
|
120
|
+
* **Gate — successful trajectories only** (AWM online mode): a failed /
|
|
121
|
+
* aborted run, or one with no steps, yields `null` without calling the
|
|
122
|
+
* inducer. Otherwise the inducer runs and the result is normalized.
|
|
123
|
+
*/
|
|
124
|
+
async function runWorkflowInduction(trajectory, inducer, options = {}) {
|
|
125
|
+
if (!trajectory.succeeded) return null;
|
|
126
|
+
if (trajectory.steps.length === 0) return null;
|
|
127
|
+
const induced = await inducer.induce(trajectory, options);
|
|
128
|
+
if (induced === null) return null;
|
|
129
|
+
return normalizeInducedProcedure({
|
|
130
|
+
title: induced.title,
|
|
131
|
+
steps: induced.steps,
|
|
132
|
+
variables: induced.variables,
|
|
133
|
+
successCriteria: induced.successCriteria
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Distil a {@link RunState} into the minimal {@link Trajectory} the inducer
|
|
138
|
+
* needs. Pure — consumes the agent's already-emitted run state, so capturing
|
|
139
|
+
* a trajectory + its success signal needs no agent-loop change. The success
|
|
140
|
+
* signal is `status === 'completed'`.
|
|
141
|
+
*/
|
|
142
|
+
function trajectoryFromRunState(run) {
|
|
143
|
+
const steps = [];
|
|
144
|
+
for (const step of run.steps) for (const tc of step.toolCalls) {
|
|
145
|
+
const detail = renderArgs(tc.call.args);
|
|
146
|
+
steps.push({
|
|
147
|
+
tool: tc.call.toolName,
|
|
148
|
+
...detail !== void 0 ? { detail } : {}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
goal: firstUserText(run.messages) ?? "",
|
|
153
|
+
steps,
|
|
154
|
+
succeeded: run.status === "completed"
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Self-verify a reuse against an induced procedure's success criteria. A
|
|
159
|
+
* criterion is met when any observed signal contains it (case-insensitive
|
|
160
|
+
* substring) — a deterministic, offline check the agent runtime can feed
|
|
161
|
+
* actual run observations into on reuse. With **no criteria**, the reuse
|
|
162
|
+
* cannot be self-verified, so `verified` is `false`.
|
|
163
|
+
*/
|
|
164
|
+
function checkSuccessCriteria(procedure, observed) {
|
|
165
|
+
const criteria = (procedure.successCriteria ?? []).map((c) => c.trim()).filter((c) => c.length > 0);
|
|
166
|
+
if (criteria.length === 0) return {
|
|
167
|
+
verified: false,
|
|
168
|
+
unmet: []
|
|
169
|
+
};
|
|
170
|
+
const haystack = observed.map((o) => o.toLowerCase());
|
|
171
|
+
const unmet = criteria.filter((c) => !haystack.some((o) => o.includes(c.toLowerCase())));
|
|
172
|
+
return {
|
|
173
|
+
verified: unmet.length === 0,
|
|
174
|
+
unmet
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function extractPlaceholders(steps) {
|
|
178
|
+
const out = [];
|
|
179
|
+
for (const step of steps) for (const match of step.matchAll(VARIABLE_PLACEHOLDER)) {
|
|
180
|
+
const name = match[1];
|
|
181
|
+
if (name !== void 0) out.push(name);
|
|
182
|
+
}
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
function normalizeVariableName(value) {
|
|
186
|
+
return (/^\{([a-zA-Z][\w-]*)\}$/u.exec(value.trim())?.[1] ?? value).trim();
|
|
187
|
+
}
|
|
188
|
+
function dedupe(values) {
|
|
189
|
+
const seen = /* @__PURE__ */ new Set();
|
|
190
|
+
const out = [];
|
|
191
|
+
for (const v of values) {
|
|
192
|
+
if (seen.has(v)) continue;
|
|
193
|
+
seen.add(v);
|
|
194
|
+
out.push(v);
|
|
195
|
+
}
|
|
196
|
+
return out;
|
|
197
|
+
}
|
|
198
|
+
function renderArgs(args) {
|
|
199
|
+
if (args === void 0 || args === null) return void 0;
|
|
200
|
+
if (typeof args === "string") return args.length > 0 ? truncate(args) : void 0;
|
|
201
|
+
try {
|
|
202
|
+
const json = JSON.stringify(args);
|
|
203
|
+
return json !== void 0 && json !== "{}" ? truncate(json) : void 0;
|
|
204
|
+
} catch {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function truncate(value, max = 200) {
|
|
209
|
+
return value.length > max ? `${value.slice(0, max)}…` : value;
|
|
210
|
+
}
|
|
211
|
+
function firstUserText(messages) {
|
|
212
|
+
for (const message of messages) {
|
|
213
|
+
if (message.role !== "user") continue;
|
|
214
|
+
const text = messageText(message);
|
|
215
|
+
if (text.length > 0) return text;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function messageText(message) {
|
|
219
|
+
const content = message.content;
|
|
220
|
+
if (typeof content === "string") return content.trim();
|
|
221
|
+
const parts = [];
|
|
222
|
+
for (const part of content) if (part.type === "text" && typeof part.text === "string") parts.push(part.text);
|
|
223
|
+
return parts.join(" ").trim();
|
|
224
|
+
}
|
|
225
|
+
function toStringArray(value) {
|
|
226
|
+
if (!Array.isArray(value)) return [];
|
|
227
|
+
return value.filter((v) => typeof v === "string");
|
|
228
|
+
}
|
|
229
|
+
function isRecord(value) {
|
|
230
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
231
|
+
}
|
|
232
|
+
function stripFence(text) {
|
|
233
|
+
return /^```[^\n]*\n([\s\S]*?)\n```/u.exec(text.trim())?.[1] ?? text;
|
|
234
|
+
}
|
|
235
|
+
function tryParseObject(candidate) {
|
|
236
|
+
if (candidate.length === 0) return void 0;
|
|
237
|
+
try {
|
|
238
|
+
return JSON.parse(candidate);
|
|
239
|
+
} catch {
|
|
240
|
+
const start = candidate.indexOf("{");
|
|
241
|
+
const end = candidate.lastIndexOf("}");
|
|
242
|
+
if (start < 0 || end < start) return void 0;
|
|
243
|
+
try {
|
|
244
|
+
return JSON.parse(candidate.slice(start, end + 1));
|
|
245
|
+
} catch {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
//#endregion
|
|
252
|
+
export { DEFAULT_INDUCTION_MAX_TOKENS, INDUCTION_SYSTEM_PROMPT, MAX_PROCEDURE_STEPS, MAX_TRAJECTORY_STEPS_SHOWN, buildInductionRequest, checkSuccessCriteria, createProviderWorkflowInducer, normalizeInducedProcedure, parseInducedProcedure, runWorkflowInduction, trajectoryFromRunState };
|
|
253
|
+
//# sourceMappingURL=induce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"induce.js","names":["merged: WorkflowInductionOptions","steps: TrajectoryStep[]","out: string[]","parts: string[]"],"sources":["../../../src/consolidator/phases/induce.ts"],"sourcesContent":["/**\n * Procedural memory extraction (P2-2) — AWM-style workflow induction. From\n * a **successful** agent trajectory, distil a reusable procedure: a goal, a\n * value-abstracted step sequence (`\"search for {product}\"`), the variable\n * names that abstraction introduced, and Voyager-style success criteria the\n * reuse can self-verify against. The induced procedure is stored in the\n * procedural tier **quarantined** + `provenance: 'induction'` (P1-4) —\n * procedures drive *actions*, so this is the highest-poisoning-risk write in\n * the system and must never bypass the quarantine gate.\n *\n * Provider-agnostic seam, mirroring the P2-3 / P2-4 retrieval seams: pure\n * request builder ({@link buildInductionRequest}) + tolerant pure parser\n * ({@link parseInducedProcedure}) + a resilient provider-backed inducer\n * ({@link createProviderWorkflowInducer}) + a pure orchestrator\n * ({@link runWorkflowInduction}) that does no I/O of its own. The module\n * imports only `@graphorin/core` types, so the default (no-inducer) path is\n * fully offline — induction only ever calls a model when a provider is wired\n * via `createMemory({ procedureInduction: { provider } })`.\n *\n * \"Capture the trajectory + success signal\" (the proposal's agent-side\n * dependency) needs **no agent change**: the agent already emits the full,\n * serializable {@link RunState} (`steps[]` + `messages[]` + `status`).\n * {@link trajectoryFromRunState} distils it into the minimal\n * {@link Trajectory} the inducer consumes — `succeeded = status ===\n * 'completed'` is the AWM online-mode success signal (no ground truth\n * needed).\n *\n * @packageDocumentation\n */\n\nimport type { Message, Provider, ProviderRequest, RunState } from '@graphorin/core';\n\n/** Output-token ceiling for one induction call. */\nexport const DEFAULT_INDUCTION_MAX_TOKENS = 512;\n\n/** Hard cap on steps in an induced procedure (defends against runaway output). */\nexport const MAX_PROCEDURE_STEPS = 50;\n\n/** Upper bound on trajectory steps rendered into the induction prompt. */\nexport const MAX_TRAJECTORY_STEPS_SHOWN = 60;\n\n/** Matches `{snake_case}` / `{kebab-case}` variable placeholders in a step. */\nconst VARIABLE_PLACEHOLDER = /\\{([a-zA-Z][\\w-]*)\\}/gu;\n\n/**\n * One distilled step of an agent trajectory — model-agnostic, so the\n * inducer never sees raw provider/tool wire formats.\n *\n * @stable\n */\nexport interface TrajectoryStep {\n /** Tool invoked at this step, when the step was a tool call. */\n readonly tool?: string;\n /** Concrete args / observation rendered to text (the value-abstraction source). */\n readonly detail?: string;\n}\n\n/**\n * The minimal trajectory an inducer needs: the goal, the ordered steps, and\n * whether the run succeeded. Induction fires on **success only** (AWM online\n * mode) — {@link runWorkflowInduction} returns `null` for a failed run.\n *\n * @stable\n */\nexport interface Trajectory {\n /** What the run set out to do (typically the first user message). */\n readonly goal: string;\n /** Ordered steps the agent took. */\n readonly steps: ReadonlyArray<TrajectoryStep>;\n /** Whether the run succeeded — the induction gate. */\n readonly succeeded: boolean;\n}\n\n/**\n * A reusable workflow distilled from a successful trajectory.\n *\n * @stable\n */\nexport interface InducedProcedure {\n /** Short imperative title / goal of the reusable workflow. */\n readonly title: string;\n /** Ordered, value-abstracted steps (`\"search for {product}\"`, …). */\n readonly steps: ReadonlyArray<string>;\n /** Variable names abstracted from concrete values (`\"product\"`, `\"day\"`). */\n readonly variables: ReadonlyArray<string>;\n /** Voyager-style verifiable success criteria for self-verification on reuse. */\n readonly successCriteria: ReadonlyArray<string>;\n}\n\n/** Per-call options for an induction request. */\nexport interface WorkflowInductionOptions {\n /** Output-token ceiling. Default {@link DEFAULT_INDUCTION_MAX_TOKENS}. */\n readonly maxTokens?: number;\n readonly signal?: AbortSignal;\n}\n\n/**\n * Provider-agnostic seam: turn one successful trajectory into a procedure.\n * Returns `null` when nothing inducible (degraded provider, empty output) —\n * never throws.\n *\n * @stable\n */\nexport interface WorkflowInducer {\n induce(\n trajectory: Trajectory,\n options?: WorkflowInductionOptions,\n ): Promise<InducedProcedure | null>;\n}\n\n/** Result of self-verifying a reuse against an induced procedure's criteria. */\nexport interface VerificationResult {\n /** True only when the procedure has criteria and every one is met. */\n readonly verified: boolean;\n /** Criteria not satisfied by the observed signals. */\n readonly unmet: ReadonlyArray<string>;\n}\n\nexport const INDUCTION_SYSTEM_PROMPT = [\n 'You are the workflow-induction step of a long-running agent memory. Given the goal and the',\n 'sequence of steps an agent took to SUCCEED at a task, distil a reusable procedure a future',\n 'agent could follow for the same kind of task. Abstract concrete values into {snake_case}',\n 'variables (e.g. \"dry cat food\" → {product}, \"Monday\" → {day}) so the procedure generalizes;',\n 'keep the steps short and imperative. Also state a few verifiable success criteria — concrete,',\n 'checkable conditions that indicate the task is done. Do NOT invent steps that did not occur.',\n 'Return strictly JSON: { \"title\": string, \"steps\": string[], \"variables\": string[],',\n '\"successCriteria\": string[] }.',\n].join(' ');\n\n/**\n * Build the (pure) induction request. Renders the trajectory as numbered\n * steps; caps the rendered steps to keep the prompt bounded.\n */\nexport function buildInductionRequest(\n trajectory: Trajectory,\n options: WorkflowInductionOptions = {},\n): ProviderRequest {\n const stepLines = trajectory.steps\n .slice(0, MAX_TRAJECTORY_STEPS_SHOWN)\n .map((step, i) => {\n const head = step.tool !== undefined && step.tool.length > 0 ? step.tool : 'step';\n return step.detail !== undefined && step.detail.length > 0\n ? `[${i + 1}] ${head}: ${step.detail}`\n : `[${i + 1}] ${head}`;\n })\n .join('\\n');\n const content = [\n `Goal: ${trajectory.goal.length > 0 ? trajectory.goal : '(unstated)'}`,\n '',\n 'Trajectory (the steps the agent took to succeed):',\n stepLines.length > 0 ? stepLines : '(none)',\n '',\n 'Distil the reusable procedure.',\n ].join('\\n');\n return {\n messages: [{ role: 'user', content }],\n systemMessage: INDUCTION_SYSTEM_PROMPT,\n temperature: 0,\n maxTokens: options.maxTokens ?? DEFAULT_INDUCTION_MAX_TOKENS,\n ...(options.signal !== undefined ? { signal: options.signal } : {}),\n outputType: { kind: 'structured' },\n };\n}\n\n/**\n * Parse the induction response into an {@link InducedProcedure}, tolerating\n * chatty / fenced output. Returns `null` when absent / unparseable / it\n * yields no steps. **Never throws on model output** — only normalizes.\n *\n * The returned `variables` are reconciled with the steps: every `{name}`\n * placeholder actually present in the steps is guaranteed to appear, so the\n * abstraction is grounded in the procedure rather than in a model-declared\n * list that may drift from it.\n *\n * @internal\n */\nexport function parseInducedProcedure(text: string | undefined): InducedProcedure | null {\n if (text === undefined || text.length === 0) return null;\n const parsed = tryParseObject(stripFence(text).trim());\n if (!isRecord(parsed)) return null;\n return normalizeInducedProcedure({\n title: typeof parsed.title === 'string' ? parsed.title : '',\n steps: toStringArray(parsed.steps),\n variables: toStringArray(parsed.variables),\n successCriteria: toStringArray(parsed.successCriteria ?? parsed.success_criteria),\n });\n}\n\n/**\n * Normalize a raw induced procedure: trim + drop empty entries, cap step\n * count, and reconcile the variable list with the placeholders actually used\n * in the steps. Returns `null` when no usable steps remain (nothing to\n * induce).\n *\n * @internal\n */\nexport function normalizeInducedProcedure(raw: {\n title: string;\n steps: ReadonlyArray<string>;\n variables: ReadonlyArray<string>;\n successCriteria: ReadonlyArray<string>;\n}): InducedProcedure | null {\n const steps = raw.steps\n .map((s) => s.trim())\n .filter((s) => s.length > 0)\n .slice(0, MAX_PROCEDURE_STEPS);\n if (steps.length === 0) return null;\n\n // Variables = placeholders actually in the steps (source of truth, keeps\n // the abstraction grounded), unioned with any extra model-declared names.\n const fromSteps = extractPlaceholders(steps);\n const declared = raw.variables.map((v) => normalizeVariableName(v)).filter((v) => v.length > 0);\n const variables = dedupe([...fromSteps, ...declared]);\n\n const successCriteria = dedupe(\n raw.successCriteria.map((c) => c.trim()).filter((c) => c.length > 0),\n );\n const title = raw.title.trim().length > 0 ? raw.title.trim() : (steps[0] ?? 'induced procedure');\n return { title, steps, variables, successCriteria };\n}\n\n/**\n * Resilient provider-backed inducer. A provider throw or unparseable output\n * degrades to `null` (no procedure) — induction never breaks the write path.\n */\nexport function createProviderWorkflowInducer(\n provider: Provider,\n options: {\n readonly maxTokens?: number;\n /**\n * Usage callback (MCON-15) — induction is the framework's highest\n * poisoning-risk LLM spend and previously flowed past every budget\n * envelope. `createMemory` wires this into the consolidator budget\n * when one is enabled; standalone callers can record it themselves.\n * Best-effort: a throwing callback never breaks induction.\n */\n readonly onUsage?: (usage: {\n readonly promptTokens: number;\n readonly completionTokens: number;\n }) => void;\n } = {},\n): WorkflowInducer {\n return {\n async induce(trajectory, callOptions = {}): Promise<InducedProcedure | null> {\n const merged: WorkflowInductionOptions = {\n maxTokens: callOptions.maxTokens ?? options.maxTokens ?? DEFAULT_INDUCTION_MAX_TOKENS,\n ...(callOptions.signal !== undefined ? { signal: callOptions.signal } : {}),\n };\n try {\n const res = await provider.generate(buildInductionRequest(trajectory, merged));\n try {\n options.onUsage?.({\n promptTokens: res.usage.promptTokens,\n completionTokens: res.usage.completionTokens,\n });\n } catch {\n // Accounting is advisory; never break the induction result.\n }\n return parseInducedProcedure(res.text);\n } catch {\n return null;\n }\n },\n };\n}\n\n/**\n * Pure orchestrator: induce a procedure from a trajectory.\n *\n * **Gate — successful trajectories only** (AWM online mode): a failed /\n * aborted run, or one with no steps, yields `null` without calling the\n * inducer. Otherwise the inducer runs and the result is normalized.\n */\nexport async function runWorkflowInduction(\n trajectory: Trajectory,\n inducer: WorkflowInducer,\n options: WorkflowInductionOptions = {},\n): Promise<InducedProcedure | null> {\n if (!trajectory.succeeded) return null;\n if (trajectory.steps.length === 0) return null;\n const induced = await inducer.induce(trajectory, options);\n if (induced === null) return null;\n return normalizeInducedProcedure({\n title: induced.title,\n steps: induced.steps,\n variables: induced.variables,\n successCriteria: induced.successCriteria,\n });\n}\n\n/**\n * Distil a {@link RunState} into the minimal {@link Trajectory} the inducer\n * needs. Pure — consumes the agent's already-emitted run state, so capturing\n * a trajectory + its success signal needs no agent-loop change. The success\n * signal is `status === 'completed'`.\n */\nexport function trajectoryFromRunState(run: RunState): Trajectory {\n const steps: TrajectoryStep[] = [];\n for (const step of run.steps) {\n for (const tc of step.toolCalls) {\n const detail = renderArgs(tc.call.args);\n steps.push({\n tool: tc.call.toolName,\n ...(detail !== undefined ? { detail } : {}),\n });\n }\n }\n return {\n goal: firstUserText(run.messages) ?? '',\n steps,\n succeeded: run.status === 'completed',\n };\n}\n\n/**\n * Self-verify a reuse against an induced procedure's success criteria. A\n * criterion is met when any observed signal contains it (case-insensitive\n * substring) — a deterministic, offline check the agent runtime can feed\n * actual run observations into on reuse. With **no criteria**, the reuse\n * cannot be self-verified, so `verified` is `false`.\n */\nexport function checkSuccessCriteria(\n procedure: { readonly successCriteria?: ReadonlyArray<string> },\n observed: ReadonlyArray<string>,\n): VerificationResult {\n const criteria = (procedure.successCriteria ?? [])\n .map((c) => c.trim())\n .filter((c) => c.length > 0);\n if (criteria.length === 0) return { verified: false, unmet: [] };\n const haystack = observed.map((o) => o.toLowerCase());\n const unmet = criteria.filter((c) => !haystack.some((o) => o.includes(c.toLowerCase())));\n return { verified: unmet.length === 0, unmet };\n}\n\n// ── helpers ──────────────────────────────────────────────────────────────\n\nfunction extractPlaceholders(steps: ReadonlyArray<string>): string[] {\n const out: string[] = [];\n for (const step of steps) {\n for (const match of step.matchAll(VARIABLE_PLACEHOLDER)) {\n const name = match[1];\n if (name !== undefined) out.push(name);\n }\n }\n return out;\n}\n\nfunction normalizeVariableName(value: string): string {\n // Accept either a bare name or a `{name}` placeholder.\n const inner = /^\\{([a-zA-Z][\\w-]*)\\}$/u.exec(value.trim());\n return (inner?.[1] ?? value).trim();\n}\n\nfunction dedupe(values: ReadonlyArray<string>): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const v of values) {\n if (seen.has(v)) continue;\n seen.add(v);\n out.push(v);\n }\n return out;\n}\n\nfunction renderArgs(args: unknown): string | undefined {\n if (args === undefined || args === null) return undefined;\n if (typeof args === 'string') return args.length > 0 ? truncate(args) : undefined;\n try {\n const json = JSON.stringify(args);\n return json !== undefined && json !== '{}' ? truncate(json) : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction truncate(value: string, max = 200): string {\n return value.length > max ? `${value.slice(0, max)}…` : value;\n}\n\nfunction firstUserText(messages: ReadonlyArray<Message>): string | undefined {\n for (const message of messages) {\n if (message.role !== 'user') continue;\n const text = messageText(message);\n if (text.length > 0) return text;\n }\n return undefined;\n}\n\nfunction messageText(message: Message): string {\n const content = message.content;\n if (typeof content === 'string') return content.trim();\n const parts: string[] = [];\n for (const part of content) {\n if (part.type === 'text' && typeof part.text === 'string') parts.push(part.text);\n }\n return parts.join(' ').trim();\n}\n\nfunction toStringArray(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.filter((v): v is string => typeof v === 'string');\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction stripFence(text: string): string {\n const match = /^```[^\\n]*\\n([\\s\\S]*?)\\n```/u.exec(text.trim());\n return match?.[1] ?? text;\n}\n\nfunction tryParseObject(candidate: string): unknown {\n if (candidate.length === 0) return undefined;\n try {\n return JSON.parse(candidate);\n } catch {\n const start = candidate.indexOf('{');\n const end = candidate.lastIndexOf('}');\n if (start < 0 || end < start) return undefined;\n try {\n return JSON.parse(candidate.slice(start, end + 1));\n } catch {\n return undefined;\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { withMemorySpan } from "../../internal/spans.js";
|
|
2
|
+
import { salience, selectForCapacityEviction } from "../decay.js";
|
|
3
|
+
|
|
4
|
+
//#region src/consolidator/phases/light.ts
|
|
5
|
+
/**
|
|
6
|
+
* Run the light phase against the supplied scope.
|
|
7
|
+
*
|
|
8
|
+
* @stable
|
|
9
|
+
*/
|
|
10
|
+
async function runLightPhase(deps) {
|
|
11
|
+
return withMemorySpan(deps.tracer, "memory.consolidate.light", deps.scope, {
|
|
12
|
+
"consolidator.phase": "light",
|
|
13
|
+
"consolidator.tier": deps.tier ?? "free"
|
|
14
|
+
}, async (span) => {
|
|
15
|
+
const startedAt = deps.now();
|
|
16
|
+
let facts = 0;
|
|
17
|
+
let archived = 0;
|
|
18
|
+
let capacityEvicted = 0;
|
|
19
|
+
if (typeof deps.store.semantic.listForDecay === "function") {
|
|
20
|
+
const decayLimit = deps.decayCapacity !== null ? Math.max(deps.maxBatchSize, deps.decayCapacity + deps.maxBatchSize) : deps.maxBatchSize;
|
|
21
|
+
const rows = await deps.store.semantic.listForDecay(deps.scope, decayLimit);
|
|
22
|
+
facts = rows.length;
|
|
23
|
+
const now = deps.now();
|
|
24
|
+
const archiveFact = deps.store.semantic.archiveFact?.bind(deps.store.semantic);
|
|
25
|
+
const survivors = [];
|
|
26
|
+
for (const row of rows) {
|
|
27
|
+
if (row.archived) continue;
|
|
28
|
+
const score = salience({
|
|
29
|
+
now,
|
|
30
|
+
lastAccessedAt: row.lastAccessedAt,
|
|
31
|
+
createdAt: row.createdAt,
|
|
32
|
+
strength: row.strength,
|
|
33
|
+
tauDays: deps.decayTauDays,
|
|
34
|
+
importance: row.importance,
|
|
35
|
+
quarantined: row.status === "quarantined",
|
|
36
|
+
foreignProvenance: isForeignProvenance(row.provenance),
|
|
37
|
+
weights: deps.salienceWeights
|
|
38
|
+
});
|
|
39
|
+
if (score < deps.decayArchiveThreshold) {
|
|
40
|
+
if (archiveFact !== void 0) {
|
|
41
|
+
await archiveFact(row.id, "low_salience");
|
|
42
|
+
archived += 1;
|
|
43
|
+
}
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
survivors.push({
|
|
47
|
+
id: row.id,
|
|
48
|
+
salience: score
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (deps.decayCapacity !== null && archiveFact !== void 0) {
|
|
52
|
+
const evictIds = selectForCapacityEviction(survivors, deps.decayCapacity);
|
|
53
|
+
for (const id of evictIds) {
|
|
54
|
+
await archiveFact(id, "capacity_exceeded");
|
|
55
|
+
archived += 1;
|
|
56
|
+
capacityEvicted += 1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const noiseFilteredCount = 0;
|
|
61
|
+
span.setAttributes({
|
|
62
|
+
"consolidator.duration_ms": Math.max(0, deps.now() - startedAt),
|
|
63
|
+
"consolidator.facts_extracted": 0,
|
|
64
|
+
"consolidator.budget_used_usd": 0,
|
|
65
|
+
"consolidator.light.facts_seen": facts,
|
|
66
|
+
"consolidator.light.facts_archived": archived,
|
|
67
|
+
"consolidator.light.capacity_evicted": capacityEvicted,
|
|
68
|
+
"consolidator.light.noise_filtered": noiseFilteredCount
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
phase: "light",
|
|
72
|
+
status: "completed",
|
|
73
|
+
factsCreated: 0,
|
|
74
|
+
factsUpdated: archived,
|
|
75
|
+
conflictsResolved: 0,
|
|
76
|
+
episodesFormed: 0,
|
|
77
|
+
insightsCreated: 0,
|
|
78
|
+
noiseFilteredCount,
|
|
79
|
+
emptyExtractions: 0,
|
|
80
|
+
llmTokensUsed: 0,
|
|
81
|
+
llmCostUsd: null,
|
|
82
|
+
errorMessage: null
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* `true` for provenance that did not originate first-party (P1-4) — used
|
|
88
|
+
* to apply the mild salience penalty in capacity eviction. First-party
|
|
89
|
+
* is `null` (legacy / direct write), `'user'`, and `'extraction'` (the
|
|
90
|
+
* consolidator distilling the user's own session); `'tool'`,
|
|
91
|
+
* `'imported'`, and `'reflection'` are treated as foreign.
|
|
92
|
+
*/
|
|
93
|
+
function isForeignProvenance(provenance) {
|
|
94
|
+
return provenance !== null && provenance !== "user" && provenance !== "extraction";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
//#endregion
|
|
98
|
+
export { runLightPhase };
|
|
99
|
+
//# sourceMappingURL=light.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"light.js","names":["survivors: Array<{ id: string; salience: number }>"],"sources":["../../../src/consolidator/phases/light.ts"],"sourcesContent":["/**\n * Light phase — no LLM. Decays every fact's retention curve and\n * archives facts whose salience has fallen below the configured\n * threshold (plus the X-1 capacity pass). Noise filtering happens in\n * the standard phase where the batch is actually consumed — the old\n * advisory re-count here read the same unconsumed messages on every\n * pass for a counter nothing acted on (MCON-17).\n *\n * @packageDocumentation\n */\n\nimport type { SessionScope, Tracer } from '@graphorin/core';\nimport { withMemorySpan } from '../../internal/spans.js';\nimport type {\n ConsolidatorMemoryStoreExt,\n MemoryStoreAdapter,\n} from '../../internal/storage-adapter.js';\nimport { type SalienceWeights, salience, selectForCapacityEviction } from '../decay.js';\nimport type { NoiseFilterPreset } from '../noise-filter.js';\nimport type { PhaseOutcome } from '../types.js';\n\n/** Inputs accepted by {@link runLightPhase}. */\nexport interface LightPhaseDeps {\n readonly store: MemoryStoreAdapter;\n readonly consolidatorStore: ConsolidatorMemoryStoreExt | null;\n readonly tracer: Tracer;\n readonly scope: SessionScope;\n readonly now: () => number;\n readonly decayTauDays: number;\n readonly decayArchiveThreshold: number;\n /**\n * Capacity-bounded eviction (X-1). When non-null, the light phase\n * archives the lowest-salience live facts in the decay window down to\n * this many, in addition to the threshold archiving. `null` (the\n * default) leaves storage unbounded — behaviour identical to pre-X-1.\n */\n readonly decayCapacity: number | null;\n /** Weights for the multi-signal salience score (X-1). */\n readonly salienceWeights: SalienceWeights;\n readonly noiseFilters: ReadonlyArray<NoiseFilterPreset>;\n readonly maxBatchSize: number;\n readonly lastProcessedMessageId: string | null;\n /** The active consolidator tier — surfaced on the AISpan attribute. */\n readonly tier?: 'free' | 'cheap' | 'standard' | 'full' | 'custom';\n}\n\n/**\n * Run the light phase against the supplied scope.\n *\n * @stable\n */\nexport async function runLightPhase(deps: LightPhaseDeps): Promise<PhaseOutcome> {\n return withMemorySpan(\n deps.tracer,\n 'memory.consolidate.light',\n deps.scope,\n {\n 'consolidator.phase': 'light',\n 'consolidator.tier': deps.tier ?? 'free',\n },\n async (span) => {\n const startedAt = deps.now();\n let facts = 0;\n let archived = 0;\n let capacityEvicted = 0;\n if (typeof deps.store.semantic.listForDecay === 'function') {\n // When a capacity bound is set we widen the decay window past the\n // batch size so there is always headroom to trim the overflow; the\n // window is the LRU head (ordered by last-accessed ASC), so the\n // bound converges over repeated passes.\n const decayLimit =\n deps.decayCapacity !== null\n ? Math.max(deps.maxBatchSize, deps.decayCapacity + deps.maxBatchSize)\n : deps.maxBatchSize;\n const rows = await deps.store.semantic.listForDecay(deps.scope, decayLimit);\n facts = rows.length;\n const now = deps.now();\n // Bind once so the optional method stays narrowed across `await`\n // (and keeps its `this`); `undefined` ⇒ the store can't archive.\n const archiveFact = deps.store.semantic.archiveFact?.bind(deps.store.semantic);\n // Pass 1 — multi-signal threshold archiving. Salience folds the\n // Ebbinghaus retention curve together with the P1-2 importance hint\n // and the P1-4 security-risk negative term, so a stale, low-value,\n // or quarantined fact crosses the threshold sooner. Neutral inputs\n // collapse salience to plain retention (pre-X-1 behaviour).\n const survivors: Array<{ id: string; salience: number }> = [];\n for (const row of rows) {\n if (row.archived) continue;\n const score = salience({\n now,\n lastAccessedAt: row.lastAccessedAt,\n createdAt: row.createdAt,\n strength: row.strength,\n tauDays: deps.decayTauDays,\n importance: row.importance,\n quarantined: row.status === 'quarantined',\n foreignProvenance: isForeignProvenance(row.provenance),\n weights: deps.salienceWeights,\n });\n if (score < deps.decayArchiveThreshold) {\n if (archiveFact !== undefined) {\n await archiveFact(row.id, 'low_salience');\n archived += 1;\n }\n continue;\n }\n survivors.push({ id: row.id, salience: score });\n }\n // Pass 2 — capacity-bounded eviction. Archive the lowest-salience\n // survivors (security-flagged first) until the window fits.\n if (deps.decayCapacity !== null && archiveFact !== undefined) {\n const evictIds = selectForCapacityEviction(survivors, deps.decayCapacity);\n for (const id of evictIds) {\n await archiveFact(id, 'capacity_exceeded');\n archived += 1;\n capacityEvicted += 1;\n }\n }\n }\n\n // MCON-17: the light phase no longer re-reads the unconsumed batch\n // just to produce an advisory dropped-count — the standard phase\n // filters where extraction actually consumes the messages.\n const noiseFilteredCount = 0;\n\n span.setAttributes({\n 'consolidator.duration_ms': Math.max(0, deps.now() - startedAt),\n 'consolidator.facts_extracted': 0,\n 'consolidator.budget_used_usd': 0,\n 'consolidator.light.facts_seen': facts,\n 'consolidator.light.facts_archived': archived,\n 'consolidator.light.capacity_evicted': capacityEvicted,\n 'consolidator.light.noise_filtered': noiseFilteredCount,\n });\n\n return {\n phase: 'light',\n status: 'completed',\n factsCreated: 0,\n factsUpdated: archived,\n conflictsResolved: 0,\n episodesFormed: 0,\n insightsCreated: 0,\n noiseFilteredCount,\n emptyExtractions: 0,\n llmTokensUsed: 0,\n llmCostUsd: null,\n errorMessage: null,\n };\n },\n );\n}\n\n/**\n * `true` for provenance that did not originate first-party (P1-4) — used\n * to apply the mild salience penalty in capacity eviction. First-party\n * is `null` (legacy / direct write), `'user'`, and `'extraction'` (the\n * consolidator distilling the user's own session); `'tool'`,\n * `'imported'`, and `'reflection'` are treated as foreign.\n */\nfunction isForeignProvenance(provenance: string | null): boolean {\n return provenance !== null && provenance !== 'user' && provenance !== 'extraction';\n}\n"],"mappings":";;;;;;;;;AAmDA,eAAsB,cAAc,MAA6C;AAC/E,QAAO,eACL,KAAK,QACL,4BACA,KAAK,OACL;EACE,sBAAsB;EACtB,qBAAqB,KAAK,QAAQ;EACnC,EACD,OAAO,SAAS;EACd,MAAM,YAAY,KAAK,KAAK;EAC5B,IAAI,QAAQ;EACZ,IAAI,WAAW;EACf,IAAI,kBAAkB;AACtB,MAAI,OAAO,KAAK,MAAM,SAAS,iBAAiB,YAAY;GAK1D,MAAM,aACJ,KAAK,kBAAkB,OACnB,KAAK,IAAI,KAAK,cAAc,KAAK,gBAAgB,KAAK,aAAa,GACnE,KAAK;GACX,MAAM,OAAO,MAAM,KAAK,MAAM,SAAS,aAAa,KAAK,OAAO,WAAW;AAC3E,WAAQ,KAAK;GACb,MAAM,MAAM,KAAK,KAAK;GAGtB,MAAM,cAAc,KAAK,MAAM,SAAS,aAAa,KAAK,KAAK,MAAM,SAAS;GAM9E,MAAMA,YAAqD,EAAE;AAC7D,QAAK,MAAM,OAAO,MAAM;AACtB,QAAI,IAAI,SAAU;IAClB,MAAM,QAAQ,SAAS;KACrB;KACA,gBAAgB,IAAI;KACpB,WAAW,IAAI;KACf,UAAU,IAAI;KACd,SAAS,KAAK;KACd,YAAY,IAAI;KAChB,aAAa,IAAI,WAAW;KAC5B,mBAAmB,oBAAoB,IAAI,WAAW;KACtD,SAAS,KAAK;KACf,CAAC;AACF,QAAI,QAAQ,KAAK,uBAAuB;AACtC,SAAI,gBAAgB,QAAW;AAC7B,YAAM,YAAY,IAAI,IAAI,eAAe;AACzC,kBAAY;;AAEd;;AAEF,cAAU,KAAK;KAAE,IAAI,IAAI;KAAI,UAAU;KAAO,CAAC;;AAIjD,OAAI,KAAK,kBAAkB,QAAQ,gBAAgB,QAAW;IAC5D,MAAM,WAAW,0BAA0B,WAAW,KAAK,cAAc;AACzE,SAAK,MAAM,MAAM,UAAU;AACzB,WAAM,YAAY,IAAI,oBAAoB;AAC1C,iBAAY;AACZ,wBAAmB;;;;EAQzB,MAAM,qBAAqB;AAE3B,OAAK,cAAc;GACjB,4BAA4B,KAAK,IAAI,GAAG,KAAK,KAAK,GAAG,UAAU;GAC/D,gCAAgC;GAChC,gCAAgC;GAChC,iCAAiC;GACjC,qCAAqC;GACrC,uCAAuC;GACvC,qCAAqC;GACtC,CAAC;AAEF,SAAO;GACL,OAAO;GACP,QAAQ;GACR,cAAc;GACd,cAAc;GACd,mBAAmB;GACnB,gBAAgB;GAChB,iBAAiB;GACjB;GACA,kBAAkB;GAClB,eAAe;GACf,YAAY;GACZ,cAAc;GACf;GAEJ;;;;;;;;;AAUH,SAAS,oBAAoB,YAAoC;AAC/D,QAAO,eAAe,QAAQ,eAAe,UAAU,eAAe"}
|