| uid | e8d2a19f | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| type | decision | ||||||||||||||||||||||||||||||
| state | active | ||||||||||||||||||||||||||||||
| title | ADR-037 — Triggers as an Embedded Governance Pattern | ||||||||||||||||||||||||||||||
| description | Triggers are a cross-cutting, embedded declaration pattern — not a standalone capsule type. Capsules, playbook Groups/Steps, actions, and release-plan ship gates declare `triggers:` inline. Execution surface layers across L1 (action-wrapping + discipline), L2 (brain auto-fire), L3 (MCP service). Composes with ADR-035 Declared-Presence as its executor architecture. | ||||||||||||||||||||||||||||||
| owner | argus | ||||||||||||||||||||||||||||||
| created | 2026-04-20 | ||||||||||||||||||||||||||||||
| modified | 2026-04-20 | ||||||||||||||||||||||||||||||
| created_by | argus-a29 | ||||||||||||||||||||||||||||||
| decision_number | ADR-037 | ||||||||||||||||||||||||||||||
| proposer | Mike Maziarz | ||||||||||||||||||||||||||||||
| status | design | ||||||||||||||||||||||||||||||
| member_of | |||||||||||||||||||||||||||||||
| tags |
|
||||||||||||||||||||||||||||||
| file_ext | md | ||||||||||||||||||||||||||||||
| schema_version | 2 | ||||||||||||||||||||||||||||||
| extraction_scope | ship | ||||||||||||||||||||||||||||||
| relationships |
|
||||||||||||||||||||||||||||||
| subsystem_hub |
|
||||||||||||||||||||||||||||||
| capsule_version | 2.5 |
📍 Vault Path: tropo-subsystems → Tropo Governance → ADR-037 — Triggers as an Embedded Governance Pattern
🔗 This file — UID e8d2a19f · type decision · state active · status design
📥 Cited by (2):
- Workflow Enforcement Architecture — Distributed Stage Ownership —
a9b4c2d6(typedesign-brief, viarefs) - Typed Pipeline Architecture + Pipelines as Playbook Subtype —
d2e7b1f4(typedesign-brief, viarefs)
PROPOSED 2026-04-20 by Mike Maziarz. Authored by Argus A29 after an architectural exchange on how Tropo enforces validation at the moment of change rather than only at on-demand invocation.
ADR-035 Declared-Presence (a7c4e5b2) names five surfaces where unreachable references must fail-loud. It declares the rule. It does not declare the execution architecture — the surfaces where the rule actually fires.
The consequence today: ADR-035 is enforced only when a human or agent explicitly invokes python3 .tropo/scripts/tropo-validate.py or dispatches sa.cold-boot. Between invocations, drift accumulates silently. sa.research 023 documented this concretely — 5 dangling board: UIDs, 1 scope-enum violation, 3 curator-drift indexes, all gone undetected until research surfaced them.
The recurring problem: the vault generates write events faster than validation runs. Two-speed drift. The absence of a trigger architecture means we ship governance (capsules, ADRs, validators) but rely on human discipline to invoke them at the right moments.
Tropo's L1 thesis is that markdown governance is executable because agents read it at the point of action. That works when agents pause long enough to read and act. It fails when the action window is compressed — mid-session rapid edits, batch refactors, agent swarms — and agents can't individually pause for every write.
Triggers are a governed, cross-cutting declaration pattern embedded inline in multiple artifact types. They are not a standalone capsule type.
A trigger is a tuple declared as a YAML object:
triggers:
- name: <descriptive-name>
fires_on: <event-pattern>
action: <skill | playbook | action UID>
scope: <optional predicate>
severity: <optional — info | warn | fail>name— human-readable identifier; used in audit trails and error surfaces.fires_on— event pattern that fires the trigger. Governed vocabulary:governed-file-write | uid-reference-declared | playbook-milestone-fire | stage-transition | ship-gate-check | living-reference-read | boot | retire.action— UID of a skill, playbook, or action to execute. Must resolve in the registry (per ADR-035 Surface 2).scope— optional predicate narrowing which events count. E.g.,type: board-snapshotmeans the trigger fires only on board-snapshot writes.severity— optional gating:failhalts the operation;warnemits a warning but continues;infologs without user surface. Default:fail(fail-loud aligns with ADR-035).
Triggers are declared inline in five classes of governed artifact:
| Artifact type | Example embed point | Example trigger |
|---|---|---|
| Capsules | Top-level triggers: key on the capsule definition |
core.capsule declares on_write: validate-frontmatter — inherited by every governed file transitively |
| Playbook Groups / Steps | Step-level triggers: key |
agent-activation.playbook Step 0.1 declares on_read_fail: halt-fail-loud for tier-file reads |
| Actions | Action frontmatter triggers: |
create-project.action declares pre_invocation: verify-parent-project-resolves |
| Release plans | Ship gate frontmatter | Gate declarations carry on_gate_check: verify-all-gates-satisfied |
| Living-reference artifacts | Frontmatter triggers: |
pm-state + registry + cascade carry on_read: check-freshness-constraint |
Triggers inherit through the capsule inheritance chain. A trigger declared on core.capsule applies to every governed file because every file's capsule extends core. New subtype capsules inherit parent triggers and add their own.
The declaration is stable across layers. The executor changes.
Agents reading a capsule see declared triggers. Agents invoking actions get mandatory trigger execution baked into the action's step sequence.
Bulletproof within actions: constructor actions (create-project, create-task, create-decision, etc.) embed their capsule's triggers: as mandatory steps. Every ledger write through an action fires the write-time triggers.
Discipline-bound outside actions: raw Edit/Write tool calls by agents bypass the action wrapper. Triggers on affected capsules are still declared; agents SHOULD read them and invoke; in practice this is where drift enters.
L1 bridge (Claude Code harness hook): a .claude/settings.json post-Edit hook running python3 .tropo/scripts/tropo-validate.py catches raw-edit drift in the Claude Code harness specifically. Harness-level, not portable to Cursor/Cowork, but a real bulletproofing move.
The local brain watches for trigger conditions (filesystem events, ledger-index deltas, read/write boundaries) and fires the declared action automatically. Same declarations; different executor. Approaches bulletproof within the watched scope.
A "Tropo trigger service" exposes as an MCP tool. Any MCP-compatible harness wires in; triggers fire across harnesses. Cross-harness, cross-vault, federated.
Triggers declared on a parent capsule are additive on its children — not overridden. Example:
core.capsuledeclareson_write: validate-frontmattertask.capsule(extends core) declareson_write: verify-owner-not-equal-verifier
An entry of type: task fires both triggers on every write, in declaration order (parent first, child second).
Children CANNOT suppress parent triggers. Children CAN shadow a parent trigger with the same name: — doing so is explicit override and must be justified in the capsule's body prose.
The fires_on: field accepts only values from a locked vocabulary. v1 locks these values:
governed-file-write— fired after any write to a file governed by the triggering capsuleuid-reference-declared— fired when a field declared as a UID reference is writtenplaybook-milestone-fire— fired when a milestone event is about to be written to run.jsonlstage-transition— fired when an artifact'sstage:field changesship-gate-check— fired when a release-plan ship gate is being evaluatedliving-reference-read— fired when a living reference (pm-state, registry, cascade) is readboot— fired during agent activation (tier-chain, identity, context)retire— fired during agent retirement
Extensions require an amendment to this ADR. Agents attempting to declare triggers with an unlisted fires_on: value produce a validation error.
Three reasons the embedded pattern wins over a standalone type: trigger capsule:
1. Locality of governance. A capsule that declares a rule should also declare how the rule is enforced. Split governance-from-enforcement is the exact pattern that produced the current drift (ADR-035 declared rules with no enforcement architecture). Embedding closes the loop.
2. Inheritance composes naturally. Capsule inheritance already carries forward fields. Triggers as an embedded field inherit by the same mechanism — zero new inheritance machinery. Standalone triggers would need their own attachment/dispatch model.
3. Proximity surfaces the contract. A reader of core.capsule sees triggers: [validate-frontmatter, verify-uid-resolution] alongside the frontmatter schema. The contract is legible at the point of use. Standalone triggers in a separate vault entry are one lookup hop away — exactly the friction that drove ADR-035.
Mike's framing — "triggers can be embedded all over the place, like in playbook stages also. very simple and elegant." — captures the winning property: the pattern is cross-cutting, not a new primitive. It adds a field to existing types rather than adding a new type.
- Single source of truth for enforcement. Every capsule that declares a rule declares how the rule fires. No more "where is that rule actually enforced?"
- ADR-035 Declared-Presence becomes operational. Each of its 5 surfaces becomes one or more trigger instances. The rule ships with its executor.
- Playbook milestone gates become first-class. Current playbook milestone gates are prose-declared and agent-honored; with triggers, they're machine-readable and verifiable at fire time.
- Three-instrument verification can automate. The third instrument (sa.cold-boot) becomes a trigger that fires on
stage-transitiontodonefor any artifact withrequires_cold_boot: true. No manual dispatch. - Ship gates tighten. Each gate carries
on_gate_check:that verifies the gate's conditions — not just checks the gate exists.
- Every capsule update may need to evolve its triggers. Adding a new field to
core.capsulemay need an accompanying trigger. Governance discipline widens. - The
fires_on:vocabulary must stay small. Extensions require ADR amendments. Not every imaginable event becomes a first-class trigger. - Agents must read trigger declarations, not just capsule bodies. Adds one reading surface. Mitigated by the embedded placement (triggers sit with the capsule rules they enforce).
- Capsule definitions retain their existing shape. Triggers are an additive field.
- Existing actions, playbooks, and skills continue to work. Triggers embed into them as they're updated; no forced migration.
- UIDs are unaffected. Triggers reference actions by UID; changing trigger declarations doesn't change any artifact identity.
- L1 remains operational without any new runtime. Discipline + action-wrapping + Claude Code hook cover the write surface; L2 is incremental hardening.
Each of ADR-035's five surfaces becomes one or more trigger instances. This ADR is the executor architecture ADR-035 was missing.
| ADR-035 Surface | Trigger declaration site | fires_on: |
Action |
|---|---|---|---|
| 1. Boot tier chain | agent-activation.playbook Groups 0.1/0.2 |
boot |
halt-on-tier-unreachable |
| 2. Ledger UID refs | core.capsule |
uid-reference-declared |
verify-uid-resolution |
| 3. Playbook step refs | playbook.capsule |
playbook-milestone-fire |
check-playbook-references |
| 4. Governance chains | core.capsule |
governed-file-write |
check-governed-by-state |
| 5. Living references | registry.capsule, pm-state.capsule, cascade entries |
living-reference-read |
check-freshness-constraint |
ADR-035 declared the rules. ADR-037 declares how the rules fire. The two compose; neither is complete without the other.
-
Priority / ordering when multiple triggers match the same event. If
core.on_write+task.on_write+ a specific trigger at the action layer all match, in what order do they fire? Recommend: capsule inheritance order, then action-declared, then instance-declared (most general to most specific). Violations halt fail-loud per severity. -
Trigger failure semantics. If a trigger's action itself fails, what happens? Recommend: the operation that fired the trigger halts unless the trigger's severity is
warnorinfo. Failure of the trigger IS failure of the operation. -
Trigger visibility in the registry. Should
rebuild-registry.tsemit trigger records? Recommend: not as standalone registry entries, but as a derived field on the capsule/action/playbook records that declared them. Preserves the "embedded, not standalone" principle; makes triggers discoverable without fragmenting discovery. -
Fire-rate limiting. Can a single action fire an unbounded number of triggers in a single operation? Recommend: the governed vocabulary above bounds the event types; within those, no artificial rate limit at v1. Revisit if real runaway patterns emerge.
-
Triggers on
ideatestage ordraftstatus artifacts. Should triggers fire on artifacts atstage: ideate/status: draft? Recommend: yes by default, but triggers MAY declarefire_on_draft: falseto opt out. Drafts are still governed.
v1.3 does NOT build this primitive. The session-agent + how-to + tool capsules from v1.2 established the typed-primitive pattern; trigger is the natural next Pillar 2 capsule primitive, parallel in structural importance. Rushing it into v1.3 would compromise the three-instrument verification discipline v1.2 proved works.
Proposed roadmap:
- v1.3 — Immediate bridge. Claude Code
.claude/settings.jsonpost-Edit hook runningpython3 .tropo/scripts/tropo-validate.py. Catches raw-edit drift. ~15 minutes to wire. Folds into v1.3 Stream D (residual cleanup). - v1.4 — Pillar 2 primary deliverable.
triggers:field lands incore.capsule+ initial vocabulary instance (validate-frontmatter,verify-uid-resolution). Three-instrument verification pattern same as v1.2 Pillar 1. - v1.5 — ADR-035 surfaces instrumented. All 5 surfaces carry concrete trigger declarations. Registry/rebuild-vault extended to emit derived trigger records.
- v1.6+ — L2 brain execution. Local brain watches filesystem and ledger-delta events; fires declared triggers automatically. L3 MCP service follows.
- ADR-035 Declared-Presence (a7c4e5b2) — the rules this ADR provides the executors for
- ADR-032 Three-Layer Boot (e6c3f410) — triggers fire at boot tier-chain reads
- ADR-036 Pipeline Bucket Taxonomy (f6a8c421) — stage-transition triggers fire on pipeline moves
- session-agent.capsule (b4e2a718), how-to.capsule (a7c3f489), tool.capsule (d5e1b4a3) — the Pillar 1 typed primitives. Trigger is the Pillar 2 analog.
- release-plan.capsule (a3f1e7b2) — ship gates carry
on_gate_check:triggers - registry.jsonl Design Spec (e2f7d195) — registry entries derive a
declared_triggers:field from their source capsule
Proposed by Mike Maziarz on 2026-04-20 during A29 session following an architectural exchange on drift patterns and how Tropo enforces validation at the moment of write. Mike's framing: "triggers can be embedded all over the place, like in playbook stages also. very simple and elegant." — that framing locked the embedded-not-standalone decision.
Argus A29 authored the ADR. Awaits Mike acceptance + A30 review. On acceptance, transitions to stage: done, status: accepted. Implementation deferred to v1.4.
ADR-037 | Triggers as an Embedded Governance Pattern | Proposed by Mike, authored by Argus A29 | 2026-04-20 "Declare the rule where the rule lives. Fire the rule where the rule applies. Same declaration; layered executors."