Skip to main content

Modularity

Four specs that share a sandbox variant, a toolset and a set of conventions should not drift apart by hand. Two mechanisms stop them, and they answer different questions:

extends: loop-base:0.0.1 # inheritance โ€” "the same agent, adjusted"
includes: # composition โ€” "this bundle, wherever needed"
- notebook-surfaces:0.0.1

Which oneโ€‹

Extension when the new spec is the old one with changes โ€” a specialist that behaves like the base agent but with a different model or an extra instruction. One parent, and the relationship is "a kind of".

Composition when several unrelated specs need the same capability โ€” the notebook surfaces, a standard toolset. Many includes, and the relationship is "has".

The distinction earns its keep in one specific way. If the notebook specialists inherited from a base agent that declares subagents, each specialist would inherit that subagent list โ€” and @NotebookCompactor could delegate to @NotebookCompactor. Shared capability goes in a fragment; only the base agent declares subagents. There is then no cycle for a depth cap to catch.

Resolved before anything runsโ€‹

Both are resolved when the catalogs are generated (make specs), so the generated Python and TypeScript stay flat, the runtime keeps no inheritance logic, and the result of a merge is visible in a diff rather than inferred at startup.

That last point is not only tidiness. A resolved spec is what travels to a running pod, where the companion process forwards it without interpreting a single field โ€” an unresolved extends would arrive somewhere that cannot understand it.

Fragments are applied first and extends second, so a parent can override what a fragment brought in.

Merge semanticsโ€‹

The rules both mechanisms obey.

Field kindRule
Scalars (model, sandbox_variant, icon, memory)the child wins
Lists (tools, skills, mcp_servers, frontend_tools, tags, suggestions)append, deduplicated by id โ€” a version suffix does not make an entry different
Keyed collections (frontend_render_tools)merged entry by entry, on tool
system_promptreplaced; system_prompt_prepend / system_prompt_append layer onto the parent's

Lists append because the common case is "the parent's tools plus mine". Two markers cover the cases where that is wrong:

tools:
- "!remove delete-cells:0.0.1" # drop one thing the parent granted
- my-own-tool:0.0.1
tools:
- "!replace" # start from nothing
- only-this:0.0.1

!remove is what a least-privilege specialist needs: an agent that may read and edit a notebook but must never delete from it.

Inspecting the resultโ€‹

Composition is resolved at generation time, so the flattened spec is what the generated catalog contains โ€” read it there, or diff it after make specs. A merge rule that changes shows up as a diff in the generated file, which is the point of resolving early rather than at runtime.