docs/book/src/architecture/decisions/ADR-004-tool-shared-state-ownership.md
This is a restored retroactive record. The original ADR was added under
docs/architecture/adr-004-tool-shared-state-ownership.md and was
dropped during the mdBook migration. The code paths have moved into
workspace crates since the original record; this restored version keeps
the accepted decision intact while updating path references where useful.
ZeroClaw tools execute in a multi-client environment where a single daemon process can serve multiple connected clients and agent sessions. Some tools need long-lived shared state:
Those states cannot all be treated the same way. Some are legitimate shared display or registry state. Some are security-sensitive and must be isolated per client or session.
Without a shared contract, new tools risk introducing duplicate state, data leaks between clients, stale state after reloads, or blocking startup validation in the wrong lifecycle phase.
Tools may own long-lived shared state when they follow the handle pattern and respect daemon-owned identity, isolation, lifecycle, and reload rules.
When a tool legitimately owns shared state, it uses a cloneable handle
passed at construction time, usually an Arc<RwLock<T>> or a narrow
wrapper around one.
Examples in the current workspace include:
| Handle | Current location | Purpose |
|---|---|---|
DelegateParentToolsHandle | crates/zeroclaw-runtime/src/tools/mod.rs | Parent-tool list for delegate agents |
PerToolChannelHandle | crates/zeroclaw-runtime/src/tools/mod.rs | Per-tool channel map handle |
ChannelMapHandle aliases | crates/zeroclaw-tools/src/ask_user.rs, poll.rs, reaction.rs | Tool-local channel maps |
CanvasStore | crates/zeroclaw-tools/src/canvas.rs | Shared canvas frames |
Tools that need shared state must:
The daemon owns client and session identity. Tools must not construct their own durable client identity keys from transport details such as IP addresses, headers, usernames, or channel-specific sender strings.
Tools that need per-client namespacing consume identity assigned by the daemon or receive an already scoped handle. Tools that do not need per-client isolation may ignore the identity surface, but they must not invent a parallel one.
Tool lifecycle has four phases:
Drop or an explicit
shutdown method when the owner provides one.Validation state derived from config, credentials, policy, or external resources must be invalidated when the source changes. Non-security display state may survive reloads only when the reload does not affect its validity.
State that can leak credentials, policy, quotas, user data, or session data must be isolated per client, agent, or session according to the owning surface. A shared handle must not store per-client secrets unless the key space is scoped by daemon-owned identity.
State that is naturally shared, such as broadcast display state, read-only registry data, or channel handles, may be shared across clients. When it uses string keys, it should support namespace prefixing or trace metadata so operators can still filter by client, agent, channel, or session.
Config-derived validation and caches are invalid after the relevant config, credential, policy, workspace, or provider source changes. The tool must either re-resolve from the source of truth at use time or receive a fresh handle/config-derived value from the owner.
The reload rule is about validity, not registry mutation. A tool may keep non-security display state across reloads only when the reload does not affect that state's validity.
Positive consequences:
Negative consequences:
AGENTS.mdcrates/zeroclaw-runtime/src/tools/mod.rscrates/zeroclaw-tools/src/ask_user.rscrates/zeroclaw-tools/src/poll.rscrates/zeroclaw-tools/src/reaction.rscrates/zeroclaw-tools/src/canvas.rscrates/zeroclaw-api/src/tool.rscrates/zeroclaw-gateway/src/lib.rs