Browse documentation

Start here

OverviewGetting startedThe mental model

Use the runtime

Run the simulatorPi Agent and workspaceInstall and manage AppsESP32-P4 reference targetESP32-S3 supported target

Build Apps

App developer guideBuild your first AppApp source and packageData and migrationsActions and ToolsView and interactionNetworking and native servicesApp resourcesSchedulesPackage and updateTesting and debugging

Understand the runtime

Runtime flowGuests and lifecycleLayers and ownershipHarness boundary

Security

Trust and capabilitiesData isolationLifecycle and recovery

Reference

App manifestPocketPi APIView APICLI referenceLimits and compatibility

Examples

Exa App walkthroughRobinhood App walkthrough

Project

Current boundariesValidation status

Networking and native services

App JavaScript owns provider mapping and domain semantics. The native host owns credential application, TLS, exact endpoint/operation policy, response bounds and the Action deadline.

HTTP: declare capability and exact policy

{
  "capabilities": ["data.sqlite", "net.http"],
  "nativeServices": {
    "http": [
      {
        "method": "POST",
        "urls": [
          "https://api.example.com/search",
          "https://api.example.com/contents"
        ],
        "allowedRequestHeaders": ["accept", "content-type"],
        "credential": {
          "id": "example.api-key",
          "header": "authorization",
          "prefix": "Bearer "
        }
      }
    ]
  }
}

The App declares where it may connect, which method it may use, which request headers its JavaScript may set and which native credential binding should be applied. The secret value is supplied only during first-install transport and is stored by the host.

Call with the installed fetch()

async function post(path, value) {
  const response = await fetch(`https://api.example.com/${path}`, {
    method: "POST",
    headers: {
      accept: "application/json",
      "content-type": "application/json",
    },
    body: JSON.stringify(value),
    timeoutMs: PocketPi.actionContext.remainingMs(),
    maxBytes: 96 * 1024,
  });

  const body = await response.json();
  if (!response.ok) {
    throw new Error(`Provider HTTP ${response.status}: ${JSON.stringify(body)}`);
  }
  return body;
}

The response provides status, url, frozen headers, ok, and async bytes(), arrayBuffer(), text() andjson(). Always set a product-appropriate maxBytes rather than relying on the default 128 KiB.

MCP: keep the connection native and the Tool policy App-owned

{
  "nativeServices": {
    "mcp": [
      {
        "connection": "portfolio",
        "url": "https://provider.example.com/mcp",
        "credential": {
          "id": "portfolio.oauth-token",
          "header": "authorization",
          "prefix": "Bearer "
        }
      }
    ]
  },
  "providerOperations": ["get_accounts", "get_portfolio"]
}
const value = PocketPi.services.call("mcp.client", "callTool", {
  connection: "portfolio",
  name: "get_portfolio",
  arguments: { account_number: account },
  retryable: true,
});

providerOperations is the native allowlist. App source still owns which public Tools exist, how arguments are validated, which upstream operation is selected, how the response maps into product Data, and what the Agent receives.

Batch only when the product needs it

const value = PocketPi.services.call("mcp.client", "callTools", {
  connection: "portfolio",
  calls: [
    { name: "get_accounts", arguments: {} },
    { name: "get_portfolio", arguments: { account_number: account } },
  ],
  retryable: true,
});

A batch shares the same Action deadline and native connection policy. It does not turn native code into the product workflow; JavaScript still decides the calls and consumes the results.

Credential file for first install

{
  "example.api-key": "secret value supplied out of band"
}
cargo xtask package app example path/to/credentials.json

The credential ids must exactly match the manifest. The Installer removes the file from the staged source and stores values natively. Update packages omit credentials and may not change native permission policy.

Rules that keep the boundary honest

  • Never put a secret in app.json, an asset, SQLite, Tool arguments or Agent workspace.
  • Use exact URLs and operations; do not declare a broad proxy and rebuild authorization in JavaScript.
  • Validate domain arguments before crossing the native service boundary.
  • Return useful live results to the Agent; persist only bounded View/product state.
  • Treat ambiguous real-world side effects as unknown, not as safe to retry with a new id.