Fabric
Agents that retry after failure.
Agents discover capabilities, delegate work, and receive uncommitted tasks again after failure. Contracts carry deadlines. Workflows carry budgets and compensation.
Built for
Multi-agent systems, durable pipelines, approvals
How it works
Two ends of one system.
Spawning a handler. Agent::builder() (Python: spawn_agent(..) as one flat call) starts an agent that listens on one topic and replies on another:
id(..)names it.listen_on(..)/respond_on(..)wire the topics.capabilities([...])advertises what it can do, so other agents discover it without knowing its identity in advance.handler(..)supplies the logic - a struct in Rust, a plain object in TypeScript, a function in Python.build().spawn(laser)starts it.ready()waits until it's actually consuming.
Addressing work. laser.contract(..) is the calling side: address work by capability, not by agent identity. A router or capability selector resolves to some agent currently advertising that skill, so a caller never hardcodes which specific instance handles a ticket.
deadline(duration)bounds how long the caller waits.- The outcome is one of four cases: Completed, Failed, NotConsumed (nothing picked it up), or TimedOut (picked up, never replied in time). "This didn't happen in time" is a normal outcome to handle, not an exception path.
Reliability. The runtime journals work on the log the same way any other primitive does:
- A handler that crashes before commit receives the task again and restarts its handler. Persist checkpoints explicitly when work must resume below handler granularity.
- Redelivery is deduplicated on the message id. External effects still need an idempotency key or a fenced write before the handler commits.
- A message that can't be handled routes to a dead-letter path instead of blocking the topic forever.
bootstrap(partitions) must run once per stream before any agent joins it - it creates the well-known agent topics a consumer group needs to exist first.
Governance, the AGDX contract, and the A2A/MCP/AG-UI bridges are supporting mechanics for this same runtime, not separate primitives:
- Governance - roles and pre-action policy checks, deny by default.
- AGDX - the portable contract for every agent record and its log semantics.
- Interop - reach the same agents over A2A, MCP, and AG-UI.
Fabric runs on every VSR-enabled Iggy target. Core agent handling does not require laser-plane.
Quick example
await using triage = Agent.builder()
.id(AgentId.new("triage"))
.listenOn(AgentTopic.Commands)
.respondOn(AgentTopic.Responses)
.capabilities([{ skillId: "resolve-ticket" }])
.ackOnPickup()
.handler({
handle: (_message, context) => context.respond(utf8("on it"))
})
.spawn(laser)
await triage.ready()
const contract = await laser
.contract(routeToCapable("resolve-ticket", ANY_ROUTE_POLICY))
.from(AgentId.new("orchestrator"))
.payload(utf8("ticket #42 is stuck"))
.inboxRoute({ kind: "fixed", topic: AgentTopic.Commands })
.deadline(60_000)
.send()
if (contract.kind === "completed") {
console.log(decodeUtf8(agentMessageBody(contract.reply)))
}struct Triage;
impl AgentHandler for Triage {
async fn handle(
&self,
_message: &AgentMessage,
ctx: &AgentCtx<'_>,
) -> Result<(), LaserError> {
ctx.respond("on it").await
}
}
let mut triage = Agent::builder()
.id("triage".parse()?)
.listen_on(AgentTopic::Commands)
.respond_on(AgentTopic::Responses)
.capabilities(vec![CapabilityDescriptor {
skill_id: "resolve-ticket".to_owned(),
..Default::default()
}])
.ack_on_pickup(true)
.handler(Triage)
.build()
.spawn(laser.clone());
triage.ready().await?;
let outcome = laser
.contract(Router::to_capable("resolve-ticket", RoutePolicy::Any))
.from("orchestrator".parse()?)
.payload("ticket #42 is stuck")
.inbox_route(InboxRoute::Fixed(AgentTopic::Commands))
.deadline(Duration::from_secs(60))
.send()
.await?;
match outcome {
Contract::Completed(reply) => {
println!("{}", String::from_utf8_lossy(reply.body()));
}
other => println!("contract ended: {other:?}"),
}async def handle(ctx, message):
await ctx.respond(b"on it")
triage = laser.spawn_agent(
"triage",
COMMANDS,
handle,
respond_on=RESPONSES,
capabilities=["resolve-ticket"],
ack_on_pickup=True,
)
await triage.ready()
reply = await laser.contract(
"resolve-ticket",
b"ticket #42 is stuck",
source="orchestrator",
fixed_inbox=COMMANDS,
deadline_ms=60_000,
)
print(reply.decode() if reply else "<no reply>")All three clients advertise the capability and acknowledge pickup. A crash before commit causes a retry. Rust and TypeScript use builders. Python uses direct calls and returns reply bytes.
Full runnable example: Rust · Python · TypeScript
The full multi-agent deep dive - discovery, workflows, quarantine, deadline recovery: orchestra
Key operations
| Verb | What it does |
|---|---|
bootstrap(partitions) | Create the well-known agent topics on a stream once |
Agent::builder()...spawn(laser) | Define and start a handler agent |
ready() | Wait until the agent is actually consuming |
contract(router_or_capability) | Address work by capability |
deadline(duration) | Bound how long the caller waits for a reply |
Completed / Failed / NotConsumed / TimedOut | The real outcome space |
Running it
Fabric runs on Laser Stack, LaserData Cloud, and standalone VSR-enabled Iggy. Managed dedup, fenced leases, and runs require advertised capabilities.