Skip to main content

Aura — aura-context-engine

A framework-agnostic context layer for LLM agents.

Retain every turn durably. Transmit per turn only what the current question needs — under an explicit token budget, with no LLM call on the read path.

Drop it into LangGraph, Agno, Google ADK, or a raw model loop. The engine owns the session; the orchestrator stays an orchestrator.

CI License: MIT Python 3.10+

Why this exists

Long agent transcripts waste three things at once:

  • Latency — prefill grows with every unused token
  • Cost — you pay for tokens the model does not need
  • Attention — models lose the middle of a long prompt (Lost in the Middle)

aura-context-engine keeps the full transcript in storage and assembles a budgeted prompt: pinned facts, the last turn, query-relevant recall, and (for teams) a distilled handoff — never the other agent's raw history.

Install

Python 3.10+.

pip install aura-context-engine

Until the first PyPI release, install from GitHub:

pip install "aura-context-engine @ git+https://github.com/hypen-code/aura.git"

From a local clone (editable):

git clone https://github.com/hypen-code/aura.git
cd aura
python3 -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate
pip install -e .

Optional extras

The core has zero required dependencies. Add only what you use.

Extra Install What it unlocks
(none) pip install aura-context-engine Assembly, SQLite/memory, BM25 recall, heuristic facts
embed pip install "aura-context-engine[embed]" Local fastembed semantic recall
extract pip install "aura-context-engine[extract]" Async LLM fact extraction via LiteLLM
langgraph pip install "aura-context-engine[langgraph]" LangGraphContextAdapter / team adapter
agno pip install "aura-context-engine[agno]" AgnoContextAdapter / team adapter
adk pip install "aura-context-engine[adk]" GoogleADKContextAdapter / team adapter
all pip install "aura-context-engine[all]" Everything above
pip install "aura-context-engine[embed,extract,langgraph]"

30-second usage

from context_engine import ContextEngine, ContextPolicy, PolicyMode

engine = ContextEngine()
session = engine.new_session()

engine.record_exchange(
    session,
    "I live in Oslo and I have two cats, Miso and Nori.",
    "Noted.",
)
engine.record_exchange(session, "I just moved to Berlin.", "Got it.")

policy = ContextPolicy(
    mode=PolicyMode.PRIORITY_SELECTIVE,
    recency_turns=1,
    top_k=4,
    token_budget=2048,
)

messages = engine.build_context(
    session,
    "Where do I live now, and what are my cats called?",
    policy=policy,
    system_prompt="Answer from the conversation only.",
)
# messages is a list of {role, content} dicts — pass it to any LLM API

Run the same flow from the repo:

python examples/quickstart.py

Add it to an orchestrator

The contract is two calls:

  1. Writerecord_turn / record_exchange after a completed turn
  2. Readbuild_context (or an adapter hook) before the next model call

Any framework (no adapter)

from context_engine import ContextEngine, ContextPolicy, PolicyMode

engine = ContextEngine()
session = engine.new_session()
policy = ContextPolicy(mode=PolicyMode.PRIORITY_SELECTIVE, token_budget=2048)

def reply(question: str, call_model) -> str:
    prompt = engine.build_context(session, question, policy=policy)
    answer = call_model(prompt)
    engine.record_exchange(session, question, answer)
    return answer

LangGraph

from langgraph.prebuilt import create_react_agent
from context_engine import ContextEngine, ContextPolicy, PolicyMode
from context_engine.adapters import LangGraphContextAdapter

engine = ContextEngine()
adapter = LangGraphContextAdapter(
    engine,
    policy=ContextPolicy(mode=PolicyMode.PRIORITY_SELECTIVE, token_budget=2048),
)
agent = create_react_agent(model=llm, tools=tools, pre_model_hook=adapter.pre_model_hook)

The hook replaces the model input with the assembled context and passes the live tool-call tail through untouched.

Agno

from agno.agent import Agent
from context_engine import ContextEngine
from context_engine.adapters import AgnoContextAdapter

engine = ContextEngine()
adapter = AgnoContextAdapter(engine)
agent = Agent(model=model, add_history_to_context=False)

prompt = adapter.messages_for(question)
response = agent.run(input=adapter.to_agno(prompt))
adapter.record_exchange(question, response.content)

Google ADK

from context_engine import ContextEngine
from context_engine.adapters import GoogleADKContextAdapter

engine = ContextEngine()
adapter = GoogleADKContextAdapter(engine, app_name="my-app")
session = await adapter.prepare_session(runner)
async for event in adapter.run(runner, session, question, agent_name="assistant"):
    ...

Multi-agent teams

Each agent gets its own scoped session. Cross-agent state moves as a handoff packet (short summary + identifier slots), not a shared transcript.

from context_engine import ContextEngine, TeamContext
from context_engine.adapters import LangGraphTeamAdapter

team = TeamContext(ContextEngine(), "order-1")
adapter = LangGraphTeamAdapter(team)

researcher = create_react_agent(
    model, tools, pre_model_hook=adapter.node_hook("researcher")
)
writer = create_react_agent(
    model, tools, pre_model_hook=adapter.node_hook("writer")
)

adapter.record_exchange("researcher", question, findings)
adapter.record_handoff("researcher", "writer", findings)

Same pattern: AgnoTeamAdapter, ADKTeamAdapter.

How assembly works

P1  system prompt + session facts + current question     never evicted
P2  most recent turn(s)                                  last
P3  handoffs / last tool response                        just before the question
P4  top-k query-relevant older turns                     recalled
P5  everything else                                      dropped first

Eviction under token_budget: P5 → shrink P4 → surplus P3 → shrink P2. A single remaining P3 block is never dropped (withholding a handoff is worse than a slightly over-budget prompt).

Write path is async: the turn is stored immediately; fact extraction and embeddings run on a background thread. The newest turn is already in P2, so the next read does not wait.

On any engine error the read path falls back to full-history replay.

Persistence

from context_engine import ContextEngine, SQLiteStorage

engine = ContextEngine(storage=SQLiteStorage("aura.db"))

Default is in-memory. SQLite uses WAL. Restart keeps turns, facts, and undelivered handoffs.

Configuration

ContextPolicy(
    mode=PolicyMode.PRIORITY_SELECTIVE,  # or FULL_HISTORY, WINDOWED, WINDOW_SUMMARY
    recency_turns=2,
    top_k=4,
    token_budget=4096,
    min_relevance=0.15,
    max_facts=12,
    tool_top_k=0,            # 0 = show every tool; >0 admits a subset
    max_result_tokens=0,     # 0 = verbatim tool results
    skill_top_k=0,           # 0 = all skill bodies; >0 manifests only
    min_handoff_relevance=0, # 0 = always display delivered packets
    max_handoff_tokens=256,
)

Optional environment:

Variable Default Meaning
CONTEXT_EXTRACTOR_MODEL groq/llama-3.1-8b-instant LiteLLM model for async fact extraction

Without extract (or if the LLM call fails) the engine uses a heuristic extractor. Without embed it uses BM25.

What this repo is

Path Ships in the wheel? Role
context_engine/ yes The product
tests/ no Unit tests for the product
examples/ no Integration sketches
eval/ no Phoenix eval harness (research)
docs/ no Design notes and measured results

eval/ and docs/research/ are how the layer was designed and measured. You do not need them to use the package.

Development

git clone https://github.com/hypen-code/aura.git
cd aura
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[embed,extract]"
python -m unittest discover -s tests -v

See CONTRIBUTING.md.

License

MIT. Contributions are welcome under the same license.

Cite / read more

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aura_context_engine-0.1.2.tar.gz (65.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aura_context_engine-0.1.2-py3-none-any.whl (60.8 kB view details)

Uploaded Python 3

File details

Details for the file aura_context_engine-0.1.2.tar.gz.

File metadata

  • Download URL: aura_context_engine-0.1.2.tar.gz
  • Upload date:
  • Size: 65.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for aura_context_engine-0.1.2.tar.gz
Algorithm Hash digest
SHA256 69309408f6e4f27fa937fe0e256531e48cb9e376a2fa05b422e1761df5476387
MD5 66ea06cad907c11985b2e85d0ed8e5eb
BLAKE2b-256 429f8c49fafad1cb3bda33b78f7e4c13df0a37f05475a648a73302abfe8c9d82

See more details on using hashes here.

Provenance

The following attestation bundles were made for aura_context_engine-0.1.2.tar.gz:

Publisher: publish.yml on hypen-code/aura

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aura_context_engine-0.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for aura_context_engine-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 faa7fd4f20f1efdeab6e460ed3927d0abc628fa17a72abf323775e2b69ae9164
MD5 2566943e8b7af858250e417c73193912
BLAKE2b-256 58dd9fff0e92f8a35b51592732c728fd0e8e9907b5023132e5a4784bc246f866

See more details on using hashes here.

Provenance

The following attestation bundles were made for aura_context_engine-0.1.2-py3-none-any.whl:

Publisher: publish.yml on hypen-code/aura

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page