A Stable Rust Tool Facade for Humans and AI: 52 Utilities, Explicit Intent
Rust does not have a shortage of good crates.
There is uuid for identifiers, chrono for time, rust_decimal for exact
decimal arithmetic, serde_json for JSON, and reqwest for HTTP. The challenge
in application development is often not finding a capability. It is keeping a
team fluent in many unrelated APIs while preventing infrastructure details from
spreading through business code.
AI coding makes that problem more visible. A model may understand the operation we want while mixing together method names from another language, another version, or another crate. The generated code looks plausible, but the API does not exist.
TeaQL Tool is our attempt to make
that surface smaller and more predictable. It places 52 common utilities behind
one T::xxx() facade, separates lightweight and dependency-heavy features, and
adds an optional context layer that requires code to state why a value is being
read, calculated, or changed.
It is partly inspired by libraries such as Hutool, but the interesting question is not how many helpers we can collect. It is whether a stable, narrow API can serve both application developers and coding agents without hiding Rust's underlying ecosystem.
The problem is API variance, not missing capability
A typical service quickly needs identifiers, timestamps, money, JSON, regular expressions, encoding, files, hashing, and HTTP. Using each underlying crate directly is entirely reasonable, especially when an application needs its full feature set.
For repeated business operations, however, every dependency introduces another construction pattern, error model, naming convention, and upgrade path. The business layer gradually learns more infrastructure than business semantics. An AI agent has an even larger API space in which to guess.
TeaQL Tool does not reimplement the ecosystem. It provides a small facade over selected, mature crates:
use teaql_tool::T;
let id = T::id().uuid();
let now = T::time().now();
let value = T::json().parse(r#"{"name":"TeaQL"}"#)?;
let digest = T::hash().sha256(b"hello");
The caller starts at T, chooses a semantically named tool, and uses a compact
operation set. The facade can evolve its implementation without requiring every
call site to know which crate performs the work.
How the 52 tools are divided
The workspace contains five crates:
teaql-tool-core
│
├── teaql-tool-std 26 standard tools
├── teaql-tool-extra 26 extension tools
│
└── teaql-tool unified T:: facade
│
└── teaql-tool-context
UserContext and intent adapters
teaql-tool-std contains 26 general-purpose tools: text, time, date ranges,
IDs, money, decimals, JSON, regex, encoding, hashes, files, lists, maps,
validation, masking, emoji, networking, colors, units, trees, and related
operations.
teaql-tool-extra contains 26 tools with heavier dependencies or more explicit
IO: HTTP, commands, archives, Excel, CSV, images, email, JWT, encryption,
barcodes, QR codes, templates, an embedded key-value store, caching, a static
file server, a reverse proxy, cron scheduling, and file watching.
teaql-tool is intentionally thin. It owns the public facade and feature
selection. The default minimal feature enables the standard tools; applications
opt into extra when they need the heavier integrations.
Until the crates are published independently, the facade can be used from the Git repository:
[dependencies]
teaql-tool = {
git = "https://github.com/teaql/teaql-rust-utils",
features = ["std", "extra"]
}
The project README contains the complete inventory and the current context coverage.
Why use a facade in Rust?
A facade has a straightforward benefit: discoverability.
let tomorrow = T::time().add_days(T::time().now(), 1);
let encoded = T::codec().base64_encode(b"Hello");
let masked = T::desensitize().chinese_phone("13812345678");
Developers and agents can search one namespace instead of rediscovering a dependency for every common operation. Naming can remain consistent across categories, and an underlying dependency upgrade does not automatically become an application-wide migration.
The same abstraction has a cost. A facade exposes a deliberately smaller API
than its dependencies. If an application needs detailed reqwest connection
pool control, the complete chrono type system, or advanced image encoding
parameters, it should use those crates directly. TeaQL Tool is a business
convenience layer, not a replacement for the Rust ecosystem.
Put the reason for an operation in the type flow
A predictable method name reduces API guessing. It does not explain why application code is reading a file, obtaining the current time, or starting a command.
teaql-tool-context explores a second idea: context-bound tools return wrappers
that require the caller to add intent before extracting a value or executing a
side effect.
The API distinguishes three meanings:
comment(...)explains a calculation;purpose(...)explains a read;audit_as(...)describes and executes a side effect.
For example:
use teaql_tool_context::prelude::*;
let now = ctx.time()
.now()
.comment("read the current time for the payment policy");
let deadline = ctx.time()
.add_days(now, 7)
.comment("calculate the payment grace period");
ctx.file()
.write_string("deadline.txt", deadline.to_rfc3339())
.audit_as("export the calculated payment deadline")?;
Calculation and read wrappers keep their inner values private. Code that needs the value must explicitly consume the wrapper through the matching intent method.
Side effects need a stronger boundary. MustAuditAs<T> stores a deferred action
rather than an already-computed result:
pub struct MustAuditAs<T> {
action: Option<Box<dyn FnOnce(String) -> T + Send + 'static>>,
}
impl<T> MustAuditAs<T> {
pub fn audit_as(mut self, description: impl Into<String>) -> T {
let action = self.action.take().expect("action executes once");
action(description.into())
}
}
If a caller drops this value without calling .audit_as(...), the deferred file
write, command, or email is not performed. Tests cover both paths: execution
after an audit description and no execution after the pending action is dropped.
These wrappers enforce that intent is supplied at the API boundary. How that description enters structured logs, traces, or an audit store remains an application-runtime integration concern. Separating those responsibilities keeps the type-level contract honest: collect intent first, route it through the appropriate runtime policy second.
Why a smaller API helps coding agents
An LLM can know that a capability exists while still confusing its crate, version, or exact method name. A facade narrows that generation space:
- entry points follow
T::xxx()orctx.xxx(); - related capabilities use a consistent naming style;
- underlying dependency changes need not propagate into business code;
- wrapper types let the compiler identify missing intent;
- project rules can disallow bypassing context-bound IO in application code.
This does not eliminate hallucinations. It changes the problem from guessing among many third-party APIs to selecting from a finite, project-owned surface, then lets the Rust compiler verify the result.
For us, this is part of a broader harness-engineering principle: if a coding rule matters repeatedly, move as much of it as possible from prompt prose into an executable interface.
Current boundaries
TeaQL Tool is an early project, and several boundaries are intentional or still in progress:
- The facade covers frequent operations, not every capability of every wrapped crate.
- Enabling
extraadds networking, image, spreadsheet, SMTP, and server dependencies; compile time and binary size should be measured rather than ignored. - Stable facade names create a compatibility responsibility for maintainers.
- The context layer currently covers all 26 standard tools, 21 extension tools, and a separate asynchronous HTTP adapter. Cron, proxy, server, and watcher adapters remain to be added.
- Intent wrappers are an enforcement boundary, but full audit-sink integration is still runtime-specific work.
The next steps are compatibility and compile-fail tests, feature-level build measurements, remaining context adapters, and deeper TeaQL runtime audit and trace integration.
A question for the Rust community
Rust's crate ecosystem is strongest when applications can use focused libraries directly. At the same time, business systems and coding agents benefit from small, stable, project-owned interfaces.
Where should that boundary sit?
Does a Hutool-style facade reduce accidental complexity in a Rust application, or does it hide crate boundaries that should remain explicit? For AI-generated code, is a stable, constrained API more valuable than direct access to every underlying capability?
TeaQL Tool is open source, and we would value concrete criticism of both the tool selection and the intent-wrapper design:
