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
6 changes: 6 additions & 0 deletions plugins/hetaoBackend/mcode-dynamic-workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ Tracked files are regular workspace files (up to 1 MB each), fingerprinted from

The repaired script runs from its beginning; checkpoints are recomputed and unreached branches are not premarked complete. Declare every data/control dependency in `dependsOn`. Untracked files, external evidence and side effects cannot be checked automatically, so stale or incorrect results must not be selected for reuse. Schema-constrained outputs accept native values, complete JSON text, or one complete JSON fence; validation errors preserve the raw output. Each node has an independent schema namespace, including local references, so repeated schema identifiers cannot conflict across nodes or runs.

## Cross-run reuse

Stored results are normally reused only within a run (resume) or through explicit repair selection. Passing `reuseAcrossRuns: true` to `workflow_start`, or setting it while a draft is pending review, additionally lets a node adopt a stored result from an earlier run of the same project. Adoption requires the node's full spec to hash identically (prompt, model, effort, input, schema, dependencies) and the run context to match on all four keys — workspace, input, executor and tracked-file fingerprints. The node's upstream lineage must match too: every step carries a lineage hash over its own spec and the lineage hashes of its declared dependencies in order, so a changed upstream (for example an edited upstream prompt) invalidates downstream candidates even when their own specs are unchanged. mcode nodes must declare an explicit model to be cross-run candidates — the default model comes from the CLI environment and is not part of the match key. Only runs executed with this feature stamp the context and lineage hashes on their steps, so legacy runs are not candidates. Adopted outputs are re-validated against the node's current schema; the step records provenance in `reusedFrom` — the immediate source run and step, `crossRun: true` — and in `originalProducer`, the run and step that originally produced the output, so chained adoptions stay traceable to their origin; it emits a `step.reused` event, and does not consume the workflow's agent-call budget.

Reuse proves only that the context was identical and that the stored result was carried over faithfully — it does not prove the original run's output was semantically correct. For critical nodes, prefer schemas with evidence fields or place an independent verification node downstream, and keep `reuseAcrossRuns` off when in doubt.

## Data, permissions and network

- Every project-scoped tool requires an absolute `workspace`; plugin process cwd is never treated as your project. Canonical project paths isolate runs, templates, history, limits and ports. Never use an unrelated project's path.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
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';
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'});
try{
await client.connect(transport);const {tools}=await client.listTools();
for(const name of ['workflow_start','workflow_update']){const tool=tools.find(t=>t.name===name);assert.ok(tool,`${name} listed`);assert.equal(tool.inputSchema.properties.reuseAcrossRuns?.type,'boolean',`${name} schema must advertise reuseAcrossRuns (additionalProperties:false)`);}
const started=await client.callTool({name:'workflow_start',arguments:{requestId:'cross-mcp',name:'Cross-run via public surface',executor:'demo',reuseAcrossRuns:true,script:'return await ctx.agent({id:"a",prompt:"p"});'}});
assert.ok(!started.isError,started.content?.[0]?.text);const run=JSON.parse(started.content[0].text);
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});}
});
Loading
Loading