{"spec_version":"1.0","service":{"name":"CAP-Shield","description":"Compression, identity and memory for AI agents. Dictionaries trained per domain, per-tenant isolation, AES-256-GCM and a hash-chained audit log. Savings are measured in BYTES OVER THE WIRE — bandwidth and storage. Compressed packets are decompressed before a model sees them, so this does not reduce inference cost.","status":"beta"},"try_without_account":{"path":"/api/v1/try","method":"POST","body":{"texts":["your","message"],"dict_id":"finans"},"note":"Measure the saving on YOUR own traffic without creating an account. Nothing is stored: the text is compressed in memory, the numbers are computed, and everything is discarded. No dictionary is trained on what you send. The response includes the degraded share — how many of your messages came out LARGER.","limits":"200 messages, 512 kB, 20 measurements per hour per IP"},"authentication":{"type":"Bearer JWT","header":"Authorization: Bearer <token>","open_paths":["/health","/.well-known/cap-shield.json","/api/v1/try","/docs/quickstart","/docs/quickstart.md","/openapi.json","/docs"],"note":"All /api/v1/ paths require a token. Rejections carry a machine-readable resolution_protocol with the steps to fix it, and a request_id you can look up."},"api":{"quickstart":"https://cap-shield-robin.fly.dev/docs/quickstart","openapi":"https://cap-shield-robin.fly.dev/openapi.json","core_paths":{"pack":"/api/v1/pack","pack_batch":"/api/v1/pack-batch","pack_schema":"/api/v1/pack-schema","package_status":"/api/v1/catalog/status","validate_recipe":"/api/v1/catalog/validate","measure_your_own_traffic":"/api/v1/catalog/measure","usage":"/api/v1/metrics/summary","consent":"/api/v1/gdpr/consent","data_export":"/api/v1/gdpr/export"}},"languages":{"default":"en","available":["en","sv"],"how":"Append ?lang=sv to this document or /api/v1/catalog/status"},"performance":{"available":true,"methodology":{"hold_out":"80 % training, 19 % measured on unseen data","header_included":true,"zstd_level":19,"dictionary_size_bytes":"per delpaket, se ORDBOKSSTORLEK i cap_train_packages.py","dictionary_size_per_package":{"data/schemas":65536,"code/workflows":65536,"commerce/products":65536,"code/commits":16384,"support/tickets":16384,"finance/operations":16384,"itops/logs":16384},"dictionary_size_note":"Sized per package, measured. Large messages have more structure to capture and gain from a bigger dictionary; short ones get WORSE — the corpus simply has no more structure, so the dictionary fills with sequences that rarely hit.","note":"Seed corpora from open-source and synthetic sources. Production dictionaries mature on real customer traffic inside the customer's isolated store."},"methodology_plain":"Hold-out: the dictionary is trained on part of the corpus and measured on messages it has never seen. The packet header is included in every figure — this is what goes over the wire, not the compression ratio of the payload.","spread":{"individual_pct":{"lowest":2.9,"highest":81.4},"batched_pct":{"lowest":54.7,"highest":89.4},"what_determines_it":"Message size. The packet header is 5 bytes and authenticated encryption adds 25 more. On a 62-byte message that is half the packet; batched, it is spread across the whole batch. If your agent cannot batch, you get the lower number."},"packages":[{"id":"code/commits","status":"batched_only","rationale":"Only 2.9 % individually — the messages are too small for the header to pay off. Batched at 50 it reaches 54.7 %. Suits customers who can batch, not latency-sensitive single calls. NOTE: 56 of 200 individual messages (28 %) came out LARGER — the payload falls back to raw, but the header is still added. Batching removes that problem.","measured":{"individual_pct":2.9,"batched_pct":54.7,"batch_size":50,"median_size_bytes":69,"samples":1000,"measured_on_unseen":200,"degraded_messages":56,"degraded_share_pct":28.0,"dictionary_contribution_pp":34.5}},{"id":"code/workflows","status":"recommended","rationale":"Measured at 75.1 % individually on 74 unseen messages. Works without the customer having to batch.","measured":{"individual_pct":75.1,"batched_pct":82.0,"batch_size":50,"median_size_bytes":1855,"samples":366,"measured_on_unseen":74,"degraded_messages":0,"degraded_share_pct":0.0,"dictionary_contribution_pp":33.3}},{"id":"commerce/products","status":"recommended","rationale":"Measured at 70.8 % individually on 200 unseen messages. Works without the customer having to batch.","measured":{"individual_pct":70.8,"batched_pct":89.4,"batch_size":50,"median_size_bytes":1158,"samples":1000,"measured_on_unseen":200,"degraded_messages":4,"degraded_share_pct":2.0,"dictionary_contribution_pp":19.9}},{"id":"data/schemas","status":"recommended","rationale":"Measured at 81.4 % individually on 176 unseen messages. Works without the customer having to batch.","measured":{"individual_pct":81.4,"batched_pct":82.7,"batch_size":50,"median_size_bytes":3349,"samples":879,"measured_on_unseen":176,"degraded_messages":0,"degraded_share_pct":0.0,"dictionary_contribution_pp":25.5}},{"id":"finance/operations","status":"recommended","rationale":"Measured at 43.5 % individually on 186 unseen messages. Works without the customer having to batch.","measured":{"individual_pct":43.5,"batched_pct":88.1,"batch_size":50,"median_size_bytes":58,"samples":928,"measured_on_unseen":186,"degraded_messages":0,"degraded_share_pct":0.0,"dictionary_contribution_pp":64.2}},{"id":"itops/logs","status":"recommended","rationale":"Measured at 46.9 % individually on 400 unseen messages. Works without the customer having to batch.","measured":{"individual_pct":46.9,"batched_pct":77.7,"batch_size":50,"median_size_bytes":113,"samples":2000,"measured_on_unseen":400,"degraded_messages":28,"degraded_share_pct":7.0,"dictionary_contribution_pp":51.4}},{"id":"support/tickets","status":"recommended","rationale":"Measured at 58.9 % individually on 200 unseen messages. Works one at a time for most, though batching lifts both the average and the tail. NOTE: 21 of 200 individual messages (10 %) came out LARGER — the payload falls back to raw, but the header is still added. Batching removes that problem.","measured":{"individual_pct":58.9,"batched_pct":67.5,"batch_size":50,"median_size_bytes":193,"samples":1000,"measured_on_unseen":200,"degraded_messages":21,"degraded_share_pct":10.5,"dictionary_contribution_pp":38.1}}]},"open_disclosure":{"note":"This section exists so an agent can decide on complete information. We would rather list our weaknesses ourselves than let you find them after integration.","discouraged_packages":[],"preliminary_packages":[],"requires_batching":["code/commits"],"recommended_packages":["code/workflows","commerce/products","data/schemas","finance/operations","itops/logs","support/tickets"],"not_measured":["Real customer traffic. The figures above come from open-source corpora. They do not predict your outcome — use /api/v1/catalog/measure on your own traffic.","Token savings against a named tokenizer. The figures are bytes over the wire, not tokens.","The semantic track (Layer 4) is lossy and is not measured as compression. It is opt-in and marked as such."],"known_limitations":["Latency-sensitive single calls with short messages get substantially lower savings than batched traffic. See spread.","Packages marked 'preliminary' rest on too few measurement points to be priced on.","Authenticated encryption adds 25 bytes per packet. On messages below roughly 100 bytes that exceeds what compression saves — batching is the answer, not disabling encryption.","The shared dictionaries are trained on English-language traffic. Dictionaries learn byte sequences, so traffic in another language shares few of them and will measure lower. See roadmap.language_support."]},"roadmap":{"note":"Things we know are missing and intend to build. Listed so you can ask rather than assume.","language_support":{"status":"available on request","detail":"Dictionaries are language-specific by nature. A dictionary covering two languages is worse at both — the space is fixed, and every sequence in one language displaces one in the other. Separate dictionaries per language is the right answer, and we build them where there is traffic to train on. If your agents run in Swedish, German or any other language, get in touch and we will measure what a dedicated dictionary gives you before you commit to anything."},"wallet_owned_identity":{"status":"planned, not built","detail":"The goal is a pass that is portable and not locked to us. Nothing of it exists yet, and we would rather say so than let it sit between the lines."}},"memory_layer":{"active_on_this_instance":true,"if_inactive":null,"note":"Compression saves BYTES. This saves TOKENS — a different mechanism: retrieve the relevant memory entries instead of the whole history. Measured 2026-08-13 with cl100k_base on 400 real entries.","token_saving":{"from_compact_formatting_pct":39.2,"formatting_explained":"Measured separately and NOT included in the figures below. Context used to carry a '### memory:<id>' heading per entry; the id is internal and never needed by the model. Ids now live in the 'item_ids' response field — same traceability, zero token cost. Lossless.","at_1000_token_budget_pct":80.5,"at_200_token_budget_pct":95.1,"floor_pct":80.5,"floor_explained":"The saving no longer collapses at large budgets. Autocut selects 61 of 400 entries no matter how much room there is, because relevance runs out before space does. Previously the budget was filled to the brim — 346 entries at a 4000-token budget, and the saving fell to 9 %.","autocut":"Context assembly cuts where the score curve bends, not where the budget ends. Geometric elbow detection, no threshold to guess: the cut is relative to each query's own score distribution. Research 2026 found more context can make answers WORSE, not just costlier — exact match fell 3.4 points going from 3 to 10 retrieved blocks."},"retrieval_quality":{"note":"A 95 % saving is worthless if what was dropped is what the customer needed. Recall was measured separately: plant entries that provably answer a question, mix with noise, count how many the selection found.","exact_wording_match_pct":100,"paraphrase_match_pct":100,"baseline_random_pct":"10-22","baseline_lexical_paraphrase_pct":0,"why_vectors":"Lexical selection scored 0 % on paraphrases — worse than random. BM25 and LSA also scored 0 %; both rely on word overlap. Semantic vectors were required."},"method":"Hybrid: vectors for meaning, lexical matching for exact identifiers. A query for 'order 4471' must find that entry, and to a model 4471 looks much like 4472.","external_benchmark":{"dataset":"LongMemEval-S","what_was_measured":"retrieval","not_a_longmemeval_score":"The full benchmark measures whether an agent can ANSWER, with a language model in the loop and judge models voting. We measure whether retrieval finds the sessions containing the answer. These figures are NOT comparable to published LongMemEval scores from other vendors — they measure something else on the same data.","questions":500,"vector":{"recall_at_10_pct":93.8,"any_at_10_pct":97.2,"mrr":0.835,"search_latency_ms":19.7},"lexical_baseline":{"recall_at_10_pct":51.9,"any_at_10_pct":64.0,"mrr":0.389,"search_latency_ms":233.4},"how_to_read":"Recall@10 is the strict measure: a question counts as a hit only when ALL gold sessions were found. Finding half the answer means the agent cannot answer correctly. The gap to Any@10 means roughly one question in twenty-five gets partial context — a harder failure than missing entirely, because the agent then answers confidently on half a basis.","reproduce":"python cap_longmemeval.py --lage vektor"},"relation_to_other_levers":{"note":"The three token levers are complementary, not competing.","model_routing":"Decides WHICH model answers. Does not touch context.","prompt_caching":"Discounts STATIC text that repeats.","context_compaction":"This. Decides WHAT is sent at all.","integration_warning":"Prompt caching requires a stable prefix. Inserting different memory entries each turn changes the prefix and misses the cache. Place the selected memory AFTER the cached prefix and both survive."},"vectorisation_policy":"On demand. A tenant who only stores and reads back by id does not pay 384 bytes per entry for semantic search they never use. Activates after three searches; backfill fills in older entries.","availability":"Included in all paid plans. Not in the free /api/v1/try — 4.8 ms per anonymous request is compute we would pay for without return.","not_shown_by_this":["The test case is constructed: eight clearly related entries in unrelated noise. Real relevance is gradual, so 100 % is a ceiling, not an expectation.","Measured on 400 entries. A 50 000-entry memory needs a vector index; today's code vectorises the whole memory on every search and does not scale.","The saving assumes the budget is tighter than the memory. A small memory with a large context budget saves nothing."]},"pricing":{"model":"Monthly fee plus a share of MEASURED savings","meter":"/api/v1/metrics/summary — the same counters the invoice uses","note":"The variable share is computed on savings actually measured in your traffic, not on the figures above. If the saving is zero, the variable part is zero.","quota":"Hard stops at the quota limit (HTTP 429). No silent overage — the service stops responding rather than sending an unexpected invoice."},"data_protection":{"isolation":"Per tenant: dictionaries, memory and catalogue in separate stores","encryption":"AES-256-GCM with key rotation","audit_log":"Hash-chained, verifiable","gdpr":{"consent":"Never pre-selected. The parts are independent.","data_export":"/api/v1/gdpr/export","erasure":"/api/v1/gdpr/erase — consequences are shown before confirmation"}}}