Measure and reduce what an agent sends into its context. Use when an agent's context is large, when retrieval quality matters, when the user asks about token cost or context bloat, or when agent answers are wrong in ways that look like missing information rather than reasoning failure. Also use to measure compression on structured machine-to-machine traffic before sending it over the wire.
Resources
6Install
npx skillscat add robinlidberg-dot/cap-shield-mcp Install via the SkillsCat registry.
CAP-Shield
Selects what goes into an agent's context, and compresses what goes over
the wire. The difference from other context tools is that the recall is
measured, not asserted.
When this matters
Reach for this when any of these are true:
- An agent gets worse as the conversation grows, and the failures look
like missing information rather than bad reasoning - Context is assembled by "include everything relevant, just in case"
- Someone asks what a large context is costing
- Structured machine-to-machine traffic is being sent uncompressed
The counter-intuitive part: more context makes agents worse. ETH
Zurich found context files LOWER task success versus giving the agent no
repository context at all, while raising inference cost by over 20 %.
Around two thirds of production agent failures trace to context problems,
not to the model being incapable.
So cutting context is not only a cost optimisation. It is a correctness
fix, and that is the reason to do it.
Do not use this for
- Short conversations that fit comfortably in the budget. Below roughly
2 000 tokens there is nothing to leave out, and the saving would be
zero for reasons that have nothing to do with how well selection works. - Free prose where every sentence is load-bearing.
- Compressing single short messages. Below roughly 100 bytes, the packet
header and authenticated encryption cost more than compression saves.
Batch instead.
Say so plainly when one of these applies rather than reporting a small
number as if it were a result.
Measure first, always
Never state what CAP-Shield will save. Measure it on the actual traffic
and report what comes back — including the parts that look bad.
pip install cap-shieldfrom cap_shield import measure, print_measurement
print_measurement(measure(
texts=[...the actual messages...],
query="what the agent would search for",
))No account, no key, nothing stored. The text is compressed in memory, the
numbers are computed, and everything is discarded.
Quotas: 200 messages, 512 kB, 20 measurements per hour per IP.
Reading the result
The response has four parts, and three of them can be bad news:
individual.saving_pct— one message at a time. Can be NEGATIVE.individual.degraded— how many messages came out LARGER. Report this
number every time, including when it is zero. If it is high AND
batching is possible, batch — do not report the weak number and stop
there; the batched figure is what the agent should act on.batched.saving_pct— the same messages packed together.token_saving— a different mechanism entirely.measured: false
means there was not enough content, not that it failed.
A real result: 30 short JSON messages gave −10.5 % individually with
all 30 degraded, and 87.9 % batched. Reporting only the second number
would be dishonest; reporting only the first would be useless. Report
both, weak number first.
Cumulative
python -m cap_gainShows what has been saved across all measurements, with the degraded
count. Collection is silent and local; nothing is sent anywhere.CAP_SHIELD_INGEN_RAKNARE=1 turns it off.
The MCP server
pip install cap-shield{
"mcpServers": {
"cap-shield": {
"command": "cap-shield-mcp",
"env": {
"CAP_SHIELD_API_KEY": "cap_live_..."
}
}
}
}The env block is only needed for the tools that require a key
(remember, assemble_context, pack_batch, unpack_batch, issue_pass).
Or take the server as a single file, if a package is unwelcome:
curl -O https://cap-shield-robin.fly.dev/cap_mcp.pyThe server itself imports nothing outside the standard library.
Eight tools:
| Tool | Purpose | Account |
|---|---|---|
measure_traffic |
Measure saving on your own traffic | No |
list_packages |
Domain packages with measured figures | No |
remember |
Store a memory entry | Yes |
assemble_context |
Retrieve the relevant entries, not the whole history | Yes |
pack_batch |
Pack several messages into one compressed packet | Yes |
unpack_batch |
Unpack a packet made by pack_batch back into the original messages |
Yes |
get_account |
Obtain a key | No |
issue_pass |
Issue a short-lived signed credential a third party can verify independently | Yes |
assemble_context calls no model. It selects and returns; the caller
decides what to do with the result.
pack_batch/unpack_batch are the same POST /api/v1/pack-batch
mechanism described under "Batching has a security condition" below —
reachable here as proper MCP tools, not only over REST. The same
trust-boundary rule applies: only batch messages that already share one
(same tenant/agent, nothing attacker-influenced mixed in with a secret).
issue_pass is the same POST /api/v1/pass endpoint described under
"Proving identity to someone besides us" below — reachable here as a
proper MCP tool, not only over REST.
Choosing a package
A package is a dictionary trained on a corpus, not a name. Seven exist.
Do not guess which fits — list_packages returns the measured figures,
and measure_traffic settles it against the actual traffic.
Two things that decide the outcome more than package choice:
Message size. The packet header is 5 bytes and authenticated
encryption adds 25. On a 62-byte message that is half the packet.
Batched, it is spread across the whole batch.
Whether batching is possible. Some packages only work batched, and
that is stated in the catalogue rather than hidden. If the agent cannot
batch, use the individual figure and say so.
Batching has a security condition
Batching compresses several messages in the same context, which opens a
CRIME/BREACH-style side channel: someone who can place chosen text in the
same batch as a secret, and observe the batch size, learns something
about the secret.
Only batch messages that already share a trust boundary. One tenant's
own traffic is safe. Traffic where a second party controls part of the
content is not.
Optional padding (padding=PADDING_STANDARD) rounds to a 64-byte step
and closes it, for under two bytes a message. It is off by default —
say so rather than let anyone assume it is on. If you batch across a
trust boundary, turning padding on is not optional — it is the
difference between the name Shield meaning something here and not.
Individual packing does not have this problem at all.
Reporting results
Follow the same rules the service follows about itself:
- Never state a figure without saying what it was measured on. The
published numbers come from open corpora and do not predict anyone
else's outcome. - Always report the degraded count, including zero. A field that
appears only with bad news teaches the reader that its absence means
something. - Keep bytes and tokens apart. Compression saves bytes over the wire.
Selection saves tokens in the context. They are different mechanisms
and adding them together produces a number that means nothing. - Compressed packets are decompressed before a model sees them.
Compression does not reduce inference cost. Saying otherwise is the
single easiest way to be wrong about this product. - Say when something has not been measured.
measured: falseis an
answer, not a failure.
Memory is versioned, and updates are stored as deltas
An entry can be updated without storing it again in full. Every fifth
version is complete; the ones between are stored as a delta against the
previous version. A SHA-256 of the original is kept per version and
checked on read, so the chain is lossless, and no version depends on
more than four others.
This matters because agent memory is mostly small edits to text that
already exists. Storing each edit in full is what makes memory expensive
to keep.
Each entry also picks its own compression strategy by size — and if
compression does not pay off, the entry is stored RAW and the response
says so. Same rule as the degraded share on the wire: report it rather
than hide it.
remember takes an optional ttl_days. After that many days,assemble_context stops selecting the entry — it is not deleted, only
no longer chosen, the same distinction the archive guarantee draws
between "never deleted" and "actively selected". Omit it and an entry
is eligible forever, as before this field existed. Use it for anything
whose truth decays with time (a status update, a temporary instruction,
an in-progress fact that will be superseded) rather than letting it
compete with current information indefinitely.
Multi-agent setups: one namespace per role
remember and assemble_context already take a namespace argument.
For a manager agent coordinating sub-agents, giving each role its own
namespace (coder, finance, support, ...) is the isolation a
multi-agent handoff needs, with no new tool: the manager writes into the
namespace that belongs to the recipient, and each sub-agent only ever
calls assemble_context against its own. This is the same isolation
mechanism already used between tenants, one level down.
What the response tells you — use it
A saving you cannot audit is a saving the customer has to take on trust.
The response says more than the total, and the extra fields are what make
the number checkable:
methodon every hit — vector or lexical, so you can see WHICH
mechanism found that entrybaseline_tokens— what the whole history would have cost, in the same
format. Without it the saving cannot be computed, only claimedcandidates_before_autocutandautocut_removed— what relevance
scoring droppeddeduplicated— near-identical entries collapsed rather than sent twiceskipped_too_biganditem_ids
When reporting a saving, quote baseline_tokens alongside it. A
percentage without its baseline is not a measurement.
Dictionaries improve on the customer's own traffic
The shared dictionaries are trained on open corpora. A customer's own
dictionary is trained on their traffic, in their own isolated store, and
is never shared or merged into the curated ones.
The part worth knowing: a new version is adopted only if it measures
better on held-out data that neither version was trained on. A
retraining that does not win is rejected, the old dictionary stays, and
the rejection is logged with both figures.
That gate matters because a dictionary trained on too little or too
skewed traffic measures WORSE than none — the payload falls back to raw
and the header is still added.
Old versions are never deleted, so packets compressed under any earlier
version still unpack.
Retraining is manual by default (POST /api/v1/catalog/mature).
Automatic retraining exists but is off unless the customer has enabled
both training and auto_mature, and it consumes quota.
Do not promise a figure for this. What maturity gains depends on the
customer's traffic. The response reports old_saving_pct,new_saving_pct and improvement_pp — quote those, not an estimate.
Portability
Dictionary versions are never deleted, and the guarantee does not rest
on CAP-Shield still existing: the archive export carries the dictionary
binaries, and cap_unpack.py runs standalone — no gateway, no network,
no account. It is served without a token, because whoever needs it most
is whoever no longer has one.
What does NOT come with it: the memory store, vector selection, autocut,
package maturity, or the measurement apparatus. Those are the service,
not a file you export once and stop paying for.
Proving identity to someone besides us
issue_pass (or POST /api/v1/pass over REST) issues a short-lived
(15 minute) signed credential that a THIRD PARTY can verify
independently against /.well-known/jwks.json — no CAP-Shield account
needed on their end. Same Bearer token as every other endpoint here, no
new onboarding.
{"acts_for": "who the agent is acting for", "audience": "optional", "scope": {"can_read": "invoice_json", "max_budget_usd": 500}}It does not validate acts_for or scope against reality. It
attests only that the holder of the key claimed it, at that moment, with
a record in the hash-chained audit log. Report that plainly if
describing it — claiming more would be exactly the kind of unmeasured
assertion this document argues against everywhere else. scope is
optional and freeform; omit it and the pass looks exactly as it did
before the field existed.
Request the pass right before the hand-off, not at the start of a
workflow that includes a wait. A pass issued before a human-approval
gate can expire before it is ever shown to the recipient — there is no
refresh, only requesting a new one, which works at any time with the
same key. Fifteen minutes is short by design; treat it as "ask right
when you need it," not "ask ahead, just in case."
No blockchain, no NFT, no wallet, no revocation list — a pass without
one must be short-lived, since a revocation list is one more service
that would need to stay up for the pass to be checkable at all.
Everything else
https://cap-shield-robin.fly.dev/.well-known/cap-shield.json carries
the live figures, the known limitations, what has NOT been measured, and
the roadmap of what is planned but not built. Fetch it rather than
relying on anything written here — this file goes stale, that document
does not.