Files
cell/docs/library/probe.md
2026-02-24 21:04:03 -06:00

4.6 KiB

title, description, weight, type
title description weight type
probe Runtime observability for actors 90 docs

Runtime observability for actors. Register named probe functions on any actor and query them over HTTP while the program runs.

var probe = use('probe')

The probe server starts automatically on the first register() call, listening on 127.0.0.1:9000.

Registering Probes

probe.register(target, probes)

Register a group of probe functions under a target name. Each probe is a function that receives an args record and returns a value.

var probe = use('probe')

var world = {
  entities: [
    {id: 1, name: "player", x: 10, y: 20, hp: 100},
    {id: 2, name: "goblin", x: 55, y: 30, hp: 40}
  ],
  tick: 0
}

probe.register("game", {
  state: function(args) {
    return world
  },
  entities: function(args) {
    return world.entities
  },
  entity: function(args) {
    return find(world.entities, function(e) {
      return e.id == args.id
    })
  }
})

probe.register("render", {
  info: function(args) {
    return {fps: 60, draw_calls: 128, batches: 12}
  }
})

A target is just a namespace — group related probes under the same target name. Register as many targets as you like; the server starts once and serves them all.

HTTP Endpoints

All responses are JSON with an ok field.

GET /discover

Lists all registered targets and their probe names. Designed for tooling — an LLM or dashboard can call this first to learn what's available, then query specific probes.

$ curl http://127.0.0.1:9000/discover
{
  "ok": true,
  "targets": {
    "game": ["state", "entities", "entity"],
    "render": ["info"]
  }
}

POST /probe

Call a single probe function by target and name. Optionally pass arguments.

$ curl -X POST -H "Content-Type: application/json" \
  -d '{"target":"game","name":"state"}' \
  http://127.0.0.1:9000/probe
{
  "ok": true,
  "result": {
    "entities": [
      {"id": 1, "name": "player", "x": 10, "y": 20, "hp": 100},
      {"id": 2, "name": "goblin", "x": 55, "y": 30, "hp": 40}
    ],
    "tick": 4821
  }
}

With arguments:

$ curl -X POST -H "Content-Type: application/json" \
  -d '{"target":"game","name":"entity","args":{"id":1}}' \
  http://127.0.0.1:9000/probe
{
  "ok": true,
  "result": {"id": 1, "name": "player", "x": 10, "y": 20, "hp": 100}
}

POST /snapshot

Call multiple probes in one request. Returns all results keyed by target/name.

$ curl -X POST -H "Content-Type: application/json" \
  -d '{"probes":[{"target":"game","name":"state"},{"target":"render","name":"info"}]}' \
  http://127.0.0.1:9000/snapshot
{
  "ok": true,
  "results": {
    "game/state": {
      "entities": [
        {"id": 1, "name": "player", "x": 10, "y": 20, "hp": 100},
        {"id": 2, "name": "goblin", "x": 55, "y": 30, "hp": 40}
      ],
      "tick": 4821
    },
    "render/info": {
      "fps": 60,
      "draw_calls": 128,
      "batches": 12
    }
  }
}

Errors

Unknown paths return 404:

{"ok": false, "error": "not found"}

Unknown targets or probe names:

{"ok": false, "error": "unknown probe: game/nonexistent"}

If a probe function disrupts:

{"ok": false, "error": "probe failed"}

Example

A game actor with a simulation loop and probe observability:

// game.ce
var probe = use('probe')

var state = {
  entities: [
    {id: 1, name: "player", x: 0, y: 0, hp: 100},
    {id: 2, name: "enemy", x: 50, y: 50, hp: 60}
  ],
  frame: 0,
  paused: false
}

probe.register("game", {
  state: function(args) {
    return state
  },
  entities: function(args) {
    return state.entities
  },
  entity: function(args) {
    return find(state.entities, function(e) {
      return e.id == args.id
    })
  }
})

// game loop
def tick = function(_) {
  if (!state.paused) {
    state.frame = state.frame + 1
    // ... update entities, physics, AI ...
  }
  $delay(tick, 0.016)
}
$delay(tick, 0.016)

While the game runs, query it from a terminal:

$ curl -s http://127.0.0.1:9000/discover | jq .targets
{
  "game": ["state", "entities", "entity"]
}

$ curl -s -X POST -d '{"target":"game","name":"state"}' \
  -H "Content-Type: application/json" \
  http://127.0.0.1:9000/probe | jq .result.frame
7834

$ curl -s -X POST -d '{"target":"game","name":"entity","args":{"id":1}}' \
  -H "Content-Type: application/json" \
  http://127.0.0.1:9000/probe | jq .result
{
  "id": 1,
  "name": "player",
  "x": 142,
  "y": 87,
  "hp": 100
}

Probes run inside the actor's normal turn, so the values are always consistent — never a half-updated frame.