capabilities
Eleven ways to do work. One contract.
A capability is a typed unit of work the workflow can call. Behind each one sits an executor — the thing that actually runs. Whatever the kind, the kernel applies the same loop: grant, execute, validate.
The executor kinds
All eleven, declared the same way in YAML. No glue code, no per-tool wrappers.
| Kind | What it runs |
|---|---|
| cli | A shell command. Captures stdout, stderr, and exit code as structured output. |
| script | A curated, hash-pinned script from the script library. Safe for deterministic steps. |
| rest | An HTTP request — templated paths, mapped bodies, optional idempotency keys. |
| mcp | A tool on a connected MCP server (stdio or SSE), with argument mapping. |
| human | Queues the step for a person and emits an audit event. Pairs with actor: human. |
| llm | A governed LLM call — the state's legal transitions become the model's tool list. |
| agent | A feature-gated LLM overlay, sibling to llm, for agentic sub-runs. |
| workflow | A sub-workflow, run as its own instance while the parent step waits. |
| parallel | Fans out N executor branches inside one transition and aggregates results. |
| pipeline | A sequence of executor steps, each output threaded into the next. |
| noop | Returns immediately. Stubs, tests, and transitions that only move state. |
MCP is one transport among many. Praxec has first-class MCP support — but a capability is just as happily a CLI, an HTTP service, a pinned script, a sub-workflow, or a person. You compose them freely inside one workflow.
processes
CLIs and scripts
The cli executor runs a command and gives you structured output — exitCode, stdout, stderr, and parsed JSON when stdout is JSON. Reliability policy is declared right next to it:
executor:
kind: cli
connection: dotnet
args: [test, "$.arguments.project"]
reliability:
timeoutMs: 120000
retry: { maxAttempts: 3, backoff: exponential }The script executor is the stricter sibling: it runs only curated, hash-pinned scripts from a library. If the script on disk doesn't match the pinned hash, it doesn't run. Use it for the deterministic steps you never want silently swapped.
services
HTTP services and MCP tools
The rest executor calls HTTP services through named connections, with path templating and body mapping from workflow context. Set idempotencyKey and the kernel derives a stable key across retries — your backend can deduplicate even if a fallback executor takes over:
executor:
kind: rest
connection: payroll
method: POST
path: /reimbursements
body:
employee: "$.workflow.input.employee"
amount: "$.workflow.input.amount"
idempotencyKey: true # retries can't double-payThe mcp executor calls a tool on any connected MCP server. This is how you bring an existing MCP ecosystem under kernel governance — the tool stays where it is, but access to it becomes a granted, validated step instead of a standing menu item:
executor:
kind: mcp
connection: github
tool: list_issues
map:
repo: "$.arguments.repo"people
Humans are capabilities too
Approvals aren't an afterthought bolted onto the side — a person is a first-class executor. Two pieces work together: the humanexecutor routes the request to a named queue and puts it on the audit record, and the actor: human gate makes the approving move unsubmittable by the model:
transitions:
approve:
actor: human # the gate: only humans can submit this
target: approved
executor:
kind: human # the routing: queue it for review
queue: approvalsThe model walks the workflow up to the gate and stops cold. A human — through your dashboard, approval tool, or a Slack bot watching the queue — makes the actual decision.
models
LLM steps, inside the boundary
The llm executor hosts a governed model call inside the runtime. The transitions legal at the current state are the model's entire tool list; it picks one per turn and the kernel advances the workflow. Iterations, wall-clock time, tokens, and cumulative cost are all capped in config — and a tools: field is rejected at parse time, so nobody can widen the box from inside a workflow file.
The agent executor is its feature-gated sibling for agentic sub-runs. Same principle: the model works inside a bounded execution context the workflow defines, never the other way around.More on bounded contexts →
composition
Workflows compose like functions
workflowstarts a sub-workflow as its own instance — its own state, its own audit trail — while the parent step waits.parallelfans out multiple executor branches inside a single transition and aggregates the results. Branches can be any kind, including nestedparallel.pipelineruns executor steps in sequence, threading each output into the next input — a small data pipeline inside one transition.
And instead of declaring executors inline, you can reference a named capability. It resolves at config-load time and brings its guards and reliability policy with it:
executor:
capability: github.list_issues # resolves at config-load time;
# its guards + reliability come alongreliability
Failure handling is declarative
Any executor can carry a reliability policy: a timeout, retries with fixed or exponential backoff, and a fallback chain that tries alternate executors in order until one succeeds. You declare which failures are retryable — timeouts, transient errors, rate limits, connection errors — and idempotency keys stay stable across retries and fallbacks, so downstream systems can deduplicate.
That's the point of putting a kernel under the workflow: retries, timeouts, and fallbacks are execution concerns, so they live in the execution layer — not scattered through prompts and glue scripts.