From d9592d2483553e85be7f3f0caaea4191455785e3 Mon Sep 17 00:00:00 2001 From: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com> Date: Sun, 2 Aug 2026 02:56:16 +0000 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20implement=20issue=20#1405=20?= =?UTF-8?q?=E2=80=94=20[Phase=202]=20Add=20the=20"How=20to=20add=20an=20ag?= =?UTF-8?q?entic=20role"=20runbook=20and=20a=20PR-template=20interaction-c?= =?UTF-8?q?ontract=20section?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/pull_request_template.md | 41 ++++++++++ docs/adding-an-agentic-role.md | 130 ++++++++++++++++++++++++++++++ docs/agentic-interaction-model.md | 5 ++ 3 files changed, 176 insertions(+) create mode 100644 .github/pull_request_template.md create mode 100644 docs/adding-an-agentic-role.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..0c32c7d9d --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,41 @@ + + +## Summary + + + +## Interaction contract + + + +- [ ] N/A — this PR does not add or change an agentic role. + +If this PR **does** add or change an agentic role, confirm: + +- [ ] The role's trigger class (1 / 2 / 3) is chosen per the standard, and its row in the + §4 classification table of `docs/agentic-interaction-model.md` is added or updated to + match the workflow's `on:` block. +- [ ] The machine-readable interaction contract is added or updated + (`personas//interaction.yml` or `interaction-contracts/.yml`), with + `triggers`, `emits`, `idempotency_key`, `concurrency_lane`, `stop_markers`, and `budget`. +- [ ] Event-first: the role subscribes to the event that represents the work, or crosses the + `GITHUB_TOKEN` boundary only via a sanctioned bridge (PAT `repository_dispatch` or a + stop-condition-gated backstop timer). +- [ ] If the role uses a timer, its timer contract is declared (`role`, `justification`, + `stop_condition`, `event_fast_path`) and it is idempotent, skips human-gate markers, + and obeys the off-peak schedule standard. + +## Checklist + +- [ ] `shellcheck scripts/*.sh` passes for any changed shell scripts. +- [ ] Docs updated where behavior or standards changed. diff --git a/docs/adding-an-agentic-role.md b/docs/adding-an-agentic-role.md new file mode 100644 index 000000000..2c6b1b073 --- /dev/null +++ b/docs/adding-an-agentic-role.md @@ -0,0 +1,130 @@ +# How to add an agentic role + +**Status:** runbook (repo-local) · **Scope:** `petry-projects/.github-private` +· **Companion to:** [`docs/agentic-interaction-model.md`](./agentic-interaction-model.md) (the normative standard) + +This is the step-by-step for introducing a new agentic role — a workflow that reacts to +events or runs on a cadence and takes automated action. The [interaction-model +standard](./agentic-interaction-model.md) says *what* a conforming role looks like; this +runbook says *how* to declare one so it passes CI on the first try. Every step cites the +standard section that governs it — read that section for the rules, and follow this page +for the sequence. + +Do not treat any step as optional: a role whose `on:` block, contract, and classification +row disagree fails CI (see [Enforcement](#enforcement) below). + +--- + +## Step 1 — Classify the role: pick the trigger class + +Decide, from the **origin of the work**, which of the three trigger classes the role is +(standard [§2](./agentic-interaction-model.md#2-the-three-trigger-classes), +[§3](./agentic-interaction-model.md#3-class-discriminator-the-parse-contract)): + +- **Class 1 — event-driven reaction.** A GitHub webhook already represents the work (a PR + opens, a review is submitted, a check run completes). Subscribe to the narrowest event(s) + that carry the signal. **No `schedule`** — the event-first principle + ([§1](./agentic-interaction-model.md#1-the-event-first-principle)) means a cron here is + either a disguised Class 2 or a leak. +- **Class 2 — backstop / reconciliation timer.** The work is fundamentally event-driven, + but the event path can *miss* (dropped webhook, race, a state that became actionable + without a fresh event). Pair the event fast-path with a **low-frequency reconciliation + timer** that declares a `timer_role`. +- **Class 3 — scheduled-origin work.** The cadence itself *is* the requirement (a weekly + report, a daily health scan, a periodic canary). There is no upstream webhook that + represents "produce this week's report." + +If you are reaching for a `schedule` on Class 1 work, stop: prefer the event +([§1](./agentic-interaction-model.md#1-the-event-first-principle)). A timer is legitimate +only as a Class 2 backstop under an event path, or as a Class 3 scheduled origin. + +**Then add a classification row.** Add exactly one row for the new workflow to the §4 table +in the standard +([Classification of current agentic workflows](./agentic-interaction-model.md#4-classification-of-current-agentic-workflows)), +in the fixed column order `Workflow (path) | Class | timer_role | Justification`. The +completeness check is bidirectional — a workflow with no row (and not on the exclusion list) +fails CI. If the role is CI infrastructure or a thin caller stub rather than an agentic role, +add it to the §4 **exclusion list** instead, with a one-line reason. + +## Step 2 — Author the interaction contract (the Story-1 shape) + +Declare the role's interaction pattern as a **machine-readable contract** in the shape fixed +by standard [§8.1](./agentic-interaction-model.md#81-fields) — do not restate the pattern in +prose scattered across workflow comments. The contract lives in one of two standalone +repo-local locations (the [§8.2](./agentic-interaction-model.md#82-file-location--decision-standalone-repo-local-contract-not-personayml-yet) +decision — option (b), so there is **no** cross-repo `persona.schema.json` dependency): + +- `personas//interaction.yml` — for a role backed by a persona manifest. +- `interaction-contracts/.yml` — for a non-persona runtime whose triggers live only in + its workflow(s) (e.g. `dev-lead`, `pr-review`, `ci-failure-analyst`). + +Copy the shape from an existing contract of the same kind +([`personas/dev-lead/interaction.yml`](../personas/dev-lead/interaction.yml) for the persona +lens, [`interaction-contracts/dev-lead.yml`](../interaction-contracts/dev-lead.yml) for the +runtime lens). Fill in every required field: + +- `interaction.triggers.events` — mirror the workflow's `on:` webhook subscriptions verbatim. +- `interaction.triggers.timers` — empty for a pure Class 1 role; one entry per `schedule.cron` + otherwise (see Step 4). +- `emits` — the events / markers this role produces. This is what proves the role never + subscribes to its own output (#860 rule 1, + [§7](./agentic-interaction-model.md#7-the-860-normative-rules)): an emit must not name an + event class the role also subscribes to. +- `idempotency_key`, `concurrency_lane`, `stop_markers`, and `budget` — the role obeys the + existing `pr-automation-budget` ceiling ([§9](./agentic-interaction-model.md#9-the-cost--run-count-bound)); + do **not** invent a new numeric cap. + +## Step 3 — Choose the event path vs. a sanctioned bridge + +A workflow step running as the default `GITHUB_TOKEN` **cannot emit webhooks that trigger +other workflows** — GitHub suppresses recursive triggering, so an event a Class 1 role is +waiting for will silently never arrive if its upstream producer runs as `GITHUB_TOKEN` +(standard [§5](./agentic-interaction-model.md#5-the-github_token-event-boundary-rule-and-sanctioned-bridges)). + +- **Prefer the direct event.** If the work is represented by a real webhook that a real + actor produces, subscribe to it directly — no bridge needed. +- If you must wake a downstream role across the `GITHUB_TOKEN` boundary, cross it **only** + via one of the two sanctioned bridges: + - **Bridge A — PAT-backed `repository_dispatch`** (event-preserving). Fire the dispatch (or + apply the triggering label) with the role's declared + `runtime.identity.credential` PAT — never a shared token borrowed to punch through the + boundary. The downstream Class 1 role wakes on its normal event path, scoped to the + payload. + - **Bridge B — a stop-condition-gated backstop timer.** Where a PAT bridge is not warranted, + a Class 2 backstop timer may reconcile the missed event on a cadence — but it must satisfy + the full timer contract in Step 4. + +Never try to cross the boundary by having `GITHUB_TOKEN` emit the triggering event and hoping +it fires — it will not, and the failure is silent. + +## Step 4 — Declare the timer contract (only if the role uses a timer) + +Skip this step for a pure Class 1 role. If the role has any `schedule.cron` (Class 2 or +Class 3), each timer is bound by the timer contract +([§6](./agentic-interaction-model.md#6-the-timer-contract)) and must be declared in the +contract's `interaction.triggers.timers`: + +- **`role`** — one of `backstop`, `safety-net`, `self-heal` + ([§6.1](./agentic-interaction-model.md#61-the-timer_role-taxonomy)). A **Class 3** + report/canary carries **no** `timer_role` (its cadence is the origin); a **Class 2** timer + **must** declare one. +- **`justification`** — why this is a timer and not an event. +- **`stop_condition`** — the timer checks it *before* acting and re-dispatches only work that + is still open and not human-gated + ([§6.2](./agentic-interaction-model.md#62-requirements-every-timer-must-meet)). +- **`event_fast_path`** — the Class 1 event this timer backstops; `null` marks the + [§6.3](./agentic-interaction-model.md#63-the-correct-example-vs-the-leak) leak (a schedule + as the *only* path to event-driven work). +- The timer must be **idempotent** (extra ticks harmless, missed ticks self-heal), must + **skip human-gate markers** (`needs-human-review`, `dev-lead:needs-human`, …), and must + **never re-arm a runaway** — its reset path is human-gated. Also obey the off-peak + scheduling standard (non-zero, staggered minute; AGENTS.md "Scheduled workflows"). + +## Enforcement + +The classification table and the per-role contracts are **CI-verified, not review-verified**: +the [`validate-interaction-model`](./agentic-interaction-model.md#10-how-this-document-is-enforced) +check (Story 4 / [#1406](https://github.com/petry-projects/.github-private/issues/1406)) fails +any PR whose `on:` block, §4 row, and contract disagree — so a non-conforming role cannot +merge. That validator is the single source of truth for the exact rules; this runbook does not +restate them. diff --git a/docs/agentic-interaction-model.md b/docs/agentic-interaction-model.md index 5aff4235b..d1d7ed1b1 100644 --- a/docs/agentic-interaction-model.md +++ b/docs/agentic-interaction-model.md @@ -9,6 +9,11 @@ pattern is explicit, reviewable, and enforceable rather than tribal knowledge. I normative source later phases reference — a contract, a validator, or a leak fix cites a rule here rather than re-deriving it. +> **Adding a new role?** Follow the step-by-step runbook +> [How to add an agentic role](./adding-an-agentic-role.md) — it walks the trigger-class +> choice, the contract shape, the event-vs-bridge decision, and the timer contract, each +> pointing back to the governing section here. + It is written **repo-local first** but shaped for promotion into an org-wide `standards/agent-standards.md` in [`petry-projects/.github`](https://github.com/petry-projects/.github): the principles and rules are org-general; only the classification table (§4) is From a708afe57dfbdaa5758d8f66eaef01fe649bf0a2 Mon Sep 17 00:00:00 2001 From: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com> Date: Sun, 2 Aug 2026 03:00:39 +0000 Subject: [PATCH 2/6] fix(reviews): address review comments [skip ci-relay] --- docs/adding-an-agentic-role.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/adding-an-agentic-role.md b/docs/adding-an-agentic-role.md index 2c6b1b073..820c434ec 100644 --- a/docs/adding-an-agentic-role.md +++ b/docs/adding-an-agentic-role.md @@ -51,7 +51,7 @@ add it to the §4 **exclusion list** instead, with a one-line reason. Declare the role's interaction pattern as a **machine-readable contract** in the shape fixed by standard [§8.1](./agentic-interaction-model.md#81-fields) — do not restate the pattern in prose scattered across workflow comments. The contract lives in one of two standalone -repo-local locations (the [§8.2](./agentic-interaction-model.md#82-file-location--decision-standalone-repo-local-contract-not-personayml-yet) +repo-local locations (the [§8.2](./agentic-interaction-model.md#82-file-location---decision-standalone-repo-local-contract-not-personayml-yet) decision — option (b), so there is **no** cross-repo `persona.schema.json` dependency): - `personas//interaction.yml` — for a role backed by a persona manifest. @@ -59,7 +59,7 @@ decision — option (b), so there is **no** cross-repo `persona.schema.json` dep its workflow(s) (e.g. `dev-lead`, `pr-review`, `ci-failure-analyst`). Copy the shape from an existing contract of the same kind -([`personas/dev-lead/interaction.yml`](../personas/dev-lead/interaction.yml) for the persona +([`personas//interaction.yml`](../personas//interaction.yml) for the persona lens, [`interaction-contracts/dev-lead.yml`](../interaction-contracts/dev-lead.yml) for the runtime lens). Fill in every required field: From 3885c25726af47a8d6f0182c0fcc2f3e3a430397 Mon Sep 17 00:00:00 2001 From: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com> Date: Sun, 2 Aug 2026 03:04:38 +0000 Subject: [PATCH 3/6] chore: dev-lead update (review-changes) [skip ci-relay] --- docs/adding-an-agentic-role.md | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/docs/adding-an-agentic-role.md b/docs/adding-an-agentic-role.md index 820c434ec..6678c6859 100644 --- a/docs/adding-an-agentic-role.md +++ b/docs/adding-an-agentic-role.md @@ -10,8 +10,9 @@ runbook says *how* to declare one so it passes CI on the first try. Every step c standard section that governs it — read that section for the rules, and follow this page for the sequence. -Do not treat any step as optional: a role whose `on:` block, contract, and classification -row disagree fails CI (see [Enforcement](#enforcement) below). +Do not treat any step as optional: the contract is CI-validated today (schema, timers, +self-trigger violations), and the full `on:`-block / §4-row / contract cross-check will be +enforced once Story 4 / #1406 lands (see [Enforcement](#enforcement) below). --- @@ -56,10 +57,10 @@ decision — option (b), so there is **no** cross-repo `persona.schema.json` dep - `personas//interaction.yml` — for a role backed by a persona manifest. - `interaction-contracts/.yml` — for a non-persona runtime whose triggers live only in - its workflow(s) (e.g. `dev-lead`, `pr-review`, `ci-failure-analyst`). + its workflow(s) (e.g. `pr-review`, `ci-failure-analyst`). Copy the shape from an existing contract of the same kind -([`personas//interaction.yml`](../personas//interaction.yml) for the persona +([`personas/dev-lead/interaction.yml`](../personas/dev-lead/interaction.yml) for the persona lens, [`interaction-contracts/dev-lead.yml`](../interaction-contracts/dev-lead.yml) for the runtime lens). Fill in every required field: @@ -122,9 +123,16 @@ contract's `interaction.triggers.timers`: ## Enforcement -The classification table and the per-role contracts are **CI-verified, not review-verified**: -the [`validate-interaction-model`](./agentic-interaction-model.md#10-how-this-document-is-enforced) -check (Story 4 / [#1406](https://github.com/petry-projects/.github-private/issues/1406)) fails -any PR whose `on:` block, §4 row, and contract disagree — so a non-conforming role cannot -merge. That validator is the single source of truth for the exact rules; this runbook does not -restate them. +The per-role contracts are **CI-validated today** by `validate-interaction-contracts` (the +`Lint` workflow). It checks: schema compliance, workflow file existence, timer structure +(`role`, `justification`, `stop_condition`, `event_fast_path`), self-trigger violations +([§7](./agentic-interaction-model.md#7-the-860-normative-rules)), and +budget/stop-marker consistency ([§9](./agentic-interaction-model.md#9-the-cost--run-count-bound)). +A contract that fails these checks blocks the PR. + +The **full cross-check** — verifying that a workflow's `on:` block, its §4 classification +row, and its contract all agree — is **planned** for Story 4 / +[#1406](https://github.com/petry-projects/.github-private/issues/1406) +([`validate-interaction-model`](./agentic-interaction-model.md#10-how-this-document-is-enforced)). +Until that check lands, keep the three artefacts in sync by convention; a mismatch will not +block merge today but will fail CI once Story 4 ships. From edd4b49e59a5fd67b430720f1731925222cda38d Mon Sep 17 00:00:00 2001 From: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com> Date: Sun, 2 Aug 2026 04:21:11 +0000 Subject: [PATCH 4/6] chore: dev-lead update (review-changes) [skip ci-relay] --- docs/adding-an-agentic-role.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/docs/adding-an-agentic-role.md b/docs/adding-an-agentic-role.md index 6678c6859..835fac4ab 100644 --- a/docs/adding-an-agentic-role.md +++ b/docs/adding-an-agentic-role.md @@ -67,10 +67,20 @@ runtime lens). Fill in every required field: - `interaction.triggers.events` — mirror the workflow's `on:` webhook subscriptions verbatim. - `interaction.triggers.timers` — empty for a pure Class 1 role; one entry per `schedule.cron` otherwise (see Step 4). -- `emits` — the events / markers this role produces. This is what proves the role never - subscribes to its own output (#860 rule 1, - [§7](./agentic-interaction-model.md#7-the-860-normative-rules)): an emit must not name an - event class the role also subscribes to. +- `emits` — the events / markers this role produces. An emit that names an event class the + role also subscribes to is allowed **only** when accompanied by a matching + `self_trigger_guards` entry — without it, the validator rejects the contract (#860 rule 1, + [§7](./agentic-interaction-model.md#7-the-860-normative-rules)). Each `self_trigger_guards` + entry carries three required fields: + - `emit` — the colliding event name. + - `guard` — one sentence describing the in-code stop-condition-gated mechanism that + prevents the loop. + - `location` — the file (and optional line range) where the guard lives. + + `interaction-contracts/dev-lead.yml` provides four worked examples: three + `dispatch:dev-lead-*-retry` guards (the retry timer fires only when its stop-condition + holds — PR still rate-limited and limit now cleared) and a `commit` guard (the intent + script drops `pull_request:synchronize` events from the bot's own commits). - `idempotency_key`, `concurrency_lane`, `stop_markers`, and `budget` — the role obeys the existing `pr-automation-budget` ceiling ([§9](./agentic-interaction-model.md#9-the-cost--run-count-bound)); do **not** invent a new numeric cap. @@ -125,7 +135,7 @@ contract's `interaction.triggers.timers`: The per-role contracts are **CI-validated today** by `validate-interaction-contracts` (the `Lint` workflow). It checks: schema compliance, workflow file existence, timer structure -(`role`, `justification`, `stop_condition`, `event_fast_path`), self-trigger violations +(`role`, `justification`, `stop_condition`, `event_fast_path`), unguarded self-trigger violations ([§7](./agentic-interaction-model.md#7-the-860-normative-rules)), and budget/stop-marker consistency ([§9](./agentic-interaction-model.md#9-the-cost--run-count-bound)). A contract that fails these checks blocks the PR. From d1063f6e7e35e620515526e84a48003d7f442afc Mon Sep 17 00:00:00 2001 From: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com> Date: Sun, 2 Aug 2026 05:32:46 +0000 Subject: [PATCH 5/6] chore: dev-lead update (review-changes) [skip ci-relay] --- docs/adding-an-agentic-role.md | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/docs/adding-an-agentic-role.md b/docs/adding-an-agentic-role.md index 835fac4ab..e80a437f8 100644 --- a/docs/adding-an-agentic-role.md +++ b/docs/adding-an-agentic-role.md @@ -51,18 +51,24 @@ add it to the §4 **exclusion list** instead, with a one-line reason. Declare the role's interaction pattern as a **machine-readable contract** in the shape fixed by standard [§8.1](./agentic-interaction-model.md#81-fields) — do not restate the pattern in -prose scattered across workflow comments. The contract lives in one of two standalone -repo-local locations (the [§8.2](./agentic-interaction-model.md#82-file-location---decision-standalone-repo-local-contract-not-personayml-yet) -decision — option (b), so there is **no** cross-repo `persona.schema.json` dependency): - -- `personas//interaction.yml` — for a role backed by a persona manifest. -- `interaction-contracts/.yml` — for a non-persona runtime whose triggers live only in - its workflow(s) (e.g. `pr-review`, `ci-failure-analyst`). - -Copy the shape from an existing contract of the same kind -([`personas/dev-lead/interaction.yml`](../personas/dev-lead/interaction.yml) for the persona -lens, [`interaction-contracts/dev-lead.yml`](../interaction-contracts/dev-lead.yml) for the -runtime lens). Fill in every required field: +prose scattered across workflow comments. Create the file(s) for the lens(es) your role +requires (the [§8.2](./agentic-interaction-model.md#82-file-location---decision-standalone-repo-local-contract-not-personayml-yet) +decision — option (b), so there is **no** cross-repo `persona.schema.json` dependency). +These are independent requirements, not alternatives — a role that qualifies for both +needs both: + +- **Role lens** → `personas//interaction.yml` — required when the role is backed by a + persona manifest. Copy the shape from + [`personas/dev-lead/interaction.yml`](../personas/dev-lead/interaction.yml). +- **Runtime lens** → `interaction-contracts/.yml` — required for any deployed runtime + (a role whose triggers live in a workflow file), whether or not a persona manifest also + exists (e.g. `pr-review`, `ci-failure-analyst`). Copy the shape from + [`interaction-contracts/dev-lead.yml`](../interaction-contracts/dev-lead.yml). + +For example, `dev-lead` has both a persona manifest and a deployed workflow, so it carries +both `personas/dev-lead/interaction.yml` (role lens) and +`interaction-contracts/dev-lead.yml` (runtime lens). Fill in every required field in +whichever file(s) you create: - `interaction.triggers.events` — mirror the workflow's `on:` webhook subscriptions verbatim. - `interaction.triggers.timers` — empty for a pure Class 1 role; one entry per `schedule.cron` From a7b021719ea9a2d9066939bc1cf6129608facdda Mon Sep 17 00:00:00 2001 From: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com> Date: Sun, 2 Aug 2026 05:43:59 +0000 Subject: [PATCH 6/6] fix(reviews): address review comments [skip ci-relay] --- .github/pull_request_template.md | 12 ++++---- docs/adding-an-agentic-role.md | 50 ++++++++++++++++++-------------- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 0c32c7d9d..a4b60c108 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -25,15 +25,17 @@ If this PR **does** add or change an agentic role, confirm: - [ ] The role's trigger class (1 / 2 / 3) is chosen per the standard, and its row in the §4 classification table of `docs/agentic-interaction-model.md` is added or updated to match the workflow's `on:` block. -- [ ] The machine-readable interaction contract is added or updated - (`personas//interaction.yml` or `interaction-contracts/.yml`), with +- [ ] The machine-readable interaction contract is added or updated for + every applicable lens (`personas//interaction.yml` and/or + `interaction-contracts/.yml`); include both when both lenses apply, with `triggers`, `emits`, `idempotency_key`, `concurrency_lane`, `stop_markers`, and `budget`. - [ ] Event-first: the role subscribes to the event that represents the work, or crosses the `GITHUB_TOKEN` boundary only via a sanctioned bridge (PAT `repository_dispatch` or a stop-condition-gated backstop timer). -- [ ] If the role uses a timer, its timer contract is declared (`role`, `justification`, - `stop_condition`, `event_fast_path`) and it is idempotent, skips human-gate markers, - and obeys the off-peak schedule standard. +- [ ] If the role uses a timer (Class 2 or Class 3), its timer contract is declared + (`justification`, `stop_condition`) and it is idempotent, skips human-gate markers, + and obeys the off-peak schedule standard; for Class 2, also declares `role` and a + non-null `event_fast_path`. ## Checklist diff --git a/docs/adding-an-agentic-role.md b/docs/adding-an-agentic-role.md index e80a437f8..900dd0974 100644 --- a/docs/adding-an-agentic-role.md +++ b/docs/adding-an-agentic-role.md @@ -43,9 +43,11 @@ only as a Class 2 backstop under an event path, or as a Class 3 scheduled origin in the standard ([Classification of current agentic workflows](./agentic-interaction-model.md#4-classification-of-current-agentic-workflows)), in the fixed column order `Workflow (path) | Class | timer_role | Justification`. The -completeness check is bidirectional — a workflow with no row (and not on the exclusion list) -fails CI. If the role is CI infrastructure or a thin caller stub rather than an agentic role, -add it to the §4 **exclusion list** instead, with a one-line reason. +bidirectional completeness check (a workflow absent from both this table and the exclusion +list will fail CI) is planned for Story 4 / `validate-interaction-model` (#1406) and is not +yet enforced; keep the table in sync by convention until that check lands. If the role is CI +infrastructure or a thin caller stub rather than an agentic role, add it to the §4 +**exclusion list** instead, with a one-line reason. ## Step 2 — Author the interaction contract (the Story-1 shape) @@ -93,26 +95,29 @@ whichever file(s) you create: ## Step 3 — Choose the event path vs. a sanctioned bridge -A workflow step running as the default `GITHUB_TOKEN` **cannot emit webhooks that trigger -other workflows** — GitHub suppresses recursive triggering, so an event a Class 1 role is -waiting for will silently never arrive if its upstream producer runs as `GITHUB_TOKEN` +A workflow step running as the default `GITHUB_TOKEN` **cannot emit most webhooks that +trigger other workflows** — GitHub suppresses recursive triggering for push, label, comment, +and similar events, so an event a Class 1 role is waiting for will silently never arrive if +its upstream producer runs as `GITHUB_TOKEN` (standard [§5](./agentic-interaction-model.md#5-the-github_token-event-boundary-rule-and-sanctioned-bridges)). +`repository_dispatch` and `workflow_dispatch` are permitted exceptions — GitHub allows +`GITHUB_TOKEN` to fire these as explicit, intentional dispatch calls. - **Prefer the direct event.** If the work is represented by a real webhook that a real actor produces, subscribe to it directly — no bridge needed. -- If you must wake a downstream role across the `GITHUB_TOKEN` boundary, cross it **only** - via one of the two sanctioned bridges: - - **Bridge A — PAT-backed `repository_dispatch`** (event-preserving). Fire the dispatch (or - apply the triggering label) with the role's declared - `runtime.identity.credential` PAT — never a shared token borrowed to punch through the - boundary. The downstream Class 1 role wakes on its normal event path, scoped to the - payload. - - **Bridge B — a stop-condition-gated backstop timer.** Where a PAT bridge is not warranted, - a Class 2 backstop timer may reconcile the missed event on a cadence — but it must satisfy - the full timer contract in Step 4. - -Never try to cross the boundary by having `GITHUB_TOKEN` emit the triggering event and hoping -it fires — it will not, and the failure is silent. +- If you must wake a downstream role, cross the boundary via one of the two sanctioned bridges: + - **Bridge A — `repository_dispatch` with the role's declared credential** (event-preserving). + Fire the dispatch (or apply the triggering label) with the role's declared + `runtime.identity.credential` PAT — never a shared token. This repo's identity policy + (AGENTS.md "Agent identity & credential secrets") requires using the named credential even + though `GITHUB_TOKEN` is technically permitted to fire `repository_dispatch`. The downstream + Class 1 role wakes on its normal event path, scoped to the payload. + - **Bridge B — a stop-condition-gated backstop timer.** Where a dispatch bridge is not + warranted, a Class 2 backstop timer may reconcile the missed event on a cadence — but + it must satisfy the full timer contract in Step 4. + +Never try to produce a suppressed event (push, label, comment) as `GITHUB_TOKEN` and expect +it to trigger a downstream workflow — the failure is silent. ## Step 4 — Declare the timer contract (only if the role uses a timer) @@ -129,9 +134,10 @@ contract's `interaction.triggers.timers`: - **`stop_condition`** — the timer checks it *before* acting and re-dispatches only work that is still open and not human-gated ([§6.2](./agentic-interaction-model.md#62-requirements-every-timer-must-meet)). -- **`event_fast_path`** — the Class 1 event this timer backstops; `null` marks the - [§6.3](./agentic-interaction-model.md#63-the-correct-example-vs-the-leak) leak (a schedule - as the *only* path to event-driven work). +- **`event_fast_path`** — **Class 2 only:** the Class 1 event this timer backstops; `null` + marks the [§6.3](./agentic-interaction-model.md#63-the-correct-example-vs-the-leak) leak + (a schedule as the *only* path to fundamentally event-driven work). For **Class 3**, omit + or set `null` — no fast path exists and that is not a leak. - The timer must be **idempotent** (extra ticks harmless, missed ticks self-heal), must **skip human-gate markers** (`needs-human-review`, `dev-lead:needs-human`, …), and must **never re-arm a runaway** — its reset path is human-gated. Also obey the off-peak