Plano is an AI-native proxy and data plane for agentic apps — with built-in orchestration, safety, observability, and smart LLM routing so you stay focused on your agents core logic. https://planoai.dev
Find a file
Musa 844f08bda7
feat(routing): automatic prompt caching + a per-session routing budget (#982)
* feat(routing): cache-aware routing and zero-config prompt caching

Reconcile prompt caching with intelligent routing: zero-config implicit
session affinity, cache_control preservation/injection across transforms,
cache-adjusted routing economics, and a response-driven cache-hit feedback loop.

* refactor(caching): model-aware cache markers, drop cache-aware routing

* feat(routing): session stickiness cache-regret cost gate

* refactor(routing): expose per-model input and cached rates

* fix(docker): patch libssh2 CVEs by upgrading after package install

* feat: record counterfactual route on vetoed session switches

Add opt-in session_stickiness.record_counterfactual that emits the
plano.switch.counterfactual_route OTEL attribute when the cache-regret
gate retains the previous model, capturing the route it would have taken
had the switch been allowed. Telemetry only; the candidate is never
dispatched. Includes schema, demo config, and a getting-started guide.

* refactor: group prompt-caching and session-stickiness logic into cohesive modules

Extract the two concerns interleaved in the LLM handler into dedicated
sibling modules for readability: prompt_caching.rs (cache-marker injection)
and session_stickiness.rs (session key/prefix-hash resolution, pin lookup,
cache-regret gate, and pin planning). handlers/llm/mod.rs is now a thin
orchestrator that calls them at clear phase boundaries. Behavior-preserving.

* refactor(routing): unify session routing into a cumulative switch budget

Replace observed_cache_hit + per-request threshold with time/provider-TTL
warmth and a per-session switch budget in a single session_router::route()
used by both the full-proxy and /routing decision paths.

* refactor(routing): move switch budget to routing.routing_budget and rename telemetry

* refactor(routing): drop negative-cost budget refund from routing_budget

* refactor(routing): express routing_budget as a percentage overhead cap

Replace the absolute seed_usd pool with max_overhead_pct (whole-number
percent) measured against the session's running never-switch baseline:
allow a paid switch only while cumulative switch spend stays within
max_overhead_pct% of what staying on the anchor would have cost. Track
baseline_usd and switch_spend_usd on the binding (monotonic, no refunds).

* refactor(observability): align routing-budget telemetry with the overhead cap

Rename the switch-decision reasons (within_budget/over_budget -> within_cap/
over_cap) and the session span attributes to match the percentage overhead-cap
model: budget_remaining_in_usd -> overhead_pct + switch_spend_in_usd +
baseline_in_usd, and switch threshold_in_usd -> overhead_ceiling_in_usd.

* feat(observability): emit per-request and cumulative session cost

Price each turn from the catalog rates and surface it as llm.usage.{input,
output,total}_cost_usd on the (llm) span, then accumulate into a conversation-
level session_cost_usd on the binding and emit plano.session.total_cost_in_usd
on the routing span. Cache-creation tokens are priced at the plain input rate,
and the OpenAI vs Anthropic prompt-token convention (cached folded in vs
reported separately) is captured at parse time so the cost math is correct for
both. Rates are resolved request-side since the response path is synchronous.

* refactor(routing): price the never-switch baseline against the session default model

Distinguish the session's default_model (the model it started on, i.e. what it
would have cost by never switching) from anchor_model (the model that handled
the latest request, which the session is warm on). The never-switch baseline now
grows at the default model's cached rate rather than the drifting anchor's, so
the overhead-cap denominator stays true to "% above never-switching"; switch cost
is still measured against the current anchor. Also rename estimate_context_tokens
-> actual_context_tokens (and est_context_tokens -> context_tokens) since the
router prefers the provider's real prompt-token count when the session is warm.

* feat(routing): track bounded route history and price returns to still-warm models

Record a bounded (LRU, capped) per-model visit history on the session binding
and use it to sharpen the switch-cost estimate: a return to a model still within
its cache window re-reads only the tokens appended since its last visit (at its
cached rate) instead of the whole context at the uncached rate, so an A->B->A
switch is no longer over-charged as a full re-ingest. History is carried through
the response-side refresh (refining the anchor's entry from real usage) and
cleared on prefix drift. Adds the plano.switch.candidate_warm_tokens span
attribute.

* refactor(routing): tidy test names and rename pin events to binding events

Shorten run-on test names and replace stale "pin" vocabulary in the session
binding metric (brightstaff_session_pin_events_total -> _binding_events_total),
dropping lifecycle event labels that are no longer emitted.

* feat(routing): price switch cost against uncached rates when caching is off

The overhead-cap gate previously always priced the anchor at its cached rate,
assuming a warm provider cache. When prompt caching is disabled there is no
warm cache to lose, so both the switch cost and the never-switch baseline are
now priced at the plain uncached input rate:
switch_cost = context_tokens x (candidate_uncached - anchor_uncached) / 1M.

* fix(brightstaff): bound pricing-feed fetch with timeouts and warn when session state runs without a tenant header

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(routing): validate the session overhead cap end-to-end across a multi-turn session

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(demos): fix stale pinning semantics and add routing_budget demo script

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Adil Hafeez <adil.hafeez@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-20 17:53:34 -07:00
.claude/skills release 0.4.12 (#830) 2026-03-15 13:03:32 -07:00
.github/workflows release 0.4.27 (#985) 2026-07-09 13:41:50 -07:00
apps release 0.4.27 (#985) 2026-07-09 13:41:50 -07:00
cli release 0.4.27 (#985) 2026-07-09 13:41:50 -07:00
config feat(routing): automatic prompt caching + a per-session routing budget (#982) 2026-07-20 17:53:34 -07:00
crates feat(routing): automatic prompt caching + a per-session routing budget (#982) 2026-07-20 17:53:34 -07:00
demos feat(routing): automatic prompt caching + a per-session routing budget (#982) 2026-07-20 17:53:34 -07:00
docs docs: fix "quuickstart" typo and duplicated heading in supported_providers.rst (#987) 2026-07-15 13:01:42 -07:00
packages feat(web): merge DigitalOcean release announcement updates (#860) 2026-04-02 06:03:52 -07:00
skills fix(ci): switch retired claude-sonnet-4-20250514 to claude-sonnet-4-6 (#975) 2026-06-24 10:13:37 -07:00
tests fix(ci): switch retired claude-sonnet-4-20250514 to claude-sonnet-4-6 (#975) 2026-06-24 10:13:37 -07:00
.dockerignore Rename all arch references to plano (#745) 2026-02-13 15:16:56 -08:00
.gitignore release 0.4.12 (#830) 2026-03-15 13:03:32 -07:00
.gitmodules Remove OMF (#78) 2024-09-24 15:18:20 -07:00
.pre-commit-config.yaml Update black hook for Python 3.14 (#857) 2026-03-31 13:18:45 -07:00
archgw.code-workspace update workspace to exclude build artifact path 2026-02-10 13:37:31 -08:00
build_filter_image.sh release 0.4.27 (#985) 2026-07-09 13:41:50 -07:00
bun.lock feat(web): merge DigitalOcean release announcement updates (#860) 2026-04-02 06:03:52 -07:00
CLAUDE.md docs+cli: deprecate prompt targets and remove generate_prompt_targets command (#944) 2026-06-03 13:38:51 -07:00
CONTRIBUTING.md Removing duplicate lines and $ sign before the bash commands to make quick copy/paste possible of these commands possible. (#719) 2026-02-01 09:55:56 -08:00
Dockerfile feat(routing): automatic prompt caching + a per-session routing budget (#982) 2026-07-20 17:53:34 -07:00
LICENSE Create LICENSE 2024-10-10 06:30:23 -07:00
package-lock.json fix: resolve all open Dependabot security alerts (#866) 2026-04-02 18:00:28 -07:00
package.json fix: resolve all open Dependabot security alerts (#866) 2026-04-02 18:00:28 -07:00
README.md docs+cli: deprecate prompt targets and remove generate_prompt_targets command (#944) 2026-06-03 13:38:51 -07:00
turbo.json feat: redesign archgw -> plano + website in Next.js (#613) 2025-12-18 15:55:15 -08:00

Plano Logo

The AI-native proxy server and data plane for agentic apps.

Plano pulls out the rote plumbing work and decouples you from brittle framework abstractions, centralizing what shouldnt be bespoke in every codebase - like agent routing and orchestration, rich agentic signals and traces for continuous improvement, guardrail filters for safety and moderation, and smart LLM routing APIs for model agility. Use any language or AI framework, and deliver agents faster to production.

Quickstart GuideBuild Agentic Apps with PlanoDocumentationContact

CI Docker Image Build and Deploy Documentation

Star the repo if you found Plano useful — new releases and updates land here first.

Overview

Building agentic demos is easy. Shipping agentic applications safely, reliably, and repeatably to production is hard. After the thrill of a quick hack, you end up building the “hidden middleware” to reach production: routing logic to reach the right agent, guardrail hooks for safety and moderation, evaluation and observability glue for continuous learning, and model/provider quirks scattered across frameworks and application code.

Plano solves this by moving core delivery concerns into a unified, out-of-process dataplane.

Plano pulls rote plumbing out of your framework so you can stay focused on what matters most: the core product logic of your agentic applications. Plano is backed by industry-leading LLM research and built on Envoy by its core contributors, who built critical infrastructure at scale for modern worklaods.

High-Level Network Sequence Diagram: high-level network plano architecture for Plano

Jump to our docs to learn how you can use Plano to improve the speed, safety and obervability of your agentic applications.

Important

Plano and the Plano family of LLMs (like Plano-Orchestrator) are hosted free of charge in the US-central region to give you a great first-run developer experience of Plano. To scale and run in production, you can either run these LLMs locally or contact us on Discord for API keys.


Build Agentic Apps with Plano

Plano handles orchestration, model management, and observability as modular building blocks - letting you configure only what you need (edge proxying for agentic orchestration and guardrails, or LLM routing from your services, or both together) to fit cleanly into existing architectures. Below is a simple multi-agent travel agent built with Plano that showcases all three core capabilities

📁 Full working code: See demos/agent_orchestration/travel_agents/ for complete weather and flight agents you can run locally.

1. Define Your Agents in YAML

# config.yaml
version: v0.3.0

# What you declare: Agent URLs and natural language descriptions
# What you don't write: Intent classifiers, routing logic, model fallbacks, provider adapters, or tracing instrumentation

agents:
  - id: weather_agent
    url: http://localhost:10510
  - id: flight_agent
    url: http://localhost:10520

model_providers:
  - model: openai/gpt-4o
    access_key: $OPENAI_API_KEY
    default: true
  - model: anthropic/claude-3-5-sonnet
    access_key: $ANTHROPIC_API_KEY

listeners:
  - type: agent
    name: travel_assistant
    port: 8001
    router: plano_orchestrator_v1  # Powered by our 4B-parameter routing model. You can change this to different models
    agents:
      - id: weather_agent
        description: |
          Gets real-time weather and forecasts for any city worldwide.
          Handles: "What's the weather in Paris?", "Will it rain in Tokyo?"

      - id: flight_agent
        description: |
          Searches flights between airports with live status and schedules.
          Handles: "Flights from NYC to LA", "Show me flights to Seattle"

tracing:
  random_sampling: 100  # Auto-capture traces for evaluation

2. Write Simple Agent Code

Your agents are just HTTP servers that implement the OpenAI-compatible chat completions endpoint. Use any language or framework:

# weather_agent.py
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
from openai import AsyncOpenAI

app = FastAPI()

# Point to Plano's LLM gateway - it handles model routing for you
llm = AsyncOpenAI(base_url="http://localhost:12001/v1", api_key="EMPTY")

@app.post("/v1/chat/completions")
async def chat(request: Request):
    body = await request.json()
    messages = body.get("messages", [])
    days = 7

    # Your agent logic: fetch data, call APIs, run tools
    # See demos/agent_orchestration/travel_agents/ for the full implementation
    weather_data = await get_weather_data(request, messages, days)

    # Stream the response back through Plano
    async def generate():
        stream = await llm.chat.completions.create(
            model="openai/gpt-4o",
            messages=[{"role": "system", "content": f"Weather: {weather_data}"}, *messages],
            stream=True
        )
        async for chunk in stream:
            yield f"data: {chunk.model_dump_json()}\n\n"

    return StreamingResponse(generate(), media_type="text/event-stream")

3. Start Plano & Query Your Agents

Prerequisites: Follow the prerequisites guide to install Plano and set up your environment.

# Start Plano
planoai up config.yaml
...

# Query - Plano intelligently routes to both agents in a single conversation
curl http://localhost:8001/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-4o",
    "messages": [
      {"role": "user", "content": "I want to travel from NYC to Paris next week. What is the weather like there, and can you find me some flights?"}
    ]
  }'
# → Plano routes to weather_agent for Paris weather ✓
# → Then routes to flight_agent for NYC → Paris flights ✓
# → Returns a complete travel plan with both weather info and flight options

4. Get Observability and Model Agility for Free

Every request is traced end-to-end with OpenTelemetry - no instrumentation code needed.

Automatic Tracing

What You Didn't Have to Build

Infrastructure Concern Without Plano With Plano
Agent Orchestration Write intent classifier + routing logic Declare agent descriptions in YAML
Model Management Handle each provider's API quirks Unified LLM APIs with state management
Rich Tracing Instrument every service with OTEL Automatic end-to-end traces and logs
Learning Signals Build pipeline to capture/export spans Zero-code agentic signals
Adding Agents Update routing code, test, redeploy Add to config, restart

Why it's efficient: Plano uses purpose-built, lightweight LLMs (like our 4B-parameter orchestrator) instead of heavyweight frameworks or GPT-4 for routing - giving you production-grade routing at a fraction of the cost and latency.


Contact

To get in touch with us, please join our discord server. We actively monitor that and offer support there.

Getting Started

Ready to try Plano? Check out our comprehensive documentation:

Contribution

We would love feedback on our Roadmap and we welcome contributions to Plano! Whether you're fixing bugs, adding new features, improving documentation, or creating tutorials, your help is much appreciated. Please visit our Contribution Guide for more details

Star the repo if you found Plano useful — new releases and updates land here first.