@dudousxd/nestjs-agent-store-drizzle 0.9.0 → 0.10.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/README.md CHANGED
@@ -36,7 +36,8 @@ export class AppModule {}
36
36
  ```
37
37
 
38
38
  The package ships the `agentSchema` (Drizzle tables), `ensureAgentSchema` (a non-destructive
39
- DDL helper for a quick start), `DrizzleAgentStore`, and `DrizzleGovernanceQueries`. For production,
39
+ DDL helper for a quick start), `DrizzleAgentStore`, `DrizzleGovernanceQueries`, and
40
+ `DrizzleRagIngestionLog`. For production,
40
41
  prefer your normal drizzle-kit migrations over the `ensureAgentSchema` helper.
41
42
 
42
43
  ## The read a turn makes
@@ -52,6 +53,55 @@ ceiling is only a token budget, names no row bound and the whole thread is read
52
53
  README. Nothing to wire: the loop probes for the method, and `getThread` remains the right read for
53
54
  a client rendering a transcript.
54
55
 
56
+ ## Memory
57
+
58
+ `DrizzleMemoryProvider` is a `MemoryProvider` over `agent_memory`, a table `ensureAgentSchema`
59
+ creates alongside the other six.
60
+
61
+ ```ts
62
+ import { DrizzleAgentStoreModule, DrizzleMemoryProvider } from '@dudousxd/nestjs-agent-store-drizzle';
63
+
64
+ AgentModule.forRootAsync({
65
+ // AGENT_STORE already comes from DrizzleAgentStoreModule.forRoot({ db }), which is global.
66
+ externalStore: true,
67
+ inject: [DrizzleMemoryProvider],
68
+ useFactory: (memory: DrizzleMemoryProvider) => ({
69
+ model,
70
+ actorResolver,
71
+ memory: { provider: memory },
72
+ }),
73
+ });
74
+ ```
75
+
76
+ The store module exports the provider but never binds `AGENT_MEMORY`: that token's **presence** is
77
+ what turns memory on, so binding it here would switch the feature on for every host that installs the
78
+ store. Naming the provider is how you opt in.
79
+
80
+ What it holds to:
81
+
82
+ - `list` filters `scope in (…)` in the **query**. The library drops out-of-scope records it is handed,
83
+ but that is a backstop — a memory held for another actor or another tenant is never selected, so it
84
+ is unreachable rather than outranked.
85
+ - `write` upserts on the (`scope`, `key`) unique index in one statement, so two turns concluding the
86
+ same key at once cannot race to a duplicate-key insert. The conflict `set` names exactly what a
87
+ rewrite may change, so `pinned`, `created_at` and `id` are left alone by construction: a rewrite
88
+ must not unpin a record, lose when the belief was formed, or invalidate the delete handle a person
89
+ was already shown.
90
+ - `write` refuses an **agent-authored** record at any scope but the actor's own. A human-authored one
91
+ above it is allowed, because that is what a console publishing an organisation's policy does, and
92
+ whether that person may write there is what `memoryWriteVerdict` answers.
93
+ - `forget` deletes only from the actor's own scope, so an id alone cannot reach a tenant's memory.
94
+ - `pin({ id, pinned })` is the operator act the SPI has no method for — a pin grants a fact a
95
+ permanent place in every future prompt, so nothing an agent can reach may set it. Authorize it in
96
+ your own console.
97
+
98
+ **`search` is not implemented**, deliberately. It buys a block filled by relevance once the applicable
99
+ set outgrows `maxMemories`, and that needs an index over the memories themselves — whose shape depends
100
+ entirely on what you already run. Without it, `list` reads the applicable scopes whole and the ceiling
101
+ never bites. The signal that it is worth building is `MemoryDigest.omitted` going non-zero, and
102
+ `pinnedOmitted` especially: a standing policy that stopped reaching any prompt. Both are on the
103
+ `aviary:agent:memory.resolved` diagnostic.
104
+
55
105
  ## Upgrading a database that already exists
56
106
 
57
107
  `ensureAgentSchema` is additive only — `CREATE TABLE IF NOT EXISTS` plus `CREATE INDEX IF NOT
@@ -89,6 +139,30 @@ A thread that was soft-deleted still counts as holding its references: the messa
89
139
  the bytes are still reachable from stored state. Delete the thread for real and the cascade makes
90
140
  them collectable on the next sweep.
91
141
 
142
+ ## RAG ingestion outcomes
143
+
144
+ `rag_ingestion_log` holds the latest outcome for every RAG document — ingested, skipped, failed,
145
+ removed — one row per document id, overwritten on re-ingest. It answers the question a vector store
146
+ structurally cannot: *which documents failed to index, and why*. A document whose extraction came
147
+ back empty, whose mime type had no extractor, or whose embedding call threw produces zero chunks, so
148
+ `VectorStore.listDocuments()` cannot tell it from one nobody ever uploaded. Pair the two: the index
149
+ is the truth about what is retrievable, this table is the truth about what was attempted.
150
+
151
+ `DrizzleRagIngestionLog` fills it by subscribing to the `aviary:rag:*` diagnostics
152
+ `@dudousxd/nestjs-agent-rag-media` publishes — the RAG package owns no storage of its own. It is
153
+ bound and exported by `DrizzleAgentStoreModule.forRoot({ db })` by default; pass
154
+ `{ ragIngestionLog: false }` to bind nothing at all. `ensureAgentSchema` creates the table, indexed
155
+ by (`collection`, `updated_at`) for the per-collection listing.
156
+
157
+ Reads: `list` / `listPage` (a page plus the unpaginated total), `get`, `remove`,
158
+ `removeByCollection`, a delete-safe keyset `iterate`, and `listDocumentIds` for an orphan sweep that
159
+ wants a collection's id set without hydrating every stack trace in the table. The paging order is
160
+ exported as `RAG_INGESTION_LOG_PAGE_ORDER` — `updated_at desc` tiebroken on the primary key, which
161
+ is what keeps consecutive pages disjoint when a bulk upload stamps a whole batch with one timestamp.
162
+
163
+ Writes are best-effort: the recorder runs detached on a diagnostics channel, so a failed write is
164
+ reported and dropped rather than taking down the ingestion that triggered it.
165
+
92
166
  ## Cost accounting
93
167
 
94
168
  `DrizzleGovernanceQueries` resolves cost per usage row as `COALESCE(reportedCostUsd, tokens × pricing)`: