v0.9 · preview
Concepts

Operators

An operator is a named AI worker that runs one operation end to end: it senses a real system through connectors, reasons with a base model, and proposes action as a validated plan. The operator is the unit you install, name, and hold accountable, and it is the one thing in Fibric that is allowed to want something. It is never the thing that does it.

What an operator is

Fibric is a vertical-agnostic kernel that runs named AI operators. An operator is not a chatbot and not a script. It is a long-lived worker with a goal stated in plain language, a fixed set of capabilities it is permitted to use, and the three-phase shape of the loop built in. Point an operator at the systems you run and it watches them, works out what is happening, and proposes what to do about it.

One sentence is worth holding onto: the operator proposes, it never disposes. Everything an operator decides is a suggestion until a deterministic executor validates it against your policy and runs what survives. That separation is what lets an agentic model touch a building, an order book, or a warehouse floor without ever going off the rails. The operator is the reasoning; the executor is the brake.

Operators are themselves integrations. The kernel hardcodes none of them. You install operators from the Marketplace or define your own with the SDK, and either way they run on the same governed runtime, under the same trust policies, leaving the same receipts.

i
Operator vs connector vs capability

An operator is the worker that reasons and proposes. A connector is how Fibric reaches a real system. A capability is a named thing a connector can do, like orders.hold. Operators request capabilities; configuration binds each capability to a connector. The operator never learns the vendor's name.

Anatomy of an operator

Every operator is defined by a small, declarative shape. The kernel reads it to know what the operator is allowed to sense, what it may propose, and how often it runs. Nothing here is code the model can rewrite at runtime, it is the fixed frame the reasoning happens inside.

name
A stable identifier, like ship-risk or comfort. It prefixes idempotency keys and shows up on every receipt, so you can always trace an action back to the operator that proposed it.
goal
The operation in plain language. This is the standing instruction the base model reasons against, not a one-off prompt.
requires
The capabilities the operator may read and act through, by intent: orders.read, orders.hold. The operator can never reach anything outside this set.
model
The base model that does the reasoning, resolved through the model-router seam so it can be swapped without touching the operator.
trigger
What wakes the operator: a schedule, an inbound observation envelope, or an explicit ask from a person.
tenant
Every run carries reseller_id and tenant_id. An operator only ever sees one tenant's data, and its proposals can only touch that tenant.
defineOperator
export default defineOperator({
  name: "ship-risk",
  goal: "Catch orders that will miss their promised ship date,
         and hold the ones a human should look at first.",
  requires: ["orders.read", "orders.hold", "notify.send"],
  model: "router:reasoning",   // resolved by the model-router seam
  trigger: { every: "15m" },   // or { on: "observation" } / { on: "ask" }

  async run(ctx) {
    const open = await ctx.sense("orders.read", { status: "open" });
    const plan = await ctx.reason(open);   // model proposes; returns an ExecutionPlan
    return plan;                       // the executor disposes. the operator stops here.
  },
});
!
The run returns a plan, never a side effect

Notice the operator's run hands back an ExecutionPlan and stops. It does not call the order system. It cannot. The only way out to a real system is through the executor, which is the subject of Governance & trust.

The lifecycle of a run

One run of an operator moves through the same phases every time, whatever the system it is pointed at. The first three are the operator's own loop. The last three belong to the executor, the operator has already handed off by then, but they are the part that makes the loop safe, so they are shown here in full.

  1. Sense Gather the real picture

    The trigger fires. The operator reads through its requires capabilities, pulling live state from each connected system into one normalized view. It only ever sees real, tenant-scoped data, never seed or mock values, and anything that cannot be verified as real is tagged so it can never be mistaken for a governed signal.

  2. Reason Work out what matters

    The base model reads what was sensed against the operator's goal. It understands the situation, predicts what is coming, and plans multi-step action, resolving conflicts up front. Its output is not an action. It is a draft of intent.

  3. Propose Emit a validated plan

    The reasoning is shaped into a validated ExecutionPlan: an ordered list of proposed actions, each one a capability plus arguments. The plan is checked for shape before it leaves the operator. The operator's job ends here.

  4. Dispose The executor takes over

    A deterministic executor validates the plan against your trust policy. Anything not explicitly allowed is refused, fail-closed. It enforces single-flight per entity and idempotency keys, so the same action cannot fire twice and two runs cannot touch the same entity at once.

  5. Act Run what survives

    The actions the policy allowed are dispatched through their connectors, in order. No new hardware, no special-casing, the same MCP interface whether the action is sending a message or unlocking a door.

  6. Receipt Account for every step

    Every action leaves an immutable receipt: what was proposed, which policy rule decided it, the idempotency key, and the outcome. The run is not complete until it can be accounted for. If you cannot explain it after the fact, it did not happen.

How an operator proposes a plan

The reason phase does not return free text and it does not return a function call to a live system. It returns an ExecutionPlan: a structured, validated object the executor knows how to read. Stating intent as a plan, rather than as actions, is what makes the propose-then-dispose split possible. The model can be as creative as it likes inside the plan; the executor still has the final say over every line of it.

ExecutionPlan (ship-risk)
{
  "operator": "ship-risk",
  "tenant_id": "t_8f2a…c901",
  "rationale": "SO-10884 is 2 days from its promise date with no
                 production scan. Hold for review before it ships late.",
  "actions": [
    {
      "capability": "orders.hold",
      "args": { "order": "SO-10884", "reason": "ship-risk-review" },
      "idempotency_key": "ship-risk:SO-10884:hold"
    },
    {
      "capability": "notify.send",
      "args": { "to": "ops-queue", "template": "ship-risk-hold" },
      "idempotency_key": "ship-risk:SO-10884:notify"
    }
  ]
}

Three things about this object are doing the real work. Each action names a capability, not a vendor, so the plan is independent of which order system is plugged in behind it. Each action carries an idempotency key built from the operator, the entity, and the action, so a retry collapses into the original rather than firing twice. And the whole plan carries a tenant, so it can only ever touch its own tenant's data. The rationale is for the receipt and for you, the executor decides on the actions, not on the prose.

!
A plan is a request, not a guarantee

An operator proposing orders.hold twice in the same run, or proposing an action it lacks the capability for, does not produce a runaway or an error in the world. The executor refuses what is not allowed and collapses the duplicate. The worst a bad proposal can do is be declined.

Three operators, three worlds

Fibric is vertical-agnostic on purpose. The same lifecycle, the same propose-then-dispose split, and the same receipts run whether the operator is watching a hotel, a paper company, or a warehouse. These three are proof of reach, not a menu, any system you operate can carry an operator.

comfort Hotel

Senses occupancy, room sensors, and the booking system. Reasons about which rooms turn over tonight and which guests have arrived, and proposes pre-cooling an arriving guest's room and dimming corridors that are empty. It never sets a thermostat directly, it proposes hvac.setpoint and the executor disposes, so a sensor glitch can never run the chillers away.

occupancy.readhvac.setpointlighting.scene
ship-risk Paper company

Senses open orders and production state across the order system and the plant feed. Reasons about which orders will miss their promised ship date, predicting the slip before a carrier scan ever would, and proposes holding the at-risk ones for a human and notifying the ops queue. The hold is a proposal; the executor places it under single-flight so two runs cannot both grab the same order.

orders.readorders.holdnotify.send
floor Warehouse

Senses the WMS, pick exceptions, and dock-door sensors. Reasons about which exceptions are blocking outbound flow and proposes re-slotting a pick, releasing a held wave, or paging a lead to a stuck lane. Every proposal is bounded by capability and policy, so the operator can clear exceptions all day and still never reach an action it was not granted.

wms.readwave.releasetask.assign

Read across the three and the shape is identical. Different connectors, different capabilities, different goals, one governed loop. Swapping the paper company's order system for a different vendor changes a binding, not the operator, because orders.hold is the contract and the connector behind it is configuration. That is the subject of Connectors & capabilities.

Installing and defining operators

There are two ways an operator lands in your workspace, and both run on the identical governed runtime.

Install from the Marketplace

Browse named operators, install one, and bind its required capabilities to the connectors you already have. Fibric is a free platform; operators and other add-ons are where paid capability lives. An installed operator is governed exactly like one you wrote, same policy, same single-flight, same receipts.

Define your own with the SDK

Use defineOperator to write the shape shown above: a name, a goal, the capabilities it requires, and a run that senses, reasons, and returns a plan. You never write the act step. The kernel disposes for you, which is the whole point, your operator carries the intelligence and the kernel carries the guarantees.

CLI
# install a named operator and bind its capabilities
fibric operators add ship-risk
fibric capabilities bind orders.read --connector magento
fibric capabilities bind orders.hold --connector magento

# or scaffold your own
fibric operators init floor --requires wms.read,wave.release,task.assign

Keep going