Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion VENDORED.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,17 @@ are NOT from upstream faremeter/interchange at all — upstream's own
commit. They are copied from gtm-workbench's own `packages/workflow-host`
workspace fork (see `docs/revendor-inventory.md` for the full provenance
note and why no ordinary upstream-publish kill date applies to this
sub-delta). `vendor/intx/inference-catalog` (CL-6280) is
sub-delta). `vendor/intx/workflow` (CL-6326) adds an
`onBodyFailure` policy field to the `onTrigger` primitive: absent (or
`"end"`) preserves terminal-is-final exactly as before, `"continue"` lets a
body run that ends `failed` (never `cancelled`) leave the section
subscribed instead of ending the whole run, so one bad turn does not kill a
long-lived section. The gate is read live off `primitive.onBodyFailure` at
both the steady-state drive loop and the crash-recovery resume plan in
`runtime/run.ts`, mirroring how `awaitSignal.onTimeout` is read live rather
than defaulted at construction. This delta targets the current pin
(`59f5e7b9`) and re-applies against the re-pinned tree once PR #59 lands —
see `docs/revendor-inventory.md`. `vendor/intx/inference-catalog` (CL-6280) is
pinned separately at `5d2aa94a`, a later `main` tip than the other twenty
rows' `59f5e7b9`, since that commit is where the package's folded
provider/model catalog first landed upstream; its own local modification
Expand Down
34 changes: 34 additions & 0 deletions docs/revendor-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,40 @@ no code-side mitigation to retire. Recommend re-running this check against
the actual Linear ticket bodies before concluding they're stale — this pass
searched by inferred keyword/CL-number, not by reading each ticket.

### A failed body occurrence permanently ended a long-lived onTrigger section (CL-6326)

`vendor/intx/workflow/src/runtime/run.ts` — `runOnTrigger`'s steady-state
drive loop and its crash-recovery counterpart, `planOnTriggerResume`

Both treat a body run that ends `failed` identically to one that ends
`cancelled`: either throws terminal-is-final, ending the whole section run.
For a long-lived, event-driven section (chat-style: one occurrence per
inbound message), that means a single bad turn — one agent step throwing —
permanently ends the entire conversation's run, with no way to keep servicing
later occurrences.

**Local fix:** a `BodyFailurePolicy` (`"end" | "continue"`) field,
`onTrigger.onBodyFailure`, defaulting to `"end"` (current behavior,
byte-compatible). `"continue"` lets the section re-arm on its input park
after a `failed` occurrence instead of throwing; `cancelled` is untouched and
always still ends the section — cancellation reflects a drain/operator
decision, not a turn-level error. The failed occurrence stays durably
recorded and loudly observable: the existing `ChildCompleted{terminalStatus:
"failed"}` commit lands on the run's own audit log before the policy check
runs either way, so a `"continue"`-policy failure is never silent, only
non-fatal to the section. Covered by
`vendor/intx/workflow/src/runtime/run.test.ts` — the intended home
`runlocal/run-local.ts:8` already names for this layer's coverage.

**This delta targets the current pin** (`59f5e7b9`). Upstream's `486a6b1b`
changed `run.ts` after this pin; the delta above re-diffs against the
re-pinned tree once PR #59 lands.

**Retire when** upstream ships a non-fatal body-failure edge on `onTrigger`
(or the whole source-format workflow line reaches a published `@intx/*` npm
version, per `vendor/intx/workflow`'s broader retirement condition in
`VENDORED.md`).

## Estimated fix effort

| Item | Shape | Rough size |
Expand Down
4 changes: 2 additions & 2 deletions scripts/checks/kill-dates.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ vendor/intx/pack-transport | sawyer | 2026-09-14 | 81f230269ae916111bd7242698f4e
vendor/intx/storage-isogit | sawyer | 2026-09-14 | 26f30a4fd27645a620bded1e9359fbb8d87d20f759b9e02c52e213efb459ca73
vendor/intx/tool-packaging | sawyer | 2026-09-14 | 47f29256729105eebab38b23b7326ec9fadc9ebcb9ab460eeba44f94a86d7e45
vendor/intx/types | sawyer | 2026-09-14 | 29d8a7b2589979a04a38706e40e14491a4c57856d6368f74e944c386afed2ef7
vendor/intx/workflow | sawyer | 2026-09-14 | 81af2a4cb3669540b98d31a9a530908fb701a2a19da94ae69dcbe55a58f8f73b
vendor/intx/workflow | sawyer | 2026-09-14 | 81d7ff7b8cfde64ab8b7422f393b5f46c9e98a8af341685f099b01c71ea17197
vendor/intx/workflow-deploy | sawyer | 2026-09-14 | de72de087e7b499d42b69b7eae6185a4c5448c34a02d3c21badf7c6d0ad3b66d
vendor/intx/workflow-host | sawyer | 2026-09-14 | 6bbc9fa69c28c81d34285016c7cf88ed470401b2bff8c57d49a9bfd4b22cf426
vendor/intx/workflow-host | sawyer | 2026-09-14 | 2cc2fc754bf195ee0eab4d36e7e2ff0d7600f5111dcb5ddc7ae3e4412412d0bb

packages/folded-runs | sawyer | 2026-11-01

2 changes: 1 addition & 1 deletion vendor/intx/workflow/VENDORED-FROM
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Source: https://github.com/faremeter/interchange (packages/workflow)
Commit: 59f5e7b9d94e7bcccfc180e7d9d11434e2e18eec
License: LGPL-2.1-only (see vendor/intx/LICENSE)
Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed.
Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. CL-6326: `onTrigger` gains an `onBodyFailure?: "end" | "continue"` policy field (definition/primitives.ts); `runtime/run.ts`'s steady-state drive loop and `planOnTriggerResume` read it live to let a `"continue"`-policy section re-arm past a `failed` body occurrence instead of ending the whole run (`cancelled` is unaffected, always terminal-is-final). See VENDORED.md and docs/revendor-inventory.md.
33 changes: 26 additions & 7 deletions vendor/intx/workflow/src/definition/primitives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import type { Trigger } from "./triggers";

export type DrainBehavior = "cancel" | "wait";

export type BodyFailurePolicy = "end" | "continue";

export interface RetryPolicy {
/** Maximum number of attempts including the first. */
maxAttempts: number;
Expand Down Expand Up @@ -217,13 +219,15 @@ export interface LoopPrimitive extends PrimitiveBase {
* `trigger.payload`.
*
* The section never self-completes: the workflow stays running while
* subscribed and terminates only on a body error or an explicit
* end-of-workflow, and a terminated run is final -- never relaunched. The
* first occurrence is the run's own firing trigger (its
* `RunStarted.trigger.payload`); each later occurrence arrives as an input
* signal carrying the next payload. `defineWorkflow` collects every `on`
* into the workflow's `triggers`, so `on` is the first-class binding
* between a trigger and the section it drives.
* subscribed and terminates only on a body run ending `cancelled`, a body
* run ending `failed` under the default `onBodyFailure: "end"` policy, or
* an explicit end-of-workflow -- `onBodyFailure: "continue"` keeps the
* section alive through a failed occurrence -- and a terminated run is
* final -- never relaunched. The first occurrence is the run's own firing
* trigger (its `RunStarted.trigger.payload`); each later occurrence
* arrives as an input signal carrying the next payload. `defineWorkflow`
* collects every `on` into the workflow's `triggers`, so `on` is the
* first-class binding between a trigger and the section it drives.
*
* `drainBehavior` defaults to `"wait"`: a live interactive section is not
* abandoned mid-conversation at redeploy unless the author opts into
Expand All @@ -234,6 +238,17 @@ export interface OnTriggerPrimitive extends PrimitiveBase {
on: Trigger;
body: OnTriggerBody;
drainBehavior?: DrainBehavior;
/**
* How a body run that ends `failed` affects the section. Absent (or
* `"end"`) preserves terminal-is-final: a failed body run ends the
* whole section run, exactly as before this field existed. `"continue"`
* records the failed occurrence and keeps the section subscribed --
* the next occurrence spawns and runs normally. A body run that ends
* `cancelled` is unaffected by this field and always ends the section:
* cancellation reflects a drain/operator decision, not a turn-level
* error.
*/
onBodyFailure?: BodyFailurePolicy;
}

/**
Expand Down Expand Up @@ -553,6 +568,7 @@ export interface OnTriggerOpts {
on: Trigger;
body: WorkflowDefinition;
drainBehavior?: DrainBehavior;
onBodyFailure?: BodyFailurePolicy;
after?: readonly string[];
}

Expand All @@ -565,6 +581,9 @@ export function onTrigger(opts: OnTriggerOpts): OnTriggerPrimitive {
// Authored inline; the deploy step rewrites this to `{ ref }`.
body: { inline: opts.body },
drainBehavior,
...(opts.onBodyFailure !== undefined
? { onBodyFailure: opts.onBodyFailure }
: {}),
...(opts.after !== undefined ? { after: opts.after } : {}),
};
}
Expand Down
Loading
Loading