Files
boshu2__agentops/docs/patterns/completion-notifications.md
Bo 6072dceac5 docs(substrate): reconcile doctrine — skill-dispatch + drop Gas City reference (ag-88y5 #docs-skills-dispatch-reframe) (#696)
## What
Two bounded doctrine-reconciliation slices (one coherent arc):

**ag-88y5** — the out-of-session dispatch unit is the `/rpi`·`/evolve`
**skill**, not the `ao rpi`/`ao evolve` CLI. Per the 3.0
skills-are-the-runtime thesis (using-ntm #693 already frames it this
way), re-narrate the **live** doctrine surfaces so the substrate spawns
an agent that *runs the /rpi skill* over a bead:
- docs/3.0.md, docs/architecture/canonical-loop-model.md,
docs/runbooks/nightly-evolution.md
- skills/using-agentops, skills/agent-native,
skills/domain/references/factory.md

Historical surfaces (MIGRATION-3.0, ADR-0009, release notes) +
CLI-reference left as-is; the actual `ao rpi`/`ao evolve` CLI removal is
the **ag-iowf** epic (live automation callers — overnight-evolve,
release-smoke — must migrate first).

**ag-ogp5** — drop the 4 docs still asserting Gas City as THE reference
substrate → NTM+MCP+managed-agents:
- docs/ARCHITECTURE.md, docs/SCHEMAS.md,
docs/contracts/agents-write-surfaces.md,
docs/patterns/completion-notifications.md (Pattern C)

The ~37 historical/illustrative GC refs are intentionally retained (per
ag-ogp5 scope).

## Verification
regen-all --check, embedded-sync, heal --strict, registry-drift,
codex-rpi-contract, codex-artifacts --scope head — all green.

Closes-scenario: ag-88y5#docs-skills-dispatch-reframe
Bounded-context: BC4-Factory
Evidence: docs/3.0.md
2026-06-03 12:11:23 +00:00

5.1 KiB

Pattern: completion notifications without a webhook server

Status: Active Captured from: epic soc-yjzp (Anthropic Managed Agents parity, May 2026) Applies to: off-API users who want "outcome complete → notify external system" behavior without standing up a webhook server.

Anthropic's 2026-05-06 Managed Agents launch shipped webhooks as a first-class primitive. Off-API users running AgentOps locally don't need a webhook server. Three patterns cover the common cases using infrastructure you already have.

Pattern A — GitHub Actions issue creation

Use when: the work is being run inside a GitHub Actions workflow (nightly dream cycle, scheduled out-of-session run, release proof harness).

The repo's .github/workflows/nightly.yml already does this for the dream-cycle proof job — when the run completes, a step opens or updates a GitHub issue with the result summary and links to the run logs. Same shape works for any outcome: a job step that calls gh issue create or gh issue comment with the structured result.

Skeleton:

- name: Notify on completion
  if: always()  # fires on success and failure
  run: |
    gh issue create \
      --title "Outcome: ${{ github.workflow }} #${{ github.run_number }}" \
      --body  "Status: ${{ job.status }}
Result: $(cat .agents/rpi/last-result.json | jq -c)
Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
      --label  outcome
  env:
    GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Trade-off: GitHub-only, asynchronous (you watch the issue), but you already have the auth and infrastructure.

Pattern B — git post-commit hook → curl

Use when: the work writes a tracked artifact (a learnings file, a .agents/rpi/execution-packet.json, a release note) and you want to fan out to Slack / Discord / Mattermost / a custom endpoint when that artifact lands on a specific branch.

Skeleton (.git/hooks/post-commit, marked executable):

#!/usr/bin/env bash
set -euo pipefail

branch=$(git rev-parse --abbrev-ref HEAD)
[[ "$branch" == "main" || "$branch" == "crank/"* ]] || exit 0

# Only fire when the watched artifact actually changed in this commit.
git diff-tree --no-commit-id --name-only -r HEAD | grep -q '^\.agents/rpi/' || exit 0

payload=$(jq -nc \
  --arg branch "$branch" \
  --arg sha    "$(git rev-parse --short HEAD)" \
  --arg subj   "$(git log -1 --format=%s)" \
  '{text: "✅ \($branch) @ \($sha): \($subj)"}')

curl -sS -X POST -H 'Content-Type: application/json' \
  -d "$payload" "$SLACK_WEBHOOK_URL" >/dev/null || true

Trade-off: scoped to the local clone (only fires on the developer / runner that ran the commit), but reaches any HTTP endpoint and runs synchronously on commit so debugging is easy.

Pattern C — substrate event tailing

Use when: the work runs unattended out of session on an orchestration substrate (the loop dispatched on an NTM swarm, MCP, or managed-agents — a scheduled evolve run, a recurring ao goals measure job, a wiki-forge job) and you want a downstream consumer to react to specific job events. AgentOps ships no daemon of its own; out-of-session execution is delegated to the substrate (see ADR-0009), and the substrate is where the event stream lives.

The substrate writes one JSON line per job event to its event log. A consumer process (systemd unit, tmux pane, container sidecar) tails that log and forwards the events it cares about.

Skeleton:

# Consumer running alongside the out-of-session substrate.
# Point the tail at your substrate's event log (path varies by deployment).
tail -F "$SUBSTRATE_EVENT_LOG" \
  | jq -c --unbuffered 'select(.event == "job.completed" and .job.kind == "dream.run")' \
  | while read -r line; do
      url=$(echo "$line" | jq -r '.job.report_url // empty')
      curl -sS -X POST -H 'Content-Type: application/json' \
        -d "{\"text\": \"Dream cycle complete: $url\"}" \
        "$SLACK_WEBHOOK_URL" >/dev/null || true
    done

Trade-off: needs a long-running consumer process and the substrate to be running, but is the closest analog to a real webhook stream — every job event is visible, including failures and partial runs, with no GitHub or commit dependency.

Decision matrix

Situation Pattern
Work runs in GitHub Actions; you watch issues A — GitHub Actions issue creation
Work writes a tracked artifact; you have a chat webhook URL B — git post-commit → curl
Work runs unattended on the substrate (NTM / MCP / managed-agents); you want real-time events C — substrate event tailing
You think you need a webhook server You probably don't — start with A or B; reach for C only if you genuinely need event streaming

What this is NOT

  • Not a managed cloud service. Nothing leaves your infrastructure.
  • Not a replacement for the Anthropic Managed Agents webhook primitive — if you're on the Claude Platform, use that. This pattern is for the off-API case where standing up a webhook server is more infrastructure than the problem warrants.
  • Not strongly-ordered. None of the three patterns guarantees delivery; if you need that, you need a queue, not a webhook.