From e7acab2c56b70b1e9628e9082349b4a3953fc6a8 Mon Sep 17 00:00:00 2001 From: sb-iam <59984144+sb-iam@users.noreply.github.com> Date: Sun, 28 Jun 2026 03:17:10 -0700 Subject: [PATCH 1/2] docs(handoff): Team-memory dynamic graph redesign + deploy reconciliation Fresh-session handoff covering the dynamic force-directed graph, honest de-noised metrics, the click-to-explain Flow pane, the divergence vs main's parallel impl, the best-of-both reconciliation (PR #38), build/verify quirks, and gotchas. Co-Authored-By: Claude Opus 4.8 --- ...laude_handoff_team_memory_dynamic_graph.md | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 docs/handoff/claude_handoff_team_memory_dynamic_graph.md diff --git a/docs/handoff/claude_handoff_team_memory_dynamic_graph.md b/docs/handoff/claude_handoff_team_memory_dynamic_graph.md new file mode 100644 index 0000000..faeab0c --- /dev/null +++ b/docs/handoff/claude_handoff_team_memory_dynamic_graph.md @@ -0,0 +1,167 @@ +# Claude Code Handoff — Team-Memory **Dynamic Graph** (redesign + deploy reconciliation) + +> **Fresh-session handoff.** Read this top-to-bottom before touching the Team-memory graph. +> It captures the dynamic-graph redesign, the honest-metrics fix, the click-to-explain "Flow" +> pane, and — most importantly — how it was **reconciled against `main`'s parallel +> implementation** so it can actually deploy. Author: Claude Code (Opus 4.8) session, 2026-06-28. + +--- + +## 0. TL;DR — what to do next + +1. **Merge [PR #38](https://github.com/karti-ai/podman/pull/38) → `main`.** It is `MERGEABLE` (no conflicts) and is the deploy path. Deploy = push to `main` (`deploy_on_push`). +2. **After deploy, hard-refresh** (Cmd-Shift-R) or use a private window — the PWA service worker caches aggressively, so you'll think nothing changed. +3. **Close [PR #22](https://github.com/karti-ai/podman/pull/22)** (the older one into `feat/live-graph-glue`) with a note pointing at #38; it is superseded for the deploy path. +4. Optionally delete the stale branches `feat/live-graph-glue` and `feat/team-memory-dynamic-graph` once #38 lands. + +**State right now:** the live site (`165-22-129-249.sslip.io` / `podman.live`) runs `main`, which has a **static** graph with **inflated metrics** and **does not render the learning-loop / activity rails** (even though its backend computes them). PR #38 fixes all three. + +--- + +## 1. Branches, PRs, deploy + +| Branch | What's on it | Status | +| --- | --- | --- | +| `main` | Trunk. Has a **parallel** Team-memory impl: computes `loop`/`activity` in the API (rich types) but **static** graph, **inflated** metrics, rails **never rendered**. Deployed. | live | +| `feat/team-memory-deploy` | **The reconciliation.** Dynamic graph + honest metrics + Flow pane, on top of `main`, rails adapted to `main`'s types. | **PR #38 → `main`, MERGEABLE** | +| `feat/live-graph-glue` | Where the original redesign (v1) was merged (PR #22). Branched ~100 commits before `main`'s later work; a direct merge to `main` was unsafe/tangled. | superseded by #38 | +| `feat/team-memory-dynamic-graph` | v1 PR branch (merged into glue via #22). | superseded | + +- **Deploy = merge/push to `main`.** Do **not** push to `main` directly; merge the PR. `main` is shared by ~4 engineers and moves fast. +- Live API to sanity-check: `curl https://165-22-129-249.sslip.io/api/pods/demo-pod/graph`. + +--- + +## 2. The divergence (read this — it's the crux) + +While the dynamic-graph redesign was being built on `feat/live-graph-glue`, **another engineer shipped a *parallel* version of the same feature on `main`.** They are not the same: + +| Concern | `main` (deployed) | This redesign (PR #38) | +| --- | --- | --- | +| Graph layout | **Static** — renders server `x`/`y` columns, no motion | **Dynamic** force-directed (`forceSim.ts`), draggable, animated | +| Learning-loop rail | **Computed in API, never rendered** | Rendered (`LearningLoop.tsx`) | +| Activity stream | **Computed in API, never rendered** | Rendered (`ActivityStream.tsx`) | +| Metric cards | **Inflated** (raw collision-signature / accepted-outcome counts → e.g. 50 risk paths for 4 files) | **De-noised** (distinct collision files / owner engineers) | +| Selected-node pane | kind/status/relationships | + **Flow** narrative ("Karti and Yahya are both editing auth.ts…") | +| `loop` type | `PodLearningLoop` = `{ activeStep, steps: PodLearningLoopStep[] }` (richer; step `status`) | **kept `main`'s** | +| `activity` type | `PodGraphActivity` = `{ id, at, kind, title, detail, nodeId?, edgeId? }` (richer) | **kept `main`'s** | + +**Reconciliation strategy (what PR #38 does):** keep `main` as the trunk; keep `main`'s **backend** materializer/`buildLoop`/activity and its **richer shared types**; swap in **this redesign's frontend graph layer**; adapt the rail components to consume `main`'s types; port only the **honest-metrics** fix into `main`'s `live.ts`. + +> A literal `feat/live-graph-glue → main` merge was attempted first and produced **invalid auto-merge states** (duplicate `loop`/`activity` keys in `demo.ts`, duplicate imports in `live.ts`) because glue was ~100 commits stale. It was aborted; the same best-of-both was re-applied cleanly on a branch off `main`. The PR diff is just the graph layer (11 files). + +--- + +## 3. What PR #38 changes (11 files) + +**Frontend — new `frontend/src/components/graph/*`, composed into `GraphView.tsx`:** +- `forceSim.ts` — **dependency-free** force layout (charge repulsion, link springs, centroid recenter + gentle pull, 2-pass collision, bounds clamp, alpha annealing). Driven by a `requestAnimationFrame` loop. **No new dependency / no `pnpm-lock.yaml` change.** +- `GraphCanvas.tsx` — SVG render from the sim: **draggable + pinnable** nodes (double-click to release), curved edges that fan parallel pairs, weight-sized geometric shapes (square / outlined-square / circle / triangle / diamond), fade-in on new nodes/edges, animated `learned_from` dash, **risk-path lit / rest dimmed**, label collision-avoidance. +- `encoding.ts` — node/edge kind colors, `highlightFor` (risk/learn/all modes), `flowNarrative(graph, nodeId)` (the plain-English path walk), `modeBlurb`, legends, `ACTIVITY_TAG` (keyed by `main`'s `PodGraphActivityKind`). +- `MetricsRail.tsx`, `LearningLoop.tsx`, `ActivityStream.tsx`, `SelectedNodePanel.tsx` — the rails + stream + detail pane in light shadcn (`@/components/ui/*`). `LearningLoop` consumes `PodLearningLoop` (steps + `activeStep`); `ActivityStream` consumes `PodGraphActivity` (title + detail); `SelectedNodePanel` renders the **Flow** section + mode-aware default copy. +- `GraphView.tsx` — composes everything; polls `/api/pods/:id/graph` every 5s and **diffs** (positions/pins preserved across refreshes — no hard replace), + best-effort `ws /api/events` nudge; drops a **stale selection** (selected node gone across a poll) so the canvas can't dim entirely. +- `lib/graph.ts` — adds `backendEventsUrl()` (http→ws) for the nudge. + +**Backend — surgical (keeps `main`'s materializer + `buildLoop` + activity builder):** +- `backend/src/graph/live.ts` — the **headline metric cards** are now derived from the **final de-noised graph**: *Open risk paths* = distinct collision **files** (`touches` edges to file nodes); *Learned owners* = distinct **owner engineers** (`owns`/`learned_from` edges). On live data: **50 → 4** risk files, **16 → 1** owner. `riskPaths` (raw signatures) is still computed and fed to `buildLoop` (the loop is a throughput view, intentionally separate). **`buildLoop` and the activity builder are untouched.** +- `backend/src/graph/demo.ts` — fallback metrics realigned to the demo graph (3 owners / 1 risk path / 100%) so the numbers never contradict the picture. + +--- + +## 4. How it works (architecture) + +``` +materializePodGraph(podId) // backend/src/graph/live.ts (main's, + metric fix) + → GET /api/pods/:id/graph // backend/src/server.ts (PodGraph incl. loop/activity) + → fetchPodGraph() poll every 5s // frontend/src/lib/graph.ts (+ ws /api/events nudge) + → GraphView // diffs snapshots, computes highlight + flow + → GraphCanvas (forceSim tick → SVG, draggable) + → MetricsRail / LearningLoop / ActivityStream / SelectedNodePanel +``` + +- **`PodGraph` data contract** (`shared/src/graph.ts`, `main`'s): `nodes`, `edges`, `metrics`, optional `loop?: PodLearningLoop`, `activity?: PodGraphActivity[]`. Node kinds: engineer/feature/file/collision/intervention. Edge kinds: owns/editing/touches/collides/warns/learned_from. +- **Force sim** ignores the server's `x`/`y` except as **seed** positions (mapped into the canvas). Key tuning constants in `forceSim.ts`: `REPEL=4400`, `CENTER_STRENGTH=0.014`, `RECENTER=0.5`, `COLLIDE_PAD=12`, `COLLIDE_ITERS=2`, `BOUND_PAD=30`. Repulsion must dominate centering or the graph collapses to a point. +- **Default mode is "Risk path"** — lights the collision→intervention→`learned_from` chain, dims the rest to opacity `0.14`. +- **Flow narrative** (`flowNarrative`) walks a node's incident edges to produce sentences, e.g. collision → "Karti and Yahya are both editing auth.ts before pushing — the overlap git can't see. PodMan stepped in and suggested a sync PR." + +--- + +## 5. Is the data real / dynamic? (FAQ — was asked) + +- **Real:** yes. The graph is materialized **live from the real Atlas collections** (`observations`, `collisions`, `interventions`, `outcomes`, `engineer_states`, `pods`, `team_model`) on every request — not the hardcoded demo. The demo only shows as a **fallback** when there's zero activity. `generatedAt` advances on each request (re-materialized, not cached). +- **Dynamic:** the backend re-reads Mongo per request and the frontend polls ~5s + WS nudge, so the UI reflects current DB state within seconds. **But it only *changes* when the PodMan agent writes new data** (vision → observations → collisions → interventions → outcomes). When the agent is idle, the graph is static at last-known state. (At handoff time the newest activity was ~52 min old — no live ingestion.) +- **Numbers look inflated** because a lot of the real data is **test churn** (repeated `infra/README.md` collisions). The honest-metrics fix (§3) counts distinct entities so the cards match the graph; `MAX_COLLISIONS=8` in the materializer caps the visible collisions. + +--- + +## 6. Build / verify (toolchain quirks — important) + +- **`pnpm` is not on PATH** in the dev sandbox; use **`CI=true npx pnpm@10.32.1 …`** (pin 10.32.1 to match CI and keep the lockfile v10-compatible; `CI=true` avoids the no-TTY abort). + - `CI=true npx pnpm@10.32.1 lint` + - `CI=true npx pnpm@10.32.1 -r typecheck` + - `CI=true npx pnpm@10.32.1 -r build` +- The `npm error config prefix cannot be changed from project config: .npmrc` line is a **non-fatal warning** (tsc/eslint still run; exit code 0). +- **Adding a dependency is high-friction**: `pnpm install` wants to wipe + recreate `node_modules` (modules-dir version mismatch) and churns the shared `pnpm-lock.yaml` that CI's `--frozen-lockfile` depends on. That's why the force sim is **in-house**, not `d3-force`. Prefer zero-dep solutions. +- **CI** (`.github/workflows/hermes-verify.yml`) runs `pnpm install --frozen-lockfile && pnpm lint && pnpm -r typecheck && pnpm -r build` (pnpm 10.32.1, node 22). + +**Running it locally to eyeball:** the **backend can't run locally** (needs LiveKit/Gemini/GitHub secrets and hard-exits without Mongo). Verify the **frontend** by pointing a vite dev server at either the live backend (CORS is `*`) or a tiny mock that serves `createDemoPodGraph()` from `backend/dist/graph/demo.js`: + +``` +# mock backend (node http) serving GET /api/pods/:id/graph from backend/dist/graph/demo.js +# then: VITE_BACKEND_URL=http://localhost:8799 in frontend/.env.local +CI=true npx pnpm@10.32.1 --filter @podman/frontend dev +``` + +A throwaway entry (`frontend/graph-preview.html` + `frontend/src/graph-preview.tsx` mounting ``) renders the view directly without the pods list. Drive it with Playwright (already a devDependency; chromium is cached) — sample node positions over time to confirm the sim ticks, `getComputedStyle` opacity to confirm dimming, click nodes to read the Flow text. **Delete all of these temp files before committing.** + +--- + +## 7. Gotchas (these already bit; don't re-learn them) + +- **PWA cache** — hard-refresh after every deploy or you'll think nothing changed. +- **StrictMode RAF freeze (fixed):** the dev double-mount cancels the animation frame between effect passes; the loop must re-arm **unconditionally** after `setData` (`ensureRaf()` is idempotent via the `rafRef==null` guard), not gated on a topology change — else the sim is frozen at seed positions in dev until first interaction. Seed positions are a plausible layout, so this can hide. +- **Dimming vs animation (fixed):** `.pm-dim` opacity is defeated if the fade-in uses `animation-fill-mode: both/forwards` (held final keyframe overrides the class). The enter animation must use **no fill-mode**. +- **Stale selection (fixed):** after a poll, a selected node can vanish from the payload; `highlightFor(selected)` would then light only a dead id and dim the whole graph. `GraphView` derives `liveSelected = selected ∈ nodeById ? selected : null` and clears it. +- **Force tuning:** repulsion must dominate centering (`REPEL ≫ CENTER_STRENGTH·r`) or the graph collapses; `COLLIDE_ITERS≥2` keeps linked nodes from stacking. +- **Pathological data:** with the *uncapped* live materializer (pre-`MAX_COLLISIONS`), engineer labels can crowd the center because each engineer fans many `collides` edges. The materializer cap is the real fix; the frontend still spreads + draggable. +- **`learned_from` "money" edge on `demo-pod`** won't draw on real data unless there's one intact accept flow (its one accepted outcome is orphaned). The demo fallback shows it. +- **Parallel impl on `main`:** keep `main`'s `PodLearningLoop`/`PodGraphActivity` types and `buildLoop`/activity builder. Do **not** reintroduce the v1 `LearningStage`/`ActivityEvent` types — they were dropped in the reconciliation. + +--- + +## 8. Open items / nice-to-haves + +- **Merge PR #38, redeploy, hard-refresh** (the headline). +- Close PR #22; delete `feat/live-graph-glue` + `feat/team-memory-dynamic-graph`. +- Optional polish: more canvas spread on small graphs; label de-clutter for pathological/uncapped data; seed a clean collision→intervention→accept chain on `demo-pod` so the violet money edge draws on real data. +- Optional: reconcile the loop "Predict" value (distinct signatures) vs the "Open risk paths" card (distinct files) — they intentionally differ today; could unify wording if it confuses. + +--- + +## 9. Key files + +| File | Role | +| --- | --- | +| `backend/src/graph/live.ts` | `materializePodGraph` — real-data graph + `buildLoop`/activity (main's) + **honest metric cards** | +| `backend/src/graph/demo.ts` | demo fallback (graph + loop + activity + consistent metrics) | +| `backend/src/graph/store.ts` | `loadPodGraph` (live → seeded `team_model.graph` → demo), `reachFrom` (`$graphLookup`) | +| `shared/src/graph.ts` | `PodGraph` contract incl. `PodLearningLoop`, `PodGraphActivity` (main's types) | +| `frontend/src/components/GraphView.tsx` | page: header, toggles, 3-panel grid, poll/WS, compose | +| `frontend/src/components/graph/forceSim.ts` | the in-house force simulation | +| `frontend/src/components/graph/GraphCanvas.tsx` | dynamic SVG graph (drag/animate) | +| `frontend/src/components/graph/encoding.ts` | colors, `highlightFor`, `flowNarrative`, legends, activity tags | +| `frontend/src/components/graph/{MetricsRail,LearningLoop,ActivityStream,SelectedNodePanel}.tsx` | rails/stream/detail | +| `frontend/src/lib/graph.ts` | `fetchPodGraph`, `backendEventsUrl` | + +--- + +## 10. Verification done (PR #38) + +`pnpm lint` + `-r typecheck` + `-r build` pass. Playwright (dev/StrictMode) confirmed, against the demo payload served from a mock: +- dynamic graph **ticks on load** (positions move with no interaction) and is **draggable**; +- **learning-loop rail** renders main's 5 steps (ADAPT active) and the **activity stream** renders main's title+detail; +- metric cards read **3 / 1 / 100%** (consistent with the graph); +- **Flow** narrative is correct per node kind (collision / intervention / engineer / file); +- **no overlapping nodes** (≥48px min separation), **zero page errors** (only an expected WS 404 against the mock, handled). + +The honest-metric formula was also re-checked against the **real live graph**: **4** distinct risk files / **1** learned owner (vs the deployed 50 / 16). From 9f509e5f347c4737b4180ca221ee3acc2487f0e6 Mon Sep 17 00:00:00 2001 From: sb-iam <59984144+sb-iam@users.noreply.github.com> Date: Sun, 28 Jun 2026 04:00:24 -0700 Subject: [PATCH 2/2] docs: add Codex handoff for DB cleanup --- ...f-mongodb-cleanup-team-memory-graph-rsi.md | 666 ++++++++++++++++++ 1 file changed, 666 insertions(+) create mode 100644 docs/handoff/codex-handoff-mongodb-cleanup-team-memory-graph-rsi.md diff --git a/docs/handoff/codex-handoff-mongodb-cleanup-team-memory-graph-rsi.md b/docs/handoff/codex-handoff-mongodb-cleanup-team-memory-graph-rsi.md new file mode 100644 index 0000000..96a4a9c --- /dev/null +++ b/docs/handoff/codex-handoff-mongodb-cleanup-team-memory-graph-rsi.md @@ -0,0 +1,666 @@ +# Codex Handoff: MongoDB Cleanup, Team Memory Graph, and RSI Learning Docs + +Date: 2026-06-28 +Repo state checked: `main` at `1d097b1` +Database checked: MongoDB Atlas database named `podman` +Scope: docs/spec handoff, live Team memory graph verification, and safe DB cleanup path + +## Current Repo State + +The local checkout was moved to `main` and fast-forwarded to `origin/main`. +Working tree was clean after inspection. + +The learning and graph specification docs are present on `main`: + +- `docs/agent-learning/README.md` +- `docs/agent-learning/spec.md` +- `docs/agent-learning/policy.md` +- `docs/agent-learning/plan.md` +- `docs/agent-learning/prompt.md` +- `docs/continual-learning/README.md` +- `docs/continual-learning/spec.md` +- `docs/continual-learning/policy.md` +- `docs/continual-learning/plan.md` +- `docs/continual-learning/prompt.md` +- `docs/graph-discovery/README.md` +- `docs/graph-discovery/spec.md` +- `docs/graph-discovery/policy.md` +- `docs/graph-discovery/plan.md` +- `docs/graph-discovery/prompt.md` + +These docs describe the intended architecture, but only some pieces are backed +by live Atlas collections today. + +## Verification Summary + +Atlas connection is valid through local `.env` `MONGODB_URI`. No secrets were +printed during verification. + +Public API checks: + +- `https://165-22-129-249.sslip.io/health` returned `200` with `{ "ok": true }`. +- `GET /api/pods` returned one real pod: `demo-pod`. +- `GET /api/pods/demo-pod/graph` returned a live materialized Team memory graph. + +Live graph response for `demo-pod`: + +- Nodes: `31` +- Edges: `59` +- Learned owners metric: `2` +- Open risk paths metric: `2` +- Accept rate metric: `39%` +- Learning loop active step: `adapt` +- Learned edges: `2` `learned_from` edges +- Activity stream populated from real records + +Important conclusion: + +The main Team memory graph endpoint is real and backed by Atlas live +materialization. It is not merely returning the demo fallback. + +## Atlas Collection Snapshot + +Approximate counts observed: + +| Collection | Count / status | +| --- | ---: | +| `pods` | 1 | +| `engineer_states` | 9 | +| `observations` | 2318 | +| `collisions` | 451 | +| `interventions` | 362 | +| `outcomes` | 107 | +| `team_model` | 65 | +| `graph_nodes` | 715 | +| `graph_edges` | 845 | +| `hermes_jobs` | 29 | +| `hermes_job_events` | 337 | +| `memory_vectors` | missing | +| `agent_runs` | missing | +| `agent_trace_events` | missing | +| `strategy_versions` | missing | +| `learning_proposals` | missing | + +Real pod: + +```text +demo-pod + name: demo pod + members: ram, Karti, yahya, shakthi +``` + +Noise observed: + +- Many `verify-pod-*` records. +- Many `verify-graph-*` records. +- `Verify ...` observations inside `demo-pod`. +- Orphaned verify outcomes in `demo-pod`. +- Some stale or duplicate engineer state casing, e.g. `Shakthi` and `shakthi`. +- `frontend-pod` outcomes with no corresponding active pod. + +## What Is Real Today + +The following are real and active: + +- Atlas connectivity. +- `demo-pod` pod record. +- Real `engineer_states` for demo members. +- Real observation/collision/intervention/outcome collections. +- Live graph materialization from source collections. +- Graph response `loop` and `activity` fields. +- Real `learned_from` edges produced by accepted real outcomes that still join + back to surviving intervention/collision records. + +The following are not yet real: + +- `memory_vectors` collection. +- `agent_runs` collection. +- `agent_trace_events` collection. +- `strategy_versions` collection. +- `learning_proposals` collection. + +That means the continual-learning story is currently supported by exact MongoDB +records and graph edges. The richer agent-learning spec is documented but not +implemented in Atlas yet. + +## Claude Code Review Addendum + +Source: pasted Claude Code review text approved for Codex to read. The review +was treated as input, then reconciled against current `main` and the Atlas check +above. Do not copy the review blindly; a few findings were from an older repo +state or have since been superseded. + +### Still Material Findings + +The review correctly identifies the main mismatch: + +```text +The docs describe a broader self-improving platform, while the shipped product +currently has a narrower but real recall-and-policy loop. +``` + +The shipped loop lives in code, not in the aspirational agent-learning docs: + +- `backend/src/agent/podman.ts` + - Calls `recallSimilar(collision)`. + - If prior memory exists, bumps severity to `critical`. + - Calls `shouldIntervene(collision, prior)`. + - Calls `preferredAction(collision, prior)`. + - Adds the visible message suffix `Seen before.` when prior memory exists. +- `backend/src/memory/policy.ts` + - Suppresses known false-positive prior outcomes. + - Enforces pod cooldown. + - Reuses a prior accepted intervention action when available. +- `backend/src/memory/vectors.ts` + - Stores `memorySignature`, `memoryText`, and optional `embedding` on + `collisions`. + - `recallSimilar` tries vector recall first, then signature/file fallback. + +This is the real recursive/self-improving asset today: + +```text +new collision -> recall prior collision -> adjust severity/action/message -> +record outcome -> future collision changes behavior +``` + +The current docs should eventually be reconciled around this loop instead of +implying the full agent-learning platform already exists. + +### Confirmed Aspirational Areas + +The following are documented but not live in Atlas/code yet: + +- `agent_runs` +- `agent_trace_events` +- `strategy_versions` +- `learning_proposals` +- `memory_vectors` + +The `agent-learning` docs should be treated as future architecture unless a +small, explicit slice is implemented. For demo purposes, do not build a broad +strategy-versioning platform. If time allows, the smallest credible slice is one +stored policy/strategy row that explains a concrete behavior change. + +### Outcome Write Caveat + +`backend/src/memory/store.ts` `recordOutcome` currently: + +- inserts the outcome into `outcomes`; +- updates the intervention status to `accepted` or `dismissed`. + +It does not currently persist `team_model.ownership`. The live graph can still +derive `owns` and `learned_from` from accepted real outcomes at read time, but a +literal "before/after MongoDB ownership write" does not exist yet. + +If the demo needs a concrete durable ownership diff, add a small explicit write +on accepted real outcomes: + +```text +accepted && wasRealCollision -> team_model.ownership[normalizedFile] = learnedOwner +``` + +That should be a separate code task, not part of the DB cleanup unless the user +explicitly asks. + +### Vector Recall Caveat + +The docs often say "exact recall first." Current code does the reverse: + +```text +recallSimilar = vector recall first, then signature/file fallback +``` + +Also: + +- Embeddings live on `collisions`, not `memory_vectors`. +- Atlas vector index name in code is `collision_embedding`. +- Gemini embedding calls request `outputDimensionality: 768`. +- Voyage embeddings may have a different dimensionality depending on model. + +For the hackathon demo, exact/signature/file fallback is the reliable story. +Vector recall should remain nice-to-have unless Atlas index configuration is +verified. + +### Demo Script Caveat + +`docs/demo-setup.md` is stale relative to the Team memory observatory demo. It +still describes an older Hermes/voice/blocker flow and does not script: + +- graph observatory; +- collision -> intervention -> outcome; +- `learned_from`; +- run 1 vs run 2 changed behavior. + +Before stage rehearsal, rewrite `docs/demo-setup.md` around the actual +observatory path. + +### Superseded Review Findings + +The pasted review included two findings that must be treated carefully: + +- It claimed the current `shared/src/graph.ts` had an older `LearningStage` / + `ActivityEvent.text` contract. Current `main` uses `PodLearningLoop` with + `activeStep`, step `status`, and `PodGraphActivity` with `title` / `detail`. + Always check `shared/src/graph.ts` before editing specs. +- It claimed the hero `learned_from` edge did not render on `demo-pod`. The + current Atlas/public API check returned two live `learned_from` edges. The + risk is still real if cleanup deletes accepted real outcomes or their joined + collision/intervention records. Preserve the intact accepted chains. + +### Priority Reconciliation Tasks + +After the DB cleanup script, the next documentation/code priorities should be: + +1. Rewrite `docs/demo-setup.md` as the canonical graph observatory demo script. +2. Add a short `docs/recursive-loop.md` or equivalent section that names the + real shipped loop in `podman.ts`, `policy.ts`, and `vectors.ts`. +3. Mark agent-learning collections and strategy versioning as not-yet-built + unless implemented. +4. Reconcile vector-recall language in docs with current `vectors.ts`. +5. Optionally add the `recordOutcome` ownership write if a durable ownership + diff is needed for judging. + +## Main Data Issue + +The live graph and the normalized graph mirror are out of sync. + +Live materializer for `demo-pod`: + +```text +31 nodes +59 edges +2 learned_from edges +``` + +Normalized mirror in `graph_nodes` / `graph_edges` for `demo-pod`: + +```text +11 seeded/demo-style nodes +13 seeded/demo-style edges +``` + +Impact: + +- `GET /api/pods/demo-pod/graph` is good and real. +- `GET /api/pods/demo-pod/graph/reach/:nodeId` uses `graph_edges`, so it can + return stale seeded paths. +- Example observed: + - `/graph/reach/engineer:karti` returned a seeded path. + - `/graph/reach/engineer:ram` returned `0`, even though Ram is present in the + live graph. + +The cleanup should therefore include a mirror rebuild after deleting test data. + +## Relevant Code Paths + +Graph and MongoDB: + +- `backend/src/graph/live.ts` + - Live materializer. + - Reads `pods`, `engineer_states`, `observations`, `collisions`, + `interventions`, `outcomes`, and `team_model`. + - Produces `nodes`, `edges`, `metrics`, `loop`, and `activity`. +- `backend/src/graph/store.ts` + - `loadPodGraph`: live materializer first, then seeded `team_model.graph`, + then demo fallback. + - `seedGraph`: writes seeded graph into `team_model`, `graph_nodes`, + `graph_edges`. + - `reachFrom`: uses `$graphLookup` over `graph_edges`. +- `backend/src/memory/db.ts` + - MongoDB connection and core collection helpers. +- `shared/src/graph.ts` + - Public graph contract including optional `loop` and `activity`. + +Docs: + +- `docs/mongodb.md` +- `docs/graph.md` +- `docs/graph-discovery/` +- `docs/continual-learning/` +- `docs/agent-learning/` + +## DB Cleanup Goal + +Get Atlas into a demo-stable state: + +1. Preserve real `demo-pod` learning history. +2. Remove verification/orphan/test records. +3. Rebuild `graph_nodes` and `graph_edges` from the live materialized graph. +4. Keep cleanup repeatable and reversible. +5. Avoid ad hoc shell deletes. + +## Required Safety Rule + +Take a backup before deleting anything. + +```bash +mongodump "$MONGODB_URI" --archive=podman-before-cleanup.archive --gzip +``` + +Do not commit the archive. + +## Cleanup Keep Set + +Start with this conservative keep set: + +```js +const keepPods = ["demo-pod"]; +``` + +Records with `podId` outside this set are cleanup candidates unless there is a +specific reason to preserve them. + +## Phase 1: Dry-Run Counts + +Write a script that defaults to dry-run. It should print counts only. + +Candidate file: + +```text +scripts/db-cleanup.mjs +``` + +Default behavior: + +```bash +node scripts/db-cleanup.mjs --dry-run +``` + +Apply behavior: + +```bash +node scripts/db-cleanup.mjs --apply +``` + +The script must not delete anything unless `--apply` is present. + +## Phase 2: Remove Orphan/Test Pod Data + +Delete records whose `podId` is not in `keepPods`. + +Collections: + +- `engineer_states` +- `observations` +- `collisions` +- `interventions` +- `outcomes` +- `team_model` +- `graph_nodes` +- `graph_edges` +- `hermes_jobs` +- `hermes_job_events` + +Filter: + +```js +{ podId: { $nin: ["demo-pod"] } } +``` + +Note: + +Some `pods` documents may use `id` instead of `podId`. For `pods`, do not use +the filter above. Keep the document with `id: "demo-pod"` and delete obvious +test pods only if they exist. + +## Phase 3: Clean Demo-Pod Verification Artifacts + +Within `demo-pod`, delete only obvious verification records. + +### Observations + +```js +{ + podId: "demo-pod", + $or: [ + { engineerId: /^Verify\b/ }, + { currentFile: /^PodMan verification screen$/ }, + { currentFile: /^frame \d+$/ } + ] +} +``` + +### Outcomes + +```js +{ + podId: "demo-pod", + $or: [ + { interventionId: /^int-verify-/ }, + { collisionId: /^col-verify-/ } + ] +} +``` + +### Collisions and Interventions + +Be more conservative. Delete only records that clearly have verify IDs or no +matching counterpart. + +Safe candidate filters: + +```js +// collisions +{ + podId: "demo-pod", + id: /^col-verify-/ +} + +// interventions +{ + podId: "demo-pod", + id: /^int-verify-/ +} +``` + +Optional orphan cleanup: + +- Delete interventions whose `collisionId` does not exist in `collisions`. +- Delete outcomes whose `interventionId` does not exist in `interventions` and + whose `collisionId` does not exist in `collisions`. + +Run orphan cleanup only after dry-run prints exact IDs and counts. + +## Phase 4: Normalize Demo-Pod Engineer State + +Keep canonical active engineers: + +```text +ram +Karti +yahya +shakthi +``` + +Cleanup candidates: + +```js +{ + podId: "demo-pod", + $or: [ + { name: /^Verify\b/ }, + { name: /^codex-check$/i }, + { name: /^testrepo/i }, + { name: "Shakthi" } + ] +} +``` + +Only delete `Shakthi` if `shakthi` is confirmed as the canonical current record. + +## Phase 5: Rebuild Graph Mirror + +This is the most important post-cleanup step. + +The live graph endpoint is real, but reachability uses stale mirrored records. +After cleanup: + +1. Materialize the live graph for `demo-pod`. +2. Delete mirrored rows for `demo-pod`. +3. Insert live graph nodes into `graph_nodes`. +4. Insert live graph edges into `graph_edges`. +5. Update `team_model.graph` and `team_model.updatedAt`. + +Pseudocode: + +```js +const graph = await materializePodGraph("demo-pod"); + +await db.collection("graph_nodes").deleteMany({ podId: "demo-pod" }); +await db.collection("graph_edges").deleteMany({ podId: "demo-pod" }); + +await db.collection("graph_nodes").insertMany( + graph.nodes.map((node) => ({ ...node, podId: "demo-pod" })) +); + +await db.collection("graph_edges").insertMany( + graph.edges.map((edge) => ({ ...edge, podId: "demo-pod" })) +); + +await db.collection("team_model").updateOne( + { podId: "demo-pod" }, + { $set: { podId: "demo-pod", graph, updatedAt: new Date().toISOString() } }, + { upsert: true } +); +``` + +Important: + +Use `materializePodGraph`, not `createDemoPodGraph`, for this rebuild. +`seedGraph` currently writes a demo graph and would recreate the stale mismatch. + +## Phase 6: Add Helpful Indexes + +Current `initMemory` creates the core indexes, but cleanup/reachability benefits +from these as well: + +```js +db.graph_nodes.createIndex({ podId: 1, id: 1 }, { unique: true }); +db.graph_edges.createIndex({ podId: 1, id: 1 }, { unique: true }); +db.graph_edges.createIndex({ podId: 1, source: 1 }); +db.graph_edges.createIndex({ podId: 1, target: 1 }); +db.graph_edges.createIndex({ podId: 1, kind: 1 }); +db.team_model.createIndex({ podId: 1 }, { unique: true }); +db.collisions.createIndex({ podId: 1, memorySignature: 1 }); +db.outcomes.createIndex({ podId: 1, interventionId: 1 }); +db.interventions.createIndex({ podId: 1, collisionId: 1 }); +``` + +Make index creation idempotent. + +## Validation After Cleanup + +Run these checks after `--apply`. + +### Atlas counts + +Confirm: + +- No `verify-pod-*` pod data remains. +- No `verify-graph-*` team models or graph rows remain. +- `demo-pod` still has meaningful observations, collisions, interventions, and + outcomes. + +### Public API + +```bash +curl https://165-22-129-249.sslip.io/health +curl https://165-22-129-249.sslip.io/api/pods +curl https://165-22-129-249.sslip.io/api/pods/demo-pod/graph +``` + +Expected: + +- Health is `ok`. +- `demo-pod` still exists. +- Graph has nodes, edges, metrics, loop, activity. +- Graph includes `learned_from` if accepted real outcomes remain. + +### Reachability + +After mirror rebuild, these should reflect the live graph, not old seed data: + +```bash +curl "https://165-22-129-249.sslip.io/api/pods/demo-pod/graph/reach/engineer%3Aram" +curl "https://165-22-129-249.sslip.io/api/pods/demo-pod/graph/reach/engineer%3Ayahya" +curl "https://165-22-129-249.sslip.io/api/pods/demo-pod/graph/reach/engineer%3Akarti" +``` + +Expected: + +- At least engineers present in the live graph should have reachable edges when + they have outbound graph edges. + +## Known Tooling Caveat + +One `pnpm exec` verification attempt was blocked by supply-chain policy: + +```text +prettier@3.9.0 was within the minimumReleaseAge cutoff +``` + +This did not indicate a MongoDB or graph failure. Direct MongoDB reads and the +existing local `backend/node_modules/.bin/tsx` binary were used instead. + +Do not run broad dependency cleanup during DB cleanup unless explicitly asked. + +## Recommended Cleanup Script Shape + +The script should: + +- Load `.env` with `dotenv`. +- Require `MONGODB_URI`. +- Print the database name. +- Refuse to run against a DB whose name is not `podman` unless `--force-db` is + passed. +- Default to `--dry-run`. +- Require `--apply` for deletes. +- Print every collection and matched count before deleting. +- Never print `MONGODB_URI`. +- Rebuild graph mirror only after delete phase succeeds. +- Print before/after counts. + +Suggested flags: + +```text +--dry-run +--apply +--skip-mirror-rebuild +--keep-pod demo-pod +--force-db +``` + +## Do Not Do + +- Do not run `seedGraph("demo-pod")` as the fix; that writes the demo graph. +- Do not delete all `outcomes`; accepted and dismissed outcomes are learning + signals. +- Do not delete all `team_model`; preserve or rebuild the `demo-pod` document. +- Do not print secrets or raw terminal/screenshot content. +- Do not rely on vector collections for the current demo; they are absent. +- Do not treat seeded graph mirror data as proof of current live learning. + +## Suggested Next Codex Prompt + +Use this prompt for the implementation pass: + +```text +Create a safe MongoDB cleanup script for PodMan. + +Read docs/handoff/README.md, docs/mongodb.md, docs/graph.md, +docs/continual-learning/spec.md, backend/src/graph/live.ts, +backend/src/graph/store.ts, backend/src/memory/store.ts, +backend/src/memory/policy.ts, backend/src/memory/vectors.ts, and +backend/src/agent/podman.ts before coding. + +Implement scripts/db-cleanup.mjs with --dry-run default and --apply required for +deletes. Keep demo-pod, remove verify/orphan pod data, remove obvious demo-pod +verification artifacts, and rebuild graph_nodes/graph_edges/team_model.graph for +demo-pod from materializePodGraph, not createDemoPodGraph. Do not print secrets. +Run dry-run first and show counts before applying. + +Do not implement broad agent-learning infrastructure in this task. The real +shipped RSI loop today is recallSimilar -> shouldIntervene/preferredAction -> +outcome -> future recall. Preserve accepted real outcome chains so learned_from +continues to render. +```