agenthold is a shared versioned state store provided as an MCP server, used to coordinate concurrent operations in multi-agent AI workflows.
Purpose The core problem agenthold solves is the silent overwrite when multiple agents read and write the same value at the same time. For example, two agents each read a $10,000 budget and allocate it independently, committing $15,000 in total, yet the budget object does not raise an error. In this kind of read-modify-write conflict, the second write silently destroys the first write, no exception is thrown, and the system keeps running but the data is wrong. agenthold provides agents with shared, versioned state and built-in conflict detection.
Main capabilities
- Optimistic concurrency control (OCC): every stored value has a version number. When writing, you pass in the version you read; if the stored version has changed, the write is rejected and the current value is returned. The agent re-reads, recalculates, and retries. This is the same mechanism used by Postgres's
UPDATE ... WHERE version = Nand DynamoDB conditional writes. - Version guarantee: the version of each key starts at 1 and increases by exactly 1 on each write.
state_historytable is append-only, recording every write before updating the active record, so history remains consistent after a crash. Deletions also first write a tombstone (event_type: "delete") before removing the active record. Ordering guarantees are per-key, not global. - Coordination tools (five by default):
agenthold_register(register and obtain a unique agent ID; must be executed once before calling claim/release),agenthold_claim(declare exclusive access to a resource before modifying it; requires a registered agent_id; if the resource has non-trivial history, the response will include previous_outcome, previous_holder, previous_outcome_at, and a hint; for moved it will provide moved_to),agenthold_release(release a claim with an explicit outcome; the outcome is stored in the idle-state record and shown to the next claimant; outcomes include released, modified, created, deleted, moved),agenthold_status(check whether a resource is available or already claimed; registration is not required),agenthold_wait(block and wait for a claimed resource to become available, until the holder releases it or it times out). - Advanced primitives (
--tools advanced): eight low-level toolsagenthold_get、agenthold_set、agenthold_list、agenthold_history、agenthold_delete、agenthold_watch、agenthold_clear_namespace、agenthold_export, providing direct read, write, and watch access to the versioned state store, with full OCC conflict detection. This mode does not send server instructions. - Resource identification: use canonical URIs. Tools accept bare paths (such as "src/main.py", resolved to a workspace named default, or the only configured workspace) or explicit URIs (for files, "file://<workspace>/<path>", and for opaque resources, "custom://<name>"). Equivalent inputs (./src/main.py, src\main.py, src//main.py, an absolute path within the workspace) are all normalized to the same internal URI. Path traversal (..) and dot segments (.) are rejected at the boundary. On case-insensitive file systems (Windows and macOS by default), file resources match case-insensitively, and src/Main.py and src/main.py resolve to the same resource; custom:// names are always case-sensitive.
- Multiple workspaces: configure with
--workspace name=path(repeatable). Without this flag, a single default workspace is created in the current working directory. - Transport: stdio by default; the MCP client spawns one agenthold subprocess per agent, and these subprocesses coordinate through a SQLite file shared on the same machine. For cross-process, cross-container, or cross-host coordination, run a long-lived Streamable HTTP server and have all agents connect via URL; the five standard tools and the advanced tools behave the same under HTTP. HTTP mode can set
--claim-ttlEnables automatic reclamation of claims held by disconnected agents. - Authentication: HTTP mode can require a bearer token. Use
--auth-tokenor the environment variableAGENTHOLD_AUTH_TOKEN(multiple tokens separated by commas; tokens themselves must not contain commas). Requests without a valid token return 401 before reaching storage. Tokens are compared in constant time. Authentication applies only to--transport http(stdio is a trusted local transport). This is all-or-nothing authentication; per-namespace scoping is still on the roadmap. If authentication is requested but all tokens are empty, the server refuses to start. - Security: the privacy of bearer tokens depends on the bearer transport; real deployments should put TLS in front (reverse proxy). By default it binds to 127.0.0.1 (localhost only); binding outside localhost without
--auth-tokenprints a startup warning.--allowed-host <host>(repeatable) enables DNS rebinding (Host header) protection; note that this is not authentication and does not replace--auth-token. - Known limitations: agenthold does not yet reclaim idle agent records, so long-running servers with frequent agent turnover will accumulate agent metadata. It is recommended to set
--claim-ttland restart periodically. Automatic reclamation is on the roadmap. - Read-only inspector: the agenthold command doubles as a read-only inspector, allowing you to view agent activity without an MCP client. The command is strictly read-only (opens the database with query_only, does not write schema) and will not modify storage. Each command accepts
--db PATH(default ./agenthold.db) and--json. With no subcommand, it still starts the MCP server as usual. - Python library usage: it can also be called directly as a Python library without using MCP. Import
StateStore, use the version-checked.get()and.set()Implement conflict-safe shared state.
Usage As an MCP server: after adding it to the MCP client configuration, agents coordinate automatically, with no need for CLAUDE.md, no system prompt modification, and no need to design namespaces. Once connected, the agent sees five self-describing tools, and the tool descriptions explain when and how to use them. Use as a Python library: import StateStore, call .get()/.set() with version checks. Conflict detection retry loop: read the value and its version; when writing, pass expected_version; if the version has changed, catch ConflictError, reread the current value, recompute, and retry. The version number is the contract: if the stored version has advanced, it means another agent wrote first. The number of retries is bounded by the number of concurrent writers, and in practice agents almost never conflict more than once. HTTP coordination: run a long-lived agenthold server listening on Streamable HTTP, and MCP clients connect by URL rather than by command.
Applicable scenarios
- Multi-agent local single-machine operation (stdio mode, coordinated through a shared SQLite file).
- Agent coordination across processes, containers, or hosts (Streamable HTTP).
- Situations requiring versioned, conflict-safe shared state for agent working memory.
- Situations requiring exclusive access to be claimed before modifying files or resources, and allowing later claimants to understand what previous ones did. The repository contains two examples with before and after scripts: order processing (two agents concurrently updating the same order record) and budget allocation (two agents drawing from a shared marketing budget).
Why use OCC instead of locks: locks require the holder to release them, and the system must handle crashes, timeouts, and stale holders; when conflicts are rare, this is not worth it. OCC pays the cost of one extra read and one retry only when a conflict actually occurs. Agents do a large amount of work between reads and writes (LLM reasoning, API calls, tool execution), and cannot hold a database lock across that work.
Why use SQLite: zero dependencies, included in the Python standard library, and runs everywhere. Enabling WAL mode keeps read-only operations (export, listen) from blocking cross-process writers. Write transactions use BEGIN IMMEDIATE acquire the write lock in advance, ensuring that OCC conflict detection works correctly when multiple agenthold processes share the same database file.busy_timeout set to 5 seconds, the second writer waits instead of failing immediately. The storage backend sits behind a clean StateStore interface and can be swapped for Postgres when needed.
Changes at production scale: the main one is replacing SQLite with Postgres for better concurrent write throughput, replication, and hosting. The StateStore interface has been designed to make this change controlled. Network transport and authentication already exist. In production, TLS should be added in front via a reverse proxy; per-namespace/tenant token scoping is not yet implemented.
License and development: MIT license, see LICENSE. CI runs on Python 3.11 and 3.12 on every push to main. For the specific commands and detailed guidance for coverage checks, lint, and type checking, see CONTRIBUTING.md.