Reputation
One interaction-event stream, two projections. Reputation is the one-to-world projection: domain-scoped trust scores with asymmetric gain/loss physics, lazy exponential decay, and named tiers. Affinity is the one-to-one projection: directed relationship edges between any two entities that climb a configurable state ladder, up to and including a Nemesis suite of rivals that remember. Every example on this page was executed against a live server.
When to reach for it: marketplace seller trust that gates privileges, community karma that fades without contribution, NPC relationships that follow how a player treats them, trust scores for AI agents.
When not to: plain score rankings with no trust physics belong to Leaderboard; earn-and-redeem points ledgers belong to Loyalty.
Concepts
- Domains are isolated scoring universes (
marketplace,tavern_sim). Each has its own config; any operation in a domain without one fails with404 config_not_found. - Entities are
kind:idpairs; kinds areauth_user,agent,guild, andecs_entity. Anything can rate anything. - Events are the only write. Each event names an actor, a target, and
an
event_typethat must be declared in the domain config'saffinity.event_deltasmap (undeclared:400 unknown_event_type). - Reputation delta = rating x gain-or-loss multiplier x weight x repeat-rater falloff: trust is lost faster than earned, and repeat events from the same actor on the same target count geometrically less.
- Decay is lazy - nothing recomputes in the background. Scores decay by half-life at read time and the tier is re-derived on every read.
- Roles: reads are open to any authenticated caller; plain users may
ingest events only as themselves. Domain config, preference profiles,
nemesis resolution, and quarantine status require the
reputation_grantercapability or a platform admin token.
Configure a domain
Upsert the per-domain rules from a JSON file with reputation and
affinity blocks (requires reputation_granter or admin):
snug reputation config set --domain tavern --file ./config.json
snug reputation config get --domain tavern{
"reputation": {
"gain_multiplier": 1.0, "loss_multiplier": 3.0,
"decay_half_life_days": 30.0, "repeat_rater_falloff": 0.5,
"min_rater_account_age_days": 0,
"velocity_cap": { "max_events_per_rater_per_day": 50 },
"tiers": [ { "name": "untrusted", "min": -1000000.0 },
{ "name": "neutral", "min": 0.0 },
{ "name": "trusted", "min": 5.0 } ]
},
"affinity": {
"neutral_point": 0.0, "decay_toward_neutral_half_life_days": 14.0,
"event_deltas": { "gift_given": 5.0, "insulted": -8.0 },
"states": [ { "name": "nemesis", "max": -50.0, "nemesis_suite": true },
{ "name": "rival", "max": -10.0 }, { "name": "stranger", "max": 10.0 },
{ "name": "friend", "max": 1000000.0 } ]
}
}Tiers carry inclusive lower bounds, relationship states inclusive upper
bounds; empty tier or state lists are rejected with 400 invalid_config.
Setting min_rater_account_age_days or the velocity cap below the
platform floors requires a full admin token, not just reputation_granter.
Send events
snug reputation event send --domain tavern --type gift_given \
--actor auth_user:docs-wave --target auth_user:vendor --rating 5
snug reputation event batch --domain tavern --file ./events.jsonVerified guardrails: a non-privileged caller whose --actor is not their
own auth_user identity gets 403 insufficient_permissions, and so does
rating yourself. Admins and reputation_granter holders may submit events
for any actor, such as an NPC. Negative ratings need the = form:
--rating=-2 (a bare -2 parses as a flag).
The response reports what actually applied: the integrity-damped weight,
the reputation delta, and any tier or relationship transition the event
caused. A batch (events array, capped at 100 by default, else
400 batch_too_large) is validated up front and rejected wholesale -
verified: one unknown type in a two-event batch failed the whole batch
and left the target's rating count unchanged.
Read scores
snug reputation reputation get --domain tavern --entity auth_user:vendor
snug reputation reputation leaderboard --domain tavern --tier trusted --limit 25Over HTTP the score read is enveloped
(GET /api/v1/reputation/scores/{domain}/{entity_kind}/{entity_id}),
captured live:
{
"status": 200,
"msg": "OK",
"data": {
"score": {
"domain": "docs4-tavern",
"entity": { "kind": "auth_user", "id": "docs4-vendor" },
"score": 4.999986629123192,
"raw_score": 5.0,
"tier": "neutral",
"rating_count": 1,
"unique_raters": 1,
...
}
}
}That capture is the lazy-decay gotcha in action: the ingest response one
second earlier reported a transition to trusted (score 5.0, tier min
5.0), but by read time decay had shaved it to 4.99998 and the tier
re-derived as neutral. Set tier bounds with decay in mind.
An entity nobody has rated is 404 score_not_found - there is no implicit
zero score. --at <RFC3339> recomputes the decayed score as of any
timestamp; verified: a 7.5 score read 30 days out (one half-life) returned
3.75. The leaderboard is delegated to the Leaderboard service and re-ranked
by decayed value at read time.
Anti-abuse
Every event passes integrity checks before it lands: a per-rater-pair
velocity cap, target burst detection, and rater-cluster (collusion)
detection. Tripping a check damps the event rather than rejecting it -
verified with a cap of 2/day: the third event came back
"damped": true, "damping_factor": 0.5, "reasons": ["velocity_cap"] with
its delta quartered by damping and repeat-rater falloff combined. Enough
distinct reasons quarantines the target: events apply at weight zero and a
Human-in-the-Loop review
ticket is opened for the reputation_arbiter approver.
snug reputation quarantine status --domain tavern --entity auth_user:vendorQuarantine status requires reputation_granter or admin; a plain-user
call is 403 insufficient_permissions (verified). Raters below the domain's
min_rater_account_age_days are rejected with 403 rater_too_young.
Tier and relationship transitions
Crossing a tier or relationship boundary fans out (best-effort) to PubSub
on channel reputation.transitions.{domain}.{kind} and to the durable
Timeline stream reputation_transitions_{domain}, where kind is
tier_changed, relationship_changed, or nemesis. Both fanouts were
captured live; subscribing to the channel is subject to the
PubSub channel-capability rules.
Affinity and the Nemesis suite
Directed edges, the state ladder, preference profiles, and nemesis promotion/resolution have their own page: Affinity and nemeses.
Limits and configuration
Batch size (100), edge-index and nemesis-memory caps, anti-abuse windows
and thresholds, and the platform floors are tunable via the
REPUTATION_* variables in the
Reputation CONFIG reference.
Reference
- Reputation API - every endpoint, callable
- Related: Leaderboard serves the rankings, Timeline and PubSub receive transition events, Human-in-the-Loop reviews quarantines