LaserData Cloud
Laser SDK

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

VerbWhat 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 / TimedOutThe 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.

On this page