ServicesReputation

Affinity and Nemeses

Affinity is the one-to-one projection of the same event stream that feeds reputation: a directed edge from actor to target, moved by the per-event deltas in the domain config, decaying toward the configured neutral point, and labeled with a relationship state from the domain's ladder.

Reading edges

snug reputation affinity get --domain tavern \
  --from auth_user:docs-wave --to auth_user:vendor
snug reputation affinity edges --domain tavern \
  --entity auth_user:docs-wave --sort negative --limit 10

An edge that has never been touched is 404 affinity_not_found. Edges are directed: the gift you gave the vendor moves you -> vendor, not the reverse. edges lists an entity's strongest outgoing edges - positive for top friends, negative for top enemies - from a maintained index (capped per entity; see the configuration reference). The edge payload carries the decayed score, the current state, and the state history:

{
  "edge": {
    "domain": "docs4-tavern",
    "from": { "kind": "auth_user", "id": "docs-wave" },
    "to": { "kind": "auth_user", "id": "docs4-vendor" },
    "score": 3.9999255057288954,
    "state": "stranger",
    "state_history": [ { "state": "stranger", "entered_at": "2026-08-28T14:16:06.753783Z" } ],
    "nemesis": null,
    ...
  }
}

The state ladder

Relationship states are inclusive upper bounds, checked in order, so a ladder like nemesis (-50) / rival (-10) / stranger (10) / friend (50) labels an edge by where its score falls. Crossing a boundary during ingestion returns a relationship_transition in the event response and fans out a relationship_changed transition event. One -30 event moved a fresh edge stranger -> rival, a second moved it rival -> nemesis.

Preference profiles

A target's preference profile multiplies affinity deltas for events that match, optionally on a metadata key/value (requires reputation_granter or admin to set):

snug reputation preferences set --domain tavern \
  --entity auth_user:vendor --file ./prefs.json
{
  "preferences": [
    { "event_type": "gift_given", "metadata_key": "flower",
      "metadata_value": "rose", "delta_multiplier": 3.0 }
  ]
}

Verified: with that profile, a gift_given event carrying "metadata": {"flower": "rose"} applied +15 to the edge instead of the base +5. event send has no metadata flag - attach metadata via the batch file or the raw HTTP body. A rule without metadata_key/metadata_value multiplies every event of its type. Preferences scale affinity deltas only, never the reputation score.

Nemesis promotion

A state marked "nemesis_suite": true in the domain config is special: when an edge falls into it, the transition reports "nemesis_promoted": true, a nemesis transition event fans out, and the edge grows a nemesis block - escalation level, victory/defeat counts, and a bounded memory of the events that forged the rivalry. The target's roster lists everyone currently holding a nemesis-state edge against them:

snug reputation nemesis list --domain tavern --entity auth_user:docs-wave
{
  "count": 1,
  "nemeses": [
    {
      "edge": {
        "from": { "kind": "ecs_entity", "id": "docs4-npc-guard" },
        "to": { "kind": "auth_user", "id": "docs-wave" },
        "score": -60.0,
        "state": "nemesis",
        "nemesis": {
          "level": 1, "victories": 0, "defeats": 0,
          "memories": [ { "event_type": "ambushed", "summary": "ambushed event",
                          "weight": 1.0, "occurred_at": "2026-08-28T14:17:04.445485Z" } ],
          ...
        }
      },
      "level": 1
    }
  ]
}

Resolving a rivalry

Rivalries do not decay away on their own - they end deliberately (requires reputation_granter or admin):

snug reputation nemesis resolve --domain tavern \
  --from ecs_entity:docs4-npc-guard --to auth_user:docs-wave \
  --outcome reconciled

reconciled re-labels the edge stranger; nemesis_defeated_final re-labels it rival. Either way the nemesis block is archived and removed from the roster. Verified: the resolved edge came back "state": "stranger", "nemesis": null with its numeric score untouched at -60.0 - resolution rewrites the label, then ordinary decay toward neutral does the rest. Resolving an edge with no active nemesis block is 404 nemesis_not_found (verified by resolving twice).

The exact parameters and schemas for every operation here are in the Reputation API reference.

On this page