Task input, output, and persistence contracts
This page defines the basic contracts shared by Task authors, API users, and file consumers. See Research artifact contracts for research-specific fields and Task lifecycle for execution semantics. Timestamps and run_id values in the following examples are illustrative.
Input and output base classes
BaseInputParams uses extra="forbid" and assignment validation. Each Task's input_cls declares business fields on top of the base class.
| Base input field | Type and default | Constraints |
|---|---|---|
| task_name | string or null; default null | 1–32 letters, digits, or hyphens; an empty string is treated as unspecified |
| source_tasks | string; default empty string | Valid Task IDs separated by ASCII commas; duplicate IDs are rejected |
source_tasks strips whitespace around each item and rejoins them. source_task(TaskType.X) requires exactly one upstream task of the specified type; it does not automatically validate that the directory or artifacts exist.
BaseOutputParams uses extra="forbid" and populate_by_name=True. Its base field is artifacts: dict[str, dict[str, Any]], defaulting to an empty dictionary. output_cls should explicitly declare business extension fields.
from axonx.task.core import BaseInputParams, BaseOutputParams
class AddInput(BaseInputParams):
x: int
y: int
class AddOutput(BaseOutputParams):
result: intbuild_output_params() must return an output_cls instance; returning a plain dict or the wrong type fails. Output is readable only after execution completes and prepare_output runs.
TaskContext
TaskContext is a frozen dataclass attached to a Task, providing runtime-owned values:
| Field | Type | Purpose |
|---|---|---|
| workspace_path | Path | Resolved workspace root |
| registration_name | string | Definition registration name |
| task_id | string | Directory identity |
| run_id | string | Execution identity |
| created_at | datetime | Creation time for this execution |
| logger | Logger object | Record step execution |
Plugins should read these values without changing execution identity. task.task_dir is <workspace>/<type>/<task_id>; source_task_dir(id) locates an upstream directory in the same workspace.
TaskDefinition and TaskHandle
TaskDefinition describes an executable definition with name, source (native/plugin), plugin (nullable), task_type, description, input_schema, and output_schema. description comes from the Task class's detailed docstring; definitions without valid descriptions are rejected.
TaskHandle is the immutable dataclass returned by submit:
{
"task_id": "base#demo#contract-01",
"run_id": "a9f248807a0a496abf3738422b379a51",
"task": "demo"
}The task field is the registration name. There is no task_name field or address for automatic remote routing. Clients must retain execution-machine information and use task_id + run_id to wait for this execution.
TaskStatus
| Field | Type/default | Meaning |
|---|---|---|
| task_id | string; required | Must match the directory ID |
| run_id | Nonempty string; required | Current execution identity |
| task_type | TaskType; required | Must match the type in the ID |
| task_name | string; default empty string | The current implementation writes the registration name |
| config | object; default {} | JSON representation of typed input, including the instance task_name |
| state | TaskState; default queued | Current execution state |
| pid | integer or null | Process identifier at that time |
| created_at / started_at / finished_at | datetime or null | Creation, start, and finish times |
| steps | TaskStepStatus[]; default [] | Snapshots of started steps |
| result | object; default {} | Constructed typed output |
| error | string; default empty string | Error description |
| exit_code | integer; default 0 | 0–255 |
| log_path | string; default empty string | Separate log file location |
TaskStepStatus contains nonempty name, nullable started_at/finished_at, and nullable percentage (0–100). The percentage describes the current step, rather than progress of the entire research task.
A simplified complete example of a successful status:
{
"task_id": "base#demo#contract-01",
"run_id": "a9f248807a0a496abf3738422b379a51",
"task_type": "base",
"task_name": "demo",
"config": {
"task_name": "contract-01",
"source_tasks": "",
"x": 1,
"y": 2,
"fail": false
},
"state": "succeeded",
"pid": 12345,
"created_at": "2026-01-05T09:00:00+08:00",
"started_at": "2026-01-05T01:00:00Z",
"finished_at": "2026-01-05T01:00:01Z",
"steps": [
{
"name": "finish",
"started_at": "2026-01-05T01:00:00Z",
"finished_at": "2026-01-05T01:00:01Z",
"percentage": 100
}
],
"result": {
"artifacts": {},
"result": 3,
"branch": "different",
"operands": ["x", "y"]
},
"error": "",
"exit_code": 0,
"log_path": ""
}steps shows only the final step; the actual demo includes more steps. The log value is empty solely to avoid specifying a deployment path; the logging system supplies the actual value.
The key differences on failure are shown below; this is not a separate complete TaskStatus:
{
"state": "failed",
"result": {},
"error": "RuntimeError: Demo failure requested",
"exit_code": 1
}A custom nonzero exit code also produces failed, but result may already exist; failure does not necessarily mean output was never constructed. The manager records cancellation as cancelled, usually with exit code 130.
TaskMetadata
The metadata top level strictly represents successful tasks' inputs and outputs:
{
"task_id": "base#demo#contract-01",
"reg_name": "demo",
"created_at": "2026-01-05T09:00:00+08:00",
"task_type": "base",
"input_params": {
"task_name": "contract-01",
"source_tasks": "",
"x": 1,
"y": 2,
"fail": false
},
"output_params": {
"artifacts": {},
"result": 3,
"branch": "different",
"operands": ["x", "y"]
}
}metadata has no top-level run_id, state, result, or error. TaskStatus.result corresponds to metadata.output_params; the task identity's registration name is in metadata.reg_name.
A successful execution first constructs output and determines the exit code, then writes metadata and publishes successful status. When metadata is written, nonfinite floating-point values are recursively converted to null for JSON consumers.
Artifact records
{
"artifacts": {
"dataset": {
"path": "data/dataset.parquet",
"size": 10240,
"sha256": "<64-digit hexadecimal checksum>"
}
}
}The base class only specifies artifacts as a nested mapping. path, size, and sha256 are records generated by standard artifact utilities, which research plugins should use. artifact_path() requires a nonempty path relative to the Task directory that does not escape it.
Do not include the workspace root in artifact path. Consumers combine <type>/<task_id>/<artifact.path> and check the producing plugin's expected logical names, such as dataset, model, or daily.
Compatible reads and events
TaskStatus reading supports the historical execution_id field as a validation alias for run_id. New writes still use run_id; do not write both identity fields or treat execution_id as a new API field.
Workspace reading rejects invalid JSON, incorrect task_id/type, and unsafe directories, making records appear missing. Tolerant reading isolates bad records; it does not guarantee automatic repair of old data.
events.jsonl is an append-only progress record; ordinary logs are stored at log_path. Writers record progress first, then publish matching status, giving terminal-state consumers a chance to read preceding events. See Event protocol for transport projections and final result rules.
Jobs and Tasks · Task API · Research artifact contracts
Source: Input/output, Context, Definitions, Handle, Status, Metadata.