Interfaces & ops
MCP tools & deployment
The Open Brain is a single self-contained .NET 10 service that exposes the team's entire
semantic code index to AI agents over the Model Context Protocol at
https://brain.motlogs.com, backed by Qdrant. This page catalogs the MCP tools agents call — 46
declared (McpService.cs's FullToolList), of which
25 are advertised today — the response shapes they return, and the
deployment, Qdrant, re-ingest, and enforcement machinery that keeps the service running.
Transport — streamable HTTP + stdio
The primary transport is streamable HTTP — POST /mcp, JSON-RPC 2.0, protocol version
2024-11-05 (McpService.cs:26), wired at Program.cs:1586;
GET /mcp (Program.cs:1631) is a discovery/health card. JSON-RPC methods are dispatched
(McpService.cs:197-203): initialize, ping, tools/list,
tools/call, resources/list, resources/read; a null response
(notifications) returns HTTP 204. Bearer auth (Program.cs:1586-1616) resolves a presented
Authorization: Bearer <token> against (a) the shared Mcp:BearerToken → identity
"shared", or (b) a registered per-developer token via TokenRegistry → real {developerId, team};
a registered token always wins over the shared token, a mismatch returns 401, the compare is constant-time
(HttpAccessGate.ConstantTimeEquals, Services/HttpAccessGate.cs:132-139), and with no
shared token configured the service is public but registered tokens are still honoured. Note that
/mcp does not waive the bearer for a loopback/tailnet peer the way /api/*
does (see the access gate below) — every caller, on-host or remote, presents the same header. stdio mode
(Program.cs:40, --mcp) binds nothing in Kestrel, routes logging to stderr so stdout
is a clean JSON-RPC channel, runs RunStdioLoopAsync() (McpService.cs:131), then
exits.
One server satisfies both the shared remote brain (Claude Code, Antigravity, any MCP client) and a locally-piped agent, with per-developer identity flowing into telemetry and attribution either way.
Connecting — the recipe
Add the server as an http-type MCP entry (Claude Code: ~/.claude.json's
mcpServers, or claude mcp add). Remote, off-host:
{
"mot-ledger": {
"type": "http",
"url": "https://brain.motlogs.com/mcp",
"headers": { "Authorization": "Bearer <your token>" }
}
}
On-host — a session running directly on the Hetzner box, or tunnelled to it — talks to the service
on its loopback port, skipping nginx and TLS entirely: http://localhost:5005/mcp
(or 127.0.0.1), same JSON shape, same bearer header (the port and lack of TLS are the only
difference — the bearer requirement above still applies even to loopback). GET /mcp without a
body returns the discovery/health card (server name, transport, protocol version) and needs no auth, so it is
the quickest way to confirm the service is up before wiring the full config.
MCP resources
Two read-only resources are read at session start (McpService.cs:1128-1173):
ledger://manifest (what's indexed and freshness per repo) and ledger://global-arch
(the GLOBAL_ARCH.md system spec).
An agent orients before its first query — it knows which repos are fresh and has the system-wide spec in hand, which prevents confident-but-wrong answers later.
The root argument — searching your own worktree
The main index mirrors main. query_ledger, read_span and
symbol_lookup each accept an optional root: the absolute path of a registered
checkout overlay (a worktree/workspace, e.g.
/root/code/motmaster-openbrain/.claude/worktrees/<id>, or a ws-*/wt-*
checkout). When set and registered, a file that differs between that checkout and the indexed mirror is served
from the checkout instead — its uncommitted or unmerged edits become findable within minutes — and overlay
hits are marked ⊕ from your checkout. symbol_lookup's symbol projection is still
built from the main index only, so a registered root there gets a note that overlay symbols aren't in that
projection yet and matches keep coming from the main index. find_definition /
find_references / find_implementations take the same idea via root too,
but there it names a known repo id or any registered worktree path, defaulting to
Precise:DefaultRoot — not the overlay mechanism, the precise lane's own workspace resolution.
A root must be registered first: POST /api/admin/overlay/register {root}
(loopback-only). An unregistered or unaccepted root, or the overlay being disabled, is ignored with a one-line
hint rather than an error — a missing registration never turns a good query into a failure. A PostToolUse hook
(motmaster-infra/claude-hooks/overlay-refresh.sh) fires this automatically after an Edit/Write/
MultiEdit inside an accepted checkout shape, debounced to one refresh per root per 30s — see
Governance layer for the hook wiring.
Without root a session gets main's version of a file it has already changed and
may plan against code that no longer exists — the overlay closes that gap for anyone working in a
worktree.
What tools/list advertises — 25 of 46, today
The ledger declares 46 tools. Measured over 7,657 instrumented calls (the original
pre-WP0.8 baseline), eight accounted for 98.9% of use — query_ledger alone was
84.8% — and find_similar had never been called once, in those 7,657 calls or in 69 local
transcripts.
That surface is not free: every advertised schema is carried in the model's context for the whole session,
and before its first call the model must fetch those schemas (see schema preload
below). The tail was costing more than it earned, so tools/list trims to a
CoreTools set (McpService.cs:264-279) — but that set itself grew on
2026-09-12 (WP0.8): eight reference/refactor-safety tools that were previously hidden were un-hidden after the
change-safety suite found they were never being exercised while invisible (docs/LEDGER_SCALE_PLAN.md
§10 WP0.8). Two independent gates decide the final advertised list
(McpService.FilterTools, McpService.cs:312-323): Mcp:AdvertiseAllTools
bypasses the CoreTools trim, but the three precise-lane tools are hidden
whenever Precise:Enabled=false regardless of that flag — an advertised-but-non-functional tool
is worse than an absent one.
| Group | Advertised | Why these |
|---|---|---|
| The measured workhorses | query_ledger, recent_activity,
node_neighbours, invariants_for, tests_for,
why_query, concept_neighbours, orient_me |
98.9% of measured usage. |
| Reading a cited span | read_span, symbol_lookup |
Used in the benchmark transcripts, and the cheap way to read a citation the ledger just gave. |
| Mandated workflow | todo_list, todo_add,
todo_complete, feedback |
Low volume and would look droppable on usage alone. CLAUDE.md mandates the todo flow
and feedback closes the learning loop, so they are kept deliberately. |
| Un-hidden 2026-09-12 (WP0.8) | reference_set, blast_radius,
blast_radius_diff, refactor_plan, cross_stack_path,
prior_decisions, find_similar, caller_recall_audit |
Zero-usage while hidden was itself the finding — the change-safety suite could not tell whether
better blast-radius tools help because they were never advertised. Advertising them changed usage
decisively: reference_set went from 0 calls to 16 in the very next re-test
(docs/evidence/change-safety-retest-2026-09-12/README.md). |
Precise C# lane — advertised only when Precise:Enabled=true |
find_definition, find_references, find_implementations |
Roslyn-backed, compiler-verified answers (WP3.1). Live in production as of 2026-09-13 07:0x UTC
(docs/LEDGER_SCALE_PLAN.md §12) — so the count advertised today is 22 + 3 =
25 of the 46 declared. |
tools/call
resolves against the full switch, never the advertised subset, so every script, hook and automation that
calls a tail tool by name still works — except the three precise-lane tools while
Precise:Enabled=false, which return a plain {"error": "precise lane disabled
(Precise:Enabled=false)"} result rather than a thrown exception, so a caller that doesn't check the
advertised list first still degrades cleanly (McpService.PreciseDisabledResult,
McpService.cs:2899-2904). Setting Mcp:AdvertiseAllTools=true restores the full list
(precise tools excepted, per the rule above). Both properties are pinned by
McpToolSurfaceTests, which also asserts the list actually shrinks — a trim that trims nothing is
worse than none, because it looks done.The full MCP tool catalog
All 46 are registered in tools/list and dispatched by name in
McpService.cs; the ones not in the advertised set above are callable but not advertised. A
read-focused subset is also exposed to the in-app chat loop via ExecuteToolForChatAsync.
| Tool | Group | What it does |
|---|---|---|
query_ledger | Search | Authoritative semantic search over the whole
codebase; the mandatory first call. Supports depth, edge_kinds, repo/layer pre-filters, skeleton detail,
toon layout, lane attribution, and a root checkout overlay (see below). |
ledger_ask | Search | Ask a natural-language question, get a synthesised, cited answer in one call instead of raw chunks. |
symbol_lookup | Search | Resolve a function/class/interface/SQL-object name to exact definition coordinates (repo, file, line-span). |
read_span | Search | Return an exact line range from indexed sources — token-bounded, no vector lookup. |
reference_set | Search/Refactor | USE WHEN you need
every place a symbol is used before a rename or signature change (complete, approximate: a word-boundary
text sweep across all repos, not a compiler). NOT FOR "what does X do"
(query_ledger) or "where is X defined" (symbol_lookup). |
find_similar | Graph/Trace | USE WHEN porting a pattern
or hunting duplicates: structurally similar code (mode code, same language) or semantically
similar design (mode design, cross-language) to an anchor. |
node_neighbours | Graph/Trace | Walk the dependency graph up/down/both — "what calls X / what X depends on". |
concept_neighbours | Graph/Trace | Cross-language/cross-repo chunks in the same k-means concept cluster (C#↔TS↔SQL of one concept). |
cross_stack_path | Graph/Trace | USE WHEN a question crosses layers — screen → API route → stored procedure → tables — for a route or symbol. NOT FOR single-layer lookups. |
blast_radius | Graph/Trace | USE WHEN you are about to
edit files and want to see what is downstream in the dependency graph (pass changed_files
or a commit, and hops). Graph preview only — confirm callers with reference_set before
relying on it. |
tests_for | Refactor-safety | Test files/chunks that exercise a symbol (resolves anchor, walks callers upstream). |
refactor_plan | Refactor-safety | USE WHEN planning a multi-file change to a symbol or file: returns the ordered touch points, the tests that cover them and the invariants to check. NOT FOR single-file edits. |
blast_radius_diff | Refactor-safety | USE WHEN a
refactor must not miss a caller: compares the graph's blast radius with the text-sweep reference set for
the same anchor and reports what each misses (a caller-recall % plus in_both /
graph_only / lexical_only buckets). |
caller_recall_audit | Refactor-safety | USE WHEN deciding
whether blast_radius can be trusted for a symbol: measures how completely the graph knows
its callers against a text sweep, per repo. |
prior_decisions | Team/Learning | USE WHEN you are about to decide something that may already have been decided: returns decisions extracted from earlier sessions that touched this area, with their reasons — context to revisit, never a veto. |
drift_since | Refactor-safety | Structural delta of a file since a time/commit — only symbols added/removed/moved/signature-changed. |
find_definition | Precise (C#) | USE WHEN you need to
jump to where a C# symbol is DECLARED — the compiler-verified source location. NOT FOR
"what does X do" (query_ledger) or "why does this exist" (why_query). Roslyn
MSBuildWorkspace; advertised only when Precise:Enabled=true. |
find_references | Precise (C#) | USE WHEN you need EVERY
compiler-known usage of a C# symbol before a rename or signature change — recall and precision 1.0
against the compiler, not a text sweep. NOT FOR prose questions (→
query_ledger) or a first look at unfamiliar code (→ orient_me). Falls back to
the SQL reference lane (kind:"sql") or the deterministic text sweep when the compiler can't
resolve the symbol; every hit is labelled truth:"compiler" or truth:"text". |
find_implementations | Precise (C#) | USE WHEN you need
the concrete classes BEHIND an interface, or the overrides of a virtual/abstract member — compiler-
verified, not a text guess. NOT FOR "how is this used" (→ find_references)
or "what does this do" (→ query_ledger). Roslyn SymbolFinder.FindImplementationsAsync. |
orient_me | Orientation | Single-call landing pad for a file: identity, quality, git, tests, invariants, concept siblings, graph link. |
task_intro | Orientation | Canonical session opener: fans out to query_ledger + orient_me on the top hit + invariants_for. |
why_query | Orientation | Why a file exists / was last changed: git provenance, inferred intent, ADR pointers, load-bearing status. |
invariants_for | Orientation | Load-bearing rules: CLAUDE.md / LOAD_BEARING / AGENTS.md / INVARIANTS.md + must/never chunks. |
recent_activity | Orientation | Most recently modified files (git last-modified desc), optionally per-file history or per-repo. |
cluster_cohort | Cohort/Aggregate | Group a facet-selected population into families by clustering design vectors. |
cohort_consistency | Cohort/Aggregate | Find the odd-one-out / inconsistency within a cohort that should look alike. |
cohort_count | Cohort/Aggregate | Count a facet-selected population grouped by repo/layer/pattern — the "how many" tool. |
count_sprocs | Cohort/Aggregate | Count DISTINCT stored-procedure definitions parsed from .sql, grouped by repo. |
fix_queue_list | Quality/Fix-queue | Lists approved findings (bugs, inconsistencies, UX) awaiting a code fix, across every repo. |
fix_get_brief | Quality/Fix-queue | Complete self-contained fix brief for one finding: issue+evidence, target files+blast radius, current code, acceptance criteria. |
fix_mark_status | Quality/Fix-queue | Advance a finding through its lifecycle so the shared queue never double-hands. |
todo_list | Todo | Everything still outstanding across every repo — the cross-repo todo list, filterable by repo or by the session identity that raised each item. |
todo_add | Todo | Record work left undone (deferred, skipped, discovered-but-not-fixed) so it outlives the session; deterministic ids, so re-adding updates in place. |
todo_complete | Todo | Close a todo once the work is verified done — the list is only worth reading if "done" means done. |
learning_needs | Team/Learning | Team/developer capability analysis from the quality rubric + recurring finding classes, attributed by git author. |
prior_work | Team/Learning | "Has anyone worked on this before?" — past sessions that touched a file/task, labelled by outcome. |
recall_conversation | Team/Learning | Semantic recall over past (redacted) conversation transcript spans. |
feedback | Team/Learning | Submit useful/not-useful feedback on a prior query_ledger result (closes the learning loop). |
mark_visited | Context/Recall | Record a confirmed code node this session so later turns cite path:L#-L#. |
recall_visited | Context/Recall | Return the session's confirmed nodes so the agent cites verified coordinates instead of re-reading. |
bench_list | Benchmark | The token-efficiency benchmark queue + aggregate report (ledger vs direct-file cost). |
bench_get_task | Benchmark | One pending benchmark question + the exact two-path run protocol + the bench_record call. |
bench_record | Benchmark | Record per-path token usage for a question and mark it done. |
sync_ledger | Admin | Trigger a background re-ingest of the configured repos (high-risk tier). |
policy_check | Admin/Enforcement | Risk-tiered verdict (allow/deny/review) for a proposed tool+path+action — MCP-as-policy-enforcer. |
suggest_personas | Governance | Reads a task and suggests expert sub-agent personas to wrap it in (Performance Tuner, Legal Expert, Security Reviewer, SQL Optimiser, …), each with a recommended model + tool scope. Advisory; backed by the governance persona library — see the Governance layer. |
The catalog moves agents from "grep-and-read the whole tree" to targeted, token-cheap, graph-aware retrieval — search, trace, orient, refactor-safely, and learn from prior work, all through one authenticated endpoint.
Span-first hits — every result leads with where the code is
A query_ledger hit used to open with its intent sentence and a labelled metadata row, and on
the default path carried no line coordinates at all: an agent had to read to the bottom of a block to learn
it could not navigate from it. Span-first reverses that order —
path L<from>-<to> becomes the hit heading, the intent drops to the first
bullet, and the metadata collapses to one unlabelled separator-delimited row whose schema is stated once in a
legend line at the top of the response.
Nothing is dropped: repo, layer, pattern, chunk type, confidence band and percent, build sha, graph link,
snippet, graph footer and the ```json cited block all survive. Removing the repeated labels more
than pays for the coordinates and the legend, so the response is smaller — replaying the 20 frozen
token queries over the t0 snapshot gives −3,902 bytes (−3.14%), about
−49 estimated tokens per query, assuming a pessimistic 11-character coordinate on every hit.
Config-gated on Mcp:SpanFirstLayout, so the flag flip is the single attributable change.
The only actionable fact in a hit is where the code is. Leading with it means an agent can go
straight to read_span instead of scanning to a footer to find out whether it can.
The cited block — ledger_id, start_line, end_line,
indexed_sha
Every rendered response that cites files closes with a fenced ```json cited block — one entry
per distinct file, deduplicated. Beyond the always-present repo/file pair, an entry
carries whichever of these the call site actually had a real hit object for (a
SearchResult or SymbolMatch, never fabricated): ledger_id (the chunk's
stable identity — survives a file rename), start_line/end_line (the coordinates
actually shown in the rendered heading, so read_span lands on the same lines the agent read),
and indexed_sha (the commit the chunk was indexed at — SymbolMatch hits never carry
this field). This is what lets follow-up tooling — the audit script's per-hit follow rate, the Ask drawer's
"View code" button — survive a file rename instead of matching on a bare path.
Compact defaults — Mcp:DefaultFormat / Mcp:DefaultLimit (toon is live)
An explicit format/limit argument on a query_ledger call always
wins, but absent one, it resolves through two optional config keys instead of a hardcoded
"markdown"/5: Mcp:DefaultFormat ("markdown" or
"toon") and Mcp:DefaultLimit (1–20, clamped). An invalid value (unparseable, or not
one of the accepted formats) falls back to the code default and logs once rather than failing the call.
Both keys are no-op in the shipped appsettings.json code defaults — but the live
production config is not the code default: at 19:08 UTC on 2026-09-12 (deploy #4,
docs/LEDGER_SCALE_PLAN.md §12), the live values were flipped to
Mcp:DefaultFormat=toon, Mcp:DefaultLimit=3, and verified live by a
query_ledger call with no limit argument returning 3 toon rows. So today, any caller
that omits format/limit gets the dense toon layout at 3 rows, not 5-row markdown —
still overridable per call. The change is being judged by the nightly per-hit follow rate.
symbol_lookup hands over the definition, not an errand
On an exact match symbol_lookup already knew the file and the line range — and then
printed a literal instruction, Read it: read_span(...), telling the agent to spend its next turn
fetching what the ledger could have handed over in the one it was already paying for. Seven of the twenty
recorded read_span calls in the real-session benchmark followed that line. The tool was
manufacturing its own follow-up turn.
With Mcp:InlineSymbolSpan on, an exact match inlines the definition itself — read from the
source file, not the indexed chunk, so it is immune to the chunk cap that leaves large
members internally truncated in the corpus — padded by Mcp:InlineSymbolSpanContextLines (default
20) on each side and bounded by the same Retrieval:ReadSpanMaxLines cap (default 400) that
read_span would have applied. The whole inline section of a response is further bounded by one
read_span's worth of lines, so a 20-match lookup cannot cost more than the turn it removes.
read_span handle stays — it is how an agent pages past a
capped or clamped span — but it is no longer phrased as an instruction to fetch what is already on screen.
Three cases deliberately keep the legacy handle instead of inlining: a fuzzy (contains) match, because a
partial match is a real disambiguation step and inlining several probably-wrong bodies would spend tokens
rather than save them; a spent line budget or unreadable file; and a definition whose start is past
end-of-file, where stale coordinates would otherwise print the wrong lines.Preloading the tool schemas
Claude Code defers MCP tool schemas by default: the tools arrive as names only, and before
its first call the model must spend an entire turn calling ToolSearch to fetch a schema. Only
then can it make the call it wanted to make. Measured over the 21 recorded ledger-arm sessions in the
real-session benchmark:
| Ledger arm | Control arm | |
|---|---|---|
Sessions opening with a ToolSearch turn | 21 / 21 | 0 / 21 |
Total ToolSearch calls | 24 | 0 |
Share of all turns spent on ToolSearch | 13.8% | 0% |
The control scores zero because it loads no MCP server at all, so it has nothing to defer — the deferral
was a fixed startup tax on one side of a two-arm comparison, measuring the harness rather than the
ledger. Every session that opens by consulting the ledger, which is what CLAUDE.md instructs
every session to do, pays that turn. Setting ENABLE_TOOL_SEARCH=false — as an environment
variable in front of claude, or as an env key in
~/.claude/settings.json — removes it. Full guidance and accepted values:
docs/TOOL_SCHEMA_PRELOAD.md.
Benchmark answer keys are excluded server-side
The benchmark's reference answers were once excluded only as a caller argument:
BenchRunner passed exclude_paths on its own calls, nothing else did, and
exclude_paths is unadvertised, so no real session ever sent one. On the 2026-08 real-session run,
6 of 21 ledger sessions were served answer-key prose as ranked query_ledger
hits. Worse, the documents had already been deleted from the sources tree — ingest is upsert-only and
nothing walks Qdrant asking whether a file still exists, so about 19 chunks outlived their files by weeks and
kept being retrieved.
Retrieval/BenchDocGuard.cs makes it a server rule instead, applied inside the retrieval path
regardless of what the caller passed: hybrid search merges the server list into its exclusions (a union, so
the bench keeps its own), symbol_lookup applies it per point because it never went through hybrid
search at all, and read_span refuses by path because it answers from disk where no index change
can reach it. Retrieval:ExcludeBenchDocs defaults true — this is an integrity
fix, not an experiment, so the safe state is the default state, the flag exists only for emergency rollback,
and an unparseable value degrades to on rather than throwing inside search. A localhost-gated
POST /api/admin/qdrant/purge-bench-docs then deletes every matching point, all versions and
deliberately not is_latest-filtered, because a superseded version still carries the full
answer-key text in its payload.
An exclusion that lives in one caller is not an exclusion. A second copy of the path list is exactly how
this hole opened, which is why the guard borrows BenchRunner's list rather than restating
it.
PR pre-flight — asking the ledger what a diff disturbs
tools/pr-preflight.sh takes a PR, asks the live ledger what the diff touches, and prints a
report: affected callers with real file:line anchors, cross-repo impact, invariants touched, and
a call table recording every request it made with its outcome. It is print-only by default —
posting a comment requires an explicit --post — costs $0 and calls no model. A run where a ledger
call had failed exits non-zero and renders that section as "Could not ask the ledger", never as a clean empty.
The acceptance capture against a real merged PR is
docs/PR_PREFLIGHT_ACCEPTANCE.md.
blast_radius,
reference_set, refactor_plan and cross_stack_path "were on the
allowlist and were called zero times across all 20 ledger sessions". Its own conclusion:
"Nothing here supports building the PR pre-flight bot on a better-blast-radius claim, and nothing here
refutes that claim either — it was never exercised." See
docs/CHANGE_SAFETY_RESULTS.md
§9, and the summary on the Overview.policy_check — the risk-tiered gate
Given a proposed action (tool + optional path + intent), policy_check returns a
machine-readable allow | deny | review verdict an MCP gateway or hook consults before the action
runs. It is backed by the pure, testable PolicyService (Services/PolicyService.cs):
ReadOnly → allow (every discovery tool; unknown tools default here); Write → allow-but-flagged (feedback,
mark_visited, bench_record, fix_mark_status); HighRisk → review (sync_ledger + configured). The tier map is
config-overridable and UNIONed onto defaults; ToWire emits stable lower-case strings; and the
verdict attaches the relevant invariants for the target path. It is documented in full on the
Team analysis page.
Proportional control — read-heavy discovery stays frictionless while mutating or irreversible actions get gated to a human, without hard-coding what is risky.
Token-efficiency benchmark (bench_*)
Proves and measures that answering via the ledger costs fewer tokens than reading files directly, as a real
A/B. Benchmark/BenchRunner.cs runs each pending discovery question through two isolated agentic
loops with HARD-separated tool sets: (A) LEDGER = a single query_ledger tool hitting the local MCP
endpoint (http://127.0.0.1:5005); (B) DIRECT = grep/read over
/opt/arch-ledger/sources/current. It records each path's real token usage
(input/cache_read/cache_write/output) from Anthropic API usage blocks, with loops capped at 40 turns / 4096
tokens. An opt-in correctness gate (BenchGate, off by default via
Benchmark:Judge:Enabled) enforces that a cheaper answer only counts as a win if it is BOTH strictly
cheaper AND at least as correct (an LLM judge). Results persist in the Benchmark Qdrant collection, and the
system-wide ratio is surfaced by EfficiencySummary in TeamMetricsService. The
in-session drain flow (bench_list → bench_get_task → run both ways in fresh contexts → bench_record) is $0 and
deterministic; the real-API A/B spends money, is gated on Anthropic:ApiKey, and only runs on an
explicit trigger (model default claude-opus-5).
It turns "the ledger saves tokens" into a measured, correctness-gated, per-question ledger — the number behind the Ledger Token Saving tile.
Deployment topology
An agent posts to https://brain.motlogs.com/mcp (HTTPS + JSON-RPC + Bearer) → nginx (TLS
terminator) → 127.0.0.1:5005 .NET 10 service. The publish is a self-contained linux-x64 binary,
shipped as a tarball via scp over SSH. The systemd unit (Deploy-Hetzner.ps1:573-591) sets
WorkingDirectory=/opt/arch-ledger,
ExecStart=/opt/arch-ledger/MOTLogs.ArchitecturalLedger, Restart=always,
ASPNETCORE_URLS=http://*:5005; the service name is arch-ledger. The nginx vhost
(:120-181) redirects 80→443, terminates TLS 1.2/1.3 with a wildcard cert, and proxies to
http://127.0.0.1:5005 with WebSocket upgrade + forwarded headers, while
location /api/admin/ { return 404; } blocks admin at the proxy (loopback/SSH-tunnel only). Deploy
flags include -NoNginx, -ConfigureNginxOnly, -RestartOnly, plus
source-sync-only and transcript-sync-only modes; the graphify CLI is installed on-host via uv tool.
A single self-contained binary behind a TLS-terminating proxy is trivial to reason about and restart, and the admin surface is blocked at two layers (proxy 404 + app-level tailnet gate) so it can't leak.
Public API access gate (WP0.7)
Every /api/* and /hook/* request now passes one gate (Program.cs,
registered beside the audit-trail middleware, delegating its actual decision to the pure, unit-tested
HttpAccessGate.IsApiRequestAuthorized in Services/HttpAccessGate.cs — trusted-peer
loopback/tailnet detection, public-path matching, and bearer/registry resolution are each a standalone
testable predicate there, not inline logic only a booted host could exercise): a small
Security:PublicPaths allow-list
(/api/health, /api/ask, /api/symbols, /api/gate-health by
default — the latter two already self-gate or were already public, so this just leaves them alone) is let
through with no check; everything else needs a trusted loopback/tailnet peer
(Security:TrustLoopback / Security:TrustTailnet, both default true) or a bearer —
either the shared Mcp:BearerToken or a TokenRegistry-registered per-developer token,
the same rule POST /mcp already applies — presented as Authorization: Bearer … or
?token=…. This closed the previous hole: GET /api/todos had no gate of its own and
returned the full cross-repo findings list to anyone on the internet. Per-route checks that already existed
(IsLocalhost/IsGraphAuthorized) are unchanged and still run afterwards. Because
/api/stats is deliberately not on the allow-list, the dashboard (index.html,
script.js, admin.html, app.html) now needs a token to load unless it
arrives over an SSH tunnel/tailnet session (trusted peer, no token needed). Per decision D8 the dashboard is
bearer-only rather than exempted, so every page routes its /api/* calls through
wwwroot/token.js's authFetch: it attaches Authorization: Bearer … once a
token is known, accepts one the same way the graph viewer always has (?token=<Mcp:BearerToken>,
stored to localStorage and stripped from the URL on read), and on a genuine 401 shows a small
one-time banner asking the viewer to paste the token, then retries the failed call once. The token itself is
never embedded in any served page or script. Security:Enforce=false in the runtime settings file
is the documented rollback if this gate ever needs to come out without a redeploy.
The gate used to be a property each route had to remember to add; now it's a property of the surface —
a new /api/* route is closed by default and has to be deliberately added to the allow-list to
become public, rather than the other way around.
Qdrant operations
The image is PINNED to qdrant/qdrant:v1.15.4 (1.16.x panics on
scout_vehicles.location — an empty geo index unwraps None at gridstore.rs:333), runs
with --ulimit nofile=1048576 (the default 1024 panics), and uses two bind mounts — storage
(/var/lib/qdrant) and snapshots (/var/lib/qdrant-snapshots, else snapshots vanish on
docker rm) — with loopback-only ports 6333/6334 and restart-always. The host needs 125 GB RAM and
≥16 GB swap (scout_vehicles peaks ~118 GB). The ledger collection
motlogs_unified_architecture (~2,241 points, named vectors code_vector/design_vector, 384-dim
Cosine) is tiny next to scout_vehicles (135M points / 686 GB).
totalChunks: 0 — always wait for healthz then
systemctl restart arch-ledger.The pinned image, raised ulimit, and separated snapshot mount are hard-won production settings — each one prevents a specific crash or data-loss mode, so the vector DB stays up under a 686 GB co-tenant.
Re-ingest / backfill runbook
A cache-preserving rebuild re-chunks and re-embeds without paying for the Groq analysis cache again.
- Pre-flight — verify the commits on the host binary, that repo paths point
at
motmaster-*, that Qdrant is healthy, and take a snapshot. - Rebuild —
POST /api/admin/qdrant/rebuild(guarded — NEVER clear-collection; returns 202). - Monitor —
/api/admin/ingest-status(2–4h atMaxParallelism:2, watchingBudgetSkippedChunks). - Post-passes —
POST /api/admin/concepts/run+/api/admin/cross-stack/run(materialises the G1 controller→sproc edges). - Measure —
GET /api/admin/eval/relevance(the 24-question golden set) as the no-regression gate. - Snapshot — take a fresh snapshot.
Byte-identical content stays Groq-cold-free (~$0). The high-risk sync_ledger MCP tool triggers
the background re-ingest programmatically.
A full re-vector of the corpus is a routine, ~$0, gated operation — with a golden-set eval as the no-regression contract — so tuning changes ship on evidence, not hope.
Enforcement hook
Every agent is nudged or forced to consult the ledger before touching code, via a PreToolUse hook. As of
2026-09-13 08:19 UTC this is a single, user-level, versioned wiring — not per-repo copies —
installed from motmaster-infra/claude-hooks/. Full detail, including the current gate mode, the
A/B under way, and /api/gate-health, is on the Governance layer
page; this section states only what's relevant to the MCP surface itself: the ledger MCP tools are always
exempt (mcp__mot-ledger__* never gates, or the gate would be unsatisfiable), and every non-ledger
MCP tool is gated the same as Read/Grep/Bash/Edit/Write (pattern
^mcp__(?!mot-ledger__).+) — so a different MCP server's tool is subject to the same consult
requirement as a file edit.
It guarantees answers are grounded in the ledger rather than model memory — the tool is the constraint, so compliance is mechanical rather than advisory (while that rule itself is under a pre-registered A/B — see Governance).
/root/code/motmaster-openbrain/CLAUDE.md. bench_* defaults to
claude-opus-4-8 and only spends when Anthropic:ApiKey is set; the in-session drain
path is $0.Open Brain — the MOTMaster Architectural Ledger · generated from source 2026-07-07