Cross-service registry and persistence layer for tau_agent — providers, agents, tools, skills, configs, and chat sessions, with encrypted API keys.
Project description
tau_hub
A cross-service persistence and registry layer for tau_agent — the portable harness layer of the tau coding agent.
tau_hub adds a shared database layer so multiple services can create, load, and share providers, agents, tools, skills, configs, and chat sessions — without duplicating configuration or bootstrapping logic across processes. Provider API keys are encrypted at rest.
Why
tau_agent is intentionally stateless and portable — it has no file I/O, no CLI, and no resource-loading. tau_hub fills that gap for multi-service environments: it owns the storage layer and exposes a clean API for agent lifecycle management.
Features
- Providers —
register_provider(...)/get_provider(name)with API keys encrypted at rest (Fernet, AES-128-CBC + HMAC-SHA256) - Agents —
register_agent(...)/get_agent(name)for shared system prompts - Tools —
register_tool(...)/get_tool(name); executors round-trip through the DB viadill - Skills —
register_skill(...)/get_skill(name); reusable prompt extensions attachable to configs - Configs —
register_config(...)/get_config(name)assembles a ready-to-runAgentHarnessConfig(provider + agent + tools + skills) - Sessions —
hub.session_storage(session_id)is a drop-in replacement for Tau'sJsonlSessionStorage, storing durable chat history in any hub backend instead of local JSONL files, plus JSONL import/export for migration - Backend-agnostic — pluggable
BaseAgentStoreinterface; ships with TinyDB (default), SQLite, MongoDB, Redis, and PostgreSQL - Zero-configuration quick-start — the TinyDB backend requires no external services
Installation
# Default install — TinyDB backend (pure Python, no external services)
pip install tau-hub
# With MongoDB support
pip install tau-hub[mongo]
# With Redis support
pip install tau-hub[redis]
# With PostgreSQL support
pip install tau-hub[postgres]
# Everything
pip install tau-hub[all]
Or from source:
git clone https://github.com/mmpouya/tau_hub
cd tau_hub
pip install -e .
Core dependencies (installed automatically): tinydb, tau-ai, dill, cryptography.
Quick Start
import asyncio
from tau_hub import TauHub
from tau_agent.harness import AgentHarness
async def main():
# TinyDB backend by default (./.tau_hub/tau_hub.json).
# The secret key enables encryption of provider API keys at rest.
hub = TauHub(secret_key="my-shared-secret")
await hub.init_db()
# Register once (e.g. from an admin service)...
await hub.register_provider(
name="claude",
provider_class="AnthropicProvider",
api_key="sk-ant-...", # stored encrypted, never in plaintext
base_url=None,
model_name="claude-sonnet-4-5",
)
await hub.register_agent("assistant", system="You are a helpful assistant.")
await hub.register_skill(
"metric_units",
description="Prefer the metric system.",
content="Always answer using metric units.",
)
await hub.register_config(
"assistant_config",
agent_name="assistant",
provider_name="claude",
tool_names=[],
skill_names=["metric_units"],
)
# ...and load a ready-to-run harness anywhere else.
config = await hub.get_config("assistant_config")
harness = AgentHarness(config)
async for event in harness.prompt("Hello, who are you?"):
print(event)
asyncio.run(main())
Encrypted API keys
Pass a secret_key when constructing TauHub (or set the TAU_HUB_SECRET_KEY environment variable). Every service that shares the hub database must use the same secret key.
from tau_hub import TauHub, SecretBox
# Generate a strong key once and keep it in your secrets manager:
print(SecretBox.generate_key()) # e.g. 'qERt3...44 chars...='
hub = TauHub("postgres://user:pass@localhost/tau", secret_key="<that key>")
register_provider(...)encryptsapi_keybefore it is written; the DB only ever seesenc::v1::<token>.get_provider(name)/get_config(name)decrypt transparently.- Reading an encrypted key without a secret key raises
MissingSecretKeyError; a wrong key raisesDecryptionError. - Plaintext keys written by older tau_hub versions remain readable, so you can migrate gradually (re-register providers to encrypt them).
- If no secret key is configured, keys are stored in plaintext and a warning is logged (legacy behaviour).
Keys are derived from the passphrase with PBKDF2-HMAC-SHA256 (600k iterations); a raw 44-char Fernet key is used directly.
DB-backed chat sessions
Tau's tau_coding layer stores sessions as JSONL files under ~/.tau/sessions/. With tau_hub you can keep them in the hub database instead — shared across services and backed up together with everything else:
hub = TauHub("mongodb://localhost:27017")
# Drop-in replacement for tau_agent's JsonlSessionStorage —
# implements the same SessionStorage protocol (append / read_all):
storage = hub.session_storage("session-2026-07-11")
await storage.append(entry) # SessionEntry models or dicts
entries = await storage.read_all() # typed SessionEntry models
# Housekeeping
await hub.list_sessions() # {session_id: entry_count}
await hub.delete_session("session-2026-07-11")
# Migrate existing JSONL sessions into the DB (and back out)
await hub.import_session_jsonl("old-session", "~/.tau/sessions/abc.jsonl")
await hub.export_session_jsonl("old-session", "/tmp/abc.jsonl")
Entries are stored with exactly the same JSON shape as Tau's JSONL lines, so import/export is lossless. On MongoDB and PostgreSQL, appends are atomic ($push / jsonb concatenation), making concurrent writers safe.
Skills
Skills are reusable prompt extensions, mirroring Tau's user skills:
await hub.register_skill(
"code_review",
description="Reviews code rigorously.",
content="When reviewing code, check for correctness, security, and style.",
config={"severity": "strict"},
)
skill = await hub.get_skill("code_review") # -> Skill dataclass
print(skill.as_prompt_section()) # '## Skill: code_review\n...'
await hub.list_skills() # {name: Skill}
await hub.delete_skill("code_review")
Attach skills to a config via skill_names=[...]; get_config appends each skill to the agent's system prompt as a ## Skill: <name> section.
Using a different backend
Pick a backend with a URL...
TauHub() # TinyDB (default)
TauHub("sqlite:./tau_hub.sqlite3") # SQLite
TauHub("mongodb://localhost:27017") # MongoDB
TauHub("redis://localhost:6379") # Redis
TauHub("postgres://user:pass@localhost/tau") # PostgreSQL
...or pass a pre-built store for full control:
from tau_hub import TauHub
from tau_hub.db.mongo import MongoStore
hub = TauHub(store=MongoStore(uri="mongodb://localhost:27017", db="tau"))
Call await hub.init_db() once at startup (creates tables / connects pools where needed) and await hub.close() on shutdown.
Architecture
┌─────────────┐ ┌─────────────┐ ┌───────────────┐
│ Service A │ │ Service B │ │ Service C │
└──────┬──────┘ └──────┬──────┘ └────────┬──────┘
│ │ │
└───────────────────┼─────────────────────┘
│
┌──────▼──────┐
│ tau_hub │ ← this package
└──────┬──────┘
│
┌──────────┬──────┴─────┬────────────┐
│ │ │ │
┌─────▼───┐ ┌────▼────┐ ┌─────▼───┐ ┌──────▼────┐
│ TinyDB │ │ SQLite │ │ MongoDB │ │ Postgres │ ...
│(default)│ │ │ │ │ │ / Redis │
└─────────┘ └─────────┘ └─────────┘ └───────────┘
tau_hub depends on an abstract BaseAgentStore interface. The public API (register_provider, get_config, session_storage, etc.) calls only this interface — never a concrete backend directly. You swap backends by passing a different store_url/store= at construction time.
Collections
All entities are stored as flat, independent documents — no relational joins needed:
| Collection | Key | Value |
|---|---|---|
providers |
name |
{provider_class, api_key (encrypted), base_url, model_name} |
agents |
name |
{system} |
tools |
name |
{description, input_schema, executor (dill+base64)} |
skills |
name |
{description, content, config} |
config |
name |
{agent_name, provider_name, tool_names, skill_names} |
sessions |
name |
{entries: [<tau session entry>, ...]} |
Backends
TinyDB (default)
Pure-Python JSON document store. No external service required. Best for single-process or single-writer deployments.
⚠️ TinyDB is not ACID-compliant and does not handle concurrent writes safely across multiple processes. If you run multiple workers writing to the same database simultaneously, use the SQLite or Postgres backend instead.
from tau_hub.db.tinydb import TinyDBStore
store = TinyDBStore(path="tau_hub.json")
SQLite
Standard-library backend using a single generic documents table with WAL journaling. ACID, no external service, good for local multi-writer setups.
from tau_hub.db.sqlite import SQLiteStore
store = SQLiteStore("sqlite:./tau_hub.sqlite3") # or a plain path / ":memory:"
MongoDB (requires tau-hub[mongo])
from tau_hub.db.mongo import MongoStore
store = MongoStore(uri="mongodb://localhost:27017", db="tau")
Session appends use atomic $push — safe for concurrent writers.
Redis (requires tau-hub[redis])
Stores documents as JSON strings under prefix:collection:name keys. Suitable when Redis is already in your stack and you want sub-millisecond reads.
from tau_hub.db.redis import RedisStore
store = RedisStore(url="redis://localhost:6379", prefix="tau")
PostgreSQL (requires tau-hub[postgres])
Uses a single documents table with (collection, name, data jsonb). Good for multi-process concurrent writes; session appends are atomic jsonb concatenations.
from tau_hub.db.postgres import PostgresStore
store = PostgresStore(dsn="postgresql://user:pass@localhost/tau")
Implementing a Custom Backend
Subclass BaseAgentStore from tau_hub.db.base:
from tau_hub.db.base import BaseAgentStore
class MyStore(BaseAgentStore):
async def get(self, collection: str, name: str) -> dict | None: ...
async def put(self, collection: str, name: str, data: dict, **extra) -> None: ...
async def delete(self, collection: str, name: str) -> None: ...
async def batch_get(self, collection: str) -> list[dict]: ...
# Optional overrides (have working defaults):
# async def init_db(self) -> None: ...
# async def close(self) -> None: ...
# async def append_to_list(self, collection, name, field, item) -> None: ...
Override append_to_list with your database's native atomic list-append if it has one — sessions use it on every appended entry.
Security notes
- API keys are encrypted with authenticated symmetric encryption (Fernet). The secret key itself is never stored; keep it in a secrets manager or environment variable, not in code.
- Tool executors are serialized with
dill; loading a tool executes arbitrary code on deserialization. Only share a hub database with services and people you trust.
Running the tests
The test suite uses only the standard library plus cryptography, so it runs without any database services:
python -m unittest discover -s tests -v
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tau_hub-0.3.0.tar.gz.
File metadata
- Download URL: tau_hub-0.3.0.tar.gz
- Upload date:
- Size: 29.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
22ca32966d451ec8ccef6726e9366dd946072cb07569c7b41be158fff802e889
|
|
| MD5 |
2ce37dcbc5a5c84ec91f32a4e28abbbd
|
|
| BLAKE2b-256 |
1858fd6af9cec5ae810aa79fdb5c29c3e67d355e7960bccf610f4468d45b084f
|
File details
Details for the file tau_hub-0.3.0-py3-none-any.whl.
File metadata
- Download URL: tau_hub-0.3.0-py3-none-any.whl
- Upload date:
- Size: 29.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4ef26bab83a0c9d95a6974733f044596e8e93e7b80285c18a30a2a586a7b60d9
|
|
| MD5 |
862f47990c6edcb6cedc6e1acaf71a5e
|
|
| BLAKE2b-256 |
0b922dd19df53022399058c39a7035569119ba243cdd4529f21b4ac6812bfb76
|