Events
All sandbox activity is observable through the event stream, sandbox.getEventsStream(): a chronological, real-time record of everything the sandbox does. It doubles as an audit log, every command, file access, and network request the agent makes shows up here.
Streaming events
import { getOrCreateSandbox } from "@hiver.sh/client";
const sandbox = await getOrCreateSandbox("my-sandbox");
const ac = new AbortController();
for await (const event of sandbox.getEventsStream({ signal: ac.signal })) {
console.log(event.id, event.timestamp, event.type);
}
// Stop the stream
ac.abort();The stream auto-reconnects on network drops (exponential backoff). Pass a signal to stop it cleanly.
Event types
| Event | Fields | Description |
|---|---|---|
config.apply | success, changes, errorMessage? | Sandbox config changed |
egress.request | access, host, method, path, query?, headers?, body? | Outbound HTTP request |
egress.response | request_id, status, duration_ms, headers? | Response to an egress request |
egress.chunk | request_id, body, label? | Streaming/WebSocket frame (label: up or down) |
ingress.request | port, method, path, query?, headers?, body? | Inbound request to a port exposed by the sandbox |
ingress.response | request_id, status, duration_ms, headers? | Response to an ingress request (status + headers at time-to-first-byte) |
ingress.chunk | request_id, body, label? | Inbound response body/WebSocket frame as it streams (label: up or down) |
fs.request | access, mount, path, operation | File access attempt (read or write) |
fs.response | backend, request_id, duration_ms, error? | File operation result |
stdio | stdout?, stderr? | Process output |
resource.usage | cpu_percent, memory_bytes | Periodic resource sample |
exec.request | cwd, command | Exec call started |
exec.response | request_id | Exec call completed |
system.start | — | The request to start the VM or container was received |
system.config-changed | config | Config was updated; config is the new config |
system.vm-resumed | — | A microVM was resumed from a snapshot instead of cold booting |
system.shutdown | — | The sandbox expired its TTL without activity and is shutting down |
system.fs-sync-request | backend, operation, path | Request to an external file-system backend (e.g. GCS, S3) is starting |
system.fs-sync-response | request_id, duration_ms, error? | The external-backend request completed |
access on egress.request and fs.request is "allowed" or "denied".
fs.request/fs.response describe the agent's file operations against a
mount. system.fs-sync-request/system.fs-sync-response are one level down:
they fire around each request the FUSE layer makes to an external backend
(Google Cloud Storage, S3, Google Drive, OneDrive, Azure, or an external HTTP
host) to sync a path — operation is one of list, list-dir, stat,
get, put, delete, or move. Purely local mounts have no external
backend and emit none. Pair a response to its request via request_id, which
matches the request event's id.
Restricting which events are observed
By default every event type above is emitted. Set SandboxConfig.events to observe only a subset — useful to cut noise, or to avoid the overhead of capturing high-volume traffic (like ingress.chunk/egress.chunk bodies) you don't plan to read. An empty array observes nothing. events is reconciled at runtime, like fs and egress.
const sandbox = await getOrCreateSandbox("my-sandbox", {
events: ["egress.request", "egress.response", "exec.request", "exec.response", "stdio"],
});This isn't just a display filter: an excluded event type's underlying work is skipped too. For example, narrowing events to exclude ingress.chunk stops the sandbox from capturing the response body of proxied inbound requests at all, rather than capturing it and discarding it unread.
Resuming from a cursor
Each event has a numeric id. Pass lastEventId to resume from where you left off, for example after a reconnect or process restart:
let cursor: number | undefined;
for await (const event of sandbox.getEventsStream({ signal: ac.signal })) {
cursor = event.id;
}
// Later, replay only events after the saved cursor
for await (const event of sandbox.getEventsStream({ lastEventId: cursor })) {
// only events after id `cursor`
}Filtering client-side
Filter events by checking event.type in the loop — useful for ad hoc filtering that doesn't need the overhead savings of SandboxConfig.events above:
for await (const event of sandbox.getEventsStream()) {
if (event.type === "egress.request" && event.access === "denied") {
console.log("blocked:", event.host, event.method, event.path);
}
if (event.type === "fs.request" && event.access === "denied") {
console.log("file denied:", event.path, event.operation);
}
}From the CLI
hiver events <sandbox-key> streams the same events to your terminal as JSON, one per line:
hiver events my-sandbox # replay history, then stream live
hiver events my-sandbox --follow # keep streaming and reconnect on closePass --jq <filter> to run each event through a jq expression before it's printed. The filter is applied to one event at a time, so select(...) drops events, .field projects, and a filter that yields multiple values prints one line each — the same as piping to jq, but built in:
# only denied egress requests
hiver events my-sandbox --jq 'select(.type == "egress.request" and .access == "denied")'
# a set of event types
hiver events my-sandbox --jq 'select(.type | IN("exec.request","fs.request","stdio"))'
# just the command of each exec call
hiver events my-sandbox --jq 'select(.type == "exec.request") | .command'A non-matching select produces no output, so those events are dropped from the stream. Wrap the filter in single quotes so the shell leaves the double quotes inside intact. An invalid filter fails fast with a syntax error; a filter that errors on a particular event is reported to stderr while the stream keeps going.
Because the output is a JSON stream, you can also pipe it straight into another tool — for example, into an LLM for a plain-English summary:
hiver events my-sandbox \
--jq 'select(.type | IN("exec.request","egress.request","fs.request","stdio"))' \
| claude -p "what did the agent do?"