About DavidHLPL · RHINE LAB RSS ↗
FILE X-1034 / AI tools

Hindsight Unified Memory Access: FastMCP Bridge and Dynamic Multi-Project Routing

ENGINEERING NOTES

Published / DavidHLPL

This page records the bridge implementation and configuration for connecting the Vectorize Hindsight memory engine to the terminal agents OMP (Oh-My-Pi) and Codex CLI. The central improvement is dynamic workspace routing with multi-project isolation plus a FastMCP stdio bridge that preserves session state.

Scope and review (2026-09-08): This preserves the Hindsight v0.9.1 practice reported in the 2026-08-17 snapshot. OMP, Codex, Node.js, and bridge versions are not fully pinned. The code is historical; that revision performed text/protocol review only, without restarting services or validating current client configuration compatibility.


1. Adaptive Isolation Across Multiple Projects

In multi-project development, hard-coding one global Bank (for example, always using ulticode) causes memories, entities, and rules from different repositories to contaminate one another.

The Git-root probe derives Bank IDs from repository directory names, such as ulticode, resicache, and davidhlp_github_io. This is logical application partitioning, not physical database/process isolation or access control. The code uses only basename and character replacement: repositories with the same basename in different paths, and a.b versus a_b, can collide. Set distinct HINDSIGHT_BANK_ID values explicitly when needed and check the Bank OMP actually uses.

Project workspace (/path/to/project-a)  ──> Git root detection ──> Bank: project-a ──> Hindsight /mcp/project-a/
Project workspace (/path/to/project-b)  ──> Git root detection ──> Bank: project-b ──> Hindsight /mcp/project-b/

2. Historical FastMCP Bridge Example (~/.hindsight/hindsight-bridge.mjs)

Codex CLI and some Agent child processes communicate over JSON-RPC through stdio, while Hindsight v0.9.1 exposes an HTTP Streamable FastMCP endpoint. A small local Node.js bridge is therefore needed to convert the protocol and preserve the Session ID:

#!/usr/bin/env node
/**
 * Dynamic Multi-Project FastMCP Bridge for Hindsight
 * Resolves the Git repository root to derive the project Bank ID accurately, even from subdirectories.
 */
import readline from "node:readline";
import path from "node:path";
import { execSync } from "node:child_process";
import fs from "node:fs";

function findProjectRoot() {
	try {
		const gitRoot = execSync("git rev-parse --show-toplevel", {
			stdio: ["pipe", "pipe", "ignore"],
			encoding: "utf-8"
		}).trim();
		if (gitRoot && fs.existsSync(gitRoot)) return gitRoot;
	} catch {}

	let curr = process.cwd();
	while (curr && curr !== path.dirname(curr)) {
		if (fs.existsSync(path.join(curr, ".git"))) return curr;
		curr = path.dirname(curr);
	}
	return process.cwd();
}

function getDynamicBankId() {
	if (process.env.HINDSIGHT_BANK_ID?.trim()) {
		return process.env.HINDSIGHT_BANK_ID.trim();
	}
	const root = findProjectRoot();
	const baseName = path.basename(root);
	if (!baseName || baseName === "/" || baseName === "." || baseName === "root") {
		return "default";
	}
	return baseName.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
}

const BANK_ID = getDynamicBankId();
const BASE_URL = process.env.HINDSIGHT_API_BASE_URL || "http://127.0.0.1:8888";
const TARGET_URL = `${BASE_URL.replace(/\/+$/, "")}/mcp/${BANK_ID}/`;

let sessionId = null;

const rl = readline.createInterface({
	input: process.stdin,
	output: process.stdout,
	terminal: false
});

rl.on("line", async (line) => {
	const trimmed = line.trim();
	if (!trimmed) return;
	try {
		const payload = JSON.parse(trimmed);
		const headers = {
			"Content-Type": "application/json",
			Accept: "application/json, text/event-stream"
		};
		if (sessionId) {
			headers["mcp-session-id"] = sessionId;
		}
		const res = await fetch(TARGET_URL, {
			method: "POST",
			headers,
			body: JSON.stringify(payload)
		});

		const returnedSessionId = res.headers.get("mcp-session-id");
		if (returnedSessionId) {
			sessionId = returnedSessionId;
		}

		const contentType = res.headers.get("content-type") || "";
		if (contentType.includes("text/event-stream")) {
			const text = await res.text();
			for (const l of text.split("\n")) {
				if (l.startsWith("data:")) {
					const data = l.slice(5).trim();
					if (data && data !== "[DONE]") {
						process.stdout.write(data + "\n");
					}
				}
			}
		} else {
			const json = await res.json();
			process.stdout.write(JSON.stringify(json) + "\n");
		}
	} catch (err) {
		try {
			const parsed = JSON.parse(trimmed);
			if (parsed.id !== undefined) {
				process.stdout.write(
					JSON.stringify({
						jsonrpc: "2.0",
						id: parsed.id,
						error: {
							code: -32603,
							message: `Hindsight Bridge Error: ${err.message}`
						}
					}) + "\n"
				);
			}
		} catch {}
	}
});

Protocol gaps in the example

The example retains Session ID forwarding and Git-root detection, but is not a complete generic MCP proxy. It waits for the entire SSE response before forwarding, does not handle full SSE event boundaries, and lacks explicit handling of empty notification responses, concurrent handshakes, reinitialization after session expiry, and the negotiated protocol-version header. Under the MCP 2025-06-18 transport specification, successful notifications can return HTTP 202 without a body; Streamable HTTP already supports SSE; subsequent Session ID forwarding is required when the server assigned one. Validate these cases for the actual clients before deployment: one successful tools/list is not general compatibility proof.

3. Agent Client Configuration

Path configuration rule: Node processes and environments do not automatically expand ~ in args paths. Use an absolute path (such as /home/user/) or inject the path through an environment variable.

1. OMP configuration (~/.omp/agent/config.yml and ~/.omp/agent/mcp.json)

~/.omp/agent/config.yml:

memory:
  backend: hindsight
  hindsight:
    apiUrl: http://127.0.0.1:8888
    scoping: per-project # isolate Banks by project automatically
    autoRecall: true     # retrieve and inject memory at session start
    autoRetain: true     # extract and retain memory at session end
    mentalModelsEnabled: true # enable mental models
    mentalModelAutoSeed: true # generate seed mental models automatically
    recallBudget: mid
    autolearn:
      enabled: true
      autoContinue: true

~/.omp/agent/mcp.json (replace <USER_HOME> with the actual absolute path):

{
  "mcpServers": {
    "hindsight": {
      "type": "stdio",
      "command": "node",
      "args": ["<USER_HOME>/.hindsight/hindsight-bridge.mjs"]
    }
  }
}

2. Codex configuration (~/.codex/config.toml)

[mcp_servers.hindsight]
command = "node"
args = ["<USER_HOME>/.hindsight/hindsight-bridge.mjs"]

[mcp_servers.hindsight.env]
HINDSIGHT_API_BASE_URL = "http://127.0.0.1:8888"
# Do not hard-code HINDSIGHT_BANK_ID; the bridge routes by Git root automatically.

4. Verification Results and Observed Metrics

The following retells one write/recall record from the 2026-08-17 raw, not a fresh execution. Repetitions, load, full versions, and timing methods are not supplied; scores and 0.059s are not benchmarks, and the test text is not a current project configuration fact:

[Write stage (Codex MCP Bridge -> GPU Fact Extraction)]
Written: "Backend uses Java 17 and Spring Boot 3.2.5 with three owner services: auth 9101, admin 9102, app 9103."
Extraction result: 3 fact entities were extracted successfully and indexed with BAAI/bge-m3.

[Query stage (OMP / Codex Recall)]
Query: "What port does the auth service use?"
Result: matched "Backend has three owner services: auth (9101), admin (9102), and app (9103)"
Metrics: reranker score 0.965, semantic score 0.668, retrieval latency 0.059s.

5. Evidence and Uncertainty

  • Source report: The pinned raw hindsight-local-deployment-and-agent-integration reports handshake, tool discovery, and one successful retrieval. It does not prove a complete state machine or compatibility with all clients.
  • Synthesis: Bridge code, dual-client configurations, and observed metrics form an Entity integration record.
  • Statically confirmable: Without a Git root or explicit HINDSIGHT_BANK_ID, code uses CWD and derives the Bank from its basename. Only an empty basename, /, ., or root returns default; non-Git subdirectory drift remains possible.
  • Unconfirmed: Current agreement between OMP per-project naming and this algorithm, plus model, permission, and complete retain/recall behavior, requires runtime checks.

Research notes

  1. Original document: src/content/note/en/hindsight-omp-codex-integration.md
  2. Source revision: f49953fb0d4bd52a39bf81167b0c4b7dd7d78a64
  3. Evidence status: active

Original source ↗