Long-running agents¶
An agent that runs for weeks has different problems from one that runs for a minute. This page is about those.
The failure curve¶
flowchart LR
A["week 1<br/>30 memories<br/>retrieval sharp"] --> B["week 4<br/>400 memories<br/>near-duplicates appear"]
B --> C["month 3<br/>3000 memories<br/>recency faded for most<br/>importance decides a lot"]
C --> D["month 6<br/>project too broad<br/>time to split"]
Nothing breaks. It degrades, quietly, and the symptom is "retrieval got worse" with no error anywhere.
What changes as the store grows¶
Recency stops helping¶
Recency fades over weeks. After a couple of months it no longer separates most memories, and a mature project ranks mainly on relevance and importance.
Consequence: importance becomes your primary ranking control, and it was
set months ago by an agent that had no idea how crowded the project would get.
Set it against a fixed rubric from day one, not relative to what is already stored:
| Value | Meaning — decide once, apply forever |
|---|---|
0.9–1.0 |
Violating this breaks the system or the contract |
0.6–0.8 |
A decision someone would need to know before changing the design |
0.4–0.5 |
Useful background |
0.1–0.3 |
Ephemeral |
Duplicates accumulate¶
Venkai does not deduplicate. An agent that records "we use Postgres" at the end of every run has 60 near-identical memories after two months, and they can fill the top-5 between them.
Deduplicate before writing:
def record_once(content: str, threshold: float = 0.9, **kw) -> bool:
"""Skip the write if something very close is already stored.
Returns True if written."""
existing = requests.get(f"{BASE}/api/context/{PROJECT}/relevant",
headers=_H, params={"query": content, "limit": 3},
timeout=10).json()["memories"]
if existing and existing[0]["similarity"] >= threshold:
return False
record(content, **kw)
return True
Calibrate threshold on your own data: write a few known duplicates and
paraphrases, and look at the similarity they actually get.
Projects get too broad¶
A single ranked query considers a bounded number of memories, favouring the most recent (Large projects). Long before that matters, a project mixing many domains ranks worse than several focused ones. Track project size:
curl -s https://api.venkai.fr/api/projects -H "Authorization: Bearer $VENKAI_API_KEY" \
| python -c "import json,sys; [print(p['contexts'], p['project_id']) for p in json.load(sys.stdin)['projects']]"
When a project reaches several thousand memories, split it by bounded context.
Measure latency yourself¶
Time retrieval client-side as the project grows, from the region your agents run in. Do not design around a number you have not measured.
Patterns that hold up¶
Split by bounded context, not by time¶
billing-service ← one deployable, one set of constraints
billing-service-2026q1 ← ✗ splits by time; the constraints live in the wrong quarter
Project boundaries should follow what an agent needs to know at once. A time-based split scatters the durable rules across projects, and the agent has to query several to get a complete picture — Venkai has no cross-project query.
Correct rather than append¶
When a decision is reversed, PATCH the original:
requests.patch(f"{BASE}/api/context/{ctx_id}", headers=_H, timeout=10, json={
"content": "We use Postgres. REVERSED 2026-08: moving to MongoDB, "
"the join requirement was removed with the new ranking model."
})
Appending a contradiction leaves both retrievable, side by side, with nothing
indicating which is current. PATCH records a version and leaves one truth in
the store. The history stays available via
GET /api/context/{id}/versions.
Periodic consolidation¶
Once a month, review the low-value tail and rewrite it:
# what is actually in there, by type
curl -s https://api.venkai.fr/api/impact -H "Authorization: Bearer $VENKAI_API_KEY" \
| python -c "import json,sys; print(json.load(sys.stdin)['types_distribution'])"
A project that is 90 % fact and 2 % decision is one where the agent is
narrating instead of concluding. Fix that at the write site.
Budget by characters, not by count¶
At scale, limit=5 can mean 200 characters or 40 000. Enforce a character
budget client-side — Context optimization.
Multi-agent over time¶
Several agents on one project share a store with no locking and no conflict detection. Two can write contradictory memories and both will be retrieved with equal standing.
If contradiction matters:
- Before writing a
decisionorconstraint, recall against its content. - If something contradicts it,
PATCHthat memory rather than appending. - Keep
agent_idaccurate so?agent=filtering can attribute anything odd.
Monitoring checklist¶
| Watch | Where | Act when |
|---|---|---|
| Memory count per project | GET /api/projects → contexts |
> 7000 |
| Type distribution | GET /api/impact → types_distribution |
fact dominates |
| Retrieval latency | Your own client-side timing | Trending up |
| Duplicate rate | Sample /relevant outputs by eye |
Repeats in the top-5 |
| Score distribution | score on returned memories |
Top result consistently < 0.35 |
That last one is the useful early warning: when the best available memory scores poorly, retrieval is returning the least-bad rather than the right thing — and there is no relevance floor to stop it.