activity-core/docs/task-emission-consumer-contract.md
tegwick 4f5399df84
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 3s
Build and Publish Container Image / build-and-push (push) Successful in 28s
Implement ACTIVITY-WP-0022/0023: safe sink default and gap closures
Default ISSUE_SINK_TYPE to state-hub (no silent Forgejo issues), hard-fail
prune apply without live-images protection, refresh-live-images script,
disable TaskExecutor stub by default, and document consumer/sink contracts.
2026-07-21 21:40:08 +02:00

3.2 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).

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

Sink matrix (ACTIVITY-WP-0022)

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).

Payload: activity_task_spawn (State Hub)

Produced by StateHubProgressSink. Consumers should treat detail as the authoritative task spec.

{
  "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

  • Using TaskExecutorWorkflow in activity-core for real work (disabled by default; ACTIVITY-WP-0023-T08).
  • Global ISSUE_SINK_TYPE=rest for all definitions (reintroduces Forgejo spam).
  • Treating task_spawn_log as task status authority.