@stackbilt/aegis-core 0.4.0 → 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/package.json +3 -2
- package/schema.sql +5 -0
- package/src/kernel/memory/index.ts +1 -1
- package/src/kernel/memory/procedural.ts +34 -0
- package/src/kernel/memory-service.ts +252 -0
- package/src/kernel/types.ts +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackbilt/aegis-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Persistent AI agent framework for Cloudflare Workers. Multi-tier memory, autonomous goals, dreaming cycles, MCP native.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"publishConfig": {
|
|
@@ -61,7 +61,8 @@
|
|
|
61
61
|
"./contracts/cc-task": "./src/contracts/cc-task.contract.ts",
|
|
62
62
|
"./contracts/memory-entry": "./src/contracts/memory-entry.contract.ts",
|
|
63
63
|
"./wiki/client": "./src/wiki/client.ts",
|
|
64
|
-
"./wiki/types": "./src/wiki/types.ts"
|
|
64
|
+
"./wiki/types": "./src/wiki/types.ts",
|
|
65
|
+
"./kernel/memory-service": "./src/kernel/memory-service.ts"
|
|
65
66
|
},
|
|
66
67
|
"scripts": {
|
|
67
68
|
"dev": "wrangler dev",
|
package/schema.sql
CHANGED
|
@@ -65,7 +65,9 @@ CREATE TABLE IF NOT EXISTS episodic_memory (
|
|
|
65
65
|
reclassified INTEGER NOT NULL DEFAULT 0,
|
|
66
66
|
thread_id TEXT, -- conversation thread for dreaming cycle
|
|
67
67
|
executor TEXT, -- which executor handled this
|
|
68
|
+
court_card TEXT, -- composite court card (king/queen/knight/page)
|
|
68
69
|
complexity_tier TEXT, -- aegis#563: procedureKey complement (low|mid|high); NULL for non-dispatcher producers
|
|
70
|
+
executor_config TEXT, -- aegis#563: config snapshot at emit time (evaluator-replay fidelity)
|
|
69
71
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
70
72
|
);
|
|
71
73
|
|
|
@@ -111,6 +113,8 @@ CREATE TABLE IF NOT EXISTS procedural_memory (
|
|
|
111
113
|
last_used TEXT,
|
|
112
114
|
candidate_executor TEXT, -- probation: untrusted executor being tested
|
|
113
115
|
candidate_successes INTEGER NOT NULL DEFAULT 0, -- consecutive successes of candidate
|
|
116
|
+
gap_signal_count INTEGER NOT NULL DEFAULT 0, -- aegis#497: unresolved grounding-gap signals; >0 triggers tier escalation
|
|
117
|
+
gap_last_seen TEXT,
|
|
114
118
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
115
119
|
);
|
|
116
120
|
|
|
@@ -204,6 +208,7 @@ CREATE INDEX IF NOT EXISTS idx_memory_validation_stage ON memory_entries(validat
|
|
|
204
208
|
CREATE INDEX IF NOT EXISTS idx_episodic_class ON episodic_memory(intent_class);
|
|
205
209
|
CREATE INDEX IF NOT EXISTS idx_episodic_created ON episodic_memory(created_at);
|
|
206
210
|
CREATE INDEX IF NOT EXISTS idx_episodic_thread ON episodic_memory(thread_id);
|
|
211
|
+
CREATE INDEX IF NOT EXISTS idx_episodic_class_complexity ON episodic_memory(intent_class, complexity_tier);
|
|
207
212
|
CREATE INDEX IF NOT EXISTS idx_procedural_pattern ON procedural_memory(task_pattern);
|
|
208
213
|
CREATE INDEX IF NOT EXISTS idx_procedural_status ON procedural_memory(status);
|
|
209
214
|
CREATE INDEX IF NOT EXISTS idx_heartbeat_created ON heartbeat_results(created_at);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { recordEpisode, retrogradeEpisode, sanitizeEpisodicOutcome, getRecentEpisodes, getEpisodeStats, getEpisodeStatsByComplexity, getAllEpisodeStatsByComplexity, type EpisodeStatsAggregate, getConversationHistory, estimateTokens, budgetConversationHistory } from './episodic.js';
|
|
2
|
-
export { PROCEDURE_MIN_SUCCESSES, PROCEDURE_MIN_SUCCESS_RATE, complexityTier, procedureKey, getProcedure, getAllProcedures, getProcedureWithDerivedStats, getAllProceduresWithDerivedStats, type DriftLogOpts, findNearMiss, upsertProcedure, addRefinement, degradeProcedure, maintainProcedures } from './procedural.js';
|
|
2
|
+
export { PROCEDURE_MIN_SUCCESSES, PROCEDURE_MIN_SUCCESS_RATE, complexityTier, procedureKey, getProcedure, getAllProcedures, getProcedureWithDerivedStats, getAllProceduresWithDerivedStats, type DriftLogOpts, findNearMiss, upsertProcedure, addRefinement, degradeProcedure, recordGapSignal, clearGapSignal, maintainProcedures } from './procedural.js';
|
|
3
3
|
export { normalizeTopic, tokenize, jaccardSimilarity, recordMemory, searchMemoryByKeywords, getMemoryEntries, recallMemory, computeEwaScore, getAllMemoryForContext } from './semantic.js';
|
|
4
4
|
export { pruneMemory } from './pruning.js';
|
|
5
5
|
export { consolidateEpisodicToSemantic } from './consolidation.js';
|
|
@@ -465,3 +465,37 @@ export async function maintainProcedures(db: D1Database): Promise<void> {
|
|
|
465
465
|
console.log(`[procedures] Utility-pruned: ${proc.task_pattern} (${(rate * 100).toFixed(0)}% success over ${proc.success_count + proc.fail_count} invocations)`);
|
|
466
466
|
}
|
|
467
467
|
}
|
|
468
|
+
|
|
469
|
+
/** Increment gap signal for a procedure. Called when the response carried
|
|
470
|
+
* unverified_claims[] or unknowns[] on a grounding-gated classification. */
|
|
471
|
+
export async function recordGapSignal(db: D1Database, taskPattern: string): Promise<void> {
|
|
472
|
+
const procedure = await getProcedure(db, taskPattern);
|
|
473
|
+
if (!procedure) return;
|
|
474
|
+
|
|
475
|
+
const newCount = (procedure.gap_signal_count ?? 0) + 1;
|
|
476
|
+
await db.prepare(
|
|
477
|
+
"UPDATE procedural_memory SET gap_signal_count = ?, gap_last_seen = datetime('now') WHERE task_pattern = ?"
|
|
478
|
+
).bind(newCount, taskPattern).run();
|
|
479
|
+
|
|
480
|
+
console.log(`[procedures] Gap signal recorded: ${taskPattern} (count=${newCount})`);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/** Decrement gap signal for a procedure (min 0). Called on clean grounded
|
|
484
|
+
* response. When the counter returns to 0, gap_last_seen is cleared. */
|
|
485
|
+
export async function clearGapSignal(db: D1Database, taskPattern: string): Promise<void> {
|
|
486
|
+
const procedure = await getProcedure(db, taskPattern);
|
|
487
|
+
if (!procedure) return;
|
|
488
|
+
|
|
489
|
+
const current = procedure.gap_signal_count ?? 0;
|
|
490
|
+
if (current <= 0) return;
|
|
491
|
+
|
|
492
|
+
const newCount = current - 1;
|
|
493
|
+
const newLastSeen = newCount === 0 ? null : procedure.gap_last_seen ?? null;
|
|
494
|
+
await db.prepare(
|
|
495
|
+
'UPDATE procedural_memory SET gap_signal_count = ?, gap_last_seen = ? WHERE task_pattern = ?'
|
|
496
|
+
).bind(newCount, newLastSeen, taskPattern).run();
|
|
497
|
+
|
|
498
|
+
if (newCount === 0) {
|
|
499
|
+
console.log(`[procedures] Gap signal cleared: ${taskPattern}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MemoryService — unified facade over the memory tiers (episodic,
|
|
3
|
+
* procedural, semantic, insights) + goals. Phase 1B of the god-object
|
|
4
|
+
* decomposition epic (aegis#532 / aegis#534).
|
|
5
|
+
*
|
|
6
|
+
* Why: the audit flagged that dispatch, self-improvement, ambient-capture
|
|
7
|
+
* each reach into memory/*.ts and memory-adapter.ts directly, repeating
|
|
8
|
+
* the `{db, memoryBinding}` threading and obscuring which tier each call
|
|
9
|
+
* actually hits. This service hands callers one object and hides the
|
|
10
|
+
* backing-store distinction.
|
|
11
|
+
*
|
|
12
|
+
* Tier → backing store:
|
|
13
|
+
* - episodic, procedural, insights, goals → D1 (env.db)
|
|
14
|
+
* - semantic, context-recall → memory-worker (env.memoryBinding) via
|
|
15
|
+
* memory-adapter (aegis-core re-export)
|
|
16
|
+
*
|
|
17
|
+
* Prefer this over the raw tier functions in new code. Existing callers
|
|
18
|
+
* migrate incrementally — Phase 2/3 decomps will naturally fold the rest
|
|
19
|
+
* as those files get broken up.
|
|
20
|
+
*/
|
|
21
|
+
import {
|
|
22
|
+
recordEpisode as _recordEpisode,
|
|
23
|
+
retrogradeEpisode as _retrogradeEpisode,
|
|
24
|
+
getRecentEpisodes as _getRecentEpisodes,
|
|
25
|
+
getEpisodeStatsByComplexity as _getEpisodeStatsByComplexity,
|
|
26
|
+
upsertProcedure as _upsertProcedure,
|
|
27
|
+
getProcedure as _getProcedure,
|
|
28
|
+
getProcedureWithDerivedStats as _getProcedureWithDerivedStats,
|
|
29
|
+
getAllProceduresWithDerivedStats as _getAllProceduresWithDerivedStats,
|
|
30
|
+
type DriftLogOpts,
|
|
31
|
+
addRefinement as _addRefinement,
|
|
32
|
+
degradeProcedure as _degradeProcedure,
|
|
33
|
+
findNearMiss as _findNearMiss,
|
|
34
|
+
recordGapSignal as _recordGapSignal,
|
|
35
|
+
clearGapSignal as _clearGapSignal,
|
|
36
|
+
publishInsight as _publishInsight,
|
|
37
|
+
validateInsight as _validateInsight,
|
|
38
|
+
recordGoalAction as _recordGoalAction,
|
|
39
|
+
type InsightPayload,
|
|
40
|
+
type ValidationStage,
|
|
41
|
+
type AgentAction,
|
|
42
|
+
} from './memory/index.js';
|
|
43
|
+
import {
|
|
44
|
+
recordMemory as _recordMemoryViaAdapter,
|
|
45
|
+
searchMemoryByKeywords as _searchMemoryByKeywordsViaAdapter,
|
|
46
|
+
getAllMemoryForContext as _getAllMemoryForContextViaAdapter,
|
|
47
|
+
} from './memory-adapter.js';
|
|
48
|
+
import type { MemoryServiceBinding } from '../types.js';
|
|
49
|
+
import type { EpisodicEntry, ProceduralEntry, Refinement } from './types.js';
|
|
50
|
+
|
|
51
|
+
export type { ValidationStage, InsightPayload };
|
|
52
|
+
|
|
53
|
+
export interface DispatchOutcomeArgs {
|
|
54
|
+
episode: Omit<EpisodicEntry, 'id' | 'created_at'>;
|
|
55
|
+
procedureKey: string;
|
|
56
|
+
executor: string;
|
|
57
|
+
executorConfig: string;
|
|
58
|
+
outcome: 'success' | 'failure' | 'partial_failure';
|
|
59
|
+
latencyMs: number;
|
|
60
|
+
cost: number;
|
|
61
|
+
/** Optional refinement log — added when a replan promotes a degraded procedure. */
|
|
62
|
+
refinement?: Refinement;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface GoalActionOpts {
|
|
66
|
+
autoExecuted?: boolean;
|
|
67
|
+
authorityLevel?: string;
|
|
68
|
+
toolCalled?: string;
|
|
69
|
+
toolArgsJson?: string;
|
|
70
|
+
toolResultJson?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export class MemoryService {
|
|
74
|
+
constructor(
|
|
75
|
+
private readonly db: D1Database,
|
|
76
|
+
private readonly memoryBinding?: MemoryServiceBinding,
|
|
77
|
+
) {}
|
|
78
|
+
|
|
79
|
+
// ─── Episodic ──────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
recordEpisode(entry: Omit<EpisodicEntry, 'id' | 'created_at'>): Promise<void> {
|
|
82
|
+
return _recordEpisode(this.db, entry);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
retrogradeEpisode(threadId: string): Promise<EpisodicEntry | null> {
|
|
86
|
+
return _retrogradeEpisode(this.db, threadId);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
getRecentEpisodes(intentClass: string, limit = 5): Promise<EpisodicEntry[]> {
|
|
90
|
+
return _getRecentEpisodes(this.db, intentClass, limit);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// aegis#563: aggregates keyed by (intent_class, complexity_tier), matching
|
|
94
|
+
// procedural_memory's procedureKey shape. Foundation for the #564
|
|
95
|
+
// projection-vs-cache decomposition.
|
|
96
|
+
getEpisodeStatsByComplexity(intentClass: string, complexityTier: string) {
|
|
97
|
+
return _getEpisodeStatsByComplexity(this.db, intentClass, complexityTier);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ─── Procedural ────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
upsertProcedure(
|
|
103
|
+
taskPattern: string,
|
|
104
|
+
executor: string,
|
|
105
|
+
executorConfig: string,
|
|
106
|
+
outcome: 'success' | 'failure' | 'partial_failure',
|
|
107
|
+
latencyMs: number,
|
|
108
|
+
cost: number,
|
|
109
|
+
): Promise<void> {
|
|
110
|
+
return _upsertProcedure(this.db, taskPattern, executor, executorConfig, outcome, latencyMs, cost);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
getProcedure(taskPattern: string): Promise<ProceduralEntry | null> {
|
|
114
|
+
return _getProcedure(this.db, taskPattern);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// aegis#564 Phase 1 — callers that read the five cached aggregate
|
|
118
|
+
// columns (success_count, fail_count, avg_latency_ms, avg_cost,
|
|
119
|
+
// last_used) should route through this helper. Phase 1 is a pass-
|
|
120
|
+
// through; Phase 2 adds shadow-read logging; Phase 3 flips to derived.
|
|
121
|
+
getProcedureWithDerivedStats(taskPattern: string, opts?: DriftLogOpts): Promise<ProceduralEntry | null> {
|
|
122
|
+
return _getProcedureWithDerivedStats(this.db, taskPattern, opts);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
getAllProceduresWithDerivedStats(opts?: DriftLogOpts): Promise<ProceduralEntry[]> {
|
|
126
|
+
return _getAllProceduresWithDerivedStats(this.db, opts);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
addRefinement(taskPattern: string, refinement: Refinement): Promise<void> {
|
|
130
|
+
return _addRefinement(this.db, taskPattern, refinement);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
degradeProcedure(taskPattern: string, executor: string): Promise<void> {
|
|
134
|
+
return _degradeProcedure(this.db, taskPattern, executor);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
findNearMiss(taskPattern: string) {
|
|
138
|
+
return _findNearMiss(this.db, taskPattern);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
recordGapSignal(taskPattern: string): Promise<void> {
|
|
142
|
+
return _recordGapSignal(this.db, taskPattern);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
clearGapSignal(taskPattern: string): Promise<void> {
|
|
146
|
+
return _clearGapSignal(this.db, taskPattern);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ─── Semantic (memory-worker via adapter) ──────────────────
|
|
150
|
+
|
|
151
|
+
async recordMemory(
|
|
152
|
+
topic: string,
|
|
153
|
+
fact: string,
|
|
154
|
+
confidence: number,
|
|
155
|
+
source: string,
|
|
156
|
+
metadata?: Record<string, unknown>,
|
|
157
|
+
) {
|
|
158
|
+
this.requireBinding('recordMemory');
|
|
159
|
+
return _recordMemoryViaAdapter(this.memoryBinding!, topic, fact, confidence, source, metadata);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async searchMemory(query: string, limit?: number) {
|
|
163
|
+
this.requireBinding('searchMemory');
|
|
164
|
+
return _searchMemoryByKeywordsViaAdapter(this.memoryBinding!, query, limit);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async getMemoryForContext(query?: string) {
|
|
168
|
+
this.requireBinding('getMemoryForContext');
|
|
169
|
+
return _getAllMemoryForContextViaAdapter(this.memoryBinding!, query);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ─── Insights ──────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
publishInsight(payload: InsightPayload) {
|
|
175
|
+
return _publishInsight(this.db, payload, this.memoryBinding);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
validateInsight(factHash: string, validatingRepo: string, confirmed: boolean) {
|
|
179
|
+
return _validateInsight(this.db, factHash, validatingRepo, confirmed);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ─── Goals ─────────────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
recordGoalAction(
|
|
185
|
+
goalId: string | null,
|
|
186
|
+
actionType: AgentAction['action_type'],
|
|
187
|
+
description: string,
|
|
188
|
+
outcome: AgentAction['outcome'] = 'success',
|
|
189
|
+
opts?: GoalActionOpts,
|
|
190
|
+
): Promise<void> {
|
|
191
|
+
return _recordGoalAction(this.db, goalId, actionType, description, outcome, opts);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ─── Domain: dispatch outcome bookkeeping ──────────────────
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Record a dispatch outcome: episode + procedural upsert, plus an
|
|
198
|
+
* optional refinement log when a replan happens on a degraded procedure.
|
|
199
|
+
* Collapses the three-call pattern that dispatch.recordOutcome carries
|
|
200
|
+
* inline.
|
|
201
|
+
*/
|
|
202
|
+
async recordDispatchOutcome(args: DispatchOutcomeArgs): Promise<void> {
|
|
203
|
+
// aegis#563: enrich episode with complexity_tier (derived from
|
|
204
|
+
// procedureKey = `classification:complexity_tier`) and executor_config
|
|
205
|
+
// snapshot. Caller-supplied values win; otherwise derive from args so
|
|
206
|
+
// dispatcher call sites don't need to pass these explicitly.
|
|
207
|
+
const tierFromKey = args.procedureKey.includes(':')
|
|
208
|
+
? args.procedureKey.slice(args.procedureKey.indexOf(':') + 1)
|
|
209
|
+
: null;
|
|
210
|
+
const enrichedEpisode: Omit<EpisodicEntry, 'id' | 'created_at'> = {
|
|
211
|
+
...args.episode,
|
|
212
|
+
complexity_tier: args.episode.complexity_tier ?? tierFromKey,
|
|
213
|
+
executor_config: args.episode.executor_config ?? args.executorConfig,
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
await _recordEpisode(this.db, enrichedEpisode);
|
|
217
|
+
await _upsertProcedure(
|
|
218
|
+
this.db,
|
|
219
|
+
args.procedureKey,
|
|
220
|
+
args.executor,
|
|
221
|
+
args.executorConfig,
|
|
222
|
+
args.outcome,
|
|
223
|
+
args.latencyMs,
|
|
224
|
+
args.cost,
|
|
225
|
+
);
|
|
226
|
+
if (args.refinement) {
|
|
227
|
+
await _addRefinement(this.db, args.procedureKey, args.refinement);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ─── Internal ──────────────────────────────────────────────
|
|
232
|
+
|
|
233
|
+
private requireBinding(method: string): void {
|
|
234
|
+
if (!this.memoryBinding) {
|
|
235
|
+
throw new Error(
|
|
236
|
+
`[memory-service] ${method}() requires memoryBinding — constructor received none`,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Factory: construct a MemoryService from any env-shape carrying
|
|
244
|
+
* `db` and (optionally) `memoryBinding`. The daemon's EdgeEnv satisfies
|
|
245
|
+
* this shape; scheduled tasks and tests can pass a narrower object.
|
|
246
|
+
*/
|
|
247
|
+
export function memoryServiceFor(env: {
|
|
248
|
+
db: D1Database;
|
|
249
|
+
memoryBinding?: MemoryServiceBinding;
|
|
250
|
+
}): MemoryService {
|
|
251
|
+
return new MemoryService(env.db, env.memoryBinding);
|
|
252
|
+
}
|
package/src/kernel/types.ts
CHANGED
|
@@ -38,6 +38,9 @@ export interface EpisodicEntry {
|
|
|
38
38
|
reclassified?: boolean;
|
|
39
39
|
thread_id?: string | null;
|
|
40
40
|
executor?: string | null;
|
|
41
|
+
court_card?: string | null;
|
|
42
|
+
complexity_tier?: string | null;
|
|
43
|
+
executor_config?: string | null;
|
|
41
44
|
created_at?: string;
|
|
42
45
|
}
|
|
43
46
|
|
|
@@ -65,6 +68,9 @@ export interface ProceduralEntry {
|
|
|
65
68
|
last_used?: string;
|
|
66
69
|
candidate_executor?: string | null;
|
|
67
70
|
candidate_successes?: number;
|
|
71
|
+
// aegis#497 — gap-driven tier escalation
|
|
72
|
+
gap_signal_count?: number;
|
|
73
|
+
gap_last_seen?: string | null;
|
|
68
74
|
created_at?: string;
|
|
69
75
|
}
|
|
70
76
|
|