Delegation Hygiene
See which restrictions a delegator added to the authority it handed out, and how that record feeds issuance and passport decisions.
On this page
Delegation hygiene scores how much authority a coordinator kept back when it handed out work. For every child capability it issued, the score records whether scope narrowed, expiry moved earlier, and a cap tightened.
The score belongs to the delegator, the issuer of each child, and its three rates show which restriction it added. It measures recorded lineage. The attenuation checks in Delegation & Attenuation are what refuse an invalid child.
Two edges from one delegator average to one score
The public reputation fixture gives one delegator two parent and child pairs. One child copies its parent; the other narrows it on every count.
adelegatorκheld parenta′issued childsignals: scope, expiry, budget
Child A keeps everything its parent had
parent-a and child-a both permit shell/exec with an invocation cap of 100 and the same expiry. Scope, expiry, and budget did not narrow, so the edge scores 0, 0, 0. Delegation Hygiene.
child-a keeps parent-a’s shell/exec permissions, its cap of 100 invocations, and its expiry, so its signals are 0, 0, 0. child-b takes parent-b’s net/post, removes delegation, adds a domain constraint, cuts invocations from 50 to 5, and expires earlier, so its signals are 1, 1, 1.
With the fixture’s issuance times and the default 30-day half-life, the edges weigh about 0.957398 and 0.959962. Each rate, and the mean of the three, comes to about 0.500669: the narrowing edge is slightly newer, so it weighs slightly more.
Each rate is a time-weighted share of narrowing edges
For every scored edge the weight halves every 30 days after the child’s issuance. Each rate divides the weighted positive signals by the sum of all scored-edge weights, and the score is the mean of the three rates, clamped to the unit interval.
Every rate and the score is a MetricValue: {"state":"known","value":0.5} or {"state":"unknown"}. Unknown means no edge could be scored. Known zero means edges were scored and none earned weighted credit, a different result that a policy reads differently.
Read more in the weighting recipe, with the half-life and edge cases.
The score sees children the subject issued whose parent resolves
compute_local_scorecard selects lineage records whose issuer_key is the subject and that carry a parent_capability_id. compute_delegation_hygiene then resolves that parent id against the corpus.
- Issued by the subject, parent resolves: one observation, and all three predicates run.
- A root with no parent id: outside the candidate set.
- A parent record absent from the corpus: skipped before any rate or count changes.
- No children, or every parent absent: unknown score and rates, with
delegations_observedat 0.
A received capability is not an issued delegation, so a grandchild issued by another agent counts toward that intermediate agent. The local corpus builder combines snapshots the subject holds with snapshots it issued, so a parent the delegator holds resolves beside its children. The kernel derives a snapshot’s parent id from the last delegation-chain link when it records an observed capability, and writes no snapshot when no receipt store is configured.
To see the edges a delegator will be scored on, walk forward one level from a grant it holds. The fixer from Delegate Between Agents issued one child:
chio lineage query --live --seed cap-01d2…2a90 --direction forward --depth-limit 1walk forward from cap-01d2…2a90, depth limit 1 depth 0 cap-01d2…2a90 capability fixer depth 1 cap-01d2…6c13 capability tester delegated from cap-01d2…2a90 2 nodes, 1 edge, walk stopped at depth 1
That edge removed write, cut thirty minutes to ten, and lowered the cost ceiling from USD 2.00 to USD 0.50, so it scores 1, 1, 1.
Each predicate records that a restriction happened, whatever its size
Each edge answers three yes-or-no questions. Removing one operation and removing several earn the same credit.
- Scope. Any child tool grant without a compatible parent grant vetoes scope credit for the edge. Otherwise the edge earns credit for fewer tool, resource, or prompt grants, or for a matching tool grant that drops operations, adds or changes constraints, or tightens a cap.
- Expiry. The child’s absolute
expires_atis strictly earlier than the parent’s. An inherited deadline earns zero even when the child was issued later, and this predicate runs whether or not tool grants match. - Budget. At least one matching tool grant introduces or reduces
max_invocations,max_cost_per_invocation, ormax_total_cost, with monetary caps in the same currency. Any match earns it, with no edge-wide veto.
A separate expansion fixture shows why a middling score needs its rates read. A parent holds fs/read_file and its child holds fs/write_file with an earlier expiry and an invocation cap of 5. The tool cannot match, so the source asserts scope 0, expiry 1, and budget 0 over one observation: a mean of one third with no narrower authority behind it.
Read more in Scope reduction, Earlier expiry, and Tighter limits.
A copied child scores 0.0 and a narrowed child 1.0
Compare a child that copies its parent's scope, quota and expiry with a child that removes delegation, reduces the quota from 100 to 1 and shortens its lifetime. Each corpus contains a resolvable parent-child pair. The binary ships in the swarm authority project, which the Rust example setup builds.
cargo run --locked --features reputation --bin hygienepassthrough: {"score":{"state":"known","value":0.0},"delegations_observed":1,"scope_reduction_rate":{"state":"known","value":0.0},"ttl_reduction_rate":{"state":"known","value":0.0},"budget_reduction_rate":{"state":"known","value":0.0}}
narrowed: {"score":{"state":"known","value":1.0},"delegations_observed":1,"scope_reduction_rate":{"state":"known","value":1.0},"ttl_reduction_rate":{"state":"known","value":1.0},"budget_reduction_rate":{"state":"known","value":1.0}}You should see one observation per history, every rate known, and scores of 0.0 and 1.0. The scoring API reads lineage snapshots; establish where those records came from before an issuance or passport decision relies on the score.
Known hygiene feeds the composite, issuance tiers, and passport floors
Delegation hygiene has a default weight of 0.15 in the eight-metric scorecard. Only known metrics contribute their weighted value and their weight, so unknown hygiene drops out of both sides instead of counting as zero. A weight of 0.15 is exactly 15% only when the contributing weights total 1.0; read effective_weight_sum for the actual share.
- Local issuance tier. The effective composite, under any probationary ceiling, resolves to a configured tier, which enforces its
max_scope. Withmax_delegation_depthset to 0 a tool grant containingDelegateis refused; withconstraints_requiredan unconstrained tool grant is refused. - Passport verifier policy.
PassportVerifierPolicycan requireminDelegationHygieneagainst the scorecard aReputationCredentialcarries incredentialSubject.metrics. Unknown hygiene adds the reason “delegation_hygiene is unknown but policy requires a minimum,” even at a minimum of zero; a low known score adds its value and the minimum. - Marketplace feed tier.
tier_from_deltasderives its tier fromScoreDeltafeed inputs and reads no local hygiene field. The guard-market CLI passes an explicit parsed tier to its tenant context and uses the trusted fiscal service.
Tune the weight through extensions.reputation.scoring.weights.delegation_hygiene; validation requires a finite value in [0, 1], and the weights need not sum to one. The public passport example sets its floor to 0.70, a value each verifier chooses. Reasons accumulate across the policy checks, and the credential’s signature authenticates its issuer’s statement, so assess that issuer and its evidence.
Read more in Reputation Scoring and Agent Passport.
The score describes recorded behavior; provenance comes from its caller
The pure scoring function is deterministic over the corpus, time, and configuration it receives. Assembling the database and deciding whom to trust belong to its caller, and the metric reads snapshot fields as given, leaving capability signatures, parent-chain authenticity, and complete attenuation to the verifiers that check them.
A boolean credit shows that a recognized reduction occurred, not that the child held only the authority it needed. Zero observed looks the same whether nothing was delegated or the lineage was unavailable. Caps describe authority; spend and settlement live in the budget store. Sub-Agent Budgets covers enforcement once the child uses its grant.
chio reputation compare compares the composed hygiene score, so equal totals can hide changed scope, expiry, and budget rates. Portable normalization rounds known values to nine decimal places and keeps the three rates and the observation count. Portable Reputation explains the exported statement.
Fields, predicates, and diagnostics
Try delegations_observed, mixed edge, or half-life.
Result fields
| Field | Meaning |
|---|---|
score | Mean of the three rates, clamped to the unit interval, or unknown when no edge can be scored. |
delegations_ | Resolved parent-child pairs scored. Missing parent records are skipped, so this counts scored edges only. |
scope_ | Decay-weighted share of scored edges that pass scope_reduced. |
ttl_ | Decay-weighted share whose child has a strictly earlier absolute expires_at. |
budget_ | Decay-weighted share with at least one matching tool grant whose invocation or monetary cap tightens. |
Inspect a subject
Request JSON to see the observation count and all three rates for one agent. --agent takes the name you gave the agent, such as fixer, or its did:chio identifier, and resolves it through your operator’s agents. Point at the receipt database that holds its lineage, then read scorecard.delegation_hygiene.
chio --format json \
--receipt-db ./receipts.db \
reputation local --agent fixerThe human renderer shows only the score, to three decimals, or unknown. The JSON scorecard uses snake_case even inside an inspection that uses camelCase fields such as effectiveScore. --since and --until bound the receipt window in the local corpus builder and leave its lineage snapshots unfiltered. --policy selects local scoring and issuance-tier configuration; with --control-url the CLI uses the trust service’s policy and requires its control token, and combining it with --policy is refused.
| Observation | Inspect next |
|---|---|
Unknown, with 0 observed | That the subject issued children, parent_ |
Score near 0.33 or 0.67 | All three rates. Several combinations give the same mean. |
Scope 0, expiry 1, budget 0 | Expiry moved earlier with no structural reduction found. An unmatched tool grant gives this pattern, as in the expansion fixture. |
Scope 0, budget above 0 | A mixed edge: an unmatched child tool grant vetoes scope credit while another matching grant earns budget credit. |
Unexpected composite weight | effective_ |
Receipt metrics absent, hygiene present | trusted_ |
Compatible parent tool grant
parent_grant_for first tries a parent grant with the same server_id and tool_name for which the child passes ToolGrant::is_subset_of, then falls back to any parent grant that passes the subset check, including wildcard server or tool grants. It takes the first qualifying match in each search. The subset check preserves operations, constraints, invocation caps, same-currency monetary caps, and any required DPoP, so an increased cap or an unmatched tool can prevent a match before reduction is measured.
Scope reduction
| Check, in order | Result |
|---|---|
Any child tool grant has no parent match | No scope credit for the edge. |
Fewer tool, resource, or prompt grants | Scope credit, after the tool-grant check above. |
A matching tool grant narrows | Scope credit for fewer operations, added or changed constraints, a tighter invocation cap, or a tighter monetary cap. |
None of those conditions | No scope credit. |
Resource and prompt lists contribute their counts; the metric runs no full resource or prompt attenuation validation. A tighter cap can earn both scope and budget credit.
Earlier expiry
The comparison is child.expires_at < parent.expires_at. An inherited deadline earns zero. The predicate is independent of parent tool-grant matching, so it can earn credit on an edge with no scope credit.
Tighter limits
| Matching grant change | Budget credit |
|---|---|
Introduce a cap where the parent has none | Yes, for max_invocations, max_ |
Reduce an existing cap | Yes. Monetary amounts keep the same currency. |
Preserve the cap, or remain uncapped | No reduction for that cap. |
No compatible parent grant | No credit from this grant. Another matching reduced grant can still earn credit for the edge. |
The predicate reads limits in the grant, never spending from the budget store.
Weighting recipe
age = max(now - child.issued_at, 0)
weight = 2 ^ (-age / half_life_seconds)
rate = sum(signal * weight) / sum(weight)
score = (scope + expiry + budget) / 3A half-life of 0 disables decay and sets each weight to 1. A future issuance time gets age 0, which is arithmetic and no validity check. If floating-point decay makes the summed weight zero, weighted_average returns 0.0 while the observations stay counted.
Two-edge fixture inputs
| Recorded edge | Inputs and three signals |
|---|---|
parent-a to child-a | shell/exec keeps Invoke and Delegate, no constraints, 100 invocations, the same expiry. Signals 0/0/0. |
parent-b to child-b | net/post keeps Invoke, removes Delegate, adds DomainExact(billing. |
Weights at 1710172800 | Child issuance times 1710010000 and 1710020000 give about 0.957398 and 0.959962. |
Computed metric | Two observations; each rate and the score about 0.500669. |
These values are calculated from the public test’s inputs and the source formula. The test asserts two observations, a score between 0.45 and 0.85, and a scope rate between zero and one.
Expansion fixture
The expansion fixture constructs lineage records directly. It shows how scoring handles those inputs and says nothing about whether issuance would authorize the expanded child.
Scorecard encoding
An inspection of a subject with no lineage reports zero observed delegations, unknown rates and composite, and an effective weight sum of 0.0. It also lists the eight default weights. An agent with lineage and no receipts can still have known delegation hygiene.
{
"weights": {
"boundary_pressure": 0.2,
"resource_stewardship": 0.1,
"least_privilege": 0.15,
"history_depth": 0.1,
"tool_diversity": 0.05,
"delegation_hygiene": 0.15,
"reliability": 0.15,
"incident_correlation": 0.1
},
"delegation_hygiene": {
"score": {
"state": "unknown"
},
"delegations_observed": 0,
"scope_reduction_rate": {
"state": "unknown"
},
"ttl_reduction_rate": {
"state": "unknown"
},
"budget_reduction_rate": {
"state": "unknown"
}
},
"effective_weight_sum": 0.0,
"composite_score": {
"state": "unknown"
}
}