Search

Search the documentation

Protobox
Use cases

Automations

Run tools from workflows and background jobs - stateless execution with the same credentials, policies, and traces as your agents - and react to platform events with standing instructions.

Scenario: Not everything happens inside a conversation. Before a support chat starts, Acme enriches the customer from HubSpot. After a call ends, a summary email goes out and the CRM gets updated - from a queue worker, minutes later. Same tools, same per-user credentials, no agent in the loop.

Stateless execution

actions.execute runs one action by slug - no session, no conversation:

const result = await protobox.actions.execute('HUBSPOT_SEARCHCONTACTS', {
  actorId: 'user_123',                      // whose credential to use
  arguments: { query: 'initech.com' },
});

if (result.status === 'completed') {
  console.log(result.data);
} else if (result.status === 'pending_approval') {
  // a toolset policy gated it - see Human-in-the-loop
} else {
  console.error(result.error?.code, result.error?.message); // e.g. 'not_connected'
}

Every execute lands in the same execution log as agent calls - one audit trail for the whole product.

Find the action and its schema

const actions = await protobox.actions.search({ connectorSlug: 'hubspot', query: 'contact' });
const schema = await protobox.actions.getSchema('HUBSPOT_SEARCHCONTACTS');
// schema.inputSchema - validate your job payloads against it in CI

Dry-run from CI

dryRun: true validates arguments and resolves the credential without calling the provider - perfect for smoke-testing a workflow deploy:

const check = await protobox.actions.execute('ACME_CREATETICKET', {
  actorId: 'user_123',
  arguments: { subject: 'smoke' },
  dryRun: true,
});

Relay your own context

context.forward is carried verbatim as an X-Protobox-Context header to connectors that opted in (forwardContext: true at registration) - so your API can correlate tool calls to your conversations without Protobox parsing anything:

await protobox.actions.execute('ACME_CREATETICKET', {
  actorId: 'user_123',
  arguments: { subject: 'Renewal follow-up' },
  context: { forward: JSON.stringify({ conversationId: 'c_991' }) },
});

The three moments

MomentPattern
Before an interactionEnrich at boot: a few read-only actions.execute calls, results into your prompt/context
DuringThe agent path - sessions or adapters
AfterQueue worker: write-backs, follow-up email, ticket updates - gated by approvals where they're risky

Reacting to events

The platform pushes its own events to your webhook - connection lifecycle and execution outcomes (reference). The high-leverage automation pair:

switch (event.type) {
  case 'connection.activated':
    // kick off a first-sync job for this user's newly connected app
    await enqueueFirstSync(event.data.callerId, event.data.integrationId);
    break;
  case 'execution.failed':
    // alert on failures from unattended jobs
    await notifyOncall(event.data.executionId);
    break;
}

Standing instructions with automations

protobox.automations (alias: protobox.triggers - same instance) is a persisted "when <event> happens, run <tool|skill>" rule. Every run goes through the SAME quota, permission, policy, and approval gates a live call gets - an automation that would need approval pauses into your Approvals inbox exactly like an agent call would.

const automation = await protobox.automations.create({
  name: 'New GitHub issue -> Slack summary',
  trigger: { kind: 'webhook', source: 'github', eventType: 'issues.opened' },
  action: {
    kind: 'tool',
    name: 'slack_send_message',
    argsTemplate: { channel: 'C0123', text: 'New issue: {{event.payload.title}}' },
  },
  budget: { maxRunsPerDay: 50 },
});

// webhook triggers get a signing secret, shown ONLY here - send events to
// POST /api/v1/automations/inbound/<automation.id>, HMAC-signed with it
// (X-Automation-Signature: t=<unix>,v1=<hex>, same scheme as the platform's
// own outbound webhooks - see webhooks.verify).
console.log(automation.secret);

Three trigger kinds:

KindFires whenExample
webhookA signed event lands on POST /automations/inbound/:id matching source/eventTypeA GitHub/Zapier/your-own-backend event
scheduleA 5-field cron expression matches the current UTC minute{ cron: '0 9 * * *' } - daily at 9am
knowledge.updated / memory.storedA document is added/updated, or a memory is stored, in this workspaceRe-summarize a KB doc on every edit

Three action kinds. A tool action renders argsTemplate's {{event.x}} tokens from the trigger event and executes the tool directly; a skill action renders inputTemplate the same way, resolves the named skill's prompt content with those variables, stores the rendered text as the run's output, and sends you an in-app notification; an agent action takes the only real LLM turn of the three.

const runs = await protobox.automations.runs(automation.id);
// [{ id, status: 'done' | 'pending_approval' | 'failed' | 'budget_stopped', executionId, output, ... }]

budget.maxRunsPerDay caps runs per UTC calendar day - a run created at/over the cap is recorded with status: 'budget_stopped' and never reaches the executor, so a misconfigured or noisy trigger can't spend past the cap.

Headless agent turns

{ kind: 'agent', instructions, toolsetId?, maxSteps?, budget: { maxCentsPerDay } } gives the event to a bounded model turn instead of a fixed tool call - Protobox decides what to do, not you:

const triage = await protobox.automations.create({
  name: 'Triage new tickets',
  trigger: { kind: 'webhook', source: 'zendesk', eventType: 'ticket.created' },
  action: {
    kind: 'agent',
    instructions: 'A support ticket just came in: "{{event.payload.subject}}". Look up the customer and post a triage summary to Slack.',
    toolsetId: 'ts_support',   // the tools this turn may call - same approval gates as the toolset's tool/skill actions
    maxSteps: 4,               // hard-capped at 5
    budget: { maxCentsPerDay: 100 },
  },
  budget: { maxRunsPerDay: 100 },
});
  • Every tool the model calls goes through the same ToolExecutorService.execute(..., 'automation') path a tool action gets - quota, permission, policy, and approval all apply. A toolset approve policy pauses the RUN into pending_approval the instant the gated call happens; the turn takes no further steps.
  • The model comes from your workspace's engine policy, same as memory and knowledge: a BYO OpenAI key (Integrations) runs on any tier; without one, paid tiers run on a chanl-managed key and free-tier runs are refused - the run is recorded status: 'failed' with an UPGRADE_REQUIRED error, never silently skipped.
  • action.budget.maxCentsPerDay is a separate cap from budget.maxRunsPerDay - it bounds this action's real provider spend specifically. Once today's spend for the automation already meets the cap, the next run is recorded budget_stopped before the model is ever called.
  • The run's transcript ({ steps, toolCalls, finalText }) and costCents record what happened - check run.transcript.toolCalls to see exactly what the model did.
const [run] = await protobox.automations.runs(triage.id);
console.log(run.transcript?.finalText, run.costCents);

Production notes

  • Retries are yours. The SDK makes exactly one attempt per call - no built-in retry or backoff. Run executes from a queue with your retry policy; on rate_limited errors, honor error.retryAfter.
  • Idempotency: idempotencyKey is accepted on execute but not enforced yet (it arrives with a later phase). Until then, make write actions safe to repeat at your layer - dedupe by your job id before enqueueing.
  • Multi-account actors: pass accountKey alongside actorId to pick which of the user's accounts the job runs as.

On this page