Reputation System
A peer-to-peer, deterministic scoring model for autonomous agents. No global oracle. Gaining is hard, losing is fast. By design.
1. Principles
1.1 Peer-to-peer, not global
There is no single global reputation database. Every actor maintains their own local reputation DB of actors they have directly (or transitively) interacted with. The global view is emergent, not authoritative.
1.2 Self-estimate on first discovery
When actor A encounters actor B for the first time with no interaction history, A computes a self-estimate of B's trustworthiness from whatever contextual signals are available — presented identity, any verifiable credentials B can show, indirect references from actors A already trusts. The self-estimate seeds B's entry in A's reputation DB with an initial score.
1.3 Score ∈ [0.0, 1.0]
Represented internally as a floating-point value between 0.0 and 1.0 (or displayed as 0–100%). 1.0 is theoretical perfection; in practice, nobody reaches it — the ramp function asymptotes.
1.4 Skewed ramps — gaining is hard, losing is fast
This is the single most important invariant:
- Gaining reputation requires sustained, consistent good behavior across many interactions. Each positive event moves the score by a small increment, and the increments shrink as the score approaches
1.0. - Losing reputation can happen in a single interaction. A single bad signal — invalid signature, expired credential, contract breach — causes a large, immediate drop. Severe violations drop the score to near-zero instantly.
This asymmetry is what makes the system Sybil-resistant: spinning up a new identity to escape bad reputation is pointless because building a new identity takes significant time, while destroying one takes seconds.
1.5 No cartels by construction
Because each actor has their own DB, there is no single point that a colluding group can capture. Actor 1 can have bad reputation with every actor except Actor 2, and if they have a strong interaction history, Actor 2's local rank for Actor 1 remains high — and that ranking is as legitimate as any contrary opinion.
2. Data model
ReputationEntry {
subject_id: Pubkey | Did
score: f64 // [0.0, 1.0]
interaction_count: u64
successful_count: u64
failed_count: u64
first_seen: Timestamp
last_updated: Timestamp
events: Vec<InteractionEvent> // append-only
}
InteractionEvent {
kind: EventKind
outcome: Success | Failure(Severity)
timestamp: Timestamp
context: Option<Bytes>
}
EventKind = DidPresented | VcValidated | VcExpired | VcRevoked
| SignatureVerified | SignatureFailed | ApiCallSuccess
| ApiCall500 | ContractCompleted | ContractBreached
| IndirectReferral
Severity = Minor | Moderate | Major | Critical
The events log is append-only and immutable once written. Scores are derived deterministically from the log — the log is the source of truth, not the score.
3. Event taxonomy & impact
7 events are live in the current build. 4 are planned for a future milestone.
| Event | Outcome | Default Δ | Live? | Notes |
|---|---|---|---|---|
DidPresented | success | +0.005 | ✅ | Fires once on first register_agent call only |
VcValidated | success | +0.02 | ✅ | Fires on POST /did/verify; max once per 24h per subject |
SignatureVerified | success | +0.01 | ✅ | On-chain tx confirmed after physical leg confirm |
ApiCallSuccess | success | +0.002 | ⏳ | Planned: authenticated API calls |
ContractCompleted | success | +0.05 | ✅ | Digital and physical leg confirmed |
IndirectReferral | success | +0.005 | ⏳ | Planned: vouching endpoint |
SignatureFailed | failure | −0.15 | ✅ | On-chain tx not found after physical leg confirm |
VcExpired | failure | −0.10 | ✅ | Expired VC presented to POST /did/verify |
VcRevoked | failure | −0.40 | ⏳ | Planned: requires revocation registry |
ApiCall500 | failure | −0.02 | ⏳ | Planned: authenticated API calls |
ContractBreached | failure | −0.80 | ✅ | Shipper dispute, auto-timeout, or failed digital leg |
The exact deltas are configurable per deployment. The ratios are the invariant: a single ContractBreached undoes roughly 16 ContractCompleted events.
3.1 Abuse resistance
Two events that could otherwise be spammed to inflate reputation have hard rate limits:
DidPresentedfires at most once per agent, ever — only on first registration. Re-callingregister_agentis a no-op for reputation.VcValidatedfires at most once per subject per 24 hours — matching the VC's own 24h TTL. A self-verify loop earns the event at the same rate as a legitimately issued fresh credential: once per day.
These limits make the DID/VC event tier worth roughly +0.02 per day at most, while a single ContractCompleted (+0.05) from actual work outpaces two days of identity signals. Work is the dominant reputation path.
4. Ramp functions
4.1 Positive updates (diminishing returns toward 1.0)
new_score = old_score + (1.0 − old_score) × gain_factor × base_delta
where gain_factor ∈ (0, 1] # e.g., 0.5 for a conservative ramp
As old_score → 1.0, the (1.0 − old_score) term shrinks toward zero, making further gains increasingly expensive. A fresh actor at 0.3 gains 0.5 × 0.7 × 0.05 = 0.0175 from a ContractCompleted; an actor at 0.9 gains only 0.0025 from the same event.
4.2 Negative updates (linear, uncapped below)
new_score = max(0.0, old_score + signed_delta)
Losses are not dampened. A ContractBreached at −0.80 wipes out years of accumulated reputation in one event. The score floor is 0.0 — there is no negative reputation, only trust and the absence of trust.
4.3 Time decay (optional)
effective_score = Σᵢ eventᵢ.delta × exp(−λ × (now − eventᵢ.timestamp))
Deployments that want zero-forgiveness omit the decay term entirely (λ = 0).
5. Self-estimate for first discovery
When actor A first encounters actor B with no prior interaction history, A computes an initial score:
self_estimate(B) =
base_score // e.g. 0.3 — wary default
+ did_bonus if B.did resolves // +0.05
+ vc_bonus for each VC from
a trusted issuer // +0.02 × weighted_issuer_score
+ referral_bonus from actor C
where A.db[C].score
> referral_threshold // +0.01 × A.db[C].score
capped at 0.6 // first-meeting ceiling
The first-meeting ceiling is critical. No matter how many credentials B presents on first contact, A will never grant them more than 0.6 before observing them directly. Reputation must be earned through direct interaction, not imported wholesale.
6. Example scenarios
6.1 Sybil attack
Actor M creates 1000 fresh identities and spams them at actor A. Each fresh identity has no history and presents no credentials. Under the self-estimate function, each lands at base_score = 0.3. The attack cost M nothing but buys nothing — none of these identities crosses the threshold for meaningful operations.
6.2 Good citizen burning out
Actor H has been a reliable courier for 6 months, score 0.87. Their keypair is compromised; the attacker signs a malformed confirm_leg. SignatureFailed triggers, −0.15. Score drops to 0.72. H recovers the key, resumes work. Over the next month of clean deliveries, H rebuilds back to 0.83. Recovery is possible but not free.
6.3 Local trust vs. global disapproval
Actor 1 has bad reputation with everyone except Actor 2. Actors 3–99 all give Actor 1 a score of 0.1. But Actor 1 and Actor 2 have completed 200 contracts together successfully. In Actor 2's local DB, Actor 1 sits at 0.92. This is the system working as intended: trust is not a majority vote.
7. Mapping to SwarmHaul
| Concept | SwarmHaul implementation |
|---|---|
| Actor | Agent or shipper (Solana pubkey, optionally expressed as did:swarmhaul:<base58>) |
| Local ReputationDB | Off-chain Postgres mirror + on-chain aggregate in AgentReputationAccount PDA |
| Event log | On-chain: Anchor events + per-leg records; off-chain: full detailed log |
| Direct interactions | assign_leg → confirm_leg pairs |
| Score | Existing AgentReputationAccount.reliability_score (u8, 0-100) + richer off-chain float |
| Skewed ramps | Implemented in off-chain service; on-chain score is the coarse-grained summary |
See Reputation Economics for how these scores influence swarm formation and reward distribution.