axiom.yaml manifest reference
The complete schema of the axiom.yaml package manifest: every field, its type, default, validation rule, and which CLI commands read or write it.
View as Markdownaxiom.yaml is the package manifest. It sits at the root of every Axiom
package and declares the package's identity, its nodes, the message types
it imports from other packages, and optional build overrides. The Axiom CLI
locates the package root by walking up from the current directory until it
finds axiom.yaml.
Three fields are required: name, version, and language. Everything
else is optional. axiom validate checks the manifest before publishing
(see Validation rules).
Complete example
Every field the parser accepts, in one manifest:
# axiom.yaml — at the package root
name: christian/text-tools
version: 1.2.0
language: python
description: Text processing nodes
author: Christian Lucas
license: MIT
tags:
- text
- nlp
nodes:
- name: SummarizeText
description: Summarize input text
input: TextRequest
output: SummaryResult
required_secrets:
- OPENAI_API_KEY
- name: StreamTokens
input: TextRequest
output: TokenFrame
type: pipeline
imports:
- package: axiom-official/axiom-conv-ai
version: 1.0.0
messages:
- ChatTurn
env:
- name: LOG_LEVEL
description: Verbosity of node logging
required: false
secret: false
default: info
build:
dockerfile: custom/Dockerfile
system_deps:
- ffmpeg
maven_deps:
- org.dmfs:lib-recur:0.17.1
nuget_deps:
- Cronos:0.8.4A minimal valid manifest is just the three identity fields — axiom init
writes exactly that (plus your --description if given, and a commented
example nodes: block you can delete).
Top-level fields
| Field | Type | Required | Default | Purpose |
|---|---|---|---|---|
name | string | yes | — | Scoped package name, handle/package-name |
version | string | yes | — | Semantic version of the package |
language | string | yes | — | One of go, python, rust, java, typescript, csharp |
type | string | no | "" | proto-only to skip build and deployment; otherwise omit |
description | string | no | "" | Shown in the registry on publish |
author | string | no | "" | Stored with the package record on publish |
license | string | no | "" | Stored with the package record on publish |
tags | list of string | no | [] | Discovery tags, stored in the registry's tag index on publish |
nodes | list of node | no | [] | The package's nodes (see nodes) |
imports | list of import | no | [] | Message types from other packages (see imports) |
env | list of env var | no | [] | Runtime environment variables (see env) |
build | object | no | unset | Build overrides (see build) |
description, author, and license are written to the registry when you
publish with axiom push. tags are written to the registry's tag index
on publish. Marketplace search currently matches on package name,
description, and author — not on these tags.
Identity fields: name, version, language
name must be scoped: your-handle/package-name. Both parts may
contain only lowercase letters, digits, and hyphens, and each part must
start with a letter or digit (pattern:
^[a-z0-9][a-z0-9-]*/[a-z0-9][a-z0-9-]*$). An unscoped or missing name
fails validation. axiom init christian/text-tools creates a local
directory named after the part after the last / (text-tools/).
version must be semantic versioning: MAJOR.MINOR.PATCH, with an
optional leading v and an optional pre-release suffix of letters, digits,
and dots after a hyphen (pattern:
^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$). Examples: 1.0.0,
v2.1.3, 0.4.0-beta.1. axiom init writes 0.1.0 unless you pass
--version.
language must be exactly one of go, python, rust, java,
typescript, csharp. It selects the node templates, the generated
service code, and the Dockerfile the build uses. axiom init defaults to
go unless you pass --language (-l).
nodes
Each entry in nodes declares one node in the package. axiom create node
appends entries here for you; you can also edit the list by hand.
| Field | Type | Required | Default | Purpose |
|---|---|---|---|---|
name | string | yes | — | PascalCase node name, unique within the package |
description | string | no | "" | Shown in the registry |
input | string | yes | — | Input message name |
output | string | yes | — | Output message name |
type | string | no | unary | Omit or unary for one-in/one-out; pipeline for a streaming generator |
required_secrets | list of string | no | [] | Secret names the node reads at runtime |
mutation_capable | bool | no | false | Declares the node emits state mutations on its response |
kind | string | no | "" | "" / "concrete" (default) — fixed, specific types decided at authoring time; "generic" — a marketplace connector whose port(s) an Instance later binds to a real type (see Generic and Instance nodes) |
generic_ports | list of string | no | [] | Which port(s) are generic — "input", "output", or both; required (non-empty) when kind: generic, forbidden otherwise |
retry | object | no | unset | Package-author invocation-retry override (see retry) |
name — axiom create node enforces PascalCase: the first character
must be uppercase, and only letters and digits are allowed. Duplicate node
names in one manifest are rejected. The node's source file is the
snake_case form of the name (SummarizeText → nodes/summarize_text.py).
input / output — each must name a message
defined in messages/messages.proto or available from an imported package.
axiom validate fails when a referenced message cannot be found.
type — a node is unary unless type: pipeline is set. axiom create node --type accepts only unary or pipeline and writes type: pipeline
to the manifest only for pipeline nodes (the key is omitted for unary).
See execution model.
required_secrets — names of tenant secrets
the node reads at runtime. The declaration is informational: the platform
does not enforce it at invocation time. A missing secret does not block a run —
secrets.Get("NAME") simply yields a not-found result (found = false) at read
time, which the node handles. The marketplace displays the list so users know
which secrets to register. axiom validate emits a non-blocking warning when
node source calls secrets.Get("NAME") with a name missing from this list. See
manage secrets.
mutation_capable — declares that the node emits state mutations on
its response. The flow compiler reads this across a flow's nodes to decide
whether to enable the mutation path; the default false keeps a node out
of that path entirely.
Generic and Instance nodes
kind: generic marks a node as a Generic node: a real, published,
deployed connector (a database query, an HTTP call) whose input and/or
output port is declared broad enough to serve every use of the node, but not
yet the specific shape any one flow wants — the author can't know a
particular table's columns or an API's exact response shape at publish time.
A Generic node cannot be placed in a flow by itself.
nodes:
- name: Query
input: QueryParams # a real, concrete, GENERALIZED message — not a placeholder
output: Row # declare real fields broad enough for every use of this node
kind: generic # "" / "concrete" (default) | "generic"
generic_ports: [input, output] # which port(s): "input", "output", or bothgeneric_ports names which port(s) of a kind: generic node are
generic. It is required (non-empty) whenever kind: generic, and forbidden
(must be empty) on any other node. Each entry must be input or output,
with no duplicates. Genericness is signaled entirely by kind +
generic_ports — never by the message's name; there is no required
naming convention (such as a Placeholder suffix) for a generic port's
message. axiom validate does warn (non-blocking) if a generic port's
message declares zero fields, since a field-less message forces the node's
own handler code to hand-decode raw protobuf wire bytes instead of reading
ordinary named fields — declare real, concrete, generalized fields instead.
An Instance binds a Generic node's port(s) to a real, specific type via a
field mapping — created with axiom instance create (or the marketplace's
Generic Nodes tab), not authored in axiom.yaml. An Instance carries no code
of its own: it shares the Generic node's already-built, already-deployed
image at runtime, so creating one is pure server-side type-binding — no git
commit, no build, no Knative deploy. kind: instance can never be
hand-authored in a manifest — only the registry stamps that value when an
Instance is created; axiom validate rejects a manifest that sets it. See
Configure an Instance from a Generic node
for the full declare→create→reference loop (including a field whose type is
another message, defined recursively, and previewing a mapping before you
save), and the HTTP API reference for
the underlying POST /v1/instances endpoint.
retry
Axiom guarantees at-least-once invocation of every node by default — this
requires no configuration. retry is the optional package-author layer of a
three-layer model (platform defaults → package author, here → flow author,
flow.yaml's retry: block): set only the fields you want to override for
this node; anything you omit inherits from the platform default, and a flow
author can still override this node's effective retry again per-flow. See
the axiom-package-authoring skill for the full model and the platform
defaults, and the axiom-flow-authoring skill for the flow-author layer.
nodes:
- name: Query
retry:
max_attempts: 5
initial_interval_ms: 500
backoff_coefficient: 2.0
max_interval_ms: 30000
per_attempt_timeout_ms: 5000
overall_timeout_ms: 60000
non_retryable_codes: [TIMEOUT]| Field | Type | Required | Purpose |
|---|---|---|---|
max_attempts | int | no | Retry attempts before the node fails; rejected by axiom validate/axiom push with a blocking error if it exceeds the platform ceiling (10) |
initial_interval_ms | int | no | Backoff before the 2nd attempt |
backoff_coefficient | float | no | Multiplier applied to the interval after each attempt |
max_interval_ms | int | no | Cap on the backoff interval |
per_attempt_timeout_ms | int | no | Budget for a single attempt |
overall_timeout_ms | int | no | Total budget across all attempts |
non_retryable_codes | list of string | no | NodeErrorCode short names to exclude from retry — TIMEOUT/TRANSPORT only here |
This layer applies only to NODE_ERROR_TRANSPORT / NODE_ERROR_TIMEOUT
(platform invocation failures) — never to NODE_ERROR_USER (the node's own
code returning/throwing an error), which is hardcoded non-retryable at every
layer and is never a valid non_retryable_codes entry here (axiom validate
rejects USER, PANIC, or CANCELLED in this field with an error). Because
invocation retry is at-least-once, not exactly-once, write node handlers to
be idempotent — see the idempotency guidance in the axiom-package-authoring
skill and the SDK reference pages for
Go, Python, and
TypeScript.
imports
Each entry in imports declares a dependency on another package's message
types. Do not write these entries by hand — run
axiom import <package>[@version], which downloads the package's .proto
files into imports/<flattened-name>/<version>/ (scope / becomes -,
e.g. christian/text-ops → imports/christian-text-ops/1.0.0/), adds or
updates the manifest entry, and runs axiom generate.
| Field | Type | Required | Purpose |
|---|---|---|---|
package | string | yes | Scoped name of the imported package |
version | string | yes | Exact version of the imported package |
messages | list of string | yes | Message names imported from that package |
Validation rules for imports:
- An import entry with an empty
messageslist is a hard error and blocks publishing. Fix it by re-runningaxiom import <package>@<version>, which populates the list. - A message name listed here but not found in the downloaded proto files is a warning — it usually means a stale entry after the imported package changed.
- The downloaded proto files must exist locally under
imports/<flattened-name>/<version>/; if they are missing, re-runaxiom import <package>@<version>.
See import package types for the full workflow.
env
Each entry in env describes a runtime environment variable for the
package. All fields except name are optional.
| Field | Type | Required | Default | Purpose |
|---|---|---|---|---|
name | string | yes | — | Variable name |
description | string | no | "" | What the variable controls |
required | bool | no | false | Whether the variable must be set |
secret | bool | no | false | Whether the value is sensitive |
default | string | no | "" | Default value |
The env section is declarative metadata about what the package expects;
axiom validate does not check it. For credentials read by node code at
runtime, use secrets and
required_secrets instead — secrets are tenant-scoped and reach node code
through AxiomContext.
build
The optional build section overrides how the package's container image is
built. Most packages never need it — axiom build and axiom push
generate a Dockerfile automatically from the package language.
| Field | Type | Required | Purpose |
|---|---|---|---|
dockerfile | string | no | Path (relative to the package root) to a custom Dockerfile used instead of the generated one |
system_deps | list of string | no | Extra OS-level packages installed into the generated image |
maven_deps | list of string | no | (Java only) Third-party Maven dependencies, each an exact-pinned groupId:artifactId:version coordinate |
nuget_deps | list of string | no | (C# only) Third-party NuGet dependencies, each an exact-pinned PackageID:Version coordinate |
maven_deps
maven_deps lets a Java package depend on third-party libraries from
Maven Central. Each entry is a coordinate groupId:artifactId:version, for
example org.dmfs:lib-recur:0.17.1. The coordinates are rendered into the
<dependencies> block of the generated pom.xml — which is regenerated on
every build, so you declare dependencies here rather than editing pom.xml
directly (hand edits are overwritten). The same declaration is honored by the
local build (axiom build), by axiom push, and by the server-side registry
build, so a Java node can call the library at runtime after publish.
The version must be an exact pin for reproducibility: axiom validate
rejects Maven version ranges ([1.0,2.0)), Gradle-style dynamic versions
(0.17.+), the LATEST/RELEASE keywords, and -SNAPSHOT versions, as well
as any coordinate that is not exactly three colon-separated parts. maven_deps
declared on a non-Java package is a validation error. Classifiers, exclusions,
BOM imports, and custom <repositories> are not supported today — only plain
Maven-Central coordinates.
nuget_deps
nuget_deps is the C# equivalent: it lets a C# package depend on
third-party libraries from NuGet.org. Each entry is a coordinate
PackageID:Version, for example Cronos:0.8.4. The coordinates are rendered as
<PackageReference> items into the generated service.csproj (and the
tests/Tests.csproj that axiom test runs) — both are regenerated on every
build, so you declare dependencies here rather than editing them directly (hand
edits are overwritten). The same declaration is honored by the local build
(axiom build), by axiom push, and by the server-side registry build, so a C#
node can call the library at runtime after publish.
The version must be an exact pin for reproducibility: axiom validate
rejects NuGet floating versions (6.0.*), version ranges ([1.0,2.0)), and any
coordinate that is not exactly two colon-separated parts. nuget_deps declared
on a non-C# package is a validation error. Custom NuGet feeds
(<RestoreSources>), private sources, and per-reference asset controls are not
supported today — only plain NuGet.org coordinates.
system_deps
system_deps entries are installed by the generated Dockerfile as
OS-level packages. For Python packages, the special value axiom installs
the Axiom CLI binary into the image instead of an OS package.
For Python packages, axiom validate scans node source for subprocess
calls and emits a non-blocking warning when a node invokes a common tool
that is absent from the slim base image and not listed in
build.system_deps — without the entry, the tool would be missing from the
runtime image. The scan covers a fixed list of tools: git, curl,
wget, ssh, rsync, zip, unzip, make, gcc, g++, svn, jq.
A tool outside that list (such as ffmpeg) still needs a system_deps
entry to be present in the image — it just is not flagged by validation.
Proto-only packages
A proto-only package publishes message types for other packages to import, with no nodes and no running service. A package is treated as proto-only when either:
type: proto-onlyis set explicitly, or- the
nodeslist is empty (proto-only is inferred).
For a proto-only package, the publish pipeline skips the Docker build and
the service deployment, and stores only the proto definitions and package
metadata. Other packages then pull its messages with axiom import. See
the type system.
Which commands read and write axiom.yaml
axiom init <name>creates the manifest withname,version(default0.1.0),language(defaultgo), and your--description. Unless--no-example-commentis passed, it appends a commented examplenodes:block showing therequired_secretsfield.axiom create node <Name>appends a node entry (name,description,input,output, andtype: pipelinefor pipeline nodes) after scaffolding the source files.axiom import <package>[@version]adds or updates animportsentry, merging newly downloaded message names into themessageslist.axiom validate,axiom build,axiom push,axiom dev, andaxiom testread the manifest; they must be run inside a package directory (any directory below the one containingaxiom.yaml).
When a CLI command rewrites the manifest, it re-serializes the whole file — inline comments you wrote in axiom.yaml are not preserved by these operations. Keep notes elsewhere if you need them to survive.
Validation rules
axiom validate (also run as a gate by axiom push) checks the manifest
as its first layer. Failures block publishing unless marked as warnings:
| Check | Severity |
|---|---|
name present and scoped (handle/package-name, lowercase/digits/hyphens) | error |
version present and valid semver | error |
language one of the six supported values | error |
Every node's input and output resolves to a known message | error |
Every import entry has a non-empty messages list | error |
| Imported message names exist in the downloaded proto files | warning |
secrets.Get("NAME") calls covered by required_secrets | warning |
Python subprocess tools covered by build.system_deps | warning |
retry.max_attempts within the platform ceiling; retry.per_attempt_timeout_ms not exceeding retry.overall_timeout_ms; retry.non_retryable_codes entries are TIMEOUT/TRANSPORT only | error |
kind is one of "", "concrete", "generic" — "instance" can never be hand-authored | error |
generic_ports is non-empty (and each entry is input/output, no duplicates) when kind: generic; empty otherwise | error |
A generic_ports-listed port's message declares at least one field | warning |
Later validation layers check the proto files, node function signatures, and node tests — see the axiom validate reference.