openapi: 3.0.3
info:
  title: Yuragi Engine — Partner API
  version: "1.0"
  description: |
    Interface for driving Yuragi-engine creatures over HTTP/WebSocket, for
    building a game layer (birth, aging, death, memory, life-story replay)
    on top of the engine without embedding it directly.

    ## Core idea

    Every creature's mood, personality, and action are a pure deterministic
    function of `(identity string, tick number)` — there is no randomness
    anywhere in the engine. The same name always produces the same lifelong
    trajectory of internal states. That trajectory can always be recomputed
    from scratch and checked against a stored value (**replay**), and any
    single moment of it can be reduced to a short fingerprint (**state
    hash**) for cheap comparison or optional on-chain timestamping.

    ## Scope of this document

    This spec covers the **interface only** — request/response shapes you
    can build a client against. It does not describe the internal
    equations, genome-derivation formula, or memory-fold mechanism that
    produce these values; those aren't published here.

    ## Authentication

    Every endpoint in this document is **open — no API key, token, or
    signature required**. Spawning a creature, replaying it, feeding it
    experience, chatting, and playing games are all unauthenticated calls.

    A separate, smaller set of operator-only endpoints exists on the same
    server (reissuing a lost ownership-claim token, creating/transferring a
    creature's on-chain "Living UTXO") and requires an admin token — those
    are intentionally **not included in this spec** because they aren't
    needed to create or run creatures, only to manage optional on-chain
    ownership/custody features.

    ## Typical flow for a single-room prototype

    1. `POST /api/agents` once per creature — birth. No credentials needed.
    2. Open the WebSocket (or poll `GET /api/world`) to drive per-tick
       animation/behavior from live state.
    3. `POST /api/agents/{id}/experience` whenever something happens to a
       creature worth remembering (met another creature, was taught
       something, had an outcome).
    4. `POST /api/play` for direct interactions between 2–3 creatures.
    5. `GET /api/agents/{id}/replay` at any later point (including after a
       creature has "died" in your game layer) to reconstruct and verify its
       full life story.

    ## Errors

    All error responses share one shape: `{ "error": "<message>" }`, with a
    non-2xx HTTP status (400 invalid input, 404 unknown creature id, 409
    conflict, 429 rate-limited).
  contact:
    name: Yuragi Engine
    url: https://yuragiengine.com
servers:
  - url: https://yuragiengine.com
    description: Hosted instance
  - url: http://localhost:4820
    description: Self-hosted instance (npm install && npm start)

tags:
  - name: World
    description: Whole-world snapshots and the live feed.
  - name: Lifecycle
    description: Birth and life-story verification.
  - name: Memory
    description: Feeding events in and reading accumulated memory/knowledge back out.
  - name: Expression
    description: Chat and deterministic audio/visual output.
  - name: Interaction
    description: Deterministic games/interactions between creatures.
  - name: Proof
    description: Optional on-chain verification — not required for a prototype.

paths:
  /api/world:
    get:
      tags: [World]
      summary: Full world snapshot
      description: >
        Poll for the current state of every creature. For a running game,
        prefer subscribing to the WebSocket feed below instead of polling
        this on an interval.
      responses:
        "200":
          description: World snapshot
          content:
            application/json:
              schema:
                type: object
                properties:
                  height: { type: integer, description: "Current timechain block height. One tick per block, ~3s per block." }
                  tipHash: { type: string, description: "Hash of the most recent timechain block." }
                  couplingK: { type: number, description: "Kuramoto coupling strength across creatures. 0 = fully independent; higher values pull creatures' rhythms toward sync." }
                  syncOrder: { type: number, description: "0..1 herd synchronization order parameter — 0 = independent, 1 = fully synchronized." }
                  bsvMode: { type: string, enum: [simulated, dry-run, live], description: "Whether/how state hashes are anchored on BSV mainnet. Irrelevant to gameplay." }
                  agents:
                    type: array
                    items: { $ref: "#/components/schemas/AgentSummary" }
              example:
                height: 18422
                tipHash: "6f2a1c...9e"
                couplingK: 0.3
                syncOrder: 0.42
                bsvMode: simulated
                agents:
                  - id: "a1b2c3"
                    name: "Momo"
                    tick: 18422
                    stateHash: "d4e5f6..."
                    decision: "explore"
                    personality: { curiosity: 0.71, calm: 0.44, sociability: 0.62, boldness: 0.38 }
                    state: { x: 0.6123, phi: 2.198, theta: -0.041, thetaDot: 0.017 }
      x-websocket-alternative:
        url: "wss://yuragiengine.com"
        description: >
          Pushes `{ "type": "world", "data": <same shape as this response> }`
          once per tick. Prefer this over polling for real-time behavior/animation.

  /api/agents:
    post:
      tags: [Lifecycle]
      summary: Spawn a creature (birth)
      description: >
        Creates a new creature. The name is permanent and defines the
        creature's genome forever — the same name always produces the same
        creature if spawned again in a fresh world.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                  maxLength: 24
                  description: Permanent identity. Max 24 characters.
              example: { name: "Momo" }
      responses:
        "200":
          description: Spawned
          content:
            application/json:
              schema:
                type: object
                properties:
                  id: { type: string, description: "Use this id in all subsequent calls." }
                  name: { type: string }
                  identityHash: { type: string }
                  claimToken:
                    type: string
                    description: >
                      One-time BSV ownership-claim token. Only relevant if
                      you want players to cryptographically claim/own a
                      creature on-chain — not needed for a single-owner
                      game backend, safe to ignore.
              example:
                id: "a1b2c3"
                name: "Momo"
                identityHash: "9f8e7d..."
                claimToken: "ctok_..."
        "400":
          description: Missing name
        "409":
          description: Name already taken in this world

  /api/agents/{id}/replay:
    get:
      tags: [Lifecycle]
      summary: Replay & verify a creature's history (life story)
      description: >
        Recomputes the creature's entire trajectory from genesis up to
        `tick` and returns a hash. If a `liveHash` is also returned (i.e.
        `tick` is the creature's current tick) and the two match, the
        history is proven untampered — recomputed independently, not just
        asserted. This is the primitive to build a "life story" replay/audit
        feature on, including for creatures your game has since aged out or
        "killed" — their full history remains reconstructable forever from
        just their name.
      parameters:
        - $ref: "#/components/parameters/AgentId"
        - name: tick
          in: query
          schema: { type: integer }
          description: Tick to replay up to. Defaults to the creature's current tick.
        - name: mind
          in: query
          schema: { type: string, enum: ["1"] }
          description: Include folded experience/memory events in the replay.
        - name: herd
          in: query
          schema: { type: string, enum: ["1"] }
          description: Include cross-creature coupling history. Needed for an exact match whenever couplingK has been > 0 at any point in this creature's life.
        - name: math
          in: query
          schema: { type: string, enum: ["fp", "fixed", "fp32"] }
          description: >
            Use the Q32.32 fixed-point twin (`v3fp` hashes) instead of the live
            float core. Solo only — omit herd/mind. Does not match liveHash or
            existing BSV anchors; for bit-exact cross-machine audits and new
            consumers.
      responses:
        "200":
          description: Replay result
          content:
            application/json:
              schema:
                type: object
                properties:
                  identity: { type: string }
                  tick: { type: integer }
                  replayedHash: { type: string, description: "Hash recomputed from genesis." }
                  liveHash: { type: string, nullable: true, description: "Hash of the currently-stored state at this tick, if available." }
                  verified:
                    description: "true/false if a liveHash was available to compare against; otherwise a status string explaining why not (e.g. replaying a past tick)."
                    oneOf: [{ type: boolean }, { type: string }]
                  decision: { type: string, description: "The action the creature chose at this tick." }
                  personality: { $ref: "#/components/schemas/Personality" }
              example:
                identity: "Momo"
                tick: 18422
                replayedHash: "d4e5f6..."
                liveHash: "d4e5f6..."
                verified: true
                decision: "explore"
                personality: { curiosity: 0.71, calm: 0.44, sociability: 0.62, boldness: 0.38 }
        "404":
          description: Unknown creature id

  /api/agents/{id}/experience:
    post:
      tags: [Memory]
      summary: Log a life event (feed into memory)
      description: >
        Queues an event for this creature. Events are folded into memory at
        the *next* tick, never mid-request — this keeps replay exact even
        with memory in the mix. Use this for anything your game wants a
        creature to remember: meeting another creature, an outcome of an
        interaction, or a fact taught to it.
      parameters:
        - $ref: "#/components/parameters/AgentId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [kind]
              properties:
                kind:
                  type: string
                  enum: [converse, observe, teach, outcome]
                  description: >
                    `converse` carries no subject/predicate/value by design
                    (it never teaches a fact by itself). `observe`, `teach`,
                    and `outcome` do, via `payload`.
                actorId: { type: string, description: "Other creature's id, if this event involves one." }
                payload:
                  type: object
                  description: "Small object, e.g. { subject, predicate, value } for observe/teach."
              example:
                kind: "teach"
                actorId: "b2c3d4"
                payload: { subject: "berry-patch", predicate: "location", value: "north-corner" }
      responses:
        "200":
          description: Queued (will be folded at the next tick)
          content:
            application/json:
              schema:
                type: object
                properties:
                  accepted: { type: boolean }
                  willFoldAtHeight: { type: integer, description: "Timechain height at which this event will be folded into memory." }
                  pendingCount: { type: integer, description: "Events still queued for this creature." }
        "400":
          description: Invalid event kind or oversized payload
        "404":
          description: Unknown creature id
        "429":
          description: Too many pending events for this creature — wait for next tick before sending more

  /api/agents/{id}/mind:
    get:
      tags: [Memory]
      summary: Read a creature's accumulated memory/knowledge
      parameters:
        - $ref: "#/components/parameters/AgentId"
      responses:
        "200":
          description: Mind snapshot
          content:
            application/json:
              schema:
                type: object
                properties:
                  name: { type: string }
                  tick: { type: integer }
                  recentEvents: { type: array, items: { type: object }, description: "Most recent folded events (last 20)." }
                  knowledge:
                    type: array
                    description: "Facts this creature has accumulated, each with a confidence that can decay or reinforce over time."
                    items:
                      type: object
                      properties:
                        key: { type: string }
                        subject: { type: string }
                        predicate: { type: string }
                        value: { type: string }
                        confidence: { type: number, minimum: 0, maximum: 1 }
                        reinforceCount: { type: integer }
                  knowledgeRoot:
                    type: string
                    nullable: true
                    description: "Canonical fingerprint of accumulated knowledge, null until this creature's first experience event has been folded."
        "404":
          description: Unknown creature id

  /api/agents/{id}/memory:
    get:
      tags: [Memory]
      summary: Read a creature's recent conversational memory
      parameters:
        - $ref: "#/components/parameters/AgentId"
      responses:
        "200":
          description: Memory log
          content:
            application/json:
              schema:
                type: object
                properties:
                  name: { type: string }
                  tick: { type: integer }
                  memory:
                    type: array
                    items:
                      type: object
                      properties:
                        user: { type: string }
                        reply: { type: string }
                        tick: { type: integer }
        "404":
          description: Unknown creature id

  /api/agents/{id}/converse:
    post:
      tags: [Expression]
      summary: Chat with a creature
      description: >
        Returns a deterministic reply derived from the creature's current
        personality/state (optionally rephrased by an LLM server-side if
        configured — the underlying content is never invented by the LLM).
      parameters:
        - $ref: "#/components/parameters/AgentId"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                message: { type: string, maxLength: 500 }
                context: { type: string, maxLength: 32, default: react, description: "Situational hint, e.g. 'greet', 'react'." }
              example: { message: "Hello!", context: "greet" }
      responses:
        "200":
          description: Reply
          content:
            application/json:
              schema:
                type: object
                properties:
                  utterance: { type: string }
                  memory: { type: array, items: { type: object }, description: "Updated recent conversational memory, same shape as GET /memory." }
        "404":
          description: Unknown creature id

  /api/play:
    post:
      tags: [Interaction]
      summary: Run a deterministic interaction/mini-game between creatures
      description: >
        Runs one of the games listed by `GET /api/games`. Two-creature games
        take `agentA`/`agentB`; group games (`herd-vote`, `perfect-choice`)
        take an `agents` array of 2–3+ ids — perfect for your single-room
        cast of creatures interacting with each other.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [game]
              properties:
                game: { type: string, description: "See GET /api/games for the list and requirements of each." }
                agentA: { type: string, description: "Required for 2-creature games." }
                agentB: { type: string, description: "Required for 2-creature games." }
                agents: { type: array, items: { type: string }, description: "Required for group games." }
              example: { game: "rps", agentA: "a1b2c3", agentB: "b2c3d4" }
      responses:
        "200":
          description: Game result (shape depends on the game — see GET /api/games for each game's `reads`)
          content:
            application/json:
              schema: { type: object }
        "400":
          description: Unknown game, or wrong number of creatures for this game
        "404":
          description: One or more creature ids not found

  /api/games:
    get:
      tags: [Interaction]
      summary: List available games and their metadata
      description: Single source of truth for what each game needs (min/max creatures) and what it reads from a creature's state.
      responses:
        "200":
          description: Game catalog
          content:
            application/json:
              schema:
                type: object
                properties:
                  games:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string }
                        label: { type: string }
                        blurb: { type: string }
                        reads: { type: array, items: { type: string } }
                        minAgents: { type: integer }
                        maxAgents: { type: integer }

  /api/agents/{id}/melody:
    get:
      tags: [Expression]
      summary: Deterministic melody derived from current state
      parameters:
        - $ref: "#/components/parameters/AgentId"
        - name: bars
          in: query
          schema: { type: integer, minimum: 4, maximum: 32, default: 16 }
      responses:
        "200":
          description: Melody export
          content:
            application/json:
              schema:
                type: object
                properties:
                  name: { type: string }
                  tick: { type: integer }
                  tempo: { type: number }
                  notes: { type: array, items: { type: object } }
        "404":
          description: Unknown creature id

  /api/agents/{id}/portrait:
    get:
      tags: [Expression]
      summary: Deterministic SVG portrait derived from current state
      parameters:
        - $ref: "#/components/parameters/AgentId"
      responses:
        "200":
          description: SVG image
          content:
            image/svg+xml:
              schema: { type: string }
        "404":
          description: Unknown creature id

  /api/bsv/verify:
    get:
      tags: [Proof]
      summary: Verify an on-chain YURAGI PushDrop anchor
      description: |
        Fetch a mainnet tx, decode YURAGI PushDrop outputs, replay the agent
        to the anchored tick, and compare hashes.

        For agents known to this server, omitted `herd` / `mind` flags
        **smart-default**: herd on (historical coupling), mind on when
        `eventSeq > 0`. Pass `herd=0` / `mind=0` to force solo/v1 replay.
        Batched state-anchor txs require `agentId` or `vout` (or an
        `identity` that uniquely matches one agent in the batch).
      parameters:
        - name: txid
          in: query
          required: true
          schema: { type: string, pattern: "^[a-f0-9]{64}$" }
        - name: agentId
          in: query
          schema: { type: string }
          description: Select one agent in a batched state-anchor tx.
        - name: vout
          in: query
          schema: { type: integer }
          description: Select by output index in a batched tx.
        - name: identity
          in: query
          schema: { type: string }
          description: e.g. cubebear|Name — required if the agent is not local; can also disambiguate a batch.
        - name: herd
          in: query
          schema: { type: string, enum: ["0", "1", "true", "false"] }
          description: Force herd-aware on/off; omit for smart default.
        - name: mind
          in: query
          schema: { type: string, enum: ["0", "1", "true", "false"] }
          description: Force mind-aware on/off; omit for smart default.
        - name: full
          in: query
          schema: { type: string }
          description: When "1", include the full art-burn message body.
      responses:
        "200":
          description: Verification result (`verified` true/false, or needs-selector / needs-identity / art-anchor)
          content:
            application/json:
              schema: { type: object }
        "400":
          description: Invalid txid or unrecognized anchor format
        "404":
          description: No Yuragi anchor found in that transaction
        "502":
          description: Upstream chain read / replay failure

components:
  parameters:
    AgentId:
      name: id
      in: path
      required: true
      schema: { type: string }
      description: Creature id returned by POST /api/agents.
  schemas:
    Personality:
      type: object
      description: Sigmoid-projected trait readout of the creature's current internal state — not stored values, recomputed every tick.
      properties:
        curiosity: { type: number, minimum: 0, maximum: 1 }
        calm: { type: number, minimum: 0, maximum: 1 }
        sociability: { type: number, minimum: 0, maximum: 1 }
        boldness: { type: number, minimum: 0, maximum: 1 }
    AgentSummary:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        tick: { type: integer }
        stateHash: { type: string, description: "Canonical fingerprint of this creature's current state." }
        decision: { type: string, description: "Action chosen at this tick." }
        personality: { $ref: "#/components/schemas/Personality" }
        state:
          type: object
          description: >
            Raw dynamical-system readout at this tick. These are values, not
            the equations that produce them from tick to tick — the update
            rule itself is intentionally out of scope of this document.
          properties:
            x: { type: number, description: "Die subsystem value." }
            phi: { type: number, description: "Metronome subsystem phase." }
            theta: { type: number, description: "Pendulum subsystem angle." }
            thetaDot: { type: number, description: "Pendulum subsystem angular velocity." }
