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
impl Trampoline
Sourcepub fn set_observability_config(&mut self, config: ObservabilityConfig)
pub fn set_observability_config(&mut self, config: ObservabilityConfig)
Set event summarization and validation configuration.
Sourcepub async fn run(
&self,
workflow: Workflow,
) -> Result<WorkflowOutcome, WorkflowError>
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(())
}Sourcepub fn next_action(&self, workflow: &Workflow) -> WorkflowNext
pub fn next_action(&self, workflow: &Workflow) -> WorkflowNext
Return the next sanitized action the trampoline would take.
Sourcepub async fn run_one_local_call(
&self,
workflow: Workflow,
) -> Result<WorkflowStepOutcome, WorkflowError>
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.
Sourcepub fn resume_anthropic(
&self,
workflow: Workflow,
output_key: impl Into<String>,
message: Message,
) -> Result<Workflow, SError>
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.
Sourcepub fn resume_human<T: Serialize>(
&self,
workflow: Workflow,
output_key: impl Into<String>,
value: T,
) -> Result<Workflow, SError>
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.
Sourcepub fn resume_tool_call(
&self,
workflow: Workflow,
output_key: impl Into<String>,
results: Vec<ToolResultBlock>,
) -> Result<Workflow, SError>
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.
Sourcepub fn resume_fork_join(
&self,
workflow: Workflow,
lhs: Workflow,
rhs: Workflow,
) -> Result<Workflow, SError>
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.
Sourcepub fn resume_open_ai<T: Serialize>(
&self,
workflow: Workflow,
output_key: impl Into<String>,
value: T,
) -> Result<Workflow, SError>
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.
Sourcepub fn register(
&mut self,
function: impl Into<String>,
implementation: impl for<'a> Fn(&'a mut Workflow) -> CallFuture<'a> + 'static,
)
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?
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
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}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}Sourcepub fn register_tool(&mut self, tool: impl Tool + 'static)
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?
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
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}Sourcepub fn tool(&self, name: &str) -> Option<Arc<dyn Tool>>
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.
Sourcepub async fn run_tool_calls(
&self,
run_id: &str,
tool_uses: &[ToolUseBlock],
) -> Result<Vec<ToolResultBlock>, SError>
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
impl Default for Trampoline
Source§fn default() -> Trampoline
fn default() -> Trampoline
Auto Trait Implementations§
impl !RefUnwindSafe for Trampoline
impl !Send for Trampoline
impl !Sync for Trampoline
impl !UnwindSafe for Trampoline
impl Freeze for Trampoline
impl Unpin for Trampoline
impl UnsafeUnpin for Trampoline
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
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