Skip to main content

everruns_runtime/
builders.rs

1// Embedder-facing builders for core seed models.
2// Decision: keep these in everruns-runtime so core domain structs stay literal
3// data models while runtime owns the public embedding ergonomics.
4
5use std::collections::HashMap;
6
7use chrono::{DateTime, Utc};
8use everruns_core::network_access::NetworkAccessList;
9use everruns_core::{
10    Agent, AgentCapabilityConfig, AgentId, AgentStatus, DEFAULT_ORG_PUBLIC_ID, Harness, HarnessId,
11    HarnessStatus, ModelId, PrincipalId, ScopedMcpServers, Session, SessionId, SessionStatus,
12    ToolDefinition, plugin_capability_id,
13};
14use uuid::Uuid;
15
16/// Builds a [`Harness`] with runtime-friendly defaults.
17///
18/// Use this when embedding Everruns directly and seeding a harness in code.
19/// IDs and timestamps are generated at [`build`](Self::build) time unless
20/// explicitly set.
21#[derive(Debug, Clone)]
22pub struct HarnessBuilder {
23    id: HarnessId,
24    name: String,
25    display_name: Option<String>,
26    description: Option<String>,
27    system_prompt: String,
28    parent_harness_id: Option<HarnessId>,
29    default_model_id: Option<ModelId>,
30    tags: Vec<String>,
31    capabilities: Vec<AgentCapabilityConfig>,
32    initial_files: Vec<everruns_core::InitialFile>,
33    network_access: Option<NetworkAccessList>,
34    mcp_servers: ScopedMcpServers,
35    embedder_metadata: HashMap<String, String>,
36    is_built_in: bool,
37    status: HarnessStatus,
38    created_at: Option<DateTime<Utc>>,
39    updated_at: Option<DateTime<Utc>>,
40}
41
42impl HarnessBuilder {
43    /// Create a harness builder from the required embedder-facing fields.
44    pub fn new(name: impl Into<String>, system_prompt: impl Into<String>) -> Self {
45        Self {
46            id: HarnessId::new(),
47            name: name.into(),
48            display_name: None,
49            description: None,
50            system_prompt: system_prompt.into(),
51            parent_harness_id: None,
52            default_model_id: None,
53            tags: Vec::new(),
54            capabilities: Vec::new(),
55            initial_files: Vec::new(),
56            network_access: None,
57            mcp_servers: ScopedMcpServers::default(),
58            embedder_metadata: HashMap::new(),
59            is_built_in: false,
60            status: HarnessStatus::Active,
61            created_at: None,
62            updated_at: None,
63        }
64    }
65
66    /// Set a stable harness id instead of generating one.
67    pub fn id(mut self, id: HarnessId) -> Self {
68        self.id = id;
69        self
70    }
71
72    /// Return the id currently assigned to this builder.
73    pub fn harness_id(&self) -> HarnessId {
74        self.id
75    }
76
77    pub fn name(mut self, name: impl Into<String>) -> Self {
78        self.name = name.into();
79        self
80    }
81
82    pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
83        self.display_name = Some(display_name.into());
84        self
85    }
86
87    pub fn description(mut self, description: impl Into<String>) -> Self {
88        self.description = Some(description.into());
89        self
90    }
91
92    pub fn system_prompt(mut self, system_prompt: impl Into<String>) -> Self {
93        self.system_prompt = system_prompt.into();
94        self
95    }
96
97    pub fn parent_harness_id(mut self, parent_harness_id: HarnessId) -> Self {
98        self.parent_harness_id = Some(parent_harness_id);
99        self
100    }
101
102    pub fn default_model_id(mut self, default_model_id: ModelId) -> Self {
103        self.default_model_id = Some(default_model_id);
104        self
105    }
106
107    pub fn tag(mut self, tag: impl Into<String>) -> Self {
108        self.tags.push(tag.into());
109        self
110    }
111
112    pub fn tags<I, S>(mut self, tags: I) -> Self
113    where
114        I: IntoIterator<Item = S>,
115        S: Into<String>,
116    {
117        self.tags.extend(tags.into_iter().map(Into::into));
118        self
119    }
120
121    pub fn capability(mut self, capability: impl Into<AgentCapabilityConfig>) -> Self {
122        self.capabilities.push(capability.into());
123        self
124    }
125
126    pub fn with_capability(self, capability: impl Into<AgentCapabilityConfig>) -> Self {
127        self.capability(capability)
128    }
129
130    pub fn capabilities<I, C>(mut self, capabilities: I) -> Self
131    where
132        I: IntoIterator<Item = C>,
133        C: Into<AgentCapabilityConfig>,
134    {
135        self.capabilities
136            .extend(capabilities.into_iter().map(Into::into));
137        self
138    }
139
140    pub fn initial_file(mut self, file: everruns_core::InitialFile) -> Self {
141        self.initial_files.push(file);
142        self
143    }
144
145    pub fn network_access(mut self, network_access: NetworkAccessList) -> Self {
146        self.network_access = Some(network_access);
147        self
148    }
149
150    pub fn mcp_servers(mut self, mcp_servers: ScopedMcpServers) -> Self {
151        self.mcp_servers = mcp_servers;
152        self
153    }
154
155    pub fn metadata_entry(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
156        self.embedder_metadata.insert(key.into(), value.into());
157        self
158    }
159
160    pub fn metadata_entries<I, K, V>(mut self, entries: I) -> Self
161    where
162        I: IntoIterator<Item = (K, V)>,
163        K: Into<String>,
164        V: Into<String>,
165    {
166        self.embedder_metadata
167            .extend(entries.into_iter().map(|(k, v)| (k.into(), v.into())));
168        self
169    }
170
171    pub fn is_built_in(mut self, is_built_in: bool) -> Self {
172        self.is_built_in = is_built_in;
173        self
174    }
175
176    pub fn status(mut self, status: HarnessStatus) -> Self {
177        self.status = status;
178        self
179    }
180
181    pub fn created_at(mut self, created_at: DateTime<Utc>) -> Self {
182        self.created_at = Some(created_at);
183        self
184    }
185
186    pub fn updated_at(mut self, updated_at: DateTime<Utc>) -> Self {
187        self.updated_at = Some(updated_at);
188        self
189    }
190
191    /// Build the harness. Builders do not validate domain invariants.
192    pub fn build(self) -> Harness {
193        let created_at = self.created_at.unwrap_or_else(Utc::now);
194        let updated_at = self.updated_at.unwrap_or(created_at);
195
196        Harness {
197            id: self.id,
198            name: self.name,
199            display_name: self.display_name,
200            description: self.description,
201            system_prompt: self.system_prompt,
202            parent_harness_id: self.parent_harness_id,
203            default_model_id: self.default_model_id,
204            tags: self.tags,
205            capabilities: self.capabilities,
206            initial_files: self.initial_files,
207            network_access: self.network_access,
208            mcp_servers: self.mcp_servers,
209            embedder_metadata: self.embedder_metadata,
210            is_built_in: self.is_built_in,
211            status: self.status,
212            created_at,
213            updated_at,
214            archived_at: None,
215            deleted_at: None,
216        }
217    }
218}
219
220/// Builds an [`Agent`] with runtime-friendly defaults.
221#[derive(Debug, Clone)]
222pub struct AgentBuilder {
223    id: AgentId,
224    name: String,
225    display_name: Option<String>,
226    description: Option<String>,
227    system_prompt: String,
228    default_model_id: Option<ModelId>,
229    tags: Vec<String>,
230    capabilities: Vec<AgentCapabilityConfig>,
231    initial_files: Vec<everruns_core::InitialFile>,
232    network_access: Option<NetworkAccessList>,
233    max_iterations: Option<usize>,
234    tools: Vec<ToolDefinition>,
235    mcp_servers: ScopedMcpServers,
236    status: AgentStatus,
237    created_at: Option<DateTime<Utc>>,
238    updated_at: Option<DateTime<Utc>>,
239}
240
241impl AgentBuilder {
242    /// Create an agent builder from the required embedder-facing fields.
243    pub fn new(name: impl Into<String>, system_prompt: impl Into<String>) -> Self {
244        Self {
245            id: AgentId::new(),
246            name: name.into(),
247            display_name: None,
248            description: None,
249            system_prompt: system_prompt.into(),
250            default_model_id: None,
251            tags: Vec::new(),
252            capabilities: Vec::new(),
253            initial_files: Vec::new(),
254            network_access: None,
255            max_iterations: None,
256            tools: Vec::new(),
257            mcp_servers: ScopedMcpServers::default(),
258            status: AgentStatus::Active,
259            created_at: None,
260            updated_at: None,
261        }
262    }
263
264    /// Set a stable agent id instead of generating one.
265    pub fn id(mut self, id: AgentId) -> Self {
266        self.id = id;
267        self
268    }
269
270    /// Return the id currently assigned to this builder.
271    pub fn agent_id(&self) -> AgentId {
272        self.id
273    }
274
275    pub fn name(mut self, name: impl Into<String>) -> Self {
276        self.name = name.into();
277        self
278    }
279
280    pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
281        self.display_name = Some(display_name.into());
282        self
283    }
284
285    pub fn description(mut self, description: impl Into<String>) -> Self {
286        self.description = Some(description.into());
287        self
288    }
289
290    pub fn system_prompt(mut self, system_prompt: impl Into<String>) -> Self {
291        self.system_prompt = system_prompt.into();
292        self
293    }
294
295    pub fn default_model_id(mut self, default_model_id: ModelId) -> Self {
296        self.default_model_id = Some(default_model_id);
297        self
298    }
299
300    pub fn tag(mut self, tag: impl Into<String>) -> Self {
301        self.tags.push(tag.into());
302        self
303    }
304
305    pub fn tags<I, S>(mut self, tags: I) -> Self
306    where
307        I: IntoIterator<Item = S>,
308        S: Into<String>,
309    {
310        self.tags.extend(tags.into_iter().map(Into::into));
311        self
312    }
313
314    pub fn capability(mut self, capability: impl Into<AgentCapabilityConfig>) -> Self {
315        self.capabilities.push(capability.into());
316        self
317    }
318
319    pub fn with_capability(self, capability: impl Into<AgentCapabilityConfig>) -> Self {
320        self.capability(capability)
321    }
322
323    pub fn capabilities<I, C>(mut self, capabilities: I) -> Self
324    where
325        I: IntoIterator<Item = C>,
326        C: Into<AgentCapabilityConfig>,
327    {
328        self.capabilities
329            .extend(capabilities.into_iter().map(Into::into));
330        self
331    }
332
333    pub fn initial_file(mut self, file: everruns_core::InitialFile) -> Self {
334        self.initial_files.push(file);
335        self
336    }
337
338    pub fn network_access(mut self, network_access: NetworkAccessList) -> Self {
339        self.network_access = Some(network_access);
340        self
341    }
342
343    pub fn max_iterations(mut self, max_iterations: usize) -> Self {
344        self.max_iterations = Some(max_iterations);
345        self
346    }
347
348    pub fn tool(mut self, tool: ToolDefinition) -> Self {
349        self.tools.push(tool);
350        self
351    }
352
353    pub fn tools<I>(mut self, tools: I) -> Self
354    where
355        I: IntoIterator<Item = ToolDefinition>,
356    {
357        self.tools.extend(tools);
358        self
359    }
360
361    pub fn mcp_servers(mut self, mcp_servers: ScopedMcpServers) -> Self {
362        self.mcp_servers = mcp_servers;
363        self
364    }
365
366    pub fn status(mut self, status: AgentStatus) -> Self {
367        self.status = status;
368        self
369    }
370
371    pub fn created_at(mut self, created_at: DateTime<Utc>) -> Self {
372        self.created_at = Some(created_at);
373        self
374    }
375
376    pub fn updated_at(mut self, updated_at: DateTime<Utc>) -> Self {
377        self.updated_at = Some(updated_at);
378        self
379    }
380
381    /// Build the agent. Builders do not validate domain invariants.
382    pub fn build(self) -> Agent {
383        let created_at = self.created_at.unwrap_or_else(Utc::now);
384        let updated_at = self.updated_at.unwrap_or(created_at);
385
386        Agent {
387            public_id: self.id,
388            internal_id: Uuid::nil(),
389            name: self.name,
390            display_name: self.display_name,
391            description: self.description,
392            system_prompt: self.system_prompt,
393            default_model_id: self.default_model_id,
394            default_version_id: None,
395            forked_from_agent_id: None,
396            forked_from_version_id: None,
397            root_agent_id: None,
398            tags: self.tags,
399            capabilities: self.capabilities,
400            initial_files: self.initial_files,
401            network_access: self.network_access,
402            max_iterations: self.max_iterations,
403            tools: self.tools,
404            mcp_servers: self.mcp_servers,
405            status: self.status,
406            created_at,
407            updated_at,
408            archived_at: None,
409            deleted_at: None,
410            usage: None,
411        }
412    }
413}
414
415/// Builds a [`Session`] with runtime-friendly defaults.
416#[derive(Debug, Clone)]
417pub struct SessionBuilder {
418    id: SessionId,
419    organization_id: String,
420    harness_id: HarnessId,
421    agent_id: Option<AgentId>,
422    owner_principal_id: PrincipalId,
423    title: Option<String>,
424    locale: Option<String>,
425    tags: Vec<String>,
426    model_id: Option<ModelId>,
427    capabilities: Vec<AgentCapabilityConfig>,
428    tools: Vec<ToolDefinition>,
429    mcp_servers: ScopedMcpServers,
430    system_prompt: Option<String>,
431    initial_files: Vec<everruns_core::InitialFile>,
432    network_access: Option<NetworkAccessList>,
433    max_iterations: Option<usize>,
434    status: SessionStatus,
435    created_at: Option<DateTime<Utc>>,
436    updated_at: Option<DateTime<Utc>>,
437}
438
439impl SessionBuilder {
440    /// Create a session builder for the required harness id.
441    pub fn new(harness_id: HarnessId) -> Self {
442        Self {
443            id: SessionId::new(),
444            organization_id: DEFAULT_ORG_PUBLIC_ID.to_string(),
445            harness_id,
446            agent_id: None,
447            owner_principal_id: PrincipalId::from_seed(1),
448            title: None,
449            locale: None,
450            tags: Vec::new(),
451            model_id: None,
452            capabilities: Vec::new(),
453            tools: Vec::new(),
454            mcp_servers: ScopedMcpServers::default(),
455            system_prompt: None,
456            initial_files: Vec::new(),
457            network_access: None,
458            max_iterations: None,
459            status: SessionStatus::Started,
460            created_at: None,
461            updated_at: None,
462        }
463    }
464
465    /// Set a stable session id instead of generating one.
466    pub fn id(mut self, id: SessionId) -> Self {
467        self.id = id;
468        self
469    }
470
471    /// Return the id currently assigned to this builder.
472    pub fn session_id(&self) -> SessionId {
473        self.id
474    }
475
476    pub fn organization_id(mut self, organization_id: impl Into<String>) -> Self {
477        self.organization_id = organization_id.into();
478        self
479    }
480
481    pub fn harness(mut self, harness_id: HarnessId) -> Self {
482        self.harness_id = harness_id;
483        self
484    }
485
486    pub fn agent(mut self, agent_id: AgentId) -> Self {
487        self.agent_id = Some(agent_id);
488        self
489    }
490
491    pub fn owner_principal_id(mut self, owner_principal_id: PrincipalId) -> Self {
492        self.owner_principal_id = owner_principal_id;
493        self
494    }
495
496    pub fn title(mut self, title: impl Into<String>) -> Self {
497        self.title = Some(title.into());
498        self
499    }
500
501    pub fn locale(mut self, locale: impl Into<String>) -> Self {
502        self.locale = Some(locale.into());
503        self
504    }
505
506    pub fn tag(mut self, tag: impl Into<String>) -> Self {
507        self.tags.push(tag.into());
508        self
509    }
510
511    pub fn tags<I, S>(mut self, tags: I) -> Self
512    where
513        I: IntoIterator<Item = S>,
514        S: Into<String>,
515    {
516        self.tags.extend(tags.into_iter().map(Into::into));
517        self
518    }
519
520    pub fn model_id(mut self, model_id: ModelId) -> Self {
521        self.model_id = Some(model_id);
522        self
523    }
524
525    pub fn capability(mut self, capability: impl Into<AgentCapabilityConfig>) -> Self {
526        self.capabilities.push(capability.into());
527        self
528    }
529
530    pub fn with_capability(self, capability: impl Into<AgentCapabilityConfig>) -> Self {
531        self.capability(capability)
532    }
533
534    pub fn capabilities<I, C>(mut self, capabilities: I) -> Self
535    where
536        I: IntoIterator<Item = C>,
537        C: Into<AgentCapabilityConfig>,
538    {
539        self.capabilities
540            .extend(capabilities.into_iter().map(Into::into));
541        self
542    }
543
544    pub fn tool(mut self, tool: ToolDefinition) -> Self {
545        self.tools.push(tool);
546        self
547    }
548
549    pub fn tools<I>(mut self, tools: I) -> Self
550    where
551        I: IntoIterator<Item = ToolDefinition>,
552    {
553        self.tools.extend(tools);
554        self
555    }
556
557    pub fn mcp_servers(mut self, mcp_servers: ScopedMcpServers) -> Self {
558        self.mcp_servers = mcp_servers;
559        self
560    }
561
562    pub fn system_prompt(mut self, system_prompt: impl Into<String>) -> Self {
563        self.system_prompt = Some(system_prompt.into());
564        self
565    }
566
567    pub fn initial_file(mut self, file: everruns_core::InitialFile) -> Self {
568        self.initial_files.push(file);
569        self
570    }
571
572    pub fn network_access(mut self, network_access: NetworkAccessList) -> Self {
573        self.network_access = Some(network_access);
574        self
575    }
576
577    pub fn max_iterations(mut self, max_iterations: usize) -> Self {
578        self.max_iterations = Some(max_iterations);
579        self
580    }
581
582    pub fn status(mut self, status: SessionStatus) -> Self {
583        self.status = status;
584        self
585    }
586
587    pub fn created_at(mut self, created_at: DateTime<Utc>) -> Self {
588        self.created_at = Some(created_at);
589        self
590    }
591
592    pub fn updated_at(mut self, updated_at: DateTime<Utc>) -> Self {
593        self.updated_at = Some(updated_at);
594        self
595    }
596
597    /// Build the session. Builders do not validate domain invariants.
598    pub fn build(self) -> Session {
599        let created_at = self.created_at.unwrap_or_else(Utc::now);
600        let updated_at = self.updated_at.unwrap_or(created_at);
601
602        Session {
603            id: self.id,
604            workspace_id: everruns_core::WorkspaceId::from_uuid((self.id).uuid()),
605            organization_id: self.organization_id,
606            harness_id: self.harness_id,
607            agent_id: self.agent_id,
608            agent_version_id: None,
609            agent_identity_id: None,
610            owner_principal_id: self.owner_principal_id,
611            resolved_owner_user_id: None,
612            owner: None,
613            effective_owner: None,
614            title: self.title,
615            locale: self.locale,
616            preview: None,
617            output_preview: None,
618            tags: self.tags,
619            model_id: self.model_id,
620            capabilities: self.capabilities,
621            tools: self.tools,
622            mcp_servers: self.mcp_servers,
623            system_prompt: self.system_prompt,
624            initial_files: self.initial_files,
625            hints: None,
626            network_access: self.network_access,
627            max_iterations: self.max_iterations,
628            status: self.status,
629            created_at,
630            updated_at,
631            started_at: None,
632            finished_at: None,
633            usage: None,
634            is_pinned: None,
635            active_schedule_count: None,
636            features: Vec::new(),
637            parent_session_id: None,
638            blueprint_id: None,
639            blueprint_config: None,
640        }
641    }
642}
643
644/// High-level builder for seeding one harness, one agent, and one session.
645///
646/// This is the compact path used by embedders that want a runnable runtime
647/// without constructing each core model separately.
648#[derive(Debug, Clone)]
649pub struct SingleSessionBuilder {
650    harness: HarnessBuilder,
651    agent: AgentBuilder,
652    session: SessionBuilder,
653}
654
655impl Default for SingleSessionBuilder {
656    fn default() -> Self {
657        let harness = HarnessBuilder::new("embedded-harness", "");
658        let agent = AgentBuilder::new("embedded-agent", "");
659        let session = SessionBuilder::new(harness.harness_id()).agent(agent.agent_id());
660        Self {
661            harness,
662            agent,
663            session,
664        }
665    }
666}
667
668impl SingleSessionBuilder {
669    /// Configure the seeded harness. Mutates the existing `HarnessBuilder`
670    /// in place so previously configured fields (e.g. `network_access`) are
671    /// preserved regardless of call order.
672    pub fn harness(mut self, name: impl Into<String>, system_prompt: impl Into<String>) -> Self {
673        let harness_id = self.harness.harness_id();
674        self.harness = self.harness.name(name).system_prompt(system_prompt);
675        self.session = self.session.harness(harness_id);
676        self
677    }
678
679    /// Configure the seeded agent. Mutates the existing `AgentBuilder` in
680    /// place so previously configured fields (e.g. `network_access`) are
681    /// preserved regardless of call order.
682    pub fn agent(mut self, name: impl Into<String>, system_prompt: impl Into<String>) -> Self {
683        let agent_id = self.agent.agent_id();
684        self.agent = self.agent.name(name).system_prompt(system_prompt);
685        self.session = self.session.agent(agent_id);
686        self
687    }
688
689    /// Add a harness-level capability.
690    pub fn with_capability(self, capability: impl Into<AgentCapabilityConfig>) -> Self {
691        self.harness_capability(capability)
692    }
693
694    /// Add a harness-level capability.
695    pub fn harness_capability(mut self, capability: impl Into<AgentCapabilityConfig>) -> Self {
696        self.harness = self.harness.capability(capability);
697        self
698    }
699
700    /// Add an agent-level capability.
701    pub fn agent_capability(mut self, capability: impl Into<AgentCapabilityConfig>) -> Self {
702        self.agent = self.agent.capability(capability);
703        self
704    }
705
706    /// Enable a previously loaded plugin on the seeded agent.
707    ///
708    /// Adds a `plugin:{name}` capability ref to the agent with an empty config.
709    /// The hydrated definition must be supplied separately via
710    /// [`InProcessRuntimeBuilder::with_plugin_dir`] — this method only records
711    /// the capability ref on the agent so the capability is active for this
712    /// session. The builder looks up the hydrated config at build time from the
713    /// `plugin_capability_configs` accumulated by `with_plugin_dir` calls.
714    ///
715    /// If you need to pass the fully hydrated `AgentCapabilityConfig` (e.g.
716    /// from [`InProcessRuntimeBuilder::plugin_capability`]), use
717    /// [`Self::agent_capability`] directly.
718    pub fn agent_plugin(mut self, name: &str) -> Self {
719        self.agent = self.agent.capability(plugin_capability_id(name));
720        self
721    }
722
723    /// Add a session-level capability.
724    pub fn session_capability(mut self, capability: impl Into<AgentCapabilityConfig>) -> Self {
725        self.session = self.session.capability(capability);
726        self
727    }
728
729    /// Configure session-scoped MCP servers (specs/runtime-mcp.md). Discovered
730    /// and executed by the runtime alongside built-in tools.
731    pub fn session_mcp_servers(mut self, mcp_servers: ScopedMcpServers) -> Self {
732        self.session = self.session.mcp_servers(mcp_servers);
733        self
734    }
735
736    pub fn harness_display_name(mut self, display_name: impl Into<String>) -> Self {
737        self.harness = self.harness.display_name(display_name);
738        self
739    }
740
741    pub fn agent_display_name(mut self, display_name: impl Into<String>) -> Self {
742        self.agent = self.agent.display_name(display_name);
743        self
744    }
745
746    pub fn harness_description(mut self, description: impl Into<String>) -> Self {
747        self.harness = self.harness.description(description);
748        self
749    }
750
751    pub fn agent_description(mut self, description: impl Into<String>) -> Self {
752        self.agent = self.agent.description(description);
753        self
754    }
755
756    pub fn session_title(mut self, title: impl Into<String>) -> Self {
757        self.session = self.session.title(title);
758        self
759    }
760
761    pub fn locale(mut self, locale: impl Into<String>) -> Self {
762        self.session = self.session.locale(locale);
763        self
764    }
765
766    pub fn tag(mut self, tag: impl Into<String>) -> Self {
767        let tag = tag.into();
768        self.harness = self.harness.tag(tag.clone());
769        self.agent = self.agent.tag(tag.clone());
770        self.session = self.session.tag(tag);
771        self
772    }
773
774    pub fn session_model_id(mut self, model_id: ModelId) -> Self {
775        self.session = self.session.model_id(model_id);
776        self
777    }
778
779    pub fn harness_default_model_id(mut self, model_id: ModelId) -> Self {
780        self.harness = self.harness.default_model_id(model_id);
781        self
782    }
783
784    pub fn agent_default_model_id(mut self, model_id: ModelId) -> Self {
785        self.agent = self.agent.default_model_id(model_id);
786        self
787    }
788
789    pub fn agent_max_iterations(mut self, max_iterations: usize) -> Self {
790        self.agent = self.agent.max_iterations(max_iterations);
791        self
792    }
793
794    pub fn session_max_iterations(mut self, max_iterations: usize) -> Self {
795        self.session = self.session.max_iterations(max_iterations);
796        self
797    }
798
799    pub fn agent_tool(mut self, tool: ToolDefinition) -> Self {
800        self.agent = self.agent.tool(tool);
801        self
802    }
803
804    pub fn session_tool(mut self, tool: ToolDefinition) -> Self {
805        self.session = self.session.tool(tool);
806        self
807    }
808
809    pub fn harness_initial_file(mut self, file: everruns_core::InitialFile) -> Self {
810        self.harness = self.harness.initial_file(file);
811        self
812    }
813
814    pub fn agent_initial_file(mut self, file: everruns_core::InitialFile) -> Self {
815        self.agent = self.agent.initial_file(file);
816        self
817    }
818
819    pub fn session_initial_file(mut self, file: everruns_core::InitialFile) -> Self {
820        self.session = self.session.initial_file(file);
821        self
822    }
823
824    pub fn harness_network_access(mut self, network_access: NetworkAccessList) -> Self {
825        self.harness = self.harness.network_access(network_access);
826        self
827    }
828
829    pub fn agent_network_access(mut self, network_access: NetworkAccessList) -> Self {
830        self.agent = self.agent.network_access(network_access);
831        self
832    }
833
834    pub fn session_network_access(mut self, network_access: NetworkAccessList) -> Self {
835        self.session = self.session.network_access(network_access);
836        self
837    }
838
839    pub fn harness_id(&self) -> HarnessId {
840        self.harness.harness_id()
841    }
842
843    pub fn agent_id(&self) -> AgentId {
844        self.agent.agent_id()
845    }
846
847    /// Pin the seeded session's id. When unset, the underlying
848    /// `SessionBuilder` generates a fresh `SessionId` at build time.
849    ///
850    /// Useful for embedders that need the id ahead of build — e.g. the
851    /// `examples/coding-cli` JSONL session log uses `<id>.jsonl` as the
852    /// filename and must open the file before the runtime exists.
853    pub fn session_id(mut self, id: SessionId) -> Self {
854        self.session = self.session.id(id);
855        self
856    }
857
858    pub(crate) fn build(self) -> (Harness, Agent, Session, SessionId) {
859        let session_id = self.session.session_id();
860        (
861            self.harness.build(),
862            self.agent.build(),
863            self.session.build(),
864            session_id,
865        )
866    }
867}