activity-core/docs/task-emission-consumer-contract.md
tegwick f0a897e088
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Container Image / build-and-push (push) Successful in 33s
Harden ops run identity and leases
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a028de-e2c8-7732-8521-46a7fc5db82f
2026-08-23 13:01:46 +02:00

4.9 KiB

Task emission consumer contract

Audience: agent-harness, per-repo Temporal workers, operators.
Owners: activity-core (producer), consumers (executors).
Related: ACTIVITY-WP-0022 (sink policy), ACTIVITY-WP-0023-T02 (executor gap), ACT-ADR-005 / ACTIVITY-WP-0026 (ops_run claim queue).

activity-core answers when / what / where. It does not execute work. Consumers must pick up emitted tasks and produce domain evidence.

Primary claim path: ops_run (ACTIVITY-WP-0026)

For scheduled / automation work (FI daily brief, Binky rhythm, etc.), the authoritative claimable instance is an ops_run row in activity-core — not issue-core and not a Forgejo ticket.

Step Call Notes
List / poll GET /ops-runs?state=open Filter by labels via claim body
Claim POST /ops-runs/claim { worker_id, labels?, limit?, lease_seconds? }
Heartbeat POST /ops-runs/{id}/heartbeat Extend lease during long runs
Complete POST /ops-runs/{id}/complete { worker_id, result }
Fail POST /ops-runs/{id}/fail { worker_id, error, reopen? }

The worker token is bound by Activity Core to one configured worker_id. Every worker mutation must name that exact identity and must still hold a lease whose deadline is strictly in the future. Operator/SSO authentication does not substitute for worker authentication on these calls.

Full field list, auth, and env: docs/ops-run-queue.md.
Consumer implementation (rein-aharness): REIN-A-0002.

Emit dual-writes: ops_run (claim) + existing IssueSink progress (state-hub by default) + task_spawn_log audit. Do not treat activity_task_spawn progress as a claim queue — it is visibility only.

Sink matrix (ACTIVITY-WP-0022) — dual-write / projection

ISSUE_SINK_TYPE Behaviour When to use
state-hub (default) POST State Hub /progress/ with event_type=activity_task_spawn (override via STATE_HUB_TASK_EVENT_TYPE) Internal fleet findings; no Forgejo issues
null Synthetic null-* refs in task_spawn_log only Dry-run / contract review
rest POST issue-core /issues/ (may project to Forgejo) Explicit opt-in only when external tracker issues are intended and backend is healthy

Unset or unknown values fall back to state-hub (safe default).
Ops claim does not require rest or issue-core.

Payload: activity_task_spawn (State Hub)

Produced by StateHubProgressSink. Consumers should treat detail as the visibility task spec; claim authority is ops_run when the queue is enabled.

{
  "event_type": "activity_task_spawn",
  "author": "activity-core",
  "summary": "<task title, max ~240 chars>",
  "detail": {
    "task_ref": "sh-<uuid>",
    "title": "Run Binky daily rhythm (daily_brief) for 2026-07-21",
    "description": "...",
    "target_repo": "binky-control",
    "priority": "medium",
    "labels": ["binky", "rhythm", "automated"],
    "source_type": "rule",
    "source_id": "emit-daily-rhythm-task",
    "triggering_event_id": "manual-… or scheduled",
    "activity_definition_id": "<activity uuid>",
    "backend": "state-hub-progress"
  }
}

Required consumer behaviour

  1. Idempotency: key on detail.task_ref or (activity_definition_id, triggering_event_id, source_id, title).
  2. Routing: use target_repo (and labels) to select checkout / lane.
  3. Completion evidence: post a domain progress event when work finishes (e.g. Binky: event_type=binky_daily_brief with detail.repo=binky-control and date), so rhythm resolvers can set due=false.
  4. Do not re-implement task lifecycle in activity-core.

Payload: issue-core REST (ISSUE_SINK_TYPE=rest)

See docs/issue-core-emission-boundary.md. task_spawn_log.task_ref is the issue-core issue_id. Prefer this only for intentional external projection.

Binky daily brief path (reference)

  1. Schedule / one-shot: Binky Daily Operating Rhythm.
  2. Context: binky_rhythm_statusdue=true for daily_brief.
  3. Rule emit → sink (state-hub recommended).
  4. Consumer (out of repo): agent-harness or operator session runs binky-control OperatingRhythm / brief scripts.
  5. Completion: post binky_daily_brief progress so the next fire is not due.

Until a harness consumer is wired, operators may complete the brief manually and still post the progress event — spawn without completion leaves due=true.

Anti-patterns

  • Adding an in-repo task executor or reviving the retired TaskExecutorWorkflow/task_instances surface.
  • Global ISSUE_SINK_TYPE=rest for all definitions (reintroduces Forgejo spam).
  • Treating task_spawn_log or State Hub activity_task_spawn as claim authority (use POST /ops-runs/claim — ACT-ADR-005).
  • Using issue-core or Forgejo as the ops automation claim queue.
  • Product references to Gitea — self-hosted forge is Forgejo only.