pip install agentcompile
Wrap your agent's model client. Jobs your agent repeats run compiled, with no model call; everything else goes to your model unchanged, with your own provider key.
from openai import OpenAI
import agentcompile
client = agentcompile.wrap(OpenAI(), key="ack_...") # or AGENTCOMPILE_KEY
resp = client.chat.completions.create(
model="gpt-5", messages=messages, tools=tools,
conversation_id=ticket.id, # one id per conversation
)Anthropic works the same way (agentcompile.wrap(Anthropic()), then client.messages.create(...)). So do async clients, streaming and tool calls. Instead of the keyword, you can set the id for a block of calls:
with agentcompile.conversation(ticket.id):
run_agent(client)- With a conversation id, the SDK asks AgentCompile for a decision (
/v1/decide). The whole call takes at mosttimeout(default 5 s), and AgentCompile is told that, so it answers in time. - compiled: the answer (a tool call or a reply) comes back in exactly the provider's response shape; your loop runs the tool as usual. A tool call is only ever one of the
toolsyou passed. - forwarded: your model is called as normal.
- fail-open: AgentCompile errored, was slow or sent something unusable, so your model is called as normal. After 5 failures in a row the SDK stops asking for 30 s, then tries one call.
- unsupported: the request asks for something a compiled answer couldn't honour (
n> 1, a forcedtool_choice, aresponse_format), so your model is called without asking. - no conversation id: your model is called and no decision is made.
Your provider key never leaves your process. mode="shadow" decides but always calls your model, so you can see what it would have done before you switch it on.
Every call is written to ~/.agentcompile/trail.jsonl (trail= sets a path, or False turns it off; on_event= gets each event).
agentcompile trail # recent calls and a summary
agentcompile trail -f # follow live
14:02:11 c1 compiled get_order_details gpt-5 48 ms decide
14:02:12 c2 forwarded no job matched gpt-5 51 ms decide
2 calls: compiled 1, forwarded 1. Model calls avoided: 1 (50%).
Your AgentCompile key (ack_...) identifies your company by itself. We show it once and store only its hash; a new key revokes the old one. AgentCompile is pre-launch: to get a key, write to founders@tryagentcompile.com.
Commit with conventional messages (fix: ... is a patch, feat: ... a minor version, feat!: ... a major one). A release PR stays open with the next version and its changelog; merging it tags the release and publishes it to PyPI. The version always comes from the git tag.
pip install -e ".[dev]"
pytest tests
Tell AgentCompile how a conversation ended, so it learns only from the ones that went well:
agentcompile.outcome(ticket.id, "resolved") # or escalated, unresolved, abandoned, reopened, complaintPass a stable customer id so repeat jobs are counted per customer (scrubbed like everything else when capture is on):
with agentcompile.conversation(ticket.id, customer=ticket.customer_id):
run_agent()Streamed answers are captured too: your agent reads the stream as always, and the whole answer is captured once it ends.
A wrapped OpenAI client captures client.responses.create(...) as well (streamed or not). Those
calls always go to your model: compiled answers come in Chat Completions' and Anthropic's shapes.
When a framework builds its own client (LangChain, LiteLLM, CrewAI, Pydantic AI, the OpenAI Agents SDK), give it a capturing HTTP client instead of wrapping anything:
http = agentcompile.http_client(key="ack_...") # async: agentcompile.async_http_client()
ChatOpenAI(model="gpt-5", http_client=http) # LangChain
litellm.client_session = http # LiteLLM, CrewAI
OpenAIProvider(http_client=agentcompile.async_http_client()) # Pydantic AI
set_default_openai_client(AsyncOpenAI(http_client=agentcompile.async_http_client())) # Agents SDK
with agentcompile.conversation(ticket.id):
run_agent()It captures the POSTs to /chat/completions, /responses and /messages that pass through
it, scrubbed the same way, and leaves everything else alone. It only captures: to have known
jobs answered compiled, use wrap. An SDK that takes only httpx2 clients (newer anthropic
releases) takes agentcompile.http_client(lib="httpx2").
agentcompile.transport() gives the bare transport, to wrap one of your own.
With capture=True, personal data is scrubbed on your machine before anything is sent:
emails, payment cards, phone numbers and account numbers become keyed tokens
(<email:3f9a1c2e>), the same value always giving the same token, so AgentCompile can still
match values across a conversation without seeing them. The key stays with you:
AGENTCOMPILE_SCRUB_KEY (set the same one on all your servers), else one created once in
~/.agentcompile/scrub.key. scrub=False turns it off. Live decisions (/v1/decide) need
real values to act on a customer's request; they are used in memory and stored scrubbed.