-
Notifications
You must be signed in to change notification settings - Fork 1
fix: raise Fullstack QA Loop cyclic channel maxCycles 6 → 50 #2275
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1244,13 +1244,13 @@ export const FULLSTACK_QA_LOOP_WORKFLOW: SpaceWorkflow = { | |
| { | ||
| from: 'Review', | ||
| to: 'Coding', | ||
| maxCycles: 6, | ||
| maxCycles: 50, | ||
|
lsm marked this conversation as resolved.
|
||
| label: 'Review → Coding (feedback)', | ||
| }, | ||
| { | ||
| from: 'QA', | ||
| to: 'Coding', | ||
| maxCycles: 6, | ||
| maxCycles: 50, | ||
| label: 'QA → Coding (issues found)', | ||
| }, | ||
| ], | ||
|
|
@@ -1910,19 +1910,38 @@ function mergeChannelsFromTemplate( | |
| ); | ||
| if (!existingChannels) return remappedTemplateChannels; | ||
|
|
||
| const existingKeys = new Set( | ||
| existingChannels.map((channel) => | ||
| JSON.stringify({ from: channel.from, to: channel.to, gateId: channel.gateId ?? null }) | ||
| ) | ||
| const channelKey = (channel: NonNullable<SpaceWorkflow['channels']>[number]) => | ||
| JSON.stringify({ from: channel.from, to: channel.to, gateId: channel.gateId ?? null }); | ||
|
|
||
| const templateChannelByKey = new Map( | ||
| remappedTemplateChannels.map((channel) => [channelKey(channel), channel]) | ||
| ); | ||
|
|
||
| // In-place merge of structural channel fields (maxCycles, label) onto | ||
| // channels that already exist in the seeded workflow, mirroring the gate | ||
| // (writers/features) and node-agent (toolGuards) merges. Channels are | ||
| // structural topology: {from, to, gateId} is the stable match key and the | ||
| // template owns maxCycles + label. Propagating them keeps structural changes | ||
| // (e.g. raising a cyclic cap 6 → 50) landing on pre-existing spaces; without | ||
| // this, the unconditional templateHash write would stamp the new hash while | ||
| // leaving the old field values in place, then block any future fix from | ||
| // reaching them (the matching hash skips the row on every later startup). | ||
| const mergedExisting = existingChannels.map((channel) => { | ||
| const templateChannel = templateChannelByKey.get(channelKey(channel)); | ||
| if (!templateChannel) return channel; | ||
| return { | ||
| ...channel, | ||
| maxCycles: templateChannel.maxCycles, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For an existing Fullstack run that has already reached six traversals—the exact upgrade case this change targets—writing Useful? React with 👍 / 👎. |
||
| label: templateChannel.label, | ||
|
lsm marked this conversation as resolved.
|
||
| }; | ||
| }); | ||
|
|
||
| const mergedExistingKeys = new Set(mergedExisting.map(channelKey)); | ||
| const missingTemplateChannels = remappedTemplateChannels.filter( | ||
| (channel) => | ||
| !existingKeys.has( | ||
| JSON.stringify({ from: channel.from, to: channel.to, gateId: channel.gateId ?? null }) | ||
| ) | ||
| (channel) => !mergedExistingKeys.has(channelKey(channel)) | ||
| ); | ||
|
|
||
| return [...existingChannels, ...missingTemplateChannels]; | ||
| return [...mergedExisting, ...missingTemplateChannels]; | ||
| } | ||
|
|
||
| function remapTemplateHookAgentSlots( | ||
|
|
@@ -2103,10 +2122,16 @@ export function mergeGateStructuralFieldsFromTemplate( | |
| * Gate `features` are copied from matching template gates so data-driven runtime | ||
| * checks land on pre-existing spaces. Missing template gates are appended. | ||
| * Existing checks, scripts, and gate topology remain untouched. | ||
| * - Missing template channels are appended so newly-added built-in branches become | ||
| * reachable on pre-existing spaces. Template hooks are copied from the built-in | ||
| * template so hook-based runtime metadata lands during drift re-stamps. Existing | ||
| * channels, layout, and node rows are not regenerated. Workflow IDs, node IDs, and persisted node-agent slots | ||
| * - Structural channel fields (maxCycles, label) are merged in-place onto channels matched by | ||
| * {from, to, gateId}, and missing template channels are appended so newly-added built-in | ||
| * branches become reachable on pre-existing spaces. This is how a raised cyclic cap (e.g. | ||
| * maxCycles 6 → 50) lands on pre-existing spaces instead of only newly-created ones. Like the | ||
| * other template-owned structural fields (completionAutonomyLevel, gate writers/features, node | ||
| * toolGuards, hooks), built-in channel maxCycles/label are template-managed: a user-customized | ||
| * value (editable via the visual editor) is reset to the template value when drift triggers a | ||
| * re-stamp — clone to a custom (non-re-stamped) workflow for a persistent custom cap. Template | ||
| * hooks are copied from the built-in template so hook-based runtime metadata lands during | ||
| * drift re-stamps. Existing channels, layout, and node rows are not regenerated. Workflow IDs, node IDs, and persisted node-agent slots | ||
| * are stable identifiers for in-flight runs, so template drift must never | ||
| * replace node rows. Agent `toolGuards` are updated in-place on existing node | ||
| * configs instead. | ||
|
|
@@ -2117,7 +2142,7 @@ const RESTAMP_FIELDS = [ | |
| 'templateHash', | ||
| 'nodes(postApproval + toolGuards in-place + missing template nodes)', | ||
| 'gates(field writers + features in-place + missing template gates)', | ||
| 'channels(missing template channels)', | ||
| 'channels(maxCycles + label in-place on matched channels + missing template channels)', | ||
| 'hooks(template hooks)', | ||
| ] as const; | ||
|
|
||
|
|
@@ -2206,8 +2231,15 @@ export function seedBuiltInWorkflows( | |
| ); | ||
| const removedLegacyPrReadyChannels = | ||
| (existingChannels?.length ?? 0) !== (row.channels?.length ?? 0); | ||
| const hasNewTemplateChannels = | ||
| (mergedChannels?.length ?? 0) > (existingChannels?.length ?? 0); | ||
| // mergeChannelsFromTemplate propagates structural fields (maxCycles, | ||
| // label) in-place on matched channels in addition to appending any | ||
| // missing template channels. Detect whether the merge changed anything | ||
| // — added channels OR updated structural fields — so the result is | ||
| // persisted even when the channel count is unchanged (e.g. raising a | ||
| // cyclic cap 6 → 50 on an already-seeded workflow). | ||
| const channelsChanged = | ||
| removedLegacyPrReadyChannels || | ||
| JSON.stringify(mergedChannels) !== JSON.stringify(existingChannels); | ||
|
|
||
| workflowManager.updateWorkflow(row.id, { | ||
| completionAutonomyLevel: template.completionAutonomyLevel, | ||
|
|
@@ -2219,9 +2251,7 @@ export function seedBuiltInWorkflows( | |
| mergeHooksFromTemplate(template.hooks, template.nodes, migratedNodes, row.hooks) ?? | ||
| null, | ||
| nodes: migratedNodes, | ||
| ...(hasNewTemplateChannels || removedLegacyPrReadyChannels | ||
| ? { channels: mergedChannels } | ||
| : {}), | ||
| ...(channelsChanged ? { channels: mergedChannels } : {}), | ||
| templateHash: expectedHash, | ||
| }); | ||
| restamped.push(template.name); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.