Skip to content
Open
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
5 changes: 5 additions & 0 deletions .github/workflows/dynamic-workflow.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Dynamic Workflow

on:
workflow_dispatch:
pull_request:
paths:
- 'plugins/hetaoBackend/mcode-dynamic-workflows/**'
Expand All @@ -27,6 +28,8 @@ jobs:
node-version: 22
cache: npm
cache-dependency-path: plugins/hetaoBackend/mcode-dynamic-workflows/package-lock.json
- name: Record the exact checked-out revision
run: git rev-parse HEAD
- run: npm ci --ignore-scripts --registry=https://registry.npmjs.org
- run: npm test
- run: npm run build
Expand All @@ -44,5 +47,7 @@ jobs:
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
- name: Record the exact checked-out revision
run: git rev-parse HEAD
- run: npm ci --ignore-scripts --registry=https://registry.npmjs.org
- run: node --test checks/process-tree.check.mjs
4 changes: 4 additions & 0 deletions plugins/hetaoBackend/mcode-dynamic-workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ The dev-only source checks use the pinned dependencies. They live in `checks/*.c

Verification covers isolated demo execution, approval gating, selective reuse and invalidation, project routing, persistence, parsing, local HTTP protections and UI behavior. Controlled executors are not evidence of live model correctness, account authorization or provider availability. No paid model calls or user research reruns were used for this contribution.

When upgrading, stop each existing project service before starting the updated `dist/main.mjs`; a connected chat may otherwise keep using an older background process. Use the existing service's workspace and data directory with `node dist/main.mjs --stop-service --workspace <absolute-project-path> --data-dir <active-data-directory>`, then reconnect through the updated package and confirm the dashboard address belongs to the new instance.

The store rolls back residual failed transactions before subsequent reads or writes. If recovery still fails, the operation reports an error rather than acknowledging a state that could later disappear. HTTP `after` and MCP `afterSequence` share the same nonnegative safe-integer cursor policy; invalid HTTP cursors return a client error before waiting.

## MCP tools

`workflow_validate`, `workflow_start`, `workflow_update`, `workflow_repair`, `workflow_status`, `workflow_results`, `workflow_wait`, `workflow_pause`, `workflow_cancel`, `workflow_resume`, `workflow_dashboard`.
Expand Down
56 changes: 56 additions & 0 deletions plugins/hetaoBackend/mcode-dynamic-workflows/REVIEW-NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# PR #67: durable progress batching

The change preserves immediate progress delivery while preventing acknowledged cursors from
being reused after a process crash. SQLite stays in WAL mode with `synchronous=FULL`.

- Reserve 1024 sequence numbers in a separately committed high-watermark transaction before
exposing a cursor. Recovery skips unused numbers; gaps are valid. Reservation failures expose
no cursor. Lease exhaustion within an existing transaction fails rather than publishing an
unreserved sequence.
- Durable events flush preceding progress and keep their own synchronous transaction.
- Failed batch commits restore the ordered buffer and roll back ledger updates, including a
nested flush when its outer transaction fails.
- Shutdown attempts all cleanup. A failed flush still closes SQLite and releases the owner lock;
the service reports failure through stderr and exit status.
- The public MCP test now stops its detached service before removing the temporary directory,
fixing its Windows `EBUSY` cleanup failure without changing service lifetime behavior.

## Follow-up repair and local verification, October 11, 2026

These counts were measured on the `codex/pr67-durable-batching` worktree based on `3712fbcbc4102528cd1ed1f9a073be95d81753e3` before committing this revision. Post-commit rechecks are recorded separately; no same-head GitHub CI result is claimed.

- Residual failed transactions are rolled back before subsequent Store reads/writes, reservation, maintenance and close. Recovery failures are reported, not acknowledged as durable writes. Six dedicated residual-transaction checks cover acknowledged saves, duplicate/uncommitted events, saveStep/saveSetting, engine finish state, flush failure and leasing/FULL semantics.
- HTTP/dashboard `after` and MCP `afterSequence` share `parseEventCursor`. Defaults remain zero; negative, fractional, nonfinite and unsafe integers are rejected before wait/query. Boolean/array/object coercion is rejected too. Actual HTTP and bundled stdio MCP are exercised, including maximum safe integer and legal cursors.
- Web bundle generation normalizes the embedded example's CRLF to LF, so rebuilding on Windows does not drift from Linux committed output. `dist/main.mjs` and `web/app.js` are regenerated; the rebuilt artifacts were stable in a second build.
- Final Windows/Node 24.12.0 checks: **136 passed, 2 skipped, 0 failed** (138); public package check: **1 passed, 0 skipped**. The two existing skips require POSIX signal/process-group behavior. No paid model calls or live account operations were used.
- Local root validation with development `node_modules` present scans third-party README text and fails on a third-party placeholder. The separately staged delivery check excludes development environments/dependencies, matching shipped files; its result is recorded in the handoff evidence. The unrelated root validator is not weakened.

CI records the checked-out revision and has a manual dispatch entry; the existing source/package and Windows process jobs remain enabled. Exact-head CI remains pending until these repairs are committed and checked on that SHA. Stop old workspace services before reconnecting to the new package; an existing owner process does not reload itself just because dist changed.

## Local verification, October 9, 2026

Windows, Node 24.12.0:

- `npm test`: **124 passed, 2 skipped, 0 failed** (126 tests).
- `node --test checks/integrity.check.mjs checks/durability.check.mjs test/package.test.mjs`:
**25 passed**. Includes a killed child whose memory-only progress was acknowledged by
`workflow_wait`, reopen/cursor checks, FULL pragmas, commit injection and failed-close cleanup.
- Package build regenerated `dist/main.mjs`; unrelated generated output was restored.

## Repeatable performance comparison

Run `node scripts/benchmark-durable-batching.mjs` from this plugin directory.
The baseline reads Store source from repository commit `6481e4a`. Both modes require FULL and
verify all 1100 event rows and the integrity chain after writing. Five rounds on the same host:

| Mode | Times (ms) | Median (ms) |
| --- | --- | --- |
| FULL, commit every progress event | 2988.755, 2911.603, 2863.510, 3312.873, 3098.278 | 2988.755 |
| FULL, safe batching | 217.803, 198.446, 361.923, 163.422, 143.395 | 198.446 |

This microbenchmark is **15.06x faster**. It measures event persistence overhead, not whole
workflow/model latency. Progress may disappear if the process dies before its batch is committed;
durable state retains FULL semantics and future sequence numbers exceed the lost acknowledged
cursor. SIGKILL/transaction injection is not a physical power-loss test.

GitHub CI execution is separate from these local results; a pending approval is not a test pass.
30 changes: 30 additions & 0 deletions plugins/hetaoBackend/mcode-dynamic-workflows/REVIEW-REPLY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
## October 11 follow-up — local worktree evidence only

Residual failed transactions now recover before subsequent Store entry points can acknowledge state. Six regression checks verify save/saveStep/saveSetting durability, event visibility, consistent engine finish state, failed flush/close reporting and unchanged FULL/sequence leasing behavior.

HTTP/dashboard and MCP now use the same safe-integer event cursor parser, before querying or waiting. Actual HTTP and bundled MCP reject negative/fractional/nonfinite/oversized values and boolean/array/object coercion; valid/default cursors still work. Runtime assets are rebuilt and embedded-example line endings are normalized for reproducible Windows/Linux output.

Windows Node 24.12.0: 136 checks passed, 2 POSIX-only checks skipped; public package check passed separately. CI records the checked-out SHA and supports manual dispatch. This revision is committed locally after the worktree checks, with post-commit independent review requested. It remains unpushed; no new GitHub run or same-head approval is claimed. Existing services must be stopped/reconnected before the updated package takes effect. REVIEW-NOTES.md contains limitations and the older historical performance evidence below.

---

Thanks for identifying the durability issues. SQLite WAL is restored to synchronous=FULL.
Progress batching remains, but sequences are now leased through an independently committed
high watermark before publishing a cursor. After a crash, unused lease values are skipped,
so workflow_wait(afterSequence) cannot skip future durable events due to cursor reuse.

Failed batch commits restore the ordered buffer and roll back ledger writes, including nested
flushes in a failed outer transaction. Shutdown attempts every cleanup step; a flush error
still closes SQLite/releases the owner lock and is reported through stderr/exit status.

Local verification: 124 tests passed, 2 platform skips; 25 focused integrity/durability/package
tests passed, including a killed process after workflow_wait acknowledged memory-only progress.
The Windows public MCP test also now stops its detached service before temporary-directory
cleanup. Build output is updated.

The repeatable 1100-event, five-round comparison against commit 6481e4a checks FULL, row counts
and ledger integrity in both modes: median 2989 ms with per-event commits versus 198 ms with
safe batching (15.06x). This measures persistence overhead, not end-to-end workflow latency.
REVIEW-NOTES.md documents that progress can still be lost before batch commit, while cursors
are never reused and durable state retains FULL semantics. No physical power-loss or GitHub
CI pass is claimed from these local tests.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import test from 'node:test';import assert from 'node:assert/strict';import {Client} from '@modelcontextprotocol/sdk/client/index.js';import {StdioClientTransport} from '@modelcontextprotocol/sdk/client/stdio.js';import {mkdtemp,rm,writeFile} from 'node:fs/promises';import {tmpdir} from 'node:os';import {join,resolve} from 'node:path';
import {execFile} from 'node:child_process';import {promisify} from 'node:util';
test('packaged MCP advertises reuseAcrossRuns and accepts it through the public tool surface',async()=>{
const dir=await mkdtemp(join(tmpdir(),'wf-cross-mcp-'));await writeFile(join(dir,'settings.json'),JSON.stringify({workspace:dir,dataDir:dir}));
const client=new Client({name:'cross-reuse-mcp-test',version:'1'});const transport=new StdioClientTransport({command:process.execPath,args:[resolve('dist/main.mjs'),'--stdio','--settings',join(dir,'settings.json')],stderr:'pipe'});
Expand All @@ -10,5 +11,8 @@ test('packaged MCP advertises reuseAcrossRuns and accepts it through the public
assert.equal(run.reuseAcrossRuns,true,'the flag must survive the public tool surface');
const rejected=await client.callTool({name:'workflow_start',arguments:{requestId:'cross-mcp-bad',name:'Bad flag type',executor:'demo',reuseAcrossRuns:'yes',script:'return 1;'}});
assert.ok(rejected.isError,'a non-boolean flag must be rejected by the public surface');
}finally{await client.close();await transport.close();await rm(dir,{recursive:true,force:true});}
}finally{await client.close();await transport.close();
// stdio is a client of a detached owner; release SQLite before Windows cleanup.
await promisify(execFile)(process.execPath,[resolve('dist/main.mjs'),'--stop-service','--settings',join(dir,'settings.json')],{windowsHide:true,timeout:15000});
await rm(dir,{recursive:true,force:true,maxRetries:10,retryDelay:100});}
});
Loading