Agent observability

Add Caveman to AgentOps: compress a CrewAI crew

Keep AgentOps recording your crew. Wrap one CrewAI agent with Caveman Middleware, compare two tagged AgentOps sessions, and roll back by removing one wrapper.

By CavemanReviewed 4 min readRead Markdown ↗
The path to validate
  1. 01Start the Caveman runtime
  2. 02Wrap the heaviest agent
  3. 03Record two tagged sessions
  4. 04Compare or roll back

This guide adds Caveman Middleware to a CrewAI crew that AgentOps already records. AgentOps keeps doing what it does: same agentops.init(), same dashboard, same session replays. You wrap one agent, run the crew twice with tagged traces, and compare. For the background, read Caveman vs AgentOps.

CrewAI is the example because AgentOps instruments it out of the box. The same steps work with Caveman's AutoGen, LangChain, Agno and LiteLLM adapters. Entry points and version ranges are on the frameworks page.

What do you need before you start?

You need a crew AgentOps already records, and one agent whose tools return a lot of text: search results, logs, file reads, API payloads. Open a recent AgentOps replay and pick the agent whose calls carry the biggest tool results. That is the one to wrap first.

Check versions. caveman-middleware needs Python 3.13 or newer, and the CrewAI adapter accepts crewai >=1.15 <2. Outside that range it declines and passes calls through untouched.

Step 1: Start the Caveman runtime

The middleware sends candidates to a local runtime that compresses them and keeps the originals on disk:

terminal
npm install -g @caveman-ai/cli && caveman setup --install
CAVEMAN_MODE=compress caveman start

Plain caveman start runs in record mode, which measures and changes nothing. Set compress on the runtime and switch the baseline on the client side instead, so the two runs differ in one setting.

Step 2: Wrap one agent, keep AgentOps as it is

terminal
pip install caveman-sdk "caveman-middleware[crewai]==0.1.0a1"

with_caveman_agent takes the keyword arguments you already pass to CrewAI's Agent and returns new ones. Role, goal and backstory pass through untouched. It wraps the llm and adds the caveman_retrieve tool:

python
import os, uuid
import agentops
from crewai import Agent, LLM
from caveman_cloud.middleware import MiddlewareRuntime, Scope
from caveman_middleware.crewai import with_caveman_agent

agentops.init(auto_start_session=False) # reads AGENTOPS_API_KEY

mode = os.environ.get("CAVEMAN_CLIENT_MODE", "compress")
runtime = MiddlewareRuntime(mode=mode)
if mode != "off":
runtime.ready() # raises if the runtime is unreachable
run_id = str(uuid.uuid4())

analyst = Agent(**with_caveman_agent({
"role": "Log analyst",
"goal": "Find the root cause in the service logs",
"backstory": "You read logs and recover original evidence when needed.",
"llm": LLM(model="gpt-4.1-mini"),
"tools": [read_logs], # your existing tools
}, runtime=runtime, scope=Scope("triage", f"{run_id}-analyst", "main", "0")))

Give each agent context its own Scope. The four parts key the stored originals, so a handle only resolves under the scope that created it.

The rest of the crew, tasks and process stay the same.

Step 3: Record two tagged sessions

Wrap the crew run in an AgentOps trace tagged with the mode:

python
trace = agentops.start_trace("triage", tags=[f"caveman-{mode}"])
result = crew.kickoff(inputs={"service": "checkout"})
agentops.end_trace(trace, end_state="Success")
terminal
CAVEMAN_CLIENT_MODE=record python triage.py
CAVEMAN_CLIENT_MODE=compress python triage.py

Same inputs, same model, same tools. The record run is your baseline: the runtime measures each call and sends the original bytes.

How do you check it worked?

Open the two sessions in AgentOps side by side and work through this list.

  1. The analyst's model calls. Search the compress replay for caveman: shortened. AgentOps hooks CrewAI's LiteLLM-backed LLM.call; CrewAI 1.x routes gpt-4.1-mini to a native OpenAI class, so if the replay shows no model spans, pass is_litellm=True to LLM(...) (needs litellm installed) or read the Caveman reports below instead. The tool span keeps the full original.
  2. Recovery calls. Count caveman_retrieve tool calls. Each carries the original back and costs tokens.
  3. Extra turns. Compare iterations, retries and delegations across every agent. If the writer asks the analyst twice, that cost belongs in the total.
  4. Tokens. Compare provider-reported input tokens for the whole session, every agent included. Where AgentOps records the model call, it takes usage from the provider response, so it reflects what was actually sent.
  5. The deliverable. Judge both final outputs with the acceptance check you already trust. AgentOps shows how the runs differed; your check decides whether the result is still correct.

For a per-call decision, pass on_report=print to MiddlewareRuntime. applied or reused means a tool result was shortened. skipped comes with a reason, such as no_candidate when the call had no eligible tool result.

What if the agents run on different machines?

A handle resolves only against the runtime that stored the original. If the analyst and the writer run in separate containers and both may call caveman_retrieve, point them at one shared runtime. That needs a token, TLS and explicit consent to send content off the machine, all covered in the deployment docs. Until that's set up, wrap only agents that share a host with the runtime.

How do you roll back?

Build the agent from the original dict again: Agent(role=..., llm=LLM(...), tools=[read_logs]). with_caveman_agent returns new options and mutates nothing. To switch off without a code change, set CAVEMAN_CLIENT_MODE=off. AgentOps was never modified, so both tagged sessions stay in the dashboard for reference.

Keep the runtime running until in-flight crews finish, because a handle the model already holds needs the stored original. Grants last 24 hours from their last use.

Where to go next

Try it on work you know

Keep your stack. Add the adapter.

Wrap the client or framework you already call, run one agent task you can check, and compare the tokens sent.

Open the middleware docs Explore comparisons ↗