akm-cli
Version:
akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cur
467 lines (466 loc) • 19.2 kB
JSON
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://itlackey.github.io/akm/schemas/akm-workflow.json",
"title": "AKM Workflow",
"description": "This frontmatter schema applies to and validates the Markdown (.md) workflow source: the standard AKM asset envelope plus its orchestration graph. Markdown is not the sole or only workflow source; peer GitHub-shaped .yml sources use the bounded adapter and both formats compile to source IR v1. Per-step prose instructions and gate rubrics live in the Markdown body, joined to this graph by step id. Hand-authored alongside src/workflows/program/schema.ts; see docs/reference/workflow-schema.md.",
"type": "object",
"required": ["steps"],
"additionalProperties": false,
"properties": {
"type": { "$ref": "akm-asset-envelope.json#/definitions/type" },
"description": { "$ref": "akm-asset-envelope.json#/definitions/description" },
"tags": { "$ref": "akm-asset-envelope.json#/definitions/tags" },
"when_to_use": { "$ref": "akm-asset-envelope.json#/definitions/when_to_use" },
"xrefs": { "$ref": "akm-asset-envelope.json#/definitions/xrefs" },
"updated": { "$ref": "akm-asset-envelope.json#/definitions/updated" },
"timestamp": { "$ref": "akm-asset-envelope.json#/definitions/timestamp" },
"generated": { "$ref": "akm-asset-envelope.json#/definitions/generated" },
"verified": { "$ref": "akm-asset-envelope.json#/definitions/verified" },
"provenance": { "$ref": "akm-asset-envelope.json#/definitions/provenance" },
"status": { "$ref": "akm-asset-envelope.json#/definitions/status" },
"stale_after": { "$ref": "akm-asset-envelope.json#/definitions/stale_after" },
"params": {
"type": "object",
"minProperties": 1,
"description": "Run parameters: param name -> JSON Schema declaration. Names must be params.<name>-addressable identifiers.",
"propertyNames": {
"pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
},
"additionalProperties": {
"$ref": "#/definitions/jsonSchemaObject"
}
},
"outputs": {
"type": "object",
"minProperties": 1,
"description": "Named, optionally schema-validated projections of step artifacts, exported when the run completes. Names must be steps.<child>.output.<name>-addressable identifiers. Each entry: { from: steps.<id>.output(.<seg>)*, schema?: <bounded JSON Schema> }. Markdown-frontmatter only.",
"propertyNames": {
"pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
},
"additionalProperties": {
"$ref": "#/definitions/outputDeclaration"
}
},
"defaults": {
"$ref": "#/definitions/defaults"
},
"budget": {
"$ref": "#/definitions/budget"
},
"steps": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/definitions/step"
}
}
},
"definitions": {
"identifier": {
"type": "string",
"pattern": "^[A-Za-z_][A-Za-z0-9_-]*$",
"description": "Step id: a letter or underscore first, then letters, digits, underscores, or dashes (no dots, no leading digit). This is exactly the steps.<id>.output-addressable grammar; dots are reserved for the .output path separator and the engine's internal <stepId>.gate node id."
},
"reference": {
"type": "string",
"minLength": 1,
"description": "A bare reference string: params.<name> or steps.<id>.output(.<ident>|[<int>])*. No ${{ }} delimiters — every position this appears in is a whole-value frontmatter field. Full grammar validation happens at compile time, not here."
},
"timeout": {
"description": "Duration: \"<n>ms\" | \"<n>s\" | \"<n>m\" | \"none\" (bare integers are milliseconds). Must resolve to at most 2147483647 ms (setTimeout's 32-bit signed ceiling) — the parser enforces the same ceiling on the string form.",
"oneOf": [
{
"type": "string",
"pattern": "^([0-9]+(ms|s|m)?|none)$"
},
{
"type": "integer",
"minimum": 1,
"maximum": 2147483647
}
]
},
"engineName": {
"type": "string",
"minLength": 1,
"maxLength": 63,
"pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$",
"description": "Named engine: lowercase dash-separated words of letters and digits, starting with a letter. Same grammar the frozen-plan decoder enforces (src/workflows/resource-limits.ts WORKFLOW_ENGINE_NAME_PATTERN)."
},
"onError": {
"description": "Failure policy: fail the step on the first unit failure (default), or record failures and let the gate decide.",
"enum": ["fail", "continue"]
},
"llm": {
"type": "object",
"additionalProperties": false,
"properties": {
"temperature": { "type": "number" },
"max_tokens": { "type": "integer", "minimum": 1 },
"supports_json_schema": { "type": "boolean" },
"extra_params": { "$ref": "#/definitions/extraParams" },
"context_length": { "type": "integer", "minimum": 1 },
"enable_thinking": { "type": "boolean" },
"reasoning_effort": { "type": "string", "minLength": 1 }
}
},
"extraParams": {
"type": "object",
"description": "Provider-specific request fields. Top-level AKM-owned fields are forbidden; credential-shaped keys are forbidden recursively after lowercase/non-alphanumeric normalization.",
"propertyNames": {
"not": {
"enum": ["model", "messages", "temperature", "maxtokens", "responseformat", "stream", "streamoptions", "enablethinking", "reasoningeffort", "chattemplatekwargs", "authorization", "headers", "apikey", "token", "password", "secret", "cookie", "setcookie"]
}
},
"additionalProperties": { "$ref": "#/definitions/extraParamValue" },
"x-akm-protectedTopLevelNormalizedKeys": ["model", "messages", "temperature", "maxtokens", "responseformat", "stream", "streamoptions", "enablethinking", "reasoningeffort", "chattemplatekwargs"],
"x-akm-recursivelyForbiddenNormalizedKeys": ["authorization", "headers", "apikey", "token", "password", "secret", "cookie", "setcookie"]
},
"extraParamValue": {
"anyOf": [
{ "type": ["string", "number", "boolean", "null"] },
{ "type": "array", "items": { "$ref": "#/definitions/extraParamValue" } },
{
"type": "object",
"propertyNames": {
"not": {
"enum": ["authorization", "headers", "apikey", "token", "password", "secret", "cookie", "setcookie"]
}
},
"additionalProperties": { "$ref": "#/definitions/extraParamValue" },
"x-akm-recursivelyForbiddenNormalizedKeys": ["authorization", "headers", "apikey", "token", "password", "secret", "cookie", "setcookie"]
}
]
},
"reducer": {
"description": "How a map step folds per-item unit results into the step artifact.",
"enum": ["collect", "vote"]
},
"isolation": {
"description": "Filesystem isolation for file-mutating units.",
"enum": ["none", "worktree"]
},
"failureReason": {
"description": "Persisted AgentFailureReason taxonomy (src/integrations/agent/spawn.ts).",
"enum": [
"timeout",
"spawn_failed",
"non_zero_exit",
"parse_error",
"cooldown",
"llm_rate_limit",
"llm_content_filter",
"llm_invalid_json",
"content_policy_reject",
"unsupported_type",
"no_change",
"quality_rejected",
"aborted"
]
},
"jsonSchemaObject": {
"type": "object",
"description": "A JSON Schema declaration. The parser validates it as a schema definition against the runtime's enforced subset (src/core/json-schema.ts checkJsonSchemaDefinition): typo'd type names and keywords outside the subset (e.g. $ref, const, format, patternProperties) are authoring-time errors, not silent no-ops."
},
"outputDeclaration": {
"type": "object",
"description": "One outputs: entry: a validated steps.<id>.output(.<seg>)* reference into a step artifact, plus an optional bounded JSON Schema.",
"required": ["from"],
"additionalProperties": false,
"properties": {
"from": {
"type": "string",
"description": "A steps.<id>.output(.<seg>)* reference into a step artifact. Never params.<name> — an output projects a step artifact, never a param."
},
"schema": {
"$ref": "#/definitions/jsonSchemaObject"
}
}
},
"retry": {
"type": "object",
"description": "Bounded retry on transient failures, keyed on the persisted failure_reason taxonomy.",
"required": ["max", "on"],
"additionalProperties": false,
"properties": {
"max": {
"type": "integer",
"minimum": 0,
"description": "Maximum retry attempts per unit."
},
"on": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/definitions/failureReason"
}
}
}
},
"budget": {
"type": "object",
"description": "Run-level budget ceilings, enforced by the engine per run (journal-seeded). Hitting a ceiling fails the step hard, regardless of on_error.",
"additionalProperties": false,
"properties": {
"max_tokens": {
"type": "integer",
"minimum": 1,
"description": "Ceiling on total reported token usage across the run (seeded from journaled unit rows)."
},
"max_units": {
"type": "integer",
"minimum": 1,
"description": "Ceiling on total dispatched units across the run (seeded from journaled unit rows)."
}
}
},
"defaults": {
"type": "object",
"description": "Run-level defaults, overridable per unit.",
"additionalProperties": false,
"properties": {
"engine": {
"$ref": "#/definitions/engineName"
},
"model": {
"type": "string",
"minLength": 1,
"description": "Model alias (tier) or exact id; resolved per-harness at dispatch."
},
"timeout": {
"$ref": "#/definitions/timeout"
},
"on_error": {
"$ref": "#/definitions/onError"
},
"llm": {
"$ref": "#/definitions/llm"
}
}
},
"exec": {
"type": "object",
"description": "Run a shell command as this unit instead of dispatching to an engine. `command` is an ARGV ARRAY and there is no shell-string form: the child is spawned directly, so shell metacharacters (`;` `|` `&&` `$(…)` `>` `*`) are inert literal argument bytes — the quoting/injection class simply does not exist. Write the interpreter explicitly (e.g. [\"bash\", \"-lc\", \"a | b\"]) when a pipeline is really wanted. stdout is the promoted step artifact with trailing newlines stripped (like shell `$(…)`); when the unit declares an `output` schema, stdout must instead be exactly one JSON value, which is validated against it. A non-zero exit fails the unit with failure reason `non_zero_exit`, honoring `retry:` and `on_error:` like any other unit. The child's environment is an ALLOWLIST by default (PATH, HOME, locale/temp vars, the Windows process-creation essentials) — widen it only with named `env:` environment bindings or `pass_env`.",
"required": ["command"],
"additionalProperties": false,
"properties": {
"command": {
"type": "array",
"description": "argv; command[0] is the program, resolved through PATH. Never shell-parsed.",
"minItems": 1,
"items": {
"type": "string",
"minLength": 1
}
},
"cwd": {
"type": "string",
"description": "Optional RELATIVE working directory inside the unit's working directory (its fresh worktree under `isolation: worktree`). Absolute paths, Windows drive letters, \"~\", and \"..\" segments are rejected here, and containment is re-checked against the resolved base — symlinks included — before the command is spawned.",
"minLength": 1,
"pattern": "^(?!/|\\\\|~|[A-Za-z]:)(?!.*(?:^|[/\\\\])\\.\\.(?:[/\\\\]|$)).+$"
},
"pass_env": {
"type": "array",
"description": "Extra environment variable NAMES copied through from akm's own environment on top of the default allowlist — for a per-machine toolchain variable (e.g. CARGO_HOME) that no committed `env:` asset could carry. NAMES ONLY: values never appear in a workflow, and unlike `env:` bindings these values are NOT redacted from the command's output, so never list a credential here.",
"minItems": 1,
"uniqueItems": true,
"items": {
"type": "string",
"pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
}
}
}
},
"unit": {
"type": "object",
"description": "Optional dispatch-override bag for a unit/map step. A step with none of these carries the run's engine/model/timeout defaults verbatim. Declaring `exec` turns the step into a shell-command unit: it names no engine, so `engine`, `model`, and `llm` are rejected alongside it.",
"additionalProperties": false,
"allOf": [
{
"if": {
"required": ["exec"]
},
"then": {
"not": {
"anyOf": [
{
"required": ["engine"]
},
{
"required": ["model"]
},
{
"required": ["llm"]
}
]
}
}
}
],
"properties": {
"exec": {
"$ref": "#/definitions/exec"
},
"engine": {
"$ref": "#/definitions/engineName",
"description": "Named engine selected when the workflow run is created."
},
"model": {
"type": "string",
"minLength": 1
},
"llm": {
"$ref": "#/definitions/llm"
},
"timeout": {
"$ref": "#/definitions/timeout"
},
"retry": {
"$ref": "#/definitions/retry"
},
"on_error": {
"$ref": "#/definitions/onError"
},
"output": {
"$ref": "#/definitions/jsonSchemaObject"
},
"env": {
"type": "array",
"uniqueItems": true,
"items": {
"type": "string",
"minLength": 1
},
"description": "Env asset refs injected into the dispatched unit env. Entries must be unique: the frozen-plan decoder rejects duplicates at run start."
},
"isolation": {
"$ref": "#/definitions/isolation"
}
}
},
"map": {
"type": "object",
"description": "Fan the step out over a reference-addressed item list.",
"required": ["over"],
"additionalProperties": false,
"properties": {
"over": {
"$ref": "#/definitions/reference",
"description": "Reference naming the producer of the item list (e.g. steps.discover.output.files)."
},
"concurrency": {
"type": "integer",
"minimum": 1,
"maximum": 64,
"description": "Max concurrent units for this step. Defaults to workflow.defaultMapConcurrency (4 when unset) — a map step is parallel by default since 0.9.1; write 1 for serial execution. The effective width is the minimum of this, workflow.maxConcurrency, the selected engine's concurrency, and the host CPU cap."
},
"reducer": {
"$ref": "#/definitions/reducer"
},
"unit": {
"$ref": "#/definitions/unit"
}
}
},
"routeBranch": {
"type": "object",
"required": ["match", "step"],
"additionalProperties": false,
"properties": {
"match": {
"type": ["string", "number", "boolean"],
"minLength": 1,
"description": "Branch key. An empty string is rejected: the frozen-plan decoder requires every `when` key to be non-empty."
},
"step": {
"$ref": "#/definitions/identifier"
}
}
},
"route": {
"type": "object",
"description": "Route on an explicit reference input to a LATER step. Targets must come after the routing step.",
"required": ["input", "when"],
"additionalProperties": false,
"properties": {
"input": {
"$ref": "#/definitions/reference",
"description": "Reference naming the value to route on."
},
"when": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/definitions/routeBranch"
}
},
"default": {
"$ref": "#/definitions/identifier"
}
}
},
"gate": {
"type": "object",
"description": "Optional validation-loop configuration. The rubric itself lives in the body under a `### gate` sub-heading inside the step's section; omitted or empty rubric text skips validation.",
"additionalProperties": false,
"properties": {
"max_loops": {
"type": "integer",
"minimum": 1,
"description": "Evaluator-optimizer loop bound."
}
}
},
"inputs": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": {
"$ref": "#/definitions/reference"
},
"description": "Prior-step artifacts this unit/map step consumes, as reference strings (sub-paths legal). Attached to the dispatched unit as structured context; never spliced into instructions. Entries must be unique: the frozen-plan decoder rejects duplicates at run start."
},
"step": {
"type": "object",
"description": "One step of the gated spine. A step with no map/route is a unit step (bare `{id: ...}` is the complete minimal declaration). At most one of map/route.",
"required": ["id"],
"additionalProperties": false,
"properties": {
"id": {
"$ref": "#/definitions/identifier"
},
"unit": {
"$ref": "#/definitions/unit"
},
"map": {
"$ref": "#/definitions/map"
},
"route": {
"$ref": "#/definitions/route"
},
"inputs": {
"$ref": "#/definitions/inputs"
},
"output": {
"$ref": "#/definitions/jsonSchemaObject"
},
"gate": {
"$ref": "#/definitions/gate"
}
},
"allOf": [
{
"not": { "required": ["map", "route"] }
},
{
"if": { "required": ["route"] },
"then": { "not": { "anyOf": [{ "required": ["unit"] }, { "required": ["map"] }, { "required": ["inputs"] }] } }
},
{
"if": { "required": ["map"] },
"then": { "not": { "required": ["unit"] } }
}
]
}
}
}