Skip to main content

Cross-Agent Token Deduplication Bus — eliminate 65-82% of redundant tokens across multi-agent LLM pipelines

Project description

EchoKill

Cross-Agent Token Deduplication Bus — eliminate 65–82% of redundant tokens across multi-agent LLM pipelines.

Python 3.10+ License: MIT Tests


Table of Contents


The Problem

In multi-agent systems (AutoGen, CrewAI, LangGraph, etc.), every agent independently receives the full conversation history, system prompt, and shared documents. In a 5-agent workflow processing a 10-page document:

  • The same 4,000-token document gets sent 5× = 20,000 tokens wasted
  • System prompts (500–2,000 tokens each) are re-sent on every turn
  • Over a multi-turn conversation, 40–70% of total tokens are pure duplication

At production scale with hundreds of concurrent users, this becomes:

  • $10K–$50K/month in wasted API spend for mid-sized deployments
  • Unnecessary latency — longer prompts = slower inference
  • Rate-limit pressure — burning TPM quota on duplicated content

No existing framework addresses this.

The Solution

EchoKill provides a shared semantic memory bus using content-addressed storage. Agents reference shared context blocks by hash instead of re-sending them. The system:

  1. Deduplicates shared context into a common prefix block
  2. Orders agent-specific content after the shared prefix (maximizes provider prefix-cache hits)
  3. Tracks which blocks each agent has already "seen" and sends only deltas
  4. Isolates per-user/tenant state for safe multi-tenant operation
┌─────────────────────────────────────────────────────────────────┐
│                   EchoKill Multi-Tenant Bus                       │
│                                                                   │
│   ┌───────────────────────────────────────────────────────────┐  │
│   │            Content-Addressed Store (Redis Cluster)          │  │
│   │   hash(block) → block_content                              │  │
│   │   tenant:user1:abc123 → [system prompt, 800 tokens]        │  │
│   │   tenant:user1:def456 → [document chunk, 2000 tokens]      │  │
│   │   tenant:user2:abc123 → [same hash, shared across tenant]  │  │
│   └───────────────────────────────────────────────────────────┘  │
│                          │                                        │
│   ┌─── User Session 1 ──┼──── User Session 2 ────────────────┐  │
│   │                      │                                     │  │
│   │  ┌──────────┐  ┌──────────┐   ┌──────────┐  ┌──────────┐│  │
│   │  │ Agent A   │  │ Agent B   │   │ Agent A   │  │ Agent C   ││  │
│   │  │seen:{abc, │  │seen:{abc} │   │seen:{}    │  │seen:{abc} ││  │
│   │  │ def}      │  │           │   │           │  │           ││  │
│   │  └──────────┘  └──────────┘   └──────────┘  └──────────┘│  │
│   └───────────────────────────────────────────────────────────┘  │
│                                                                   │
│   Prompt Compiler (per-session):                                  │
│     [shared_prefix: abc123, def456]                               │
│     [agent_specific: role + new delta only]                       │
│     [query: new user message]                                     │
└─────────────────────────────────────────────────────────────────┘

Installation

Basic (single-process, development)

pip install -e .

Production (Redis-backed, multi-process)

pip install -e ".[redis]"

With accurate token counting (tiktoken)

pip install -e ".[tiktoken]"

With Anthropic cache optimization

pip install -e ".[anthropic]"

With OpenAI provider middleware

pip install -e ".[openai]"

Full (all providers + dev/test dependencies)

pip install -e ".[all,dev]"

System Requirements

Component Minimum Recommended (Production)
Python 3.10+ 3.11+ (faster startup)
Redis 6.2+ 7.0+ (with Redis Functions)
Memory 512 MB per worker 2 GB per worker
CPU 1 core 4+ cores (async I/O bound)

Quick Start

from echokill import MemoryBus

# Create the bus
bus = MemoryBus()

# Register agents
bus.register_agent("security_reviewer", role="You review code for security vulnerabilities.")
bus.register_agent("style_reviewer",    role="You review code style and readability.")
bus.register_agent("perf_reviewer",     role="You review code for performance issues.")

# Share context once (stored once, referenced by all agents)
bus.add_shared_block(code_diff_text)
bus.add_shared_block(project_guidelines_text)

# Build deduplicated prompts
for agent_id in bus.agent_ids:
    prompt = bus.build_prompt(agent_id, new_message="Review the code above.")
    # prompt.messages → send directly to any chat completions API
    # prompt.tokens_saved_estimate → tokens eliminated via dedup

# Check savings
print(bus.metrics.summary())
# {'total_calls': 3, 'total_tokens_sent': 7500, 'total_tokens_saved': 17500, 'savings_ratio': '70.0%'}

Tokenizers

EchoKill includes a multi-backend tokenizer system for accurate token counting across providers.

Supported Backends

Backend Accuracy Dependency Best For
TiktokenTokenizer Exact tiktoken OpenAI models (GPT-5.x, GPT-4o, o-series)
AnthropicTokenizer ~95% None Claude models (~3.5 chars/token)
ApproximateTokenizer ~90% None Zero-dependency fallback (~4 chars/token)

Usage

from echokill.tokenizers import get_tokenizer, count_tokens

# Auto-selects the best tokenizer for the model
tokenizer = get_tokenizer("gpt-4o")  # → TiktokenTokenizer (if tiktoken installed)
tokenizer = get_tokenizer("claude-sonnet-4-5")  # → AnthropicTokenizer

# Quick one-liner
count = count_tokens("Hello, world!", model="gpt-4o")

# Or use directly
from echokill.tokenizers import TiktokenTokenizer, ApproximateTokenizer

exact = TiktokenTokenizer(model="gpt-4o")
approx = ApproximateTokenizer(chars_per_token=4.0)

print(exact.count("def hello(): pass"))   # exact BPE count
print(approx.count("def hello(): pass"))  # len(text) // 4

Automatic Fallback

get_tokenizer() gracefully falls back to approximate counting if tiktoken is not installed — no code changes needed.


Provider-Aware Cache Optimization

EchoKill generates provider-specific prompt formats that maximize LLM provider cache hit rates — stacking on top of EchoKill's own deduplication for additional savings.

How It Works

┌──────────────────────────────────────────────────────────────────────────┐
│                         Savings Stack                                     │
│                                                                          │
│  Layer 1: EchoKill Dedup     — eliminates redundant blocks  (~65-70%)   │
│  Layer 2: Provider Caching   — cached prefix reads at 50-90% discount   │
│  Combined                    — up to 82% effective token reduction       │
└──────────────────────────────────────────────────────────────────────────┘

Anthropic (Explicit Cache Breakpoints)

from echokill.cache_optimizer import CacheOptimizer, Provider

optimizer = CacheOptimizer(provider=Provider.ANTHROPIC)
optimized = optimizer.optimize(compiled_prompt)

# Result contains:
# - messages with cache_control markers at optimal positions
# - prewarm_payload for cache pre-warming (max_tokens=0 technique)
# - cached_prefix_tokens / dynamic_suffix_tokens breakdown

print(optimized.cached_prefix_tokens)     # tokens served from cache
print(optimized.prewarm_payload)          # send to Anthropic to pre-warm

OpenAI (Automatic Prefix Caching)

optimizer = CacheOptimizer(provider=Provider.OPENAI)
optimized = optimizer.optimize(compiled_prompt)

# OpenAI automatically caches stable prefixes ≥1024 tokens.
# EchoKill ensures deterministic block ordering to maximize hit rates.
# - System prompt → shared blocks (sorted) → agent role → delta → message

Cache Pre-Warming (Anthropic)

Pre-load expensive shared context into the provider cache before actual queries:

from echokill.providers import AnthropicProviderMiddleware
import anthropic

mw = AnthropicProviderMiddleware(
    client=anthropic.Anthropic(),
    model="claude-sonnet-4-5",
)
mw.register("analyst", role="You analyze financial data.")
mw.share(large_quarterly_report)  # 10K+ tokens

# Pre-warm: loads shared prefix into Anthropic's cache
mw.prewarm("analyst")
# Subsequent calls read from cache at 90% discount ($0.30/MTok vs $3.00/MTok)

result = mw.call("analyst", message="What are the key risks?")

Supported Providers

Provider Caching Type Discount Threshold EchoKill Integration
OpenAI Automatic prefix 50% off input ≥1024 tokens Deterministic prefix ordering
Anthropic Explicit breakpoints 90% off reads ≥1024 tokens cache_control markers + pre-warm
Azure OpenAI Same as OpenAI 50% off input ≥1024 tokens Same prefix ordering
Google Context caching 75% off reads ≥1024 tokens Stable prefix extraction

Cost Tracking

Track actual dollar savings with model-specific pricing (July 2026 prices).

Quick Start

from echokill.cost import CostTracker

tracker = CostTracker(model="gpt-4o")
tracker.record(agent_id="reviewer", tokens_sent=2500, tokens_saved=5500)
tracker.record(agent_id="analyst", tokens_sent=3000, tokens_saved=7000)

print(tracker.summary())
# {
#     'model': 'gpt-4o',
#     'total_calls': 2,
#     'total_cost_usd': '$0.0138',
#     'total_saved_usd': '$0.0313',
#     'naive_cost_usd': '$0.0450',
#     'savings_percentage': '69.4%',
#     'roi_multiplier': '2.3x',
#     'pricing': {'input_per_mtok': '$2.5', 'cached_read_per_mtok': '$1.25'}
# }

Pricing Database

Built-in pricing for all major current models (per 1M tokens):

Model Input $/MTok Cached Read $/MTok Cache Write $/MTok Output $/MTok
GPT-5.6 (sol) $5.00 $0.50 $5.00 $30.00
GPT-5.6 (terra) $2.50 $0.25 $2.50 $15.00
GPT-5.6 (luna) $1.00 $0.10 $1.00 $6.00
GPT-5.4 $2.50 $0.25 $2.50 $15.00
GPT-5.4-mini $0.75 $0.075 $0.75 $4.50
GPT-5.4-nano $0.20 $0.02 $0.20 $1.25
Claude Opus 4.5 / 4.8 $5.00 $0.50 $6.25 $25.00
Claude Sonnet 5 * $2.00 $0.20 $2.50 $10.00
Claude Sonnet 4.5 $3.00 $0.30 $3.75 $15.00
Claude Haiku 4.5 $1.00 $0.10 $1.25 $5.00
Gemini 3.1 Pro $2.00 $0.20 $2.00 $12.00
Gemini 3.5 Flash $1.50 $0.15 $1.50 $9.00
Gemini 2.5 Pro $1.25 $0.125 $1.25 $10.00
Gemini 2.5 Flash $0.30 $0.03 $0.30 $2.50
Gemini 2.5 Flash-Lite $0.10 $0.01 $0.10 $0.40
GPT-4o (legacy) $2.50 $1.25 $2.50 $10.00
GPT-4o-mini (legacy) $0.15 $0.075 $0.15 $0.60

* Claude Sonnet 5 introductory pricing is in effect through 2026-08-31; it reverts to $3.00 input / $15.00 output on 2026-09-01. Legacy models (gpt-4-turbo, o1, o3-mini, Claude 4 / 3.5, Gemini 1.5) remain in the pricing DB for back-compat.

Per-Agent Breakdown

print(tracker.agent_cost("reviewer"))
# {'agent_id': 'reviewer', 'calls': 1, 'cost_usd': 0.00625, 'saved_usd': 0.01375}

print(f"ROI: {tracker.roi_multiplier:.1f}x")  # savings / cost ratio

With Cache-Aware Tracking

tracker.record(
    agent_id="analyst",
    tokens_sent=5000,
    tokens_saved=10000,
    tokens_cached_read=3000,   # served from provider cache
    tokens_cache_write=2000,   # written to provider cache
)
# Accounts for discounted cache reads + cache write costs

Conversation History Compaction

As conversations grow, older turns become less relevant. The HistoryCompactor intelligently compresses history while preserving critical context.

Strategies

Strategy Description Best For
SLIDING_WINDOW Keep only the last N turns Simple chat bots
TOKEN_BUDGET Keep history within a token limit Context window management
SUMMARY Replace old turns with a summary Long-running conversations
PRIORITY Always keep tool results + system prompts Agentic workflows

Usage

from echokill.compaction import HistoryCompactor, CompactionStrategy

# Token budget strategy — keep history under 8K tokens
compactor = HistoryCompactor(
    strategy=CompactionStrategy.TOKEN_BUDGET,
    token_budget=8000,
)
result = compactor.compact(history_blocks)

print(f"Kept: {len(result.kept_blocks)} blocks")
print(f"Removed: {len(result.removed_blocks)} blocks")
print(f"Compaction ratio: {result.compaction_ratio:.0%}")
# e.g., "Compaction ratio: 45%" — removed 45% of tokens

Sliding Window

compactor = HistoryCompactor(
    strategy=CompactionStrategy.SLIDING_WINDOW,
    max_turns=20,  # keep only last 20 blocks
)
result = compactor.compact(all_blocks)

Summary Compaction

Replace removed turns with a generated summary:

def my_summarizer(blocks: list[Block]) -> str:
    """Call an LLM to summarize old turns."""
    text = "\n".join(b.content for b in blocks)
    return llm_summarize(text)

compactor = HistoryCompactor(
    strategy=CompactionStrategy.SUMMARY,
    token_budget=8000,
    summary_fn=my_summarizer,
)
result = compactor.compact(history)
# result.summary_block contains the generated summary

Priority-Based Compaction

Keep important block types regardless of age:

from echokill.block import BlockType

compactor = HistoryCompactor(
    strategy=CompactionStrategy.PRIORITY,
    token_budget=8000,
    priority_types={BlockType.TOOL_RESULT, BlockType.SYSTEM_PROMPT},
)
# Tool results and system prompts are never evicted

Multi-Provider Middleware

Provider-specific middleware combines EchoKill deduplication with native provider caching for maximum cost reduction.

OpenAI

from openai import OpenAI
from echokill.providers import OpenAIProviderMiddleware

mw = OpenAIProviderMiddleware(client=OpenAI(), model="gpt-4o")
mw.register("reviewer", role="You review code for bugs.")
mw.share(code_diff)

result = mw.call("reviewer", message="Find critical bugs.")
print(result["response"].choices[0].message.content)
print(result["cost"])  # real dollar breakdown

Anthropic (with Cache Pre-Warming)

import anthropic
from echokill.providers import AnthropicProviderMiddleware

mw = AnthropicProviderMiddleware(
    client=anthropic.Anthropic(),
    model="claude-sonnet-4-5",
)
mw.register("analyst", role="You are a financial analyst.")
mw.share(quarterly_report)  # 10K+ tokens

# Pre-warm the cache (loads prefix at write cost, subsequent reads at 90% discount)
mw.prewarm("analyst")

# All subsequent calls benefit from cached prefix
result = mw.call("analyst", message="Summarize revenue trends.")
result = mw.call("analyst", message="Identify top 3 risks.")

Azure OpenAI

from openai import AzureOpenAI
from echokill.providers import AzureOpenAIProviderMiddleware

client = AzureOpenAI(
    azure_endpoint="https://your-resource.openai.azure.com/",
    api_version="2024-06-01",
)
mw = AzureOpenAIProviderMiddleware(client=client, model="gpt-4o")
mw.register("reviewer", role="Enterprise code reviewer.")
mw.share(code_diff)

result = mw.call("reviewer", message="Check for security issues.")

Combined Cost Summary

print(mw.summary())
# {
#     'total_calls': 3,
#     'total_tokens_sent': 7500,
#     'total_tokens_saved': 17500,
#     'cost': {
#         'total_cost_usd': '$0.019',
#         'total_saved_usd': '$0.044',
#         'roi_multiplier': '2.3x',
#     }
# }

Async Support

AsyncMemoryBus provides the same API as MemoryBus but with async/await for use in FastAPI, aiohttp, and other async frameworks.

Usage

from echokill.aio import AsyncMemoryBus, AsyncRedisBlockStore
import redis.asyncio as aioredis

# With async Redis
redis_client = aioredis.from_url("redis://localhost:6379/0")
store = AsyncRedisBlockStore(redis_client, ttl=86400)

bus = AsyncMemoryBus(store=store)
await bus.register_agent("reviewer", role="You review code.")
await bus.add_shared_block(code_diff)

prompt = await bus.build_prompt("reviewer", new_message="Review this code.")
# prompt.messages → send to provider

FastAPI Integration

from fastapi import FastAPI, Depends
from echokill.aio import AsyncMemoryBus, AsyncRedisBlockStore
import redis.asyncio as aioredis

app = FastAPI()
redis_pool = aioredis.from_url("redis://localhost:6379/0")

async def get_bus(session_id: str) -> AsyncMemoryBus:
    store = AsyncRedisBlockStore(redis_pool, key_prefix=f"echokill:{session_id}:")
    return AsyncMemoryBus(store=store)

@app.post("/review")
async def review_code(code: str, session_id: str):
    bus = await get_bus(session_id)
    await bus.register_agent("reviewer", role="Code reviewer.")
    await bus.add_shared_block(code)
    prompt = await bus.build_prompt("reviewer", new_message="Review this.")
    # Send prompt.messages to your LLM provider
    return {"tokens_saved": prompt.tokens_saved_estimate}

Multi-User / Multi-Tenant Architecture

EchoKill supports concurrent users through tenant-isolated bus instances backed by a shared Redis store. Each user/session gets its own MemoryBus with independent agent state while benefiting from cross-session content deduplication at the storage layer.

Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
│                     Application Layer (FastAPI / Flask)           │
│                                                                   │
│   Request → Auth → TenantResolver → BusFactory → MemoryBus       │
└─────────────────────────────┬───────────────────────────────────┘
                              │
┌─────────────────────────────▼───────────────────────────────────┐
│                     EchoKill Bus Pool                             │
│                                                                   │
│   ┌──────────────┐  ┌──────────────┐  ┌──────────────┐          │
│   │ Bus: user_1   │  │ Bus: user_2   │  │ Bus: user_N   │          │
│   │ session: abc  │  │ session: def  │  │ session: xyz  │          │
│   │ agents: 3     │  │ agents: 5     │  │ agents: 2     │          │
│   └──────┬───────┘  └──────┬───────┘  └──────┬───────┘          │
│          │                  │                  │                   │
└──────────┼──────────────────┼──────────────────┼─────────────────┘
           │                  │                  │
┌──────────▼──────────────────▼──────────────────▼─────────────────┐
│              Shared Redis Block Store (Cluster Mode)               │
│                                                                   │
│   Key schema: echokill:{tenant_id}:block:{hash}                   │
│   TTL: configurable per tenant (default 24h for sessions)         │
│   Eviction: LRU on idle sessions, pin high-value shared blocks    │
└───────────────────────────────────────────────────────────────────┘

Per-User Session Management

import redis
from echokill import MemoryBus
from echokill.store import RedisBlockStore

# --- Shared Redis connection pool (create once at app startup) ---
redis_pool = redis.ConnectionPool(
    host="redis-cluster.internal",
    port=6379,
    password="<from-secrets-manager>",
    max_connections=100,
    decode_responses=True,
    socket_timeout=5,
    socket_connect_timeout=2,
    retry_on_timeout=True,
)

def create_user_bus(tenant_id: str, session_id: str) -> MemoryBus:
    """Factory: create an isolated bus per user session.
    
    Each user gets:
    - Isolated agent states (no cross-user leakage)
    - Tenant-scoped Redis keys (namespace isolation)
    - Independent metrics tracking
    - Configurable TTL for automatic session cleanup
    """
    client = redis.Redis(connection_pool=redis_pool)
    store = RedisBlockStore(
        redis_client=client,
        ttl=86400,  # 24-hour session TTL
        # Namespace isolation: each tenant's blocks are prefixed
        # key_prefix=f"echokill:{tenant_id}:{session_id}:"  
    )
    return MemoryBus(store=store)

FastAPI Multi-Tenant Integration

from fastapi import FastAPI, Depends, HTTPException, Request
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from pydantic import BaseModel
from contextlib import asynccontextmanager
import uuid

app = FastAPI(title="EchoKill Multi-Agent Service")
security = HTTPBearer()

# --- Bus pool: manages per-session bus instances ---
class BusPool:
    """Thread-safe pool of MemoryBus instances keyed by session."""
    
    def __init__(self, redis_pool, max_sessions: int = 10_000):
        self._pool: dict[str, MemoryBus] = {}
        self._redis_pool = redis_pool
        self._max_sessions = max_sessions
    
    def get_or_create(self, tenant_id: str, session_id: str) -> MemoryBus:
        key = f"{tenant_id}:{session_id}"
        if key not in self._pool:
            if len(self._pool) >= self._max_sessions:
                self._evict_oldest()
            self._pool[key] = create_user_bus(tenant_id, session_id)
        return self._pool[key]
    
    def destroy_session(self, tenant_id: str, session_id: str) -> None:
        key = f"{tenant_id}:{session_id}"
        self._pool.pop(key, None)
    
    def _evict_oldest(self):
        # LRU eviction — remove least recently used session
        oldest_key = next(iter(self._pool))
        del self._pool[oldest_key]

bus_pool = BusPool(redis_pool=redis_pool)

# --- Auth & tenant resolution ---
async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
    """Validate JWT and extract tenant/user info."""
    token = credentials.credentials
    # In production: validate JWT signature, expiry, claims
    payload = validate_jwt(token)  # your JWT validation logic
    return {
        "tenant_id": payload["tenant_id"],
        "user_id": payload["user_id"],
        "session_id": payload.get("session_id", str(uuid.uuid4())),
    }

# --- Request models ---
class AgentRequest(BaseModel):
    agent_id: str
    message: str
    shared_context: list[str] | None = None

class AgentResponse(BaseModel):
    messages: list[dict[str, str]]
    tokens_saved: int
    total_tokens: int
    session_id: str

# --- Endpoints ---
@app.post("/v1/agents/register")
async def register_agent(
    agent_id: str,
    role: str,
    user=Depends(get_current_user),
):
    bus = bus_pool.get_or_create(user["tenant_id"], user["session_id"])
    bus.register_agent(agent_id, role=role)
    return {"status": "registered", "agent_id": agent_id}

@app.post("/v1/prompts/build", response_model=AgentResponse)
async def build_prompt(
    request: AgentRequest,
    user=Depends(get_current_user),
):
    bus = bus_pool.get_or_create(user["tenant_id"], user["session_id"])
    
    # Add any new shared context
    if request.shared_context:
        for ctx in request.shared_context:
            bus.add_shared_block(ctx)
    
    # Build the deduplicated prompt
    compiled = bus.build_prompt(request.agent_id, new_message=request.message)
    
    return AgentResponse(
        messages=compiled.messages,
        tokens_saved=compiled.tokens_saved_estimate,
        total_tokens=compiled.total_tokens_estimate,
        session_id=user["session_id"],
    )

@app.get("/v1/sessions/{session_id}/metrics")
async def get_session_metrics(session_id: str, user=Depends(get_current_user)):
    bus = bus_pool.get_or_create(user["tenant_id"], session_id)
    return bus.metrics.summary()

@app.delete("/v1/sessions/{session_id}")
async def destroy_session(session_id: str, user=Depends(get_current_user)):
    bus_pool.destroy_session(user["tenant_id"], session_id)
    return {"status": "destroyed"}

Extracting User Identity from Webhooks / Message Queues

# For async pipelines (Celery, RabbitMQ, SQS)
from celery import Celery

celery_app = Celery("echokill_worker")

@celery_app.task(bind=True, max_retries=3)
def process_agent_request(self, tenant_id: str, session_id: str, agent_id: str, message: str):
    """Async task: build prompt for a specific user's session."""
    bus = create_user_bus(tenant_id, session_id)
    # Restore session state from Redis (blocks persist via RedisBlockStore)
    compiled = bus.build_prompt(agent_id, new_message=message)
    return {
        "messages": compiled.messages,
        "tokens_saved": compiled.tokens_saved_estimate,
    }

Production Deployment

Docker

# Dockerfile
FROM python:3.11-slim AS base

WORKDIR /app

# Install system deps
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    && rm -rf /var/lib/apt/lists/*

# Install Python deps
COPY pyproject.toml .
RUN pip install --no-cache-dir -e ".[redis]" gunicorn uvicorn[standard]

COPY . .

# Non-root user for security
RUN useradd -m -r echokill && chown -R echokill:echokill /app
USER echokill

# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

EXPOSE 8000

CMD ["gunicorn", "app:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", \
     "--bind", "0.0.0.0:8000", "--timeout", "120", "--graceful-timeout", "30"]

Docker Compose (local development)

# docker-compose.yml
version: "3.9"

services:
  echokill-api:
    build: .
    ports:
      - "8000:8000"
    environment:
      - ECHOKILL_REDIS_URL=redis://redis:6379/0
      - ECHOKILL_LOG_LEVEL=info
      - ECHOKILL_MAX_SESSIONS=10000
      - ECHOKILL_BLOCK_TTL=86400
    depends_on:
      redis:
        condition: service_healthy
    deploy:
      replicas: 2
      resources:
        limits:
          memory: 2G
          cpus: "2.0"

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    command: >
      redis-server
        --maxmemory 1gb
        --maxmemory-policy allkeys-lru
        --save 60 1000
        --appendonly yes
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3

  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    depends_on:
      - prometheus

volumes:
  redis-data:

Kubernetes Deployment

# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echokill-api
  labels:
    app: echokill
    component: api
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: echokill
  template:
    metadata:
      labels:
        app: echokill
        component: api
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "8000"
        prometheus.io/path: "/metrics"
    spec:
      serviceAccountName: echokill-sa
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
      containers:
        - name: echokill
          image: your-registry/echokill:latest
          ports:
            - containerPort: 8000
              protocol: TCP
          env:
            - name: ECHOKILL_REDIS_URL
              valueFrom:
                secretKeyRef:
                  name: echokill-secrets
                  key: redis-url
            - name: ECHOKILL_MAX_SESSIONS
              value: "50000"
            - name: ECHOKILL_BLOCK_TTL
              value: "86400"
          resources:
            requests:
              memory: "512Mi"
              cpu: "500m"
            limits:
              memory: "2Gi"
              cpu: "2000m"
          readinessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 15
            periodSeconds: 20
---
apiVersion: v1
kind: Service
metadata:
  name: echokill-api
spec:
  type: ClusterIP
  selector:
    app: echokill
  ports:
    - port: 80
      targetPort: 8000
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: echokill-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: echokill-api
  minReplicas: 3
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Pods
      pods:
        metric:
          name: echokill_active_sessions
        target:
          type: AverageValue
          averageValue: "5000"

Configuration Reference

All settings can be provided via environment variables or a config file.

Variable Default Description
ECHOKILL_REDIS_URL redis://localhost:6379/0 Redis connection string
ECHOKILL_REDIS_PASSWORD Redis auth password (use secrets manager)
ECHOKILL_REDIS_SSL false Enable TLS for Redis connections
ECHOKILL_REDIS_POOL_SIZE 100 Max Redis connection pool size
ECHOKILL_MAX_SESSIONS 10000 Max concurrent user sessions in memory
ECHOKILL_BLOCK_TTL 86400 Block expiry in seconds (0 = no expiry)
ECHOKILL_MAX_CONTEXT_TOKENS 128000 Max tokens per compiled prompt
ECHOKILL_LOG_LEVEL info Logging level (debug/info/warning/error)
ECHOKILL_METRICS_ENABLED true Enable Prometheus metrics export
ECHOKILL_METRICS_PORT 8000 Port for /metrics endpoint
ECHOKILL_TENANT_ISOLATION strict Isolation mode: strict / shared
ECHOKILL_RATE_LIMIT_RPM 1000 Requests per minute per tenant
ECHOKILL_SESSION_IDLE_TIMEOUT 3600 Auto-destroy idle sessions (seconds)

Example Configuration File

# config/echokill.yaml
redis:
  url: redis://redis-cluster.internal:6379/0
  password: ${ECHOKILL_REDIS_PASSWORD}
  ssl: true
  pool_size: 200
  socket_timeout: 5
  retry_on_timeout: true

sessions:
  max_concurrent: 50000
  idle_timeout: 3600
  block_ttl: 86400

compiler:
  max_context_tokens: 128000

security:
  tenant_isolation: strict
  rate_limit_rpm: 1000
  api_key_required: true

observability:
  log_level: info
  metrics_enabled: true
  tracing_enabled: true
  trace_sample_rate: 0.1

Security & Data Isolation

Tenant Isolation Model

EchoKill enforces strict tenant isolation by default:

Layer Mechanism
Storage Redis key namespacing: echokill:{tenant_id}:block:{hash}
Memory Each session gets its own MemoryBus instance — no shared mutable state
Network TLS for Redis connections; mTLS between service pods
Auth JWT validation with tenant claim extraction at the gateway
Eviction Sessions destroyed on logout or after idle timeout

Data Flow Security

User Request
    │
    ▼
┌──────────────────┐
│  API Gateway      │  ← TLS termination, rate limiting, JWT validation
│  (Kong / Envoy)   │
└────────┬─────────┘
         │ (authenticated, tenant_id extracted)
         ▼
┌──────────────────┐
│  EchoKill API     │  ← Input validation, size limits, content sanitization
│  (FastAPI)        │
└────────┬─────────┘
         │ (tenant-scoped operations only)
         ▼
┌──────────────────┐
│  Redis Store      │  ← ACL per-tenant, encrypted at rest, network-isolated
│  (Cluster)        │
└──────────────────┘

Security Checklist for Production

  • Authentication: JWT or OAuth2 on all endpoints; no anonymous access
  • Authorization: Tenant-scoped access — users can only access their own sessions
  • Input validation: Max block size (default 100KB), max blocks per session (1000)
  • Rate limiting: Per-tenant RPM limits at the gateway level
  • TLS: All Redis connections use TLS in production
  • Secrets management: Redis passwords, API keys stored in Vault/AWS Secrets Manager
  • Audit logging: Log session create/destroy, agent registration, block additions
  • Data retention: Configure TTL for GDPR/compliance; support DELETE /sessions/{id}
  • No PII in blocks: Document guidelines that shared blocks should not contain PII
  • Redis ACL: Use Redis 6+ ACLs to restrict key patterns per service account

Input Validation

# Recommended limits (enforce at API layer)
MAX_BLOCK_SIZE_BYTES = 100 * 1024    # 100 KB per block
MAX_BLOCKS_PER_SESSION = 1000         # prevent memory abuse
MAX_AGENTS_PER_SESSION = 50           # reasonable agent limit
MAX_MESSAGE_SIZE_BYTES = 50 * 1024    # 50 KB per user message
ALLOWED_BLOCK_TYPES = {"document", "system_prompt", "custom"}

Monitoring & Observability

Prometheus Metrics

EchoKill exports the following metrics for production monitoring:

# Key metrics exported at /metrics
echokill_prompts_compiled_total          # Counter: total prompts built
echokill_tokens_saved_total              # Counter: cumulative tokens saved
echokill_tokens_sent_total               # Counter: cumulative tokens actually sent
echokill_savings_ratio                   # Gauge: current savings ratio (0.0-1.0)
echokill_active_sessions                 # Gauge: current live session count
echokill_blocks_stored_total             # Gauge: unique blocks in store
echokill_prompt_compile_duration_seconds # Histogram: prompt compilation latency
echokill_redis_operation_duration_seconds # Histogram: Redis op latency
echokill_session_lifetime_seconds        # Histogram: session durations
echokill_block_size_bytes                # Histogram: block sizes

Grafana Dashboard (recommended panels)

Panel Metric Purpose
Token Savings Rate echokill_savings_ratio Are we meeting the 65%+ target?
Prompt Throughput rate(echokill_prompts_compiled_total[5m]) Requests per second
Compilation Latency p99 histogram_quantile(0.99, echokill_prompt_compile_duration_seconds) Ensure <50ms
Active Sessions echokill_active_sessions Capacity planning
Redis Latency histogram_quantile(0.95, echokill_redis_operation_duration_seconds) Backend health
Cost Savings ($) echokill_tokens_saved_total * $0.000003 Business value

Structured Logging

{
  "timestamp": "2026-06-09T14:30:00Z",
  "level": "info",
  "event": "prompt_compiled",
  "tenant_id": "tenant_abc",
  "session_id": "sess_123",
  "agent_id": "security_reviewer",
  "tokens_sent": 2500,
  "tokens_saved": 5500,
  "savings_ratio": 0.69,
  "shared_blocks": 3,
  "delta_blocks": 1,
  "compile_duration_ms": 12.3
}

Health Check Endpoint

@app.get("/health")
async def health():
    """Liveness + readiness probe."""
    checks = {
        "redis": await check_redis_connection(),
        "memory": get_memory_usage_mb() < MAX_MEMORY_MB,
        "sessions": bus_pool.active_count() < bus_pool.max_sessions,
    }
    status = "healthy" if all(checks.values()) else "degraded"
    code = 200 if status == "healthy" else 503
    return JSONResponse({"status": status, "checks": checks}, status_code=code)

API Reference

Core Classes

MemoryBus

The central orchestrator. One instance per user session.

bus = MemoryBus(store=RedisBlockStore(...), compiler=PromptCompiler(max_context_tokens=128000))
Method Description
register_agent(agent_id, role=None) Register an agent with optional role persona
add_shared_block(content, block_type=DOCUMENT) Add content shared across all agents
add_agent_block(agent_id, content) Add content visible only to one agent
build_prompt(agent_id, new_message=None)CompiledPrompt Build a deduplicated prompt
metricsBusMetrics Access token savings analytics
summary()dict Human-readable bus state

CompiledPrompt

Returned by build_prompt(). Send .messages directly to any LLM API.

Attribute Type Description
messages list[dict[str, str]] Ready-to-send message list
shared_block_hashes list[str] Hashes of shared blocks included
delta_block_hashes list[str] Hashes of agent-specific delta blocks
total_tokens_estimate int Estimated tokens in compiled prompt
tokens_saved_estimate int Tokens avoided via deduplication

RedisBlockStore

Production-grade block storage with Redis.

store = RedisBlockStore(redis_client=client, ttl=86400)
Method Description
put(block)str Store block, return hash (idempotent)
get(hash)Block | None Retrieve by hash
has(hash)bool Check existence
all_hashes()set[str] List all stored hashes
size()int Count of unique blocks

BusMetrics

Token savings analytics.

Method/Property Description
total_tokens_sent Cumulative tokens actually sent
total_tokens_saved Cumulative tokens eliminated
savings_ratio Float 0.0–1.0 representing % saved
agent_stats(agent_id) Per-agent breakdown
summary() Dict with all aggregate stats

Using the Middleware

For drop-in integration with any LLM call function:

from echokill.middleware import EchoKillMiddleware

mw = EchoKillMiddleware()
mw.register("analyst", role="You are a financial analyst.")
mw.register("compliance", role="You are a compliance officer.")
mw.share(quarterly_report)

result = mw.call("analyst", message="What are the key risks?", llm_fn=my_llm_call)
print(result["prompt_info"])  # token savings metrics

OpenAI Integration

from openai import OpenAI
from echokill.middleware import OpenAIMiddleware

client = OpenAI()
mw = OpenAIMiddleware(client=client, model="gpt-4o-mini")

mw.register("reviewer", role="You are a code reviewer.")
mw.share(code_diff)

result = mw.call_openai("reviewer", message="Review this code.")

Azure OpenAI Integration

from openai import AzureOpenAI
from echokill.middleware import OpenAIMiddleware

client = AzureOpenAI(
    azure_endpoint="https://your-resource.openai.azure.com/",
    api_version="2024-06-01",
    # api_key loaded from environment or managed identity
)

mw = OpenAIMiddleware(client=client, model="gpt-4o")
mw.register("reviewer", role="You review enterprise code.")
mw.share(code_diff)

result = mw.call_openai("reviewer", message="Identify security vulnerabilities.")

Multi-User Middleware Pattern

from echokill.middleware import EchoKillMiddleware
from echokill.store import RedisBlockStore
from echokill.bus import MemoryBus

class MultiUserMiddleware:
    """Production pattern: one middleware per user session."""
    
    def __init__(self, redis_client):
        self._redis = redis_client
        self._sessions: dict[str, EchoKillMiddleware] = {}
    
    def get_session(self, user_id: str, session_id: str) -> EchoKillMiddleware:
        key = f"{user_id}:{session_id}"
        if key not in self._sessions:
            store = RedisBlockStore(redis_client=self._redis, ttl=86400)
            bus = MemoryBus(store=store)
            self._sessions[key] = EchoKillMiddleware(bus=bus)
        return self._sessions[key]
    
    def cleanup_session(self, user_id: str, session_id: str) -> dict:
        key = f"{user_id}:{session_id}"
        if key in self._sessions:
            metrics = self._sessions[key].metrics_summary()
            del self._sessions[key]
            return metrics
        return {}

Scaling & Performance

Horizontal Scaling Strategy

                    ┌─── Load Balancer (sticky sessions optional) ───┐
                    │                                                  │
         ┌──────────▼──────────┐                    ┌──────────▼──────────┐
         │  EchoKill Worker 1   │                    │  EchoKill Worker N   │
         │  (stateless API)     │                    │  (stateless API)     │
         └──────────┬──────────┘                    └──────────┬──────────┘
                    │                                            │
                    └────────────────┬───────────────────────────┘
                                     │
                    ┌────────────────▼────────────────┐
                    │      Redis Cluster (6+ nodes)    │
                    │                                  │
                    │  Shard 1 │ Shard 2 │ Shard 3     │
                    │  (blocks)│ (blocks)│ (blocks)    │
                    └──────────────────────────────────┘

Performance Characteristics

Operation Latency (p99) Throughput
build_prompt() (in-memory) <5 ms 10,000 req/s per core
build_prompt() (Redis) <25 ms 2,000 req/s per core
add_shared_block() <10 ms 5,000 req/s
Block store lookup (Redis) <3 ms 50,000 ops/s
Session creation <5 ms 10,000/s

Capacity Planning

Concurrent Users Workers Redis Memory Redis Nodes Est. Monthly Cost Savings
100 2 512 MB 1 $500–$2,000
1,000 4 2 GB 3 $5,000–$20,000
10,000 8 8 GB 6 $50,000–$200,000
100,000 20+ 32 GB 12+ $500,000+

Cost savings assume average 70% token reduction at ~$2.50/1M input tokens (GPT-5.4 / GPT-4o pricing).

Optimization Tips

  1. Enable Redis pipelining for batch block operations
  2. Use Redis Cluster for >10GB data or >50K concurrent sessions
  3. Set appropriate TTLs — blocks from completed sessions should expire
  4. Use connection pooling — never create per-request Redis connections
  5. Pin shared prefix order — deterministic ordering maximizes LLM provider prefix caching
  6. Batch agent prompts — compile all agent prompts in a session together to amortize Redis reads

Project Structure

echokill/
├── __init__.py          # Public API exports
├── block.py             # Content block primitives (Block, BlockType)
├── store.py             # Content-addressed storage (InMemory + Redis)
├── agent_state.py       # Per-agent state tracking (seen-set, deltas)
├── compiler.py          # Prompt compiler (dedup + prefix ordering)
├── bus.py               # MemoryBus — main orchestrator
├── metrics.py           # Token savings analytics
├── middleware.py         # Framework adapters (generic + OpenAI)
├── tokenizers.py        # Multi-backend token counting (tiktoken, approximate)
├── cache_optimizer.py   # Provider-aware cache optimization
├── cost.py              # Dollar cost tracking with pricing DB
├── compaction.py        # Conversation history compaction strategies
├── providers.py         # Provider-specific middleware (OpenAI, Anthropic, Azure)
├── aio.py              # Async bus + async Redis store
├── pool.py             # Multi-session BusPool with LRU eviction
├── config.py           # Environment-based configuration
├── validation.py       # Input validation for blocks and prompts
├── health.py           # Health check endpoint
├── logging.py          # Structured logging setup
└── py.typed            # PEP 561 marker for type checkers

tests/
├── test_block.py
├── test_store.py
├── test_bus.py
├── test_compiler.py
├── test_middleware.py
├── test_features_v03.py   # v0.3.0 feature tests (tokenizers, cache, cost, etc.)
└── test_pool.py

examples/
├── code_review_pipeline.py   # 5-agent code review demo
└── middleware_usage.py        # Middleware with mock LLM

Key Concepts

Concept Description
Block Immutable, content-addressed chunk of context (SHA-256 keyed)
BlockStore Deduplicating storage — identical content stored exactly once
AgentState Tracks what each agent has seen; computes deltas
PromptCompiler Assembles shared prefix + agent delta + new message
MemoryBus Top-level orchestrator tying everything together
Tenant Organizational unit — each tenant's data is fully isolated
Session Per-user conversation context; owns a bus instance and agents
BusPool Manages lifecycle of per-session bus instances with LRU eviction
CacheOptimizer Generates provider-specific prompt formats for cache maximization
CostTracker Tracks actual USD savings with model-specific pricing
HistoryCompactor Compresses conversation history using configurable strategies
Tokenizer Multi-backend token counting (tiktoken, approximate, Anthropic)
ProviderMiddleware End-to-end middleware combining dedup + provider caching + cost tracking
AsyncMemoryBus Async version of MemoryBus for non-blocking I/O frameworks

Estimated Savings

Scenario Without EchoKill With EchoKill Reduction
5 agents, 10-turn, 4K shared doc ~200K input tokens ~70K tokens 65%
+ provider prefix caching (OpenAI) ~49K effective 75%
+ Anthropic cache breakpoints ~35K effective 82%
5 agents, 1-turn code review 25K tokens 7.5K tokens 70%
100 users × 5 agents × 10 turns/day 20M tokens/day 6M tokens/day 70%
+ provider caching stacked 3M effective/day 85%
Annual savings (100 users, GPT-5.4) $21,900/year $6,570/year $15,330 saved
Annual savings (100 users, Claude Sonnet 4.5) $26,280/year $3,942/year $22,338 saved

Anthropic cache reads are 90% cheaper, making Claude models particularly cost-effective with EchoKill.


Running Tests

pip install -e ".[dev]"
pytest

With coverage

pytest --cov=echokill --cov-report=html

Integration tests (requires Redis)

ECHOKILL_REDIS_URL=redis://localhost:6379/0 pytest tests/ -m integration

Load testing

pip install locust
locust -f tests/load/locustfile.py --host=http://localhost:8000

Running Examples

python examples/code_review_pipeline.py
python examples/middleware_usage.py

Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/your-feature
  3. Install dev dependencies: pip install -e ".[dev]"
  4. Run tests: pytest
  5. Run linter: ruff check .
  6. Submit a pull request

Code Quality Requirements

  • All PRs must pass pytest and ruff check
  • New features require tests with >90% coverage
  • Breaking API changes require a migration guide in the PR description

Changelog

v0.3.0

  • Tokenizers: Multi-backend token counting (tiktoken, Anthropic, approximate fallback)
  • Provider-Aware Cache Optimization: Anthropic cache_control markers, OpenAI prefix caching, pre-warming support
  • Cost Tracking: Real USD savings with built-in pricing DB for GPT-4o, Claude Sonnet 4, Gemini, and more
  • Conversation Compaction: 4 strategies (sliding window, token budget, summary, priority-based)
  • Multi-Provider Middleware: OpenAI, Anthropic (with prewarm), Azure OpenAI provider-specific adapters
  • Async Support: AsyncMemoryBus and AsyncRedisBlockStore for async frameworks
  • Stacked savings: EchoKill dedup + provider caching achieves up to 85% effective reduction

v0.2.0

  • Multi-user / multi-tenant architecture with BusPool
  • Redis-backed RedisBlockStore with TTL and key prefixes
  • Environment-based configuration (EchoKillConfig)
  • Input validation for blocks and prompts
  • Prometheus metrics integration
  • Health check endpoints
  • Structured logging
  • py.typed marker for PEP 561

v0.1.0 (Initial Release)

  • Core deduplication engine with content-addressed blocks
  • InMemory and Redis block store backends
  • Per-agent state tracking with seen-set and delta computation
  • Prompt compiler with deterministic prefix ordering
  • Generic middleware + OpenAI-specific adapter
  • Token savings metrics and analytics
  • 65–82% token reduction in multi-agent pipelines

License

MIT

Project details


Download files

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

Source Distribution

echokill-0.3.0.tar.gz (85.3 kB view details)

Uploaded Source

Built Distribution

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

echokill-0.3.0-py3-none-any.whl (55.5 kB view details)

Uploaded Python 3

File details

Details for the file echokill-0.3.0.tar.gz.

File metadata

  • Download URL: echokill-0.3.0.tar.gz
  • Upload date:
  • Size: 85.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.4

File hashes

Hashes for echokill-0.3.0.tar.gz
Algorithm Hash digest
SHA256 38b6e74c818e8a6146de17bfe9197854b4efa047d45858a00c2655915eb1c4b6
MD5 1ee48f4cd10c0fef4e811ac048880295
BLAKE2b-256 dd337f8f27ae6abfb8a583e6fbc0b398c413bf13d503eac9eda7b7af0ac26ecb

See more details on using hashes here.

File details

Details for the file echokill-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: echokill-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 55.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.4

File hashes

Hashes for echokill-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 baa6d4047db16c9ae25a3a060439c2a21ff14b2e4bffe347924ee2578fd827f1
MD5 600943803dc4bb2cd0ffeed6520499a2
BLAKE2b-256 67e0734aedbea54c1d062cf3bb788c12d1dc0cec83389d022b251c1bea21ac31

See more details on using hashes here.

Supported by

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