Technical reference
This page documents Mimir’s technical contracts. For setup and daily use, start with Installation and the CLI reference.
1. Purpose
Section titled “1. Purpose”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.
2. System boundary
Section titled “2. System boundary”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
3. Authentication
Section titled “3. Authentication”3.1 Machine requests
Section titled “3.1 Machine requests”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.
3.2 Dashboard requests
Section titled “3.2 Dashboard requests”Deployed /dashboard/api/* and /dashboard/log-objects/* routes require a
verified Cf-Access-Jwt-Assertion. Verification uses:
DASHBOARD_ACCESS_AUDDASHBOARD_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.
4. HTTP API
Section titled “4. HTTP API”4.1 Proxy
Section titled “4.1 Proxy”| 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.
4.2 Machine API
Section titled “4.2 Machine API”| 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.
4.3 Dashboard API
Section titled “4.3 Dashboard API”| 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.
5. Capture lifecycle
Section titled “5. Capture lifecycle”For a supported model request, the Worker:
- Authenticates the machine token.
- Reads and bounds the request body at 10 MiB.
- Parses optional Mimir session metadata.
- Reads capture configuration and lazily expires stale sessions.
- Replaces caller credentials with the OpenRouter Worker secret.
- Sends the request to OpenRouter.
- Returns the upstream response stream to the caller.
- Uses a second stream branch and
waitUntilfor persistence. - Resolves the session, redacts the request, and records an accepted exchange in D1 while the archive stream is still being consumed.
- Bounds the captured response at 20 MiB.
- Parses ordinary JSON or reconstructs server-sent events.
- Redacts the response and derives searchable evidence.
- Writes the complete redacted v1 envelope to R2.
- 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.
5.1 Capture provenance
Section titled “5.1 Capture provenance”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.
6. Redaction and evidence
Section titled “6. Redaction and evidence”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.
7. Sessions
Section titled “7. Sessions”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-sessionx-mimir-repox-mimir-harnessx-mimir-git-refx-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.
7.1 Session objects
Section titled “7.1 Session objects”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.
8. Search and configuration
Section titled “8. Search and configuration”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.
9. Storage model
Section titled “9. Storage model”9.1 R2
Section titled “9.1 R2”Each saved exchange is one redacted JSON object under:
log/YYYY/MM/DD/<ulid>.jsonNew 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.
9.2 D1
Section titled “9.2 D1”The migration sequence defines:
sessions: identity, title provenance, timing, boundary, lifecycle, context, usage, and latest outcome projectionexchanges: searchable metadata, request kind, title candidates, capture lifecycle, usage, latency, and R2 referenceexchange_files: schema-v1 file facets with exchange-level provenanceexchange_errors: schema-v1 error signatures with exchange-level provenancesession_outcome_events: immutable outcome, source, reason, and timestamp historysession_files: normalized file facetssession_errors: normalized error facetsconfig: persisted deployment configurationmachines: stable installation identities, editable device names, platform metadata, activity, and revocation stateaccess_tokens: machine-token hashes and lifecycle fieldshermes_credentials: OpenRouter credential hashes authorized only for the Hermes proxy surfaceharness_loads: loaded integration source hashes and receipt identity used by diagnostics
D1 stores searchable state. R2 stores the complete redacted archive.
9.3 Device identity and association
Section titled “9.3 Device identity and association”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.
9.4 Local index
Section titled “9.4 Local index”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.
10. Client and harness access
Section titled “10. Client and harness access”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.
11. Setup and login
Section titled “11. Setup and login”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.
12. Observability
Section titled “12. Observability”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.
13. Non-goals
Section titled “13. Non-goals”- 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
14. Validation
Section titled “14. Validation”The exact local validation commands for these surfaces are:
npm --prefix worker test -- src/config.test.ts src/session-titles.test.tsbun test plugins/pi/ plugins/opencode/python -m unittest discover -s plugins/hermes -p "test_*.py"go test ./internal/harness/hooks ./internal/install ./internal/doctornpm --prefix worker run typecheckDeployment 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.
