Architecture¶
Athena is a Memory Operating System: a single service that gives every agent in a fleet persistent, multi-tier memory. Agents talk to one API; behind it, a background pipeline continuously distills raw conversation into summarized topics and, eventually, into a knowledge graph.
The system at a glance¶
flowchart LR
subgraph Clients
A[Agent / Platform]
end
subgraph Athena["Athena memory-server"]
GW[REST :8080<br/>grpc-gateway]
G[gRPC :9090]
S[MemoryService]
W[Cognitive Workers]
P[Promoter · 30m]
AR[Archiver · 60m]
end
subgraph Stores
R[(Redis<br/>STM hot + queues)]
M[(MongoDB<br/>events + chains)]
V[(Milvus<br/>chain embeddings)]
KG[(ArangoDB<br/>athena_ltm graph)]
B[(Blob store<br/>MinIO/S3/GCS)]
end
A -->|REST| GW --> S
A -->|gRPC| G --> S
S --> R & M & B
W --> R & M & V
P --> M & KG
AR --> M & V & B
One binary, cmd/memory-server, hosts everything: the API, the worker pool, and the schedulers. There is no separate worker deployment.
One API, two protocols¶
The API is defined once in api/grpc/memory.proto. gRPC serves it natively on :9090; grpc-gateway translates the same RPCs to REST on :8080 using the google.api.http annotations. Every RPC therefore has a canonical REST mapping (documented per-endpoint in the API Reference).
Port 8080 additionally serves three non-RPC routes: /health (liveness, no auth), /metrics (Prometheus, no auth), and the gateway-mounted /api/v1/* routes.
Why this design?
Athena serves two very different audiences. Its primary consumers are platform Go services: latency-sensitive, high-frequency calls where gRPC's binary framing, HTTP/2 multiplexing, and generated typed clients pay off. But memory is also something scripts, benchmark harnesses, CronJobs, and curl need to reach, and those get plain JSON over REST with zero extra tooling.
Generating both surfaces from one proto eliminates contract drift: field renames, new endpoints, and deprecations propagate to gRPC, REST, and the OpenAPI spec mechanically via make generate. Auth middleware wraps the service once, so tenant isolation is never implemented twice. The accepted trade-offs: a codegen step in CI (generated *.pb.go / *.pb.gw.go files are never committed), grpc-gateway's URL-mapping constraints, and gRPC→HTTP error translation being fixed by convention (InvalidArgument→400, NotFound→404, FailedPrecondition→412, Internal→500).
Write path¶
When an agent calls StoreInteraction or StoreEvent:
- Dual-write. The event lands in the Redis STM window (
stm:{tenant}:{user}:{agent}, newest-first) and in MongoDBcognitive_eventsfor durability. Redis makes reads instant; MongoDB survives restarts. - Blob offload. Binary payloads are uploaded to blob storage and replaced with a URI.
- Task enqueue. For user messages, a
cognitive_chain_checktask is pushed to a per-user Redis queue, and that queue's name is pushed to the global dispatcher queue: a two-level design that keeps one noisy user from starving others (details).
The write returns as soon as step 1–3 complete. Everything cognitive happens asynchronously.
Background pipeline¶
Three independent loops move memory between tiers:
| Loop | Trigger | Job |
|---|---|---|
| Workers (×2) | Task queue (BRPop) |
Detect topic breaks, form cognitive chains (Chain Formation) |
| Promoter | 30-minute ticker | Score chain heat; promote hot chains to the LTM graph (Heat & Decay, Promotion) |
| Archiver | 60-minute ticker | Freeze cold chains; delete their vectors and blobs (Archival) |
Why these cadences?
Each ticker only needs to sample faster than its signal changes. The promoter's input is heat, which decays with \(\tau = 24h\): in 30 minutes heat moves by roughly 2%, so a 30-minute sweep can never miss a promotion window. It's also the expensive loop (every pass re-scores all active chains; every promotion is an LLM call plus graph writes), so its interval is effectively an LLM budget knob. Nothing user-facing waits on it either, since new chains are already searchable in MTM seconds after formation. The archiver's decision inputs are "idle > 7 days" and a heat floor: against a 7-day window, hourly is massively oversampled, and the work is pure janitorial cleanup with no LLM cost. The 2:1 ratio encodes promotion is time-sensitive-ish and costly; archival is neither.
Both are env-tunable (MEMORY_OS_PROMOTER_INTERVAL_MIN, MEMORY_OS_ARCHIVER_INTERVAL_MIN); local development sets both to 1 minute (run_local_server.sh) so you can watch the pipeline work without waiting.
A fourth process, Graph Analytics, runs outside the server as a daily Kubernetes CronJob calling POST /api/v1/admin/analytics/trigger. It must never run in-process: Pregel loads the whole graph into memory.
Read path¶
Reads answer from the tiers directly, without touching the pipeline:
GetContextreturns the STM window from Redis (plus MTM pages: semantic if aqueryis given, otherwise recency-ranked).SearchMemoryembeds the query, searches Milvus for matching chains across all the user's agents, and enriches results from the LTM graph.
The stores and what owns them¶
| Store | Owns | Written by | Read by |
|---|---|---|---|
| Redis | STM window, task queues, task results | API, workers | API, workers |
| MongoDB | cognitive_events (raw log), cognitive_chains (MTM), sessions |
API, workers, promoter, archiver | everything |
| Milvus | one embedding per chain | workers | SearchMemory, GetContext(query) |
| ArangoDB | athena_ltm: 5 vertex collections + MemoryEdges |
promoter, analytics | SearchMemory enrichment |
| Blob store | oversized event payloads | API | consumers via URI |
Athena owns exactly one ArangoDB database
Everything Athena writes lives in athena_ltm. It never creates or touches other databases on the instance, so co-hosting other applications' databases on the same ArangoDB deployment is safe.
Multi-tenancy¶
Every key, document, and vector is scoped by the tenant → user → agent hierarchy; identity comes from the auth layer, not from request bodies. Sessions & Identity covers the model; the Security Model covers enforcement.
Deployment shape¶
A single stateless container (multi-stage Alpine build, non-root UID 1000, health-checked on /health). State lives entirely in the five stores, so you scale by running replicas; the two-level task queue and Redis-based coordination keep workers from colliding. Run it anywhere containers run: Docker Compose for local development, Kubernetes or a managed container platform in production. See Deploying Athena.