name: cave
description: Drive the whole cave-teams library from one function, as DATA — build/run any team from a spec in any sequence, save a team to your proven library, reuse a proven team anywhere, and discover teams across projects. Use when you want to serialize a team, run a team from JSON/dict, save+reuse a proven team across repos ("golden"), or have a higher system / global app drive cave-teams. NOT how you normally program (use the cave-teams DSL for that). Triggers: "cave()", "metacontrol", "run from a spec", "team as data", "golden team", "reuse a proven team", "scan for .cave", "global cave".
cave — the metacontrol function
This is NOT how you program with cave-teams. You program with the native API — the >> / |
DSL and the pattern functions (see the cave-teams skill and each cave-<pattern> skill). That
is ergonomic and direct.
cave() is the layer above: one super-compiled function that can call everything in the
native API, in any sequence, from a plain-data spec. Reach for it when you want to: serialize a
team as data, run a team from JSON/a dict, save and reuse a proven team across projects, or let a
higher system (or a global app) drive the whole library through one door.
Run any team from a spec
A spec is a tree of {"op": ..., ...} nodes. cave() builds it (dispatching op recursively over
the native API) and runs it. It never throws — it returns a uniform envelope.
import asyncio
from cave_teams import cave, registered_ops
spec = {"op": "seq", "links": [
{"op": "par", "links": [
{"op": "agent", "name": "security"},
{"op": "agent", "name": "perf"}]},
{"op": "agent", "name": "ship"}]}
result = await cave(spec, context={"goal": "ship the feature"})
await cave(spec, describe_only=True)
registered_ops()
Each cave-<pattern> skill gives that pattern's op shape. On a malformed spec you get
{"status": "construction_error", "error": ..., "hint": "read the cave-<op> skill"} — the error
names the exact skill to read. Construction errors (you built the spec wrong) are distinct from
runtime_error (it ran and broke).
Ops are SLOTS you fill — not a closed menu
An op is a NAME registered to a builder that returns a Link (a step). The canonical ops
(seq/par/gate/agent/tournament/…) are just pre-registered builders. A slot can hold:
{"op": "seq", "links": [
{"op": "call", "import": "pkg.mod.fn",
"input_key": "goal", "output_key": "cleaned"},
{"op": "call", "fn": "my_registered_callable"},
{"op": "golden", "name": "ship_crew"},
]}
call — put ANY function in a slot: "import": "module.function" (dynamic import) or
"fn": "<register_fn name>"; input_key/output_key map its IO. The code must already exist —
the spec points at it, it is not inlined (that's the difference from using the lib in Python).
golden — load a proven config by name as a step (see the library section below).
- extend with
register(op, builder) / register_fn(name, callable); a goldenized team is an op too.
Two runtimes through the one door
cave()'s default execute runs the composition in-process (a function threading a dict). To run
the leader-driven plane instead (a leader routes messages between agents over files/inboxes with
guardrail conditions — the run_team/cave_team runtime), use the team_run op:
from cave_teams import register_fn
register_fn("leader_rt", my_leader_runtime)
register_fn("maker_rt", my_maker_runtime)
await cave({"op": "team_run",
"topology": {"op": "seq", "links": [{"op": "agent", "name": "maker"},
{"op": "agent", "name": "reviewer"}]},
"leader": "leader_rt", "runtimes": {"maker": "maker_rt", "reviewer": "..."},
"task": "build and review"})
The proven-team library (build once, reuse forever)
Save a team you trust, then drop it into any project as one building block.
await cave(spec, name="ship_crew", save=True)
await cave(goldenize=True, name="ship_crew")
from cave_teams import golden
release = planner >> golden("ship_crew") >> publish
await cave(search="ship")
Goldenizing is the one un-automated arc: the agent proposes (save → quarantine), the human
approves (goldenize). A goldenized team becomes a callable op — that's how the metacontrol surface
grows.
Discover teams across projects (for a global cave)
from cave_teams import scan_caves, scan_library
scan_caves(roots=["~/work"])
scan_library(roots=["~/work"], kind="golden")
The roots you pass are the boundary — nothing outside them is touched, heavy dirs
(node_modules/.git/…) are pruned. A global .cave (a separate app) uses scan_caves to
enumerate and federate every cave-enabled project.
Extend it
from cave_teams import register, register_fn
register("my_pattern", lambda s: my_builder(...))
register_fn("my_mutator", fn)
The native API is the instruction set; cave() is the interpreter; register/register_fn +
goldenize extend the instruction set.
See also
cave-teams (the language) · every cave-<pattern> skill (each pattern's op shape)
Sync, no daemon, never throws. Part of the cave-teams plugin.