Skip to content

Agents

Where Venkai sits in an agent architecture, and the two lines of code that put it there.

The pattern

sequenceDiagram
  autonumber
  participant U as Task
  participant AG as Agent
  participant VK as Venkai
  participant LLM as LLM

  U->>AG: "add a new billing plan"
  AG->>VK: GET .../relevant?query=add a new billing plan&limit=5
  VK-->>AG: 5 memories + scores
  AG->>LLM: system + selected context + task
  LLM-->>AG: plan / action
  AG->>AG: execute
  AG->>VK: POST /api/memory (what was decided)
  VK-->>AG: stored

Read before the prompt. Write after the decision. That is the whole integration.

Minimal implementation

import os, requests

BASE = os.environ.get("VENKAI_BASE_URL", "https://api.venkai.fr")
H = {"Authorization": f"Bearer {os.environ['VENKAI_API_KEY']}"}
PROJECT = "billing-service"     # a constant: a typo silently creates a new project


def load_context(task: str, limit: int = 5) -> str:
    r = requests.get(f"{BASE}/api/context/{PROJECT}/relevant",
                     headers=H, params={"query": task, "limit": limit}, timeout=10)
    r.raise_for_status()
    memories = r.json()["memories"]
    if not memories:
        return ""
    lines = [f"- [{m['type']}] {m['content']}" for m in memories]
    return "Established context for this project:\n" + "\n".join(lines)


def record(content: str, type: str = "decision", importance: float = 0.8) -> None:
    requests.post(f"{BASE}/api/memory", headers=H, timeout=10, json={
        "project_id": PROJECT, "agent_id": "planner",
        "content": content, "type": type, "importance": importance,
    }).raise_for_status()


def run(task: str):
    prompt = f"{load_context(task)}\n\nTask: {task}"
    result = call_your_llm(prompt)          # your framework, your model
    if result.decision:
        record(result.decision, type="decision", importance=0.85)
    return result

Where to put each call

Read: once per task, not once per LLM call

A multi-step agent that recalls before every tool call pays for retrieval n times and floods each prompt with the same memories. Load once at the top of the task and pass the block down.

Recall again only when the subject changes — a sub-agent working on a different component should query for its own component.

Write: on conclusions, not on observations

The trap is writing every intermediate thought. That fills the store with low-value rows that then compete for retrieval budget.

Write Skip
A decision and its reason "I am now reading the config file"
A discovered hard constraint Tool output you can re-read
A correction of a previous belief Anything already in the repository
A user preference The step-by-step trace

Rule of thumb: if a competent colleague joining next week would want to know it, write it. Otherwise let it go.

Multi-agent

Agents in the same project share one store. Each writes with its own agent_id, so contributions stay attributable and filterable (GET /api/context/{project}?agent=planner).

flowchart TB
  P["planner"] --> S[("project: billing-service")]
  C["coder"] --> S
  R["reviewer"] --> S
  S -->|"recall(task)"| P
  S -->|"recall(task)"| C
  S -->|"recall(task)"| R

There is no locking and no conflict detection. Two agents can write contradictory memories and both will be retrieved, side by side, with no signal that they disagree. If that matters, have the agent check for a contradicting memory before writing, and PATCH rather than append when it finds one.

Handoffs Experimental

POST /api/handoffs records a from_agent → to_agent transfer with a list of context_ids and a note. It is a record of the handoff, not a mechanism — nothing is enforced or delivered by it.

Framework notes

Venkai has no framework-specific packages. It is two HTTP calls, so it drops into anything:

Framework Where it goes
LangGraph A node before the model node loads context into state; a node after writes conclusions.
CrewAI In a task callback, or a custom tool the agent can call itself.
Claude Code / MCP clients Via the MCP server — no code at all.
Custom loop load_context() before the prompt, record() after.

Letting the agent decide

Exposing recall as a tool the model can call is tempting and mostly a mistake: the model calls it with vague queries, or forgets. Deterministic loading at the top of the task is more reliable.

Exposing write as a tool is the opposite — the model knows when it has concluded something, and you do not. Give it record() and describe when to use it.