Task Contract
Anvil keeps AWS business logic in plain Python task modules while the engine handles authentication, role assumption, dependency ordering, result aggregation, and concurrency.
Task Discovery
Tasks are discovered from two sources:
- Stock tasks shipped with Anvil under
anvil.tasks. - Plugin tasks registered through the
anvil.tasksentry-point group.
Directories named tasks/ are conventional only. They are not automatically
scanned unless the surrounding project registers tasks through the plugin
mechanism.
Once configured, custom tasks behave like stock tasks:
tasks:
- name: inventory
- name: cleanup
depends_on: [inventory]
Runtime Contract
Each task module must define a callable run function. This is the minimum
interface required for Anvil to discover and execute a task.
from anvil.actions import ActionRecorder
def run(
*,
account_id: str,
account_alias: str,
session,
dry_run: bool,
metadata: dict[str, object],
actions: ActionRecorder,
) -> None:
"""
Execute the task for one AWS account-region pair.
"""
Arguments
account_id: AWS account ID currently being processed.account_alias: Friendly name of the account.session: boto3-style session already scoped to the target account and region.dry_run: Indicates whether the task should make changes.metadata: Organization metadata defined in the configuration file.actions: Action recorder provided by Anvil for planned or completed work.
The return value is optional. Any returned data may be included in execution results.
Returned Results
A task can return any JSON-serializable value from run(). Returned data is the
native baseline for structured task output. It is stored under each task
result's result field and is useful for inventory, measurements, findings,
resource IDs, counts, timing, and other task-specific data.
def run(
*,
account_id: str,
account_alias: str,
session,
dry_run: bool,
metadata: dict[str, object],
actions=None,
) -> dict[str, object]:
user_name = str(metadata["user_name"])
iam = session.client("iam")
groups = [
group["GroupName"]
for group in iam.list_groups_for_user(UserName=user_name)["Groups"]
]
return {
"user_name": user_name,
"dry_run": dry_run,
"groups": groups,
"summary": {"groups": len(groups)},
}
The returned value appears in the task result:
{
"task": "function_returned_results",
"region": "us-east-1",
"status": "success",
"result": {
"user_name": "example-user",
"dry_run": false,
"groups": ["Developers"],
"summary": {
"groups": 1
}
},
"error": null
}
Returned data is also available in the flattened JSONL query artifact. See CLI results for query examples.
ActionRecorder
Tasks can use Anvil-provided utilities to produce structured results.
ActionRecorder allows tasks to:
- record planned or executed actions
- produce structured output for reporting
- integrate with Anvil execution summaries
Using these utilities is not required, but it is recommended for tasks that modify infrastructure or need richer audit output.
Record concise audit-level actions from a task:
from anvil.actions import ActionRecorder
def run(
*,
account_id: str,
account_alias: str,
session,
dry_run: bool,
metadata: dict[str, object],
actions: ActionRecorder,
) -> None:
if dry_run:
actions.record("(dry-run) Would validate account configuration")
else:
actions.record("Validated account configuration")
Choosing a Result Channel
Use returned results when the task needs to report structured data:
- inventory lists
- counts and measurements
- validation findings
- resource IDs and metadata
- timing or diagnostic values
Use ActionRecorder when the task needs a concise audit trail:
- created, updated, or deleted resources
- skipped resources and decisions
- dry-run planned actions
- governance or cleanup outcomes
Production tasks may use both channels when that is useful.
See the Anvil Results examples
for complete returned-result and ActionRecorder examples.
Task Validation
Anvil includes a task validation mode that checks discovered tasks for structural correctness without executing them:
anvil tasks validate
Validation verifies that:
- the task has a valid non-empty name
- the task exposes a callable
run(...)entrypoint - the
run(...)signature includes required runtime parameters - the task does not use unsupported positional-only parameters
- duplicate task names are rejected
Because this validation is structural, it does not perform AWS calls or execute task logic.
Example validation failure:
[ERROR] task validation failed:
- task 'cleanup' is missing required run() parameters: ['account_alias']
- task 'inventory' is missing required run() parameters: ['metadata']
Example validation success:
[OK] all tasks are valid
Dependency-Aware Execution
Tasks execute in dependency order within each account-region pair.
If a task depends on a failed earlier dependency, Anvil records that task as blocked by dependency failure. Optional tasks can be skipped after dependency failure without failing the entire account, while non-optional task failures stop further execution for that region.