Pheromone Store
Share time-sensitive observations between agents and organizations, then rank work by their decayed, receiver-weighted strength.
On this page
Agents can tell each other what they have seen. One agent deposits a signed signal, such as a suspected prompt injection in a support workflow, and agents behind other kernels rank their next work by how strong that signal still is.
Each receiving kernel decides which deposits it admits, how quickly they fade, and how far it trusts their origin. Signals rank work and grant nothing: acting on one still takes a grant the kernel admits. The running example is the support-workflow fixture, a support.prompt_injection observation in the dev.chio.support namespace.
A signal fades on a clock the deposit carries
Every deposit names a starting confidence and its own half-life. Its strength halves with each half-life until it falls below its evaporation floor and leaves the sum.
At hour 5.5, the weighted concentration is 0.106. 3 of 3 deposits count.
In Rust · substrate.rs:21312 lines
let strength = strength_at(deposit, now_unix_ms);
if let Some(floor) = deposit.body.evaporation_floor {
if strength < floor {
continue;
}
}
let weight = peer_weight(&deposit.body.kernel_id, reputation_epoch);
// … WeightOutOfRange unless 0.0 <= weight <= 1.0
let discount = newcomer_discount_for_deposit(
// … deposit, context, epoch, subject class
)?;
total_strength += strength * weight * discount;
The receiver scales each remaining deposit by two factors it owns: the origin kernel’s peer weight and a newcomer discount for young passports. The sum, the concentration, changes with evaluation time even when no new deposit arrives. The mechanism and its CLI keep the name pheromone (chio pheromone, the chio-pheromone crate); the public name is signals.
The agent signs the observation, and a verifier can sign its cost
A deposit is an agent-signed observation carrying identity, treaty scope, confidence, time, and optional workflow evidence. Gossip carries deposits to a recipient in batches; the receiver checks the envelope and each frame on its own.
σsignaturewworkflowccostnnonceBcapacity
Two signers sign two different things
The agent’s passport key signs the canonical body with cost_commitment removed, so workflow_context is covered. A cost verifier signs a separate statement that binds depositBodySha256 and depositSignatureSha256 to its policy and telemetry evidence. Deposit fields.
The agent’s passport key signs canonical JSON of the deposit body with cost_commitment removed, so workflow_context stays covered and a changed workflow reference breaks the signature. A cost statement carries its own signature. It binds depositBodySha256, the hash of that same body projection, and depositSignatureSha256, the hash of the signature’s hex text as bytes.
Where policy requires observation-cost evidence, the receiver checks the statement’s deposit and policy bindings, cost unit, and observation window. It resolves an authorized verifier root, checks the trust-floor state, verifies the statement signature, and rebuilds the telemetry leaf for its RFC 6962 inclusion proof against the signed telemetry root. That authenticates a scoped verifier statement; the receiver still judges the verifier and its measurement policy.
Read more in Deposit fields, which lists every signed field and the retained fixture.
The receiver admits each frame on its own, in one transaction
A gossip batch addresses one or more deposit frames to a recipient under a treaty. The receive path runs four boundaries in order:
- Batch and frame. Recipient, treaty, and origin are checked against the authenticated sender and the transit policy. Direct-origin frames and hub transit verify under different context, so a sender cannot relabel an origin.
- Deposit evidence. Schema, fields, time, subject policy, the resolved passport, and the agent signature, then workflow references and any required cost evidence.
- Admission state. The nonce, scoped capacity, per-pair count, and distinct-passport limit. The SQLite store commits these with the passport and the deposit in one transaction.
- Batch result. Each frame runs under its own savepoint, so a refused frame rolls back while valid siblings commit. The report carries per-frame verdicts and
batch_outcome.
The report’s accepted flag is true only when the whole batch is accepted. A partial batch reports accepted=false and batch_outcome=partial with some deposits stored, and the CLI writes that report before it exits nonzero, so read the frame verdicts before assuming nothing was admitted.
The outbound gossip queue is bounded FIFO per peer and treaty. It keeps separate deposits, never replacing an older observation with a newer one, and drops the oldest frame when full. A queued frame is not stored until the receiver admits it. A custom store calls validate_deposit_for_admission for the stateless checks, then runs the stateful admission that consumes the nonce and capacity counters.
Read more in Relay Operations, which covers durable forwarding and recovery.
Three counters bound how much one origin can deposit
The receiver applies three overlapping limits, all counters inside a scoped window. Successful admission consumes capacity; a signature check alone consumes none, and the counters do not refill continuously.
| Limit | Scope | Refusal |
|---|---|---|
Shared capacity, tokenCapacity | Epoch, window, treaty, namespace, and class, across every origin | rate_limit_exhausted |
Per pair, maxDepositsPerPair | The same scope plus origin kernel and passport key | diversity_cap_exceeded |
| Passports per kernel | Active-peers epoch, window, treaty, namespace, class, origin kernel | sqrt_n_passport_cap_exceeded |
One kernel can use at most ceil(sqrt(max(activePeers, 1))) distinct passport keys in that scope. With 64 active peers that is 8, and a ninth passport is refused; 16 peers allow 4 and 256 allow 16, so a population four times larger doubles the cap. The cap limits passport proliferation per admitted kernel; who counts as a peer is the receiver’s admission decision.
The limits come from a PheromoneScarcityPolicy: hash-bound, windowed material inside the runtime policy document. The loader verifies the enclosing signed envelope and requires its signer in both the externally trusted issuer roots and the admission policy’s issuer roots. Admission resolves exactly one active policy for the scope; a missing, invalid, stale, or ambiguous policy refuses the deposit.
Read more in Reputation & Scarcity, which models the capacity these counters allow.
The fixture’s query keeps 0.5625 of its decayed strength
The public fixture queries support.prompt_injection half a second after the observation, at reputation epoch 42, with a peer weight of 0.75 for the origin kernel. From the three-vendor example directory (chio new three-vendor --example three-vendor), run against a store that admitted the fixture deposit, the query writes this report:
chio pheromone query \
--store ./pheromone.db \
--subject-class support.prompt_injection \
--namespace dev.chio.support \
--reputation-epoch 42 \
--peer-weights \
./fixtures/pheromone/peer-weights.json \
--now-unix-ms 1766000000500 \
--report ./concentration-report.json{
"schema": "chio.pheromone.query-report.v1",
"accepted": true,
"concentration": {
"schema": "chio.pheromone-concentration.v1",
"subject_class": "support.prompt_injection",
"subject_class_namespace": "dev.chio.support",
"total_strength": 0.4612055973961088,
"unweighted_total_strength": 0.8199210620375268,
"distinct_origin_pairs": 1,
"peak_confidence": 0.82,
"reputation_epoch": 42,
"evaluated_at_unix_ms": 1766000000500,
"treaty_scopes": [
"treaty:buyer-dataco:support-ops",
"treaty:buyer-llamaworks:support-ops"
]
}
}You should see the raw decayed strength, 0.82 halved over 0.5 of 3,600 seconds, as unweighted_total_strength. The passport was first seen in epoch 37, so its age at epoch 42 is 6 epochs and its newcomer factor 6/8 = 0.75. Peer weight times newcomer factor is 0.5625, which brings the contribution to total_strength 0.4612055973961088.
Newcomer age is max(epoch − firstSeenEpoch, 0) + 1. With the default horizon of 8, a first-epoch passport counts one eighth and reaches full weight after eight epochs; a passport missing at query time also takes the first-epoch discount. The standard weights provider requires the weights file’s epoch to match and gives an unlisted kernel weight 1.0, so list explicit zero weights where your policy needs them.
The retained report is fixtures/pheromone/query-report.json in the three-vendor example. Read more in the 3-Vendor Walkthrough, which supplies the surrounding workflow.
The example project refuses a replayed nonce and a kernel-signed deposit
Create separate passport and kernel keys, supply receiver-owned passport admission and scarcity policy, and deposit a signed observation. Query it at an explicit reputation epoch with a peer weight of 0.8. Download the project and follow the Rust example setup.
cargo run --locked --features pheromone --bin pheromoneconcentration: {"schema":"chio.pheromone-concentration.v1","subject_class":"support.prompt_injection","subject_class_namespace":"dev.chio.support","total_strength":0.49195263722251614,"unweighted_total_strength":0.8199210620375268,"distinct_origin_pairs":1,"peak_confidence":0.82,"reputation_epoch":42,"evaluated_at_unix_ms":1789230068161,"treaty_scopes":["treaty:buyer-llamaworks:support-ops"]}
replayed observation: replay_window_exceeded: nonce-001
kernel-signed observation: signature_invalid: deposit signature does not verifyYou should see a concentration whose total_strength is 0.6 of the decayed confidence, the 0.8 peer weight times a 0.75 newcomer discount, then two refusals: the repeated nonce fails with replay_window_exceeded, and the deposit signed with the kernel key instead of the admitted passport fails with signature_invalid. The store is local and in memory; a shared reputation service is a separate component.
Example source: src/bin/pheromone.rs
use chio_core_types::Keypair;
use chio_pheromone::*;
use observation::{body, live_context};
use swarm_authority_examples::now_ms;
use swarm_authority_examples::observation;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let passport = Keypair::generate();
let kernel = Keypair::generate();
let context = live_context(&passport, &kernel);
let store = InMemoryPheromoneSubstrate::new();
let deposit = sign_deposit(body(&passport), &passport)?;
store.deposit(deposit.clone(), &context)?;
let concentration = store.query_concentration(
"support.prompt_injection",
"dev.chio.support",
now_ms(),
42,
&context,
&|_peer, _epoch| 0.8,
)?;
println!("concentration: {}", serde_json::to_string(&concentration)?);
println!(
"replayed observation: {}",
store
.deposit(deposit, &context)
.expect_err("nonce cannot repeat")
);
let mut new_body = body(&passport);
new_body.nonce = "nonce-002".into();
let wrong_signer = sign_deposit(new_body, &kernel)?;
println!(
"kernel-signed observation: {}",
store
.deposit(wrong_signer, &context)
.expect_err("passport must sign")
);
Ok(())
}Reconstructing a result takes its inputs, beyond the epoch
A concentration identifies its inputs by reputation_epoch and evaluated_at_unix_ms. Rebuilding it takes the same retained deposits, evaluation time, peer weights, passport first-seen data, and newcomer policy. The query checks the context it is given; it fetches no anchored reputation snapshot and does not recheck stored passports against a live revocation service.
The standalone chio pheromone query supplies no scarcity policies, so it uses the default horizon of 8 even where admission used a custom one. The receiver API can query with explicit policy context. SQLite fills in stored passport records beside the caller’s context.
An explicit evaporation floor excludes deposits below that raw strength. The in-memory garbage collector uses 0.01 when the field is absent, while the query applies no fallback floor, so collection changes the corpus. Retain the records you need before reconstructing a past result; results are reproducible from retained inputs, with summation order able to move the last bits.
Deposit fields, refusal codes, and commands
Try workflow_context, treaty_scopes, or fresh passport.
Deposit fields
The wire schema is chio.pheromone-deposit.v1. The serialized deposit flattens its body fields beside signature.
kernel_id, agent_passport_key_hash, agent_passport_jwk_thumbprint
Origin kernel and agent signing-key references. Admission resolves an allowed passport in the receiver context, checks its validity and revocation flag, and refuses a configured kernel key used as the depositor key.
subject_class_namespace, subject_class, indicator
The domain and exact class reported, plus an application-defined indicator. Concentration matches class and namespace exactly, with no automatic class hierarchy.
confidence, severity
Confidence is the starting strength. Severity is descriptive metadata and stays out of the concentration formula.
timestamp_unix_ms, decay_half_life_secs, evaporation_floor
Observation time, a positive per-deposit half-life, and an optional strength cutoff, validated against the receiver’s time policy before admission.
nonce, treaty_scope
Replay identity and allowed treaty scopes. The reference admission path requires an accepted treaty; an empty list admits no local-only deposit.
workflow_context
Signed references to the workflow, receipt, intersection, step, tool receipt, bilateral envelope digest, and consistency anchor. The runtime resolver checks them against verified workflow evidence.
cost_commitment
A separately signed observation-cost statement. Subject-class and scarcity policy decide whether it is required and verified.
The retained fixture below is the observation the query report reads. Body and workflow fields use snake_case; the cost statement uses camelCase, while its nested inclusionProof keeps tree_size, leaf_index, and audit_path.
{
"schema": "chio.pheromone-deposit.v1",
"kernel_id": "did:chio:llamaworks",
"agent_passport_key_hash": "1f5fe64ec2f8c901be3aad94a41d70c12d2f3a8f43755f902e665c96f41c87e3",
"agent_passport_jwk_thumbprint": "lWkhqaoVMPD-k3r62OPO7M8ak2U9g1rMbZFNL9STZ50",
"subject_class": "support.prompt_injection",
"subject_class_namespace": "dev.chio.support",
"indicator": {
"indicatorDigest": "ec8cf4e0ba62aa1d2a7609e2653fc88aa1df035bbbab9b9c82781053fc27bf24",
"kind": "prompt_injection",
"workflowId": "wf-chio-refund-001"
},
"severity": "high",
"confidence": 0.82,
"timestamp_unix_ms": 1766000000000,
"decay_half_life_secs": 3600.0,
"evaporation_floor": 0.01,
"nonce": "pheromone-nonce-llamaworks-001",
"treaty_scope": [
"treaty:buyer-llamaworks:support-ops",
"treaty:buyer-dataco:support-ops"
],
"cost_commitment": {
"schema": "chio.pheromone-cost-commitment.v1",
"statement": {
"schema": "chio.pheromone-observation-cost-statement.v1",
"commitmentId": "cost-commitment-llamaworks-001",
"verifierId": "did:chio:dataco-cost-verifier",
"verifierKeyId": "cost-root-key-1",
"runtimePolicySha256": "b0cfe65c6f966a7e7b3abbc3490bcc9a2c2546e46df757aa15ab6473096f6955",
"scarcityPolicySha256": "69073f134018c3cc27f53726aa2a98a63bdfedf6bd68bfde7bb2b3777f3050d4",
"depositBodySha256": "acd0286d4d079536cd32c91c0b48f214195ae0cb1e8e6ff62662ddc95e81ce9f",
"depositSignatureSha256": "a69c9d048e8bd66cf6bdccbb3ad0f0e086d29597cfe4ead175881831c3ac4f1c",
"kernelId": "did:chio:llamaworks",
"agentPassportKeyHash": "1f5fe64ec2f8c901be3aad94a41d70c12d2f3a8f43755f902e665c96f41c87e3",
"treatyId": "treaty:buyer-dataco:support-ops",
"subjectClassNamespace": "dev.chio.support",
"subjectClass": "support.prompt_injection",
"observationWindowStartUnixMs": 1765999940000,
"observationWindowEndUnixMs": 1766000060000,
"observedAtUnixMs": 1766000000000,
"eventDigestSha256": "b012fd9cf705d6123879782db8db9fc964ffd0ac0ce1ae3873c562f0120739bd",
"cost": {
"unit": "chio.observation.microunit.v1",
"amount": 125
},
"telemetry": {
"schema": "chio.pheromone-observation-cost-telemetry-root.v1",
"algorithm": "rfc6962-sha256-v1",
"rootHash": "0x03d57ee94d7c00e7980572b34426973abe83bd7e7c38a3c230f187b3d4cad693",
"treeSize": 1,
"verifierId": "did:chio:dataco-cost-verifier",
"verifierKeyId": "cost-root-key-1",
"closedAtUnixMs": 1766000000250
},
"inclusionProof": {
"tree_size": 1,
"leaf_index": 0,
"audit_path": []
},
"leafPreimageSha256": "87e975957c8a68e5ef9fad681bfd5b9635eec4f3c261186808a9b1ece26d9576"
},
"signature": "a18ba6cbaeb3689c098f980bcbc7fdf215777155644ec4b79b679ead7f221a674255b49e07a15e0244d54fe82a593963066be36d676757cb331d35c0e3e9090e"
},
"workflow_context": {
"schema": "chio.pheromone-workflow-context.v1",
"workflow_id": "wf-chio-refund-001",
"workflow_receipt_id": "wf-chio-refund-001",
"workflow_receipt_sha256": "7fffe9c72b7113a65d0abadf73f98099e92e31ee5a5595a304199608a2ae0a10",
"workflow_intersection_id": "workflow-intersection:buyer-refund:001",
"workflow_intersection_sha256": "c852513d4ebcee42d0d5d020eb9bf3be40b85c7dec78e4c2578898ecd89892dd",
"step_index": 0,
"tool_receipt_id": "ea1f74990eaef54beaac9d7af04893a2320390b773b556fbd310fa9246ed3b3d",
"bilateral_dsse_sha256": "3e7ac510ae43d4668966534d81c7592d2c7bb62d10e7938e1bb7c9e477c49077",
"consistency_anchor": "chio:consistency:wf-chio-refund-001:0"
},
"signature": "5a6f94c43773d1f97174d5aa65a32ac9fe16e9900593617a30ecfb33197db54d6a1eed72ec8fcf142ce404c48e8c173eb9f7590b622c0b916fd6ff20b5a60d0d"
}Concentration recipe and result fields
For each matching deposit above its evaporation floor, the query multiplies decayed confidence by the origin’s peer weight and the passport’s newcomer discount.
raw = confidence * 2^(-age_s / half_life_s)
contribution = raw * peer * newcomer
total_strength = sum(contribution)
unweighted_total_strength = sum(raw)total_strength
Sum after decay, peer weighting, and newcomer discount. An aggregate strength, unbounded by [0, 1].
unweighted_total_strength
Sum of decayed confidence before peer weighting and newcomer discount.
distinct_origin_pairs
Distinct kernel and passport-key pairs among the non-evaporated deposits considered. A zero peer weight keeps a pair in this count.
peak_confidence
The largest original confidence among those deposits, before decay or weighting.
reputation_epoch, evaluated_at_unix_ms
The requested weighting epoch and evaluation time. They name inputs; the corpus and trust context are retained separately.
treaty_scopes
Sorted union of the included deposits’ treaty scopes. It describes the deposits, and makes no claim that the query was authorized under each treaty.
Scarcity policy fields
The policy’s camelCase wire fields include tokenCapacity, newcomerHorizonEpochs, reputationEpoch, activePeersEpoch, treatyScope, subjectClassNamespace, subjectClass, and the window bounds. Policy and window hashes bind the configured material.
Refusal codes
| Code | Inspect next |
|---|---|
signature_ | The agent signing key, the canonical body projection, and post-signing changes. workflow_ |
kernel_ | Sign with the admitted agent passport key, never a configured kernel key. |
replay_ | Elapsed age and duplicate nonce state. A reused origin, passport, and nonce is refused; the exact window endpoint counts as expired. |
deposit_ | timestamp_ |
treaty_ | The deposit’s treaty list and the receiver’s allowed subject-class scope. |
rate_ | The shared epoch, window, treaty, and class capacity is spent. A fresh passport does not create a new shared bucket. |
diversity_ | The origin kernel and passport pair reached its scoped deposit limit. |
sqrt_ | The origin kernel reached its distinct- |
scarcity_ | Resolve one valid active scarcity window for the receiver’s epoch, treaty, namespace, and class. |
observation_ | Deposit hashes, origin, passport, treaty, subject, and verifier against the cost statement and policy. |
observation_ | Rebuild the bound leaf and verify the audit path against the signed telemetry root. |
unknown_ | Query context, the weights-file epoch, and finite peer weights in [0, 1]. These are query failures, distinct from admission. |
Receive and query flags
chio pheromone receive requires --batch, --transit-policy, --proof-package, --trust-bundle, --context, --store, and --report, with the verified workflow material that belongs to the batch. Its evaluation time defaults to the batch’s flushed_at_unix_ms; pass --now-unix-ms to evaluate at the receiver’s current time. chio pheromone query defaults to wall-clock time.
An empty store returns a successful zero concentration with accepted=true. Compare the origin count, raw strength, and frame results before reading a zero as no signal.
Crates
| Crate | Responsibility |
|---|---|
chio-pheromone | Deposit types, signing projection, validation, scarcity rules, and the Pheromone |
chio-federation | Scoped gossip frames, bounded push queues, and transit verification. |
chio- | Verified workflow resolution, receiver orchestration, SQLite admission state, concentration queries, and reports. |
chio- | Durable forwarding, catch-up, and operator lifecycle. |