Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

No bridge

instrument wraps a function, which only helps if your host has one to wrap. Plenty don’t.

Maybe your sandbox runs in another process and calls back over HTTP. Maybe it drops requests on a queue and something else picks them up. Maybe you only find out what it did by reading a log after it finishes. In all of those there’s no function sitting on the boundary, so you record the calls yourself.

You get the same spans either way. instrument is a convenience built on top of what’s below.

Recording

Three calls. Start one when you learn a call began, end it when you learn how it went.

const crossing = execution.crossing.start({ target: "orders.list", input: params });

crossing.output(result);  // it came back
crossing.error(failure);  // it didn't

target is the only thing required. Everything else is optional and gets filled in with what you know.

Late

Calls don’t have to end in the order they started, and they don’t have to end at all.

const a = execution.crossing.start({ target: "orders.list" });
const b = execution.crossing.start({ target: "inventory.check" });

b.error(new Error("upstream 503"));   // second one settles first
a.output({ rows: 2 });

execution.crossing.start({ target: "slow_thing" });  // never settles
execution.complete();
execute_tool inventory.check   error
execute_tool orders.list       output
execute_tool slow_thing        abandoned
execute_code                   completed

Anything still open when the execution ends gets closed for you and marked abandoned. A sandbox that goes quiet leaves a record saying so rather than a hole you have to notice.

Order

Timestamps often won’t order these for you. Calls under a millisecond tie, and a sandbox on another machine has a clock you don’t control. If you know the order the program asked in, say it:

execution.crossing.start({ target: "orders.list", seq: 2 });

Only pass seq if it’s real. A wrong order is worse than no order, so anything that isn’t a positive integer is refused and the count mocon kept itself is used instead.

Declaring

Watching from a distance usually means seeing less, and the declaration is where you say so. Three settings matter here.

crossing_edge. Use "dispatch" if what you see is the request you sent toward the tool, rather than the call the program asked for. Anything observing at the network layer is dispatch. A retry you did on the program’s behalf is one call to the program and several dispatches, and this is what tells a reader which one they’re looking at.

observes_crossings. Use "some" unless you’re certain you see every call. If calls reach you through a queue you might drop from, or a log you might read late, you see some.

unmediated_egress. Set it true if the program has any path out that doesn’t come through you. This is the one that stops a reader concluding “no calls recorded, so it called nothing”, which is the wrong conclusion to let someone reach.

codeMode({
  capabilities: {
    observes_crossings: "some",
    unmediated_egress: true,
    crossing_edge: "dispatch",
    attested: [],
  },
});

Declaring goes through all of it, including how to sharpen these once you’ve checked what you actually see.

Reconnecting

If your host learns about an execution in one place and its calls in another, you don’t need to hold the handle between them. Give the execution an id you already have:

const execution = observed.execution.start({ program: source, id: runId });

Every crossing carries that id, so a query finds them by it without needing the parent span. That’s what lets a worker process record a call for a run that a different process started.