Skip to main content

Trampoline

Struct Trampoline 

Source
pub struct Trampoline { /* private fields */ }
Expand description

Dispatches registered local calls until execution reaches a scheduler boundary.

The trampoline is deliberately small. It knows how to call named Rust functions and how to recognize workflow steps that require an external runtime. It does not own persistence, provider credentials, or scheduling policy.

Implementations§

Source§

impl Trampoline

Source

pub fn set_observability_config(&mut self, config: ObservabilityConfig)

Set event summarization and validation configuration.

Source

pub async fn run( &self, workflow: Workflow, ) -> Result<WorkflowOutcome, WorkflowError>

Run local workflow steps until the workflow halts or suspends.

Registered calls execute asynchronously in the current task. When a call chooses an Anthropic, human, OpenAI, or fork/join continuation, the trampoline returns the corresponding WorkflowResult instead of performing the external work itself.

§Errors

Returns missing-function if a Call step names a function that has not been registered. Also returns any handled::SError produced by a registered call.

§Examples
use langcontinuation::{CallFuture, Trampoline, Workflow, WorkflowResult};

#[tokio::main]
async fn main() -> Result<(), handled::SError> {
    let mut trampoline = Trampoline::default();
    trampoline.register("mark", |workflow| -> CallFuture<'_> {
        Box::pin(async move {
            workflow.into_env("done", true).unwrap();
            Ok(())
        })
    });

    let result = trampoline.run(Workflow::new("run", "mark")).await?.result;
    let WorkflowResult::Halt { workflow } = result else {
        panic!("workflow should halt");
    };
    assert_eq!(workflow.from_env::<bool>("done").unwrap(), Some(true));
    Ok(())
}
Source

pub fn next_action(&self, workflow: &Workflow) -> WorkflowNext

Return the next sanitized action the trampoline would take.

Source

pub async fn run_one_local_call( &self, workflow: Workflow, ) -> Result<WorkflowStepOutcome, WorkflowError>

Execute exactly one local call.

§Errors

Returns WorkflowError if the workflow is not currently at a local call, if the function is not registered, or if the function itself returns an error.

Source

pub fn resume_anthropic( &self, workflow: Workflow, output_key: impl Into<String>, message: Message, ) -> Result<Workflow, SError>

Store an Anthropic response and advance the suspended workflow.

The workflow must be paused at the Anthropic step that requested output_key. The message is serialized into the environment and the workflow advances to the continuation that was waiting behind the provider step.

§Errors

Returns not-suspended-at-anthropic if the workflow is not paused at an Anthropic step, anthropic-output-key-mismatch if the supplied key does not match the suspended step, or invalid-anthropic-response if the response cannot be serialized into the environment.

Source

pub fn resume_human<T: Serialize>( &self, workflow: Workflow, output_key: impl Into<String>, value: T, ) -> Result<Workflow, SError>

Store a human answer and advance the suspended workflow.

The workflow must be paused at the human step that requested output_key. The answer is serialized into the environment and the workflow advances to the continuation that was waiting behind the human request.

§Errors

Returns not-suspended-at-human if the workflow is not paused at a human step, human-output-key-mismatch if the supplied key does not match the suspended step, or invalid-human-response if the answer cannot be serialized into the environment.

Source

pub fn resume_tool_call( &self, workflow: Workflow, output_key: impl Into<String>, results: Vec<ToolResultBlock>, ) -> Result<Workflow, SError>

Store tool results and advance the suspended workflow.

The workflow must be paused at the tool-call step that requested output_key. The Vec<ToolResultBlock> is serialized into the environment under that key and the workflow advances to the receiver that was waiting behind the tool-call step. The crate does not thread the blocks into a conversation; the receiver owns the transcript and decides how the results become the next user message.

§Errors

Returns not-suspended-at-tool-call if the workflow is not paused at a tool-call step, tool-call-output-key-mismatch if the supplied key does not match the suspended step, or invalid-tool-results if the results cannot be serialized into the environment.

Source

pub fn resume_fork_join( &self, workflow: Workflow, lhs: Workflow, rhs: Workflow, ) -> Result<Workflow, SError>

Merge halted branch workflows and advance the parent to its join call.

Branch environments are compared to the parent environment captured at the fork. A key changed by only one branch is accepted. A key changed by both branches is accepted only when both branches wrote the same JSON value.

§Errors

Returns not-suspended-at-fork-join if the parent is not paused at a fork/join step, fork-join-branch-not-halted if either branch still has work remaining, or fork-join-env-conflict if branches wrote conflicting values for the same environment key.

Source

pub fn resume_open_ai<T: Serialize>( &self, workflow: Workflow, output_key: impl Into<String>, value: T, ) -> Result<Workflow, SError>

Store an OpenAI response value and advance the suspended workflow.

OpenAI is represented in the workflow state machine, but the bundled live executor does not yet perform OpenAI requests. Custom runtimes can use this method to resume workflows after they have obtained a response from a suitable Rust OpenAI client.

§Errors

Returns not-suspended-at-openai if the workflow is not paused at an OpenAI step, or invalid-openai-response if value cannot be serialized into the environment.

Source

pub fn register( &mut self, function: impl Into<String>, implementation: impl for<'a> Fn(&'a mut Workflow) -> CallFuture<'a> + 'static, )

Associate an exact function name with a local workflow call.

Names used by Workflow::new, Continuation::call, and ForkBranch::new must match a registration name exactly before the trampoline can execute that call. Registering the same name again replaces the previous implementation.

§Examples
use langcontinuation::{CallFuture, Trampoline, Workflow, WorkflowResult};

#[tokio::main]
async fn main() -> Result<(), handled::SError> {
    let mut trampoline = Trampoline::default();
    trampoline.register("entry", |workflow| -> CallFuture<'_> {
        Box::pin(async move {
            let run_id = workflow.run_id().to_string();
            workflow.into_env("visited", run_id).unwrap();
            Ok(())
        })
    });

    let WorkflowResult::Halt { workflow } =
        trampoline.run(Workflow::new("run", "entry")).await?.result
    else {
        panic!("workflow should halt");
    };
    assert_eq!(
        workflow.from_env::<String>("visited").unwrap(),
        Some("run".to_string())
    );
    Ok(())
}
Examples found in repository?
examples/durable_tool_call.rs (line 181)
176async fn main() -> Result<(), Box<dyn std::error::Error>> {
177    let mut workflow = Workflow::new("durable-tool-call", "entry");
178    push_env!(workflow.question: String = "What is 21 plus 21?".to_string());
179
180    let mut trampoline = Trampoline::default();
181    trampoline.register("entry", entry);
182    trampoline.register("receive", receive);
183    trampoline.register("after_tools", after_tools);
184    trampoline.register_tool(CalculatorTool);
185
186    let anthropic = Anthropic::new(None)?;
187    let executor = Executor::new(trampoline).with_anthropic("anthropic", anthropic);
188    let workflow = executor.run_workflow(workflow).await?;
189
190    from_env!(let answer: String = workflow.lookup());
191    println!("{answer}");
192    Ok(())
193}
More examples
Hide additional examples
examples/three_stage_support_pipeline.rs (line 36)
24async fn main() -> Result<(), handled::SError> {
25    async {
26        let ticket = Ticket {
27            id: "ticket-001".into(),
28            what:
29                "Customer reports that chargeback details are missing from the invoice export UI."
30                    .into(),
31        };
32        let mut workflow = Workflow::new("ticket-001", "triage_ticket");
33        push_env!(workflow.input_ticket: Ticket = ticket);
34
35        let mut trampoline = Trampoline::default();
36        trampoline.register("triage_ticket", triage_ticket);
37        trampoline.register("close_ticket_with_reason", close_ticket_with_reason);
38
39        let executor = Executor::new(trampoline);
40        let workflow = executor.run_workflow(workflow).await?;
41        from_env!(let reason: String = workflow.lookup());
42        println!("{reason}");
43        Ok(())
44    }
45    .await
46}
examples/tool_calling_and_web_searching.rs (line 655)
653fn register_workflow(config: &RunConfig) -> Trampoline {
654    let mut trampoline = Trampoline::default();
655    trampoline.register("entrypoint", entrypoint);
656    trampoline.register("start_langchain_branch", start_langchain_branch);
657    trampoline.register(
658        "start_langcontinuation_branch",
659        start_langcontinuation_branch,
660    );
661    trampoline.register("ask_langchain", ask_langchain);
662    trampoline.register("ask_langcontinuation", ask_langcontinuation);
663    trampoline.register("handle_langchain_response", handle_langchain_response);
664    trampoline.register(
665        "handle_langcontinuation_response",
666        handle_langcontinuation_response,
667    );
668    trampoline.register("after_langchain_tools", after_langchain_tools);
669    trampoline.register("after_langcontinuation_tools", after_langcontinuation_tools);
670    trampoline.register("start_join", start_join);
671    trampoline.register("ask_join", ask_join);
672    trampoline.register("handle_join_response", handle_join_response);
673    trampoline.register("after_join_tools", after_join_tools);
674    // One registered tool serves every branch and the join.
675    trampoline.register_tool(TextEditorTool::new(config));
676    trampoline
677}
Source

pub fn register_tool(&mut self, tool: impl Tool + 'static)

Register a client-side Tool under a name.

The name should match the name field of the ToolUseBlock values the model emits. A runtime resolves a WorkflowResult::ToolCall by looking up each tool_use by name in this registry. Registering the same name again replaces the previous tool. Tools are held only by the trampoline; they are never serialized into a Workflow.

§Examples
use std::pin::Pin;
use std::future::Future;
use langcontinuation::{Tool, ToolCallId, Trampoline};
use claudius::{ToolResultBlock, ToolUnionParam, ToolUseBlock};

struct Echo;
impl Tool for Echo {
    fn name(&self) -> String {
        "echo".into()
    }
    fn to_param(&self) -> ToolUnionParam {
        unimplemented!("doc test does not advertise the tool")
    }
    fn call<'a>(
        &'a self,
        _id: ToolCallId,
        tool_use: &'a ToolUseBlock,
    ) -> Pin<Box<dyn Future<Output = ToolResultBlock> + Send + 'a>> {
        let id = tool_use.id.clone();
        Box::pin(async move { ToolResultBlock::new(id).with_string_content("ok".into()) })
    }
}

let mut trampoline = Trampoline::default();
trampoline.register_tool(Echo);
Examples found in repository?
examples/durable_tool_call.rs (line 184)
176async fn main() -> Result<(), Box<dyn std::error::Error>> {
177    let mut workflow = Workflow::new("durable-tool-call", "entry");
178    push_env!(workflow.question: String = "What is 21 plus 21?".to_string());
179
180    let mut trampoline = Trampoline::default();
181    trampoline.register("entry", entry);
182    trampoline.register("receive", receive);
183    trampoline.register("after_tools", after_tools);
184    trampoline.register_tool(CalculatorTool);
185
186    let anthropic = Anthropic::new(None)?;
187    let executor = Executor::new(trampoline).with_anthropic("anthropic", anthropic);
188    let workflow = executor.run_workflow(workflow).await?;
189
190    from_env!(let answer: String = workflow.lookup());
191    println!("{answer}");
192    Ok(())
193}
More examples
Hide additional examples
examples/tool_calling_and_web_searching.rs (line 675)
653fn register_workflow(config: &RunConfig) -> Trampoline {
654    let mut trampoline = Trampoline::default();
655    trampoline.register("entrypoint", entrypoint);
656    trampoline.register("start_langchain_branch", start_langchain_branch);
657    trampoline.register(
658        "start_langcontinuation_branch",
659        start_langcontinuation_branch,
660    );
661    trampoline.register("ask_langchain", ask_langchain);
662    trampoline.register("ask_langcontinuation", ask_langcontinuation);
663    trampoline.register("handle_langchain_response", handle_langchain_response);
664    trampoline.register(
665        "handle_langcontinuation_response",
666        handle_langcontinuation_response,
667    );
668    trampoline.register("after_langchain_tools", after_langchain_tools);
669    trampoline.register("after_langcontinuation_tools", after_langcontinuation_tools);
670    trampoline.register("start_join", start_join);
671    trampoline.register("ask_join", ask_join);
672    trampoline.register("handle_join_response", handle_join_response);
673    trampoline.register("after_join_tools", after_join_tools);
674    // One registered tool serves every branch and the join.
675    trampoline.register_tool(TextEditorTool::new(config));
676    trampoline
677}
Source

pub fn tool(&self, name: &str) -> Option<Arc<dyn Tool>>

Look up a registered tool by name.

Runtimes use this while performing a WorkflowResult::ToolCall. Returns None if no tool was registered under the name.

Source

pub async fn run_tool_calls( &self, run_id: &str, tool_uses: &[ToolUseBlock], ) -> Result<Vec<ToolResultBlock>, SError>

Run every tool_use block through the registered tools.

This is the shared dispatch used by both the live and batch runtimes when they perform a WorkflowResult::ToolCall. Each tool_use is resolved by name with Self::tool, assigned a replay-deterministic ToolCallId from run_id and the tool_use id, and executed in order. The returned blocks are in the same order as tool_uses.

§Errors

Returns missing-tool if any tool_use names a tool that is not registered.

Trait Implementations§

Source§

impl Default for Trampoline

Source§

fn default() -> Trampoline

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more