Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ crates/
├── classification/ # `ToolScope`, `ToolCategory`
├── call/ # `ToolCallOptions`, `ToolTimeout`
├── context/ # `ToolRunContext`
└── naming/ # rendering a call for a human
├── naming/ # rendering a call for a human
└── shared/ # `SharedTool`: an `Arc<dyn Tool>` as an owned belt entry
# each: mod.rs / types.rs / mod_tests.rs
docs/
├── specs/ # behavior and architecture specifications
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ compiles neither the harness nor the host.
| `context` | `ToolRunContext` — the narrow seam onto a live run |
| `workspace` | `WorkspaceDescriptor`, `SandboxMode` — the root a tool may touch, and how strictly it is sandboxed |
| `naming` | `humanize_tool_name`, `context_detail_from_args` — rendering a call for a human |
| `shared` | `SharedTool`, `share_belt`, `owned_belt` — one built `Arc<dyn Tool>` handed out as many owned `Box<dyn Tool>` belts, forwarding every trait method |
| `rank` | `ToolRanker`, `RankCandidate`, `RankHit`, `Bm25Ranker` — ranking a catalogue of tools against an intent, with the lexical ranker built in |

The workspace also contains `tinytools-agent`, a separate crate for
Expand Down
9 changes: 9 additions & 0 deletions crates/tinytools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ facts: workspace, thread id, and output cap — plus `host_extension()`, the
same type-erased escape hatch `Tool::host_extension` offers, so a tool written
against one specific harness can downcast to that harness's full context.

## Sharing one tool across owned belts

A host that builds a tool once and keeps it as `Arc<dyn Tool>` can still hand a
harness the owned `Vec<Box<dyn Tool>>` it asks for each turn: `share_belt`
moves a built belt into `Arc`s, and `owned_belt` mints a fresh owned belt of
`SharedTool` handles over them. Each handle forwards **every** `Tool` method —
the defaulted declarations included — so a wrapped write-level, effectful or
hidden tool never reads as the trait default.

## Rich tool returns

`ToolContent` has four block kinds: `Text`, `Json`, `Image`, and `File`.
Expand Down
4 changes: 4 additions & 0 deletions crates/tinytools/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
//! - [`context`] — [`ToolRunContext`], the narrow seam onto a live run.
//! - [`workspace`] — [`WorkspaceDescriptor`], the root a tool may touch.
//! - [`naming`] — rendering a call for a human.
//! - [`shared`] — [`SharedTool`], [`share_belt`] and [`owned_belt`]: one
//! built `Arc<dyn Tool>` handed out as many owned `Box<dyn Tool>` belts.
//! - [`rank`] — [`ToolRanker`], ranking a catalogue of tools against an
//! intent, and the lexical [`Bm25Ranker`] every host gets for free.
//!
Expand Down Expand Up @@ -111,6 +113,7 @@ pub mod permission;
pub mod policy;
pub mod rank;
pub mod result;
pub mod shared;
pub mod spec;
pub mod tool;
pub mod workspace;
Expand Down Expand Up @@ -141,6 +144,7 @@ pub use rank::{
Bm25Index, Bm25Ranker, RankCandidate, RankContext, RankError, RankHit, ToolRanker, tokenize,
};
pub use result::{FileData, ImageData, ToolContent, ToolControl, ToolErrorKind, ToolResult};
pub use shared::{SharedTool, owned_belt, share_belt};
pub use spec::ToolSpec;
pub use tool::{Tool, ToolExposure};
pub use workspace::{SandboxMode, WorkspaceDescriptor};
46 changes: 46 additions & 0 deletions crates/tinytools/src/shared/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//! Sharing one built tool across many owned belts.
//!
//! A host commonly builds an agent's tools **once** and keeps them as
//! `Arc<dyn Tool>`, because the same instance is wanted in several places — a
//! per-agent pool, an MCP server re-exporting the belt, a catalogue index. A
//! harness, meanwhile, often asks for an owned `Vec<Box<dyn Tool>>` and asks
//! for it **per turn**, because the session it hands the belt to is rebuilt
//! between turns and a `Box<dyn Tool>` cannot outlive it.
//!
//! [`SharedTool`] bridges the two: a thin `Box` around the `Arc`, minted per
//! turn, delegating every call to the one shared instance. No tool is rebuilt,
//! no state is duplicated, and a tool holding a connection or a cache keeps
//! holding exactly one. [`share_belt`] and [`owned_belt`] convert a whole belt
//! in each direction.

mod types;

use std::sync::Arc;

use crate::tool::Tool;

pub use types::SharedTool;

/// Moves a built belt into shared handles, so a host can keep one copy and
/// hand others out without rebuilding any tool.
#[must_use]
pub fn share_belt(belt: Vec<Box<dyn Tool>>) -> Vec<Arc<dyn Tool>> {
belt.into_iter().map(Arc::from).collect()
}

/// The shared belt as an owned one, for a single turn.
///
/// Call it wherever a harness wants owned tools — typically once per turn from
/// an agent's tool factory. Each entry is a [`SharedTool`] pointing at the
/// same instance; the tools themselves are not rebuilt.
#[must_use]
pub fn owned_belt(shared: &[Arc<dyn Tool>]) -> Vec<Box<dyn Tool>> {
shared
.iter()
.map(|tool| Box::new(SharedTool::new(Arc::clone(tool))) as Box<dyn Tool>)
.collect()
}

#[cfg(test)]
#[path = "mod_tests.rs"]
mod tests;
Loading
Loading