From d997963ea7409ab9887659611c7ac4a1c6c76648 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:05:47 -0500 Subject: [PATCH] Enforce scheduler memory workflow and verification --- scripts/agent/run-scheduler-cycle.mjs | 111 ++++++++++++++++++++++++++ src/prompts/META_PROMPTS.md | 32 ++++++-- src/prompts/scheduler-flow.md | 29 +++++-- torch-config.json | 28 +++++++ 4 files changed, 186 insertions(+), 14 deletions(-) diff --git a/scripts/agent/run-scheduler-cycle.mjs b/scripts/agent/run-scheduler-cycle.mjs index d3ee006..f68e252 100644 --- a/scripts/agent/run-scheduler-cycle.mjs +++ b/scripts/agent/run-scheduler-cycle.mjs @@ -85,6 +85,43 @@ async function readJson(filePath, fallback) { } } +function normalizeStringList(value, fallback = []) { + if (!Array.isArray(value)) return fallback; + return value.filter((item) => typeof item === 'string' && item.trim()).map((item) => item.trim()); +} + +async function artifactExistsSince(filePath, sinceMs) { + if (!filePath) return false; + const resolved = path.isAbsolute(filePath) + ? filePath + : path.resolve(process.cwd(), filePath); + try { + const stat = await fs.stat(resolved); + return stat.isFile() && stat.mtimeMs >= sinceMs; + } catch { + return false; + } +} + +async function verifyMemoryStep({ name, markers, artifacts, outputText, sinceMs }) { + const markerMatched = markers.some((marker) => outputText.includes(marker)); + let artifactMatched = false; + + for (const artifact of artifacts) { + if (await artifactExistsSince(artifact, sinceMs)) { + artifactMatched = true; + break; + } + } + + return { + name, + markerMatched, + artifactMatched, + complete: markerMatched || artifactMatched, + }; +} + function parseFrontmatterAgent(markdown) { const lines = markdown.split(/\r?\n/); if (lines[0]?.trim() !== '---') return null; @@ -135,6 +172,9 @@ async function getSchedulerConfig(cadence, { isInteractive }) { const scheduler = cfg.scheduler || {}; const handoffCommand = scheduler.handoffCommandByCadence?.[cadence] || null; const missingHandoffCommandForMode = !isInteractive && !handoffCommand; + const memoryPolicyRaw = scheduler.memoryPolicyByCadence?.[cadence] || {}; + const mode = memoryPolicyRaw.mode === 'required' ? 'required' : 'optional'; + return { firstPrompt: scheduler.firstPromptByCadence?.[cadence] || null, handoffCommand, @@ -142,6 +182,19 @@ async function getSchedulerConfig(cadence, { isInteractive }) { validationCommands: Array.isArray(scheduler.validationCommandsByCadence?.[cadence]) ? scheduler.validationCommandsByCadence[cadence].filter((cmd) => typeof cmd === 'string' && cmd.trim()) : ['npm', 'run', 'lint'], + memoryPolicy: { + mode, + retrieveCommand: typeof memoryPolicyRaw.retrieveCommand === 'string' && memoryPolicyRaw.retrieveCommand.trim() + ? memoryPolicyRaw.retrieveCommand.trim() + : null, + storeCommand: typeof memoryPolicyRaw.storeCommand === 'string' && memoryPolicyRaw.storeCommand.trim() + ? memoryPolicyRaw.storeCommand.trim() + : null, + retrieveSuccessMarkers: normalizeStringList(memoryPolicyRaw.retrieveSuccessMarkers, ['MEMORY_RETRIEVED']), + storeSuccessMarkers: normalizeStringList(memoryPolicyRaw.storeSuccessMarkers, ['MEMORY_STORED']), + retrieveArtifacts: normalizeStringList(memoryPolicyRaw.retrieveArtifacts), + storeArtifacts: normalizeStringList(memoryPolicyRaw.storeArtifacts), + }, }; } @@ -257,10 +310,25 @@ async function main() { const promptPath = path.resolve(process.cwd(), 'src/prompts', cadence, `${selectedAgent}.md`); const runArtifactSince = new Date().toISOString(); + const runStartMs = Date.parse(runArtifactSince); + const outputChunks = []; + + if (schedulerConfig.memoryPolicy.retrieveCommand) { + const retrieveResult = await runCommand('bash', ['-lc', schedulerConfig.memoryPolicy.retrieveCommand], { + env: { AGENT_PLATFORM: platform, SCHEDULER_AGENT: selectedAgent, SCHEDULER_CADENCE: cadence, SCHEDULER_PROMPT_PATH: promptPath }, + }); + outputChunks.push(retrieveResult.stdout, retrieveResult.stderr); + if (retrieveResult.code !== 0) { + await writeLog({ cadence, agent: selectedAgent, status: 'failed', reason: 'Memory retrieval command failed', detail: schedulerConfig.memoryPolicy.retrieveCommand }); + process.exit(retrieveResult.code); + } + } + if (schedulerConfig.handoffCommand) { const handoff = await runCommand('bash', ['-lc', schedulerConfig.handoffCommand], { env: { AGENT_PLATFORM: platform, SCHEDULER_AGENT: selectedAgent, SCHEDULER_CADENCE: cadence, SCHEDULER_PROMPT_PATH: promptPath }, }); + outputChunks.push(handoff.stdout, handoff.stderr); if (handoff.code !== 0) { await writeLog({ cadence, agent: selectedAgent, status: 'failed', reason: 'Prompt/handoff execution failed', detail: 'Handoff callback failed.' }); process.exit(handoff.code); @@ -273,6 +341,49 @@ async function main() { process.exit(1); } + if (schedulerConfig.memoryPolicy.storeCommand) { + const storeResult = await runCommand('bash', ['-lc', schedulerConfig.memoryPolicy.storeCommand], { + env: { AGENT_PLATFORM: platform, SCHEDULER_AGENT: selectedAgent, SCHEDULER_CADENCE: cadence, SCHEDULER_PROMPT_PATH: promptPath }, + }); + outputChunks.push(storeResult.stdout, storeResult.stderr); + if (storeResult.code !== 0) { + await writeLog({ cadence, agent: selectedAgent, status: 'failed', reason: 'Memory storage command failed', detail: schedulerConfig.memoryPolicy.storeCommand }); + process.exit(storeResult.code); + } + } + + const memoryOutput = outputChunks.join('\n'); + const retrieveCheck = await verifyMemoryStep({ + name: 'retrieve', + markers: schedulerConfig.memoryPolicy.retrieveSuccessMarkers, + artifacts: schedulerConfig.memoryPolicy.retrieveArtifacts, + outputText: memoryOutput, + sinceMs: runStartMs, + }); + const storeCheck = await verifyMemoryStep({ + name: 'store', + markers: schedulerConfig.memoryPolicy.storeSuccessMarkers, + artifacts: schedulerConfig.memoryPolicy.storeArtifacts, + outputText: memoryOutput, + sinceMs: runStartMs, + }); + + const missingSteps = [retrieveCheck, storeCheck].filter((step) => !step.complete).map((step) => step.name); + if (missingSteps.length && schedulerConfig.memoryPolicy.mode === 'required') { + await writeLog({ + cadence, + agent: selectedAgent, + status: 'failed', + reason: 'Required memory steps not verified', + detail: `Missing evidence for: ${missingSteps.join(', ')}`, + }); + process.exit(1); + } + + if (missingSteps.length) { + console.warn(`[scheduler] Optional memory evidence missing for ${missingSteps.join(', ')}.`); + } + const artifactCheck = await runCommand('node', [ 'scripts/agent/verify-run-artifacts.mjs', '--since', diff --git a/src/prompts/META_PROMPTS.md b/src/prompts/META_PROMPTS.md index dbbdbdb..627a8fb 100644 --- a/src/prompts/META_PROMPTS.md +++ b/src/prompts/META_PROMPTS.md @@ -50,15 +50,25 @@ MUST 3: Run these commands in this order: AGENT_PLATFORM= npm run lock:lock -- --agent --cadence daily Exit 0 = lock acquired, proceed. Exit 3 = race lost, go back to step 3. 6) Execute selected prompt from src/prompts/daily/ -7) Run repository checks (for example: npm run lint) +7) Run required memory workflow for this cadence: + - Before execution, run the retrieval command if configured: + `scheduler.memoryPolicyByCadence.daily.retrieveCommand` + - After execution, run the storage command if configured: + `scheduler.memoryPolicyByCadence.daily.storeCommand` + - Emit or collect verifiable evidence for both steps using configured markers/artifacts. +8) Validate memory evidence: + - Confirm retrieval evidence exists (marker and/or artifact file). + - Confirm storage evidence exists (marker and/or artifact file). + - If `scheduler.memoryPolicyByCadence.daily.mode` is `required`, fail the run if either check is missing. +9) Run repository checks (for example: npm run lint) - If any validation command exits non-zero, do not call `lock:complete`. - Instead, write `_failed.md` with the failing command and reason, then stop. -8) Publish completion before writing `_completed.md` (only after step 7 passes): +10) Publish completion before writing `_completed.md` (only after step 9 passes): AGENT_PLATFORM= npm run lock:complete -- --agent --cadence daily (Equivalent command allowed: torch-lock complete --agent --cadence daily) - Exit 0: continue. - Exit non-zero: fail the run, write `_failed.md` with a clear completion-publish failure reason and retry guidance, then stop. -9) Only after step 8 succeeds, write final task log (`_completed.md` for success). For any failure in step 7 or step 8, write `_failed.md`, then stop. +11) Only after step 10 succeeds, write final task log (`_completed.md` for success). For any failure in step 9 or step 10, write `_failed.md`, then stop. Worked example (required order): - `AGENT_PLATFORM=codex npm run lock:lock -- --agent content-audit-agent --cadence daily` @@ -122,15 +132,25 @@ MUST 3: Run these commands in this order: AGENT_PLATFORM= npm run lock:lock -- --agent --cadence weekly Exit 0 = lock acquired, proceed. Exit 3 = race lost, go back to step 3. 6) Execute selected prompt from src/prompts/weekly/ -7) Run repository checks (for example: npm run lint) +7) Run required memory workflow for this cadence: + - Before execution, run the retrieval command if configured: + `scheduler.memoryPolicyByCadence.weekly.retrieveCommand` + - After execution, run the storage command if configured: + `scheduler.memoryPolicyByCadence.weekly.storeCommand` + - Emit or collect verifiable evidence for both steps using configured markers/artifacts. +8) Validate memory evidence: + - Confirm retrieval evidence exists (marker and/or artifact file). + - Confirm storage evidence exists (marker and/or artifact file). + - If `scheduler.memoryPolicyByCadence.weekly.mode` is `required`, fail the run if either check is missing. +9) Run repository checks (for example: npm run lint) - If any validation command exits non-zero, do not call `lock:complete`. - Instead, write `_failed.md` with the failing command and reason, then stop. -8) Publish completion before writing `_completed.md` (only after step 7 passes): +10) Publish completion before writing `_completed.md` (only after step 9 passes): AGENT_PLATFORM= npm run lock:complete -- --agent --cadence weekly (Equivalent command allowed: torch-lock complete --agent --cadence weekly) - Exit 0: continue. - Exit non-zero: fail the run, write `_failed.md` with a clear completion-publish failure reason and retry guidance, then stop. -9) Only after step 8 succeeds, write final task log (`_completed.md` for success). For any failure in step 7 or step 8, write `_failed.md`, then stop. +11) Only after step 10 succeeds, write final task log (`_completed.md` for success). For any failure in step 9 or step 10, write `_failed.md`, then stop. Worked example (required order): - `AGENT_PLATFORM=codex npm run lock:lock -- --agent bug-reproducer-agent --cadence weekly` diff --git a/src/prompts/scheduler-flow.md b/src/prompts/scheduler-flow.md index 77d58ed..864b5f0 100644 --- a/src/prompts/scheduler-flow.md +++ b/src/prompts/scheduler-flow.md @@ -30,7 +30,13 @@ Every agent prompt invoked by the schedulers (daily/weekly) MUST enforce this co 3. **Capture reusable failures and unresolved issues**: - Record reusable failures in `docs/agent-handoffs/incidents/` - Record active unresolved reproducible items in `KNOWN_ISSUES.md` -4. **Publish lock completion only after validation passes and before writing success logs**: +4. **Execute memory retrieval before implementation begins**: + - Run configured memory retrieval workflow before prompt execution (for example via `scheduler.memoryPolicyByCadence..retrieveCommand`) + - Capture explicit evidence of retrieval with output markers and/or created artifact files +5. **Store memory after implementation and before completion publish**: + - Run configured memory storage workflow after prompt execution (for example via `scheduler.memoryPolicyByCadence..storeCommand`) + - Persist summary/decision memory and emit verifiable markers/artifacts +6. **Publish lock completion only after validation passes and before writing success logs**: - Run `npm run lock:complete -- --agent --cadence ` (or equivalent `complete`) successfully - If any validation command exits non-zero, do **not** call `lock:complete`; write `_failed.md` with the validation failure reason and stop - Only after completion publish succeeds may the agent write final `*_completed.md` log files @@ -125,12 +131,19 @@ Every agent prompt invoked by the schedulers (daily/weekly) MUST enforce this co 8. Execute `/` end-to-end. -9. Run repository checks (for example: `npm run lint`). +9. Confirm memory contract completion: + + - Memory retrieval evidence must exist for this run (output marker and/or artifact file). + - Memory storage evidence must exist for this run (output marker and/or artifact file). + - If `scheduler.memoryPolicyByCadence..mode = required`, missing evidence is a hard failure. + - If mode is `optional`, log warning context and continue. + +10. Run repository checks (for example: `npm run lint`). - If any validation command exits non-zero: **fail the run immediately**, write `_failed.md` with the failing command and reason, and stop. - - When step 9 fails, step 10 MUST NOT be executed (`lock:complete` is forbidden until validation passes). + - When step 10 fails, step 11 MUST NOT be executed (`lock:complete` is forbidden until validation passes). -10. Publish completion before writing final success log: +11. Publish completion before writing final success log: ```bash AGENT_PLATFORM= \ @@ -139,15 +152,15 @@ Every agent prompt invoked by the schedulers (daily/weekly) MUST enforce this co (Equivalent invocation is allowed: `torch-lock complete --agent --cadence `.) - - Exit `0`: completion published successfully; continue to step 11. + - Exit `0`: completion published successfully; continue to step 12. - Exit non-zero: **fail the run**, write `_failed.md` with a clear reason that completion publish failed and retry guidance (for example: `Retry npm run lock:complete -- --agent --cadence after verifying relay connectivity`), then stop. -11. Create final task log only after step 10 succeeds: +12. Create final task log only after step 11 succeeds: - `_completed.md` MUST be created only after completion publish succeeds. - - `_failed.md` is required when step 9 or step 10 fails, and should include the failure reason and next retry action. + - `_failed.md` is required when step 10 or step 11 fails, and should include the failure reason and next retry action. -12. Commit and push. +13. Commit and push. Worked post-task example (MUST order): diff --git a/torch-config.json b/torch-config.json index 0ac71af..dfb17d4 100644 --- a/torch-config.json +++ b/torch-config.json @@ -17,6 +17,34 @@ "firstPromptByCadence": { "daily": "scheduler-update-agent", "weekly": "bug-reproducer-agent" + }, + "memoryPolicyByCadence": { + "daily": { + "mode": "optional", + "retrieveCommand": null, + "storeCommand": null, + "retrieveSuccessMarkers": [ + "MEMORY_RETRIEVED" + ], + "storeSuccessMarkers": [ + "MEMORY_STORED" + ], + "retrieveArtifacts": [], + "storeArtifacts": [] + }, + "weekly": { + "mode": "optional", + "retrieveCommand": null, + "storeCommand": null, + "retrieveSuccessMarkers": [ + "MEMORY_RETRIEVED" + ], + "storeSuccessMarkers": [ + "MEMORY_STORED" + ], + "retrieveArtifacts": [], + "storeArtifacts": [] + } } } }