Skip to content

11. The extension contract, as core defines it

Core discovers extension manifests without knowing any extension in advance, and contains no extension ID. The contract is version 1.1. Each extension has a UTF-8 JSON manifest named familiar-extension.json at the root of its package. extension-manifest.schema.json is the authority for machine checks, and extension-contract.md is its prose companion.

Required fields: contract (always 1.1), id, display_name, version (semver), core (a compatible version range), type (workflow | specialty | enforcement | integration), namespace, contributions, capabilities, unavailable_behavior, package. Optional fields: compatible_with, conflicts.

contributions requires four arrays: routes, skills, agents and state_surfaces. It allows two more arrays for project-lifecycle pieces, checkin_fragments and track_presets. Each is a list of file paths relative to the package. A route is an object with id, skill and select_when. A state surface is the four-field object in §6.5.

capabilities requires required and optional arrays. unavailable_behavior is hide, degrade or report-unavailable. It applies when an optional capability is missing. package requires update_source.

There is no separate list of extensions. An extension exists exactly when its skill folder is on disk in an agent’s skill folder, with its own familiar-extension.json manifest. A route’s host_status is therefore worked out when it is read. The agent compares the manifest’s required and optional capabilities with the table in .familiar/installed.json. The result is not stored anywhere.

Nothing checks an extension when you add it. There is no installer or validator at run time (§12). Adding an extension is a plain folder merge.

If you build an extension from familiar-template, work through the template’s CONTRACT-CHECKLIST.md, and run its check_extension.py where the template includes it. Core runs no check on it.

The agent evaluates capabilities for each agent and records the result under host_status. If a required capability is missing, the extension is unavailable on that agent. If an optional capability is missing, hide, degrade or report-unavailable applies. The bootstrap checks this status before it selects an extension route.

Core route IDs are reserved. The order in the registry decides only the display order. It never lets one route override another.

A plugin-scoped subagent <namespace>:<agent> appears in a project as <namespace>-<agent>. No tool renames it when you add the extension. Each extension’s folder ships with the names already in this form:

  • Subagent files are named <namespace>-<agent> in every agent’s subagent folder.
  • Their frontmatter name: matches.
  • The skills call them by that name.

An extension’s folder holds its package at .familiar/extensions/<id>/package/. Its skills and subagents are already in each agent’s own folders. To add it, merge that folder onto the project root. No list records this. The files on disk and the package’s own manifest are the record. .familiar/installed.json still records the capability state for each agent.

To remove an extension, delete the paths that its _about.md lists. Nothing checks whether you edited one of those files, so look before you delete.