Documentation

From API key to a remembering agent  //  quickstart ~15 minutes

Hivemind gives your agents persistent shared memory and a working-context compiler: store what matters, recall it semantically, and assemble a token-budgeted, fully audited prompt for every turn. Your model, your key — Hivemind never calls an LLM.

Quickstart

1. Get your key

  1. Sign in and subscribe (14-day trial, card up front, nothing billed today).
  2. Under Storage, connect your Qdrant cluster — its URL and API key. Your memory lives there, not with us. Any reachable Qdrant works: self-host the open-source server on your own infrastructure, or use Qdrant Cloud — its free tier is enough to start.
  3. Under API Keys, create a key. It is shown once — store it as HIVEMIND_API_KEY.

2. Integrate — any language

Hivemind is a plain HTTPS API: the whole loop is two JSON calls per exchange, from any language. The wire protocol below is the complete integration contract, with the exchange translated into curl, JavaScript, Python, and Go.

3. Prove the connection

A new workspace starts empty — nothing is written into your cluster except what you and your agents store. Prove the connection with one store and one recall:

curl -s https://api.hivemind.militant.ai/hivemind/v1/create_memory \
  -H "Authorization: Bearer $HIVEMIND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content": "Hivemind connected: this workspace remembers from here on.", "metadata": {"topic": "setup"}}'

curl -s https://api.hivemind.militant.ai/hivemind/v1/recall_memory \
  -H "Authorization: Bearer $HIVEMIND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "when did this workspace start remembering?", "top_k": 5}'

Point your agents at this documentation for how the system works — the loop, what to store, and the full reference are written to be read by them as much as by you.

The loop: three lines inside your agent

packet = POST /turn { user_input, session, budget, pair-id, history }   # store → recall → compile
reply  = your model, run on packet.bundle.messages                       # your model, your key
POST /conversation/store { reply, same pair-id }                         # completes the exchange

Working code for that loop, in four languages, is in the wire protocol.

One /turn is one request: the service stores the user message (receipted), semantically recalls relevant memories and past conversation, and compiles local history + recalled records + active holds into a token-budgeted bundle. The response’s bundle.messages is a contiguous, internally-ordered block: use it as the entire prompt, or surround it — your system prompt above, turn-local instructions below; the packet keeps its place either way. Never insert into or reorder inside the block, and if you wrap it, size budget_total as your model window minus your wrapper. The store call files the reply and completes the exchange: the whole loop is two calls, with your model between them.

Conversation is remembered as call/response exchanges. Recall matches your query against both sides of every past exchange and returns whole exchanges — one result slot per exchange, rendered call-then-response, never an answer without the message that produced it. Facts that appear only in a reply are just as findable as the calls that prompted them. The recall pool sizes itself from your session's token budget, and the compiler's budget admission decides what enters the bundle; pass recall_top_k only to force a fixed pool. The store call completes the exchange — treat it as part of the loop. Durable facts that should stand alone — decisions, outcomes, lessons — belong in deliberate memory.

The wire protocol — any language

The service is language-agnostic. One exchange is two HTTPS calls, with your model between them; any client library is a convenience layer over exactly this.

Call 1 (/turn) stores the user message, recalls, and returns the compiled prompt packet — the response's data.bundle.messages is the prompt block for your model, data.receipt the audit record. Call 2 (/conversation/store) records the reply, completing the exchange with the same exchange_pair_id and turn_number. The whole exchange, translated:

# 1. compile the turn
curl -s https://api.hivemind.militant.ai/hivemind/v1/turn \
  -H "Authorization: Bearer $HIVEMIND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "user_input": "Can we ship the migration tonight?",
    "session_id": "session-1",
    "turn_number": 7,
    "budget_total": 8192,
    "exchange_pair_id": "xp-7f3a1c",
    "messages": [{"role": "user", "content": "Can we ship the migration tonight?"}]
  }'

# ... run your model on data.bundle.messages ...

# 2. record the reply (same exchange_pair_id + turn_number)
curl -s https://api.hivemind.militant.ai/hivemind/v1/conversation/store \
  -H "Authorization: Bearer $HIVEMIND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Yes — window is clear until 03:00 UTC.",
    "session_id": "session-1",
    "turn_number": 7,
    "role": "assistant",
    "exchange_pair_id": "xp-7f3a1c"
  }'

The protocol's rules — every client must honour these:

  • Pairing. Generate a fresh exchange_pair_id per turn; the record call must reuse it and the turn number, so the exchange is stored as one call/response pair. Any collision-resistant unique string works — the project's convention is a time-ordered UUIDv7, so the id doubles as a temporal datum.
  • Aborted generation: record nothing. If the user cancels mid-stream, make no call — the stored user message stands as an unanswered call, and the next message is a new turn. Never record a partial reply as if it was said, and never send empty content.
  • Never retry a timed-out turn. A timeout does not mean the request failed — re-sending can store the message twice. Retry only requests that failed before any response began.
  • Honour Retry-After. A 429 carries it; back off that many seconds and retry. That is the service asking your client to slow down, not an error.
  • Budget ceiling. budget_total at most 128,000; a 400 names the limit.
  • Optional: select a project with the X-Hivemind-Project header.

Every other endpoint speaks the same JSON envelope with the same auth header. The full API reference documents every endpoint — request fields, response shapes, errors — with working examples in all four languages, plus the complete client implementation guide.

The working context — representative output

This is what one /turn (or /context/compile) hands back, rendered: the trace, the admitted items, the accounting, and the compiled messages your model receives. Toggle the response JSON to see the exact wire shape.

WORKING CONTEXT BUNDLE / CONTEXT.COMPILE RECEIPT e9b3c1a0

TRACE

Session ID
session_001
Turn Number
14
Built At
2026-07-05T14:32:11+00:00
Event ID
event_001
Trace ID
trace_001
User Input
Find the lowest-risk path to stabilise checkout latency before peak traffic.
Context Messages
13

ITEMS (KIND · SOURCE, TOP 5 OF 13)

  • MEMORY · HIVEMINDmemory-9d41c2

    Checkout latency spiked during the payment provider failover; regional failback plus cache warmup restored p95 within 11 minutes.

  • DIRECTIVE · DIRECTIVEcontext-briefing-14

    Prefer reversible mitigations and preserve checkout stability during active traffic.

  • MESSAGE · SYSTEMcontext-artifacts-14

    Artifacts: Checkout Latency Playbook v3.2; SLO dashboard shows p95 above threshold in us-east-1.

  • MESSAGE · RECALLED_CONVERSATIONrecalled-conv-310

    Last incident we deferred config changes and scaled the payment worker pool first.

  • HOLD · SYSTEMhold-8c2f

    Checkout change freeze: avoid changes that increase regression risk during active checkout traffic.

+ ranked, deduped, and trimmed before final assembly

ACCOUNTING (API DATA)

  • POLICYbalanced

    Balanced admission inside max_context_tokens with per-class caps on memory, tool, and hold items.

  • COUNTSsources

    Admitted items counted by source: hivemind, conversation, recalled_conversation, directive, system.

  • DECISIONSdiagnostic

    Every candidate gets an action: included, dropped_budget, dropped_policy, or trimmed_budget. Not prompt content.

  • TRACEsnapshot

    Compiler diagnostics remain inspectable alongside the assembled bundle.

MESSAGES (COMPILED 13)

  • SYSTEMstatus line

    Context: 3220/4096 tokens | turn 14 | 13 items

  • SYSTEMdirective

    Prefer reversible mitigations and preserve checkout stability during active traffic.

  • USERactive input

    Find the lowest-risk path to stabilise checkout latency before peak traffic.

+ the remaining 10 messages of this example's bundle: memory, artifacts, hold, and recalled conversation

TOKEN COUNT: 3220 BUDGET AVAILABLE: 876 CONVERSATION TOKENS: 310 HOLD TOKENS: 84 SOURCES: hivemind 4 / system 4 / conversation 2 / recalled_conversation 2 / directive 1

Deliberate memory

# store a durable fact with your metadata
curl -s .../hivemind/v1/create_memory -H "$AUTH" -H "$JSON" -d '{
  "content": "The staging database is rebuilt every Monday 03:00 UTC; do not schedule migrations near that window.",
  "metadata": {"project": "atlas", "topic": "ops"}
}'

# recall it, semantically or filtered
curl -s .../hivemind/v1/recall_memory -H "$AUTH" -H "$JSON" \
  -d '{"query": "when is it safe to run a staging migration?", "top_k": 5}'
curl -s .../hivemind/v1/recall_with_metadata -H "$AUTH" -H "$JSON" \
  -d '{"query": "database", "metadata": {"project": "atlas"}, "top_k": 10}'

Store decisions and their rationale, lessons learned, durable facts, and outcomes. Attach consistent metadata keys — they are what make filtered recall and surgical, audited deletion possible later. Empty recall on a new workspace returns [] — that is “nothing relevant stored yet”, not an error. /recall_memory searches deliberate memories only: conversation history is a separate record class, recalled automatically inside the loop (or explicitly via /conversation/recall).

The audit trail

curl -s ".../hivemind/v1/receipts?session_id=session-1&limit=50" -H "$AUTH"

Every operation — store, recall, compile, delete — emits a persisted receipt with lineage: what ran, what it consumed, and what it produced.

The context window

The compiled packet is not an opaque blob — it has a fixed anatomy, and knowing it is how you place every kind of content deliberately. A real compile looks like this (roles on the left):

system     === AGENT FIXED CONTEXT BUFFER ===
system     [your operator_briefing — persona, rules, mode flags]
system     Context: 3210/8192 tokens | 2 holds active (350 tokens) | ...
system     === HOLD === id: stop-order · title · tokens · held_since ...
system     [artifacts_context — "Available files:" when supplied]
system     === SEMANTIC RECALL BUFFER ===
system     [recalled deliberate memories, relevance-ranked]
system     === SEARCH RECALL BUFFER ===
user       [recalled exchange: the call]
assistant  [recalled exchange: the response]
system     === ACTIVE CONVERSATION BUFFER ===
user       [recent conversation, reading order]
assistant  ...
user       [the current message — always last]

Three things to read off that structure:

  • The fixed context buffer is the boundary your integration works within. Everything a developer would normally wrap around a prompt has a named slot inside the packet instead — and content inside is budget-managed, admission-decided, counted in the status line, and receipted; content wrapped outside is invisible to all accounting.
    External contentIts slot
    System prompt / persona / rulesoperator_briefing — protected system directive, re-sent per turn, never stored
    User profiling, standing operational stateholds — persistent until released, labeled, token-visible
    Documents, file excerpts, uploadsartifacts_context — this compile only
    Mode flags, turn-local steeringoperator_briefing per-turn override
    Tool definitionsnot context — your API's separate tools field
  • The model sees its own budget. The status line (third message) reports tokens used against budget, active holds and their cost, every single turn — the feedback that lets an agent manage its own window.
  • The packet is self-labeling and position-independent. Its section markers announce its regions; nothing inside references absolute position. Stack your own messages above or below and every internal relationship holds — Hivemind can be the entire window or a region within one.

API surface

Every call. All responses arrive as {"ok": true, "data": ..., "request_id": ...} envelopes; the envelope carries them.

EndpointWhat it does
POST /turnThe composed exchange: store the call, inline recall (both searches), compile. Params: budget_total, messages (local history), operator_briefing, artifacts_context, holds, author, recall_top_k (omit for budget-dynamic), policy. Returns bundle + receipt + recall counts + warnings + timings
POST /conversation/storeThe reply half of the exchange, paired by exchange_pair_id
POST /create_memoryDeliberate durable memory with your metadata
POST /recall_memorySemantic recall over deliberate memories only
POST /recall_with_metadataSemantic recall scoped by exact metadata matches
POST /conversation/recallManual episode recall: session_id=None searches all sessions (one result per exchange); a session id replays that session in full (both halves, newest-first)
POST /context/compileRaw compile for custom loops built around your own recall
GET /receiptsAudit trail, newest-first; filter by session_id / operation
GET /usageStorage, point counts, and the limits your subscription carries
GET /exportEvery record you own, paged; client.export_to_file(path) writes JSONL. Portability by contract
POST /delete_by_metadataSoft-deletes everything matching all given keys. Your key is the authorization (included with your subscription). Out of recall immediately, recoverable until purged, always receipted. Mind filter breadth — what your agents may delete is your harness's policy

The turn, in detail

Everything turn() accepts, and what each input does:

ParameterBehavior
user_inputThe initiating message. Stored as the call half of a new exchange, used as the query for both recall searches, and always the packet's last message. Must be non-empty
budget_totalThe compile's token budget — the single number that governs the whole window. Set per session or per turn; if you wrap the packet, size it as your model window minus your wrapper. Maximum 128,000 — a larger value is rejected with 400 VALIDATION_ERROR whose details.max_budget_total names the limit. The budget bounds what is surfaced per turn, not what is remembered: memory is unbounded
messagesYour local history: {role, content, timestamp} dicts, oldest first, real timestamps (the compiler orders conversation by them). Your client maintains and sends this buffer every turn, and rebuilds it from its own store after restarts (see Building a chat host)
operator_briefingYour system layer — compiled as a protected directive at the top of the packet, re-sent every turn, never stored, never recalled
artifacts_contextOne-compile content (document excerpts, file listings). Rendered in the fixed buffer this turn only; never stored
holdsPinned state riding every compile until you clear it — see the chat-host section for the lifetime contract
authorWho produced this call — identity metadata, stored on the record and surfaced as the message's name
recall_top_kOmit for a budget-scaled candidate pool (the default and usually right); set to force a fixed pool, gated by your subscription's ceiling
policyAdvanced: a compile-policy override (item caps, include switches) for this turn — most integrations never need it

And everything it returns:

FieldContents
bundle.messagesThe compiled packet — the block you send to your model
bundle.token_count / budget_totalThe packet's measured size against the budget — the context meter
receiptThe compile's audit record; receipt_id links it in the receipts trail
storedThe stored call half's id — empty if the store degraded (see warnings)
recalled_memories / recalled_conversationHow many records each recall search contributed to the compile
warningsDegradations that didn't fail the turn — a failed store or recall search reports here and the compile proceeds without it
timingsServer-side phase breakdown, seconds: embed_s (encoding your input once, shared by both recall searches), store_and_recall_s (the store and both recall searches, run concurrently), completion_s (fetching missing exchange halves), compile_s (assembly and admission), total_s. A slow turn names its own bottleneck

The client-side state these calls assume (history buffer, open exchange, holds) is specified in Building a chat host.

Conventional environment names

Nothing requires these, but examples and tooling use them:

Env varMeaning
HIVEMIND_BASE_URLService root — the host only (https://api.hivemind.militant.ai); clients append /hivemind/v1
HIVEMIND_API_KEYSent as Authorization: Bearer <key>; identifies and tenant-binds in one step
HIVEMIND_PROJECTOptional project binding, sent as X-Hivemind-Project
HIVEMIND_TIMEOUTClient request timeout in seconds — compiles at large budgets are multi-second operations; 120 is a reasonable default
HIVEMIND_BUDGET_TOTALDefault compile budget when your host does not set one per session

When storing exchange halves via POST /conversation/store, put the initiating message's text under additional_metadata.semantic_content on the reply half so a recalled reply carries its question, and additional_metadata.author on either half to attribute it.

Memory & metadata

Every stored record carries a record class, stamped at write time, and each recall path searches exactly one class — the classes never overlap:

ClassWhatRecalled by
semanticDeliberate memories (/create_memory)/recall_memory, /recall_with_metadata, and the loop's semantic recall — relevance-ranked
temporalConversation exchangesThe loop's conversation recall and /conversation/recall — returned newest-first
diagnosticReceipts, document referencesNever semantically recalled; scrolled by explicit query only

Metadata is the control surface. Two kinds of keys live on every record. Service-stamped keys are set for you at write time — filter on them freely, but don't overwrite them:

KeyMeaningWhy you care
typeWhat kind of record this is (conversation_exchange, usage_guide, your own values via metadata)The broadest filter; conversation records are identified by it
session_id / turn_numberWhich conversation, which turnSession replay, receipts filtering, chronological ordering of recalled exchanges
rolePositional: which side of the exchange — user initiated (the call), assistant answered — not who was talkingDrives exchange pairing and rendering; identity lives in author, never here
exchange_pair_idThe identity of one exchange — both halves share it, nothing else ever doesDrives one-slot-per-exchange dedupe and pair completion in recall
recall_modeThe record class: semantic, temporal, or diagnosticDecides which recall path can ever return this record
storage_modeinline or chunked (content over ~1,000 chars is split)Chunked records reassemble transparently on recall; chunk_group_id ties the pieces
timestampUnix time at storageOrders conversation into reading order; drives newest-first returns
recall_countHow many times recall has returned this record — incremented automatically after every recallThis is attention: it makes frequently-useful records gravitational sources in ranking (see below)
semantic_relativity, resonance_scoreQuery-time relevance: the engine's similarity for this query, and the final ranking score after resonanceRead them off recall results to understand why something ranked where it did
deleted_atSoft-deletion stampPresent = out of all recall, recoverable until purged

Caller-set keys are yours, supplied via metadata on remember() or additional_metadata on stores. Every one becomes an exact-match filter in recall_filtered and a deletion selector in delete_by_metadata — consistent keys are what make surgical operations possible later. The ones with built-in behavior:

KeyBuilt-in behavior
authorWho produced the message — surfaces as the chat message's name field in compiles and recalls; pass author on /turn and additional_metadata.author on the store
semantic_contentOn a reply half: the call text that produced it, so a reply recalled alone renders with its question — set it on the reply half of every store
tagsA list; shared tags create a ranking linkage between records (see below), and tags filter like any key
document_idTies records to a document; same-document records link in ranking, and it scopes recall_filtered for per-document recall
anything elseNo built-in behavior — pure filter/delete selector. project, customer_id, agent_run: whatever your operations need to slice by

Storage mechanics worth knowing: content over ~1,000 characters is chunked (each chunk embedded from its own text, so long content stays reachable at any depth; recall reassembles the whole record). Deletion is soft — a deleted_at stamp removes the record from all recall while remaining recoverable until purged. Ids are time-ordered UUIDv7 — the identifier is also the temporal datum.

Recall & ranking

Two recall paths, never conflated: inline — automatic inside turn(), on the raw user input, no query authored by anyone — and manual — the explicit endpoints with deliberate queries and explicit top_k. The inline path runs two searches concurrently: one over deliberate memories (the semantic record class) and one over conversation exchanges (the temporal record class).

The exchange is the recall unit

Ranked results are deduplicated by exchange_pair_id: when both halves of one exchange match your query, the better-scoring half takes one result slot and the freed slot goes to the next different record — so top-5 recall means five different past exchanges, never the same one twice. Dedupe is by identity, not similarity: three near-identical exchanges from three sessions have three pair ids and all three can rank. Every recalled exchange then renders whole, by one of two routes: a reply found alone carries its call via semantic_content, and a call found alone gets its reply fetched in one batched lookup before the compile. Session-scoped replay (/conversation/recall with a session_id) is exempt from all of this — replay returns every row verbatim.

Pool sizing

The pool is how many candidates recall hands the compiler; the compiler's budget admission — not the pool — decides what enters the packet. Omit recall_top_k and the pool scales with the budget: budget_total / 256, floor 5, ceiling 50, then clamped to your subscription's max_top_k. So a 2k-budget turn considers 8 candidates per search, an 8k turn 32, a 13k+ turn 50. Pass an explicit recall_top_k to force a fixed pool — explicit values above your subscription's ceiling are rejected with a 400 rather than clamped.

Ranking: relevance first, resonance second

The base score of every candidate is the engine's own similarity between your query and that record — nothing outranks being about what was asked. On top of that base, resonance adjusts the ordering:

  • Attention. Every time recall returns a record, its recall_count increments (asynchronously — it never slows your request). High-attention records — the top quartile within the current candidate set — become sources.
  • Sources pull their relatives, not themselves. A source never boosts its own score. It boosts candidates it is structurally linked to, with strength by relationship:
    RelationshipStrengthWhy it pulls
    Same exchange1.0The other half of a proven-useful exchange is almost certainly relevant with it
    Same chunk group0.8Pieces of one long record belong together
    Same session, within ±2 turns0.6Conversational neighbourhood — what surrounded a useful moment
    Same document_id0.5Material from one document travels together
    Shared tags0.3Your own declared grouping, weakest because broadest
  • Distance-gated and capped. A source's influence is divided by its own distance from the query — a popular record that has nothing to do with the current question exerts effectively nothing — and the total boost is capped at 2×, so attention can bend the ordering but can never invert a clear relevance gap. Popularity cannot beat being right.

Two practical consequences: tagging and document ids aren't just filters — they create the linkage structure ranking acts through; and the system adapts to your workload over time, because what your agents actually use accrues attention. Cross-session conversation results return newest-first; the compiler then lays recalled exchanges into the packet in reading order.

Budget & admission

budget_total is the authority: the compiler admits content until the budget is spent.

What fills the window, in what order

Protected core admits first, unconditionally: your operator_briefing and the current user message are always in the packet, whatever the budget. Everything else competes through balanced admission across sources — each candidate item belongs to one source, and sources are weighted:

SourceWhat fills itPriority
conversationThe live history you sent in messages, newest prioritizedHighest — the active conversation has the highest priority in the window
semanticRecalled deliberate memoriesSecond — durable knowledge relevant to this turn
searchRecalled past exchanges, in reading orderThird — episodic context from other sessions and older turns
toolTool-output items, when a caller passes themFourth — current-turn scaffolding
fixedHolds, artifacts_context, section markers, the status lineAdmitted with the fixed buffer; holds are capped only by max_hold_items if you set one

Weights bias, they don't ration: an empty source simply doesn't compete. With no recall to place, conversation takes all the space; a short conversation leaves the window to recall. Nothing is reserved for a source that has no candidates.

Caps scale with the budget

Two item-count backstops exist so a degenerate compile can't admit thousands of one-token fragments — and both scale so a large window always receives enough candidates: total items = max(50, budget_total/128), memory items = max(10, budget_total/512). At a 4k budget: 50 items, 10 memories. At 128k: 1,024 items, 256 memories.

The meter

The status line reports the economy inside the packet every turn — Context: 3210/8192 tokens | 2 holds active (350 tokens) | ... — where the model can read it and act on it. result.token_count / budget_total report the same numbers to your code, and they are the authoritative source for any context meter: local history length undercounts (it knows nothing about recall), and your LLM provider's usage measures a different tokenizer after your own additions.

Building a chat host

The two-call loop is the whole integration contract — but a long-lived, restartable, streaming host needs a layer around it that the loop alone doesn't provide. This is that layer, stated at the wire level: your client keeps a little state, and these are its rules.

  • Your client's state is three things: a turn counter (increment per turn), the open exchange (its exchange_pair_id and turn number, held between the /turn call and its /conversation/store), and a rolling local history buffer — the last few dozen messages of this session (40 is a good default), each {role, content, timestamp} with a real unix timestamp. Send that buffer plus the current message as messages on every /turn: it is the "recent conversation" the compiler combines with recalled memory, and the compiler orders it by timestamp — fabricated times scramble reading order.
  • Stream first, compile second. /turn does real work — a store, semantic recall, and a full context compile — and at large budgets that takes seconds, not milliseconds. If you call it before sending anything, streaming clients receive no bytes and many treat the connection as failed. Establish the stream first (headers or a keep-alive frame), then compile, then stream your model. data.timings carries the server-side phase breakdown for every turn, so a slow one names its own bottleneck in your logs.
  • Restarts: the history buffer is held in your process and is lost when that process stops. After a restart, rebuild it from your own store — nothing is re-sent to the service; the buffer is purely what you pass as messages. Map your conversation id to session_id so a restarted host lands in the same session; when a user clears a thread, start a new session id so the fresh thread doesn't inherit what they asked to be rid of.
  • Aborted generation: make no call. When a user cancels or a stream drops, discard the open exchange and move on. The user's message was already stored by /turn and stays recallable as an unanswered call — which is what happened. Never store empty content (the service rejects it), never store a partial reply as cleanup — the one legitimate partial case is a host that deliberately keeps what was shown on screen as what was said, decided after the stream ends.
  • Recording the reply carries two metadata conventions. On the /conversation/store call, put the initiating message's text under additional_metadata.semantic_content — it lets a recalled reply carry its question when rendered — and additional_metadata.author to attribute it. Both optional, both worth doing.
  • Continuation hops: role marks position in the exchange — which side initiated, which side answered — not human vs. machine. So feeding the model's own output back in as the next /turn (tool loops, inner monologue, "keep going") is correct use: the prior output is the initiating utterance of the next exchange, and recall will later find the exchange by it.
  • Authorship: who produced a message is a separate question from which side it sits on: pass author on /turn and additional_metadata.author on the store — identity as ordinary metadata, surfaced as the chat message's name field in compiles and recalls. Multi-agent transcripts stay attributed, and it filters: /recall_with_metadata with {"author": ...}.
  • Turn-local instructions: where content enters decides how long it lives. artifacts_context is included in one compile and never stored. operator_briefing is also compiled-only, but re-send it every turn — standing instructions and mode flags. holds is a list of {id, title, content} objects you keep client-side and pass on every /turn until you drop them — pinned operational state ("stop-order", "API is down") that never enters memory: not stored, not recalled, not in receipts. Durable facts go to /create_memory — forever, recallable from any session. Pick the shortest lifetime that does the job; anything longer persists into future context where it no longer belongs.
  • Context meter: read your gauge from the response's token_count against budget_total — the compiled packet's own numbers. Local history length undercounts (it knows nothing about recall); provider usage measures a different tokenizer after your own additions.
  • Control tokens: Hivemind stores and returns text verbatim — but recalled text re-enters your model's context later, and that model's tokenizer may remove its own special tokens or interpret them as control sequences. Strip or alter markers like <|...|> before storing.
  • Failure severities differ. A 429 carries Retry-After — sleep it and retry. A 402 storage cap blocks writes but recall and compile keep working — don't fail the chat. A failed /conversation/store means the chat already succeeded — retry it out-of-band rather than surfacing an error to the user. Never re-send a /turn that timed out: the request may have executed, and replaying double-stores the message.

Projects and storage

Your memory lives in a Qdrant cluster you own — we never hold your data. After subscribing, connect a cluster from the dashboard: paste its URL and API key, we validate it live (reachable, key accepted, collection created with its indexes) and requests start working within minutes. Until a cluster is connected, every request answers 400 with "No storage connected". Any Qdrant instance the service can reach works: Qdrant is open source, so you can self-host it on your own server or VPS and pay nobody for storage, or use Qdrant Cloud if you'd rather not operate it — its free tier is enough to start. Every turn round-trips to your cluster, so distance between the service and your cluster is latency, and your cluster's capacity sets your throughput. If your cluster is unreachable, requests fail closed rather than writing anywhere else.

A workspace holds multiple projects, each bound to one cluster (the same cluster or different ones). Projects are hard data separation: each resolves to its own storage, so records in one project are never candidates for recall in another — separate agents, environments, or customers of yours can share a workspace without sharing memory. Requests select a project with the X-Hivemind-Project header; without it, your default project serves. Credentials can be rotated and clusters moved per project at any time from the dashboard.

Errors and limits

StatusMeaning & what's in details
404 MEMORY_NOT_FOUNDNothing relevant stored yet — normal for a new workspace; proceed and store what you learn.
402 STORAGE_LIMIT_EXCEEDEDStorage allowance reached — only when a subscription carries one. Your memory lives in your own Qdrant cluster, which has no allowance imposed by us; its capacity is yours to manage. Where a limit applies it is writes only — recall and compile keep working, so a capped agent degrades to read-only memory instead of losing recall entirely. details carries max_storage_bytes and used_storage_bytes
429 RATE_LIMITEDPer-minute, per-workspace limit hit. The Retry-After header says exactly how long to sleep — use it instead of guessing a backoff
400 VALIDATION_ERRORMalformed request: empty content, missing session id, an explicit recall_top_k above your plan's ceiling, or a budget_total above 128,000. details.field names the offending field. Also "No storage connected" (details.action = connect_cluster): your workspace is active but no Qdrant cluster is connected yet — connect one on the dashboard
401 UNAUTHORIZEDMissing/invalid/revoked key, or an operation your subscription doesn't enable — the message names which. Key revocations propagate within ~5 minutes
5xx / timeoutService or storage trouble. Your call whether to fail open (proceed with local context only) or fail closed — decide once, document it in your host

All errors arrive as one machine-readable envelope shape: {"ok": false, "error": {"code", "message", "details"}, "request_id"}. The code values are stable strings (branch on them, not on messages), and request_id is the correlator to quote in a support request — it ties your call to our logs and receipts.

See our terms of service and how we use your data and protect your privacy. Questions, integration help, or something missing here: support@militant.ai.