Lifecycle history and webhooks require hosted or local Supabase-backed state.

List recent events

Account-wide listing is available from the Python SDK, CLI, and HTTP API. The TypeScript SDK exposes lifecycle history per machine (machine.lifecycle.list), shown under Per-machine events.
from nullspace import Lifecycle

lifecycle = Lifecycle()
events = lifecycle.list_events(limit=20)
for event in events.events:
    print(event.operation, event.status, event.machine_id)
lifecycle.close()
nullspace lifecycle events --limit 20
curl "${NULLSPACE_API_URL}/v1/lifecycle/events?limit=20" \
  -H "Authorization: Bearer ${NULLSPACE_API_KEY}"

Per-machine events

from nullspace import Machine

with Machine.create(template="base") as machine:
    machine.commands.run("true", shell=True)
    events = machine.list_lifecycle(limit=10)
    for event in events.events:
        print(event.operation, event.status)
import { Machine } from "@nullspace/sdk";

const machine = await Machine.create({ template: "base" });
await machine.commands.run("true", { shell: true });
const events = await machine.lifecycle.list({ limit: 10 });
for (const event of events.events) {
  console.log(event.operation, event.status);
}
nullspace lifecycle events --machine-id mch_123 --limit 10
curl "${NULLSPACE_API_URL}/v1/machines/mch_123/lifecycle?limit=10" \
  -H "Authorization: Bearer ${NULLSPACE_API_KEY}"

Stream events

The lifecycle stream is a WebSocket channel (/v1/lifecycle/ws). The SDKs and CLI handle the upgrade and replay for you; raw HTTP clients perform the upgrade described in the WebSocket protocol.
from nullspace import Lifecycle

lifecycle = Lifecycle()
for event in lifecycle.stream_events(
    machine_id="mch_123",
    live_only=False,
    replay_limit=100,
    metadata={"run": "docs"},
):
    print(event.id, event.operation, event.status)
lifecycle.close()
import { Machine } from "@nullspace/sdk";

const machine = await Machine.connect("mch_123");
for await (const event of machine.lifecycle.subscribe({
  liveOnly: false,
  replayLimit: 100,
})) {
  console.log(event.id, event.operation, event.status);
}
nullspace lifecycle stream --machine-id mch_123 --replay-limit 100
# WebSocket upgrade; send a `subscribe` message after connecting.
# See ../reference/websocket-protocol for message shapes.
wscat -c "${NULLSPACE_API_URL}/v1/lifecycle/ws" \
  -H "Authorization: Bearer ${NULLSPACE_API_KEY}"
Use subscribe() when you want callbacks in a background reader:
subscription = lifecycle.subscribe(
    machine_id="mch_...",
    on_event=lambda event: print(event.operation, event.status),
    on_error=lambda error: print(error.message),
)
subscription.stop()

Filters

list_events supports operation, status, audience, machine ID, snapshot ID, source machine ID, target machine ID, time range, ordering, offset, and limit filters. stream_events supports machine ID, snapshot ID, operation, status, audience, metadata matching, after_event_id, live_only, and replay_limit. It does not support source/target machine filters, time range, ordering, offset, or limit. Use after_event_id to resume from a known event ID after reconnecting.

Event fields

FieldNotes
operationcreate, destroy, timeout_updated, timeout_expired, hibernate, snapshot_created, resume_from_snapshot, fork, or reconcile.
statusrequested, succeeded, or failed.
audiencePublic SDK helpers expose customer-visible events.
machine_id, snapshot_idPresent when the operation is tied to that resource.
source_machine_id, target_machine_idPresent for fork and resume-style transitions.
error_code, error_messagePopulated for failed transitions.

Snapshot events

events = lifecycle.list_snapshot_events("snap_123", limit=20)
for event in events.events:
    print(event.operation, event.status, event.machine_id)
nullspace lifecycle events --snapshot-id snap_123 --limit 20
curl "${NULLSPACE_API_URL}/v1/snapshots/snap_123/lifecycle?limit=20" \
  -H "Authorization: Bearer ${NULLSPACE_API_KEY}"