Termitaria
Command on the surface. Agents work in the tunnels. The colony remembers.
Built for the Nebius × NVIDIA Global AI Hackathon — Best Apps & Agents track.
Inspiration
A single chat window can answer a question. It cannot reliably act as the researcher, the reviewer, and the person who remembers last week's papers.
Those jobs need different instructions, tools, and memories. They also need a place where papers, claims, and working notes persist after the chat has scrolled out of view.
We kept running into the same problem in our own research workflow: the model that generated a hypothesis was also grading it, while the context behind a number disappeared as soon as the thread moved on. We wanted a system where specialists could work on one project, challenge one another, and leave the project better than they found it.
The name comes from termitaria, the mounds built by termites. No single termite has the blueprint; the colony maintains it through shared signals. We apply the same idea to intellectual work: the orchestrator works on the surface, specialists work in the tunnels, and the colony remembers.
What it does
Create a project and choose a team template, or paste in your own SwarmSpec. Add notes, links, or files to the project, then send a message.
The orchestrator plans the work and delegates tasks to specialists. The thread shows the conversation, while the swarm view shows who is speaking. The results are saved as artifacts, such as a sourced market brief and slide deck for Market Research, or a reviewed hypothesis and experiment plan for Autoresearch.
The repository includes two example teams:
- Copper Desk: desk lead, market researcher, and reviewer. It prices the previous full week, explains the move with sources, and publishes a report and slide deck. The reviewer removes any number not returned by the tools.
- Autoresearch: principal investigator, literature collector, idea generator, experiment designer, skeptical reviewer. It produces a reviewed hypothesis and an experiment plan, written against the project's knowledge graph.
Both teams run on the same harness. Roles, tools, budgets, and memory policies live in YAML, so a new scenario does not require a separate codebase.
The team is a spec
A SwarmSpec defines the orchestrator, the specialists, the tools each one may call, and the token budget. The Go control plane compiles it into a running colony: one supervisor per agent, one actor per session, and policy checks for turns and budgets.
goal: Track the copper market and produce one reviewed trading brief.
agents:
- id: desk
kind: ORCHESTRATOR
rolePrompt: ...
- id: researcher
kind: WORKER
tools: [web_search]
- id: critic
kind: WORKER
tools: [kg_query]
Sessions are durable logs stored in NATS JetStream. The session actor is the single writer. Workers never write to the session log; they stream tokens, and the actor appends the final reply.
If an actor crashes, it rebuilds its state by replaying the event log:
$$ S_t = \sigma(S_{t-1}, e_t) $$
where $e_t$ is the next event in the log and $\sigma$ is the actor's transition function. This single-writer boundary makes replay and idempotency tractable: task IDs are derived from the triggering message ID, and duplicate deliveries collapse into one task.
The colony has two memories
The key design choice is that the two memory systems are strictly separate.
| Store | Who can read it | What it holds |
|---|---|---|
| Knowledge graph | Every agent in the project | One Graphiti graph in FalkorDB. Graphiti reads document text and writes entities and fact edges. |
| Agent memory | That agent only | Role, rules, principles, and episodes. Postgres stores the items, while Qdrant stores that agent's vectors. |
For project $p$ and agent $a$:
$$ G_p = (V_p, E_p), \qquad R(a) = M_a \cup G_p $$
$$ M_a \cap M_b = \varnothing \quad \text{for } a \neq b $$
A recall query against agent memory returns the top $k$ items belonging to that agent:
$$ M_a^{(k)}(q) = \operatorname*{topk}_{m \in M_a} \operatorname{sim}(q, m) $$
It does not return graph nodes. The graph is the project's library; agent memory is each specialist's private notebook. Agent A cannot recall Agent B's notes.
When a session closes, each participant stores its own episode. Documents added to the project go into the knowledge graph, not into anyone's private memory.
That separation changes how the agents work. The reviewer can be skeptical without inheriting the researcher's working notes, while the researcher can accumulate hunches without polluting the shared record. The project preserves what the team learned; each agent keeps what it learned privately.
Nebius + NVIDIA
Every model call from a worker uses the OpenAI-compatible chat-completions API. For the hackathon, those calls go to Nebius Token Factory and use an NVIDIA Nemotron 3 open model.
- Super:
nvidia/nemotron-3-super-120b-a12bfor everyday tasks such as collecting, drafting, and delegating. - Ultra:
nvidia/Nemotron-3-Ultra-550b-a55bfor demanding tasks such as skeptical reviews and final synthesis.
The Go processes never load a model. They publish tasks and stream tokens back. This separation lets a demanding review run on Ultra while the rest of the colony stays on Super.
SwarmSpec records the tier for each role, and the worker resolves the corresponding model through Token Factory. In the current build, the model picker sets the tier for each turn; automatic per-role routing is next.
The worker also runs without an API key, returning a stub stream so the UI can still be tested. During development, a thin provider adapter let us iterate cheaply with another model before switching to the final Nebius/Nemotron path. Keeping that boundary in place meant the provider change did not leak into the agent code.
How we built it
We started with the contracts. Protobuf definitions specify the session, task, memory, and graph-edge interfaces. The Go control plane owns the gateway, dispatcher, session actors, and budget checks. The Python worker runs the agent graph, tool calls, delegation, and streaming. The memory service handles per-agent recall and writes; the graph service handles Graphiti ingestion and project-graph queries. The Next.js client provides the product surface: channels, threads, the swarm view, artifacts, and the knowledge graph.
The turn loop is intentionally simple:
- Your message is added to a session.
- The session actor decides who speaks next and publishes a task.
- Before the task runs, the dispatcher loads the agent's memory and relevant context from the project knowledge graph.
- A Python worker calls Nemotron through Token Factory and streams the reply to the UI.
- The session actor appends the completed reply.
- When the session closes, each participant stores its own episode.
The control plane is written in Go because sessions, turns, and budgets require tight, predictable policy checks. The workers are written in Python because that is where the agent ecosystem lives: LangGraph, MCP, document parsing, and Graphiti. This split keeps the control plane from blocking on inference and lets workers scale independently.
Challenges we ran into
Coordination was harder than generation. The first version could produce a good paragraph, but it could not reliably decide who should speak, who should write, or what should be shared. We had to make the orchestrator understand that a coworker runs within its turn and cannot delegate again; that the reviewer's job is to reject unsourced numbers, not rewrite the brief; and that a plain reply does not end a turn. Most of our prompt work focused on defining boundaries, not on improving eloquence.
The orchestrator needed to understand its team. In an early end-to-end run, it knew its own role but saw only its coworkers' names, so it guessed. We added a team roster containing each coworker's role prompt, visible only to the orchestrator. We also made sure a one-agent swarm does not receive a delegate tool with no valid target. That change eliminated wasted rounds and made delegation more deliberate.
Models spend tokens in ways the UI does not show. Reasoning models may use most of their budget before writing anything, so a low cap can produce an empty response. We learned to keep production budgets generous, make tool errors actionable ("the section is 12 characters; it needs at least 40") instead of returning "too short", and test with small, explicit scenarios before a demo.
Two memories result in two failure modes. Early recall could leak a hypothesis from one project into another. We scoped episodes by project and made the isolation rule explicit. The graph and agent memory also fail differently: the graph is shared and eventually consistent because Graphiti extraction is asynchronous, while agent memory is private and immediate. The UI needs to explain that distinction without forcing users to learn the architecture.
Deployment was part of the product. A demo that runs only on the machine where it was built is not a usable demo. We ran a clean-room test from a copy with no .env, generated protobufs, virtualenv, node_modules, or project data. We followed the README, generated the bindings, installed the dependencies, added only two keys to .env (Nebius and Tavily), and ran the stack with an empty home directory so no local secrets could leak in. That test exposed several non-agent problems: a missing make proto step, a sample WORKER_AGENTS list that excluded most agents, and infrastructure variables that the code did not actually read. Fixing them turned "works on my machine" into something a stranger could run.
Provider portability without a rewrite. We wanted to iterate cheaply, but the final submission had to run on Nebius with NVIDIA models. A small adapter layer kept provider-specific code out of the agent logic. We verified the Nebius path with DeepSeek on Token Factory first, then with Nemotron, using small token budgets before running longer demos. The same boundary let us make Tavily the default search provider and keep Brave as a fallback without changing the agent prompts.
What we learned
- A team is a configuration, not a codebase. Once roles, tools, budgets, and memory policies moved into SwarmSpec, adding a new scenario stopped meaning "build a new app"; it meant "define a team".
- Memory isolation is a product feature. Users do not want a shared pot of context. They want a project library and a private notebook. The database boundary is the product boundary.
- Multi-agent systems are coordination systems. The model is only one component. The hard parts are turn-taking, single-writer discipline, budget policy, and making failures understandable.
- Observability is part of the demo. Per-model token usage, tool-call logs, and a clear account of which provider and model actually ran are not just debugging details; they are how you build trust in the system on stage.
- Deployment is a feature. A clean clone, a short .env, and a README that a stranger can follow are worth more than one more clever prompt.
What's next
- Apply the SwarmSpec tier policy to each delegate, so the reviewer can run on Ultra while the rest of the team stays on Super without changing the model picker manually.
- Move long-running ingestion and research jobs to Nebius Serverless Jobs, and move embedding and extraction workloads to Serverless Endpoints.
- Add an evaluation harness for delegation quality and claim faithfulness, so prompt changes are measured rather than judged by intuition.
- Ship more teams: a decision room, a lesson desk, a product studio, and a story room. The harness stays the same; only the YAML changes.
Try it out
The repository is at Termitaria. The README lists the startup steps:
install the prerequisites, run uv sync --extra memory and npm install --prefix web, run make proto, copy
.env.example to .env, add Nebius Token Factory and Tavily keys, and run make up.
A fresh clone has no projects. Create one, choose copper-desk, and ask what moved copper last week. The colony handles the rest.
Built With
- falkordb
- go
- graphiti
- langgraph
- multi-agent
- nats
- nebius
- nemotron
- qdrant
Log in or sign up for Devpost to join the conversation.