Skip to main content

Workspace

Struct Workspace 

Source
pub struct Workspace { /* private fields */ }
Expand description

One workspace, resolved: the runtime it borrows, the model, and everything discovered on disk, ready to mint runs from.

Held by reference by every run it mints, so a host keeps one per repository for as long as it wants to send prompts at it. Dropping it does not end the runs already minted — a PreparedRun owns its session — but the MCP connections go with it, and so does the runtime when this held the last Arc.

Send and Sync: the runtime is shared through Arcs and creates sessions from &self, so concurrent minting from one workspace needs no lock of basis’s own.

§What a workspace’s guards do and do not cover

Everything a repository says about itself — its .basis/hooks.json chain, its ShellAccess posture, its .git carve-out, its memory roots — reaches a run through the session this workspace mints: the policy is stated per session, and the hook chain is registered for this workspace’s own ToolAudience, which every session minted or resumed here carries and every delegated child inherits.

A session this workspace did not mint carries none of it, even in this directory. An agent a host creates for itself through mentra_runtime, or drives through run::prepare_with_session, has no tool audience at all, and mentra never consults an audience-scoped registration for such an agent — so a .basis/hooks.json deny hook guarding this repository is not asked about that agent’s calls, and its policy is the runtime’s rather than this workspace’s, however its base directory is spelled. basis routed on the call’s working directory before mentra could scope a registration, and that is what changed. The two things a host can do about it: register a RuntimeBuilder::with_interceptor guard, which is global and therefore judges every session on the runtime; or mint through prepare and resume, which is what puts a run inside this workspace’s guards in the first place.

Implementations§

Source§

impl Workspace

Source

pub async fn open(path: impl Into<PathBuf>) -> Result<Self, RunError>

Opens path with basis’s defaults: a private runtime with the provider auto-detected from the environment, the newest model it offers, and every convention discovered where convention says to look.

builder is the same call with the knobs exposed — including with_runtime, for the host that opens many workspaces on one Runtime.

Source

pub fn builder(path: impl Into<PathBuf>) -> WorkspaceBuilder

Configures a workspace before opening it.

Source

pub fn prepare(&self, spec: impl Into<RunSpec>) -> Result<PreparedRun, RunError>

Mints a run: a fresh conversation against this workspace.

Synchronous, and deliberately so — everything expensive already happened. What this does is spawn a session on the existing runtime and hand back the PreparedRun that drives it.

The spec’s prompt may be empty. Once a session outlives a turn, a conversation with nothing said yet is a real state — it is what ACP’s session/new opens — so the emptiness check belongs where a prompt is actually sent, which is PreparedRun::execute_with_approver and PreparedRun::send_with_options. (The free run keeps its own up-front check, because a one-shot caller that passed nothing wants to hear about it before a session exists.)

Source

pub fn resume( &self, agent_id: &str, spec: impl Into<RunSpec>, ) -> Result<PreparedRun, RunError>

Picks up a conversation a previous process left behind.

agent_id is PreparedRun::agent_id, not the session id: mentra persists agents, and a session is one process’s view of one. Resuming replays the transcript from the store, so the first turn after this already knows everything the last one did.

Resuming is also where a “…for this session” approval answer dies (see ApprovalDecision). A new one needs nothing basis does about it: mentra 0.27 remembers it into PermissionRuleScope::Process (mentra#53), a rung owned by the live session that gave it and never written to the runtime store, so resume_session_with_options already hands back a session with a fresh handle and an empty rung, whatever the agent id. A row a pre-0.12 basis binary remembered into the durable Session scope instead is a different matter — mentra still loads and matches those — so the attach still clears that one scope for exactly that legacy case, and a corrupt or unwritable rules.json fails the resume with RunError::SessionRulesNotCleared naming the file to repair or delete. Project- and global-scope rules are durable by definition and are unaffected either way.

The workspace has to be the one the conversation belongs to, and this checks it: mentra’s store is keyed by agent rather than by path, so an id alone cannot say where its conversation ran, and a resume restates this workspace’s policy, tool audience and persisted-row tag onto whatever it is given. Under another repository’s conversation those are the wrong .git carve-out, the wrong shell posture and the wrong roster, while the agent stays based in its own directory — which mentra’s file tools always allow writes under. So an agent based somewhere other than root is refused with RunError::WorkspaceMismatch, before the resume touches it at all. A caller that means “one of mine” takes the id from store::list for this workspace.

mentra does not persist a model’s context window (Agent::from_loaded always resumes at None — set_model is the only way back), so a resumed session starts with an unknown one. This reapplies this workspace’s own resolved model exactly when the resumed conversation is still on it and this resume is not also changing reasoning — the same model prepare would have minted — which restores mentra’s own compaction threshold as well as PreparedRun::context_window. A reasoning-changing resume leaves the window unknown so it performs one persisted mutation rather than two. A conversation PreparedRun::set_model had already moved elsewhere keeps whatever that call left it at instead: basis has no window for a model it does not resolve, and forcing this one back would silently undo a choice the caller made.

Source

pub fn fingerprint(&self) -> Snapshot

A cheap stand-in for everything in this workspace a run could see.

The utility ADR-0014 kept when watch was deleted, on the type its ledger row promised it to. The semantics are crate::fingerprint’s verbatim: a digest over git ls-files plus HEAD, stat only, and every uncertain answer resolving to changed rather than unchanged.

Fingerprints the workspace as it is now, not as it was when the workspace was opened — that is the whole point, since a caller’s loop asks it repeatedly against one long-lived workspace.

Blocking: it spawns git and stats files. An async caller belongs on a blocking thread — tokio::task::spawn_blocking, or the equivalent.

Source

pub fn path(&self) -> &Path

The directory this workspace’s runs are scoped to.

The same value as root, and deliberately so: a caller that opened a relative or symlinked spelling gets back the directory that spelling named, not the spelling. Both names are kept because hosts use both.

Source

pub fn root(&self) -> &Path

The workspace root: absolute, symlinks followed, resolved once at open. What the run header reports, what the agent is based in, what the runtime’s policy roots are built from, and what fingerprint reads — one directory under one spelling, so workspace and context_files can never disagree.

Source

pub fn model(&self) -> &str

The model every run from this workspace uses, resolved once.

Source

pub fn provider(&self) -> &str

Source

pub fn context(&self) -> &WorkspaceContext

The context documents discovered at open, weakest precedence first.

Source

pub fn memories(&self) -> &[Memory]

The memories discovered at open, name-ordered, a workspace memory shadowing a global one of the same name.

Frontmatter only: the index in the system prompt is what a memory costs by default, and the body stays on disk for the model — or a host showing its user what the agent remembers — to read on demand. See crate::memory for the convention.

Source

pub fn skills(&self) -> &[LoadedSkill]

Every skill a run from this workspace could reach at open, after layering, name-ordered.

On a private runtime that is exactly this workspace’s four roots. On a shared one (ADR-0018) the registry is the runtime’s and additive, so it is this workspace’s roots and whatever a sibling workspace open at the time had registered — which is what a run can actually load_skill, and therefore the honest answer to what this reports. LoadedSkill::root is how to tell the two apart: a root under root is this repository’s.

A snapshot, taken once at open like everything else here. A sibling that opens afterwards adds skills this list does not name, and one that drops takes its own away — since mentra 0.24 a workspace hands its roots back when it goes, so a shared runtime no longer accumulates the skills of every repository a host has ever opened on it.

Source

pub fn templates(&self) -> &[Template]

The prompt templates this workspace defines, after layering, name-ordered. Over ACP these become the client’s commands.

Source

pub fn mcp_servers(&self) -> &[String]

The MCP servers connected at open, by the names that took effect — which is the configured name unless another workspace on the shared runtime already held it, in which case it carries a deterministic suffix. Names only: nothing here echoes a command or a credential.

Source

pub fn declared_tools(&self) -> &[String]

The tools this workspace’s manifests declared, by name, after layering — this workspace’s own first, name-ordered within each manifest.

Names only, for mcp_servers’s reason: nothing here echoes a command or a credential.

Source

pub fn host_tools(&self) -> &[String]

The native tools the host supplied for this workspace (WorkspaceBuilder::with_tool), by name, in the order it supplied them.

Names only, for mcp_servers’s reason. Reported beside declared_tools because the two are one question for anything looking at a run — what did this open put within the model’s reach that basis did not? — and the answer is not otherwise readable: these are registered for this workspace’s audience, which mentra’s own registry readers do not walk.

Source

pub fn config(&self) -> &Config

What config.json said about this workspace, and which file said it.

The answers here are already in force — the model below is what they resolved to — so this is for the host that reports its own configuration, or that wants to hand the same value to a shared Runtime’s builder rather than read the files twice.

Source

pub fn config_files(&self) -> &[ContextFile]

The config files that took effect, most specific first.

Reported the way .mcp.json’s and .basis/tools.json’s sources are, and for a milder version of their reason: what this file decides — which model, which provider — a run already names in its own header, so the question left is which file said so, and a repository that resolved a model nobody expected should be able to find out in one place.

Source

pub fn declared_tool_files(&self) -> &[ContextFile]

The tool manifests that took effect, most specific first.

A file that says which programs the model may run is the last thing that should apply invisibly, which is why discovery reports its sources the way .mcp.json’s does.

Source

pub fn mentra_runtime(&self) -> &Runtime

The mentra runtime the runs are minted on, for a host that wants mentra’s own surface — the task board, teams, the store — alongside basis’s.

The same bargain as PreparedRun::session: basis does not hide mentra, and reaching past basis’s surface is a supported thing to do rather than a workaround. Renamed from runtime() when ADR-0018 gave basis a Runtime of its own, so the name says whose surface comes back.

Trait Implementations§

Source§

impl Debug for Workspace

Hand-written because neither the runtime nor the registration is Debug material, and because the context documents hold whole files — a derived impl would dump them.

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more