Run

wippy run kickside/automation

KickSide Automation

kickside/automation owns the runtime for installed Automations. An automation kind is a contract.binding with meta.type: kickside.automation; its install contract declares the source, while the shared engine owns durable execution. Data Sync in platform/sync is a working kind.

Drives and rounds

An installed Automation is a component with a main drive and, when declared, one timer drive per schedule. Each active drive has one reusable kickside.automation:round job slot. A round acquires a bounded page, applies the declared transform, delivers to the sink, and commits its cursor past the whole page together with the ledger rows of the items it did not deliver and the receipts of destinations that changed. The drive row holds the cursor, phase, revision, deadlines, and retry state. A failed, waiting or uncertain item stays in the ledger in its record's order and retries on its own pacing; it never holds the items after it, and it is never treated as complete while its write is unconfirmed. A first-attempt delivery writes no ledger row.

Event drives pull committed thread events and wake when the source thread appends an event. Collection drives pull from kickside.data:pullable. Timer drives acquire persisted occurrences from kickside.automation:timer_pullable. Schedules use interval, cron, once, or ticker cadence with a timezone and wake the slot at an exact deadline. The automation engine owns these drives and slots; automation kinds do not create a second polling worker.

Source → transform → sink

Domain modules publish registry.entry ports with meta.type: kickside.automation.port. An event source port names a declared thread event; a collection source uses kickside.data:pullable. A destination port binds kickside.data:writable and declares its supported operations. The installed kind selects a source port, an optional transform, and a sink port. Rounds carry stable item identity and per-delivery idempotency keys through that path. The sink confirms writes with classified results; the engine records receipts and advances only after the round's durable work succeeds.

Editors read real payload samples with POST /api/v1/automations/source-samples: { "source": "port-entry-id", "config": {}, "limit": 1 }. The limit is an integer from 1 through 100. The response contains samples and the selected instance's output_schema when declared. This reads one bounded source page under the caller's identity through the existing acquisition path and leaves installed drive positions unchanged. Deleted items supply no payload sample; an empty source returns an empty array. Sync passes these samples to Transform for validation and generation.

Writable bindings must declare meta.delivery (exactly_once, at_least_once, at_most_once) and meta.input_shape (record, content); describe_sink refuses missing or invalid values. Round consumers answer each input exactly once across their outcome lists, using key = item.delivery_key.

Work ends at the earlier of round start plus round.timeout and the job's input.job.deadline, minus round.commit_reserve. Blocking sink waits use this work deadline. Stored ledger, receipt, command, and prepared data use age-based retention windows. Size limits remain max_row_bytes, prepared_bytes_per_item, and destination_slots_per_item; oversized rows return refusal reason row_too_large.

Commands and controls

Declaration changes and controls enter each Automation's command stream. A command fences affected drives with a new generation so an older round cannot commit over it. The dispatcher recovers pending steps, wakes drive slots, and repairs missing slots. Callers read operational state through status and health and can use pause, resume, run_now, resync, replay, and discard. The type catalog publishes engine controls alongside kind-declared actions; see the control model. resync restarts source position and reconciliation; replay retries eligible ledger items. discard resolves named uncertain item keys. All three require a caller supplied request_id. Repeating the same ID with the same input returns the recorded outcome; changing the input under that ID conflicts. set_execution_identity is an ADMIN control for changing the execution principal and also requires a caller ID and expected identity generation.

Authority-bearing inputs

Kind input schemas can mark a field with x-authority: subject. Only the execution subject can change that field, including adding or removing it. The annotation applies to the whole value of an object or array; nested properties (including object schemas without an explicit type) and homogeneous array item schemas can also carry it. Protected annotations under unsupported composition, references, tuple items or dynamic property schemas are rejected rather than silently ignored. Schema traversal rejects cycles and nesting beyond the canonical JSON limit. For example:

inputs:
  type: object
  properties:
    title: { type: string }
    behavior:
      type: object
      x-authority: subject

Mark opaque function targets and configuration, code, tool configuration and LLM settings that execute with the subject's authority. The declaration validator compares against the protected stored input before reserving a reconfigure command; the trusted writer repeats the check before writing. Unmarked fields retain their existing authorization rules. Existing declarations keep running; an edit that leaves marked values unchanged can proceed under the existing reference grants.

Further reading

For authoring and operational details, use the developer handbook chapters Threads, Events, And Projections, Execution Identity And Deadline Jobs, and Automations And Dataflows.

Receipts and deletion

Every writable call supplies origin; kickside.data:writable_sink checks it and its agreement with automation_ref.id. Rounds use the automation ID; non-round producers use a stable explicit origin such as example.meetings.link. Held or failed items create ledger rows; resolved tombstones carry an expiry. Dead evidence remains while its recovery owner exists, and unresolved work remains until it settles. Engine receipts change only when their destination mapping changes or reconciliation updates their pass marker. Deleting a sync retires its receipts and keeps destination records.