opbox

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 opbox CLI — 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.

  1. 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.
  2. 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).
  3. 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.
  4. 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.
  5. Do the work. Only now does the handler run, inside one database transaction, fenced to the caller’s workspace by row-level security.
  6. 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.

One client's box, the trust perimeter: the Caddy front door serves the web app and the agent-chat surface and routes to the kernel (the verb door, the MCP door, and the opbox CLI); behind it sit Postgres with pgvector and the caged, kernel-MCP-only agent.

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.)

Isolation is structural

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:

InvariantWhat it guarantees
INV-1One write path per fact - no two state machines for one thing.
INV-3A door-stamped source; an AGENT is denied over HTTP/CLI (MCP is the agent door).
INV-8The event log is append-only and hash-chained per workspace (tamper-evident audit).
INV-11Fail-closed: an un-authored verb, a missing tier, a bad bearer all deny before any DB work.
INV-12Least-privilege = authz tier + autonomy level + budget cap, never a feature flag.
INV-14One dispatch behind all three doors - no second backend.
INV-15No 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.