Skip to main content

.atool Manifest Specification — v2

An .atool file is a gzip-compressed tar archive. The archive must contain a file named atool.json at its root. All other files referenced in the manifest must also be present in the archive.

Schema v2 is the current and only supported schema. A package declaring "schema_version": "1" is rejected with a clear migration error — run alex-sdk migrate to upgrade.


What changed from v1

Areav1v2
Valid kind valuestool, agent, skill, llm-runtime, llm-backend, bundletool, skill, agent only
Agent LLM hintmodel: Stringllm: Option<String>
Skill LLM hintmodel_hint: Stringllm: Option<String>
Agent compositionnot modeledcomponents: [], install.flatten
Tool default portdefault_port: u16 (0 = unset)default_port: Option<u16> (absent = unset; 0 is a validation error)
Dependency versionoptionalrequired — no #[serde(default)]
Signature fieldstop-levelnested under a Signature block

llm-runtime, llm-backend, and bundle were removed deliberately. LLM endpoints are registered via alexandria llm add, not installed as packages. Bundles are replaced by Agent composition via components.


Archive layout

{name}_{version}_{arch}.atool (*.tar.gz)
├── atool.json # manifest — always at root
├── bin/<binary> # tool kinds only
└── components/<name>/... # inline component files (agent kinds)

Top-level manifest fields

FieldTypeRequiredDescription
schema_versionstringyesMust be "2". "1" → migration error.
namestringyesPackage identifier, e.g. "vendor/name" or "name".
versionstringyesSemantic version string, e.g. "1.2.0".
kindstringyesOne of tool, skill, agent.
descriptionstringyesHuman-readable one-line summary.
authorstringnoAuthor name or email.
licensestringnoSPDX license identifier, e.g. "MIT".
requires_alexandriastringnoMinimum Alexandria version.
dependenciesDependency[]noOther .atool packages this one needs.
configConfigyesKind-specific configuration (discriminated union).
filesFileEntry[]noFiles included in the archive.
permissionsPermissionsnoPermissions this package requests.
signatureSignaturenoEd25519 signing block. Absent = unsigned.

Dependency

{ "name": "vendor/other-tool", "version": ">=1.0.0" }
FieldTypeDescription
namestringPackage name.
versionstringRequired — version constraint, free-form, checked by the installer.

Config (discriminated union)

The config object is tagged by an inner kind field that must match the top-level kind.

kind: "tool"

{
"kind": "tool",
"binary": "bin/my-server",
"default_port": 7800,
"transport": "http",
"args": ["--verbose"],

"k8s_image": "registry.example.com/my-tool:1.2.0",
"k8s_capabilities": ["fs.read", "net.outbound"],
"k8s_port": 9000,
"k8s_transport": "grpc",
"k8s_resources": {
"requests": { "cpu": "100m", "memory": "128Mi" },
"limits": { "cpu": "500m", "memory": "512Mi" }
},
"k8s_min_warm": 0,
"k8s_idle_timeout_seconds": 300
}
FieldTypeDefaultDescription
binarystringRelative path to the server binary inside the archive.
default_portu16?nullMCP server port. null = unset. 0 is a validation error.
transportstring"http""http" or "sse".
argsstring[][]Extra arguments forwarded to the binary.
k8s_imagestring""OCI image URI for the managed k8s Deployment. Empty = not k8s-deployable.
k8s_capabilitiesstring[][]Capability tags advertised by the tool.
k8s_portu169000Container port.
k8s_transportstring"grpc""grpc", "http", or "sse".
k8s_resources.requests{cpu, memory}{}k8s CPU/memory requests.
k8s_resources.limits{cpu, memory}{}k8s CPU/memory limits.
k8s_min_warmu320Minimum replicas. 0 enables scale-to-zero (v1.1+).
k8s_idle_timeout_secondsu32300Idle despawn budget.

A tool is k8s-deployable iff k8s_image is non-empty.

kind: "skill"

{
"kind": "skill",
"system_prompt": "Summarise the following text in three bullets.",
"allowed_tools": ["fs"],
"llm": "claude-haiku-4-5",
"tags": ["summarisation", "text"]
}
FieldTypeDefaultDescription
system_promptstringPrompt template for the skill.
allowed_toolsstring[][]Tools the skill may use.
llmstring?nullPreferred LLM id. null = orchestrator default. Replaces model_hint.
tagsstring[][]Searchable tags.

kind: "agent"

{
"kind": "agent",
"system_prompt": "You are a research assistant.",
"allowed_tools": ["web", "fs"],
"llm": "claude-opus-4-7",
"history_limit": 40,
"components": [ /* see below */ ],
"install": {
"flatten": {
"system_prompt": "concat",
"allowed_tools": "union"
}
}
}
FieldTypeDefaultDescription
system_promptstring""System prompt injected on every request.
allowed_toolsstring[][]MCP tool names this agent may call.
llmstring?nullPreferred LLM id. null = orchestrator default.
history_limitu320Message history window (0 = server default).
componentsComponent[]?nullSub-agents / sub-skills (composition tree).
installInstallBlock?nullInstall-time composition rules.

Component (agents only)

Component is an untagged union — the installer disambiguates by shape.

InlineComponent

An embedded sub-agent or sub-skill defined directly in the parent manifest.

{
"name": "summariser",
"id": "acme/summariser@0.1.0",
"kind": "skill",
"config": {
"kind": "skill",
"system_prompt": "Summarise in three bullets.",
"allowed_tools": ["fs"],
"llm": null,
"tags": ["summarisation"]
}
}
FieldTypeRequiredDescription
namestringyesLocal name for prompt headers and merging.
idstringyesFully-qualified id "ns/name@version" used for dedup/coalescing.
kind"agent" | "skill"yesTools cannot be inline — use RefComponent instead.
configConfigyesSame shape as the top-level config.
componentsComponent[]?noNested components (recursive).
filesFileEntry[]?noFiles this component contributes.
permissionsPermissions?noPermissions this component contributes.
dependenciesDependency[]?noComponent-local dependencies.

Validation: an inline component with kind: "tool" is rejected by AToolManifest::validate(). Tools must always be RefComponent.

RefComponent

A reference to an externally installed component.

{ "ref": "vendor/web-tool@1.0.0" }
FieldTypeDescription
refstring"ns/name@version" — must be already installed (or fetchable).

InstallBlock — flatten composition

When installed with --mode=flatten, the installer collapses an agent's component tree into a single composed agent. The flatten rules govern how fields merge.

{
"flatten": {
"system_prompt": "concat",
"allowed_tools": "union"
}
}

system_prompt merge modes

ModeBehaviour
concat (default)Concatenate all prompts with ## <component_name> headers. Root's block is last.
root_winsUse root's prompt only; discard children's.
error_on_conflictAbort install if any child has a different system prompt.

allowed_tools merge modes

ModeBehaviour
union (default)Set-union across the tree, deduplicated.
root_winsUse root's allowed_tools only.

Implicit rules

  • llm — always root_wins. A root with llm: null overrides children's preferences (the package author's deliberate flexibility is preserved).
  • history_limit — always root_wins.
  • Delegate tools — every inline sub-agent generates a synthetic delegate.<name> tool the root can dispatch to. Ref components do not generate delegate tools (they're external).

FileEntry

{
"archive_path": "bin/my-server",
"install_path": "tools/my-server",
"executable": true,
"sha256": "abc123..."
}
FieldTypeDescription
archive_pathstringPath inside the archive (relative).
install_pathstringDestination relative to the Alexandria data root.
executableboolSet 0o755 on install (Unix).
sha256stringSHA-256 hex digest — populated by alexandria pack, checked by verify. Empty = skipped.

Permissions

{
"provides_tools": ["my-server"],
"needs_tools": ["fs", "web"],
"suggested_role": "worker"
}
FieldTypeDescription
provides_toolsstring[]MCP tool names this package exposes (tool kinds).
needs_toolsstring[]MCP tool names this package needs (agent/skill kinds — requires admin approval).
suggested_rolestringworker (default), coordinator, or supervisor.

Signature

Ed25519 signing block. Absent = unsigned package. Installs in --require-signed mode reject unsigned packages.

{
"alg": "ed25519",
"key_fingerprint": "a1b2c3d4e5f6a7b8",
"value": "hex-encoded-signature-bytes",
"scope": "bundle"
}
FieldTypeDescription
algstringAlways "ed25519" (currently the only supported algorithm).
key_fingerprintstringFirst 8 bytes of SHA-256 of the public key, hex-encoded.
valuestringHex-encoded signature bytes over the canonical signing payload.
scope"bundle" | "per-component"bundle signs the whole archive; per-component signs each component file individually.

The signing payload is built deterministically from manifest digests and is independent of field ordering, so the same package always produces the same signature input.


Complete examples

Standalone MCP tool

{
"schema_version": "2",
"name": "acme/pdf-parser",
"version": "0.3.1",
"kind": "tool",
"description": "Extract text and metadata from PDF files",
"author": "Acme Corp",
"license": "MIT",
"requires_alexandria": "1.0.0",
"dependencies": [],
"config": {
"kind": "tool",
"binary": "bin/pdf-parser",
"default_port": 7800,
"transport": "http",
"args": []
},
"files": [
{
"archive_path": "bin/pdf-parser",
"install_path": "tools/pdf-parser",
"executable": true,
"sha256": ""
}
],
"permissions": {
"provides_tools": ["pdf-parser"],
"needs_tools": [],
"suggested_role": "worker"
}
}

k8s-deployable tool

{
"schema_version": "2",
"name": "acme/pdf-parser",
"version": "0.3.1",
"kind": "tool",
"description": "Extract text and metadata from PDF files",
"config": {
"kind": "tool",
"binary": "bin/pdf-parser",
"transport": "http",
"k8s_image": "registry.example.com/acme/pdf-parser:0.3.1",
"k8s_capabilities": ["fs.read"],
"k8s_port": 9000,
"k8s_transport": "grpc",
"k8s_resources": {
"requests": { "cpu": "100m", "memory": "128Mi" },
"limits": { "cpu": "500m", "memory": "512Mi" }
},
"k8s_min_warm": 0,
"k8s_idle_timeout_seconds": 300
},
"files": [],
"permissions": { "provides_tools": ["pdf-parser"] }
}

Standalone skill

{
"schema_version": "2",
"name": "acme/summarise",
"version": "0.1.0",
"kind": "skill",
"description": "Summarise a document in three bullet points",
"config": {
"kind": "skill",
"system_prompt": "Summarise the following text in exactly three concise bullet points.",
"allowed_tools": [],
"llm": null,
"tags": ["summarisation", "text"]
},
"files": [],
"permissions": {}
}

Composed agent with sub-skills and a ref'd tool

{
"schema_version": "2",
"name": "acme/researcher",
"version": "1.0.0",
"kind": "agent",
"description": "Research assistant with web access and a summariser sub-skill",
"dependencies": [
{ "name": "acme/web-tool", "version": ">=1.0.0" }
],
"config": {
"kind": "agent",
"system_prompt": "You are a research assistant. Delegate summarisation to the sub-skill.",
"allowed_tools": ["web"],
"llm": "claude-opus-4-7",
"history_limit": 40,
"components": [
{
"name": "summariser",
"id": "acme/summariser@0.1.0",
"kind": "skill",
"config": {
"kind": "skill",
"system_prompt": "Summarise in three bullets.",
"allowed_tools": [],
"llm": null,
"tags": ["summarisation"]
}
},
{ "ref": "acme/web-tool@1.0.0" }
],
"install": {
"flatten": {
"system_prompt": "concat",
"allowed_tools": "union"
}
}
},
"files": [],
"permissions": {
"provides_tools": [],
"needs_tools": ["web"],
"suggested_role": "coordinator"
}
}

Integrity verification

When a package is packed with alexandria pack, the sha256 field of each FileEntry is computed automatically. alexandria install calls verify before extracting, which re-hashes every file and rejects any archive where the computed digest does not match the manifest value.

Files with an empty sha256 field are skipped during verification (useful for generated or dynamic files).

When a signature block is present, verify also checks the Ed25519 signature against the signing payload and the embedded key fingerprint. alexandria install --require-signed rejects unsigned packages outright.


Validation rules

AToolManifest::validate() enforces the following beyond what serde catches:

  1. schema_version must be "2". "1" → clear migration error. Anything else → error.
  2. Inline components with kind: "tool" are rejected. Tools must always be RefComponent.
  3. ToolConfig.default_port == Some(0) is rejected. Use null to mean "unset".
  4. Dependency.version is required (no default).

Versioning policy

  • schema_version is incremented only for breaking changes.
  • Fields marked optional (#[serde(default)]) will not be removed without a version bump.
  • New optional fields may be added at any time without incrementing the version.