-
Notifications
You must be signed in to change notification settings - Fork 1
Add canonical tool policy vocabulary #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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 Additional
|
||
| edition = "2024" | ||
| rust-version = "1.88" | ||
| license = "GPL-3.0-only" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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" | ||
| ``` | ||
|
|
||
|
|
@@ -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 | | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Document only APIs that exist This adds a public [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 | | ||
|
|
@@ -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 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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" } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update Cargo.lock for the workspace version bump The dependency now requires the local [RULE] lockfile-consistency · |
||
| tracing = { workspace = true, optional = true } | ||
|
|
||
| [features] | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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" | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep the package README path valid Cargo resolves this path relative to Additional
|
||||||||||
| readme = "README.md" | |
| readme = "../../README.md" |
[RULE] missing-file ·
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| readme = "README.md" | |
| readme = "../../README.md" |
[RULE] invalid-path ·
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Document every direct dependency used by the example The example also names
Suggested change
[RULE] incomplete-dependency-guidance · |
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update the lockfile with the workspace version bump
Changing the inherited workspace package version changes the
tinytoolspackage metadata recorded inCargo.lock, but this pull request does not update that committed lockfile. Commands run with--lockedwill reject the repository until the lockfile's workspace package entry is regenerated; regenerate and commitCargo.locktogether with this change.[RULE] stale-lockfile ·