threepp
A cross-platform C++20 3D library with Python bindings and the high-level API of three.js.
Open-source context engineering library for AI agents. Manage caching, compaction, summarization, and graceful degradation. Config-driven, Apache 2.0.
Developer toolsPythonGitHub @terminus-labs-aiOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Context engineering for AI Agents. Manage your LLM's context window with caching, compaction, summarization, and graceful degradation.
Taken from the repository README
Loading the full README.
Read the full README ↗SR2 manages the full lifecycle of what goes into an LLM's context window: layer assembly, compaction, summarization, memory, token budgeting, KV-cache alignment, and graceful degradation.
Version: 2.0.0-alpha (revamp branch — clean-slate rebuild)
SR2 is not a library you call once, and not a harness. It's a distinct middle tier in the agent stack: a context runtime that sits between your agent harness and the LLM provider.
┌──────────────────────────────────────────────────────────┐
│ HARNESS your agent / Claude Code / LangGraph │
│ owns: I/O, session identity, agent personality, │
│ tool *implementations*, config │
└───────────────────────────┬─────────────────────────────────┘
│ turn(user_input)
▼
┌──────────────────────────────────────────────────────────┐
│ SR2 the context runtime │
│ owns: WHAT fills the window + how it evolves │
│ drives the multi-iteration tool loop: │
│ compile → call LLM → run tools → repeat → post-process │
│ layers · resolvers · transformers · memory · compaction │
│ summarization · budgeting · cache alignment · degradation │
└───────────────────────────┬─────────────────────────────────┘
│ CompletionRequest (injected ↓)
▼
┌──────────────────────────────────────────────────────────┐
│ LLMCallable LiteLLMCallable (default) / your own │
│ owns: the raw provider HTTP call (Anthropic, OpenAI, …) │
└──────────────────────────────────────────────────────────┘
Context management is a closed loop, not a one-shot transform:
compile context → call the model → observe the response → post-process (memory extraction, compaction, summarization, cache tracking)
A pure "compile-only" library forces the harness into a two-call dance — compile, call the LLM yourself, then hand the response back — and every guarantee SR2 makes (cache-stable prefixes, correct conversation-zone transitions, reliable post-processing) depends on the harness getting that dance exactly right. So SR2 owns the turn loop instead.
It does not own the provider HTTP call or tool execution. Both are injected dependencies (inversion of control):
| Dependency | Type | Provided by | SR2's use |
|---|---|---|---|
llm | LLMCallable (complete / stream) | harness | called each LLM iteration |
tool_executor | async (ToolUseBlock) -> ToolResultBlock | harness | called per tool request; SR2 never implements tools |
token_counter | TokenCounter | harness | budget enforcement |
memory_store, memory_extractor, tracer, provenance_store | protocols | harness (optional) | memory, observability |
This is how SR2 owns the lifecycle without becoming a provider client or a harness. The harness supplies adapters + I/O + personality; SR2 drives the turn.
The same SR2 core ships two ways:
LiteLLMCallable, call turn() in-process.
This is the primary mode (see Quick Start).sr2-relay provides; it's a thin wire-format adapter over the
same turn().SR2.turn(user_input) -> AsyncIterator[StreamEvent] is the entry point. Per
turn it:
LLMCallabletool_executor
(concurrently, order-preserving), appends results, and loopsmax_tool_iterations)end event, then awaits post_processCallers see a clean stream: text and usage events, loop-progress events
(tool_use_emitted, tool_result_received, iteration_complete), and exactly
one end. At most one error event may appear per phase, always before end.
import asyncio
from sr2 import SR2
from sr2.config.models import PipelineConfig
from sr2.integrations.litellm import LiteLLMCallable
from sr2.pipeline.token_counting import CharacterTokenCounter
from sr2.models import TextBlock
config = PipelineConfig(
layers=[
{"name": "system", "target": "system",
"resolvers": [{"type": "static",
"config": {"text": "You are a helpful assistant."}}]},
{"name": "conversation", "target": "messages",
"resolvers": [{"type": "session"}, {"type": "input"}]},
]
)
sr2 = SR2(
config,
llm=LiteLLMCallable("anthropic/claude-haiku-4-5-20251001"),
token_counter=CharacterTokenCounter(),
)
async def main():
async for event in sr2.turn([TextBlock(text="Hello")]):
if event.type == "text":
print(event.text, end="")
asyncio.run(main())
Or run a config straight from the CLI:
sr2 configs/minimal.yaml
See QUICKSTART.md for the YAML config shape, tool wiring, and multi-turn sessions.
The pipeline runs on an EventBus, not a fixed sequence. Components subscribe
to event names (turn_start, user_input, assistant_response, …); the engine
drives a drain-process loop each turn until no component produces new work.
A layer is the unit of context. Each layer holds resolvers (produce
content), transformers (reshape content), and optional tool providers
(produce tool definitions). Layers compile to one of three targets — system,
messages, or tools — and enforce a per-layer token budget.
Layer order drives KV-cache efficiency: stable layers (system prompt, project context) go first and stay cached; dynamic layers (conversation) go last.
Four extension points, each a runtime_checkable Protocol, discovered lazily
via Python entry points — nothing activates unless your config names it:
| Group | Protocol | Built-ins |
|---|---|---|
sr2.resolvers | Resolver | static, input, session, event_payload, memory, markdown_file, static_text |
sr2.transformers | Transformer | summarize, memory_extraction, compaction |
sr2.extractors | MemoryExtractor | rule_based |
sr2.tool_providers | ToolProvider | (extension point; no built-ins yet) |
Add a capability by registering an entry point — never by modifying the engine (Open/Closed).
MemoryStore / MemoryExtractor protocols; MemoryResolver
injects relevant memories on input, MemoryExtractionTransformer saves them
from responses. In-memory store ships with core.schema_and_sample,
result_summary) with an optional cost gate (cache-invalidation economics).CircuitBreaker (closed/open/half-open) plus a
5-level DegradationLadder.InMemoryProvenanceStore or durable SQLiteProvenanceStore); Tracer
captures per-component firing records with a human-readable timeline.src/sr2/). The v1 multi-package
monorepo, runtime, bridge, and premium split are gone.InMemoryMemoryStore); async persistence backends can be added via the MemoryStore protocol.The authoritative module map and architecture reference lives in CLAUDE.md. High level:
src/sr2/
__init__.py # public API — `from sr2 import SR2`, plus core models
sr2.py # facade re-export of the SR2 orchestrator
orchestrator.py # SR2 class: the turn loop
models.py # core data models (TextBlock, Message, ToolDefinition, …)
protocols/llm.py # LLMCallable, CompletionRequest/Response, StreamEvent
integrations/ # LiteLLMCallable
config/ # Pydantic config models
pipeline/ # engine, event bus, layers, resolvers, transformers, tracing
memory/ # stores, extractors, resolver
compaction/ # rule engine + cost gate
degradation/ # circuit breaker, ladder
tokenization/ # tiktoken counter
v2.0.0-alpha on revamp. Turn loop, event-driven pipeline, layers,
resolvers/transformers, memory, compaction, degradation, provenance, and tracing
are in place. post_process is a wired no-op pending the extraction/maintenance
hookup.
Apache-2.0. See LICENSE.