REF · STACKBONE / WIKI · v0.9.4 stackbone

Agent protocol

Every Stackbone agent must serve POST /invoke, GET /health and GET /schema.

Every Stackbone agent must serve these three HTTP endpoints. They are the contract between your container and the rest of the platform — they're what stackbone dev exercises locally and what the production runtime expects in the cloud. The contract is framework-agnostic: the official templates use Hono, but you can use Express, Fastify, plain Node, or any other server.

The full wire spec for the Studio API (the broader surface orchestrator + cloud serve, e.g. /api/runs, /api/contract) lives in docs/arquitectura/specs/stackbone-agent-protocol-v1.md. This page covers only the three endpoints the agent itself must implement.

Why these three?

Endpoint Purpose
/invoke The agent's main entrypoint — every external trigger (webhook, scheduled run, manual run, queue) lands here.
/health Liveness probe. The runtime waits on this during boot; if it never returns 200 the deploy is rolled back.
/schema Self-description: input, output, optional configSchema. The runtime uses it to validate inputs and Studio uses it to render forms and config editors.

Minimal agent

The hello-world template is the canonical reference:

import { serve } from '@hono/node-server';
import { Hono } from 'hono';

const app = new Hono();

app.post('/invoke', async (c) => {
  const body = await c.req.json().catch(() => ({}));
  return c.json({ hello: body.who ?? 'world' });
});

app.get('/health', (c) => c.json({ status: 'ok' }));

app.get('/schema', (c) =>
  c.json({
    input: { type: 'object', properties: { who: { type: 'string' } } },
    output: { type: 'object', properties: { hello: { type: 'string' } } },
  }),
);

const port = Number(process.env.PORT ?? 8080);
serve({ fetch: app.fetch, port });

POST /invoke

The agent's main entrypoint.

  • Method: POST.
  • Content-Type: application/json for synchronous results, or text/event-stream for SSE streams (see below).
  • Body: any JSON shape — your agent decides. Validate against your /schema input.
  • Response: any JSON shape — again, your agent decides. Match it to your /schema output.

Errors should use HTTP status codes. The Studio UI surfaces both the status and the body, so a 4xx with a structured body is more useful to the operator than a 200 with { "error": "..." }.

Streaming (SSE)

For long-running invocations (LLM streams, multi-step reasoning), respond with SSE:

app.post('/invoke', (c) => {
  return streamSSE(c, async (stream) => {
    await stream.writeSSE({ event: 'token', data: '...' });
    await stream.writeSSE({ event: 'done', data: '...' });
  });
});

TODO — formal event taxonomy (token, tool_call, tool_result, done, error) once Epic 5 (official streaming templates) lands.

Auth

In production the runtime ensures only the platform can reach /invoke (network policy + JWT). The emulator does not enforce auth — anything with the agent port can hit it. Don't expose the emulator's agent port publicly.

GET /health

Liveness probe. Return 200 { "status": "ok" } (any extra fields are fine; the runtime only checks the status code).

app.get('/health', (c) => c.json({ status: 'ok' }));

The runtime polls /health during startup and during steady-state operation. If the probe fails for too long the container is recycled.

TODO — formal definition of the failure window (timeout, retries, recycling threshold) lands with Epic 5 of the runtime component.

GET /schema

Self-description used by the runtime (input validation) and by Studio (form rendering). Minimum shape:

{
  "input": {
    /* JSON Schema for /invoke request body */
  },
  "output": {
    /* JSON Schema for /invoke response body */
  },
}

Optional fields:

  • configSchema — JSON Schema for the value Studio's "Config dinámica" panel writes into AGENT_CONFIG. When present, Studio renders a typed form. When absent, free-form JSON is accepted. See docs/arquitectura/componentes/11-stackbone-studio.md for how configSchema flows through the platform.
{
  "input": {
    "type": "object",
    "required": ["goal"],
    "properties": { "goal": { "type": "string" } },
  },
  "output": { "type": "object", "properties": { "answer": { "type": "string" } } },
  "configSchema": {
    "type": "object",
    "properties": {
      "tone": { "type": "string", "enum": ["formal", "casual"] },
      "maxIterations": { "type": "integer", "minimum": 1, "maximum": 20 },
    },
  },
}

Optional endpoints

Endpoint Purpose Status
POST /checkpoint/save, /checkpoint/load LangGraph-style checkpointing for resumable workflows. The runtime persists the blob between calls. TODO (V1)
Webhook receivers (e.g. POST /approvals/...) Verify HMAC-signed callbacks from the platform (HITL approvals, queue jobs, …). Owned by the agent code, not the platform. Stable (in @stackbone/sdk)

Port and binding

The runtime injects PORT into the container. Bind your server to it:

const port = Number(process.env.PORT ?? 8080);
serve({ fetch: app.fetch, port });

Bind to 0.0.0.0 inside the container — the platform's network policy controls which traffic can actually reach it. Locally, stackbone dev passes the agent process the auto-picked port and proxies /invoke through the Studio API at http://127.0.0.1:4242.