Skip to content

Actions and Jobs

Actions are buttons with a runner behind them. Use them for bounded operations such as Test connection, Refresh status, Populate embeddings, or Run evaluation sample.

ActionSpec

An ActionSpec describes the operator-facing command: label, summary, fields, resources, effects, cancellation mode, and runner.

ActionRegistry validates at startup that action keys are unique and that every page_key refers to a registered page.

from groundskeeping.contracts import (
    ActionRegistry,
    ActionSpec,
    ExecutionKind,
    FieldKind,
    FieldSpec,
)

registry = ActionRegistry(
    (
        ActionSpec(
            key="setup.verify",
            page_key="setup",
            label="Verify connection",
            summary="Open a read-only connection and report the server version.",
            runner=verify_runner,
            fields=(FieldSpec(key="timeout", label="Timeout", kind=FieldKind.INTEGER),),
            execution=ExecutionKind.QUICK,
        ),
    )
)

Fields, parsing, and redaction

FieldSpec parses and redacts input before it reaches the runner. Each field declares a FieldKindTEXT, SECRET, MULTILINE, INTEGER, DECIMAL, BOOLEAN, CHOICE, EXISTING_PATH, or OUTPUT_PATH — and parse returns both the real value and a presentation-safe value.

A field is masked when it is explicitly sensitive or when its kind is SECRET. Masked values render as <redacted> in confirmations, diffs, and result surfaces.

Numeric fields validate bounds through minimum and maximum, and any field may carry a validator that returns a ValidationIssue. Parsing failures raise ValueError with the field's operator-facing label, not its key.

Running an action

The runner receives an ActionContext with progress and cancellation. That lets a runner report useful status without importing Textual widgets.

from collections.abc import Mapping

from groundskeeping.contracts import (
    ActionContext,
    ActionOutcome,
    SemanticStatus,
    TableRow,
    TableView,
)


def verify_runner(params: Mapping[str, object], context: ActionContext) -> ActionOutcome:
    context.emit("verify", completed=0, total=1, message="connecting")
    ...
    return ActionOutcome(
        status=SemanticStatus.OK,
        summary="Connection verified",
        view=TableView(
            title="Connection",
            columns=("check", "result"),
            rows=(TableRow(key="server", cells=("server", "postgres 16.2")),),
        ),
    )

ActionOutcome.view is a SurfaceViewTableView, TreeView, EmptyView, or LoadingView — so an action decides how its own result is presented in the upper-right pane. refresh_pages names the page keys whose content is now stale.

run_action_sync runs an action end to end for tests, demos, and simple quick actions: it parses params, runs preflight validation, builds a context, and returns the outcome.

Operation policy

OperationPolicy decides whether an action may proceed and what the operator is told first. AllowAllOperationPolicy is the permissive default. Applications can substitute a policy that uses their own vocabulary and blocks unsafe operations.

Jobs

A shell job is work launched by this TUI process. It is not a durable processing queue.

Use JobManager and JobPolicy to gate in-process work and show progress. SingleForegroundJobPolicy is the default and allows one foreground job at a time.

Keep queue state, retries, leases, and durable records inside the application. The shell tracks what is running right now so it can render progress and honour cancellation; it does not remember work across restarts.