Understanding the AgentMemory Real-Time Viewer Architecture

The AgentMemory real-time viewer is a lightweight HTTP front-end that combines a static file server, dynamic Content Security Policy injection, and a reverse proxy into a single Node.js process.

The rohitg00/agentmemory repository provides you with a real-time viewer that runs alongside the core AgentMemory service. This architecture eliminates cross-origin issues by exposing the full REST API through the same host and port as the web interface, making client-side development seamless.

Core Components of the Viewer

The viewer logic is completely isolated under src/viewer, allowing you to start or omit it without impacting the backend service. It consists of three tightly coupled subsystems.

HTTP Server Layer (src/viewer/server.ts)

The entry point creates a raw Node.js http server using createServer. This server handles three distinct responsibilities:

  • Static UI delivery: Serves the viewer interface on the routes /, /viewer, and /agentmemory/viewer
  • CORS handling: Applies a configurable allow-list via the VIEWER_ALLOWED_ORIGINS environment variable
  • Request routing: Proxies all other requests to the AgentMemory REST API

By default, the viewer assumes the REST API runs on viewerPort - 2. If you start the viewer on port 3111, it automatically proxies API calls to port 3109.

HTML Rendering and Security (src/viewer/document.ts)

When you request the viewer UI, the server does not serve a raw static file. Instead, loadViewerTemplate reads viewer/index.html and injects dynamic security tokens:

  1. Per-request nonce: Generated via createViewerNonce to prevent XSS attacks
  2. Version metadata: Injected from VERSION constant in src/version.ts
  3. CSP header: Built by buildViewerCsp to restrict script execution to resources matching the generated nonce

This ensures that even if the HTML template contains placeholders, only scripts with the correct nonce can execute during that specific request.

API Proxy Layer (src/viewer/server.ts)

The proxyToRestApi function acts as a thin reverse proxy with these specific behaviors:

  • Path rewriting: Prepends /agentmemory/... to incoming paths before forwarding
  • Method preservation: Forwards the original HTTP method and body for POST, PUT, DELETE, and PATCH requests
  • Header passthrough: Carries over the Authorization bearer token and other headers
  • Timeout protection: Uses an AbortController with a 10-second timeout to prevent hanging upstream calls
  • Response streaming: Returns the upstream status, body, and content-type while preserving CORS headers

Bootstrap and Configuration

You initialize the viewer using the startViewerServer function exported from src/viewer/server.ts. The signature accepts the AgentMemory KV store, SDK instance, and optional authentication parameters.

import { startViewerServer } from "./src/viewer/server.js";

// Start viewer on 3111, REST API expected on 3109
const viewer = startViewerServer(
  3111,              // viewer port
  kvInstance,        // AgentMemory KV store
  sdkInstance,       // iii-SDK instance
  process.env.VIEWER_SECRET, // optional API auth secret
);

Once running, you can access the UI and proxy API calls through the same origin:

// Fetch the viewer HTML (with injected nonce)
const html = await fetch("http://localhost:3111/").then(r => r.text());

// Proxy a request to the underlying REST API
await fetch("http://localhost:3111/agentmemory/remember", {
  method: "POST",
  headers: { 
    "Content-Type": "application/json", 
    "Authorization": "Bearer SECRET" 
  },
  body: JSON.stringify({ content: "Important memory" }),
});

Security Implementation Details

The security model relies on nonce-based CSP rather than complex authentication flows. The buildViewerCsp function, referenced through utilities in src/auth.js, constructs headers that tie the injected nonce to allowed script sources. This approach lets the viewer safely load dynamic scripts while mitigating injection attacks.

The optional VIEWER_SECRET parameter enables basic bearer token validation. When provided, the proxy layer validates the Authorization header before forwarding requests to the REST API.

Summary

  • Single-process architecture: The viewer runs as one Node.js process alongside AgentMemory, not a separate service
  • Three-layer design: HTTP server (server.ts), document generator (document.ts), and proxy handler (proxyToRestApi)
  • Zero CORS configuration: By proxying API calls through the same port, you eliminate cross-origin browser restrictions
  • Request-scoped security: Each page load generates a unique nonce via createViewerNonce and enforces it through buildViewerCsp
  • Configurable ports: Default REST port is automatically calculated as viewerPort - 2, customizable via the restPort parameter

Frequently Asked Questions

How does the real-time viewer determine the REST API port?

By default, the viewer assumes the AgentMemory REST API runs exactly two ports below the viewer port. If you start the viewer on port 3111, it proxies API requests to 3109. You can override this by passing a custom restPort argument to startViewerServer.

Is the Content Security Policy static or dynamic?

The CSP is dynamic and request-scoped. For every incoming request to the viewer routes, document.ts generates a fresh nonce via createViewerNonce and constructs a new CSP header via buildViewerCsp that only permits scripts matching that specific nonce.

Can I disable the viewer and run only the core AgentMemory service?

Yes. All viewer code lives under src/viewer and is completely isolated from core memory functions. You can omit calling startViewerServer entirely without affecting the underlying remember, recall, or forget operations.

What happens if the REST API takes longer than 10 seconds to respond?

The proxyToRestApi implementation uses an AbortController with a 10-second timeout. If the upstream AgentMemory API does not respond within this window, the proxy aborts the request and returns an error to the client, preventing the viewer from hanging indefinitely.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →