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

234 lines
4.6 KiB
Markdown

---
title: "probe"
description: "Runtime observability for actors"
weight: 90
type: "docs"
---
Runtime observability for actors. Register named probe functions on any actor and query them over HTTP while the program runs.
```javascript
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.
```javascript
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
```
```json
{
"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
```
```json
{
"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
```
```json
{
"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
```
```json
{
"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:
```json
{"ok": false, "error": "not found"}
```
Unknown targets or probe names:
```json
{"ok": false, "error": "unknown probe: game/nonexistent"}
```
If a probe function disrupts:
```json
{"ok": false, "error": "probe failed"}
```
## Example
A game actor with a simulation loop and probe observability:
```javascript
// 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.