Skip to content
Mimir

Technical reference

This page documents Mimir’s technical contracts. For setup and daily use, start with Installation and the CLI reference.

Mimir is a self-hosted memory plane for one developer working across coding agents, repositories, and machines. It captures model traffic as sessions and makes that history available through HTTP, the CLI, and the private dashboard.

The deployment runs in the developer’s Cloudflare account. Mimir has no hosted backend, account system, multi-user tenancy, or shared memory service.

One Cloudflare Worker provides:

  • OpenAI Chat Completions and Anthropic Messages proxy routes
  • Session, search, configuration, and log APIs
  • Cloudflare Access-protected dashboard APIs
  • Static Vue dashboard assets

The Worker uses:

  • OpenRouter as the only model upstream
  • D1 for sessions, searchable exchange metadata, configuration, facets, and machine-token hashes
  • R2 for complete redacted request/response objects and session transcripts
  • Session Durable Objects for live session lifecycle: event collection, liveness, the live feed, and finalization
  • Cloudflare Access for deployed dashboard API authentication

The Go binary provides setup, login, diagnostics, local code indexing, and the primary command-line memory client. Worker HTTP APIs define the contract; the CLI and harness plugins are clients of those APIs.

Local code memory remains <repo>/.mimir/index.json. It is never uploaded to D1 or R2.

flowchart LR
    subgraph LOCAL[Developer machines]
        H[Agent harness]
        C[Go CLI]
        B[Dashboard browser]
        I[(Local code index)]
        H <--> C
        C --- I
    end

    subgraph CF[Developer's Cloudflare account]
        W[Worker proxy and canonical API]
        S[Session Durable Object]
        R[(R2 redacted objects)]
        D[(D1 metadata and state)]

        W -->|redacted exchanges| R
        W -->|search metadata and references| D
        W -.->|events and saved exchanges| S
        S -->|transcript manifest| R
        S -->|lifecycle state| D
    end

    O[OpenRouter]

    H <-->|redirected model stream| W
    H -.->|turns, heartbeats, ends| W
    C <-->|machine-token HTTP API| W
    B <-->|Access-protected dashboard API| W
    W <-->|upstream stream| O

Proxy, machine API, and CLI requests use a per-machine token supplied as either:

Authorization: Bearer <token>

or:

x-api-key: <token>

Each machine gets an independent random 32-byte token. D1 stores only its SHA-256 hash, label, creation time, and revocation state. The plaintext token is stored locally in ~/.mimir/token, or under $MIMIR_HOME, with restrictive permissions.

Before forwarding model requests, the Worker removes machine credentials and all x-mimir-* metadata, then authenticates upstream using the OPENROUTER_API_KEY Worker secret.

Deployed /dashboard/api/* and /dashboard/log-objects/* routes require a verified Cf-Access-Jwt-Assertion. Verification uses:

  • DASHBOARD_ACCESS_AUD
  • DASHBOARD_ACCESS_TEAM_DOMAIN

Localhost dashboard API requests may bypass Access for development. Static SPA assets are served separately from dashboard API authentication.

Setup can configure the Cloudflare Access application when supplied an API token. mimir access can create, repair, or attach an existing application later.

Method Route Behavior
POST /v1/chat/completions OpenAI-style Chat Completions proxy.
POST /v1/messages Anthropic-style Messages proxy.
GET /v1/models OpenRouter model-list pass-through.
GET /v1/key OpenRouter API-key metadata pass-through.
GET /v1/credits OpenRouter account-credit pass-through.
POST /v1/hermes/<installation-id>/chat/completions Installation-scoped Hermes Chat Completions proxy; capture is tagged hermes.
GET /v1/hermes/<installation-id>/{models,key,credits} Installation-scoped Hermes OpenRouter compatibility pass-through.
POST, GET /v1/hermes/{chat/completions,models,key,credits} Explicit legacy compatibility routes.

These routes do not implement the complete OpenAI or Anthropic API surfaces. General compatibility routes require a Mimir machine token. The listed Hermes routes additionally accept registered Hermes OpenRouter credentials, but those credentials cannot access session, log, dashboard, or configuration routes. Key and credit routes expose the deployment owner’s OpenRouter account metadata, matching Mimir’s personal single-owner trust model.

Method Route Behavior
GET /whoami Return deployment URL and session/exchange counts.
POST /machine/associate Associate the authenticated token with one installation when whoami.capabilities includes machine_identity_association.
GET /sessions List up to 100 recent sessions with optional filters.
GET /sessions/:id Return one session, exchanges, files, and errors.
GET /sessions/:id/status Return the derived capture summary and human receipt, with a link when Access is configured.
POST /sessions/:id/end Idempotently end the current active generation and optionally record its outcome. Also notifies the session object, which finalizes.
POST /sessions/:id/events Append a validated session event (turn, heartbeat, end) to the session object. The path session ID is authoritative; events may carry a harness title.
POST /sessions/:id/exchanges Validate, redact, and persist a bounded exchange reconstructed by a trusted harness integration.
GET /sessions/:id/live WebSocket live feed from the session object: snapshot plus event broadcast.
GET /sessions/:id/object-state Read the session object’s liveness projection and counters.
POST /sessions/:id/outcome Append an evidenced work-outcome event.
POST /sessions/:id/mark Deprecated legacy alias for setting an outcome.
POST /reconcile Reconcile bounded D1 capture rows against R2 and report orphans.
POST /search Search session metadata and excerpts.
GET /config Return defaults merged with persisted configuration.
PUT /config Validate and persist a partial configuration update.
POST /integrations/hermes/authorize Register a SHA-256 digest for a Hermes OpenRouter credential.
POST /integrations/harness-loads Record the source hash and receipt identity reported by a loaded managed integration.
GET /integrations/harness-loads Return harness-load reports for the authenticated machine so doctor can compare installed and active bytes.
GET /log/* Read one redacted R2 exchange object.

Session-list filters include repository, model, outcome, and date range.

Method Route Behavior
GET /dashboard/api/identity Return safe Cloudflare Access identity fields or the local-development identity.
GET /dashboard/api/bootstrap Return basic request/session totals.
GET /dashboard/api/log Cursor-paginated exchange metadata.
GET /dashboard/api/log/:id Return one exchange and its log-object URL.
GET /dashboard/log-objects/* Return one redacted R2 object.
GET /dashboard/api/sessions Filter and cursor-paginate root sessions.
GET /dashboard/api/sessions/:id Return session metadata, capture, files, aggregated errors, and outcome history.
GET /dashboard/api/sessions/:id/exchanges Filter, sort, and cursor-paginate the session subtree timeline.
GET /dashboard/api/sessions/:id/status Return the derived capture summary.
POST /dashboard/api/sessions/:id/outcome Append a user-sourced work-outcome event.
POST /dashboard/api/sessions/:id/mark Deprecated legacy alias for setting an outcome.
PATCH /dashboard/api/sessions/:id/title Set a normalized manual title of at most 200 characters.
GET /dashboard/api/devices List devices with status, activity, harnesses, and root-session counts.
PATCH /dashboard/api/devices/:id Change a device’s display name.
POST /dashboard/api/devices/:id/revoke Irreversibly revoke a device through the dashboard.
GET /dashboard/api/overview Return aggregate totals and top facets.
GET /dashboard/api/facets Return filter vocabulary, optionally scoped to one session subtree.

The dashboard uses /dashboard/* for browser pages. /sessions/* remains the machine API. GET /dashboard/api/facets returns up to 50 values for each supported facet and can scope results to a session subtree.

For a supported model request, the Worker:

  1. Authenticates the machine token.
  2. Reads and bounds the request body at 10 MiB.
  3. Parses optional Mimir session metadata.
  4. Reads capture configuration and lazily expires stale sessions.
  5. Replaces caller credentials with the OpenRouter Worker secret.
  6. Sends the request to OpenRouter.
  7. Returns the upstream response stream to the caller.
  8. Uses a second stream branch and waitUntil for persistence.
  9. Resolves the session, redacts the request, and records an accepted exchange in D1 while the archive stream is still being consumed.
  10. Bounds the captured response at 20 MiB.
  11. Parses ordinary JSON or reconstructs server-sent events.
  12. Redacts the response and derives searchable evidence.
  13. Writes the complete redacted v1 envelope to R2.
  14. Finalizes the exchange as saved and updates facets and session aggregates in D1.

x-mimir-request-kind accepts primary, title, summary, or compaction and defaults to primary for compatibility. The effective kind is persisted with the exchange and stripped before forwarding upstream. Title, summary, and compaction exchanges remain part of the exact session and contribute evidence and usage, but cannot initialize or overwrite session intent. The Worker also defensively recognizes known title-agent prompts that were mislabeled as primary.

Capture can be disabled globally or excluded by repository/model. The same policy applies to proxied and reported exchanges. Excluded proxy traffic is still forwarded, while reported ingestion returns an explicit skipped response; neither path creates exchange metadata or an R2 object for skipped traffic.

Mimir stores two exchange types:

  • Proxy exchanges contain the redacted request and response observed on a supported OpenRouter route.
  • Reported exchanges contain the fields exposed by a trusted harness integration. They may omit transport details, token use, timing, or tools.

Reported tool activity uses a normalized array with a tool name, input object, status, and optional output. The Worker validates and redacts it before deriving file and error facets.

Capture limits do not block the model response. If archive storage fails after streaming begins, the caller can still receive the response. Reconciliation can finalize accepted rows that have an R2 object or mark stale rows failed.

See Capture paths for harness-specific coverage.

Redaction runs before R2 storage and before searchable excerpts are generated. Built-in patterns cover common API-key, bearer-token, secret, token, and password forms. redact.patterns adds user-defined regular expressions.

Mimir derives:

  • Request and response excerpts, capped at 8,000 characters each
  • File-like paths, capped at 100 unique values per exchange
  • Error signatures, capped at 20 unique values per exchange
  • Model, provider, finish reason, token usage, latency, endpoint, harness, repository, and machine label

Derivation is regex-based. Redaction reduces accidental retention but cannot guarantee removal of every secret or sensitive value.

x-mimir-session is authoritative when present. It must be a 1-128 character identifier accepted by the Worker. A declared session can be resumed and reactivated later.

Without that header, Mimir groups traffic by exact repository/harness metadata and a configurable inactivity gap. The default gap is 15 minutes. Expiration is lazy and runs during relevant Worker requests rather than on a timer.

Optional metadata headers are:

  • x-mimir-session
  • x-mimir-repo
  • x-mimir-harness
  • x-mimir-git-ref
  • x-mimir-request-kind

Session intent comes from the first saved primary exchange with a user message. Titles use this precedence: manual > harness > generated > derived. Within one source, the newest title wins. Display falls back from title to intent to session ID.

Work outcomes are landed, discarded, abandoned, and unresolved. Outcomes are independent from capture state.

A Session Durable Object coordinates each live session. Events include turn, heartbeat, and end. A session finalizes after an end event, an explicit end request, or about ten minutes without activity. Finalization writes a transcript manifest to R2 and marks the D1 session inactive. Failed writes retry.

Liveness is derived from event age: active within 90 seconds, disconnected after 90 seconds, and finalized after the final write. New activity with the same ID reopens the session.

Each exchange has capture status accepted, saved, or failed. Session APIs summarize those rows as empty, pending, saved, failed, or partial. Status responses include a compact receipt and, when Access is configured, a credential-free dashboard link.

Outcome changes append immutable events. Session rows cache the latest event for listing and filtering.

Remote search uses SQL substring matching over session intent, exchange excerpts, normalized files, and error signatures. It supports repository and outcome filters, orders results by recency, and applies an approximate response budget. It is not semantic or vector search and does not read complete R2 objects.

CLI search federates remote results with local code recall when a usable .mimir/index.json exists in the current repository.

Supported configuration keys are:

Key Purpose
save.enabled Enable or disable persistence.
save.exclude_repos Repository exclusion patterns.
save.exclude_models Model exclusion patterns.
redact.patterns Additional redaction expressions.
session.gap_minutes Heuristic inactivity gap.

Configuration is stored in D1 and takes effect without redeployment.

Each saved exchange is one redacted JSON object under:

log/YYYY/MM/DD/<ulid>.json

New writes use the versioned v1 envelope:

{
"schema_version": 1,
"exchange_id": "<ulid>",
"session_id": "<resolved-session-id>",
"declared_session_id": "<header-value-or-null>",
"captured_at": "<rfc3339>",
"endpoint": "chat",
"request": {},
"response": { "format": "json", "body": {} },
"metadata": {
"repo": "<repo-or-null>",
"harness": "<harness-or-null>",
"git_ref": "<git-ref-or-null>",
"model": "<model>",
"provider": "<provider-or-null>",
"finish_reason": "<reason-or-null>"
},
"usage": { "input_tokens": 0, "output_tokens": 0 },
"latency_ms": 0,
"redaction": { "version": 1 }
}

The resolved session_id, not only the caller-declared header, is stored in the object. Reconstructed streams use response.format: "reconstructed_sse" with content and events instead of body. Request and response are redacted before this write; repository, harness, and Git metadata are intentionally stored as searchable identifiers. D1 keeps the envelope version, R2 key, and byte count beside searchable metadata.

D1 first records an accepted exchange, then R2 receives the envelope, then D1 finalizes it as saved and updates session aggregates. A bounded reconcile pass checks accepted and saved rows with R2 HEAD requests. Accepted rows with an object are finalized idempotently. Accepted rows without one stay pending for 15 minutes; after that they can never finish, so reconcile marks them failed with r2_object_missing and reports them as swept instead of leaving sessions permanently pending. Saved rows missing their object become failed with r2_object_missing, and affected session aggregates are rebuilt from saved rows. Schema-v1 file and error facets are retained per exchange, so that rebuild excludes facets belonging only to missing objects. Sessions that contain legacy v0 exchanges retain their existing aggregate facets because v0 did not record exchange-level provenance. A separate bounded R2 listing reports keys absent from D1 as orphans; reconcile does not import or delete them. Independent D1 and R2 cursors and a bounded limit make repeated runs safe. Each pass scans at most 100 D1 rows and 100 R2 keys to stay within Worker binding-operation limits.

Legacy v0 objects are the existing unversioned shape with top-level id, ts, session, request, response, usage, and meta. Migration marks their D1 rows saved with schema_version = 0, accepted_at = ts, and saved_at = ts. Objects remain readable as stored and are not rewritten during migration or reconciliation.

The migration sequence defines:

  • sessions: identity, title provenance, timing, boundary, lifecycle, context, usage, and latest outcome projection
  • exchanges: searchable metadata, request kind, title candidates, capture lifecycle, usage, latency, and R2 reference
  • exchange_files: schema-v1 file facets with exchange-level provenance
  • exchange_errors: schema-v1 error signatures with exchange-level provenance
  • session_outcome_events: immutable outcome, source, reason, and timestamp history
  • session_files: normalized file facets
  • session_errors: normalized error facets
  • config: persisted deployment configuration
  • machines: stable installation identities, editable device names, platform metadata, activity, and revocation state
  • access_tokens: machine-token hashes and lifecycle fields
  • hermes_credentials: OpenRouter credential hashes authorized only for the Hermes proxy surface
  • harness_loads: loaded integration source hashes and receipt identity used by diagnostics

D1 stores searchable state. R2 stores the complete redacted archive.

Each installation has a stable 32-character ID. Machine tokens, sessions, harness-load reports, and scoped Hermes credentials use this ID. The dashboard shows an editable device name separately.

POST /machine/associate binds an unassociated token to one installation. Repeated association with the same ID is safe; moving the token to another ID returns 409. Revoking a device revokes its tokens and scoped Hermes credentials but preserves historical sessions and exchanges.

mimir index writes <repo>/.mimir/index.json atomically. It indexes Git working files for selected programming-language extensions and records hashes, regex-derived symbols, and dependencies. mimir recall performs deterministic text ranking within an approximate character budget.

The local index is optional and independent from remote session storage.

The CLI calls the Worker API for search, session inspection, outcomes, explicit ending, configuration, and diagnostics. mimir session status polls briefly while capture is pending and does not report a pending read as saved.

Harness integrations capture data and report lifecycle events. Search and control still use the Worker API; Mimir does not run a local protocol server.

mimir setup materializes the packaged Worker and dashboard, installs Worker dependencies, authenticates Wrangler, creates or reuses D1 and R2, applies migrations, stores the OpenRouter secret, deploys the Worker, and saves the local connection.

mimir login connects another machine to an existing deployment. It discovers the deployment through Wrangler, registers a machine token, and saves the same connection manifest used by setup.

mimir install manages opted-in harness integrations. It changes only files recorded in install-receipt.json and preserves modified or conflicting files. See the harness setup pages for activation and provider-specific behavior.

mimir deploy ships packaged Worker and dashboard changes. Development checkouts require an explicit --worker-dir override.

Cloudflare Access protects /dashboard/auth, /dashboard/api/*, and /dashboard/log-objects/*. Machine APIs remain outside Access and use machine tokens. See Deployment, Operations, and Dashboard Access for procedures.

Wrangler observability is enabled for Worker logs and traces. Logs use full head sampling and traces use 1% head sampling. This telemetry stays in the developer’s Cloudflare account.

  • Mimir-hosted SaaS infrastructure
  • Multi-user tenancy, teams, roles, or account management
  • Custom browser passwords or browser bearer-token storage
  • Git-backed session synchronization or session Markdown
  • Uploading local code indexes to D1
  • Vector search, embeddings, or a semantic search service
  • Direct model upstreams other than OpenRouter
  • A general analytics suite
  • Automatic retention or deletion workflows
  • Automatic outcome inference services

The exact local validation commands for these surfaces are:

Terminal window
npm --prefix worker test -- src/config.test.ts src/session-titles.test.ts
bun test plugins/pi/ plugins/opencode/
python -m unittest discover -s plugins/hermes -p "test_*.py"
go test ./internal/harness/hooks ./internal/install ./internal/doctor
npm --prefix worker run typecheck

Deployment verification is separate and must not invoke /v1/chat/completions, /v1/messages, or another paid model route. Use /whoami for connectivity and direct session APIs for lifecycle checks.