Package Interface Contract
Context
autograph is composable: packages add capabilities (Docker services, tasks, skills, attitudes). For this to work reliably, packages need a formal interface contract that:
- Declares what the package provides
- Declares what it requires (engine version compatibility)
- Enables
diagnosto validate conformity - Enables the engine to compose Docker mounts and Taskfile includes automatically
Options considered
Option A - Convention only (no manifest)
Packages follow a directory convention (e.g., agent/skills/ always means skills). No explicit declaration file.
- Advantage: Zero overhead, just follow the pattern
- Disadvantage: No machine-readable metadata.
diagnosmust infer everything. No compatibility checking. Fragile.
Option B - West userdata only
All metadata lives in the West manifest's userdata.augr field.
- Advantage: Single source of truth (the manifest)
- Disadvantage:
userdatais limited (no rich structure). The package itself has no self-description. You need the project manifest to understand a package.
Option C - Package manifest file `augr.pkg.yml` (chosen)
Each package has a augr.pkg.yml at its root that declares everything about itself. West userdata contains minimal metadata (role, kind) for discovery.
- Advantage: Self-describing packages. Rich metadata.
diagnoscan validate independently. Package repos are understandable without the project context. - Disadvantage: Two places for metadata (augr.pkg.yml + west userdata). Must stay in sync.
Decision
Option C - augr.pkg.yml as the package manifest.
Packages must be self-describing. The West manifest handles discovery and resolution; augr.pkg.yml handles identity and contract.
Consequences
Package manifest format
The manifest declares identity, compatibility, and pointers to standard files - not their content. The engine discovers capabilities by reading the files at those paths.
name: autograph.pkg.<name>
version: "0.1.0"
description: "Human-readable description"
contract:
engine_version: ">=0.1.0" # Semver range of compatible engine versions
# Pointers to standard files/directories (all optional, convention-based paths)
compose: # Compose file(s) for this package's services
- infra/compose.yaml # Can be a single file or a list
- infra/compose.dev.yaml # e.g., dev-specific overrides
taskfile: Taskfile.yaml # Single entry point (includes internally if needed)
skills: agent/skills/ # Directory containing skills
attitudes: agent/attitudes/ # Directory containing attitudes
The manifest does NOT duplicate content from these files. It only declares where to find them. The engine/diagnos reads the actual files to discover services, tasks, skills, etc.
compose: Can be a single path or a list of paths. This allows packages to provide multiple compose files (e.g., base + dev overrides, or separate concerns).
taskfile: Always a single entry point. If the package needs to split tasks internally, it uses Taskfile includes - that's the package's concern, not the engine's.
Standard package layout
autograph.pkg.<name>/
├── augr.pkg.yml # Package manifest (required)
├── Taskfile.yaml # Single task entry point (required)
├── README.md # Description and usage (required)
├── infra/ # Infrastructure (optional)
│ ├── compose.yaml # Main compose (services for this package)
│ ├── compose.dev.yaml # Dev overrides (optional)
│ └── docker/
│ └── <service>/Dockerfile
├── agent/ # Agent contributions (optional)
│ ├── attitudes/
│ │ └── <name>/ATTITUDE.md
│ └── skills/
│ └── <name>/SKILL.md
└── docs/ # Documentation (optional)
Convention: if a directory/file exists at its standard path, the engine assumes it's active. The manifest confirms and allows diagnos to validate.
Validation by diagnos
diagnos checks:
augr.pkg.ymlexists and is valid YAMLcontract.engine_versionis satisfied by the current engine- Declared paths actually exist (compose, taskfile, skills/, attitudes/)
- Compose file is valid (parseable)
- Taskfile is valid (parseable)
- Package is mounted read-only in the agent container (L2 conformity)
West userdata (minimal)
userdata:
augr:
role: package
kind: core # or "third-party"
Trade-offs
- Packages must maintain both
augr.pkg.ymland appear in a West manifest - The format will evolve - v0 is intentionally simple
- No runtime validation (contract is checked at prep/diagnos time, not at every boot)
- Convention-based discovery means renaming standard paths breaks things silently -
diagnosmitigates this
Open: agent package extension
The contract above covers the generic package interface. Packages of type "agent" (pkg.agent, pkg.metalab) provide additional artifacts that are not yet formalized in the manifest:
agent/opencode.json.template-- configuration template (rendered by entrypoint via envsubst)agent/prompts/-- agent system promptsagent/plugins/-- OpenCode plugins (aggregated by entrypoint)agent/AGENTS.md-- agent operating manual
These artifacts follow the same convention-based pattern (if the path
exists, the engine assumes it's active) but are not declared in
augr.pkg.yml and not validated by diagnos.
The companion diagram (package-interface-contract.drawio) maps the full
asset lifecycle for agent packages: source location, aggregation mechanism
(entrypoint scripts, volume mounts, envsubst), and runtime destination in
the container. It captures the current state and the questions to resolve
for formalizing this extension:
- Should agent-specific paths be declared in
augr.pkg.yml(new pointers)? - Should
diagnosvalidate agent artifacts? - How to handle aggregation filtering by agent role (today all skills go to both agents)?
- How to support multi-package prompt and agent contributions?
These questions are tracked in E03 (P1-P4) and will result in an amendment to this ADR when resolved.