Opbox keeps the essence of a professional-services stack and deletes the scaffolding, because in an agentic era the agent is the interface and the integration layer. Four concerns, all co-located on one server-per-client box (the box is the trust perimeter).
The four layers
Kernel (owned)
The only substantial code Opbox owns. A single capability registry exposed as verbs — every action a human, an agent, or the CLI can take is a verb in one Rust binary. It is the trust boundary: authz, autonomy, audit, budget, and PII crypto all live here, fail-closed. It exposes three doors over one dispatch (ADR-0003, INV-14):
POST /v/:verb— the HTTP front door (bearer-authenticated).POST /mcp— the Model Context Protocol transport (the caged agent’s only path to data).- the
opboxCLI — the same registry, the same dispatch.
In a release build the kernel is token-only (ADR-0040): the dev identity is compile-excised, so a production server can never fall back to a privileged dev actor. See The kernel.
Postgres + pgvector (owned)
The single system of record — relational state and the vector store for retrieval. One database for all state; the matter-step engine and the structured domain data live here. Every other surface is a regenerable view, never a second system of record.
The agent (engine)
The reasoning loop. The caged Hermes engine is now exercised: an unmodified upstream Nous Hermes agent
runs caged (capability-stripped, egress-locked, kernel-MCP-only: its only tools are the always-on Core
kernel verbs, around 68 of them, plus an in-memory todo; more unlock on demand), so the engine is consumed
upstream while the substrate stays owned. The model is
provider-configurable (default cloud GLM-4.6 via z.ai; a local Ollama is the no-egress fallback; any
OpenAI-compatible provider works by config). The owned opbox_agent.py loop coexists as a driver and the
fallback. The chat reaches whichever engine is active through a small HTTP gateway that exposes an
OpenAI-compatible /v1 API and an AG-UI /chat/stream. Either way the agent is untrusted: its only path
to firm data is the kernel’s verb surface. See Agents & MCP.
The front end (owned)
The moat-facing product face. It is an owned Next.js front end (Next.js 16, React 19, Prisma 6,
NextAuth 4) and is the site root (ADR-0038): the marketing landing, the sign-in surface, and the whole
app shell, navigation, and design system. In the unitary stack it reads the kernel-owned tables directly
(RLS-scoped) and writes only through kernel verbs. Spotlight is the in-app AI surface (it superseded the
retired copilot-app); the agent’s conversation still streams over the bearer-gated /chat/* SSE endpoint
served by agent-chat. OpenAI-style clients (OpenWebUI, LobeChat) can sit on the gateway’s /v1 API the
same way. See Agent chat.
The request lifecycle
Every action, whoever starts it, follows the same path through the kernel. There is no shortcut and no side door.
- Arrive at one door. A request comes in through the HTTP front door (a screen or the CLI) or the MCP door (the caged agent). All three resolve to the same dispatch.
- Identity. The kernel resolves the caller from their bearer token to a real actor in a workspace. No valid bearer, no work (in a release build there is no dev fallback to fall back to).
- Permission (tier). Is this actor’s tier high enough for this verb? A client (EXTERNAL) cannot call a staff verb; a member cannot call an owner verb.
- Autonomy and budget (for the agent). If the caller is the AI: is the verb within its autonomy level, and is it under budget? Money and destructive verbs need a senior tier and an extra confirmation.
- Do the work. Only now does the handler run, inside one database transaction, fenced to the caller’s workspace by row-level security.
- Record it. Exactly one audit entry is written in the same transaction, chained to the last one so the log cannot be quietly rewritten.
If any check fails, the kernel denies and stops before touching data. It never half-does a thing and it never silently widens access. See Security & permissions for the full model.
Deployment: one box per client
A live Opbox is one server per client. The box itself is the trust perimeter: everything a firm needs runs co-located on it, and nothing crosses to another firm’s box.
Because each firm is its own box and its own workspace, there is no cross-firm query to get wrong: isolation is the default, not a feature. (The model still scales to several workspaces on one box if ever needed, because every rule is workspace-scoped at the database.)
Because the perimeter is the box, “could firm A see firm B’s data?” is not a question you have to answer with careful coding - there is no shared store where it could happen. The architecture rules it out.
The invariants
The substrate is pinned by always-true rules (the test spec). The load-bearing ones:
| Invariant | What it guarantees |
|---|---|
| INV-1 | One write path per fact - no two state machines for one thing. |
| INV-3 | A door-stamped source; an AGENT is denied over HTTP/CLI (MCP is the agent door). |
| INV-8 | The event log is append-only and hash-chained per workspace (tamper-evident audit). |
| INV-11 | Fail-closed: an un-authored verb, a missing tier, a bad bearer all deny before any DB work. |
| INV-12 | Least-privilege = authz tier + autonomy level + budget cap, never a feature flag. |
| INV-14 | One dispatch behind all three doors - no second backend. |
| INV-15 | No amplification: a conferred grant can never exceed its grantor. |
What is the moat
The kernel + Postgres + the matter-step engine + the structured domain data. The reasoning engine and the GUI chrome are swappable upstream components; the substrate and the product face are owned.