The first ten minutes

Three commands and one HTTP call gets a character talking. Every response below was captured from a live service while this page was generated, against cyberpunk-block.

  1. Install it

    pip install -e .

    Python 3.10 or newer. No third-party packages: the install pulls nothing, which is why it works behind a studio firewall and in a locked build environment.

  2. Start the service

    unscripted serve --world-pack worldpacks/cyberpunk-block --auth-token dev

    Binding off loopback without a token is refused rather than warned about. For a shipping build, add --no-debug-endpoints: /state/* exposes every character's private beliefs, which is what makes the debug HUD useful and what a game client must not have.

  3. Say something to a character

    curl -s localhost:8765/v2/avatar/turn -H "Authorization: Bearer dev" -H "Content-Type: application/json" -d '{"text": "ask Dima about clinic"}'

    One call. The response below is the real one, captured while this page was generated.

Ask what this build can do, before anything else

GET /capabilities200
{
  "runtime_version": "1.21.0",
  "contract_versions": [
    "1.0.0",
    "2.0.0"
  ],
  "default_contract": "1.0.0",
  "capabilities": {
    "semantic_moves": true,
    "deception": true,
    "retrospective_revision": true,
    "... 13 more": "..."
  }
}

A capability that is false means the feature is absent, not that it produced nothing this turn. Refuse to start if the runtime does not list a contract version you understand.

Say something to a character

POST /v2/avatar/turn {"text": "ask Dima about clinic"}200
{
  "world_time": 4334,
  "location_id": "place:main_street",
  "parsed": {
    "raw_text": "ask Dima about clinic",
    "intent": "ask",
    "confidence": 0.86,
    "actor_id": "agent:player_1",
    "target_id": "agent:dima_broke",
    "location_id": null,
    "proposition": {
      "predicate": "clinic_open",
      "slots": {
        "place": "place:clinic",
        "when": "last_night"
      },
      "polarity": "-",
      "valid_start": null,
      "valid_end": null,
      "spatial": null,
      "degree": null
    },
    "amount": null,
    "minutes": 0,
    "topic": "clinic",
    "metadata": {},
    "reasons": [
      [
        "parser.ask",
        0.86,
        {
          "target": "agent:dima_broke",
          "topic": "clinic"
        }
      ]
    ]
  },
  "message": "Dima: \"I've got nothing for you.\"",
  "observations": [],
  "receipts": [
    {
      "event_id": 1002,
      "event_type": "question",
      "world_time": 4334,
      "observed_by": [
        "agent:barkeep_12",
        "agent:corpo_okada",
        "agent:dima_broke",
        "agent:npc_red_jacket"
      ],
      "reasons": {
        "agent:barkeep_12": [
          [
            "memory.encoded",
            0.45,
            {
              "content": "player asks about clinic",
              "type": "episodic"
            }
          ]
        ],
        "agent:corpo_okada": [
          [
            "memory.encoded",
            0.45,
            {
              "content": "player asks about clinic",
              "type": "episodic"
            }
          ]
        ],
        "agent:dima_broke": [
          [
            "policy.selected",
            -0.049,
            {
              "sampled_action": "answer_truthfully",
              "top_rated_action": "greet",
              "top_rated_utility": 0.589,
              "stochastic": true,
              "T": 0.378,
              "selection_probs": [
                [
                  "greet",
                  0.604
                ],
                [
                  "evade",
                  0.259
                ],
                [
                  "answer_truthfully",
                  0.112
                ],
                [
                  "threaten",
                  0.025
                ]
              ],
              "contributions": [
                [
                  "relationship",
                  0.12,
                  1.2,
                  0.144
                ],
                [
                  "safety",
                  -0.28,
                  1.12,
                  -0.314
                ],
                [
                  "norm",
                  0.1,
                  1.21,
                  0.121
                ],
                "... 2 more"
              ]
            }
          ],
          [
            "validator.accept",
            0.0,
            {}
          ]
        ],
        "agent:npc_red_jacket": [
          [
            "memory.encoded",
            0.45,
            {
              "content": "player asks about clinic",
              "type": "episodic"
            }
          ]
        ]
      }
    }
  ],
  "npc_response": {
    "speaker_id": "agent:dima_broke",
    "text": "I've got nothing for you.",
    "act": "inform",
    "verdict": "ACCEPT",
    "utility": -0.049,
    "reasons": [
      [
        "policy.selected",
        -0.049,
        {
          "sampled_action": "answer_truthfully",
          "top_rated_action": "greet",
          "top_rated_utility": 0.589,
          "stochastic": true,
          "T": 0.378,
          "selection_probs": [
            [
              "greet",
              0.604
            ],
            [
              "evade",
              0.259
            ],
            [
              "answer_truthfully",
              0.112
            ],
            [
              "threaten",
              0.025
            ]
          ],
          "contributions": [
            [
              "relationship",
              0.12,
              1.2,
              0.144
            ],
            [
              "safety",
              -0.28,
              1.12,
              -0.314
            ],
            [
              "norm",
              0.1,
              1.21,
              0.121
            ],
            "... 2 more"
          ]
        }
      ],
      [
        "validator.accept",
        0.0,
        {}
      ]
    ],
    "style": {
      "formality": 0.389,
      "lexical_sophistication": 0.185,
      "mean_sentence_length": 8.798,
      "slang_level": 0.0,
      "directness": 0.572,
      "hedging": 0.233,
      "dialect": null,
      "jargon_domain": "street",
      "slang_lexicon": null,
      "politeness_strategy": "positive"
    }
  },
  "developer_trace": "  affect of agent:dima_broke:\n    mood PAD: valence=-0.15 arousal=-0.23 dominance=+0.07   (baseline -0.15/-0.23/+0.07)\n    active emotions: (calm)\n    stress: 0.00\n  beliefs of agent:dima_broke:\n    (none)\n  memory of agent:dima_broke @ t=4334:\n    [episodic  ] player asks about clinic\n        activation=0.00 (faint)  importance=0.45  valence=+0.00  source_conf=0.77\n  last reason trace for agent:dima_broke:\n    policy.selected              -0.049  {'sampled_action': 'answer_truthfully', 'top_rated_action': 'greet', 'top_rated_utility': 0.589, 'stochastic': True, 'T': 0.378, 'selection_probs': [('greet', 0.604), ('evade', 0.259), ('answer_truthfully', 0.112), ('threaten', 0.025)], 'contributions': [('relationship', 0.12, 1.2, 0.144), ('safety', -0.28, 1.12, -0.314), ('norm', 0.1, 1.21, 0.121), ('tom', 0.0, 1.0, 0.0), ('emotion_tendency', 0.0, 1.0, 0.0)]}\n    validator.accept             +0.000  {}",
  "done": false,
  "avatar": {
    "speaker_id": "agent:dima_broke",
    "text": "I've got nothing for you.",
    "act": "inform",
    "verdict": "ACCEPT",
    "utility": -0.049,
    "style": {
      "formality": 0.389,
      "lexical_sophistication": 0.185,
      "mean_sentence_length": 8.798,
      "slang_level": 0.0,
      "directness": 0.572,
      "hedging": 0.233,
      "dialect": null,
      "jargon_domain": "street",
      "slang_lexicon": null,
      "politeness_strategy": "positive"
    },
    "reasons": [
      [
        "policy.selected",
        -0.049,
        {
          "sampled_action": "answer_truthfully",
          "top_rated_action": "greet",
          "top_rated_utility": 0.589,
          "stochastic": true,
          "T": 0.378,
          "selection_probs": [
            [
              "greet",
              0.604
            ],
            [
              "evade",
              0.259
            ],
            [
              "answer_truthfully",
              0.112
            ],
            [
              "threaten",
              0.025
            ]
          ],
          "contributions": [
            [
              "relationship",
              0.12,
              1.2,
              0.144
            ],
            [
              "safety",
              -0.28,
              1.12,
              -0.314
            ],
            [
              "norm",
              0.1,
              1.21,
              0.121
            ],
            "... 2 more"
          ]
        }
      ],
      [
        "validator.accept",
        0.0,
        {}
      ]
    ],
    "honesty": "honest",
    "asserted": [],
    "emotion": {
      "dominant": "downcast",
      "intensity": 0.154,
      "valence": -0.154,
      "arousal": -0.231,
      "dominance": 0.073,
      "active": {}
    },
    "blendshapes": {
      "browInnerUp": 0.0385,
      "mouthFrownLeft": 0.0462,
      "mouthFrownRight": 0.0462
    },
    "gaze": {
      "target": "agent:player_1",
      "aversion": 0.0,
      "make_contact": true
    },
    "prosody": {
      "speech_rate": 0.931,
      "pitch_shift": -0.057,
      "loudness": 0.419
    },
    "posture": "neutral"
  },
  "contract_version": "2.0.0"
}

`text` is the line. `honesty` is the runtime's own record of whether it matched what the speaker believes — never infer that from the prose. `asserted` is what the line committed the character to, and who was close enough to hear it. The face and prosody blocks drive an avatar and a voice.

Ask who knows what, and on whose word

GET /v2/state/knowledge200
{
  "world_time": 4334,
  "time_of_day": "00:14",
  "facts": [
    {
      "key": "clinic_open(place=place:clinic,when=last_night)[None,None,None]",
      "text": "clinic_open(place=place:clinic, when=last_night)",
      "predicate": "clinic_open",
      "detail": 2,
      "holders": [
        {
          "agent": "agent:barkeep_12",
          "name": "Honce",
          "prob": 0.1995,
          "confidence": 0.8005,
          "hops": 0,
          "origin": "seed_bar_clinic",
          "times_heard": 1,
          "independent_origins": 1
        },
        {
          "agent": "agent:officer_kane",
          "name": "Officer Kane",
          "prob": 0.1935,
          "confidence": 0.8065,
          "hops": 0,
          "origin": "seed_kane_clinic",
          "times_heard": 1,
          "independent_origins": 1
        },
        {
          "agent": "agent:npc_red_jacket",
          "name": "Vee",
          "prob": 0.212,
          "confidence": 0.788,
          "hops": 0,
          "origin": "seed_vee_clinic",
          "times_heard": 1,
          "independent_origins": 1
        }
      ],
      "origins": [
        "seed_bar_clinic",
        "seed_kane_clinic",
        "seed_vee_clinic"
      ],
      "reach": 3,
      "first_hand": 3,
      "hearsay": 0,
      "max_hops": 0
    },
    {
      "key": "was_at(agent=agent:corpo_okada,place=place:clinic,when=last_night)[None,None,None]",
      "text": "was_at(agent=agent:corpo_okada, place=place:clinic, when=last_night)",
      "predicate": "was_at",
      "detail": 3,
      "holders": [
        {
          "agent": "agent:corpo_okada",
          "name": "Mr. Okada",
          "prob": 0.8005,
          "confidence": 0.8005,
          "hops": 0,
          "origin": "seed_okada_visit",
          "times_heard": 1,
          "independent_origins": 1
        }
      ],
      "origins": [
        "seed_okada_visit"
      ],
      "reach": 1,
      "first_hand": 1,
      "hearsay": 0,
      "max_hops": 0
    },
    {
      "key": "was_at(agent=agent:officer_kane,place=place:clinic,when=last_night)[None,None,None]",
      "text": "was_at(agent=agent:officer_kane, place=place:clinic, when=last_night)",
      "predicate": "was_at",
      "detail": 3,
      "holders": [
        {
          "agent": "agent:barkeep_12",
          "name": "Honce",
          "prob": 0.7905,
          "confidence": 0.7905,
          "hops": 0,
          "origin": "seed_bar_kane",
          "times_heard": 1,
          "independent_origins": 1
        }
      ],
      "origins": [
        "seed_bar_kane"
      ],
      "reach": 1,
      "first_hand": 1,
      "hearsay": 0,
      "max_hops": 0
    },
    {
      "key": "whereabouts(who=agent:npc_red_jacket,place=place:bar_back)[None,None,None]",
      "text": "whereabouts(who=agent:npc_red_jacket, place=place:bar_back)",
      "predicate": "whereabouts",
      "detail": 2,
      "holders": [
        {
          "agent": "agent:corpo_okada",
          "name": "Mr. Okada",
          "prob": 0.868,
          "confidence": 0.868,
          "hops": 0,
          "origin": "seed_okada_hid_milan",
          "times_heard": 1,
          "independent_origins": 1
        }
      ],
      "origins": [
        "seed_okada_hid_milan"
      ],
      "reach": 1,
      "first_hand": 1,
      "hearsay": 0,
      "max_hops": 0
    }
  ],
  "stories": [],
  "transmissions": [],
  "knowledge_classes": [
    {
      "predicate": "clinic_open",
      "classification": "common",
      "domain": "street",
      "access": [],
      "locality": [],
      "transmissibility": 0.85
    },
    {
      "predicate": "connected_to_attack",
      "classification": "common",
      "domain": "street",
      "access": [],
      "locality": [],
      "transmissibility": 0.85
    },
    {
      "predicate": "whereabouts",
      "classification": "common",
      "domain": "street",
      "access": [],
      "locality": [],
      "transmissibility": 0.85
    }
  ],
  "circles": [
    {
      "circle": "district:corpo_plaza",
      "members": [
        "agent:corpo_okada"
      ]
    },
    {
      "circle": "district:watson",
      "members": [
        "agent:barkeep_12",
        "agent:dima_broke",
        "agent:npc_red_jacket",
        "agent:pavel"
      ]
    },
    {
      "circle": "faction:badges",
      "members": [
        "agent:officer_kane"
      ]
    },
    "... 11 more"
  ],
  "bridges": [
    {
      "agent": "agent:barkeep_12",
      "name": "Honce",
      "circles": [
        "district:watson",
        "place:bar",
        "role:bartender",
        "role:service"
      ]
    },
    {
      "agent": "agent:corpo_okada",
      "name": "Mr. Okada",
      "circles": [
        "district:corpo_plaza",
        "group:arasaka",
        "role:officer"
      ]
    },
    {
      "agent": "agent:npc_red_jacket",
      "name": "Vee",
      "circles": [
        "district:watson",
        "family:k",
        "group:reds",
        "role:fixer"
      ]
    },
    "... 2 more"
  ],
  "contested": [],
  "judgements": [],
  "occupancy": [
    {
      "place": "place:bar",
      "label": "Bar",
      "people": [
        {
          "id": "agent:son_marko",
          "name": "Marko"
        }
      ]
    },
    {
      "place": "place:main_street",
      "label": "Main Street",
      "people": [
        {
          "id": "agent:dima_broke",
          "name": "Dima"
        },
        {
          "id": "agent:barkeep_12",
          "name": "Honce"
        },
        {
          "id": "agent:corpo_okada",
          "name": "Mr. Okada"
        },
        "... 2 more"
      ]
    },
    {
      "place": "place:market",
      "label": "Market",
      "people": [
        {
          "id": "agent:son_ilya",
          "name": "Ilya"
        },
        {
          "id": "agent:pavel",
          "name": "Pavel"
        }
      ]
    },
    {
      "place": "place:police_post",
      "label": "Police Post",
      "people": [
        {
          "id": "agent:officer_kane",
          "name": "Officer Kane"
        }
      ]
    }
  ],
  "contract_version": "2.0.0"
}

The debug HUD lives here: stories grouped by incident, who is a bridge between circles, which claims are contested. Turn it off for a shipping client.

Ask what the runtime wants your engine to do

GET /v2/actions/pending200
{
  "intents": [],
  "bridge": {
    "enabled": true,
    "issued": 0,
    "pending": 0,
    "... 6 more": "..."
  },
  "contract_version": "2.0.0"
}

Empty here because no world time has passed yet. When a routine moves somebody, an intent appears and the character does NOT move until you report the result — that is how the simulation and the screen are kept from disagreeing.

And what a mistake looks like

POST /advance {"minutes": -30}400
{
  "error": "'minutes' must be >= 0, got -30.",
  "code": "minutes_out_of_range"
}

World time is monotonic; rewinding it would corrupt every memory activation. Client mistakes come back as typed 4xx with a code you can branch on, never as a 500 or a silently broken world.