basis/lib.rs
1//! basis — the in-process SDK of [basis](https://github.com/oops-rs/basis), an
2//! embeddable agent harness built on [Mentra](https://github.com/oops-rs/mentra).
3//!
4//! This crate is the harness itself: workspace discovery (AGENTS.md, skills,
5//! templates, `.basis/tools.json`, `.mcp.json`), the run lifecycle, one event
6//! stream, and the seams a host plugs into (approval, hooks). It carries no
7//! protocol, no transport and no terminal code, so an embedder's dependency
8//! graph states what they actually use (ADR-0011).
9//!
10//! Embedding surfaces, in order of preference:
11//!
12//! 1. **In-process**: depend on this crate (Rust hosts).
13//! 2. **ACP**: `basis-acp` serves the Agent Client Protocol (JSON-RPC 2.0 over
14//! stdio) over this crate's event stream, for editors and web UIs. It is
15//! reached from the binary with the explicit `basis serve --acp` command.
16//! 3. **Subprocess**: `basis spawn --json` streams JSONL events for scripts and CI;
17//! `basis run` remains a compatibility alias.
18//!
19//! The core has no opinions: task-specific behavior enters through data — the
20//! prompt, the workspace (AGENTS.md, skills, templates, `.mcp.json`), and
21//! config — never through code in this crate.
22//!
23//! # Three shapes
24//!
25//! [`Workspace`] is the SDK's shape (ADR-0010). Opening one settles everything
26//! that belongs to a repository rather than to a prompt — context documents,
27//! the resolved model, skills, templates, hooks, MCP connections — and then
28//! mints runs from it without doing any of that again:
29//!
30//! ```no_run
31//! # async fn example() -> Result<(), basis::RunError> {
32//! let workspace = basis::Workspace::open("/repo").await?;
33//! let mut run = workspace.prepare("what does this repo do?")?;
34//! let report = run.execute(basis::CollectingSink::default()).await?;
35//! # let _ = report;
36//! # Ok(())
37//! # }
38//! ```
39//!
40//! [`run`](run()) and [`run_with_approver`] are the one-prompt shape: a path
41//! and a prompt in, a report out, with a workspace opened and dropped around
42//! it. They are wrappers over the same open-and-prepare — so nothing behaves
43//! differently for having gone through one.
44//!
45//! [`Runtime`] is the process's shape (ADR-0018), and only the N-repository
46//! host sees it: what changes when the host changes — provider and credential,
47//! the history store, the host's interceptors — is built once and every
48//! workspace borrows it through an `Arc`. `Workspace::open` builds a private
49//! one behind the scenes, so the other two shapes never name it.
50//!
51//! # Features
52//!
53//! - **`mcp`** (default) — `.mcp.json` discovery and the MCP binding of the
54//! tool contract. Built without it, the crate has no MCP concept at all: no
55//! `McpConfig` on a run, no servers registered, and a run header that names
56//! none (ADR-0012). Custom tools remain, because MCP was only ever one of the
57//! ways to reach them: [`tools::declared`] is core, and a workspace's
58//! `.basis/tools.json` works in a build that has never heard of MCP.
59
60pub mod approval;
61pub mod branch;
62pub mod budget;
63pub mod compaction;
64pub mod config;
65pub mod context;
66pub mod error;
67pub mod event;
68mod expand;
69pub mod fingerprint;
70mod frontmatter;
71pub mod hooks;
72#[cfg(feature = "mcp")]
73pub mod mcp;
74pub mod memory;
75mod named_roots;
76mod paths;
77pub mod provider;
78pub mod run;
79pub mod runtime;
80pub mod shell;
81pub mod skills;
82pub mod store;
83mod subprocess;
84pub mod templates;
85pub mod tools;
86pub mod workspace;
87
88// `ToolSideEffectLevel` is mentra's, and comes to the root under the same rule
89// as `CancellationToken` below: `is_consequential` and
90// `ApprovalRequest::side_effect_level` both make a caller name it, and a policy
91// written against it should not cost the host a mentra dependency of its own.
92pub use approval::{
93 AllowAll, ApprovalAnswer, ApprovalDecision, ApprovalGate, ApprovalRequest, Approver, DenyAll,
94 ToolSideEffectLevel,
95};
96pub use branch::{BranchError, EntryKind, TranscriptEntry};
97pub use budget::BudgetPool;
98// Beside the other things a workspace is built *with*: what a run's history
99// keeps is set on `WorkspaceBuilder`, so the type naming it belongs where a
100// host already looks for `ShellAccess` and `ContextConfig`.
101pub use compaction::Compaction;
102pub use config::{
103 CONFIG_SCHEMA_VERSION, Config, ConfigError, DEFAULT_GLOBAL_CONFIG_FILE,
104 DEFAULT_WORKSPACE_CONFIG_FILE, Setting,
105};
106pub use context::{
107 ContextConfig, ContextDocument, ContextError, ContextScope, DEFAULT_CONTEXT_FALLBACK_FILE,
108 DEFAULT_CONTEXT_FILE, SystemPrompt, WorkspaceContext,
109};
110pub use error::RunError;
111pub use event::{
112 EVENT_SCHEMA_VERSION, ElidedToolResult, Event, EventLine, JsonlWriter, Mutability,
113 RequestToolResultElisionPolicy, RunOutcome, SkillSummary, TemplateSummary,
114 ToolResultContentKind, ToolResultElisionAction,
115};
116// `fingerprint::snapshot` keeps its module: at the crate root `snapshot` would
117// not say a snapshot of what, and the two types beside it are only meaningful
118// as its result.
119pub use fingerprint::{Fingerprint, Snapshot};
120pub use hooks::{
121 HOOK_SCHEMA_VERSION, HookCall, HookConfigError, HookEvent, HookOutcome, HookRequest,
122 HookResponse, HookRunner, HookSpec, HooksConfig, HooksSource, Interceptor, InterceptorError,
123 OnFailure,
124};
125#[cfg(feature = "mcp")]
126pub use mcp::{
127 DEFAULT_GLOBAL_MCP_FILE, DEFAULT_WORKSPACE_MCP_FILE, McpConfig, McpError, McpServer, McpSource,
128};
129// The memory *configuration* comes to the root, beside its siblings
130// `SkillsConfig` and `TemplatesConfig` — a host pointing basis at different
131// memory roots is doing the same thing it does for those. `Memory` and its
132// kind stay in `memory`, next to the convention they only make sense beside.
133pub use memory::{MemoryConfig, WorkspaceMemoryRoot};
134pub use run::{
135 AgentEvent, AgentEventTapGuard, Bound, Bounds, CancellationToken, CollectingSink, Compacted,
136 ContentBlock, Effort, EventFanIn, EventSink, FnSink, MergedEvents, ModelInfo, NullSink,
137 OutputAttempt, OutputAttemptReport, OutputDecision, OutputReport, OutputReservation,
138 OutputSpec, PreparedRun, PromptPart, ReasoningChange, ReasoningOptions, ReasoningSummary,
139 RoundAdjustment, RoundBoundary, RoundContext, RoundDecision, RoundStrategy, RoundToolResult,
140 RunContext, RunFailure, RunFailureCategory, RunReport, RunUsage, TaggedEvent, TaggedSink,
141 TurnOptions, run, run_with_approver,
142};
143pub use runtime::{Runtime, RuntimeBuilder, RuntimeRecipe, ToolResultPolicy};
144pub use shell::ShellAccess;
145// Mentra's, deliberately. These are the types basis's own surface asks a
146// caller to *name* — a model to resolve, a provider to prefer, a provider to
147// *be* ([`RuntimeBuilder::with_provider_instance`]), the complete request and
148// paging options a [`RunProfile`] carries, or the provider-core family a host
149// customizes before [`RuntimeBuilder::with_registered_provider`]. Re-exporting
150// them is what keeps that from meaning "add mentra to your manifest, pinned to
151// whatever version basis happens to resolve". A skew there is a type error
152// with no explanation in it. Everything else mentra owns stays behind
153// `mentra::`, where an embedder that wants the runtime itself already is —
154// except what implementing `Provider` touches, which is [`runtime`]'s
155// provider-authoring re-export, beside the executor set and for its reason.
156pub use mentra::provider_core;
157pub use mentra::{
158 BuiltinProvider, ModelSelector, Provider, ProviderRequestOptions, ToolResultPagingConfig,
159};
160// The attribute both of basis's async traits make an implementor spell:
161// `Approver` and `Interceptor` are `#[async_trait]`, so without this line
162// writing either impl means adding `async-trait` to the host's own manifest —
163// a dependency basis's docs used to ask for without saying so. Same rule as the
164// mentra types above, applied to a macro.
165pub use async_trait::async_trait;
166pub use skills::{SkillsConfig, SkillsSource};
167// `store::list` keeps its module: at the crate root `list` would not say what
168// is being listed, and `PersistedSession` is only meaningful beside it.
169pub use store::PersistedSession;
170pub use templates::{Template, TemplateError, TemplateSource, TemplatesConfig};
171// `tools::spawn` keeps its module: `SpawnTool` at the crate root would sit
172// beside a dozen types that are not tools, and the name an operator writes in a
173// rule or a hook is only meaningful next to the tool it names. `ChildContext`
174// and `ChildSpec` come to the root anyway, because the caller that names them
175// is not writing a tool — `RuntimeBuilder::with_child_policy` is a builder
176// knob like every other, and its vocabulary belongs beside the builder's.
177pub use tools::{ChildContext, ChildSpec, SpawnTool};
178// The declared binding's *configuration* comes to the root, beside its
179// siblings `HooksConfig`, `SkillsConfig` and `TemplatesConfig` — a host
180// pointing basis at a different manifest is doing the same thing it does for
181// those, and `DeclaredToolError` is what a failed open hands back. The tool
182// type and the declaration stay in `tools::declared`, next to the format they
183// only make sense beside.
184pub use tools::declared::{
185 DEFAULT_GLOBAL_TOOLS_FILE, DEFAULT_WORKSPACE_TOOLS_FILE, DeclaredToolError,
186 TOOLS_SCHEMA_VERSION, ToolsConfig, ToolsSource,
187};
188pub use workspace::{RunProfile, RunSpec, ToolRoster, Workspace, WorkspaceBuilder};