Build with Pusk

Managed services in your image

Every instance gets a working LLM endpoint and a Composio MCP server from its environment. No API keys, no OAuth wiring, and it works in a fully custom image.

Every Pusk instance boots with credentials already in its environment. Your image does not receive them from you, does not store them, and does not need to ask for them:

VariableValue
PUSK_MANAGED_TOKENA per-instance token
PUSK_LLM_PROXY_URLhttps://api.pusk.ai/llm/v1
PUSK_COMPOSIO_MCP_URLhttps://api.pusk.ai/mcp/composio
PUSK_INSTANCE_IDThis instance's id

That token opens both endpoints. This applies to any image, including one you built yourself from scratch: the platform injects these at container create, not the image.

Those four are the contract. A container's environment carries other PUSK_-prefixed variables that are runtime internals; they change without notice, so build against the four above and nothing else.

A model

An OpenAI-compatible endpoint with exactly two routes: GET /v1/models and POST /v1/chat/completions. Point any OpenAI-compatible client at it.

curl -sS "$PUSK_LLM_PROXY_URL/chat/completions" \
  -H "Authorization: Bearer $PUSK_MANAGED_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "model": "default", "messages": [{"role":"user","content":"hello"}] }'

"default" is a fast, low-cost model we pick and keep current. GET /v1/models lists every paid model you can name instead; it is free to call. Streaming works. Free models and models without tool support are rejected, because agents need tools.

Integrations

A Composio MCP server, scoped to this instance's own entity, at $PUSK_COMPOSIO_MCP_URL with the same bearer token. It speaks streamable HTTP.

curl -sS "$PUSK_COMPOSIO_MCP_URL" \
  -H "Authorization: Bearer $PUSK_MANAGED_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

It exposes a handful of meta-tools rather than hundreds of individual ones, so it costs very little context: the agent searches the catalog for what it needs, then executes it. The tool names and their behavior are Composio's surface, documented in Composio's meta-tools reference. Billing is ours and is simple: discovery (searching tools, fetching schemas) is free; executing tools and managing connections bills per tool call, with a batch execute capped at 50 calls.

Tip

The connection-management meta-tool lets the agent start its own OAuth connections from inside the instance. Driving the integrations API from your backend is the alternative, not the prerequisite.

Warning

POST and DELETE only. A GET returns 405 by design, because proxying the optional server-push stream would hold one open connection per instance. Well-behaved MCP clients treat 405 as "this server has no server-push stream" and carry on, but some probe the older SSE transport and report the server as failed. If your client offers an explicit transport setting, set it to streamable HTTP.

Wiring an agent to both

Two config blocks, using pi as the example. Neither contains a credential; both reference the environment variable instead:

{
  "providers": {
    "pusk": {
      "baseUrl": "https://api.pusk.ai/llm/v1",
      "apiKey": "$PUSK_MANAGED_TOKEN",
      "api": "openai-completions",
      "models": [{ "id": "default", "name": "Pusk Managed", "contextWindow": 128000, "maxTokens": 8192 }]
    }
  }
}

A complete, runnable image doing exactly this lives at pusk-platform/pi-agent-image.

Warning

Reference the variable, never copy its value. The token is reissued, and the previous one revoked immediately, on create, start, restart, update, resize, migrate, and recovery. A config written once with the literal token baked in keeps working until the first restart and then fails with 401 forever, because your config file is on the persistent volume and survives the rotation. If your agent cannot read env vars from its config, have the entrypoint rewrite that file from $PUSK_MANAGED_TOKEN on every boot, unconditionally. Write-if-absent is the bug.

Budgets

Both endpoints draw on the instance budget and then the workspace wallet. LLM calls are metered at provider cost with no markup; Composio tool calls at $0.000114 each. Refusals are a 402:

{ "error": { "type": "instance_budget_exhausted", "message": "..." } }

insufficient_balance is the other type, meaning the workspace wallet is empty rather than the instance cap being reached.

Warning

An instance created without a budget has a cap of zero, and every managed call returns 402. Pass one at create, or PATCH /v1/instances/{id}/budget with monthly_cap_micros. Agents often surface this badly: a 402 from the model endpoint can look like a hang rather than an error. If a new instance's agent does nothing, read GET /v1/instances/{id}/budget first.

Filesystem rules for a custom image

/home/node and /home/linuxbrew are persistent volumes bind-mounted over your image at runtime. The volume is created empty, and nothing is ever copied out of the image into it, so anything your image writes under those paths is unreachable from the running container.

This catches people twice, because npm's default global prefix is under /home/node:

RUN npm install -g my-agent                        # vanishes at boot
RUN NPM_CONFIG_PREFIX=/usr/local npm install -g my-agent   # survives

Put binaries in /usr/local, and put config your agent reads at startup somewhere outside /home too, or have the entrypoint write it into the volume on every boot. Files your agent creates at runtime under /home/node persist normally across restarts and image updates.

Not using them

Nothing obliges your image to touch either endpoint. To run on your own model, ignore the proxy variables and point your agent at your own provider, as in Build a custom image. To skip integrations, leave the MCP server out of your agent's config. Unused endpoints are never billed, because billing is per call.