fix: make MongoDB mandatory, fail loud instead of degrading

Reverse the best-effort error swallowing. MongoDB is core to PodMan's
continual-learning story and must always be used, so a broken memory
layer must surface immediately rather than silently masquerade as
working (which is how observations stayed at 0 unnoticed).

- agent verifies Mongo via initMemory() at boot; bad creds / unreachable
  Atlas now fail loudly before joining the room, not mid-demo
- server exits on Mongo init failure instead of warning and limping on
- getGitStates and onScreenFrame no longer swallow Mongo errors
- memory persist() logs the failure and rethrows instead of warning

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AaCFWMkYQmTcuPsxaaACft
This commit is contained in:
Ramis
2026-06-27 20:34:27 -07:00
parent 6a6fbca9e0
commit 745f0032f7
5 changed files with 36 additions and 37 deletions
+5
View File
@@ -14,6 +14,7 @@ import sharp from 'sharp';
import { AccessToken } from 'livekit-server-sdk'; import { AccessToken } from 'livekit-server-sdk';
import { env } from './env.js'; import { env } from './env.js';
import { PodMan } from './agent/podman.js'; import { PodMan } from './agent/podman.js';
import { initMemory } from './memory/db.js';
const POD_ROOM = process.env.POD_ROOM ?? 'demo-pod'; const POD_ROOM = process.env.POD_ROOM ?? 'demo-pod';
const HERMES_IDENTITY = 'podman-hermes'; const HERMES_IDENTITY = 'podman-hermes';
@@ -30,6 +31,10 @@ async function agentToken(room: string): Promise<string> {
} }
async function main() { async function main() {
// MongoDB is mandatory. Verify the connection before joining the room so bad
// creds / unreachable Atlas fail loudly at boot, not silently mid-demo.
await initMemory();
const room = new Room(); const room = new Room();
const podman = new PodMan(room, POD_ROOM); const podman = new PodMan(room, POD_ROOM);
await room.connect(env.LIVEKIT_URL, await agentToken(POD_ROOM), { await room.connect(env.LIVEKIT_URL, await agentToken(POD_ROOM), {
+13 -19
View File
@@ -43,27 +43,21 @@ export class PodMan {
} }
async onScreenFrame(engineerId: string, jpeg: Buffer): Promise<void> { async onScreenFrame(engineerId: string, jpeg: Buffer): Promise<void> {
// Whole-frame guard: a Gemini/GitHub/Mongo failure on one frame must degrade const ctx = await analyzeFrame(engineerId, this.podId, jpeg);
// gracefully, never crash the long-running live agent loop. this.contexts.set(engineerId, ctx);
try { await recordObservation(ctx);
const ctx = await analyzeFrame(engineerId, this.podId, jpeg);
this.contexts.set(engineerId, ctx);
await recordObservation(ctx);
// Fuse git ground truth: engineer_states written by scripts/podman-agent.mjs. // Fuse git ground truth: engineer_states written by scripts/podman-agent.mjs.
// Keyed by name (matches --name arg), same as LiveKit participant identity. // Keyed by name (matches --name arg), same as LiveKit participant identity.
const gitStates = await getGitStates(this.podId); const gitStates = await getGitStates(this.podId);
for (const [id, c] of this.contexts) { for (const [id, c] of this.contexts) {
const git = gitStates.get(id); const git = gitStates.get(id);
if (git && git.changedFiles.length > 0) c.hasUnpushedChanges = true; if (git && git.changedFiles.length > 0) c.hasUnpushedChanges = true;
}
const github = await getGithubState(); // cached
const collisions = detectCollisions([...this.contexts.values()], github);
for (const collision of collisions) await this.handle(collision);
} catch (err) {
console.warn(`[agent] frame from ${engineerId} skipped: ${(err as Error).message}`);
} }
const github = await getGithubState(); // cached
const collisions = detectCollisions([...this.contexts.values()], github);
for (const collision of collisions) await this.handle(collision);
} }
private async handle(collision: Collision): Promise<void> { private async handle(collision: Collision): Promise<void> {
+8 -14
View File
@@ -69,21 +69,15 @@ export async function getGitStates(podId: string): Promise<Map<string, GitState>
recentCommit?: string | null; recentCommit?: string | null;
gitUpdatedAt?: Date; gitUpdatedAt?: Date;
}>('engineer_states'); }>('engineer_states');
const docs = await col.find({ podId }).toArray();
const map = new Map<string, GitState>(); const map = new Map<string, GitState>();
try { for (const doc of docs) {
const docs = await col.find({ podId }).toArray(); map.set(doc.name, {
for (const doc of docs) { changedFiles: doc.changedFiles ?? [],
map.set(doc.name, { branch: doc.branch ?? null,
changedFiles: doc.changedFiles ?? [], recentCommit: doc.recentCommit ?? null,
branch: doc.branch ?? null, gitUpdatedAt: doc.gitUpdatedAt ?? null,
recentCommit: doc.recentCommit ?? null, });
gitUpdatedAt: doc.gitUpdatedAt ?? null,
});
}
} catch (err) {
// Best-effort: a Mongo hiccup degrades git fusion, it must not crash the
// live agent loop. Detection falls back to vision-only signals.
console.warn(`[memory] getGitStates failed: ${(err as Error).message}`);
} }
return map; return map;
} }
+5 -3
View File
@@ -10,14 +10,16 @@ import { enrichCollisionMemory } from './vectors.js';
/** /**
* Continual-learning memory: persist observations, collisions, interventions, * Continual-learning memory: persist observations, collisions, interventions,
* and outcomes to MongoDB so later sessions get sharper. Writes are best-effort * and outcomes to MongoDB so later sessions get sharper. MongoDB is mandatory —
* — a Mongo hiccup logs a warning rather than crashing the agent/server. * a failed write is surfaced loudly and rethrown, never silently swallowed, so
* a broken memory layer can never masquerade as a working one.
*/ */
async function persist(name: string, fn: () => Promise<unknown>): Promise<void> { async function persist(name: string, fn: () => Promise<unknown>): Promise<void> {
try { try {
await fn(); await fn();
} catch (err) { } catch (err) {
console.warn(`[memory] ${name} persist failed: ${(err as Error).message}`); console.error(`[memory] ${name} persist FAILED: ${(err as Error).message}`);
throw err;
} }
} }
+5 -1
View File
@@ -177,7 +177,11 @@ http.listen(env.PORT, '0.0.0.0', () => {
console.log(`[server] :${env.PORT}`); console.log(`[server] :${env.PORT}`);
initMemory() initMemory()
.then(() => seedDefaultPods()) .then(() => seedDefaultPods())
.catch((e) => console.warn(`[memory] init failed: ${(e as Error).message}`)); .catch((e) => {
// MongoDB is mandatory — do not run a half-dead API against a broken DB.
console.error(`[memory] init FAILED, exiting: ${(e as Error).message}`);
process.exit(1);
});
}); });
let shuttingDown = false; let shuttingDown = false;