@buildaharness/aielia 0.3.3 → 0.3.5
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 +63 -20
- package/dist/PlanGraphPane.d.ts +19 -0
- package/dist/_memory-core-generated.d.ts +1764 -0
- package/dist/agent-loop.d.ts +31 -8
- package/dist/ambiguity-guard-flag.d.ts +12 -0
- package/dist/ambiguity-guard.d.ts +20 -0
- package/dist/answer-claim.d.ts +13 -1
- package/dist/ask-clarification-service.d.ts +12 -1
- package/dist/ask-response-format.d.ts +6 -0
- package/dist/assistant-session.d.ts +16 -5
- package/dist/assistant-source.d.ts +7 -0
- package/dist/assistant-types.d.ts +25 -1
- package/dist/assistant.d.ts +166 -5
- package/dist/benchmark-injected-failure.d.ts +6 -0
- package/dist/bin-CjKpxDxl.js +4836 -0
- package/dist/bin-CjKpxDxl.js.map +1 -0
- package/dist/cli-session.d.ts +26 -1
- package/dist/cli.d.ts +20 -2
- package/dist/cli.js +1 -1
- package/dist/code-plan-templates.d.ts +21 -0
- package/dist/config.d.ts +51 -0
- package/dist/constraint-check.d.ts +35 -0
- package/dist/contradiction-checker.d.ts +7 -0
- package/dist/decomposed-answer.d.ts +13 -0
- package/dist/decomposition-classifier.d.ts +2 -0
- package/dist/doctor-checks.d.ts +3 -0
- package/dist/episodic-digest.d.ts +89 -0
- package/dist/fact-extraction.d.ts +39 -0
- package/dist/file-tools-mcp-server.mjs +138 -12
- package/dist/file-tools.d.ts +4 -0
- package/dist/first-run.d.ts +3 -2
- package/dist/goal-graph-flag.d.ts +44 -0
- package/dist/goal-graph-reconcile.d.ts +55 -0
- package/dist/goal-graph-service.d.ts +48 -0
- package/dist/goal-graph-store.d.ts +485 -0
- package/dist/goal-graph-suggest-flag.d.ts +34 -0
- package/dist/goal-identity-matcher.d.ts +21 -0
- package/dist/goal-thread-identity.d.ts +32 -0
- package/dist/goal-thread-scheduler.d.ts +78 -0
- package/dist/grounding-check.d.ts +33 -0
- package/dist/harness-bridge.d.ts +75 -2
- package/dist/harness-token-budget.d.ts +9 -0
- package/dist/index.d.ts +30 -1
- package/dist/index.js +166 -80
- package/dist/index.js.map +1 -1
- package/dist/layer-policy-flag.d.ts +13 -0
- package/dist/layer-policy-wiring.d.ts +77 -0
- package/dist/layer-settings.d.ts +49 -0
- package/dist/layer-telemetry.d.ts +2 -0
- package/dist/lexical/lexical-mode.d.ts +29 -0
- package/dist/live-steering-channel.d.ts +31 -0
- package/dist/memory-consolidation.d.ts +80 -0
- package/dist/memory-governance.d.ts +30 -0
- package/dist/memory-reviewer.d.ts +114 -0
- package/dist/memory-service.d.ts +348 -9
- package/dist/model-json.d.ts +17 -0
- package/dist/next-step-context.d.ts +51 -0
- package/dist/next-step-proposer.d.ts +53 -0
- package/dist/plan-approval-service.d.ts +1 -1
- package/dist/plan-drafting-service.d.ts +1 -1
- package/dist/plan-graph-flag.d.ts +20 -0
- package/dist/plan-question.d.ts +60 -0
- package/dist/plan-service.d.ts +6 -2
- package/dist/plan-step-prompt.d.ts +15 -0
- package/dist/plan-store.d.ts +7 -1
- package/dist/plan-viz/normalize.d.ts +14 -0
- package/dist/plan-viz/plan-detail-lines.d.ts +3 -0
- package/dist/plan-viz/plan-layout.d.ts +48 -0
- package/dist/plan-viz/plan-nav.d.ts +29 -0
- package/dist/plan-viz/plan-raster.d.ts +36 -0
- package/dist/plan-viz/plan-routes.d.ts +6 -0
- package/dist/plan-viz/plan-snapshot.d.ts +73 -0
- package/dist/plan-viz/plan-viewport.d.ts +29 -0
- package/dist/plan-viz/plan-width.d.ts +15 -0
- package/dist/plan-viz/testkit/display-width.d.ts +12 -0
- package/dist/plan-viz/testkit/generators.d.ts +61 -0
- package/dist/plan-viz/testkit/grid-oracles.d.ts +38 -0
- package/dist/plan-viz/testkit/nav-oracles.d.ts +79 -0
- package/dist/plan-viz/testkit/types.d.ts +8 -0
- package/dist/plan-viz/types.d.ts +34 -0
- package/dist/provider-setup-LFsaTkpf.js +16606 -0
- package/dist/provider-setup-LFsaTkpf.js.map +1 -0
- package/dist/provider-setup.d.ts +51 -0
- package/dist/recall-tool.d.ts +43 -0
- package/dist/recovery-note.d.ts +22 -0
- package/dist/response-service.d.ts +28 -2
- package/dist/review-checker.d.ts +10 -4
- package/dist/reviewer-revision.d.ts +23 -0
- package/dist/risk-classifier.d.ts +7 -0
- package/dist/scope-urgency-classifier.d.ts +29 -0
- package/dist/scripted-llm-client.d.ts +26 -1
- package/dist/semantic-compaction.d.ts +15 -0
- package/dist/semantic-hypotheses.d.ts +40 -0
- package/dist/source-reliability.d.ts +71 -0
- package/dist/supervisor-flag.d.ts +20 -0
- package/dist/task-completion-check.d.ts +24 -0
- package/dist/tool-control-plane.d.ts +33 -1
- package/dist/tool-effect-class.d.ts +9 -0
- package/dist/transcript-compaction.d.ts +9 -0
- package/dist/{tui-app-CFfOP2DC.js → tui-app-BLR-PRFp.js} +321 -7
- package/dist/tui-app-BLR-PRFp.js.map +1 -0
- package/dist/tui-app.d.ts +38 -0
- package/dist/turn-intent-classifier.d.ts +75 -16
- package/dist/turn-interpreter.d.ts +25 -3
- package/dist/version.d.ts +1 -1
- package/package.json +7 -7
- package/dist/bin-Btzhiyzi.js +0 -2108
- package/dist/bin-Btzhiyzi.js.map +0 -1
- package/dist/cli-session-DnSc6Hhx.js +0 -8343
- package/dist/cli-session-DnSc6Hhx.js.map +0 -1
- package/dist/tui-app-CFfOP2DC.js.map +0 -1
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { LayerPolicy, LayerPolicyMode, OptInLayer, PolicyBudget, PolicyRules, RunState, TurnSignals, TurnTier } from '@buildaharness/harness';
|
|
2
|
+
import { TurnIntentClassification } from './turn-intent-classifier.js';
|
|
3
|
+
/**
|
|
4
|
+
* The opt-in layers (default OFF, one `AUDIT_*` flag each) the operator has switched on. Each flag is still read
|
|
5
|
+
* where the layer runs; this only tells the policy which ones exist this turn so its record is true.
|
|
6
|
+
*/
|
|
7
|
+
export declare function enabledOptInLayers(env?: Record<string, string | undefined>): OptInLayer[];
|
|
8
|
+
/**
|
|
9
|
+
* Whether an opt-in layer runs this turn. Restrict-only (AL-1): the layer's own `AUDIT_*` flag must be on (`flagOn`) AND the
|
|
10
|
+
* plan must not have switched it `off` (`budget_exhausted`) — a plan can never turn a disabled layer on. Only an `adaptive`
|
|
11
|
+
* plan exists (`resolveOptInPlan`); with none, or a decision the plan lacks, or a plan that throws, the flag alone decides,
|
|
12
|
+
* i.e. today's behaviour.
|
|
13
|
+
*/
|
|
14
|
+
export declare function optInLayerEnabled(layer: OptInLayer, flagOn: boolean, plan: EscalationPlan | undefined): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* The plan the opt-in layers are gated by, resolved once per turn right after classification — before the tool-less draft
|
|
17
|
+
* and the harness run, which is where the earliest opt-in call sits. Their decisions depend only on which are enabled and on
|
|
18
|
+
* the per-turn call budget (risk and posture), none of which needs the task graph, so the minimal signals here give the same
|
|
19
|
+
* answer the harness's own plan would. `undefined` unless `adaptive`: static and shadow execute today's behaviour.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveOptInPlan(mode: LayerPolicyMode, classification: TurnIntentClassification): EscalationPlan | undefined;
|
|
22
|
+
export declare const SEMANTIC_ESCALATIONS: readonly ["semantic_contradiction", "failure_match", "criterion_coverage", "change_review", "model_inferred_facts"];
|
|
23
|
+
export type SemanticEscalation = (typeof SEMANTIC_ESCALATIONS)[number];
|
|
24
|
+
export declare const ESCALATION_ENV: Readonly<Record<SemanticEscalation, string>>;
|
|
25
|
+
/** `true`/`false` when the operator set the env var explicitly; `undefined` when unset/empty. */
|
|
26
|
+
export declare function explicitEnvOverride(varName: string, env?: Record<string, string | undefined>): boolean | undefined;
|
|
27
|
+
export interface EscalationPlan {
|
|
28
|
+
mode: LayerPolicyMode;
|
|
29
|
+
tier: TurnTier;
|
|
30
|
+
/** The policy that executes (static under static/shadow). */
|
|
31
|
+
policy: LayerPolicy;
|
|
32
|
+
/** Under shadow only: what adaptive would have decided. Recorded, never acted on. */
|
|
33
|
+
shadow?: {
|
|
34
|
+
policy: LayerPolicy;
|
|
35
|
+
tier: TurnTier;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** AL9a: the per-turn LLM-call budget view for a turn's signals (risk- and posture-scaled). Static mode ignores it. */
|
|
39
|
+
export declare function turnPolicyBudget(signals: TurnSignals): PolicyBudget;
|
|
40
|
+
/** Resolves the executed policy for a turn. Never throws; any failure ⇒ today's static behaviour. */
|
|
41
|
+
export declare function resolveEscalationPlan(mode: LayerPolicyMode, signals: TurnSignals, state: RunState | undefined, rules?: PolicyRules, budget?: PolicyBudget): EscalationPlan;
|
|
42
|
+
/**
|
|
43
|
+
* Whether `layer`'s semantic (LLM) form runs this turn. `cheap` is a non-linguistic form, so for
|
|
44
|
+
* these layers only `full` runs the LLM hook. An explicit env override wins; a missing/broken plan
|
|
45
|
+
* keeps today's behaviour (ON).
|
|
46
|
+
*/
|
|
47
|
+
export declare function escalationEnabled(layer: SemanticEscalation, plan: EscalationPlan | undefined, env?: Record<string, string | undefined>): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Whether a semantic escalation hook is supplied to the harness at all: unless the operator's explicit `AUDIT_*` value switches
|
|
50
|
+
* it off. The plan no longer decides this — it is consulted per call (`HarnessRunOptions.hookEnabled`), so a hook the opening
|
|
51
|
+
* plan would have left off can still run once mid-turn evidence changes the plan. With a static plan the result is exactly
|
|
52
|
+
* what `escalationEnabled` gave (the plan says `full`, so only the env override mattered).
|
|
53
|
+
*/
|
|
54
|
+
export declare function escalationHookWired(layer: SemanticEscalation, env?: Record<string, string | undefined>): boolean;
|
|
55
|
+
/** The policy the harness runtime's ad hoc gates read: only an executed `adaptive` policy differs from today's. */
|
|
56
|
+
export declare function harnessGatePolicy(plan: EscalationPlan | undefined): LayerPolicy | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* Decomposition reframe (`reframeTaskDescriptionWithLLM`). Precedence: explicit `AUDIT_DECOMPOSITION`
|
|
59
|
+
* env > the policy's decision > today's ON. `cheap` has no LLM-free form ⇒ off (only `full` reframes).
|
|
60
|
+
*/
|
|
61
|
+
export declare function decompositionReframeEnabled(plan: EscalationPlan | undefined, env?: Record<string, string | undefined>): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* LLM injection detection on fetched content (AL-1 security rule). Skippable ONLY on a
|
|
64
|
+
* non-linguistic proof of safety: no untrusted content in context, or no tool-capable next step.
|
|
65
|
+
* Anything the policy says short of `full` without such a proof is overridden back to ON — the
|
|
66
|
+
* check never trusts a message/LLM-derived signal, and the wiring re-verifies what
|
|
67
|
+
* `resolveLayerPolicy` already clamps so a hand-built plan cannot bypass it.
|
|
68
|
+
*/
|
|
69
|
+
export declare function injectionDetectionEnabled(plan: EscalationPlan | undefined, proof: {
|
|
70
|
+
untrustedContentInContext: boolean;
|
|
71
|
+
toolCapableNextStep: boolean;
|
|
72
|
+
}): boolean;
|
|
73
|
+
/** One-line reason a layer's decision is worth surfacing (escalated / skipped-with-reason), or `undefined` when routine. */
|
|
74
|
+
export declare function decisionNote(event: {
|
|
75
|
+
decision?: string;
|
|
76
|
+
trigger?: string;
|
|
77
|
+
}): string | undefined;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The user-facing on/off surface for reasoning layers, shared by the CLI (`/layers`) and the desktop/browser
|
|
3
|
+
* Settings screen. It is a thin layer over the existing `AUDIT_*` env flags, which every layer already reads in
|
|
4
|
+
* exactly one place: `applyLayerSettings` writes the persisted `config.layers` choices into an env object, so no
|
|
5
|
+
* layer needed a second code path. An env value the operator set themselves always wins and renders as pinned,
|
|
6
|
+
* the same precedence `explicitEnvOverride` already gives it.
|
|
7
|
+
*
|
|
8
|
+
* Floor layers (safety, approval, tool policy, verification, diagnostics) are listed but locked: there is no
|
|
9
|
+
* flag for them and the adaptive policy's types make them un-decidable on purpose.
|
|
10
|
+
*/
|
|
11
|
+
export type LayerGroup = 'escalation' | 'opt_in' | 'floor';
|
|
12
|
+
export interface LayerSetting {
|
|
13
|
+
id: string;
|
|
14
|
+
group: LayerGroup;
|
|
15
|
+
/** The env flag that carries the choice; absent for locked layers. */
|
|
16
|
+
flag?: string;
|
|
17
|
+
/** What the layer does when the user hasn't chosen. */
|
|
18
|
+
defaultOn: boolean;
|
|
19
|
+
summary: string;
|
|
20
|
+
/** Rough extra cost when it fires, in plain words. */
|
|
21
|
+
cost: string;
|
|
22
|
+
/** What the audit found, in plain words — the record, not a promise. */
|
|
23
|
+
evidence: string;
|
|
24
|
+
}
|
|
25
|
+
export declare const LAYER_SETTINGS: readonly LayerSetting[];
|
|
26
|
+
export type LayerChoices = Readonly<Record<string, boolean>>;
|
|
27
|
+
export declare function findLayer(id: string): LayerSetting | undefined;
|
|
28
|
+
/** The layers a user may change: everything except the locked floor. */
|
|
29
|
+
export declare function isToggleable(layer: LayerSetting): boolean;
|
|
30
|
+
type Env = Record<string, string | undefined>;
|
|
31
|
+
export interface AppliedLayers {
|
|
32
|
+
/** Layer ids whose env flag was set outside this module (shell env / build var), so the saved choice is ignored. */
|
|
33
|
+
pinned: Set<string>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Makes `env` reflect `choices`. Idempotent: a layer no longer in `choices` is restored to unset, and a flag that
|
|
37
|
+
* holds a value this module did not write is left alone and reported as pinned. Unknown ids and locked layers are ignored.
|
|
38
|
+
*/
|
|
39
|
+
export declare function applyLayerSettings(choices: LayerChoices | undefined, env: Env): AppliedLayers;
|
|
40
|
+
/** What a layer will do right now: the saved choice, else its default; a pinned layer reports its env value. */
|
|
41
|
+
export declare function effectiveState(layer: LayerSetting, choices: LayerChoices | undefined, pinned: ReadonlySet<string>, env: Env): boolean;
|
|
42
|
+
/** Returns the choices after setting one layer; throws on an unknown or locked id so a typo never silently persists. */
|
|
43
|
+
export declare function withLayerChoice(choices: LayerChoices | undefined, id: string, on: boolean | undefined): Record<string, boolean>;
|
|
44
|
+
export declare class LayerSettingError extends Error {
|
|
45
|
+
}
|
|
46
|
+
/** Drops anything in a persisted value that is not a known toggleable layer with a boolean, so a stale or hand-edited file can't smuggle junk in. */
|
|
47
|
+
export declare function sanitizeLayerChoices(raw: unknown): Record<string, boolean>;
|
|
48
|
+
export declare function formatLayerListing(choices: LayerChoices | undefined, pinned: ReadonlySet<string>, env: Env): string;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare const LEXICAL_FAMILIES: readonly ["fact-markers", "coding-fact", "injection", "enumeration", "risk", "task-cancel", "plan-mode", "batch-list", "tool-yield", "template-keywords"];
|
|
2
|
+
export type LexicalFamily = (typeof LEXICAL_FAMILIES)[number];
|
|
3
|
+
/** The families `lexicalMode: 'disabled'` (the default) switches off: all of them. */
|
|
4
|
+
export declare const LEXICAL_CHECK_FAMILIES: readonly LexicalFamily[];
|
|
5
|
+
export type LexicalMode = 'enabled' | 'disabled';
|
|
6
|
+
export declare const DEFAULT_LEXICAL_MODE: LexicalMode;
|
|
7
|
+
type EnvLike = Record<string, string | undefined>;
|
|
8
|
+
export declare function isLexicalFamily(v: string): v is LexicalFamily;
|
|
9
|
+
/** Unset/empty ⇒ default silently; an unrecognized value ⇒ default with a warning. */
|
|
10
|
+
export declare function resolveLexicalMode(env?: EnvLike): LexicalMode;
|
|
11
|
+
/** Every family currently switched off: named in OFF, or not named in ON while the mode is disabled. */
|
|
12
|
+
export declare function resolveLexicalOff(env?: EnvLike): ReadonlySet<LexicalFamily>;
|
|
13
|
+
/** True only if this family is switched on. */
|
|
14
|
+
export declare function lexicalActive(family: LexicalFamily, env?: EnvLike): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* The off families as a comma list, for handing to an out-of-process helper (the MCP server). Always
|
|
17
|
+
* pass it, even empty: the helper reads an unset variable as "every family off" (the default) and an
|
|
18
|
+
* empty one as "none off".
|
|
19
|
+
*/
|
|
20
|
+
export declare function lexicalOffEnvValue(env?: EnvLike): string;
|
|
21
|
+
/**
|
|
22
|
+
* Hands this mode to the harness package's own lexical switch (`setHarnessLexicalMode`), so
|
|
23
|
+
* `ASSISTANT_LEXICAL_MODE=enabled` turns the harness's checks back on too. Works in a browser as well
|
|
24
|
+
* as in Node (it sets no environment variable), and an explicit HARNESS_LEXICAL_MODE /
|
|
25
|
+
* HARNESS_LEXICAL_ON / HARNESS_LEXICAL_OFF still wins inside the harness. Called at the start of every
|
|
26
|
+
* harness run.
|
|
27
|
+
*/
|
|
28
|
+
export declare function syncHarnessLexicalEnv(env?: EnvLike): void;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* R1 (mid-task steering) of plans/hierarchical_goal_tree_and_steering_plan.html — a message sent
|
|
3
|
+
* while a turn is already running is absorbed here instead of waiting for the CLI's
|
|
4
|
+
* `dispatchQueue` or chat-ui's disabled composer to free up (see Figure 1 of that plan). One
|
|
5
|
+
* instance per session, mirroring `OneShotAnswerChannel`'s per-session lifetime
|
|
6
|
+
* (ask-clarification-service.ts) but with a different shape: that channel resumes a *paused* run
|
|
7
|
+
* with exactly one answer, this one accumulates zero or more raw steering messages while a turn
|
|
8
|
+
* keeps running and hands them all back at once.
|
|
9
|
+
*
|
|
10
|
+
* Phase 3 only builds the queue and the producer-side routing (cli.ts's line-handler split,
|
|
11
|
+
* chat-ui's composer un-disabling) — `poll()` has no consumer yet. Phase 4 wires it into
|
|
12
|
+
* `checkCallerUpdates` via the Scope×Urgency classifier at the harness's own iteration boundary
|
|
13
|
+
* (`UpdateChannel.poll()` in check-caller-updates.ts), turning each drained message into a real
|
|
14
|
+
* `CallerUpdate`. This class is deliberately unaware of `GoalGraphRecord`/`GoalThread` — same
|
|
15
|
+
* "standalone, unused until the wiring phase" discipline `goal-identity-matcher.ts` (Phase 2)
|
|
16
|
+
* already used.
|
|
17
|
+
*/
|
|
18
|
+
export interface SteeringEvent {
|
|
19
|
+
message: string;
|
|
20
|
+
enqueuedAt: number;
|
|
21
|
+
}
|
|
22
|
+
export declare class LiveSteeringChannel {
|
|
23
|
+
private queue;
|
|
24
|
+
enqueue(message: string): void;
|
|
25
|
+
/**
|
|
26
|
+
* Drains every event queued since the last poll, oldest first (FIFO) — never partial, never
|
|
27
|
+
* re-delivered: a second call with nothing newly enqueued returns an empty array.
|
|
28
|
+
*/
|
|
29
|
+
poll(): SteeringEvent[];
|
|
30
|
+
get pendingCount(): number;
|
|
31
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { ILLMClient, TokenUsage } from '@buildaharness/runtime';
|
|
2
|
+
import { UserFact } from './fact-extraction.js';
|
|
3
|
+
/**
|
|
4
|
+
* Memory consolidation and staged forgetting (M5 of the agent memory framework plan).
|
|
5
|
+
*
|
|
6
|
+
* Pure pieces only: the flag, the retention window, the bounded model pass that PROPOSES changes, and
|
|
7
|
+
* the deterministic archive-candidate rule. Applying a proposal lives in MemoryService (the only
|
|
8
|
+
* place allowed to write the durable store, through `commitDurable`). Nothing here writes anything.
|
|
9
|
+
*
|
|
10
|
+
* D2: no lexical similarity anywhere. Whether two facts overlap is the model's judgement; the only
|
|
11
|
+
* deterministic rule is on the usage fields M1 added (timestamps and counters), never on text.
|
|
12
|
+
*/
|
|
13
|
+
/** `AUDIT_MEMORY_CONSOLIDATION`: default OFF; `1/true/on/yes/enabled` enables. Read fresh each call. */
|
|
14
|
+
export declare function memoryConsolidationEnabled(env?: Record<string, string | undefined>): boolean;
|
|
15
|
+
/** D4: retention window, start 90 days, to be calibrated. `AUDIT_MEMORY_RETENTION_DAYS` overrides (a positive number). */
|
|
16
|
+
export declare const DEFAULT_MEMORY_RETENTION_DAYS = 90;
|
|
17
|
+
export declare function memoryRetentionDays(env?: Record<string, string | undefined>): number;
|
|
18
|
+
export type ProposalKind = 'merge' | 'supersede' | 'tighten' | 'archive';
|
|
19
|
+
/** A staged change waiting for the user (`/memory consolidate accept|dismiss <n>`). Nothing here has been applied. */
|
|
20
|
+
export interface ConsolidationProposal {
|
|
21
|
+
id: string;
|
|
22
|
+
kind: ProposalKind;
|
|
23
|
+
/** `text|extractedAt` ids of the durable facts the proposal touches. */
|
|
24
|
+
factIds: string[];
|
|
25
|
+
/** merge/tighten: the replacement wording. */
|
|
26
|
+
text?: string;
|
|
27
|
+
/** supersede: the id of the fact that replaces the retired ones in meaning (informational). */
|
|
28
|
+
by?: string;
|
|
29
|
+
reason: string;
|
|
30
|
+
createdAt: string;
|
|
31
|
+
/** True when any touched fact is `user_asserted`: the user said it themselves, so the UI must say so. */
|
|
32
|
+
touchesUserAsserted: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface ConsolidationState {
|
|
35
|
+
/** Highest audit seq already considered. The audit log's rotation keeps everything newer than this. */
|
|
36
|
+
lastSeq: number;
|
|
37
|
+
at?: string;
|
|
38
|
+
nextProposalNo?: number;
|
|
39
|
+
/** Signatures of proposals the user dismissed, so the same proposal is not raised again. */
|
|
40
|
+
dismissed?: string[];
|
|
41
|
+
}
|
|
42
|
+
export interface RawProposal {
|
|
43
|
+
kind: 'merge' | 'supersede' | 'tighten';
|
|
44
|
+
refs: string[];
|
|
45
|
+
text?: string;
|
|
46
|
+
by?: string;
|
|
47
|
+
reason: string;
|
|
48
|
+
}
|
|
49
|
+
export interface ConsolidationInput {
|
|
50
|
+
facts: Array<{
|
|
51
|
+
ref: string;
|
|
52
|
+
text: string;
|
|
53
|
+
source: string;
|
|
54
|
+
key?: string;
|
|
55
|
+
injectedCount?: number;
|
|
56
|
+
lastInjectedAt?: string;
|
|
57
|
+
}>;
|
|
58
|
+
recentChanges: Array<{
|
|
59
|
+
op: string;
|
|
60
|
+
text: string;
|
|
61
|
+
}>;
|
|
62
|
+
budget: {
|
|
63
|
+
usedChars: number;
|
|
64
|
+
budgetChars: number;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export declare const CONSOLIDATION_SYSTEM_PROMPT: string;
|
|
68
|
+
/**
|
|
69
|
+
* One bounded call proposing merges, supersessions and tightenings. Returns the validated proposals,
|
|
70
|
+
* or `undefined` when the call failed or its answer was unusable (the caller then changes nothing,
|
|
71
|
+
* not even the watermark, so the next run retries). Anything the model returns that does not name
|
|
72
|
+
* real refs, repeats a ref, or has the wrong shape for its kind is dropped individually.
|
|
73
|
+
*/
|
|
74
|
+
export declare function proposeConsolidationOps(input: ConsolidationInput, llmClient: ILLMClient, model?: string, onUsage?: (usage: TokenUsage) => void): Promise<RawProposal[] | undefined>;
|
|
75
|
+
/**
|
|
76
|
+
* Deterministic archive rule over M1's usage fields, never over text: a live fact whose last sign of
|
|
77
|
+
* use (`lastInjectedAt`, else when it was stated) is older than the retention window. "Injected" means
|
|
78
|
+
* "was in the prompt", which is weaker than "was used"; the proposal says so rather than claiming usage.
|
|
79
|
+
*/
|
|
80
|
+
export declare function findArchiveCandidates(facts: UserFact[], nowMs: number, windowDays: number): UserFact[];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { UserFact } from './fact-extraction.js';
|
|
2
|
+
/**
|
|
3
|
+
* M6 governance: WHERE a candidate memory write is allowed to land. Pure and structural: it reads
|
|
4
|
+
* the fact's own `source`/`confidence`/`durable` bits and the configured mode, never its wording
|
|
5
|
+
* (D2: no lexical checks). `MemoryService` is the only caller (`memoryWriteMode` is resolved in
|
|
6
|
+
* MemoryService only), so no writer can pick its own route.
|
|
7
|
+
*
|
|
8
|
+
* - `auto` model-inferred in-turn facts follow today's confidence policy; cross-turn writers
|
|
9
|
+
* (digest/reviewer/consolidation, M3/M4/M5) may add durable facts directly.
|
|
10
|
+
* - `staged` (default, D1) in-turn writer keeps today's policy; every cross-turn writer is
|
|
11
|
+
* staged in the pending queue. Nothing is auto-deleted.
|
|
12
|
+
* - `user_only` the model never creates a durable fact on its own: every model-inferred candidate
|
|
13
|
+
* that would have been durable waits in the pending queue for `/memory confirm`.
|
|
14
|
+
* Facts the user stated in their own words (`user_asserted`) are the user writing.
|
|
15
|
+
*/
|
|
16
|
+
export type MemoryWriteMode = 'auto' | 'staged' | 'user_only';
|
|
17
|
+
export declare const MEMORY_WRITE_MODES: readonly MemoryWriteMode[];
|
|
18
|
+
export declare const DEFAULT_MEMORY_WRITE_MODE: MemoryWriteMode;
|
|
19
|
+
/**
|
|
20
|
+
* Which writer produced the candidate. `in_turn` is the classifier/lexical pass that already
|
|
21
|
+
* exists. The other three are EXTENSION POINTS owned by later phases: M3 digests (`digest`), M4's
|
|
22
|
+
* post-turn reviewer (`reviewer`), M5 consolidation proposals (`consolidation`). They are routed
|
|
23
|
+
* by `MemoryService.submitCandidate()` and are not built here.
|
|
24
|
+
*/
|
|
25
|
+
export type MemoryWriter = 'in_turn' | 'digest' | 'reviewer' | 'consolidation';
|
|
26
|
+
export type CrossTurnWriter = Exclude<MemoryWriter, 'in_turn'>;
|
|
27
|
+
export type WriteRoute = 'durable' | 'pending' | 'session';
|
|
28
|
+
/** Unknown / absent values resolve to the default instead of throwing: a typo must never silently widen write authority. */
|
|
29
|
+
export declare function resolveMemoryWriteMode(raw: unknown): MemoryWriteMode;
|
|
30
|
+
export declare function resolveWriteRoute(mode: MemoryWriteMode, writer: MemoryWriter, fact: Pick<UserFact, 'source' | 'durable' | 'confidence'>): WriteRoute;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { ChatMessage, ILLMClient, TokenUsage } from '@buildaharness/runtime';
|
|
2
|
+
import { MemoryService, StageReviewerResult } from './memory-service.js';
|
|
3
|
+
import { UserFact, CandidateJudgement } from './fact-extraction.js';
|
|
4
|
+
import { FactCategory } from './turn-intent-classifier.js';
|
|
5
|
+
/**
|
|
6
|
+
* M4 of the agent memory framework plan: a post-turn, cross-turn memory reviewer. The in-turn writer already catches a
|
|
7
|
+
* fact stated in one message; the only thing this adds is aggregation across turns (a preference enforced by repetition,
|
|
8
|
+
* a decision that held). It reads the session's USER messages only (the injected memory block stripped), proposes typed
|
|
9
|
+
* operations, has each judged by a bounded semantic verifier, and hands the survivors to `MemoryService.stageReviewerOps`,
|
|
10
|
+
* which stages them in the pending queue with their evidence (decision D1). No keyword, regex or substring judgement of
|
|
11
|
+
* natural language anywhere here: language is judged by the two model calls, structure by plain code.
|
|
12
|
+
*
|
|
13
|
+
* Integration point for M3 (episodic digests): `ReviewerInput.digests` is accepted and passed to the reviewer as
|
|
14
|
+
* additional context but nothing populates it yet; M3's session-edge digest can feed it without changing this module.
|
|
15
|
+
*/
|
|
16
|
+
/** `AUDIT_MEMORY_REVIEWER`: default **OFF** (opt-in layer; M4 always ships off). `1`/`true`/`on`/`yes`/`enabled` enables. */
|
|
17
|
+
export declare function memoryReviewerEnabled(env?: Record<string, string | undefined>): boolean;
|
|
18
|
+
/** `AUDIT_MEMORY_REVIEWER_VERIFY`: the bounded verifier call. Default ON when the reviewer runs; a falsy value skips it (the negative control: ops are then staged unverified). */
|
|
19
|
+
export declare function memoryReviewerVerifyEnabled(env?: Record<string, string | undefined>): boolean;
|
|
20
|
+
/** `AUDIT_MEMORY_REVIEWER_EVERY`: review every N user turns. Default 5 (to be calibrated in the pilot). */
|
|
21
|
+
export declare const DEFAULT_REVIEW_EVERY = 5;
|
|
22
|
+
export declare function memoryReviewerEvery(env?: Record<string, string | undefined>): number;
|
|
23
|
+
/** Most operations one review may stage; a bound on the output, not a language judgement. */
|
|
24
|
+
export declare const MAX_REVIEW_OPS = 5;
|
|
25
|
+
export type ReviewScope = 'general' | 'this_context';
|
|
26
|
+
export type ReviewVerification = 'supported' | 'unsupported' | 'not_checked';
|
|
27
|
+
/** One typed operation the reviewer proposes. `evidence` is the user's own words; it is stored and shown, never string-matched. */
|
|
28
|
+
export interface ReviewOp {
|
|
29
|
+
kind: 'upsert' | 'retire' | 'noop';
|
|
30
|
+
/** upsert: stable model-chosen key for a single-valued attribute (M1 supersession). */
|
|
31
|
+
key?: string;
|
|
32
|
+
/** upsert: the claim, phrased to preserve what the user said ("asked for X in this context" unless they stated a general preference). */
|
|
33
|
+
text?: string;
|
|
34
|
+
/** retire: the `id` of the existing fact (from the list the reviewer was shown) to propose retiring. */
|
|
35
|
+
targetId?: number;
|
|
36
|
+
evidence?: string;
|
|
37
|
+
scope?: ReviewScope;
|
|
38
|
+
category?: FactCategory;
|
|
39
|
+
judgement?: CandidateJudgement;
|
|
40
|
+
}
|
|
41
|
+
export interface VerifiedReviewOp extends ReviewOp {
|
|
42
|
+
verification: ReviewVerification;
|
|
43
|
+
/** Verifier's one-line reason when it said unsupported. */
|
|
44
|
+
reason?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface ReviewerInput {
|
|
47
|
+
/** The session transcript; only `user` messages are read as claims. */
|
|
48
|
+
transcript: readonly ChatMessage[];
|
|
49
|
+
/** Block last injected into the prompt; removed verbatim from every user message (feedback-loop prevention). */
|
|
50
|
+
injectedBlock: string;
|
|
51
|
+
/** Current durable facts, shown to the reviewer so a retire can name its target. `id` is the index the reviewer uses. */
|
|
52
|
+
existingFacts: readonly {
|
|
53
|
+
id: number;
|
|
54
|
+
text: string;
|
|
55
|
+
source: string;
|
|
56
|
+
}[];
|
|
57
|
+
/** M3 integration point: episodic digests of earlier sessions, context only. Unused until M3 lands. */
|
|
58
|
+
digests?: readonly string[];
|
|
59
|
+
}
|
|
60
|
+
/** The user-only digest the reviewer sees: recent turns verbatim, older turns clipped, memory block stripped, total bounded. Pure formatting, no language judgement. */
|
|
61
|
+
export declare function buildUserDigest(transcript: readonly ChatMessage[], injectedBlock: string): string[];
|
|
62
|
+
/** One bounded reviewer call. Returns no ops (never throws) on any error or unparseable output: a failed review writes nothing. */
|
|
63
|
+
export declare function proposeMemoryOps(input: ReviewerInput, llmClient: ILLMClient, model?: string, onUsage?: (usage: TokenUsage) => void): Promise<ReviewOp[]>;
|
|
64
|
+
/**
|
|
65
|
+
* One bounded verifier call (same shape as grounding-check.ts, fail-open): is each op supported by the user's messages, at
|
|
66
|
+
* the stated scope, and not a generalisation of a one-off request. Fail-open: an error, an unparseable answer or a missing
|
|
67
|
+
* verdict leaves the op `not_checked` (it still only reaches the pending queue, never durable memory).
|
|
68
|
+
*/
|
|
69
|
+
export declare function verifyMemoryOps(ops: readonly ReviewOp[], input: ReviewerInput, llmClient: ILLMClient, model?: string, onUsage?: (usage: TokenUsage) => void): Promise<VerifiedReviewOp[]>;
|
|
70
|
+
/** The facts the reviewer must read through `UserFact` shape — re-exported so the staging method has one type to take. */
|
|
71
|
+
export type ReviewerFact = UserFact;
|
|
72
|
+
/**
|
|
73
|
+
* Trigger bookkeeping: a per-session counter of user turns since the last memory write (or review). Fires on the Nth
|
|
74
|
+
* turn; a turn in which any memory write occurred resets it (the in-turn writer is already doing the job). Pure state,
|
|
75
|
+
* no timers: it can only fire when `noteTurn` is called after a turn finished, so it never fires during a turn.
|
|
76
|
+
*/
|
|
77
|
+
export declare class ReviewTrigger {
|
|
78
|
+
private readonly every;
|
|
79
|
+
private counts;
|
|
80
|
+
constructor(every?: () => number);
|
|
81
|
+
noteTurn(sessionId: string, wroteMemoryThisTurn: boolean): boolean;
|
|
82
|
+
reset(sessionId: string): void;
|
|
83
|
+
count(sessionId: string): number;
|
|
84
|
+
}
|
|
85
|
+
export interface ReviewRunResult {
|
|
86
|
+
proposed: number;
|
|
87
|
+
stage: StageReviewerResult | undefined;
|
|
88
|
+
/** The run was aborted (a new turn began) before it wrote anything. */
|
|
89
|
+
aborted: boolean;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Orchestrates one review: reviewer call, verifier call, staging. Owns the trigger counter and the single in-flight run,
|
|
93
|
+
* which `abort()` cancels (checked between every await and again immediately before the writes, so an abort leaves no
|
|
94
|
+
* partial write). Never throws. LLM usage is reported through `onUsage`, which the host folds into the session's spend.
|
|
95
|
+
*/
|
|
96
|
+
export declare class MemoryReviewer {
|
|
97
|
+
private readonly memoryService;
|
|
98
|
+
private readonly llmClient;
|
|
99
|
+
private readonly model;
|
|
100
|
+
private readonly getTranscript;
|
|
101
|
+
readonly trigger: ReviewTrigger;
|
|
102
|
+
private controller;
|
|
103
|
+
private inFlight;
|
|
104
|
+
constructor(memoryService: MemoryService, llmClient: ILLMClient, model: () => string | undefined, getTranscript: (sessionId: string) => Promise<ChatMessage[]>);
|
|
105
|
+
/** Cancels the run in progress, if any (a new user turn began). */
|
|
106
|
+
abort(): void;
|
|
107
|
+
/** Resolves when the run in progress (if any) has finished; for hosts that must not exit mid-review and for tests. */
|
|
108
|
+
settled(): Promise<void>;
|
|
109
|
+
/** Starts a review off the caller's critical path and returns immediately. */
|
|
110
|
+
start(sessionId: string, onUsage?: (u: TokenUsage) => void): void;
|
|
111
|
+
/** Runs a review to completion (session edge). */
|
|
112
|
+
runNow(sessionId: string, onUsage?: (u: TokenUsage) => void): Promise<ReviewRunResult>;
|
|
113
|
+
private run;
|
|
114
|
+
}
|