Skip to content

Capability tools

Some things an agent wants to do are heavy: turning a Markdown memory node into a polished PDF needs a headless browser, bundled fonts, and the RAM spikes that come with them. Baking that into the core server would bloat the image, drag the API process into browser-lifecycle management, and couple a privacy-sensitive front door to a noisy dependency.

Hadron keeps the core lean instead. hadron-server stays the front door — identity, access control, decryption, node loading — and delegates the heavy work to capability tools: small, stateless, independently-deployed services that each own one provider-specific concern and expose a narrow HTTP surface.

The model

A capability tool is a separate process (its own container) that implements a pure function over its HTTP inputs:

  • No database, no keys, no access-control logic. Everything that needs identity, secrets, or the graph stays in hadron-server. The tool only ever sees content that the server has already authorized and decrypted.
  • Stateless. Give it the same input, get the same output. Nothing to migrate, nothing to back up, trivial to scale horizontally or restart.
  • Narrow surface. A handful of endpoints that do one job — "turn this document into that document" — plus health/info.

This is the same isolation boundary described in Entity architecture, pushed one layer out: the core owns the what and who; a capability tool owns a heavy how.

Two flavors, one boundary

The shape above — a stateless function tool — fits pure transformations like the PDF service. A second flavor has since joined it: the provider-connection tool, which owns a standing relationship with an external provider — a Microsoft Exchange mailbox (hadrontool-ms-exchange), a Telegram bot (hadrontool-telegram), a Slack workspace (hadrontool-slack). These tools are stateful: each has its own small database holding the provider credentials, encrypted under the tool's own key (never the core's), plus whatever per-connection bookkeeping the provider's delivery semantics demand. And they have a second plane: besides answering the core's operation calls, they push normalized provider events back to an internal ingress on the core (an inbound email, a chat message, a slash command).

What both flavors share is the load-bearing rule — the tool is Hadron-blind:

  • it holds no platform data and reads no platform storage;
  • it calls no platform API — any principal a tool could hold would be standing ambient authority;
  • exactly two channels connect the sides: core → tool operation calls (already authorized), and tool → core normalized events;
  • when an operation needs platform context, the core passes it in the request — the tool never asks;
  • authorization always runs in the core, before the tool is called.

So the stateless flavor's "no database, no keys" is really a special case: a provider-connection tool has a database and keys, but they are its own — provider-side state, never platform state. Which platform user a chat sender maps to, which memory a message lands in, who may operate a mailbox — all of that is resolved in the core, every time.

A tool that dials a user-supplied URL (a webfetch conduit, a Home Assistant bridge) inherits one more duty at this boundary: because it sits inside the private network, it must guard its own egress against SSRF before it connects. See Harden a capability tool's outbound requests.

flowchart TD
    A[Agent / Portal] -->|GraphQL| S[hadron-server<br/>auth · ACL · decryption · node loading]
    S -->|HTTP · Bearer token| T[Capability tool<br/>stateless · no DB · no keys]
    T -->|result| S
    S -->|response| A

Why split it out

  • Keep the core image small. Chromium and its fonts never enter the API container.
  • Contain the blast radius. A renderer crash, a memory spike, or a zombie browser process stays in the tool, not the front door.
  • Open by design. A capability isn't hard-wired into the server. An agent that needs "render this to PDF" picks a tool advertising that capability, the server routes the request, and permission is checked at the server — more than one tool may offer the same capability. The protocol for how a tool advertises what it can do, and how the server routes to it, is still being worked out; the first tool doubles as the place that protocol gets refined.

The first tool: the PDF service

hadrontool-pdf is the first capability tool on the platform. It converts Markdown → PDF (via marked + Puppeteer/Chromium) and extracts PDF → Markdown text (via a PDF text-layer reader). It holds no database, no keys, and no access-control logic — exactly the shape above.

It is deployed internal-only: hadron-server reaches it by service name inside the private network, and every request carries a shared bearer token. It is not exposed to the public internet, so you won't call it directly — but its HTTP contract is documented for operators and for the server code that does call it. See PDF service HTTP API.

A worked example — the Portal's Download → PDF on a node:

sequenceDiagram
    participant U as Portal (user)
    participant S as hadron-server
    participant T as hadrontool-pdf
    U->>S: Download node as PDF (GraphQL)
    S->>S: access control + decryption + load node → Markdown
    S->>T: POST /convert/markdown-to-pdf (Bearer token)
    T-->>S: PDF bytes (base64)
    S-->>U: triggers browser download

The privacy invariant holds throughout: node plaintext only reaches the tool after hadron-server has done auth and decryption. The tool renders; it never decides who may read what.

More tools, same shape

The PDF service was the first; the pattern has since repeated. The most recent is hadrontool-mcp, a stateless conduit that lets headless runs call tools on external MCP servers — the core owns the registry and the encrypted auth, the conduit only speaks the protocol. It's the same boundary as the PDF service, applied to a different heavy job. See Call external MCP tools from a flow.

A stateful exception: durable watches

The "give it the same input, get the same output" rule describes the stateless request surface — a single fetch or render, in and out. But a tool can also own a narrow stateful plane without becoming a provider-connection tool. hadrontool-webfetch does: alongside its stateless fetch endpoints, it runs the page-monitor polling plane — durable watch jobs that re-fetch a URL on a schedule and deliver an event back into the core when a condition trips (see Monitor a web page).

That job state is the tool's own scheduling bookkeeping, never platform data: the Hadron-blind boundary holds unchanged. The core still owns identity, the credential, and the run that a triggered watch mints; the tool only knows "re-fetch this URL, evaluate these conditions, POST an event." So the stateless function is the common case, not an invariant — what never bends is the boundary, not the absence of state.

Transport

Today capability tools speak HTTP, with the server and tools co-located in the same private network. A platform message bus (NATS) is the planned transport: when it lands, the same pure render/extract functions can be wrapped behind a request/reply subject without touching the rendering core.

What's next

  • Add a capability tool — the recipe for building the next one: the stateless service shape, the thin server-side client with typed errors, and the new-tool checklist.
  • PDF service HTTP API — the /convert endpoints, auth, request/response shapes.
  • Entity architecture — the entity hierarchy the core front door is built around.