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
/convertendpoints, auth, request/response shapes. - Entity architecture — the entity hierarchy the core front door is built around.