Docs
Under the hood

Architecture

One Next.js app, two databases, and a very strict rule about who is allowed to make an action happen.

The whole map

WHERE WORK HAPPENSSlackGitHub · Jira · NotionEmail forwardingWebhook · REST APIAgents (MCP)THE STALE APP (NEXT.JS)1 · Ingestextract claims, dedupe, reconcile2 · Review queuerisky changes wait for a human3 · Memory enginesupersede, cascade stale, repair4 · Taint firewallstale or tainted actions can’t runAuth · roles · API keys · MCP · RESTSTORAGEFalkorDBthe memory graphnodes · sources · edgesone graph per workspaceTurso (libSQL)the control planeaccounts · sessions · keysreview queue · ingest ledger

The app is stateless. Every request is authenticated, scoped to one workspace, and then reads or writes that workspace’s graph. You can run many copies of it behind a load balancer, which is exactly what Vercel does.

Why two databases?

They hold different kinds of truth, so they use different kinds of database.

StoreHoldsWhy this one
FalkorDBFacts, beliefs, decisions, actions, sources and the edges between them.Staleness and taint are graph questions (“what can I reach from here?”). A graph database answers them in one query instead of many.
Turso / SQLiteUsers, password hashes, sessions, API keys, integrations, review items, ingest ledger.Plain relational data with constraints, and a free hosted tier. Locally it falls back to a file, so there is nothing to set up.

The graph model

Three kinds of edges, and that is all:

(Node {kind: fact | belief | decision | action}) -[:DEPENDS_ON]-> (Node) what this was built on (Node) -[:FROM]-> (Source {trust}) where a fact came from (new) -[:SUPERSEDES]-> (old) a fact was replaced

Staleness is reachability over DEPENDS_ON. Taint is reachability to an untrusted Source. Both are computed when you ask, not stored, so they can never drift out of date.

One message, start to finish

Follow a single email through the system.

It arrivesA connector (here, email forwarding) turns the raw message into a common shape: text, author, source, and a verdict on whether the sender is outside the company.
Duplicates are droppedEach message has an external id (an email message-id, a Slack timestamp). The ingest ledger remembers which ones were seen, so retries and replays do nothing.
Claims are extractedAn LLM reads the text and returns structured claims with the exact quote and a confidence. If no OpenAI key is set, a rules engine does a simpler pass.
Each claim is reconciledIt is compared with existing facts: the same (confirm), new (add), or contradicting (supersede).
Risky changes are queuedA stranger, low confidence, or a possible conflict goes to the review queue instead of changing memory.
Memory is updatedSafe changes are applied. A superseded fact turns everything that depended on it stale, and the response lists the impact, including actions already taken.
The firewall guards actionsLater, an agent proposes “wire $48,000”. Its ancestry reaches the stranger’s email, so it is tainted and blocked. The check returns the exact path.

How a request is handled

  1. Authenticate. A browser session cookie, or Authorization: Bearer sk_….
  2. Authorize. The role must be high enough: viewer for reads, member for writes, admin for keys and settings.
  3. Open the workspace graph. The graph name comes from the workspace id, never from the request body.
  4. Validate input. Every body is checked against a schema before anything touches the database.
  5. Respond uniformly. Errors are JSON with a status and details. If FalkorDB is down you get a clear 503, not a hang.

The stack

  • Next.js (App Router) serves the website, the REST API and the MCP endpoint from one deployment.
  • FalkorDB for the graph, libSQL for the control plane.
  • Model Context Protocol SDK for the agent interface, over streamable HTTP and stateless: a fresh server per request.
  • OpenAI for claim extraction and audio transcription (optional; there is a rules fallback).
  • Zod for validating every input.