From c5c729315e2f4b7c5705fff8eb2b11eadbc16171 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 5 Jun 2026 14:24:32 -0700 Subject: [PATCH] docs(solutions): per-entity execution-principal override blast-radius checklist + column-agent vocabulary --- CONCEPTS.md | 8 +++ ...ecution-principal-override-blast-radius.md | 56 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 docs/solutions/architecture-patterns/per-entity-execution-principal-override-blast-radius.md diff --git a/CONCEPTS.md b/CONCEPTS.md index 670f60620b..e3889f64d0 100644 --- a/CONCEPTS.md +++ b/CONCEPTS.md @@ -155,6 +155,14 @@ A first-class, workflow-defined unit of task state: an id, a display name, and a ### Trait Composable column configuration: declarative flags (e.g. `complete`, `archived`, `countsTowardWip`) plus optional lifecycle hooks (`guard`, `gate`, `onEnter`, `onExit`, `releaseCondition`). Built-in and plugin-contributed traits register through one registry. Sync `guard` hooks and the `complete`/`archived` flags are built-in-only; plugin traits get async hook points only. A column's effective flags are the merged flags of its traits; conflicting compositions are rejected at save (server-side and in the editor). +### Column agent +A permanent agent binding on a workflow-defined column — a registry agent plus a mode — staffing all session-running work attributable to that column (custom nodes, the execute seam's coding session, per-step sessions; foreach template nodes inherit the enclosing foreach's column unless they declare their own). `defer` makes the column agent the default, applying only when the work carries no own agent identity and no complete model pair; `override` supersedes node- and task-level agent/model settings wholesale. + +Requires both the workflow-columns and graph-executor flags; with either off, bindings are inert at execution time. A missing or deleted agent degrades to normal resolution without aborting a live session. Binding an agent whose permission policy is broader than the project default requires explicit confirmation at save time on every write surface. + +### Effective agent (execution principal) +The agent identity that actually runs a piece of work after column-agent precedence resolves — and the principal every identity-keyed subsystem must consult: permission gating, heartbeat serialization in both directions, resume re-dispatch, and mid-flight change detection. It may differ from the task's assigned agent under an override binding, and one task may have multiple effective agents across concurrent branch sessions. + ### Lane A horizontal row on the multi-lane board, one per workflow in use by visible cards. Each lane renders its own workflow's columns. Tasks with no workflow selection appear in the Default workflow's lane; every card appears in exactly one lane. Zero-card lanes are hidden; lanes are collapsible with persisted state. diff --git a/docs/solutions/architecture-patterns/per-entity-execution-principal-override-blast-radius.md b/docs/solutions/architecture-patterns/per-entity-execution-principal-override-blast-radius.md new file mode 100644 index 0000000000..068147d392 --- /dev/null +++ b/docs/solutions/architecture-patterns/per-entity-execution-principal-override-blast-radius.md @@ -0,0 +1,56 @@ +--- +category: architecture-patterns +module: engine +date: 2026-06-05 +problem_type: architecture_pattern +component: tooling +severity: high +applies_when: + - "Adding a per-entity override that substitutes WHO/WHAT executes work (agent identity, model, principal)" + - "Wiring a new binding that supersedes task- or node-level settings (e.g. column agents, defer/override precedence)" + - "Reviewing a feature whose rollback story is 'disable the experimental flag'" +tags: + - column-agent + - execution-principal + - override-precedence + - kill-switch + - heartbeat + - workflow-columns +related: + - docs/solutions/logic-errors/per-task-auto-merge-override-ignored-by-trigger-gates.md + - docs/plans/2026-06-04-002-feat-column-agent-assignment-plan.md +--- + +# Per-entity execution-principal override: the full blast-radius checklist + +## Context + +The column-agent feature (PR #1432) lets a workflow column bind a registry agent that supersedes task/node agent settings (`defer`/`override`). The auto-merge-override lesson already taught "consult the override at every trigger gate, not just the action site." This feature showed the *execution-principal* variant has an even wider blast radius: plan review, code review, and two rounds of PR bots each found another subsystem still keyed on the old identity (`task.assignedAgentId`) — found one at a time, at increasing cost. + +## Guidance + +When work can run as an identity different from the one stored on the entity, enumerate and re-key ALL of these up front (Fusion's catalog; analogous sets exist elsewhere): + +1. **Session identity** — model resolution (`resolveExecutorSessionModel` runtimeConfig arg), persona, memory tools, attribution id. +2. **Permission gating** — `buildActionGateContext`/`buildPermanentAgentGatingContext` must receive the agent *actually running* (security boundary, not UX). +3. **Serialization, BOTH directions** — the deferral gate (`shouldDeferForHeartbeat`) AND the reverse guards keyed on `agent.taskId` in `agent-heartbeat.ts` (an agent may be effectively executing work it isn't assigned to → `isAgentEffectivelyExecuting` callback, wired at every scheduler construction site). +4. **Wake-up/resume queries** — `resumeTaskForAgent`'s task-SELECTION filter, not just its gate input; a second pass matching the *effective* identity. Watch for nodes that live only in nested structures (foreach templates are not in `ir.nodes` — walk subgraphs). +5. **Change detection / hot-swap** — the restart watcher diffs *task fields*; an override sourced from a workflow definition or agent config needs its own invalidation path, including the **release** branch (binding removed, or defer re-resolving to own settings) which must also clear the tracked principal and reverse-guard map. +6. **Kill-switch parity** — if the rollback story is "disable flag X," every execution-path entry point must actually read flag X. Gate the single choke point (resolver installation) AND any path that resolves independently (resume pass 2 resolved the IR directly and needed its own guard). +7. **Write-surface parity for safety gates** — a confirmation gate (policy escalation) added to the HTTP route is bypassed by agent tools writing through the store; share one validator (`validateColumnAgentBindings` in core) across ALL write surfaces. + +Precedence itself: one shared core resolver with explicit named branches (no `??` collapse), discriminated result for audit, and all-or-nothing own-settings semantics matched to the existing model resolver's both-present rule. + +## Why This Matters + +Each missed subsystem is a distinct production failure: gates computed for the wrong principal (privilege error), tasks stranded in-progress after heartbeats (resume miss), serialization contract violated (reverse guard), stale sessions after edits (watcher), and a rollback flag that doesn't actually roll back. None are caught by the feature's own happy-path tests; all were found by adversarial review or bots after implementation. The checklist converts five rounds of discovery into one design pass. + +## When to Apply + +Any feature where resolution of "who runs this" gains a new input: column/lane staffing, per-project agent defaults, delegation, impersonation, or model-override layers. Also when reviewing: grep every reader of the old identity field (`assignedAgentId`-style) and demand each is either re-keyed or argued irrelevant. + +## Examples + +- Single-flight interaction: when one memoized implementation pass serves many callers (foreach instances), a per-call mutable slot races — the pass-*initiating* caller must own the slot for the pass's lifetime (`runGraphTaskStep` stamps `graphSeamGoverningNodeId` only when it creates the memo, clears on settle). +- Surface-matrix tests (FN-5893): mode (defer/override) × surface (custom node, execute seam, step-execute, heartbeat, missing-agent fallback) × own-settings, plus characterization tests pinning the no-binding path byte-identical (parity oracle) and a kill-switch inertness test. +- Ambiguous composite ids: `#:` is unparseable under any single split when ids contain the delimiters — iterate candidates and validate against the graph (`parseInstanceNodeIdCandidates`), including that the *template node* exists, not just the container.