HippoDb Agent SDK

Release notes2.0.0

Feature overview and upgrade guide.

On this page

2.0.0 adds the HippoDb engine features the SDK could not reach before (asking what was true at a past moment, choosing a retrieval plan, deleting edges and fabrics), makes the SDK work behind the HippoDb manager with API keys, bounds what an LLM call carries (tools and history), lets a chat choose how hard a reasoning model thinks, lets a frontend stop a running chat, and removes the code that depended on engine routes that no longer exist. Several write calls that used to discard what the engine told them now return it.

This document explains what is new, how to upgrade from 1.x, and then gives an overview of what the SDK does as a whole. The reference documents are listed at the end. For the exhaustive list of changes see CHANGELOG.md in the repository root.

Charter and Curator, two engine features, are not supported by this SDK and there is no SDK API for them.

What is new#

Ask what was true at a past moment#

think() takes an as_of argument and rebuilds the memory as it stood at that instant.

now = await hippo.think("Checkout bug")
then = await hippo.think("Checkout bug", as_of="2026-09-15")

then.as_of["instant"]        # "2026-09-15T00:00:00Z"
then.as_of["doesNotReconstruct"]

What it does and does not do:

temporal_direction="BACKWARD" or "FORWARD" limits spreading activation to one side of a point in time ("what led up to X", "what followed from X").

The memory_think tool the LLM sees accepts both arguments. as_of is the moment a memory was stored, not a date inside what it says: for data written long after the events it describes (an import, a back-fill), an event date earlier than the day the data was written returns nothing, because nothing existed yet. The tool description tells the model this, and the engine's caveat is returned with the result, including when a memory formatter is in use.

Choose the retrieval plan#

think(plan=...) sends an explicit plan. ThinkResult.plan always reports the plan the engine ran, whether you chose it or its planner did.

result = await hippo.think("Checkout bug", plan="exact > spread(2) > reserve(0.4)")
result.plan   # "resolve_exact > spread(2) > reserve(0.4)"

Steps are joined with > and are case-insensitive: exact, lexical, semantic, temporal, tag, spread(n), collapse, reserve(frac), deep(frac), diversify(n), limit(n). The names in ThinkResult.plan (resolve_exact, ...) are the engine's output form and are not accepted as input. Omitting plan gives the engine's default behavior. The SDK does not validate a plan; the engine answers HTTP 400 for an unknown step. The memory_think tool does not expose plan.

Delete edges and fabrics#

await hippo.delete_edge("Task 1", "Task 2")                       # every edge between the pair
await hippo.delete_edge("Task 1", "Task 2", edge_type="PRECEDES") # one type only
await hippo.delete_edges([{"from": "A", "to": "B"}, {"id": "e-7"}], ignore_missing=True)
await hippo.delete_edge_by_id("e-7")
await hippo.delete_fabric("scratch")                              # permanent

Work behind the HippoDb manager#

In production the engine sits behind a manager that authenticates with API keys. HippoClient now sends Authorization: Bearer <key>:

HippoClient(base_url="https://hippodb.example.com", fabric="my_agent", api_key="hdb_live_...")

Help discovery find your tools#

A tool can now say what discovery cannot guess from its name. All four attributes are optional and none of them is sent to the model:

class CartAdd(BaseTool):
    name = "cart_add"
    description = "Add an item to the cart"
    domain = "shopping"                      # linked with every tool in this domain, not with "cart_*"
    related = ("checkout_pay",)              # often used together with this one
    keywords = ("basket", "trolley")         # synonyms the name and description lack
    examples = ("put the blue shoes in my basket",)   # things a user might say

memory_think also stops returning tool-routing concepts (TOOL, INTENT, WORKFLOW) next to the user's own memory, and the agent, memory and gateway logs now rotate (LOG_MAX_BYTES, LOG_BACKUP_COUNT) instead of growing without limit. memory.log is also quieter: it holds one line per HippoDb call (think, query, ingest, link) with the turn, fabric and the fields worth grepping, instead of the whole request and response as pretty-printed JSON. In a small test the same calls took 3 KB and 26 lines instead of 16 KB and 402. MEMORY_LOG_PAYLOADS=true keeps the full bodies in memory.log; the agent log keeps them at DEBUG either way.

More robust HippoDb calls#

HippoClient takes timeout, connect_retries and transport. A request whose connection cannot be opened is retried twice with a short, jittered backoff; nothing else is retried, because only a connect failure guarantees the server saw nothing (so writes are safe). The default timeouts are unchanged.

Stop a running chat#

A frontend "stop" button used to do nothing useful on the backend: the LLM call ran in a worker thread that could not be cancelled, so the call finished and billed after the client had gone.

See Events and Streaming.

Cheaper tool-using turns: prompt caching#

A turn can use up to 20 LLM rounds, and each round resends the same tools, system prompt and conversation. For Anthropic models the SDK now marks that repeated prefix as cacheable (LLM_PROMPT_CACHING, on by default), so later rounds read it at a fraction of the normal input price. Providers that cache automatically (OpenAI, Gemini, DeepSeek, xAI) need nothing from the SDK; the stable tool order helps them too. Cache usage appears in the log line and the llm_response event (cached_tokens, cache_write_tokens). See LLM Routing.

Transient LLM failures (rate limits, 503 and other server errors, connection errors) are now retried with jittered backoff (LLM_MAX_RETRIES, default 2), where only an "overloaded" error was retried before.

Keep the prompt bounded: tools and history#

Every tool definition costs about 1.2k tokens and is sent again on every LLM round. Before 2.0.0 only the discovery result was capped (12), so the list could grow without limit (one deployment saw 33, then 45).

Typed LLM errors#

A failed LLM call now raises an LlmError subclass instead of LiteLLM's own exception: LlmRateLimitError, LlmOverloadedError, LlmConnectionError, LlmTimeoutError, LlmAuthError, LlmContextWindowError and LlmBadRequestError. Each has model, provider, status_code, retry_after (from the provider's Retry-After header) and retryable, and the LiteLLM exception is its __cause__. A host can wait and retry on a rate limit, ask the user to shorten the conversation on a context-window error, and alert an operator on an auth error, without importing LiteLLM or reading message text. The SDK still retries rate limits, overload and connection errors itself first (LLM_MAX_RETRIES, default 2).

Reasoning effort, per message#

A chat UI can now let the user choose how hard a reasoning model thinks. agent.chat(message, reasoning_effort="high") takes low, medium or high for one message, "off" turns the agent's default off for that message, and LLM_REASONING_EFFORT (or BaseAgent(reasoning_effort=...)) sets the default. LiteLLM maps it for Claude, OpenAI's reasoning models, Gemini and DeepSeek. agent.supports_reasoning_effort() says whether to show the control, and agent.last_turn.reasoning_effort says what a turn used. The SDK makes it work with tools: it adds room for the thinking to the output limit (Anthropic rejects high otherwise) and keeps the signed thinking blocks with the tool calls of the turn in progress, then drops them from the stored history. Checked against the real Anthropic API on Claude Haiku 4.5 and Claude Sonnet 5.5, with and without streaming. The newest Claude models need LiteLLM 1.104 or newer for this, so the SDK now requires litellm>=1.104.2. See LLM Routing.

Writes report what happened#

Fixes that change behavior you may have worked around#

Upgrading from 1.x#

Most 1.x code keeps working. These are the places that can break, in the order you are likely to meet them.

If your code... You will see Do this
passes depth= to HippoClient.query() TypeError: unexpected keyword argument 'depth' Drop it. The engine ignored it. Use think(steps=...) for multi-hop questions
passes dream_prompt= to RoleConfig TypeError Drop it. Its only consumer was the removed dream route
imports hippodb_agent.tuning or RequestTuning ImportError Remove the import. The tool was not in the default tool set and failed on nearly every call
lists memory_dreams or request_tuning in role prompts or scopes the tool no longer exists Remove the mentions
calls any of the 18 removed-route methods (below) EngineCapabilityRemovedError Remove the call. There is no replacement
catches httpx.HTTPStatusError around those calls the new error is not an httpx error Catch EngineCapabilityRemovedError (a HippoDbError), or remove the call
reads learn(...)["id"] KeyError Use ["label"]. The engine addresses concepts by label; "id" was always ""
imports hippodb_agent.feedback (classify_feedback, FeedbackType) ImportError Remove the import. The classifier was a standalone keyword matcher that nothing in the SDK called or acted on
imports NeuroMonitor, MemoryHealthSnapshot or any metric event class from hippodb_agent.monitor ImportError Remove them; they recorded removed features and nothing called them. brain_report is unchanged apart from dropping its crystallization, co-activation, SQL-pattern and prompt-template sections
reads settings.brain_capacity AttributeError Remove it. Nothing used it, and capacity is set by the engine
imports anthropic and relied on the SDK installing it ModuleNotFoundError Add anthropic to your own dependencies; the SDK never imported it
calls delete_edge() edges are now actually deleted It used to do nothing. Review every call site
pins LiteLLM below 1.104.2 the install fails to resolve The SDK now requires litellm>=1.104.2. Reasoning effort on the newest Claude models fails with older versions, and the SDK's tests run on 1.104.2
iterates the result of list_fabrics() as names (against a manager) each item is now a dict, {"name": ...} Read f["name"]. The engine already returned dicts, and the method is now the same against both
relies on the default history_window of 20 the model sees the last 5 turns Set HISTORY_WINDOW=20 (or BaseAgent(history_window=20)) to keep the old window
catches litellm.RateLimitError (or another LiteLLM exception) around chat() the exception is not caught: you get LlmRateLimitError Catch the matching Llm...Error, or LlmError for all of them. The LiteLLM exception is its __cause__
uses tool_scopes=[] every non-builtin tool is hidden Unchanged, now documented: use a prefix list, or None at the registry for no restriction

The 18 removed-route methods raise immediately, without sending a request: consolidate, consolidate_light, consolidate_tier1, consolidate_tier2, consolidate_tier3, consolidation_status, promote, belief_contested, belief_profile, belief_revise, belief_acknowledge, belief_audit, dream, get_dreams, init_clusters, sleep_enter, sleep_wake, sleep_status. The engine retired these route families for good; they were built, measured and removed, and are not coming back. They are kept as methods so imports resolve and the failure is loud and named instead of a bare 404 or a fake success. For init_clusters(), create cluster hubs by passing clusters on the concepts you ingest(). experience(), detect_crosslinks() and auto_crosslink() remain harmless no-ops. Automatic linking is now done by the engine itself (SIMILAR_TO and RELATED_TO edges), and the CONTRADICTS and DISPUTES edge types no longer exist.

Host applications

What the SDK does#

Running an agent#

AgentRunner is the one-line entry point. It connects to HippoDb, creates or reuses the agent's fabric, seeds it, builds the tool registry, wires safety and memory features, and starts a terminal session.

from hippodb_agent import AgentRunner, RoleConfig, BaseTool

runner = AgentRunner(
    role=RoleConfig(name="hr", display_name="HR Assistant", tool_scopes=["hr_"],
                    fabric="hr_agent", system_prompt="You are an HR assistant.\n\n{manifest}"),
    business_tools=[ListEmployees],
    api_base_url="http://localhost:9000",   # or backends={"hr": "...", "payroll": "..."}
    seed_file="seed.yaml",
)
runner.start()

AgentRunner runs a terminal session. A web or API host builds the parts itself and calls BaseAgent.chat(message, conversation_id=..., reasoning_effort=...) for each message; the guide Building an Agent Without AgentRunner has a complete, tested example and the settings to pass. RoleConfig defines identity, prompt, tool scopes and always-on tools; the {manifest} placeholder is replaced with memory context at runtime. A safety_header on the role is always prepended and cannot be overridden by templates.

Per-conversation history is kept per conversation_id, capped at history_window turns (default 5, was 20), and stores full tool traces. Optional text streaming (enable_streaming) relays model text through the agent's event_queue (see docs/technical/events-and-streaming.md), and chat_with_image() accepts an image alongside text.

Memory in HippoDb#

Every agent has its own HippoDb fabric: a graph of concepts, facts, timeline events and weighted edges.

Tools#

Business tools subclass BaseTool and call a backend through self.api (or self.apis["name"] with several backends). The SDK adds built-in memory tools, OS tools and calculator tools.

Rules#

Data gateway and compliance#

The gateway sits between a tool's result and the model. It masks PII (built-in patterns plus per-tool sensitive_fields with redact, keep_last_4 or type_only), summarizes long lists to a few representative rows (gateway_summary_threshold), and writes an audit trail of what crossed the boundary. Setting GATEWAY_COMPLIANCE=GDPR (or a comma-separated list) loads that framework's field policies; GDPR, CCPA, SOX, HIPAA, PCI_DSS, LGPD and DPDP are built in. Each framework also ships compliance principles, which ComplianceSeedGenerator turns into rules you load yourself; they are not applied automatically. Details and limits are in docs/technical/compliance.md.

LLM providers and routing#

Models run through LiteLLM, so MODEL=<provider>/<model> works for Anthropic, OpenAI, xAI, Google Gemini (use the gemini/ prefix), Cohere, Mistral and Ollama. Optional multi-model routing (LLM_ROUTING_MODELS, LLM_ROUTER_MODEL) lets a small router model pick which model handles each message; see docs/technical/llm-routing.md. llm_call_timeout_s bounds a single LLM round, which otherwise can hang for LiteLLM's 600-second default.

Agent network#

With network_nats_url set, agents register with a registry, discover each other by capability, send direct and department-wide messages, and receive enterprise tool allocations over NATS. The network_ tools are always on when networking is enabled. Each agent keeps its own brain; agents exchange messages, never memory. See docs/technical/agent-network.md.

Everything else#

Limits to know about#

These come from the engine, not the SDK, but they shape what you can build.

Compatibility#

How this was tested#

The SDK's automated tests (nearly 600) cover each feature above. The engine-facing calls were also run against a live HippoDb engine and through the manager with API keys enforced: think() with as_of, temporal_direction and plan, learn(), update_concept(), edge and fabric deletion, the removed routes, key passing and the brain_report command. Reasoning effort, tool loops and streaming were run against the live Anthropic API on Claude Haiku 4.5 and Claude Sonnet 5.5. Not run live: engines with an LLM configured for identity decisions, and the agent network.

Reference documents#