# Tool Manifest Specification > Canonical schema reference for Headquarter's manifest-driven tool registry. > Version: 1.0.0 — aligned with FN-003. ## Overview Headquarter is a manifest-driven platform: every containerized tool (OpenCode, code-server, and future tools) is declared by a YAML manifest. The orchestration backend reads these manifests to generate Docker Compose services, Traefik routing labels, volume mounts, and resource constraints. **Design goal:** Adding a new standard container tool requires only a YAML manifest—no backend code changes. ## Manifest File Format Manifests are YAML files with a single top-level mapping. They are validated on load by Pydantic v2 models. ### Built-in location Built-in manifests live in `apps/api/app/tools/manifests/*.yml` and are loaded automatically on API startup. ### Minimal valid manifest ```yaml id: my-tool name: My Tool image: my-org/my-tool:latest ports: - container_port: 8080 primary: true traefik: enabled: false ``` ## Field Reference ### `ToolManifest` (top level) | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `id` | `string` | yes | — | Lowercase slug with hyphens only (`^[a-z0-9\-]+$`). Used as the registry key. | | `name` | `string` | yes | — | Human-readable tool name. | | `description` | `string` | no | `""` | Short description of the tool. | | `version` | `string` | no | `"1.0.0"` | Manifest version (semver-ish). | | `image` | `string` | yes | — | Docker image reference. | | `runtime_command` | `string[] \| null` | no | `null` | Override the container default command. | | `runtime_entrypoint` | `string[] \| null` | no | `null` | Override the container entrypoint. | | `runtime_user` | `string \| null` | no | `null` | User to run as inside the container. | | `runtime_working_dir` | `string \| null` | no | `null` | Working directory inside the container. | | `ports` | `PortConfig[]` | no | `[]` | Exposed ports. | | `workspace_mounts` | `MountConfig[]` | no | `[]` | Workspace volume mounts (project-scoped). | | `config_mounts` | `MountConfig[]` | no | `[]` | Config volume mounts (user or tool-scoped). | | `env` | `dict` | no | `{}` | Static environment variables. | | `secrets` | `SecretRef[]` | no | `[]` | Secrets injected as environment variables. | | `health_check` | `HealthCheckConfig \| null` | no | `null` | Health check definition. | | `resource_limits` | `ResourceLimits \| null` | no | `null` | CPU and memory constraints. | | `executable` | `ExecutableConfig \| null` | no | `null` | Node.js runtime metadata for executable environments. | | `traefik` | `TraefikConfig \| null` | no | `null` | Traefik routing configuration. | ### `PortConfig` | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `container_port` | `int` | yes | — | Port inside the container. Range: 1–65535. | | `protocol` | `"tcp" \| "udp"` | no | `"tcp"` | Transport protocol. | | `name` | `string \| null` | no | `null` | Logical name, e.g. `"http"`, `"websocket"`. | | `primary` | `bool` | no | `false` | The port used for default routing and health checks. | ### `MountConfig` | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `type` | `"volume" \| "bind"` | no | `"volume"` | Mount type. | | `source_pattern` | `string` | yes | — | Template pattern resolved at spawn time, e.g. `"{project_repo}"`. | | `target` | `string` | yes | — | Absolute path inside the container. Must start with `/`. | | `read_only` | `bool` | no | `false` | Mount read-only. | ### `SecretRef` | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `name` | `string` | yes | — | Secret identifier in the secret store. | | `env_var` | `string` | yes | — | Name of the environment variable injected into the container. | | `required` | `bool` | no | `true` | Whether the tool fails to start if the secret is missing. | ### `HealthCheckConfig` | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `type` | `"http" \| "tcp" \| "command"` | no | `"http"` | Health check mechanism. | | `path` | `string \| null` | no | `null` | HTTP path. Required when `type == "http"`. | | `command` | `string[] \| null` | no | `null` | Command to execute. Required when `type == "command"`. | | `port` | `int \| null` | no | `null` | Override port; defaults to the primary port if unset. | | `interval_seconds` | `int` | no | `10` | Check interval. ≥ 1. | | `timeout_seconds` | `int` | no | `5` | Check timeout. ≥ 1. | | `retries` | `int` | no | `3` | Retries before marking unhealthy. ≥ 1. | | `start_period_seconds` | `int` | no | `5` | Grace period before checks count. ≥ 0. | ### `ResourceLimits` | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `cpus` | `float \| null` | no | `null` | CPU limit. If set, ≥ 0.01. | | `memory_mb` | `int \| null` | no | `null` | Memory limit in MiB. If set, ≥ 16. | | `memory_swap_mb` | `int \| null` | no | `null` | Swap limit in MiB. `-1` disables swap limit. | ### `ExecutableConfig` | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `node_version` | `string \| null` | no | `null` | Expected Node.js version, e.g. `"22"`, `"lts"`. | | `npm_version` | `string \| null` | no | `null` | Expected npm version. | | `package_manager` | `"npm" \| "pnpm" \| "yarn" \| "bun"` | no | `"npm"` | Preferred package manager. | | `bootstrap_commands` | `string[]` | no | `[]` | One-time setup commands run on first start. | | `install_commands` | `string[]` | no | `[]` | Commands run before the main command. | ### `TraefikConfig` | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `enabled` | `bool` | no | `true` | Whether Traefik routing is generated for this tool. | | `subdomain_prefix` | `string \| null` | no | `null` | Subdomain prefix. Defaults to the tool `id`. | | `port` | `int \| null` | no | `null` | Container port to route traffic to. | | `middlewares` | `string[]` | no | `[]` | Traefik middleware names to apply. | | `strip_prefix` | `bool` | no | `false` | Strip path prefix before forwarding. | | `entrypoint` | `string \| null` | no | `null` | Override the environment default Traefik entrypoint. | | `cert_resolver` | `string \| null` | no | `null` | Override the environment default cert resolver. | ## Validation Rules 1. `id` must match `^[a-z0-9\-]+$` (lowercase, digits, hyphens only). 2. `MountConfig.target` must be an absolute path (`starts with "/"`). 3. When `health_check.type == "http"`, `path` must be set and non-empty. 4. When `health_check.type == "command"`, `command` must be set and non-empty. 5. `container_port` must be between 1 and 65535. 6. `cpus`, if set, must be ≥ 0.01. 7. `memory_mb`, if set, must be ≥ 16. 8. If `traefik.enabled` is `true`, at least one port must have `primary: true`. ## Example: OpenCode Manifest ```yaml id: opencode name: OpenCode description: AI-powered terminal-based development environment with web interface. version: "1.0.0" image: ghcr.io/opencode-ai/opencode:latest runtime_working_dir: /workspace ports: - container_port: 3000 protocol: tcp name: http primary: true workspace_mounts: - type: volume source_pattern: "{project_repo}" target: /workspace read_only: false config_mounts: - type: volume source_pattern: "{user_config}/opencode" target: /root/.config/opencode read_only: false env: TERM: xterm-256color FORCE_COLOR: "1" health_check: type: http path: / port: 3000 interval_seconds: 10 timeout_seconds: 5 retries: 3 start_period_seconds: 15 resource_limits: cpus: 2.0 memory_mb: 4096 memory_swap_mb: -1 traefik: enabled: true subdomain_prefix: opencode port: 3000 middlewares: [] strip_prefix: false ``` ## Extension Guide: Adding a New Tool To add a new standard container tool: 1. Create a new YAML file in `apps/api/app/tools/manifests/{tool-id}.yml`. 2. Populate all required fields (`id`, `name`, `image`, `ports`). 3. Set `traefik.enabled: true` and mark one port as `primary: true` if the tool needs HTTP routing. 4. Declare `workspace_mounts` and `config_mounts` as needed. 5. Restart the API (or call `registry.load_builtin_manifests()`). No backend code changes are required for standard containers that expose an HTTP port and need volume mounts. ## Registry API The in-memory registry exposes FastAPI routes under `/api/v1/tools`: - `GET /api/v1/tools` — list all registered manifests. - `GET /api/v1/tools/{id}` — retrieve a single manifest. - `POST /api/v1/tools` — register a new manifest (returns 409 if `id` already exists). Built-in manifests are loaded automatically on application startup via the FastAPI lifespan context manager.