Skip to main content

everruns_builtins/
lib.rs

1#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used))]
2//! Portable built-in capabilities for the [Everruns](https://everruns.com) ecosystem.
3//!
4//! `everruns-builtins` is the optional, backend-neutral implementation bundle
5//! for portable capabilities that compose through `everruns-core` execution
6//! contracts. This includes the standard skills, context, and human-intent
7//! implementations as well as policy hooks. It owns no server, database,
8//! network transport, process runner, interpreter, or hosted service
9//! implementation. OpenUI and A2UI prompt capabilities are available behind
10//! the explicit `ui-capabilities` feature.
11//!
12//! # Example
13//!
14//! ```
15//! use everruns_builtins::{CurrentTimeCapability, register_portable_capabilities};
16//! use everruns_core::{Capability, CapabilityRegistry};
17//!
18//! let mut registry = CapabilityRegistry::new();
19//! register_portable_capabilities(&mut registry)?;
20//! assert_eq!(CurrentTimeCapability.id(), "current_time");
21//! assert!(registry.get("current_time").is_some());
22//! # Ok::<(), everruns_capability::CapabilityError>(())
23//! ```
24
25use std::sync::Arc;
26
27use everruns_capability::CapabilityError;
28
29// Public modules support focused imports; the root re-exports below are the
30// curated common surface.
31#[cfg(feature = "ui-capabilities")]
32pub mod a2ui;
33pub mod agent_instructions;
34pub mod attach_skill;
35pub mod auto_tool_search;
36pub mod btw;
37pub mod budgeting;
38pub mod channel_context;
39pub mod claude_tool_search;
40pub mod compaction;
41pub mod current_time;
42pub mod error_disclosure;
43mod framework_config;
44pub mod guardrails;
45pub mod human_intent;
46pub mod infinity_context;
47pub mod loop_detection;
48pub mod message_metadata;
49pub mod native_async_tools;
50pub mod openai_tool_search;
51#[cfg(feature = "ui-capabilities")]
52pub mod openui;
53pub mod parallel_tool_calls;
54pub use native_async_tools::NativeAsyncToolsCapability;
55pub mod progress_guard;
56pub mod prompt_caching;
57pub mod prompt_canary_guardrail;
58pub mod self_budget;
59pub mod skills;
60pub mod skills_scoped;
61pub mod soft_approval;
62pub mod stateless_todo_list;
63pub mod system_commands;
64pub mod tool_approval;
65pub mod tool_call_repair;
66pub mod tool_output_distillation;
67pub mod tool_output_persistence;
68pub mod tool_search;
69pub mod usage_limit_auto_continue;
70
71// Compatibility paths used by the collocated implementation tests. These are
72// aliases of core's provider-neutral execution modules, not copied contracts.
73pub(crate) use everruns_core::capabilities::{
74    Capability, CapabilityLocalization, CapabilityRegistry, CapabilityStatus, Fact, FactsContext,
75    ModelViewContext, ModelViewProvider, RiskLevel, SystemPromptContext, ToolDefinitionHook,
76    Volatility,
77};
78pub(crate) use everruns_core::tool_hooks;
79#[allow(unused_imports)]
80// Collocated unit tests use a wider compatibility subset than the library.
81pub(crate) use everruns_core::{
82    DEFAULT_ORG_PUBLIC_ID, McpToolInvoker, UtilityLlmService, retain_complete_llm_tool_exchanges,
83};
84#[allow(unused_imports)]
85// Collocated unit tests use a wider compatibility subset than the library.
86pub(crate) use everruns_core::{
87    budget, capability_dto, capability_types, command, command_host, events, guardrail_checks,
88    llm_conversions, llm_error_hook, mcp_server, message, message_filter, output_guardrail,
89    runtime_agent, session, session_file, session_files, session_resource, session_schedule, skill,
90    tool_context, tool_fingerprint, tool_narration, tool_output_sanitizer, tools, utility_llm,
91};
92#[allow(unused_imports)]
93pub(crate) use everruns_provider::driver_registry::{
94    LlmCompletionMetadata, LlmMessage, LlmMessageRole, LlmResponse, LlmResponseStream,
95};
96#[allow(unused_imports)]
97pub(crate) use everruns_provider::error::{AgentLoopError, Result};
98#[allow(unused_imports)]
99pub(crate) use everruns_provider::typed_id::{PrincipalId, WorkspaceId};
100#[allow(unused_imports)]
101pub(crate) use everruns_provider::user_facing_error::codes as user_facing_error_codes;
102#[allow(unused_imports)]
103pub(crate) use everruns_provider::{
104    driver_registry, error, model_profiles, provider, tool_types, typed_id, user_facing_error,
105};
106
107#[cfg(test)]
108mod test_fixtures;
109
110#[cfg(feature = "ui-capabilities")]
111pub use a2ui::{A2UI_CAPABILITY_ID, A2UiCapability};
112pub use agent_instructions::{
113    AGENT_INSTRUCTIONS_CAPABILITY_ID, AGENTS_MD_PATH, AgentInstructionsCapability,
114    AgentInstructionsConfig, DEFAULT_AGENT_INSTRUCTIONS_FILE, MAX_AGENT_INSTRUCTIONS_FILES,
115    MAX_AGENTS_MD_SIZE, format_agents_md_content, format_instruction_file_content,
116};
117pub use attach_skill::AttachSkillCapability;
118pub use auto_tool_search::{AUTO_TOOL_SEARCH_CAPABILITY_ID, AutoToolSearchCapability};
119pub use btw::{BTW_CAPABILITY_ID, BtwCapability};
120pub use budgeting::{BUDGETING_CAPABILITY_ID, BudgetingCapability};
121pub use channel_context::{CHANNEL_CONTEXT_CAPABILITY_ID, ChannelContextCapability};
122pub use claude_tool_search::{CLAUDE_TOOL_SEARCH_CAPABILITY_ID, ClaudeToolSearchCapability};
123pub use compaction::{
124    COMPACTION_CAPABILITY_ID, CompactionCapability, CompactionStep,
125    CompactionStrategy as RuntimeCompactionStrategy, CostControlConfig, CostControlMaskingResult,
126    HierarchicalMemoryConfig, MaskingSummaryFormat, MemoryTier, ObservationMaskingConfig,
127    ObservationMaskingResult, RuntimeCompactionConfig, SessionCompactionMetrics,
128    SummarizationConfig, aggressive_trim, apply_cost_control_masking, apply_hierarchical_memory,
129    apply_observation_masking, build_model_view_messages, build_summarization_prompt,
130    build_summary_message, classify_memory_tiers, compose_summary_with_recent, estimate_tokens,
131    estimate_total_tokens, format_messages_for_summarization, should_compact_for_cost,
132    should_compact_proactively, total_tool_result_bytes,
133};
134pub use current_time::{CURRENT_TIME_CAPABILITY_ID, CurrentTimeCapability, GetCurrentTimeTool};
135pub use error_disclosure::{
136    ERROR_DISCLOSURE_CAPABILITY_ID, ErrorDisclosureCapability, resolve_error_disclosure,
137};
138pub use framework_config::{CompactionConfig, CompactionStrategy, ToolSearch};
139pub use guardrails::{GUARDRAILS_CAPABILITY_ID, GuardrailsCapability};
140pub use human_intent::{HUMAN_INTENT_CAPABILITY_ID, HumanIntentCapability};
141pub use infinity_context::{
142    INFINITY_CONTEXT_CAPABILITY_ID, InfinityContextCapability, InfinityContextFilterOnlyCapability,
143    QueryHistoryTool,
144};
145pub use loop_detection::{LOOP_DETECTION_CAPABILITY_ID, LoopDetectionCapability};
146pub use message_metadata::{
147    MESSAGE_METADATA_CAPABILITY_ID, MessageMetadataCapability, MessageMetadataConfig,
148    MessageMetadataField, render_annotation, strip_leading_timestamp_annotations,
149};
150pub use openai_tool_search::{
151    DEFAULT_TOOL_SEARCH_THRESHOLD, OPENAI_TOOL_SEARCH_CAPABILITY_ID, OpenAiToolSearchCapability,
152    model_supports_native_tool_search,
153};
154#[cfg(feature = "ui-capabilities")]
155pub use openui::{OPENUI_CAPABILITY_ID, OpenUiCapability};
156pub use parallel_tool_calls::{
157    PARALLEL_TOOL_CALLS_CAPABILITY_ID, ParallelToolCallsCapability, ParallelToolCallsMode,
158    parallel_tool_calls_from_config,
159};
160pub use progress_guard::{PROGRESS_GUARD_CAPABILITY_ID, ProgressGuardCapability};
161pub use prompt_caching::{PROMPT_CACHING_CAPABILITY_ID, PromptCachingCapability};
162pub use prompt_canary_guardrail::{
163    DEFAULT_REPLACEMENT as PROMPT_CANARY_DEFAULT_REPLACEMENT,
164    PROMPT_CANARY_GUARDRAIL_CAPABILITY_ID, PromptCanaryGuardrailCapability,
165    REASON_CODE_SYSTEM_PROMPT_LEAK,
166};
167pub use self_budget::{SELF_BUDGET_CAPABILITY_ID, SelfBudgetCapability};
168pub use skills::{SKILLS_CAPABILITY_ID, Skills, SkillsCapability};
169pub use skills_scoped::{
170    ScopedSkillsCapability, SkillDirResolver, SkillScope, SkillsConfig, VfsSkillDirResolver,
171};
172pub use soft_approval::{
173    ApprovalModeStore, PendingApproval, PendingApprovalStore, SOFT_APPROVAL_CAPABILITY_ID,
174    SessionApprovalModes, SoftApprovalCapability, render_approval_block,
175};
176pub use stateless_todo_list::{
177    STATELESS_TODO_LIST_CAPABILITY_ID, StatelessTodoList, StatelessTodoListCapability,
178    WriteTodosTool,
179};
180pub use system_commands::{SYSTEM_COMMANDS_CAPABILITY_ID, SystemCommandsCapability};
181pub use tool_approval::{
182    ApprovalDecision, ApprovalMode, TOOL_APPROVAL_CAPABILITY_ID, ToolApprovalCapability,
183    ToolApprover,
184};
185pub use tool_call_repair::{
186    DEFAULT_MAX_REPROMPTS, MAX_SALVAGE_INPUT_BYTES, RepairOutcome, SalvageResult,
187    TOOL_CALL_REPAIR_CAPABILITY_ID, ToolCallRepairCapability, ToolCallRepairConfig,
188    salvage_tool_arguments, tool_call_repair_capability,
189};
190pub use tool_output_distillation::{
191    DistillOutputHook, TOOL_OUTPUT_DISTILLATION_CAPABILITY_ID, ToolOutputDistillationCapability,
192};
193pub use tool_output_persistence::{
194    PersistOutputHook, TOOL_OUTPUT_PERSISTENCE_CAPABILITY_ID, ToolOutputPersistenceCapability,
195};
196pub use tool_search::{
197    TOOL_SEARCH_CAPABILITY_ID, TOOL_SEARCH_TOOL_NAME, ToolSearchCapability, ToolSearchTool,
198};
199pub use usage_limit_auto_continue::{
200    AutoContinueConfig, USAGE_LIMIT_AUTO_CONTINUE_CAPABILITY_ID, UsageLimitAutoContinueCapability,
201    resolve_usage_limit_auto_continue,
202};
203
204/// Internal compatibility namespace for the collocated implementations.
205#[allow(unused_imports)] // Individual implementation and test builds use different subsets.
206pub(crate) mod capabilities {
207    pub(crate) use crate::attach_skill;
208    pub(crate) use crate::{
209        AUTO_TOOL_SEARCH_CAPABILITY_ID, AutoToolSearchCapability, CLAUDE_TOOL_SEARCH_CAPABILITY_ID,
210        CURRENT_TIME_CAPABILITY_ID, CurrentTimeCapability, DEFAULT_TOOL_SEARCH_THRESHOLD,
211        FactsContext, OPENAI_TOOL_SEARCH_CAPABILITY_ID, OpenAiToolSearchCapability,
212        STATELESS_TODO_LIST_CAPABILITY_ID, StatelessTodoListCapability, TOOL_SEARCH_CAPABILITY_ID,
213        TOOL_SEARCH_TOOL_NAME, ToolSearchCapability, ToolSearchTool, Volatility,
214    };
215    pub(crate) use everruns_core::capabilities::*;
216}
217
218/// Register the runtime-safe portable capabilities in stable order.
219///
220/// This excludes [`UsageLimitAutoContinueCapability`], whose error hook needs
221/// a schedule store and poller that the default embedded host does not supply.
222pub fn register_runtime_capabilities(
223    registry: &mut CapabilityRegistry,
224) -> std::result::Result<(), CapabilityError> {
225    register_capabilities_atomically(registry, runtime_capabilities())
226}
227
228/// Register every portable capability in stable product order.
229///
230/// Registration is explicit: linking this crate has no inventory side effect.
231/// Existing IDs and alias collisions are rejected rather than silently
232/// replacing an application-provided implementation. A rejected bundle leaves
233/// the caller's registry unchanged.
234pub fn register_portable_capabilities(
235    registry: &mut CapabilityRegistry,
236) -> std::result::Result<(), CapabilityError> {
237    register_capabilities_atomically(registry, portable_capabilities())
238}
239
240/// Build a registry containing only portable capabilities.
241pub fn portable_capability_registry() -> std::result::Result<CapabilityRegistry, CapabilityError> {
242    let mut registry = CapabilityRegistry::new();
243    register_portable_capabilities(&mut registry)?;
244    Ok(registry)
245}
246
247/// Add the portable context-free tools that belong in a host's default
248/// executor registry.
249///
250/// Capability collection contributes the same tools when their owning
251/// capability is configured. Hosts also install these two legacy defaults so
252/// executor behavior remains compatible for blueprints and seed-time tooling.
253pub fn register_default_tools(registry: &mut everruns_core::ToolRegistry) {
254    registry.register(GetCurrentTimeTool);
255    registry.register(WriteTodosTool);
256}
257
258/// Add portable context-free tools safe for scheduled monitor probes.
259pub fn register_monitor_tools(registry: &mut everruns_core::ToolRegistry) {
260    registry.register(GetCurrentTimeTool);
261}
262
263fn portable_capabilities() -> Vec<Arc<dyn Capability>> {
264    let mut capabilities = runtime_capabilities();
265    capabilities.push(Arc::new(UsageLimitAutoContinueCapability));
266    #[cfg(feature = "ui-capabilities")]
267    {
268        capabilities.push(Arc::new(OpenUiCapability));
269        capabilities.push(Arc::new(A2UiCapability));
270    }
271    capabilities
272}
273
274fn register_capabilities_atomically(
275    registry: &mut CapabilityRegistry,
276    capabilities: Vec<Arc<dyn Capability>>,
277) -> std::result::Result<(), CapabilityError> {
278    let mut candidate = registry.clone();
279    for capability in capabilities {
280        candidate.try_register_arc(capability)?;
281    }
282    *registry = candidate;
283    Ok(())
284}
285
286fn runtime_capabilities() -> Vec<Arc<dyn Capability>> {
287    vec![
288        Arc::new(HumanIntentCapability),
289        Arc::new(InfinityContextCapability),
290        Arc::new(SkillsCapability),
291        Arc::new(AgentInstructionsCapability),
292        Arc::new(ChannelContextCapability),
293        Arc::new(CurrentTimeCapability),
294        Arc::new(MessageMetadataCapability),
295        Arc::new(StatelessTodoListCapability),
296        Arc::new(BtwCapability),
297        Arc::new(BudgetingCapability),
298        Arc::new(SelfBudgetCapability),
299        Arc::new(CompactionCapability),
300        Arc::new(ErrorDisclosureCapability),
301        Arc::new(OpenAiToolSearchCapability::new()),
302        Arc::new(ClaudeToolSearchCapability::new()),
303        Arc::new(ToolSearchCapability::new()),
304        Arc::new(AutoToolSearchCapability::new()),
305        Arc::new(PromptCachingCapability::new()),
306        Arc::new(ParallelToolCallsCapability),
307        Arc::new(NativeAsyncToolsCapability),
308        Arc::new(SystemCommandsCapability),
309        Arc::new(ToolOutputPersistenceCapability),
310        Arc::new(ToolOutputDistillationCapability),
311        Arc::new(LoopDetectionCapability),
312        Arc::new(ProgressGuardCapability::new()),
313        Arc::new(ToolCallRepairCapability),
314        Arc::new(PromptCanaryGuardrailCapability),
315        Arc::new(GuardrailsCapability),
316        Arc::new(SoftApprovalCapability::new()),
317    ]
318}
319
320#[cfg(test)]
321mod bundle_tests {
322    use super::*;
323
324    const RUNTIME_IDS: [&str; 29] = [
325        "human_intent",
326        "infinity_context",
327        "skills",
328        "agent_instructions",
329        "channel_context",
330        "current_time",
331        "message_metadata",
332        "stateless_todo_list",
333        "btw",
334        "budgeting",
335        "self_budget",
336        "compaction",
337        "error_disclosure",
338        "openai_tool_search",
339        "claude_tool_search",
340        "tool_search",
341        "auto_tool_search",
342        "prompt_caching",
343        "parallel_tool_calls",
344        "native_async_tools",
345        "system_commands",
346        "tool_output_persistence",
347        "tool_output_distillation",
348        "loop_detection",
349        "progress_guard",
350        "tool_call_repair",
351        "prompt_canary_guardrail",
352        "guardrails",
353        "soft_approval",
354    ];
355
356    fn portable_ids() -> Vec<&'static str> {
357        let mut ids = RUNTIME_IDS.to_vec();
358        ids.push("usage_limit_auto_continue");
359        #[cfg(feature = "ui-capabilities")]
360        {
361            ids.push("openui");
362            ids.push("a2ui");
363        }
364        ids
365    }
366
367    #[test]
368    fn bundle_has_the_curated_stable_catalog() {
369        let capabilities = portable_capabilities();
370        let ids: Vec<_> = capabilities
371            .iter()
372            .map(|capability| capability.id())
373            .collect();
374        assert_eq!(ids, portable_ids());
375    }
376
377    #[test]
378    fn runtime_bundle_excludes_schedule_backed_auto_continue() {
379        let mut registry = CapabilityRegistry::new();
380        register_runtime_capabilities(&mut registry).unwrap();
381
382        assert_eq!(registry.len(), RUNTIME_IDS.len());
383        assert!(!registry.has(USAGE_LIMIT_AUTO_CONTINUE_CAPABILITY_ID));
384    }
385
386    #[test]
387    fn duplicate_registration_is_rejected_without_replacement() {
388        let mut registry = portable_capability_registry().unwrap();
389        let original = Arc::clone(registry.get(HUMAN_INTENT_CAPABILITY_ID).unwrap());
390
391        let error = register_portable_capabilities(&mut registry).unwrap_err();
392
393        assert!(error.is_duplicate());
394        assert_eq!(error.id(), HUMAN_INTENT_CAPABILITY_ID);
395        assert!(Arc::ptr_eq(
396            &original,
397            registry.get(HUMAN_INTENT_CAPABILITY_ID).unwrap()
398        ));
399    }
400
401    #[test]
402    fn late_collision_does_not_partially_register_the_bundle() {
403        let mut registry = CapabilityRegistry::new();
404        registry.register(GuardrailsCapability);
405
406        let error = register_portable_capabilities(&mut registry).unwrap_err();
407
408        assert!(error.is_duplicate());
409        assert_eq!(error.id(), GUARDRAILS_CAPABILITY_ID);
410        assert_eq!(registry.len(), 1);
411        assert!(registry.has(GUARDRAILS_CAPABILITY_ID));
412        assert!(!registry.has(CURRENT_TIME_CAPABILITY_ID));
413    }
414
415    #[test]
416    fn dependencies_are_portable_or_explicit_host_seams() {
417        let registry = portable_capability_registry().unwrap();
418        let host_dependencies = ["session_file_system"];
419        let mut external_dependencies = std::collections::BTreeSet::new();
420
421        for capability in registry.list() {
422            for dependency in capability.dependencies() {
423                if !registry.has(dependency) {
424                    external_dependencies.insert(dependency);
425                }
426                assert!(
427                    registry.has(dependency) || host_dependencies.contains(&dependency),
428                    "{} has an undeclared external dependency on {dependency}",
429                    capability.id()
430                );
431            }
432        }
433
434        assert_eq!(
435            external_dependencies,
436            host_dependencies.into_iter().collect(),
437            "host-provided capability dependencies are an explicit structural boundary"
438        );
439    }
440}