fabric/invoke

Serve an agent or graph run as a bounded request and response. No transport is assumed: an HTTP handler, MCP handler or job can use the same service. agent and graph bind execution; request holds caller-owned input. Configuration is opaque and waits default to 25 s.

A key is scoped by service, runtime family and principal. Retries with the same input reopen that run even when their correlation changes; a changed input is key_reused. The first request’s correlation stays with the run. Use a stable authenticated principal in a shared service. Without a key each request starts a fresh run; a deadline or the supplied cancellation selector cancels work still in flight. Keyed work outlives a disconnect, bounded by the agent or graph’s limits. Approval and reconciliation return immediately with the run id: the application owns following them up, including unkeyed requests.

let service = invoke.agent("desk", runs, desk)
let request = invoke.request(context, "Help with order 42")
  |> invoke.with_principal(subject)
  |> invoke.with_key(Some(request_key))
  |> invoke.with_cancelled(disconnected)
let response = invoke.call(service, request)
case invoke.answer(response) {
  Some(answer) -> respond(answer)
  None -> pending(invoke.id(response), invoke.describe_response(response))
}

An unavailable admission returns OutcomeUnknown with the attempted run id. Retry with the same principal and key, or recover that run explicitly; do not interpret an unconfirmed start as refusal to create a run.

A cancellation failure is reported explicitly; it never claims the run stopped. Read or recover such a run by id. An unknown effect needs reconciliation, not a retry of the effect. Cancelled and expired runs retain unresolved effect evidence in OutcomeUnknown, including effects owned by a child. A completion committed before cancellation retains its native answer.

Serving through Relay

Copy the module relay_serve of consumers/relay_tools into the application; that consumer compiles and tests it. relay_serve.serve checks the service with invoke.check and serves a Relay tool call through it. The call’s idempotency key, correlation and cancellation reach the request, and the response’s run id travels in the result metadata under io.github.gleam-dream/run-id. An answer completes the call; a response without one is a tool error that carries the response details. See the README and the design layer for composition and failure handling.

Reading a Relay run id

Copy the module relay_run of consumers/relay_tools into the application; that consumer compiles and tests it. relay_run.run_of reads the run id from a client tool result’s metadata and returns None when the entry is absent or is not a valid run id. See the README and the design layer for composition and failure handling.

Types

A finite wait must fit the BEAM timer range. Invocation deliberately requires a deadline; continuing keyed work has its own runtime bounds.

pub type ConfigError {
  InvalidWait(value: Int, minimum: Int, maximum: Int)
}

Constructors

  • InvalidWait(value: Int, minimum: Int, maximum: Int)

Stable next-action classification. Detailed machine codes may grow.

pub type Kind {
  Answered
  Working
  AwaitingApproval
  AwaitingInput
  OutcomeUnknown
  Unattended
  Ended
  Refused
}

Constructors

  • Answered
  • Working
  • AwaitingApproval
  • AwaitingInput
  • OutcomeUnknown
  • Unattended
  • Ended
  • Refused

Caller-owned input and identity. Context and cancellation are never stored.

pub opaque type Request(context, input)

A response always names the attempted run, including start failures. facts is a portable JSON object for rendering structured failures; native successful answers are available through answer.

pub opaque type Response(answer)

Execution bound to a named request boundary. Names must be stable and distinct for services sharing a store. A graph already owns its context.

pub opaque type Service(context, input, answer)

Values

pub fn agent(
  name: String,
  runs: store.Store,
  agent: agent.Agent(c, a),
) -> Service(c, String, a)

Bind an agent; its prompt is the request input and its context is live.

pub fn agent_with_history(
  name: String,
  runs: store.Store,
  agent: agent.Agent(c, a),
) -> Service(c, input.Input, a)

Bind an agent to validated original history and its current prompt. Keyed retries compare the complete original input and reopen the same run; waiting, cancellation and typed answers follow agent unchanged.

pub fn answer(response: Response(a)) -> option.Option(a)
pub fn call(
  service: Service(c, i, a),
  request: Request(c, i),
) -> Response(a)

Start or reopen, then wait once. Invalid configuration starts nothing.

pub fn check(
  service: Service(c, i, a),
) -> Result(Nil, ConfigError)

Validate configuration at application startup, before accepting calls.

pub fn code(response: Response(a)) -> String
pub fn describe_config_error(error: ConfigError) -> String
pub fn describe_response(response: Response(a)) -> String
pub fn details(response: Response(a)) -> value.Value

A portable object with error, run_id and any approval or uncertain effect evidence. Successful answers should be rendered from answer.

pub fn graph(
  name: String,
  runtime: graph.Runtime(c, s, a),
) -> Service(Nil, s, a)

Bind a graph. Its runtime owns context; pass Nil as request context and the native initial state as input. Retries compare the original state, even after the graph has changed it.

pub fn id(response: Response(a)) -> run.RunId
pub fn keyed_id(
  service: Service(c, i, a),
  principal: String,
  key: String,
) -> run.RunId

A collision-resistant, unambiguous identity for this service, principal and key. JSON frames the parts before hashing; separators inside any part cannot alias another request. This differs from the old MCP bridge derivation; previously written runs remain accessible by their old id.

pub fn request(
  context: context,
  input: input,
) -> Request(context, input)

Build a request for one trusted anonymous caller. Shared services must set the authenticated principal before accepting an idempotency key.

pub fn response_kind(response: Response(a)) -> Kind
pub fn with_cancelled(
  request: Request(c, i),
  cancelled: process.Selector(Nil),
) -> Request(c, i)

The caller supplies its disconnect or monitor selector. Consumed only for an unkeyed request; keyed requests outlive the caller.

pub fn with_correlation(
  request: Request(c, i),
  correlation: correlation.Correlation,
) -> Request(c, i)
pub fn with_key(
  request: Request(c, i),
  key: option.Option(String),
) -> Request(c, i)
pub fn with_principal(
  request: Request(c, i),
  principal: String,
) -> Request(c, i)

Scopes execution identity; this does not authenticate the caller.

pub fn with_wait(
  service: Service(c, i, a),
  wait: duration.Duration,
) -> Service(c, i, a)
✨ Search Document