Skip to main content

temporal_agent_rs/
builder.rs

1//! Convenience builder for assembling an agent-aware Temporal worker.
2//!
3//! ```ignore
4//! let mut worker = AgentWorkerBuilder::new(client)
5//!     .llm(my_llm_provider)
6//!     .tool(Arc::new(MyTool::default()))
7//!     .queue("agents")
8//!     .build_worker(&runtime)?;
9//! worker.run().await?;
10//! ```
11
12use std::sync::Arc;
13
14use autoagents_core::tool::ToolT;
15use autoagents_llm::LLMProvider;
16use temporalio_client::Client;
17use temporalio_sdk::{Worker, WorkerOptions};
18use temporalio_sdk_core::CoreRuntime;
19use tokio::sync::OnceCell;
20
21use crate::activities::AgentActivities;
22use crate::error::AgentError;
23use crate::state::ToolSchema;
24use crate::tool::{ToolRegistry, ToolRegistryBuilder};
25use crate::workflow::AgentWorkflow;
26
27/// Tool catalog made available to the workflow side.
28///
29/// The workflow is read-only with respect to tools — it only needs their
30/// schemas (name, description, args_schema) to send to the LLM each turn.
31/// Stored in a process-wide `OnceCell` so the workflow's `#[run]` body can
32/// access it deterministically (the catalog is set once at worker start, so
33/// reads return the same value on every replay).
34pub(crate) static WORKER_TOOL_CATALOG: OnceCell<Vec<ToolSchema>> = OnceCell::const_new();
35
36/// Fluent builder for an agent-aware Temporal [`Worker`].
37///
38/// Required calls: [`AgentWorkerBuilder::new`] → [`AgentWorkerBuilder::llm`]
39/// → [`AgentWorkerBuilder::build_worker`]. Tools and queue name are
40/// optional.
41pub struct AgentWorkerBuilder {
42    client: Client,
43    llm: Option<Arc<dyn LLMProvider>>,
44    tools: ToolRegistryBuilder,
45    queue: String,
46}
47
48impl AgentWorkerBuilder {
49    /// Start a new builder bound to an already-connected Temporal [`Client`].
50    #[must_use]
51    pub fn new(client: Client) -> Self {
52        Self {
53            client,
54            llm: None,
55            tools: ToolRegistry::builder(),
56            queue: "agents".to_string(),
57        }
58    }
59
60    /// Required. The LLM provider used by the `llm_chat` activity.
61    #[must_use]
62    pub fn llm(mut self, llm: Arc<dyn LLMProvider>) -> Self {
63        self.llm = Some(llm);
64        self
65    }
66
67    /// Register a tool. Call once per tool the agent should be allowed to use.
68    #[must_use]
69    pub fn tool(mut self, tool: Arc<dyn ToolT>) -> Self {
70        self.tools = self.tools.add(tool);
71        self
72    }
73
74    /// Override the default task queue name (`"agents"`).
75    #[must_use]
76    pub fn queue(mut self, queue: impl Into<String>) -> Self {
77        self.queue = queue.into();
78        self
79    }
80
81    /// Construct the Temporal worker with `AgentWorkflow` + `AgentActivities`
82    /// registered.
83    ///
84    /// Panics if [`Self::llm`] was not called.
85    pub fn build_worker(self, runtime: &CoreRuntime) -> Result<Worker, AgentError> {
86        let llm = self
87            .llm
88            .expect("AgentWorkerBuilder::llm(...) must be called before build_worker()");
89        let registry = self.tools.build();
90
91        // Publish the tool catalog so the workflow can include it in each
92        // llm_chat payload. The library does NOT inject any synthetic
93        // tools — including question-asking. Users who want human-in-the-
94        // loop register their own `ask_user`-style tool whose `execute()`
95        // blocks until an answer is delivered (see the math_agent example).
96        let catalog = registry.to_schemas();
97        // OnceCell::set is fallible if already set; in long-running test
98        // processes we tolerate re-initialization with the same data.
99        let _ = WORKER_TOOL_CATALOG.set(catalog);
100
101        let activities = AgentActivities::new(llm, registry);
102
103        let opts = WorkerOptions::new(&self.queue)
104            .register_workflow::<AgentWorkflow>()
105            .register_activities(activities)
106            .build();
107
108        Worker::new(runtime, self.client, opts)
109            .map_err(|e| AgentError::Other(format!("worker init: {e}")))
110    }
111}