Deploy Poimen Memory K8s cluster with ArgoCD tracking (M2.2, M3.5-M3.7)
ci / markdown (push) Waiting to run

This commit is contained in:
Story Crater Bot
2026-08-22 23:13:42 -07:00
parent 9c723fe66f
commit d3be7f6fd4
105 changed files with 10973 additions and 113 deletions
+38 -8
View File
@@ -17,10 +17,12 @@ time and produces the same rows.
## Facts (inlined — no spec read needed)
```rust
async fn upsert_node(&self, node: &MemoryNode, embedding: &[f32]) -> Result<()>;
async fn upsert_node(&self, node: &MemoryNode) -> Result<()>;
async fn upsert_vector(&self, sha: &Sha256Hash, kind: VectorKind, embedding: &[f32]) -> Result<()>;
async fn insert_edges(&self, child: &Sha256Hash, parents: &[Sha256Hash]) -> Result<()>;
async fn search(&self, q: &[f32], levels: &[Level], project: &ProjectId, k: usize)
-> Result<Vec<ScoredNode>>;
async fn search(&self, q: &[f32], kind: VectorKind, levels: &[Level],
project: Scope, k: usize) -> Result<Vec<ScoredNode>>;
async fn lookup_signature(&self, sig_sha: &str) -> Result<Option<SignatureHit>>;
async fn parents_of(&self, sha: &Sha256Hash) -> Result<Vec<MemoryNode>>;
async fn clear_project(&self, project: &ProjectId) -> Result<()>;
```
@@ -32,6 +34,23 @@ rows. Same for edges on the composite key.
`search` orders by `embedding <=> $1` — cosine distance, matching the
`vector_cosine_ops` index. Any other operator silently drops to a seq scan.
**`kind` must be a literal predicate in the SQL, not a bind parameter, and not a
filter applied to results.** The indexes are partial (`WHERE kind = 'text'`), and
the planner only uses a partial index when the query's predicate provably matches
it. A `WHERE kind = $2` defeats that and silently degrades to a scan over every
vector of both kinds — the same failure mode as the wrong opclass, and just as
invisible.
**`Scope` is not a `ProjectId`.** Tool-failure lookups federate across projects
because an `ERESOLVE` lesson is not project-specific, while ordinary standing-query
memories stay scoped. `Scope::Project(id)` filters; `Scope::AllProjects` does not
and lets project relevance act as a rank boost later instead of a hard filter.
`lookup_signature` is the exact-match tier: a primary-key hit on
`failure_signature`, no vector involved. It is the cheapest and highest-precision
answer the store can give, so it belongs in the repository rather than being
assembled from a `search` call by a caller who does not know it exists.
Edges are inserted **after** both endpoints exist, or the foreign key rejects
them. Rebuild therefore has two passes: all nodes, then all edges. This is not an
optimisation; a single-pass insert fails on the first forward reference.
@@ -44,12 +63,23 @@ rebuild — batch across nodes, not per node.
1. `PgRepo::connect(url)` with a pool; run migrations on connect.
2. Implement the five methods above.
3. `upsert_many(nodes)` batching embedding calls at 32 and inserting with a
multi-row statement.
4. Two-pass write: nodes, then edges.
multi-row statement. Batch across *both* vector kinds — a node with a symptom
projection contributes two texts to the same batch, not two batches.
4. Three-pass write: nodes, then vectors and signatures, then edges. Vectors and
signatures carry foreign keys to nodes, so they cannot precede them, and edges
still need both endpoints present.
5. Separate query builders per `kind` so the literal predicate is guaranteed at
compile time rather than by convention.
5. `clear_project` deletes nodes for one project; edges cascade.
6. Return `ScoredNode { node, distance }` — keep the raw distance, do not convert
to a similarity score here. The reranker (M3.2) wants the ordering, and a
lossy conversion hides ties.
6. Return `ScoredNode { node, distance, matched_kind }` — keep the raw distance,
do not convert to a similarity score here. The reranker (M3.2) wants the
ordering, and a lossy conversion hides ties. `matched_kind` tells the caller
whether the hit came from the memory text or its symptom projection, which is
the difference between "this is about your topic" and "this explains your
error".
7. Exclude superseded nodes by default: `LEFT JOIN memory_supersede` on
`old_sha`, filter where the join is null. An `include_superseded` flag exists
for audit, off everywhere else.
## Acceptance