Lamprey Architecture Contract
SkillMediaLamprey's load-bearing design decisions, the invariants that must hold, what was deliberately deleted, and the honestly-stated weak points. Load before modifying electron/ipc, electron/services, the provider or tool layers, or the renderer stores — or when you need to know WHY the system is shaped the way it is.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Lamprey Architecture Contract skill
What this skill tells your AI
The instructions your AI receives, as published by uss-parks/lamprey-harness in .claude/skills/lamprey-architecture-contract/SKILL.md and read by ahel’s review.
When to use / when not
- Use before changing any main-process, preload, or store code; when deciding where new logic belongs; when an invariant might be at stake.
- Don't use for data-layer depth (see
lamprey-database-and-persistence), provider/model specifics (seelamprey-provider-and-model-reference), config keys (seelamprey-config-and-flags), or loop internals (seelamprey-loop-reliability-campaign).
TL;DR
Electron three-process app: electron/main.ts → electron/ipc/* (handlers) → electron/services/* (logic); electron/preload.ts exposes a typed window.api; renderer is React 19 + Zustand + Tailwind 4 in src/. One chat-turn seam (runHeadlessTurn) serves both interactive sends and headless loop iterations. Everything power-shaped is opt-in; the era default is a plain single-agent harness.
The shape and why
Main / preload / renderer split. All privileged work (DB, keys, shell, providers, MCP) lives in the main process behind IPC. The preload exposes a narrow, typed surface (window.api); the renderer must guard window.api existence so the app doesn't crash in plain-browser dev mode. Never reach around this: no Node APIs in the renderer, no business logic in IPC handlers (they validate, delegate to services, wrap results).
The IPC envelope invariant. Every handler returns { success: true, data: T } | { success: false, error: string }. The renderer depends on this exact shape for error handling. New channels follow it without exception.
One turn seam. chat:send → runHeadlessTurn → runChatRound (all in electron/ipc/chat.ts). Loops and wake-ups run real turns through the same seam via an injected runner (setLoopTurnRunner, wired at handler-registration time) — injection exists specifically to avoid a services→ipc import cycle. History: the original loop scaffold bypassed this and never ran a turn (LP-0/G1). If you add another way to trigger a turn, it goes through this seam.
Provider registry. electron/services/providers/registry.ts owns MODEL_CATALOG (descriptors with capability flags), chatStream/chatOnce, and resolveModel with a strict priority chain: catalog → RETIRED_MODEL_MAP → custom models from settings.json (mtime-cached) → synthetic OpenAI-compatible fallback. Behavior differences are expressed as descriptor flags (supportsTools, supportsVision, isReasoner, defaultMaxTokens, reasoningCapOnToolUse), never as if (model === '…') scattered through dispatch. Details: lamprey-provider-and-model-reference.
Tool registry. electron/services/tool-registry.ts describes every tool with risk metadata (read/write/network/destructive/secret/sandboxBypass) and requiresApproval. Approval is driven by descriptor metadata, not hard-coded server lists. Load-bearing consequences:
- Parallel execution windows only for parallelizable, approval-free, non-mutating calls.
- Plan mode blocks mutating tools regardless of approval settings (
mutatesdefaults from risks). - Fallback-parsed calls get
fb_-prefixed ids and skip persisted "always allow" policies (trust degradation). - Provider schema normalization: core tools fail fast on mismatch, non-core drop with a warning.
System prompt discipline. electron/services/system-prompt-builder.ts composes identity + a deliberately small contract + role fragments + memory/skills blocks. Prompt bytes are measured and guard-tested — the Lampshade→Hygiene→Unburdening arc proved over-instruction measurably degrades cheap-model output. If you grow the prompt, you re-measure and justify against the byte guards (see lamprey-proof-and-analysis-toolkit recipe 3).
Context economy valves (opt-in). Lazy tool surface (CORE set + tool_search unlock round-trip; auto-downgrades to full after 3 malformed searches) and the tool-result spill valve (results > 8192 chars spill to userData/tool-results/, model gets head+tail preview + read_tool_result ref; the DB/UI keep the full result — the asymmetry is intentional). Era defaults: toolSurface: 'full', spill on.
Persist-side hygiene. sanitizePseudoTags rewrites shell-shaped pseudo-XML (<bash>…) into fenced markdown on every assistant saveMessage; the verbatim original is preserved in messages.content_raw. The safety net lives at persistence, not in prompt nagging (L6 decision).
Ghost-reply guard. Any turn failing with no visible reply row persists a role:'system' notice (user aborts exempt). Every failure path must settle the turn (JM-8). If you add a new failure path, it must uphold both.
Security posture (JM-19/20, v0.16.0): will-navigate guard on the top frame; shell.openExternal restricted to http(s); webviews denied; artifact sandbox is a WebContentsView (not deprecated BrowserView) with strict CSP incl. form-action 'none' and window-open lockdown; files:* IPC confined to the workspace root; stdio-MCP adds show the command and require approval; API keys via safeStorage in keys.json with atomic writes and plaintext-consent gating when encryption is unavailable.
The invariants (verify before relying; each has a guard or a story)
- IPC envelope shape — everywhere, no exceptions.
loopsEnabledgates every loop entry point (schedule_wakeup, fireDueWakeups, tickLoops, loop_control continue, cron automations) — source-locked byelectron/services/loop-safety.test.ts.- Migrations: one transaction per migration including the version stamp; idempotent re-run; reachable from init (the v0.9.2 lesson).
- Atomic JSON writes for keys/settings; corrupt files preserved as
.corrupt-<ts>, never healed to{}. - Sanitizer on every assistant save; original in
content_raw. - Ghost-reply guard + universal turn settlement.
- Tool windows: parallel only when parallelizable ∧ no approval ∧ no write/destructive/secret risk.
fb_fallback provenance skips "always allow".- Spill asymmetry: model sees preview, DB keeps truth.
- Settings merge
{...DEFAULT_APP_SETTINGS, ...file}; canonical and renderer default literals locked byte-for-byte bydefault-app-settings.test.ts— change both or fail tests. - Retired settings keys are inert by construction (nothing reads them).
- Capability downgrade (FC-10): 3 mismatches → session-scoped fallback mode; per-conversation state cleared on delete.
- Era chrome: no pipeline/stage/proof jargon in the UI — locked by
src/components/chat/era-chrome.test.ts. window.apiguarded in renderer code.- Reasoning is preserved end-to-end (audit phase): reasoning chunks persist with the message; past reasoning re-fed when
includePastReasoningInContext(default true).
What no longer exists (do not search for it, do not rebuild it)
Deleted in Unburdening (v0.14.0): the Planner→Coder→Reviewer pipeline, the auto-router + telemetry, the runtime proof gate (receipts scan, ProofGateBanner, implicit contracts), and the final-response composer. The reply is the model's reply byte-for-byte. Kept: multi_agent_run tool, coworker side chat, and the historical DB tables. Full inventory and rationale: lamprey-failure-archaeology #8. Rebuilding any of it is an era-lock exception requiring explicit user authorization (lamprey-change-control).
Known weak points, stated plainly (as of v0.16.0, 2026-07-02)
- Unsigned Windows builds — code signing is env-gated and ready (
WIN_CSC_LINK/WIN_CSC_KEY_PASSWORD) but awaits an Authenticode cert (owner action). Updates verify via sha512 inlatest.ymlonly. - Live GUI smoke of Electron 43 and a real RAG ingest under
@huggingface/transformers4.x were still owner first-install checks at ship time. noUncheckedIndexedAccessdeferred — measured at ~700 errors; a future dedicated cleanup.- Renderer tests run in a node environment (no jsdom suite); renderer coverage leans on source-lock tests and smokes.
- Coverage thresholds are regression floors (statements 13 / branches 12 / functions 9 / lines 14 %), not quality targets.
- Loop reliability is mechanically fixed but not yet proven live at scale — that is the active campaign (
lamprey-loop-reliability-campaign).
Provenance and maintenance
Based on source reads of electron/ipc/chat.ts, electron/services/{providers/registry,tool-registry,system-prompt-builder,ghost-reply-guard,tool-unlock-state,tool-result-spill,sanitize-pseudo-tags,atomic-json,loop-config}.ts, the guard tests named above, and ARCHITECTURE/*.md, at v0.16.0 (2026-07-02).
Re-verify:
- Invariant guards still exist:
ls electron/services/loop-safety.test.ts electron/services/default-app-settings.test.ts src/components/chat/era-chrome.test.ts - Deleted modules stay deleted:
ls electron/services | grep -E "agent-pipeline|agent-router|proof-gate|final-response-composer"(expect empty) - Turn seam:
grep -n "setLoopTurnRunner\|runHeadlessTurn" electron/ipc/chat.ts electron/services/loop-runner.ts - Weak points list: re-read CLAUDE.md "honest gaps" for entries newer than 2026-07-02
Signals
- GitHub stars
- 62
- Forks
- 2
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
lamprey-architecture-contract- Source
- github.com/uss-parks/lamprey-harness