Skip to content

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:

  1. Before writing a decision or constraint, recall against its content.
  2. If something contradicts it, PATCH that memory rather than appending.
  3. Keep agent_id accurate 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.