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
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ exclude = ["worktrees"]
# true`, so the version the release workflow bumps is written in exactly one
# place and every crate moves together.
[workspace.package]
version = "0.1.0"
version = "0.2.0"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium critique likely

Update the lockfile with the workspace version bump

Changing the inherited workspace package version changes the tinytools package metadata recorded in Cargo.lock, but this pull request does not update that committed lockfile. Commands run with --locked will reject the repository until the lockfile's workspace package entry is regenerated; regenerate and commit Cargo.lock together with this change.

[RULE] stale-lockfile ·

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium tests confident

Update the lockfile with the workspace version bump

The workspace version was bumped from 0.1.0 to 0.2.0, and the dependency on tinytools in crates/tinytools-agent/Cargo.toml was also bumped. The Cargo.lock file must be regenerated (via cargo update -p tinytools) and committed to keep the lockfile consistent with the resolved dependency versions.


Additional security observation

priority medium confident

Update the lockfile with the workspace version bump

[RULE] lockfile-consistency

The workspace package version now differs from the version recorded for the package in Cargo.lock. Commit the regenerated lockfile so reproducible builds and release checks use the same workspace version.

[RULE] missing-lockfile-update ·

edition = "2024"
rust-version = "1.88"
license = "GPL-3.0-only"
Expand Down
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ re-export the macro. Add it as a direct dependency alongside `tinytools`:

```toml
[dependencies]
tinytools = "0.1"
tinytools = "0.2"
async-trait = "0.1"
```

Expand All @@ -60,6 +60,7 @@ compiles neither the harness nor the host.
| `spec` | `ToolSpec` — the declaration a model is shown |
| `permission` | `PermissionLevel` — the privilege ladder, ordered `None` → `Dangerous` |
| `classification` | `ToolScope`, `ToolCategory` — where a tool may run, and which belt it is on |
| `policy` | `ToolPolicy`, `ToolRuntime`, `ToolAccess`, `ToolSideEffects`, `WorkspaceAccess`, `ToolDisplay` — declarative execution requirements and presentation metadata |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium critique likely

Document only APIs that exist

This adds a public policy module and six public types to the README's module table, but the repository context's documented TinyTools surface contains no policy module or these types. If the API is not actually present, users will follow this table and fail to compile. Remove this row or add the corresponding implementation and re-export before documenting it.

[RULE] documentation-contract ·

| `call` | `ToolCallOptions`, `ToolTimeout` — per-invocation inputs that are not arguments |
| `context` | `ToolRunContext` — the narrow seam onto a live run |
| `workspace` | `WorkspaceDescriptor`, `SandboxMode` — the root a tool may touch, and how strictly it is sandboxed |
Expand Down Expand Up @@ -110,6 +111,32 @@ Two consequences worth knowing:
point. Overriding only `external_effect` on a tool whose classification
depends on its arguments leaves the per-call case unhandled.

`Tool::policy()` carries the complete declarative form for hosts that need a
single auditable projection: side effects, runtime requirements, access
requirements, and display metadata. It defaults to unclassified; a host may
therefore reject undeclared tools without TinyTools making an admission decision.
The policy is descriptive only. It never enforces approval, credentials,
sandboxing, cancellation, deadlines, retries, workspace containment, or result
limits.

`ToolTimeout` has three stable JSON forms:
`{ "mode": "inherit" }`, `{ "mode": "unbounded" }`, and
`{ "mode": "millis", "timeout_ms": <u64> }`. Policy field names and omission
rules are likewise wire contracts for persisted policy and registry
introspection; literal-wire tests pin every timeout variant and a fully
populated policy declaration.

## Injected arguments and call identity

`ToolCall` / `ToolCallId` carry a model request's identity outside
`ToolResult`, where a harness can correlate it with its own events and elapsed
time. `Tool::injected_arguments()` declares schema keys owned by either the
host or the call id. A harness uses `project_injected_arguments` for the
model-facing schema, then runtime-only `InjectedToolArguments` and
`prepare_tool_arguments` to strip model-supplied protected values, inject the
authoritative values, and validate the returned object. Host values are never
serialized or exposed to the model.

## Development

```bash
Expand Down
2 changes: 1 addition & 1 deletion crates/tinytools-agent/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ readme = "README.md"
regex = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tinytools = { path = "../tinytools", version = "0.1.0" }
tinytools = { path = "../tinytools", version = "0.2.0" }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium critique likely

Update Cargo.lock for the workspace version bump

The dependency now requires the local tinytools package at version 0.2.0, matching [workspace.package], so the committed Cargo.lock entry for tinytools must also be regenerated. Without that update, locked builds can fail because the manifest and lockfile disagree. Regenerate and commit the lockfile with this version bump.

[RULE] lockfile-consistency ·

tracing = { workspace = true, optional = true }

[features]
Expand Down
2 changes: 1 addition & 1 deletion crates/tinytools/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ license.workspace = true
repository.workspace = true
description = "The agent tool vocabulary: the `Tool` trait, its result and spec types, and the permission, scope and timeout classifications a host enforces around a call."
documentation = "https://docs.rs/tinytools"
readme = "../../README.md"
readme = "README.md"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority high critique confident

Keep the package README path valid

Cargo resolves this path relative to crates/tinytools/, but the repository has no crates/tinytools/README.md; the documented README is the repository-root README.md. Commands such as cargo package -p tinytools will fail when Cargo tries to read the missing file. Point the manifest back to the existing root README.


Additional security observation

priority high confident

Point the package metadata at the existing README

[RULE] invalid-package-metadata

Cargo resolves readme relative to the package directory, so this points at crates/tinytools/README.md, which does not exist; the repository README is one directory above. Commands such as cargo package --list -p tinytools and publishing will fail until the path is restored.

Suggested change for this observation (reference only)

readme = "../../README.md"

Suggested change for the opening observation

Suggested change
readme = "README.md"
readme = "../../README.md"

[RULE] missing-file ·

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority high critique confident

Point the package metadata at the existing README

readme is resolved relative to crates/tinytools/Cargo.toml, but there is no crates/tinytools/README.md; the actual README is at the repository root. cargo package will therefore fail or omit the intended package README. Keep the existing relative path to the root README.

Suggested change
readme = "README.md"
readme = "../../README.md"

[RULE] invalid-path ·

keywords = ["llm", "agents", "tools", "tool-calling"]
categories = ["asynchronous", "api-bindings"]

Expand Down
102 changes: 102 additions & 0 deletions crates/tinytools/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# TinyTools

`tinytools` is the dependency-light vocabulary shared by a tool implementation
and the host or harness that runs it. Its primary entry points are `Tool`,
`ToolSpec`, `ToolResult`, `ToolRunContext`, and `ToolPolicy`.

```rust
use tinytools::{Tool, ToolPolicy, ToolResult};

struct Echo;

#[async_trait::async_trait]
impl Tool for Echo {
fn name(&self) -> &str { "echo" }
fn description(&self) -> &str { "Returns its input unchanged." }
fn parameters_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": { "text": { "type": "string" } },
"required": ["text"],
})
}
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {
let text = args.get("text").and_then(|value| value.as_str()).unwrap_or_default();
Ok(ToolResult::success(text))
}
fn policy(&self) -> ToolPolicy {
ToolPolicy::read_only()
}
}
```

## Public contract

`Tool` describes a callable capability. Its result is a `ToolResult` block list
with a reported-error flag and optional markdown rendering. `ToolSpec` is the
model-visible declaration. `ToolRunContext` exposes only tool-relevant run
facts: workspace, thread id, and output cap.

`ToolPolicy` is the complete host-readable declaration around a call:

- `ToolSideEffects` records filesystem, network, dependency, destructive,
external-service, payment, and read-only effects.
- `ToolRuntime` records deadline, retry, idempotency, cancellation, sandbox,
result-size, and streaming requirements.
- `ToolAccess` records workspace reach, trusted roots, credential names,
approval, and background-safety requirements.
- `ToolDisplay` supplies optional timeline/audit label and detail text.

`Tool::policy()` defaults to an unclassified declaration. A host that admits
tools fail-closed can refuse that default. The policy is descriptive only:
TinyTools does not enforce approvals, credentials, workspace containment,
sandboxing, cancellation, deadlines, retries, or result limits.

## Injected arguments and call identity

`ToolCall` and its `ToolCallId` carry the identity of one parsed model request.
That identity is invocation metadata, not a `ToolResult` field, so a harness can
correlate events and results without mutating tool-owned output.

A tool declares host-owned schema keys with `Tool::injected_arguments()`, using
`ToolInjectedArgument::host` or `ToolInjectedArgument::tool_call_id`. The host
uses `project_injected_arguments` for the model-facing schema, keeps
authoritative values in runtime-only `InjectedToolArguments`, and calls
`prepare_tool_arguments` before schema validation. The helper strips the
model-supplied protected keys, inserts authoritative values, and returns the
object to validate. It never serializes host values, and it ignores values for
undeclared keys. This ordering prevents a model from forging a protected
argument while keeping model arguments model-owned.

`ToolTimeout` is serializable and has exactly three wire forms:

| Rust value | JSON |
| --- | --- |
| `ToolTimeout::Inherit` | `{ "mode": "inherit" }` |
| `ToolTimeout::Unbounded` | `{ "mode": "unbounded" }` |
| `ToolTimeout::Millis(250)` | `{ "mode": "millis", "timeout_ms": 250 }` |

All policy vocabulary uses the documented serde field names. These forms are
part of the public persistence and introspection contract; change them only
with an intentional compatibility migration and updated literal-wire tests.
`ToolCall` and `ToolInjectedArgument` are likewise serialized vocabulary;
`InjectedToolArguments` intentionally is not, because it may hold credentials.

## Boundaries

TinyTools has no registry, dispatch loop, provider transport, runtime, or agent
harness dependency. `tinytools-agent` owns model-facing tool-call parsing,
dialects, catalogues, rendering, and transcript replay. Hosts own execution and
all policy decisions.

## Development

```sh
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo build --all-targets --all-features
cargo test --all-features
```

`Tool` implementations require a direct `async-trait` dependency because the
crate uses the macro internally but does not re-export it.
Comment on lines +101 to +102

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium critique confident

Document every direct dependency used by the example

The example also names serde_json and anyhow in the Tool implementation, but the closing guidance only tells tool authors to add async-trait. A downstream crate following this README therefore fails to compile unless it independently declares the other two direct dependencies as well; they are not made available merely because tinytools depends on them. Mention all three dependencies here, or rewrite the example to use re-exported or otherwise available types.

Suggested change
`Tool` implementations require a direct `async-trait` dependency because the
crate uses the macro internally but does not re-export it.
`Tool` implementations require direct dependencies on `async-trait`, `anyhow`, and
`serde_json` because the example uses them and the crate does not re-export them.

[RULE] incomplete-dependency-guidance ·

Loading
Loading