The Roxabi Factory

An agent engine extended with a jobs layer — the choices that shape it, and the order we build them in.

Version 1.1 · Updated

01

An Engine, Extended

The Factory is not a new system. It is the agent engine that already runs the Roxabi bots — a hexagonal core (ports & adapters), a hub-and-spoke topology, a registry of agents, and a pool of workers dispatched over a message bus — extended with one new layer: jobs. That layer turns activity into content, posts it, and runs the loop around it.

The consequence is liberating. The core left to build is smaller than the first brain-dump suggested: the worker pool, the bus, the typed contracts, the scheduler — all already run in production. The rule of scope follows directly — every new capability is an adapter on the hexagon, never a new core.

The engine already exists. The Factory is a jobs layer on top of it — an extension, not a rewrite.

02

Value Before Framework

The naive plan builds the framework first: a generic core, then modules, then publishing, then routines. That is the classic trap — a general machine standing in front of its first unit of value. We invert it.

First a thin vertical slice, human-gated end to end: one signal of what was done → the worker generates one post → a human approves → it publishes. It reuses the bus and the worker pool; the harness is minimal; there is no framework yet. Then we measure reach. Only then do we generalise the harness and the watcher — and only over the pieces the slice proved necessary.

Human-in-the-loop is the default until reach — and reputation risk across two brands — is measured. A gate, not an afterthought.

03

A Worker Is Not a Tool

The single most clarifying decision in the design. The words « tool » and « worker » had drifted across four parallel design spaces; this is the line that resolves them.

Two roles, kept apart. A provider is the service that backs a tool — and when it is a self-hosted process we run on the bus with a heartbeat, it is a satellite. One provider exposes many tools: the image satellite exposes image.generate, image.list_engines, image.cancel. A worker is the other end — a compute instance that consumes tools to run a job. A worker is not a tool, and not a provider; it is what does the work.

And a worker does not improvise. It runs a workerEngine — a coded pipeline, a deterministic sequence — that calls three kinds of step: the harness (one pure agentic turn, where the model decides and its in-turn tools fire, with no workflow logic of its own), the tools, and plain internal code. The harness is a callee of the engine, not a flavour of it — agency where you want a decision, code where you want a guarantee.

  • Provider — the service that backs a tool. Transport-agnostic; one provider, many tools.
  • Satellite — a provider deployed as a self-hosted process on the bus, with a heartbeat. Every satellite is a provider; not every provider is a satellite.
  • Worker — a compute instance that consumes tools to run a job. Not a tool, not a provider.
  • workerEngine — the coded pipeline a worker runs; orchestrates harness, tools, and internal code.
  • Harness — one pure agentic turn; the model decides. A step the engine calls, never the pipeline itself.

To the model's reasoning loop, a remote image.generate and a built-in bash look identical — same shape, same result type. The transport difference is invisible.

Image Satellite a provider · heartbeat · one identity image.generate image.list_engines image.cancel 1 provider → N tools Registry heartbeat-discovered Harness pure agentic turn Worker runs a workerEngine consume to the model:  bash (built-in)  ≡  image.generate (remote) transport is invisible — one shape, one result type
A provider exposes tools — a satellite when it is self-hosted on the bus. Registered once, every tool is consumed identically by the harness and the workers — a remote call and a built-in are indistinguishable to the model.
04

Five Layers of Tool

Not every tool is atomic. The model has five layers, from a single primitive call up to a recursive sub-agent — and one pipeline dispatches them all.

  • L0a — Built-in. Compiled into the engine; in-process; always available. bash, file read/write, web fetch.
  • L0b — Remote. A capability satellite on the bus, discovered by heartbeat. image.generate, voice.tts.
  • L1 — Macro. A deterministic chain of primitives, with no second model in the loop.
  • L2 — Skill. An instruction-layer composite — a document that guides the model through a domain sequence.
  • L3 — Sub-agent. A nested, isolated agent call that returns a summary upward.

The decisive choice is a uniform dispatcher: one access-control gate, one result type, the transport varying underneath. Not a branched pipeline where built-ins and remote tools each grow their own rules — the discipline of one path is what keeps the engine readable in an afternoon.

COMPOSITION ↑ L3 — Sub-agent nested isolated agent · returns a summary upward L2 — Skill instruction-layer composite · guides the model L1 — Macro deterministic chain · no second model L0b — Remote atomic satellite on the bus · heartbeat-discovered L0a — Built-in atomic compiled in-process · always available UNIFORM DISPATCH
Five layers, one dispatcher. From a built-in primitive to a recursive sub-agent, every tool passes the same access-control gate and returns the same result type — only the transport changes.
05

Domains by Nature

The typed contracts that travel the bus had been listed flat, mixing two natures in one drawer. We separate them.

A domain is tool-nature when its only reason to exist is to expose a capability the model invokes — voice, image, and the capability satellites to come. Everything else is plumbing: the engine's own inference transport, the job bus, persistence, security, observability, delivery.

The sharp edge is a single rule:

tool-surface  ≠  tool-nature

  github plumbing      exposes a built-in tool   → stays plumbing
  inference substrate  exposes a generate call   → stays plumbing

A plumbing domain may incidentally expose a tool without becoming tool-nature. Surfacing a tool does not promote the wire domain. That rule is what keeps the separation honest instead of letting everything drift into « tool ».

A second rule draws the line inside « provider » — because not every provider runs on the bus:

provider  ⊋  satellite

  self-hosted process on the bus    →  satellite  (heartbeats — voice, image, our Postiz adapter)
  a cloud API / one-shot CLI we call →  provider   (no heartbeat — X, GitHub, a bash tool)

A provider is a satellite only when it is something we run on the bus that heartbeats; a cloud API we merely call, or a one-shot command-line tool, is a provider without a satellite's footprint. And one trap to avoid: a self-hosted backing service — a whole app like our Postiz fork (web, database, Redis) — is infrastructure, like Postgres. A running container is not a heartbeat; the heartbeat comes from our adapter in front of it. Backing service, provider, tool: three layers, never collapsed into one.

The payoff is an addressable plane. Tool-nature subjects carry a tool. marker — …tool.voice.tts… — so the consumer side subscribes to the entire tool plane at once, and discovers what is available through one registry.

Isolation is the point. Once « the tools » are a plane, the harness and the workers address and discover them as one.

06

The Roadmap

Where this goes, in order — substrate first, value next, framework last.

  • Substrate — in production: the bus, the typed contracts, the worker pool, the scheduler.
  • Thin slice — one signal → one post → approve → publish; measure reach. The keystone.
  • Generalise — the harness and the job-watcher, over what the slice proved necessary.
  • Tools — the shared registry, the tool SDK, the satellites migrated onto the five-layer model.
  • Harness & workers — the pure agentic harness and a fleet of workers running workerEngines, both consuming the tool plane.
Substrate in production Slice next · keystone Generalise harness · watcher Tools registry · SDK Harness + Workers Tool-system architecture: designed & locked. Implementation is sequenced after the in-flight bus-subject migration — design now, build clean after.
Substrate in production; the thin slice is the keystone that gates everything after it. The tool architecture is settled — its build is sequenced behind one in-flight migration so two reorganisations never collide.

The design is settled; the build is sequenced, not rushed. Primitives, not a framework.