- Update architecture.md with spawn service and auth proxy sections - Update deployment.md with production stack details - Update development.md with spawn workflow documentation - Update mvp-scope.md, project-brief.md, tool-manifest-spec.md - Update conversation-handoff.md with current status - Replace all RunFusion references with OpenCode
8.8 KiB
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
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<string, string> |
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
idmust match^[a-z0-9\-]+$(lowercase, digits, hyphens only).MountConfig.targetmust be an absolute path (starts with "/").- When
health_check.type == "http",pathmust be set and non-empty. - When
health_check.type == "command",commandmust be set and non-empty. container_portmust be between 1 and 65535.cpus, if set, must be ≥ 0.01.memory_mb, if set, must be ≥ 16.- If
traefik.enabledistrue, at least one port must haveprimary: true.
Example: OpenCode Manifest
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:
- Create a new YAML file in
apps/api/app/tools/manifests/{tool-id}.yml. - Populate all required fields (
id,name,image,ports). - Set
traefik.enabled: trueand mark one port asprimary: trueif the tool needs HTTP routing. - Declare
workspace_mountsandconfig_mountsas needed. - 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 ifidalready exists).
Built-in manifests are loaded automatically on application startup via the FastAPI lifespan context manager.