Background
Custom tools live as tools/<name>.md (descriptor) + tools/<name>.py (implementation), discovered by walking the filesystem. Skills live as skills/<name>/SKILL.md + supporting files, also discovered by filesystem walk.
The discovery + load path is hard-coded:
- Read directory entries
- Parse
<name>.md for descriptor
- Import
<name>.py (custom tools) or read instructions inline (skills)
- Build registry, register with the agent's tool loop
Loading a tool from a database, a remote registry, or a private package index isn't expressible.
Why it matters
The tools/skills primitives are the natural extension surface for atomic-agents. Filesystem-only loading caps the ecosystem at "files on the box." Things filesystem-only blocks:
- Plugin marketplace. A registry of community tools (think pip, npm) installable per-agent. Today an operator copies a tool's two files into
tools/. With a registry: agent.tools.install("github.com/someone/web-scraper").
- Versioned tools. Tool X at version 1.2 vs 1.3, with the agent pinning a version. Filesystem has no version semantics.
- Sandboxed execution. Some tools should run in a separate process / container. Loading them inline as Python imports is the wrong shape; the protocol could express "run this tool's body in a subprocess by id."
- Per-agent tool subset. SaaS tenants get different tool catalogs. Today every agent walks its own filesystem; a
MultiTenantRegistryBackend selects the catalog by tenant.
- Audit / approval flows. "Operator must approve before tool X is loaded." Hooks at the registry level, not buried in agent init.
What to change
- New module
atomic_agents/registry/ with backend.py (Protocol) + filesystem.py (default wrapping current FS walks).
ToolRegistryBackend protocol exposes:
list_tools() → list of ToolRef
load_tool(name) → Tool (descriptor + invocable handle)
list_skills() / load_skill(name) (mirrors memory's pattern of progressive disclosure for skills — see spec/13)
install(source, version) / uninstall(name) (optional capability)
validate(name) (sandbox check before live load)
agent.__init__ picks up agent.registry.list_tools() and registers them through the existing ToolRegistry.
- Skills loader (
load_skill / load_skill_file framework tools) reroutes through agent.registry.load_skill_file(name, path).
- Spec doc
docs/spec/25-registry-backend.md.
Future backends
PyPIToolRegistryBackend — install tools from PyPI as atomic-agents-tool-* packages
GitToolRegistryBackend — install tools from git URLs (pinned by SHA)
RemoteRegistryBackend — internal company registry (auth + RBAC)
LocalDevRegistryBackend — fast iteration, no install step
Acceptance
- Existing custom tools tests + skills tests pass with
FilesystemToolRegistryBackend as default.
- Protocol conformance suite (~12 tests) — list, load, validate, install/uninstall (capability-gated).
- One non-FS mock backend proves the protocol holds.
- Existing tool-collision detection (
ToolNameCollision, MCP review) survives the indirection.
Open questions
- Does
MCPServerRegistry (currently mcp.md → server registry) merge into this, or stay separate? They're shaped differently (servers are processes, tools are functions) but conceptually both are tool catalogs.
- Versioning semantics: pip-style (one version per agent), npm-style (multiple versions coexist), git-style (SHA-pinned)? Probably pin-by-version per-agent, surfaced through the profile backend.
- Sandboxing protocol: explicit
run_sandboxed(tool, args) separate from load_tool? Or capability-gated within load_tool?
Context
Background
Custom tools live as
tools/<name>.md(descriptor) +tools/<name>.py(implementation), discovered by walking the filesystem. Skills live asskills/<name>/SKILL.md+ supporting files, also discovered by filesystem walk.The discovery + load path is hard-coded:
<name>.mdfor descriptor<name>.py(custom tools) or read instructions inline (skills)Loading a tool from a database, a remote registry, or a private package index isn't expressible.
Why it matters
The tools/skills primitives are the natural extension surface for atomic-agents. Filesystem-only loading caps the ecosystem at "files on the box." Things filesystem-only blocks:
tools/. With a registry:agent.tools.install("github.com/someone/web-scraper").MultiTenantRegistryBackendselects the catalog by tenant.What to change
atomic_agents/registry/withbackend.py(Protocol) +filesystem.py(default wrapping current FS walks).ToolRegistryBackendprotocol exposes:list_tools()→ list ofToolRefload_tool(name)→Tool(descriptor + invocable handle)list_skills()/load_skill(name)(mirrors memory's pattern of progressive disclosure for skills — see spec/13)install(source, version)/uninstall(name)(optional capability)validate(name)(sandbox check before live load)agent.__init__picks upagent.registry.list_tools()and registers them through the existingToolRegistry.load_skill/load_skill_fileframework tools) reroutes throughagent.registry.load_skill_file(name, path).docs/spec/25-registry-backend.md.Future backends
PyPIToolRegistryBackend— install tools from PyPI asatomic-agents-tool-*packagesGitToolRegistryBackend— install tools from git URLs (pinned by SHA)RemoteRegistryBackend— internal company registry (auth + RBAC)LocalDevRegistryBackend— fast iteration, no install stepAcceptance
FilesystemToolRegistryBackendas default.ToolNameCollision, MCP review) survives the indirection.Open questions
MCPServerRegistry(currently mcp.md → server registry) merge into this, or stay separate? They're shaped differently (servers are processes, tools are functions) but conceptually both are tool catalogs.run_sandboxed(tool, args)separate fromload_tool? Or capability-gated withinload_tool?Context
MemoryBackendfrom PR refactor(memory): extract MemoryBackend protocol; FilesystemBackend default #57