Oliver Backend Spec
Status: Go MVP (multi-tenant workspaces) · Updated: 2026-07-03
This backend module implements Oliver. Finished local-agent turns, connector records become immutable Postgres events. Context is derived on read from those raw events.
One rule governs the system: capture raw and immutable, derive the rest, resolve at read.
Architecture
write: pipeline -> POST /events -> Go API -> Postgres
agent hook listener -> POST /events -> Go API -> Postgres
agent hook collector -> local queue -> POST /events -> Go API -> Postgres
-> deterministic classify -> Postgres
read: client / agent -> GET /topics/{key} -> Go API -> Postgres -> digest summary
client / agent -> POST /ask -> Go API -> extract topics -> Postgres -> digest summary
Module Layout
backend/
├── cmd/
│ ├── api/
│ ├── db-setup/
│ └── agent-hook-listener/
├── internal/
│ ├── contextlayer/
│ │ ├── auth/
│ │ ├── domain/
│ │ ├── http/
│ │ ├── rules/
│ │ ├── store/
│ │ └── summary/
│ └── ingestion/
├── ingestion/
├── config/
└── database/
frontend/
└── portal/
extensions/
├── agent-hooks/
└── chrome-extension/
infra/
├── aws/
├── docker/
├── local/
docs/
├── app/
├── internal/
└── public/
scripts/
pipelines/
frontend/ remains top-level because it contains frontend/client code. extensions/
contains installable local clients such as the Claude/Codex hook collector and Chrome
extension. The backend module contains Go service code, the compatibility hook listener,
schema, and config. Repo-root pipelines/, infra/, scripts/, and docs/ hold
batch jobs, operational assets, and long-form internal and public docs.
API Contract
Machine-readable route and schema details live in
backend/openapi/openapi.yaml.
The API is multi-tenant. Every scoped request authenticates a user with a bearer token
(Authorization: Bearer usr_...) and selects a workspace with an X-Workspace: <slug>
header. A user may read/write only in workspaces they belong to; a memberships row is
the permission. actor is always the authenticated user; any client-provided actor is
ignored. Postgres Row-Level Security isolates workspaces, so the API must connect as the
non-BYPASSRLS context_app role from backend/database/roles.sql.
Identity And Workspaces
- Browser users sign up through Supabase; the API creates a local shadow user after verifying a Supabase JWT.
POST /api-keyscreates a user-scoped API key for the authenticated user. The token is shown once; only its sha256 hash is stored.POST /workspacescreates a workspace for the authenticated user, who becomesowner.GET /workspaceslists the caller's workspaces and roles.GET /workspaces/{slug}/memberslists workspace members.POST /workspaces/{slug}/memberslets an owner grant an existing user access by email.cmd/issue-tokenmints tokens for non-interactive identities such as agents and connectors, optionally joining them to a workspace.
POST /events
{
"ts": "2026-07-01T15:42:10Z",
"session_id": "s1",
"kind": "agent_task",
"intent": "the raw prompt or source title",
"output": "the raw final message or source body",
"targets": [{ "type": "path", "value": "internal/contextlayer/store/store.go" }],
"idempotency_key": "stable-client-retry-key",
"model": "optional-model-name",
"usage": { "input_tokens": 0, "output_tokens": 0 }
}
Behavior:
- Generate a ULID-like
event_id. - Attribute
actorto the authenticated user; the workspace is taken fromcurrent_setting('app.workspace_id'), never the request body. - Compute
intent_keyfrom normalized intent for grouping, not deduplication. - If
idempotency_keyconflicts within the workspace, return the existing event id. - Insert the event, upsert its artifacts, and record
touchedverb-edge actions in one transaction. - Classify deterministic tags in a background goroutine.
GET /topics and GET /topics/{key}
Topics are the addressable unit of context. A topic key is namespaced, for example
component:storage.
GET /topics lists the topics (key, title, description, event_count,
last_activity), most active first.
GET /topics/{key} returns one topic's summary. Behavior:
- Resolve members from matching manual/newest automated classification rows plus current path rules.
- Check the disposable
summariescache against the member high-water mark. - On a miss, take a Postgres advisory lock, re-check, generate a digest summary from the
most recent
SUMMARY_MAX_EVENTS, and upsert the cache row. - Return
{ key, title, body, source_event_ids, provenance }.
POST /ask
{ "query": "What's the status of the backend?" }
A key-agnostic question: the caller does not name a topic; the backend extracts which topic(s) the question is about and returns their data. Behavior:
- Require a non-empty
query. - Tokenize the query (dropping question/status stopwords) and score every topic by how many terms overlap its key value, title, and description.
- Resolve the top matches (up to 3) exactly like
GET /topics/{key}. - Return
{ query, matched_keys, topics: [ { key, title, body, source_event_ids, provenance } ] }. No match yields an empty list, not an error.
Topic extraction is deterministic — no LLM provider is required, so /ask works in every
environment.
Other Endpoints
GET /eventslists recent events, optionally windowed bytimestamp,duration, orsince_hours.GET /fileslists connector documents touched by events.GET /healthreturns{ "status": "ok" }.
Data Rules
- Every tenant-owned table carries
workspace_idand is isolated by Row-Level Security, scoped to theapp.workspace_id/app.user_idset per request.workspaces,users, andapi_tokensare global because they are resolved before a workspace is selected. - Authentication is user-scoped (
api_tokens.token_hashmaps touser_id); authorization is amemberships(workspace_id, user_id, role)row. Tokens are never workspace-bound. - Source tables
events,actions, andclassificationsare append-only. Database triggers reject update and delete. artifactsandtopicsare mutable identity tables (upserted on write).summariesis the disposable cache.- Path targets must be repo-relative POSIX paths.
- Every topic key must be namespaced:
component:storage,project:oliver,topic:caching. - Manual classifications are always preserved. Among automated classifier versions, only the newest version for an event participates in resolved tags.
Ingestion
Finite fetch sources live in repo-root Python pipelines:
notiongoogle_docsslackgithub
Each pipeline requires the Oliver API to be running and must post normalized
POST /events payloads with Authorization: Bearer ... and X-Workspace headers,
stable targets, and idempotency keys.
extensions/agent-hooks is the default local path for Claude/Codex developer-agent
events. It installs repo-local hook config and stores native hook payloads under
.oliver/agent-hooks. Its uploader normalizes queued payloads into local_agent_event
records and posts them to authenticated POST /events.
cmd/agent-hook-listener is a localhost adapter for direct HTTP hooks on
/agent-hooks/{agent}; it normalizes each payload and posts the resulting event to
POST /events.
Acceptance Tests
Run from backend/:
go test ./...
The suite covers health, verbatim event storage, authenticated actor attribution,
workspace membership isolation, idempotency, deterministic tags, topic listing and
summaries, key-agnostic /ask topic extraction, cache invalidation, append-only triggers,
and concurrent inserts.
Phase Next
- Friendlier workspace invites for emails that have not signed up yet, such as a claim code or pending-invite flow.
- Per-workspace summary-cache invalidation on path-rule change.
- Switch the deployed runtime
DATABASE_URLto thecontext_approle so RLS isolates. - Native Go Anthropic classifier and summary integrations.
- Provider-agnostic LLM client configuration beyond the first Anthropic integration.
- Native Go S3/R2 output offload and bounded hydration.
- Durable classifier queue/backfill.
- Outcome watchers for merge/revert/supersede signals.
- Denormalized resolved tags for larger installations.