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

Lifecycle Hooks

To support the complete sandbox lifecycle, your extension should implement the following four APIs. AgentENV sends a JSON request to POST {url}/sandbox-hook/<hook> when the corresponding event occurs. Any connection error, timeout, or non-2xx response fails the corresponding sandbox operation (except stop, which is best-effort).

HookWhenRequestResponse
start-freshBefore a fresh sandbox boots, after its network slot is allocatedsandboxId, sandboxInstanceId, networkNamespacePath, hostInteractionIp, customExtensionParamsoptional extraBootArgs appended to the kernel cmdline
start-resumeBefore a sandbox resumes from a snapshot (template launch, resume after pause, fork child)same as abovenone
patch-paramsWhen a user PATCHes the sandbox’s paramssandboxId, patch (verbatim user body)updated full customExtensionParams
stopWhen the sandbox runtime is torn down, before the network slot is releasedsandboxId, sandboxInstanceIdnone

Notes:

  • Instance identity. A sandboxId is reused across pause/resume cycles. Every start-fresh / start-resume carries a fresh sandboxInstanceId identifying that runtime instance, and the subsequent stop carries the same value. Because stop is best-effort and may be reordered (e.g. a pause’s stop arriving after the resume’s start-resume), treat (sandboxId, sandboxInstanceId) as the identity of a running instance and ignore stop notifications whose sandboxInstanceId is not the latest started instance for that sandbox.
  • stop also fires on pause. Pausing persists the sandbox state and then stops the VM process and releases the network namespace; the subsequent resume creates a fresh runtime and fires start-resume. In-place pause+resume during snapshot capture does not fire any hook (and keeps the same sandboxInstanceId).
  • stop is best-effort: delivery failures are only logged, and it is also fired fire-and-forget if a started sandbox is dropped without an explicit stop.
  • networkNamespacePath is the host path of the sandbox’s netns file (e.g. /var/run/netns/agentenv-ns-*), so the extension can enter the namespace (e.g. nsenter --net=...) to set up firewall rules or VPN interfaces.
  • hostInteractionIp is the per-runtime IPv4 address that AgentENV routes to this sandbox. It can change after pause/resume, so extensions must use the value from the current start hook rather than caching an older one.
  • Concurrent patch-params calls to the same sandbox are not serialized; if your patch semantics are not commutative, handle concurrency in the extension.

Minimal Extension Example

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

# Latest started runtime instance per sandbox: (sandboxId, sandboxInstanceId)
# is the identity of a running instance; a stop for a superseded instance
# (e.g. arriving after a newer start) is ignored.
latest_instance: dict[str, str] = {}

@app.post("/sandbox-hook/start-fresh")
async def start_fresh(req: Request):
    body = await req.json()
    latest_instance[body["sandboxId"]] = body["sandboxInstanceId"]
    # e.g. nsenter --net={body["networkNamespacePath"]} wg-quick up ...
    return {"extraBootArgs": None}

@app.post("/sandbox-hook/start-resume")
async def start_resume(req: Request):
    body = await req.json()
    latest_instance[body["sandboxId"]] = body["sandboxInstanceId"]
    return {}

@app.post("/sandbox-hook/patch-params")
async def patch_params(req: Request):
    body = await req.json()
    # apply body["patch"] however you like, then return the full new params
    return {"customExtensionParams": body["patch"]}

@app.post("/sandbox-hook/stop")
async def stop(req: Request):
    body = await req.json()
    if latest_instance.get(body["sandboxId"]) == body["sandboxInstanceId"]:
        latest_instance.pop(body["sandboxId"], None)
        # tear down resources for this instance
    return {}

Any non-2xx response or timeout fails the corresponding sandbox operation, except for stop, which is always tolerated.