diff --git a/AGENTS.md b/AGENTS.md index dcc6e110d..9558d03df 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -105,6 +105,8 @@ Keep it short, stable, and helper-oriented. Deep runbooks belong in checked-in d `tests/results/_agent/handoff/autonomous-governor-summary.json`. - `node tools/npm/run-script.mjs priority:governor:portfolio` refreshes the cross-repo operating receipt at `tests/results/_agent/handoff/autonomous-governor-portfolio-summary.json`. +- `node tools/npm/run-script.mjs priority:context:concentrate` refreshes the compact durable memory receipt at + `tests/results/_agent/handoff/sagan-context-concentrator.json`. - `node tools/npm/run-script.mjs priority:continuity` refreshes the continuity receipts at `tests/results/_agent/runtime/continuity-telemetry.json` and `tests/results/_agent/handoff/continuity-summary.json`. @@ -116,6 +118,7 @@ Keep it short, stable, and helper-oriented. Deep runbooks belong in checked-in d - `tests/results/_agent/issue/no-standing-priority.json` - `tests/results/_agent/handoff/autonomous-governor-summary.json` - `tests/results/_agent/handoff/autonomous-governor-portfolio-summary.json` + - `tests/results/_agent/handoff/sagan-context-concentrator.json` - `tests/results/_agent/handoff/continuity-summary.json` - `tests/results/_agent/handoff/entrypoint-status.json` - `tests/results/_agent/runtime/` diff --git a/AGENT_HANDOFF.txt b/AGENT_HANDOFF.txt index 39cf21ec9..eca6def34 100644 --- a/AGENT_HANDOFF.txt +++ b/AGENT_HANDOFF.txt @@ -9,8 +9,7 @@ Live repository state belongs in the machine-generated artifacts under ## First Actions 1. Run `pwsh -NoLogo -NoProfile -File tools/priority/bootstrap.ps1`. -2. Check `.agent_priority_cache.json` and - `tests/results/_agent/issue/router.json`. +2. Check `.agent_priority_cache.json` and `tests/results/_agent/issue/router.json`. 3. Run `pwsh -NoLogo -NoProfile -File tools/Print-AgentHandoff.ps1 -ApplyToggles -AutoTrim` when you need the current watcher/handoff snapshot. 4. When human disposition surfaces are in play, run @@ -25,7 +24,9 @@ Live repository state belongs in the machine-generated artifacts under `tests/results/_agent/handoff/autonomous-governor-summary.json` for the current repo owner decision and `tests/results/_agent/handoff/autonomous-governor-portfolio-summary.json` - for the cross-repo owner decision. + for the cross-repo owner decision, and + `tests/results/_agent/handoff/sagan-context-concentrator.json` for the compact + hot/warm durable memory view of subagent work and blockers. ## Live State Surfaces @@ -43,6 +44,8 @@ Live repository state belongs in the machine-generated artifacts under `node tools/npm/run-script.mjs priority:governor:summary` - Governor portfolio: `node tools/npm/run-script.mjs priority:governor:portfolio` +- Context concentrator: + `node tools/npm/run-script.mjs priority:context:concentrate` ## Current-State Artifacts @@ -54,6 +57,7 @@ Live repository state belongs in the machine-generated artifacts under - `tests/results/_agent/handoff/entrypoint-status.json` - `tests/results/_agent/handoff/monitoring-mode.json` - `tests/results/_agent/handoff/autonomous-governor-summary.json` +- `tests/results/_agent/handoff/sagan-context-concentrator.json` - `tests/results/_agent/handoff/autonomous-governor-portfolio-summary.json` - `tests/results/_agent/handoff/human-go-no-go-latest.json` - `tests/results/_agent/handoff/*.json` @@ -65,8 +69,7 @@ Live repository state belongs in the machine-generated artifacts under - The project board is visibility only. - Treat `queue-empty` as a valid idle state; do not invent a null issue context. - Treat `queue-empty + safe-idle + pivot-ready` as an explicit monitoring state. -- Prefer sanitized repo helpers (`node tools/npm/run-script.mjs ...`) over raw - `npm`. +- Prefer sanitized repo helpers (`node tools/npm/run-script.mjs ...`) over raw `npm`. ## When Handoff Looks Wrong diff --git a/docs/DEVELOPER_GUIDE.md b/docs/DEVELOPER_GUIDE.md index 323ce86da..534892d47 100644 --- a/docs/DEVELOPER_GUIDE.md +++ b/docs/DEVELOPER_GUIDE.md @@ -661,6 +661,9 @@ pwsh -File tools/Print-AgentHandoff.ps1 -ApplyToggles -AutoTrim `node tools/npm/run-script.mjs priority:handoff`, which now prints the entrypoint index alongside the standing-priority snapshot and other handoff summaries. +- Refresh the compact hot/warm durable memory view directly with + `node tools/npm/run-script.mjs priority:context:concentrate`, which writes + `tests/results/_agent/handoff/sagan-context-concentrator.json`. - The overall future-agent handoff contract is summarized in [`docs/knowledgebase/Agent-Handoff-Surfaces.md`](./knowledgebase/Agent-Handoff-Surfaces.md). - See [`WATCHER_TELEMETRY_DX.md`](./WATCHER_TELEMETRY_DX.md) for automation response expectations. diff --git a/docs/knowledgebase/Agent-Handoff-Surfaces.md b/docs/knowledgebase/Agent-Handoff-Surfaces.md index 4f4b59a61..b7078a2b8 100644 --- a/docs/knowledgebase/Agent-Handoff-Surfaces.md +++ b/docs/knowledgebase/Agent-Handoff-Surfaces.md @@ -25,6 +25,10 @@ entrypoint and machine-generated live state. which is the top-level machine-readable rollup for the autonomous governor's current mode, wake disposition, funding-quality posture, release-signing readiness, and next owner. +- It refreshes `tests/results/_agent/handoff/sagan-context-concentrator.json`, + which is the machine-readable hot/warm memory concentrator for the current + standing issue, current owner decision, recent subagent episodes, and durable + blocker context. - It refreshes `tests/results/_agent/handoff/autonomous-governor-portfolio-summary.json`, which is the cross-repo machine-readable rollup for compare, canonical @@ -44,6 +48,9 @@ entrypoint and machine-generated live state. - `node tools/npm/run-script.mjs priority:handoff` imports the handoff bundle and prints the entrypoint index, standing-priority snapshot, and other current summaries. +- `node tools/npm/run-script.mjs priority:context:concentrate` + rebuilds the compact context concentrator directly when you need the + synthesized hot/warm memory view without the full handoff bundle. - `node tools/npm/run-script.mjs priority:handoff-tests` exercises the contract lane used to keep these handoff surfaces from drifting. @@ -59,6 +66,7 @@ entrypoint and machine-generated live state. - `tests/results/_agent/handoff/entrypoint-status.json` - `tests/results/_agent/handoff/monitoring-mode.json` - `tests/results/_agent/handoff/autonomous-governor-summary.json` +- `tests/results/_agent/handoff/sagan-context-concentrator.json` - `tests/results/_agent/handoff/autonomous-governor-portfolio-summary.json` - `tests/results/_agent/handoff/downstream-repo-graph-truth.json` - `tests/results/_agent/release/release-signing-readiness.json` @@ -83,6 +91,9 @@ entrypoint and machine-generated live state. event-driven monitoring mode. - `tests/results/_agent/handoff/autonomous-governor-summary.json` is the top-level operating summary for the autonomous governor. +- `tests/results/_agent/handoff/sagan-context-concentrator.json` is the compact + durable memory view that keeps subagent findings, hot blockers, and current + owner decisions close to the active handoff surface. - `tests/results/_agent/handoff/autonomous-governor-portfolio-summary.json` is the cross-repo operating summary for compare, canonical template, and proving forks together. @@ -100,6 +111,8 @@ entrypoint and machine-generated live state. - funding-quality posture for the latest wake - release-signing readiness, including explicit external blockers when workflow signing material is absent + - concentrated subagent episode memory, including hot working set, + warm recent memory, archive count, and lower-bound blended spend - cross-repo owner and next-owner decisions - repo graph truth for producer lineage, canonical development, and consumer proving - wake conditions that should reopen compare or template work diff --git a/docs/schemas/delivery-agent-runtime-state-v1.schema.json b/docs/schemas/delivery-agent-runtime-state-v1.schema.json index f72b3aa9a..3f4aa961c 100644 --- a/docs/schemas/delivery-agent-runtime-state-v1.schema.json +++ b/docs/schemas/delivery-agent-runtime-state-v1.schema.json @@ -322,6 +322,34 @@ "requiresLocalCheckout": { "type": ["boolean", "null"] } } }, + "executionTopology": { + "type": ["object", "null"], + "additionalProperties": true, + "properties": { + "status": { "type": ["string", "null"] }, + "executionPlane": { "type": ["string", "null"] }, + "providerId": { "type": ["string", "null"] }, + "workerSlotId": { "type": ["string", "null"] }, + "cellId": { "type": ["string", "null"] }, + "laneId": { "type": ["string", "null"] }, + "cellClass": { "type": ["string", "null"] }, + "suiteClass": { "type": ["string", "null"] }, + "planeBinding": { "type": ["string", "null"] }, + "harnessKind": { "type": ["string", "null"] }, + "harnessInstanceId": { "type": ["string", "null"] }, + "executionCellLeaseId": { "type": ["string", "null"] }, + "dockerLaneLeaseId": { "type": ["string", "null"] }, + "premiumSaganMode": { "type": "boolean" }, + "reciprocalLinkReady": { "type": "boolean" }, + "operatorAuthorizationRef": { "type": ["string", "null"] }, + "activeLogicalLaneCount": { "type": ["integer", "null"], "minimum": 0 }, + "seededLogicalLaneCount": { "type": ["integer", "null"], "minimum": 0 }, + "runtimeSurface": { "type": ["string", "null"] }, + "processModelClass": { "type": ["string", "null"] }, + "windowsOnly": { "type": "boolean" }, + "requestedSimultaneous": { "type": "boolean" } + } + }, "providerDispatch": { "type": ["object", "null"], "additionalProperties": true, diff --git a/docs/schemas/sagan-context-concentrator-report-v1.schema.json b/docs/schemas/sagan-context-concentrator-report-v1.schema.json new file mode 100644 index 000000000..5abe1411d --- /dev/null +++ b/docs/schemas/sagan-context-concentrator-report-v1.schema.json @@ -0,0 +1,351 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://labview-community-ci-cd.github.io/compare-vi-cli-action/schemas/sagan-context-concentrator-report-v1.schema.json", + "title": "Sagan Context Concentrator Report v1", + "type": "object", + "additionalProperties": false, + "required": [ + "schema", + "generatedAt", + "repository", + "inputs", + "sources", + "focus", + "memory", + "episodes", + "cost", + "summary" + ], + "properties": { + "schema": { + "const": "priority/sagan-context-concentrator-report@v1" + }, + "generatedAt": { + "type": "string", + "format": "date-time" + }, + "repository": { + "type": ["string", "null"] + }, + "inputs": { + "type": "object", + "additionalProperties": false, + "required": [ + "priorityCachePath", + "governorSummaryPath", + "governorPortfolioSummaryPath", + "monitoringModePath", + "operatorSteeringEventPath", + "episodeDirectoryPath" + ], + "properties": { + "priorityCachePath": { "type": ["string", "null"] }, + "governorSummaryPath": { "type": ["string", "null"] }, + "governorPortfolioSummaryPath": { "type": ["string", "null"] }, + "monitoringModePath": { "type": ["string", "null"] }, + "operatorSteeringEventPath": { "type": ["string", "null"] }, + "episodeDirectoryPath": { "type": ["string", "null"] } + } + }, + "sources": { + "type": "object", + "additionalProperties": false, + "required": [ + "priorityCache", + "governorSummary", + "governorPortfolioSummary", + "monitoringMode", + "operatorSteeringEvent", + "episodeDirectory" + ], + "properties": { + "priorityCache": { "$ref": "#/$defs/sourcePathState" }, + "governorSummary": { "$ref": "#/$defs/sourcePathState" }, + "governorPortfolioSummary": { "$ref": "#/$defs/sourcePathState" }, + "monitoringMode": { "$ref": "#/$defs/sourcePathState" }, + "operatorSteeringEvent": { "$ref": "#/$defs/sourcePathState" }, + "episodeDirectory": { + "type": "object", + "additionalProperties": false, + "required": ["path", "exists", "fileCount", "validEpisodeCount", "invalidEpisodeCount"], + "properties": { + "path": { "type": ["string", "null"] }, + "exists": { "type": "boolean" }, + "fileCount": { "type": "integer", "minimum": 0 }, + "validEpisodeCount": { "type": "integer", "minimum": 0 }, + "invalidEpisodeCount": { "type": "integer", "minimum": 0 } + } + } + } + }, + "focus": { + "type": "object", + "additionalProperties": false, + "required": [ + "activeIssue", + "currentOwnerRepository", + "nextOwnerRepository", + "nextAction", + "governorMode", + "monitoringStatus" + ], + "properties": { + "activeIssue": { + "type": ["object", "null"], + "additionalProperties": false, + "required": ["number", "title", "url", "state", "repository"], + "properties": { + "number": { "type": "integer", "minimum": 1 }, + "title": { "type": ["string", "null"] }, + "url": { "type": ["string", "null"] }, + "state": { "type": ["string", "null"] }, + "repository": { "type": ["string", "null"] } + } + }, + "currentOwnerRepository": { "type": ["string", "null"] }, + "nextOwnerRepository": { "type": ["string", "null"] }, + "nextAction": { "type": ["string", "null"] }, + "governorMode": { "type": ["string", "null"] }, + "monitoringStatus": { "type": ["string", "null"] } + } + }, + "memory": { + "type": "object", + "additionalProperties": false, + "required": ["hotWorkingSet", "warmMemory", "archiveCount"], + "properties": { + "hotWorkingSet": { + "type": "array", + "items": { "$ref": "#/$defs/memoryItem" } + }, + "warmMemory": { + "type": "array", + "items": { "$ref": "#/$defs/memoryItem" } + }, + "archiveCount": { "type": "integer", "minimum": 0 } + } + }, + "episodes": { + "type": "object", + "additionalProperties": false, + "required": [ + "totalCount", + "validCount", + "invalidCount", + "invalidEpisodes", + "byStatus", + "byAgent", + "recent" + ], + "properties": { + "totalCount": { "type": "integer", "minimum": 0 }, + "validCount": { "type": "integer", "minimum": 0 }, + "invalidCount": { "type": "integer", "minimum": 0 }, + "invalidEpisodes": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["path", "error"], + "properties": { + "path": { "type": "string" }, + "error": { "type": "string" } + } + } + }, + "byStatus": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["status", "count"], + "properties": { + "status": { "type": "string" }, + "count": { "type": "integer", "minimum": 0 } + } + } + }, + "byAgent": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["agentId", "agentName", "count"], + "properties": { + "agentId": { "type": "string" }, + "agentName": { "type": ["string", "null"] }, + "count": { "type": "integer", "minimum": 0 } + } + } + }, + "recent": { + "type": "array", + "items": { "$ref": "#/$defs/episodeDigest" } + } + } + }, + "cost": { + "type": "object", + "additionalProperties": false, + "required": [ + "episodeCountWithCost", + "tokenUsd", + "operatorLaborUsd", + "blendedLowerBoundUsd", + "observedDurationSeconds" + ], + "properties": { + "episodeCountWithCost": { "type": "integer", "minimum": 0 }, + "tokenUsd": { "type": "number", "minimum": 0 }, + "operatorLaborUsd": { "type": "number", "minimum": 0 }, + "blendedLowerBoundUsd": { "type": "number", "minimum": 0 }, + "observedDurationSeconds": { "type": "number", "minimum": 0 } + } + }, + "summary": { + "type": "object", + "additionalProperties": false, + "required": [ + "status", + "concentrationStatus", + "currentOwnerRepository", + "nextOwnerRepository", + "nextAction", + "activeIssueNumber", + "hotWorkingSetCount", + "warmMemoryCount", + "archiveCount", + "blockerCount", + "recentEpisodeCount", + "blendedLowerBoundUsd" + ], + "properties": { + "status": { "type": "string", "enum": ["active", "monitoring"] }, + "concentrationStatus": { "type": "string", "enum": ["pass", "warn", "incomplete"] }, + "currentOwnerRepository": { "type": ["string", "null"] }, + "nextOwnerRepository": { "type": ["string", "null"] }, + "nextAction": { "type": ["string", "null"] }, + "activeIssueNumber": { "type": ["integer", "null"], "minimum": 1 }, + "hotWorkingSetCount": { "type": "integer", "minimum": 0 }, + "warmMemoryCount": { "type": "integer", "minimum": 0 }, + "archiveCount": { "type": "integer", "minimum": 0 }, + "blockerCount": { "type": "integer", "minimum": 0 }, + "recentEpisodeCount": { "type": "integer", "minimum": 0 }, + "blendedLowerBoundUsd": { "type": "number", "minimum": 0 } + } + } + }, + "$defs": { + "sourcePathState": { + "type": "object", + "additionalProperties": false, + "required": ["path", "exists"], + "properties": { + "path": { "type": ["string", "null"] }, + "exists": { "type": "boolean" } + } + }, + "memoryItem": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "kind", + "label", + "status", + "detail", + "executionPlane", + "cellId", + "dockerLaneId", + "harnessInstanceId", + "runtimeSurface", + "processModelClass", + "premiumSaganMode", + "executionOwnershipLabel", + "sourcePath", + "updatedAt", + "issueNumber", + "repository", + "agentName", + "nextAction" + ], + "properties": { + "id": { "type": ["string", "null"] }, + "kind": { "type": ["string", "null"] }, + "label": { "type": ["string", "null"] }, + "status": { "type": ["string", "null"] }, + "detail": { "type": ["string", "null"] }, + "executionPlane": { "type": ["string", "null"] }, + "cellId": { "type": ["string", "null"] }, + "dockerLaneId": { "type": ["string", "null"] }, + "harnessInstanceId": { "type": ["string", "null"] }, + "runtimeSurface": { "type": ["string", "null"] }, + "processModelClass": { "type": ["string", "null"] }, + "premiumSaganMode": { "type": ["boolean", "null"] }, + "executionOwnershipLabel": { "type": ["string", "null"] }, + "sourcePath": { "type": ["string", "null"] }, + "updatedAt": { "type": ["string", "null"] }, + "issueNumber": { "type": ["integer", "null"], "minimum": 1 }, + "repository": { "type": ["string", "null"] }, + "agentName": { "type": ["string", "null"] }, + "nextAction": { "type": ["string", "null"] } + } + }, + "episodeDigest": { + "type": "object", + "additionalProperties": false, + "required": [ + "episodeId", + "generatedAt", + "agentId", + "agentName", + "agentRole", + "status", + "taskSummary", + "nextAction", + "blocker", + "executionPlane", + "dockerLaneId", + "cellId", + "executionCellLeaseId", + "dockerLaneLeaseId", + "cellClass", + "suiteClass", + "harnessKind", + "harnessInstanceId", + "runtimeSurface", + "processModelClass", + "operatorAuthorizationRef", + "premiumSaganMode", + "executionOwnershipLabel", + "sourcePath" + ], + "properties": { + "episodeId": { "type": ["string", "null"] }, + "generatedAt": { "type": ["string", "null"] }, + "agentId": { "type": ["string", "null"] }, + "agentName": { "type": ["string", "null"] }, + "agentRole": { "type": ["string", "null"] }, + "status": { "type": ["string", "null"] }, + "taskSummary": { "type": ["string", "null"] }, + "nextAction": { "type": ["string", "null"] }, + "blocker": { "type": ["string", "null"] }, + "executionPlane": { "type": ["string", "null"] }, + "dockerLaneId": { "type": ["string", "null"] }, + "cellId": { "type": ["string", "null"] }, + "executionCellLeaseId": { "type": ["string", "null"] }, + "dockerLaneLeaseId": { "type": ["string", "null"] }, + "cellClass": { "type": ["string", "null"] }, + "suiteClass": { "type": ["string", "null"] }, + "harnessKind": { "type": ["string", "null"] }, + "harnessInstanceId": { "type": ["string", "null"] }, + "runtimeSurface": { "type": ["string", "null"] }, + "processModelClass": { "type": ["string", "null"] }, + "operatorAuthorizationRef": { "type": ["string", "null"] }, + "premiumSaganMode": { "type": ["boolean", "null"] }, + "executionOwnershipLabel": { "type": ["string", "null"] }, + "sourcePath": { "type": ["string", "null"] } + } + } + } +} diff --git a/docs/schemas/subagent-episode-report-v1.schema.json b/docs/schemas/subagent-episode-report-v1.schema.json new file mode 100644 index 000000000..4140f7e7b --- /dev/null +++ b/docs/schemas/subagent-episode-report-v1.schema.json @@ -0,0 +1,150 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://labview-community-ci-cd.github.io/compare-vi-cli-action/schemas/subagent-episode-report-v1.schema.json", + "title": "Subagent Episode Report v1", + "type": "object", + "additionalProperties": false, + "required": [ + "schema", + "generatedAt", + "repository", + "inputs", + "episodeId", + "agent", + "task", + "execution", + "summary", + "evidence", + "cost" + ], + "properties": { + "schema": { + "const": "priority/subagent-episode-report@v1" + }, + "generatedAt": { + "type": "string", + "format": "date-time" + }, + "repository": { + "type": ["string", "null"] + }, + "inputs": { + "type": "object", + "additionalProperties": false, + "required": ["sourcePath"], + "properties": { + "sourcePath": { + "type": ["string", "null"] + } + } + }, + "episodeId": { + "type": "string", + "minLength": 1 + }, + "agent": { + "type": "object", + "additionalProperties": false, + "required": ["id", "name", "role", "model"], + "properties": { + "id": { "type": ["string", "null"] }, + "name": { "type": ["string", "null"] }, + "role": { "type": ["string", "null"] }, + "model": { "type": ["string", "null"] } + } + }, + "task": { + "type": "object", + "additionalProperties": false, + "required": ["summary", "class", "issueNumber", "issueUrl"], + "properties": { + "summary": { "type": "string", "minLength": 1 }, + "class": { "type": ["string", "null"] }, + "issueNumber": { "type": ["integer", "null"], "minimum": 1 }, + "issueUrl": { "type": ["string", "null"] } + } + }, + "execution": { + "type": "object", + "additionalProperties": false, + "required": [ + "status", + "lane", + "branch", + "executionPlane", + "dockerLaneId", + "hostCapabilityLeaseId", + "cellId", + "executionCellLeaseId", + "dockerLaneLeaseId", + "cellClass", + "suiteClass", + "harnessKind", + "harnessInstanceId", + "runtimeSurface", + "processModelClass", + "operatorAuthorizationRef", + "premiumSaganMode" + ], + "properties": { + "status": { "type": "string", "minLength": 1 }, + "lane": { "type": ["string", "null"] }, + "branch": { "type": ["string", "null"] }, + "executionPlane": { "type": ["string", "null"] }, + "dockerLaneId": { "type": ["string", "null"] }, + "hostCapabilityLeaseId": { "type": ["string", "null"] }, + "cellId": { "type": ["string", "null"] }, + "executionCellLeaseId": { "type": ["string", "null"] }, + "dockerLaneLeaseId": { "type": ["string", "null"] }, + "cellClass": { "type": ["string", "null"] }, + "suiteClass": { "type": ["string", "null"] }, + "harnessKind": { "type": ["string", "null"] }, + "harnessInstanceId": { "type": ["string", "null"] }, + "runtimeSurface": { "type": ["string", "null"] }, + "processModelClass": { "type": ["string", "null"] }, + "operatorAuthorizationRef": { "type": ["string", "null"] }, + "premiumSaganMode": { "type": ["boolean", "null"] } + } + }, + "summary": { + "type": "object", + "additionalProperties": false, + "required": ["status", "outcome", "blocker", "nextAction", "detail"], + "properties": { + "status": { "type": "string", "minLength": 1 }, + "outcome": { "type": ["string", "null"] }, + "blocker": { "type": ["string", "null"] }, + "nextAction": { "type": ["string", "null"] }, + "detail": { "type": ["string", "null"] } + } + }, + "evidence": { + "type": "object", + "additionalProperties": false, + "required": ["filesTouched", "receipts", "commands", "notes"], + "properties": { + "filesTouched": { "$ref": "#/$defs/stringArray" }, + "receipts": { "$ref": "#/$defs/stringArray" }, + "commands": { "$ref": "#/$defs/stringArray" }, + "notes": { "$ref": "#/$defs/stringArray" } + } + }, + "cost": { + "type": "object", + "additionalProperties": false, + "required": ["observedDurationSeconds", "tokenUsd", "operatorLaborUsd", "blendedLowerBoundUsd"], + "properties": { + "observedDurationSeconds": { "type": ["number", "null"] }, + "tokenUsd": { "type": ["number", "null"] }, + "operatorLaborUsd": { "type": ["number", "null"] }, + "blendedLowerBoundUsd": { "type": ["number", "null"] } + } + } + }, + "$defs": { + "stringArray": { + "type": "array", + "items": { "type": "string" } + } + } +} diff --git a/package.json b/package.json index e73cdeac5..60ca3055f 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,8 @@ "priority:monitoring:mode": "node tools/priority/handoff-monitoring-mode.mjs", "priority:governor:summary": "node tools/priority/autonomous-governor-summary.mjs", "priority:governor:portfolio": "node tools/priority/autonomous-governor-portfolio-summary.mjs", + "priority:subagent:episode": "node tools/priority/subagent-episode.mjs", + "priority:context:concentrate": "node tools/priority/sagan-context-concentrator.mjs", "priority:monitoring:inject-work": "node tools/priority/monitoring-work-injection.mjs", "priority:wake:adjudicate": "node tools/priority/wake-adjudication.mjs", "priority:wake:accounting": "node tools/priority/wake-investment-accounting.mjs", diff --git a/tests/Import-HandoffState.Tests.ps1 b/tests/Import-HandoffState.Tests.ps1 index 16306ef62..84573b8a0 100644 --- a/tests/Import-HandoffState.Tests.ps1 +++ b/tests/Import-HandoffState.Tests.ps1 @@ -421,4 +421,141 @@ Describe 'Import-HandoffState' -Tag 'Unit' { Remove-Variable -Name HandoffAutonomousGovernorPortfolioSummary -Scope Global -ErrorAction SilentlyContinue } + + It 'surfaces context concentrator summary when present' { + $repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path + $scriptPath = Join-Path $repoRoot 'tools' 'priority' 'Import-HandoffState.ps1' + $handoffDir = Join-Path $TestDrive 'handoff' + New-Item -ItemType Directory -Force -Path $handoffDir | Out-Null + + [ordered]@{ + schema = 'priority/sagan-context-concentrator-report@v1' + generatedAt = '2026-03-23T23:25:00Z' + repository = 'LabVIEW-Community-CI-CD/compare-vi-cli-action' + inputs = [ordered]@{ + priorityCachePath = '.agent_priority_cache.json' + governorSummaryPath = 'tests/results/_agent/handoff/autonomous-governor-summary.json' + governorPortfolioSummaryPath = 'tests/results/_agent/handoff/autonomous-governor-portfolio-summary.json' + monitoringModePath = 'tests/results/_agent/handoff/monitoring-mode.json' + operatorSteeringEventPath = 'tests/results/_agent/handoff/operator-steering-event.json' + episodeDirectoryPath = 'tests/results/_agent/memory/subagent-episodes' + } + sources = [ordered]@{ + priorityCache = [ordered]@{ path = '.agent_priority_cache.json'; exists = $true } + governorSummary = [ordered]@{ path = 'tests/results/_agent/handoff/autonomous-governor-summary.json'; exists = $true } + governorPortfolioSummary = [ordered]@{ path = 'tests/results/_agent/handoff/autonomous-governor-portfolio-summary.json'; exists = $true } + monitoringMode = [ordered]@{ path = 'tests/results/_agent/handoff/monitoring-mode.json'; exists = $true } + operatorSteeringEvent = [ordered]@{ path = 'tests/results/_agent/handoff/operator-steering-event.json'; exists = $false } + episodeDirectory = [ordered]@{ + path = 'tests/results/_agent/memory/subagent-episodes' + exists = $true + fileCount = 2 + validEpisodeCount = 2 + invalidEpisodeCount = 0 + } + } + focus = [ordered]@{ + activeIssue = [ordered]@{ + number = 1909 + title = '[governor]: build Sagan context concentrator for durable subagent memory' + url = 'https://github.com/LabVIEW-Community-CI-CD/compare-vi-cli-action/issues/1909' + state = 'OPEN' + repository = 'LabVIEW-Community-CI-CD/compare-vi-cli-action' + } + currentOwnerRepository = 'LabVIEW-Community-CI-CD/compare-vi-cli-action' + nextOwnerRepository = 'LabVIEW-Community-CI-CD/compare-vi-cli-action' + nextAction = 'merge concentrator handoff support' + governorMode = 'compare-governance-work' + monitoringStatus = 'active' + } + memory = [ordered]@{ + hotWorkingSet = @( + [ordered]@{ + id = 'issue-1909' + kind = 'active-issue' + label = '#1909: [governor]: build Sagan context concentrator for durable subagent memory' + status = 'OPEN' + detail = 'Current standing-priority objective' + executionPlane = $null + cellId = $null + dockerLaneId = $null + harnessInstanceId = $null + runtimeSurface = $null + processModelClass = $null + premiumSaganMode = $null + executionOwnershipLabel = $null + sourcePath = '.agent_priority_cache.json' + updatedAt = '2026-03-23T23:24:00Z' + issueNumber = 1909 + repository = 'LabVIEW-Community-CI-CD/compare-vi-cli-action' + agentName = $null + nextAction = 'merge concentrator handoff support' + }, + [ordered]@{ + id = 'episode-euler-1' + kind = 'subagent-episode' + label = 'Euler: Inspect concentrator seams' + status = 'reported' + detail = 'cell cell-euler-001 / docker docker-euler-001 / harness ts-euler-001 / windows-native-teststand / sequential' + executionPlane = 'windows-host' + cellId = 'cell-euler-001' + dockerLaneId = 'docker-euler-001' + harnessInstanceId = 'ts-euler-001' + runtimeSurface = 'windows-native-teststand' + processModelClass = 'sequential' + premiumSaganMode = $false + executionOwnershipLabel = 'cell cell-euler-001 / docker docker-euler-001 / harness ts-euler-001 / windows-native-teststand / sequential' + sourcePath = 'tests/results/_agent/memory/subagent-episodes/euler.json' + updatedAt = '2026-03-23T23:23:00Z' + issueNumber = 1909 + repository = 'LabVIEW-Community-CI-CD/compare-vi-cli-action' + agentName = 'Euler' + nextAction = 'merge concentrator handoff support' + } + ) + warmMemory = @() + archiveCount = 1 + } + episodes = [ordered]@{ + totalCount = 2 + validCount = 2 + invalidCount = 0 + invalidEpisodes = @() + byStatus = @([ordered]@{ status = 'reported'; count = 2 }) + byAgent = @([ordered]@{ agentId = 'euler-id'; agentName = 'Euler'; count = 1 }) + recent = @() + } + cost = [ordered]@{ + episodeCountWithCost = 2 + tokenUsd = 0.12 + operatorLaborUsd = 10.416667 + blendedLowerBoundUsd = 10.536667 + observedDurationSeconds = 150 + } + summary = [ordered]@{ + status = 'active' + concentrationStatus = 'pass' + currentOwnerRepository = 'LabVIEW-Community-CI-CD/compare-vi-cli-action' + nextOwnerRepository = 'LabVIEW-Community-CI-CD/compare-vi-cli-action' + nextAction = 'merge concentrator handoff support' + activeIssueNumber = 1909 + hotWorkingSetCount = 2 + warmMemoryCount = 0 + archiveCount = 1 + blockerCount = 0 + recentEpisodeCount = 2 + blendedLowerBoundUsd = 10.536667 + } + } | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Join-Path $handoffDir 'sagan-context-concentrator.json') -Encoding utf8 + + $output = & $scriptPath -HandoffDir $handoffDir *>&1 | Out-String + + $output | Should -Match '\[handoff\] Context concentrator' + $output | Should -Match 'issue\s+: #1909' + $output | Should -Match 'hot/warm\s+: 2/0' + $output | Should -Match 'Euler: Inspect concentrator seams \[reported\] :: cell cell-euler-001 / docker docker-euler-001 / harness ts-euler-001 / windows-native-teststand / sequential' + $global:HandoffContextConcentrator.schema | Should -Be 'priority/sagan-context-concentrator-report@v1' + + Remove-Variable -Name HandoffContextConcentrator -Scope Global -ErrorAction SilentlyContinue + } } diff --git a/tools/Print-AgentHandoff.ps1 b/tools/Print-AgentHandoff.ps1 index b23483597..52e873565 100644 --- a/tools/Print-AgentHandoff.ps1 +++ b/tools/Print-AgentHandoff.ps1 @@ -1297,6 +1297,7 @@ try { $releaseSigningReadinessScript = Join-Path $repoRoot 'tools' 'priority' 'release-signing-readiness.mjs' $governorSummaryScript = Join-Path $repoRoot 'tools' 'priority' 'autonomous-governor-summary.mjs' $governorPortfolioSummaryScript = Join-Path $repoRoot 'tools' 'priority' 'autonomous-governor-portfolio-summary.mjs' + $contextConcentratorScript = Join-Path $repoRoot 'tools' 'priority' 'sagan-context-concentrator.mjs' $nodeCmd = Get-Command node -ErrorAction SilentlyContinue if ($nodeCmd) { $promotionDir = Join-Path $ResultsRoot '_agent/promotion' @@ -1320,6 +1321,7 @@ try { $monitoringModePath = Join-Path $handoffDir 'monitoring-mode.json' $governorSummaryPath = Join-Path $handoffDir 'autonomous-governor-summary.json' $governorPortfolioSummaryPath = Join-Path $handoffDir 'autonomous-governor-portfolio-summary.json' + $contextConcentratorPath = Join-Path $handoffDir 'sagan-context-concentrator.json' if (Test-Path -LiteralPath $repoGraphTruthScript -PathType Leaf) { & $nodeCmd.Source $repoGraphTruthScript ` @@ -1385,6 +1387,18 @@ try { --repo-graph-truth $repoGraphTruthPath ` --output $governorPortfolioSummaryPath | Out-Host } + + if (Test-Path -LiteralPath $contextConcentratorScript -PathType Leaf) { + & $nodeCmd.Source $contextConcentratorScript ` + --repo-root $repoRoot ` + --priority-cache (Join-Path $repoRoot '.agent_priority_cache.json') ` + --governor-summary $governorSummaryPath ` + --governor-portfolio-summary $governorPortfolioSummaryPath ` + --monitoring-mode $monitoringModePath ` + --operator-steering-event (Join-Path $handoffDir 'operator-steering-event.json') ` + --episode-directory (Join-Path $ResultsRoot '_agent/memory/subagent-episodes') ` + --output $contextConcentratorPath | Out-Host + } } } catch { Write-Warning ("Failed to refresh monitoring-mode handoff state: {0}" -f $_.Exception.Message) @@ -1828,6 +1842,58 @@ try { Write-Warning ("Failed to display governor portfolio summary: {0}" -f $_.Exception.Message) } +try { + $contextConcentratorPath = Join-Path $ResultsRoot '_agent/handoff/sagan-context-concentrator.json' + if (Test-Path -LiteralPath $contextConcentratorPath -PathType Leaf) { + $concentrator = Get-Content -LiteralPath $contextConcentratorPath -Raw | ConvertFrom-Json -ErrorAction Stop + Write-Host '' + Write-Host '[Context Concentrator]' -ForegroundColor Cyan + Write-Host (" status : {0}" -f (Format-NullableValue $concentrator.summary.concentrationStatus)) + if ($concentrator.summary.activeIssueNumber) { + Write-Host (" issue : #{0}" -f (Format-NullableValue $concentrator.summary.activeIssueNumber)) + } + Write-Host (" owner : {0}" -f (Format-NullableValue $concentrator.summary.currentOwnerRepository)) + Write-Host (" next : {0}" -f (Format-NullableValue $concentrator.summary.nextAction)) + Write-Host (" hot/warm : {0}/{1}" -f (Format-NullableValue $concentrator.summary.hotWorkingSetCount), (Format-NullableValue $concentrator.summary.warmMemoryCount)) + Write-Host (" archive : {0}" -f (Format-NullableValue $concentrator.summary.archiveCount)) + Write-Host (" blockers : {0}" -f (Format-NullableValue $concentrator.summary.blockerCount)) + Write-Host (' spend : ${0}' -f (Format-NullableValue $concentrator.summary.blendedLowerBoundUsd)) + foreach ($entry in @($concentrator.memory.hotWorkingSet | Select-Object -First 3)) { + $ownershipLabel = if ($entry.PSObject.Properties['executionOwnershipLabel']) { + $entry.executionOwnershipLabel + } else { + $null + } + if ($ownershipLabel) { + Write-Host (" - {0} [{1}] :: {2}" -f (Format-NullableValue $entry.label), (Format-NullableValue $entry.status), (Format-NullableValue $ownershipLabel)) + } else { + Write-Host (" - {0} [{1}]" -f (Format-NullableValue $entry.label), (Format-NullableValue $entry.status)) + } + } + if ($env:GITHUB_STEP_SUMMARY) { + $activeIssueLabel = if ($concentrator.summary.activeIssueNumber) { + "#$($concentrator.summary.activeIssueNumber)" + } else { + 'n/a' + } + $contextLines = @( + '### Context Concentrator', + '', + ('- Status: {0}' -f (Format-NullableValue $concentrator.summary.concentrationStatus)), + ('- Active issue: {0}' -f $activeIssueLabel), + ('- Current owner: {0}' -f (Format-NullableValue $concentrator.summary.currentOwnerRepository)), + ('- Next action: {0}' -f (Format-NullableValue $concentrator.summary.nextAction)), + ('- Hot/warm/archive: {0}/{1}/{2}' -f (Format-NullableValue $concentrator.summary.hotWorkingSetCount), (Format-NullableValue $concentrator.summary.warmMemoryCount), (Format-NullableValue $concentrator.summary.archiveCount)), + ('- Blockers: {0}' -f (Format-NullableValue $concentrator.summary.blockerCount)), + ('- Blended lower-bound spend: ${0}' -f (Format-NullableValue $concentrator.summary.blendedLowerBoundUsd)) + ) + ($contextLines -join "`n") | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8 + } + } +} catch { + Write-Warning ("Failed to display context concentrator summary: {0}" -f $_.Exception.Message) +} + try { $steeringPath = Join-Path $ResultsRoot '_agent/handoff/operator-steering-event.json' if (Test-Path -LiteralPath $steeringPath -PathType Leaf) { diff --git a/tools/Test-AgentHandoffEntryPoint.ps1 b/tools/Test-AgentHandoffEntryPoint.ps1 index 92cca6b9a..78e772c6f 100644 --- a/tools/Test-AgentHandoffEntryPoint.ps1 +++ b/tools/Test-AgentHandoffEntryPoint.ps1 @@ -37,6 +37,7 @@ $requiredArtifacts = @( 'tests/results/_agent/handoff/continuity-summary.json', 'tests/results/_agent/handoff/entrypoint-status.json', 'tests/results/_agent/handoff/monitoring-mode.json', + 'tests/results/_agent/handoff/sagan-context-concentrator.json', 'tests/results/_agent/handoff/autonomous-governor-portfolio-summary.json', 'tests/results/_agent/handoff/*.json', 'tests/results/_agent/sessions/*.json' @@ -51,6 +52,7 @@ $commandCatalog = [ordered]@{ monitoringMode = 'node tools/npm/run-script.mjs priority:monitoring:mode' governorSummary = 'node tools/npm/run-script.mjs priority:governor:summary' governorPortfolio = 'node tools/npm/run-script.mjs priority:governor:portfolio' + contextConcentrator = 'node tools/npm/run-script.mjs priority:context:concentrate' } $artifactCatalog = [ordered]@{ @@ -62,6 +64,7 @@ $artifactCatalog = [ordered]@{ entrypointStatus = 'tests/results/_agent/handoff/entrypoint-status.json' monitoringMode = 'tests/results/_agent/handoff/monitoring-mode.json' autonomousGovernorSummary = 'tests/results/_agent/handoff/autonomous-governor-summary.json' + saganContextConcentrator = 'tests/results/_agent/handoff/sagan-context-concentrator.json' autonomousGovernorPortfolioSummary = 'tests/results/_agent/handoff/autonomous-governor-portfolio-summary.json' handoffGlob = 'tests/results/_agent/handoff/*.json' sessionGlob = 'tests/results/_agent/sessions/*.json' diff --git a/tools/priority/Import-HandoffState.ps1 b/tools/priority/Import-HandoffState.ps1 index b9ebcc28e..60f4a4e5d 100644 --- a/tools/priority/Import-HandoffState.ps1 +++ b/tools/priority/Import-HandoffState.ps1 @@ -46,6 +46,7 @@ $continuitySummary = Read-HandoffJson -Name 'continuity-summary.json' $monitoringMode = Read-HandoffJson -Name 'monitoring-mode.json' $governorSummary = Read-HandoffJson -Name 'autonomous-governor-summary.json' $governorPortfolioSummary = Read-HandoffJson -Name 'autonomous-governor-portfolio-summary.json' +$contextConcentrator = Read-HandoffJson -Name 'sagan-context-concentrator.json' $operatorSteeringEvent = Read-HandoffJson -Name 'operator-steering-event.json' if ($issueSummary) { @@ -344,6 +345,32 @@ if ($governorPortfolioSummary) { } Set-Variable -Name HandoffAutonomousGovernorPortfolioSummary -Scope Global -Value $governorPortfolioSummary -Force } +if ($contextConcentrator) { + Write-Host '[handoff] Context concentrator' -ForegroundColor Cyan + Write-Host (" status : {0}" -f (Format-NullableValue $contextConcentrator.summary.concentrationStatus)) + if ($contextConcentrator.summary.activeIssueNumber) { + Write-Host (" issue : #{0}" -f (Format-NullableValue $contextConcentrator.summary.activeIssueNumber)) + } + Write-Host (" owner : {0}" -f (Format-NullableValue $contextConcentrator.summary.currentOwnerRepository)) + Write-Host (" next : {0}" -f (Format-NullableValue $contextConcentrator.summary.nextAction)) + Write-Host (" hot/warm : {0}/{1}" -f (Format-NullableValue $contextConcentrator.summary.hotWorkingSetCount), (Format-NullableValue $contextConcentrator.summary.warmMemoryCount)) + Write-Host (" archive : {0}" -f (Format-NullableValue $contextConcentrator.summary.archiveCount)) + Write-Host (" blockers : {0}" -f (Format-NullableValue $contextConcentrator.summary.blockerCount)) + Write-Host (' spend : ${0}' -f (Format-NullableValue $contextConcentrator.summary.blendedLowerBoundUsd)) + foreach ($entry in @($contextConcentrator.memory.hotWorkingSet | Select-Object -First 3)) { + $ownershipLabel = if ($entry.PSObject.Properties['executionOwnershipLabel']) { + $entry.executionOwnershipLabel + } else { + $null + } + if ($ownershipLabel) { + Write-Host (" - {0} [{1}] :: {2}" -f (Format-NullableValue $entry.label), (Format-NullableValue $entry.status), (Format-NullableValue $ownershipLabel)) + } else { + Write-Host (" - {0} [{1}]" -f (Format-NullableValue $entry.label), (Format-NullableValue $entry.status)) + } + } + Set-Variable -Name HandoffContextConcentrator -Scope Global -Value $contextConcentrator -Force +} if ($operatorSteeringEvent) { Write-Host '[handoff] Operator steering event' -ForegroundColor Cyan Write-Host (" steering : {0}" -f (Format-NullableValue $operatorSteeringEvent.steeringKind)) diff --git a/tools/priority/__tests__/autonomous-governor-summary.test.mjs b/tools/priority/__tests__/autonomous-governor-summary.test.mjs index 1b07b79b8..0fc0c5a62 100644 --- a/tools/priority/__tests__/autonomous-governor-summary.test.mjs +++ b/tools/priority/__tests__/autonomous-governor-summary.test.mjs @@ -227,6 +227,31 @@ function createDeliveryRuntimeState(overrides = {}) { completionStatus: 'waiting', failureClass: null }, + executionTopology: { + status: 'bundle-committed', + executionPlane: 'hosted', + providerId: 'hosted-github-workflow', + workerSlotId: 'worker-slot-2', + activeLogicalLaneCount: 2, + seededLogicalLaneCount: 4, + catalogCount: 4, + runtimeSurface: 'windows-native-teststand', + processModelClass: 'parallel-process-model', + windowsOnly: true, + requestedSimultaneous: true, + cellClass: 'kernel-coordinator', + suiteClass: 'dual-plane-parity', + operatorAuthorizationRef: 'budget-auth://operator/session-2026-03-24', + premiumSaganMode: true, + reciprocalLinkReady: true, + executionCellLeaseId: 'exec-lease-123', + dockerLaneLeaseId: 'docker-lease-456', + harnessKind: 'teststand-compare-harness', + harnessInstanceId: 'ts-harness-01', + cellId: 'cell-sagan-kernel', + laneId: 'docker-lane-01', + planeBinding: 'dual-plane-parity' + }, concurrentLaneStatus: { executionBundle: { status: 'committed', @@ -507,6 +532,120 @@ test('runAutonomousGovernorSummary carries queue-owned delivery runtime state in assert.equal(report.summary.queueAuthoritySource, 'delivery-runtime'); }); +test('runAutonomousGovernorSummary prefers concentrated delivery execution topology over raw bundle-derived conflicts', async () => { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'governor-summary-execution-topology-preference-')); + writeJson(path.join(tmpDir, 'tests', 'results', '_agent', 'issue', 'no-standing-priority.json'), createQueueEmpty()); + writeJson(path.join(tmpDir, 'tests', 'results', '_agent', 'handoff', 'continuity-summary.json'), createContinuitySummary()); + writeJson(path.join(tmpDir, 'tests', 'results', '_agent', 'handoff', 'monitoring-mode.json'), createMonitoringMode()); + writeJson(path.join(tmpDir, 'tests', 'results', '_agent', 'issue', 'wake-lifecycle.json'), createWakeLifecycle()); + writeJson( + path.join(tmpDir, 'tests', 'results', '_agent', 'capital', 'wake-investment-accounting.json'), + createWakeInvestmentAccounting() + ); + writeJson( + path.join(tmpDir, 'tests', 'results', '_agent', 'runtime', 'delivery-agent-state.json'), + createDeliveryRuntimeState({ + activeLane: { + issue: 1863, + prUrl: 'https://github.com/LabVIEW-Community-CI-CD/compare-vi-cli-action/pull/1864', + laneLifecycle: 'waiting-ci', + actionType: 'merge-pr', + outcome: 'waiting-ci', + blockerClass: 'none', + nextWakeCondition: 'checks-green', + reason: 'Waiting for hosted checks to finish before merge queue advances.', + providerDispatch: { + providerId: 'hosted-github-workflow', + providerKind: 'hosted-github-workflow', + executionPlane: 'hosted', + assignmentMode: 'async-validation', + dispatchSurface: 'github-actions', + completionMode: 'async', + workerSlotId: 'worker-slot-2', + dispatchStatus: 'completed', + completionStatus: 'waiting', + failureClass: null + }, + executionTopology: { + status: 'provider-waiting', + executionPlane: 'local', + providerId: 'local-codex', + workerSlotId: 'worker-slot-9', + activeLogicalLaneCount: 1, + seededLogicalLaneCount: 2, + catalogCount: 2, + runtimeSurface: 'windows-native-teststand', + processModelClass: 'sequential-process-model', + windowsOnly: true, + requestedSimultaneous: false, + cellClass: 'worker-cell', + suiteClass: 'single-plane-review', + operatorAuthorizationRef: 'budget-auth://operator/session-override', + premiumSaganMode: false, + reciprocalLinkReady: false, + executionCellLeaseId: 'exec-lease-override', + dockerLaneLeaseId: 'docker-lease-override', + harnessKind: 'teststand-compare-harness', + harnessInstanceId: 'ts-harness-override', + cellId: 'cell-worker-09', + laneId: 'docker-lane-09', + planeBinding: 'native-labview-2026-64' + }, + concurrentLaneStatus: { + executionBundle: { + status: 'committed', + planeBinding: 'dual-plane-parity', + cellClass: 'kernel-coordinator', + suiteClass: 'dual-plane-parity', + harnessKind: 'teststand-compare-harness', + premiumSaganMode: true, + reciprocalLinkReady: true, + effectiveBillableRateUsdPerHour: 375, + executionCellLeaseId: 'exec-lease-123', + dockerLaneLeaseId: 'docker-lease-456', + harnessInstanceId: 'ts-harness-01', + operatorAuthorizationRef: 'budget-auth://operator/session-2026-03-24', + cellId: 'cell-sagan-kernel', + laneId: 'docker-lane-01' + } + } + } + }) + ); + + const { report } = await runAutonomousGovernorSummary({ repoRoot: tmpDir }); + + assert.equal(report.compare.deliveryRuntime.executionTopology.status, 'provider-waiting'); + assert.equal(report.compare.deliveryRuntime.executionTopology.executionPlane, 'local'); + assert.equal(report.compare.deliveryRuntime.executionTopology.providerId, 'local-codex'); + assert.equal(report.compare.deliveryRuntime.executionTopology.workerSlotId, 'worker-slot-9'); + assert.equal(report.compare.deliveryRuntime.executionTopology.activeLogicalLaneCount, 1); + assert.equal(report.compare.deliveryRuntime.executionTopology.seededLogicalLaneCount, 2); + assert.equal(report.compare.deliveryRuntime.executionTopology.catalogCount, 2); + assert.equal(report.compare.deliveryRuntime.executionTopology.processModelClass, 'sequential-process-model'); + assert.equal(report.compare.deliveryRuntime.executionTopology.requestedSimultaneous, false); + assert.equal(report.compare.deliveryRuntime.executionTopology.cellClass, 'worker-cell'); + assert.equal(report.compare.deliveryRuntime.executionTopology.suiteClass, 'single-plane-review'); + assert.equal( + report.compare.deliveryRuntime.executionTopology.operatorAuthorizationRef, + 'budget-auth://operator/session-override' + ); + assert.equal(report.compare.deliveryRuntime.executionTopology.providerDispatch.dispatchStatus, 'completed'); + assert.equal(report.compare.deliveryRuntime.executionTopology.executionBundle.status, 'committed'); + assert.equal(report.compare.deliveryRuntime.executionBundle.status, 'committed'); + assert.equal(report.summary.executionTopologyStatus, 'provider-waiting'); + assert.equal(report.summary.executionTopologyExecutionPlane, 'local'); + assert.equal(report.summary.executionTopologyProviderId, 'local-codex'); + assert.equal(report.summary.executionTopologyWorkerSlotId, 'worker-slot-9'); + assert.equal(report.summary.executionTopologyActiveLogicalLaneCount, 1); + assert.equal(report.summary.executionTopologySeededLogicalLaneCount, 2); + assert.equal(report.summary.executionTopologyProcessModelClass, 'sequential-process-model'); + assert.equal(report.summary.executionTopologyRequestedSimultaneous, false); + assert.equal(report.summary.executionTopologyCellClass, 'worker-cell'); + assert.equal(report.summary.executionTopologySuiteClass, 'single-plane-review'); + assert.equal(report.summary.executionTopologyOperatorAuthorizationRef, 'budget-auth://operator/session-override'); +}); + test('runAutonomousGovernorSummary exposes queue authority refresh telemetry from delivery runtime state', async () => { const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'governor-summary-runtime-refresh-')); writeJson(path.join(tmpDir, 'tests', 'results', '_agent', 'issue', 'no-standing-priority.json'), createQueueEmpty()); diff --git a/tools/priority/__tests__/delivery-agent-schema.test.mjs b/tools/priority/__tests__/delivery-agent-schema.test.mjs index 2eabfdf28..64bd4d633 100644 --- a/tools/priority/__tests__/delivery-agent-schema.test.mjs +++ b/tools/priority/__tests__/delivery-agent-schema.test.mjs @@ -993,6 +993,26 @@ test('delivery-agent runtime state schema validates persisted runtime state', as assert.equal(state.activeLane.workerProviderSelection.selectedProviderId, 'hosted-github-workflow'); assert.equal(state.activeLane.providerDispatch.providerId, 'hosted-github-workflow'); assert.equal(state.activeLane.providerDispatch.workerSlotId, 'worker-slot-2'); + assert.equal(state.activeLane.executionTopology.status, 'bundle-committed'); + assert.equal(state.activeLane.executionTopology.executionPlane, 'hosted'); + assert.equal(state.activeLane.executionTopology.providerId, 'hosted-github-workflow'); + assert.equal(state.activeLane.executionTopology.workerSlotId, 'worker-slot-2'); + assert.equal(state.activeLane.executionTopology.cellId, 'cell-sagan-kernel'); + assert.equal(state.activeLane.executionTopology.laneId, 'docker-lane-01'); + assert.equal(state.activeLane.executionTopology.cellClass, 'kernel-coordinator'); + assert.equal(state.activeLane.executionTopology.suiteClass, 'dual-plane-parity'); + assert.equal(state.activeLane.executionTopology.planeBinding, 'dual-plane-parity'); + assert.equal(state.activeLane.executionTopology.harnessKind, 'teststand-compare-harness'); + assert.equal(state.activeLane.executionTopology.harnessInstanceId, 'ts-harness-01'); + assert.equal(state.activeLane.executionTopology.executionCellLeaseId, 'exec-lease-123'); + assert.equal(state.activeLane.executionTopology.dockerLaneLeaseId, 'docker-lease-456'); + assert.equal(state.activeLane.executionTopology.premiumSaganMode, true); + assert.equal(state.activeLane.executionTopology.reciprocalLinkReady, true); + assert.equal(state.activeLane.executionTopology.operatorAuthorizationRef, 'budget-auth://operator/session-2026-03-24'); + assert.equal(state.activeLane.executionTopology.runtimeSurface, 'windows-native-teststand'); + assert.equal(state.activeLane.executionTopology.processModelClass, 'parallel-process-model'); + assert.equal(state.activeLane.executionTopology.windowsOnly, true); + assert.equal(state.activeLane.executionTopology.requestedSimultaneous, true); assert.equal(state.activeLane.concurrentLaneStatus.executionBundle.status, 'committed'); assert.equal(state.activeLane.concurrentLaneStatus.executionBundle.planeBinding, 'dual-plane-parity'); assert.equal(state.activeLane.concurrentLaneStatus.executionBundle.cellClass, 'kernel-coordinator'); diff --git a/tools/priority/__tests__/handoff-entrypoint-contract.test.mjs b/tools/priority/__tests__/handoff-entrypoint-contract.test.mjs index 2bb352033..984b6b74b 100644 --- a/tools/priority/__tests__/handoff-entrypoint-contract.test.mjs +++ b/tools/priority/__tests__/handoff-entrypoint-contract.test.mjs @@ -31,6 +31,7 @@ test('AGENT_HANDOFF stays bounded and points agents to live state artifacts', () assert.match(handoff, /tests\/results\/_agent\/handoff\/entrypoint-status\.json/); assert.match(handoff, /tests\/results\/_agent\/handoff\/monitoring-mode\.json/); assert.match(handoff, /tests\/results\/_agent\/handoff\/autonomous-governor-summary\.json/); + assert.match(handoff, /tests\/results\/_agent\/handoff\/sagan-context-concentrator\.json/); assert.match(handoff, /tests\/results\/_agent\/handoff\/autonomous-governor-portfolio-summary\.json/); assert.match(handoff, /tests\/results\/_agent\/handoff\/\*\.json/); assert.match(handoff, /tests\/results\/_agent\/sessions\/\*\.json/); @@ -70,14 +71,18 @@ test('handoff entrypoint contract is wired into automation and operator docs', ( assert.match(printHandoff, /autonomous-governor-summary\.json/); assert.match(printHandoff, /autonomous-governor-portfolio-summary\.mjs/); assert.match(printHandoff, /autonomous-governor-portfolio-summary\.json/); + assert.match(printHandoff, /sagan-context-concentrator\.mjs/); + assert.match(printHandoff, /sagan-context-concentrator\.json/); assert.match(printHandoff, /docker-review-loop-summary\.json/); assert.match(importHandoff, /entrypoint-status\.json/); assert.match(importHandoff, /continuity-summary\.json/); assert.match(importHandoff, /monitoring-mode\.json/); assert.match(importHandoff, /autonomous-governor-summary\.json/); assert.match(importHandoff, /autonomous-governor-portfolio-summary\.json/); + assert.match(importHandoff, /sagan-context-concentrator\.json/); assert.match(importHandoff, /\[handoff\] Autonomous governor summary/); assert.match(importHandoff, /\[handoff\] Governor portfolio summary/); + assert.match(importHandoff, /\[handoff\] Context concentrator/); assert.match(importHandoff, /\[handoff\] Monitoring mode/); assert.match(importHandoff, /\[handoff\] Continuity summary/); assert.match(importHandoff, /docker-review-loop-summary\.json/); @@ -98,9 +103,12 @@ test('handoff entrypoint contract is wired into automation and operator docs', ( assert.match(handoffGuide, /monitoring-mode\.json/); assert.match(handoffGuide, /autonomous-governor-summary\.json/); assert.match(handoffGuide, /autonomous-governor-portfolio-summary\.json/); + assert.match(handoffGuide, /sagan-context-concentrator\.json/); assert.match(handoffGuide, /docker-review-loop-summary\.json/); assert.match(handoffGuide, /release-signing/i); assert.match(handoffGuide, /priority:handoff/); + assert.match(handoffGuide, /priority:context:concentrate/); + assert.match(handoffGuide, /subagent/i); assert.match(handoffGuide, /queue-empty/); assert.match(handoffGuide, /future agents may pivot/i); }); diff --git a/tools/priority/__tests__/runtime-supervisor.test.mjs b/tools/priority/__tests__/runtime-supervisor.test.mjs index b02bdd96b..41f1fe297 100644 --- a/tools/priority/__tests__/runtime-supervisor.test.mjs +++ b/tools/priority/__tests__/runtime-supervisor.test.mjs @@ -786,6 +786,29 @@ test('buildCompareviTaskPacket projects concurrent lane status receipts from the assert.equal(packet.status, 'waiting-ci'); assert.equal(packet.evidence.delivery.laneLifecycle, 'waiting-ci'); + assert.equal(packet.evidence.delivery.executionTopology.status, 'bundle-committed'); + assert.equal(packet.evidence.delivery.executionTopology.executionPlane, 'hosted'); + assert.equal(packet.evidence.delivery.executionTopology.providerId, 'hosted-github-workflow'); + assert.equal(packet.evidence.delivery.executionTopology.workerSlotId, 'worker-slot-2'); + assert.equal(packet.evidence.delivery.executionTopology.cellId, 'cell-sagan-kernel'); + assert.equal(packet.evidence.delivery.executionTopology.laneId, 'docker-lane-01'); + assert.equal(packet.evidence.delivery.executionTopology.cellClass, 'kernel-coordinator'); + assert.equal(packet.evidence.delivery.executionTopology.suiteClass, 'dual-plane-parity'); + assert.equal(packet.evidence.delivery.executionTopology.planeBinding, 'dual-plane-parity'); + assert.equal(packet.evidence.delivery.executionTopology.harnessKind, 'teststand-compare-harness'); + assert.equal(packet.evidence.delivery.executionTopology.harnessInstanceId, 'ts-harness-01'); + assert.equal(packet.evidence.delivery.executionTopology.executionCellLeaseId, 'exec-lease-123'); + assert.equal(packet.evidence.delivery.executionTopology.dockerLaneLeaseId, 'docker-lease-456'); + assert.equal(packet.evidence.delivery.executionTopology.premiumSaganMode, true); + assert.equal(packet.evidence.delivery.executionTopology.reciprocalLinkReady, true); + assert.equal( + packet.evidence.delivery.executionTopology.operatorAuthorizationRef, + 'budget-auth://operator/session-2026-03-24' + ); + assert.equal(packet.evidence.delivery.executionTopology.runtimeSurface, 'windows-native-teststand'); + assert.equal(packet.evidence.delivery.executionTopology.processModelClass, 'parallel-process-model'); + assert.equal(packet.evidence.delivery.executionTopology.windowsOnly, true); + assert.equal(packet.evidence.delivery.executionTopology.requestedSimultaneous, true); assert.equal(packet.evidence.delivery.concurrentLaneStatus.executionBundle.status, 'committed'); assert.equal(packet.evidence.delivery.concurrentLaneStatus.executionBundle.cellClass, 'kernel-coordinator'); assert.equal(packet.evidence.delivery.concurrentLaneStatus.executionBundle.suiteClass, 'dual-plane-parity'); @@ -7520,6 +7543,28 @@ test('persistDeliveryAgentRuntimeState keeps deferred concurrent lane obligation 'release-with-deferred-local' ); assert.equal(persistedState.activeLane.concurrentLaneStatus.summary.shadowLaneCount, 1); + assert.equal(persistedState.activeLane.executionTopology.status, 'logical-lanes-tracked'); + assert.equal(persistedState.activeLane.executionTopology.executionPlane, null); + assert.equal(persistedState.activeLane.executionTopology.providerId, null); + assert.equal(persistedState.activeLane.executionTopology.workerSlotId, null); + assert.equal(persistedState.activeLane.executionTopology.cellId, null); + assert.equal(persistedState.activeLane.executionTopology.laneId, null); + assert.equal(persistedState.activeLane.executionTopology.cellClass, null); + assert.equal(persistedState.activeLane.executionTopology.suiteClass, null); + assert.equal(persistedState.activeLane.executionTopology.planeBinding, null); + assert.equal(persistedState.activeLane.executionTopology.harnessKind, null); + assert.equal(persistedState.activeLane.executionTopology.harnessInstanceId, null); + assert.equal(persistedState.activeLane.executionTopology.executionCellLeaseId, null); + assert.equal(persistedState.activeLane.executionTopology.dockerLaneLeaseId, null); + assert.equal(persistedState.activeLane.executionTopology.premiumSaganMode, false); + assert.equal(persistedState.activeLane.executionTopology.reciprocalLinkReady, false); + assert.equal(persistedState.activeLane.executionTopology.operatorAuthorizationRef, null); + assert.equal(persistedState.activeLane.executionTopology.activeLogicalLaneCount, 4); + assert.equal(persistedState.activeLane.executionTopology.seededLogicalLaneCount, 4); + assert.equal(persistedState.activeLane.executionTopology.runtimeSurface, null); + assert.equal(persistedState.activeLane.executionTopology.processModelClass, null); + assert.equal(persistedState.activeLane.executionTopology.windowsOnly, false); + assert.equal(persistedState.activeLane.executionTopology.requestedSimultaneous, false); assert.equal( persistedState.artifacts.concurrentLaneApplyReceiptPath, 'tests/results/_agent/runtime/concurrent-lane-apply-receipt.json' diff --git a/tools/priority/__tests__/sagan-context-concentrator-schema.test.mjs b/tools/priority/__tests__/sagan-context-concentrator-schema.test.mjs new file mode 100644 index 000000000..28430e4cc --- /dev/null +++ b/tools/priority/__tests__/sagan-context-concentrator-schema.test.mjs @@ -0,0 +1,186 @@ +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; + +import { Ajv2020 } from 'ajv/dist/2020.js'; +import addFormats from 'ajv-formats'; + +import { runSaganContextConcentrator } from '../sagan-context-concentrator.mjs'; + +const repoRoot = path.resolve(process.cwd()); + +function writeJson(filePath, payload) { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8'); +} + +function readJson(filePath) { + return JSON.parse(fs.readFileSync(filePath, 'utf8')); +} + +test('sagan context concentrator report matches schema', async () => { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sagan-context-concentrator-schema-')); + writeJson(path.join(tmpDir, '.agent_priority_cache.json'), { + number: 1909, + repository: 'LabVIEW-Community-CI-CD/compare-vi-cli-action', + title: '[governor]: build Sagan context concentrator for durable subagent memory', + url: 'https://github.com/LabVIEW-Community-CI-CD/compare-vi-cli-action/issues/1909', + state: 'OPEN' + }); + writeJson(path.join(tmpDir, 'tests', 'results', '_agent', 'handoff', 'autonomous-governor-summary.json'), { + schema: 'priority/autonomous-governor-summary-report@v1', + generatedAt: '2026-03-23T23:00:00Z', + repository: 'LabVIEW-Community-CI-CD/compare-vi-cli-action', + inputs: { + queueEmptyReportPath: 'tests/results/_agent/issue/no-standing-priority.json', + continuitySummaryPath: 'tests/results/_agent/handoff/continuity-summary.json', + monitoringModePath: 'tests/results/_agent/handoff/monitoring-mode.json', + wakeLifecyclePath: 'tests/results/_agent/issue/wake-lifecycle.json', + wakeInvestmentAccountingPath: 'tests/results/_agent/capital/wake-investment-accounting.json', + deliveryRuntimeStatePath: 'tests/results/_agent/runtime/delivery-agent-state.json', + releaseSigningReadinessPath: 'tests/results/_agent/release/release-signing-readiness.json' + }, + compare: {}, + wake: {}, + funding: {}, + summary: { + governorMode: 'compare-governance-work', + currentOwnerRepository: 'LabVIEW-Community-CI-CD/compare-vi-cli-action', + nextOwnerRepository: 'LabVIEW-Community-CI-CD/compare-vi-cli-action', + nextAction: 'keep-building-concentrator', + queueState: 'active', + monitoringStatus: 'active', + releaseSigningStatus: 'missing' + } + }); + writeJson(path.join(tmpDir, 'tests', 'results', '_agent', 'handoff', 'autonomous-governor-portfolio-summary.json'), { + schema: 'priority/autonomous-governor-portfolio-summary-report@v1', + generatedAt: '2026-03-23T23:00:10Z', + repository: 'LabVIEW-Community-CI-CD/compare-vi-cli-action', + inputs: { + compareGovernorSummaryPath: 'tests/results/_agent/handoff/autonomous-governor-summary.json', + monitoringModePath: 'tests/results/_agent/handoff/monitoring-mode.json', + repoGraphTruthPath: 'tests/results/_agent/handoff/downstream-repo-graph-truth.json' + }, + compare: {}, + portfolio: { + repositoryCount: 1, + repositories: [], + dependencies: [], + unsupportedPaths: [] + }, + summary: { + status: 'active', + governorMode: 'compare-governance-work', + currentOwnerRepository: 'LabVIEW-Community-CI-CD/compare-vi-cli-action', + nextOwnerRepository: 'LabVIEW-Community-CI-CD/compare-vi-cli-action', + nextAction: 'keep-building-concentrator', + ownerDecisionSource: 'compare-governor-summary', + templateMonitoringStatus: 'pass', + supportedProofStatus: 'pass', + repoGraphStatus: 'pass', + queueHandoffStatus: 'none', + queueHandoffNextWakeCondition: null, + queueHandoffPrUrl: null, + queueAuthoritySource: 'none', + viHistoryDistributorDependencyStatus: 'unknown', + viHistoryDistributorDependencyTargetRepository: null, + viHistoryDistributorDependencyExternalBlocker: null, + viHistoryDistributorDependencyPublicationState: null, + viHistoryDistributorDependencyPublishedBundleState: null, + viHistoryDistributorDependencyPublishedBundleReleaseTag: null, + viHistoryDistributorDependencyAuthoritativeConsumerPin: null, + viHistoryDistributorDependencySigningAuthorityState: null + } + }); + writeJson(path.join(tmpDir, 'tests', 'results', '_agent', 'handoff', 'monitoring-mode.json'), { + schema: 'agent-handoff/monitoring-mode-v1', + generatedAt: '2026-03-23T23:00:20Z', + repository: 'LabVIEW-Community-CI-CD/compare-vi-cli-action', + policy: { + compareRepository: 'LabVIEW-Community-CI-CD/compare-vi-cli-action' + }, + summary: { + status: 'active', + futureAgentAction: 'continue-compare-governance-work', + wakeConditionCount: 0 + } + }); + writeJson(path.join(tmpDir, 'tests', 'results', '_agent', 'memory', 'subagent-episodes', 'episode.json'), { + schema: 'priority/subagent-episode-report@v1', + generatedAt: '2026-03-23T22:59:00Z', + repository: 'LabVIEW-Community-CI-CD/compare-vi-cli-action', + inputs: { + sourcePath: 'tmp/episode.json' + }, + episodeId: 'episode-1', + agent: { + id: 'euler-id', + name: 'Euler', + role: 'explorer', + model: 'gpt-5.4-mini' + }, + task: { + summary: 'Inspect concentrator seams', + class: 'exploration', + issueNumber: 1909, + issueUrl: 'https://github.com/LabVIEW-Community-CI-CD/compare-vi-cli-action/issues/1909' + }, + execution: { + status: 'completed', + lane: '1909-sagan-context-concentrator', + branch: 'issue/upstream-1909-sagan-context-concentrator', + executionPlane: 'windows-host', + dockerLaneId: 'docker-euler-001', + hostCapabilityLeaseId: 'lease-euler-001', + cellId: 'cell-euler-001', + executionCellLeaseId: 'cell-lease-euler-001', + dockerLaneLeaseId: 'docker-lease-euler-001', + cellClass: 'worker-cell', + suiteClass: 'handoff-analysis', + harnessKind: 'teststand-instance', + harnessInstanceId: 'ts-euler-001', + runtimeSurface: 'windows-native-teststand', + processModelClass: 'sequential', + operatorAuthorizationRef: null, + premiumSaganMode: false + }, + summary: { + status: 'reported', + outcome: 'seams-found', + blocker: null, + nextAction: 'patch handoff', + detail: null + }, + evidence: { + filesTouched: [], + receipts: [], + commands: [], + notes: [] + }, + cost: { + observedDurationSeconds: 30, + tokenUsd: 0.02, + operatorLaborUsd: 2.083333, + blendedLowerBoundUsd: 2.103333 + } + }); + + const { report } = await runSaganContextConcentrator( + { + repoRoot: tmpDir + }, + { + now: new Date('2026-03-23T23:01:00Z') + } + ); + + const schema = readJson(path.join(repoRoot, 'docs', 'schemas', 'sagan-context-concentrator-report-v1.schema.json')); + const ajv = new Ajv2020({ allErrors: true, strict: false }); + addFormats(ajv); + const validate = ajv.compile(schema); + const valid = validate(report); + assert.equal(valid, true, JSON.stringify(validate.errors, null, 2)); +}); diff --git a/tools/priority/__tests__/sagan-context-concentrator.test.mjs b/tools/priority/__tests__/sagan-context-concentrator.test.mjs new file mode 100644 index 000000000..703771d55 --- /dev/null +++ b/tools/priority/__tests__/sagan-context-concentrator.test.mjs @@ -0,0 +1,282 @@ +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; + +import { runSaganContextConcentrator } from '../sagan-context-concentrator.mjs'; + +function writeJson(filePath, payload) { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8'); +} + +function createGovernorSummary() { + return { + schema: 'priority/autonomous-governor-summary-report@v1', + generatedAt: '2026-03-23T22:30:00Z', + repository: 'LabVIEW-Community-CI-CD/compare-vi-cli-action', + inputs: { + queueEmptyReportPath: 'tests/results/_agent/issue/no-standing-priority.json', + continuitySummaryPath: 'tests/results/_agent/handoff/continuity-summary.json', + monitoringModePath: 'tests/results/_agent/handoff/monitoring-mode.json', + wakeLifecyclePath: 'tests/results/_agent/issue/wake-lifecycle.json', + wakeInvestmentAccountingPath: 'tests/results/_agent/capital/wake-investment-accounting.json', + deliveryRuntimeStatePath: 'tests/results/_agent/runtime/delivery-agent-state.json', + releaseSigningReadinessPath: 'tests/results/_agent/release/release-signing-readiness.json' + }, + compare: {}, + wake: {}, + funding: {}, + summary: { + governorMode: 'compare-governance-work', + currentOwnerRepository: 'LabVIEW-Community-CI-CD/compare-vi-cli-action', + nextOwnerRepository: 'LabVIEW-Community-CI-CD/compare-vi-cli-action', + nextAction: 'publish-producer-native-vi-history-bundle', + queueState: 'active', + monitoringStatus: 'active', + releaseSigningStatus: 'warn', + releaseSigningExternalBlocker: 'tag-signature-unverified', + releasePublicationState: 'tag-created-not-published', + releasePublishedBundleState: 'producer-native-incomplete', + releasePublishedBundleReleaseTag: 'v0.6.3-tools.14' + } + }; +} + +function createGovernorPortfolioSummary() { + return { + schema: 'priority/autonomous-governor-portfolio-summary-report@v1', + generatedAt: '2026-03-23T22:31:00Z', + repository: 'LabVIEW-Community-CI-CD/compare-vi-cli-action', + inputs: { + compareGovernorSummaryPath: 'tests/results/_agent/handoff/autonomous-governor-summary.json', + monitoringModePath: 'tests/results/_agent/handoff/monitoring-mode.json', + repoGraphTruthPath: 'tests/results/_agent/handoff/downstream-repo-graph-truth.json' + }, + compare: {}, + portfolio: { + repositoryCount: 4, + repositories: [], + dependencies: [], + unsupportedPaths: [] + }, + summary: { + status: 'active', + governorMode: 'compare-governance-work', + currentOwnerRepository: 'LabVIEW-Community-CI-CD/compare-vi-cli-action', + nextOwnerRepository: 'LabVIEW-Community-CI-CD/compare-vi-cli-action', + nextAction: 'publish-producer-native-vi-history-bundle', + ownerDecisionSource: 'compare-governor-summary', + templateMonitoringStatus: 'pass', + supportedProofStatus: 'pass', + repoGraphStatus: 'pass', + queueHandoffStatus: 'none', + queueHandoffNextWakeCondition: null, + queueHandoffPrUrl: null, + queueAuthoritySource: 'none', + viHistoryDistributorDependencyStatus: 'blocked', + viHistoryDistributorDependencyTargetRepository: 'LabVIEW-Community-CI-CD/LabviewGitHubCiTemplate', + viHistoryDistributorDependencyExternalBlocker: 'producer-native-incomplete', + viHistoryDistributorDependencyPublicationState: 'tag-created-not-published', + viHistoryDistributorDependencyPublishedBundleState: 'producer-native-incomplete', + viHistoryDistributorDependencyPublishedBundleReleaseTag: 'v0.6.3-tools.14', + viHistoryDistributorDependencyAuthoritativeConsumerPin: null, + viHistoryDistributorDependencySigningAuthorityState: 'configured' + } + }; +} + +function createMonitoringMode() { + return { + schema: 'agent-handoff/monitoring-mode-v1', + generatedAt: '2026-03-23T22:32:00Z', + repository: 'LabVIEW-Community-CI-CD/compare-vi-cli-action', + policy: { + compareRepository: 'LabVIEW-Community-CI-CD/compare-vi-cli-action' + }, + summary: { + status: 'active', + futureAgentAction: 'remain-in-monitoring', + wakeConditionCount: 0 + } + }; +} + +function createEpisode(agentName, status, generatedAt, extra = {}) { + return { + schema: 'priority/subagent-episode-report@v1', + generatedAt, + repository: 'LabVIEW-Community-CI-CD/compare-vi-cli-action', + inputs: { + sourcePath: `tmp/${agentName}.json` + }, + episodeId: `${agentName}-${generatedAt.replace(/[:.]/g, '-')}`, + agent: { + id: `${agentName.toLowerCase()}-id`, + name: agentName, + role: 'explorer', + model: 'gpt-5.4-mini' + }, + task: { + summary: extra.taskSummary || `Task from ${agentName}`, + class: 'exploration', + issueNumber: 1909, + issueUrl: 'https://github.com/LabVIEW-Community-CI-CD/compare-vi-cli-action/issues/1909' + }, + execution: { + status: 'completed', + lane: '1909-sagan-context-concentrator', + branch: 'issue/upstream-1909-sagan-context-concentrator', + executionPlane: extra.executionPlane || 'windows-host', + dockerLaneId: extra.dockerLaneId || null, + hostCapabilityLeaseId: extra.hostCapabilityLeaseId || null, + cellId: extra.cellId || null, + executionCellLeaseId: extra.executionCellLeaseId || null, + dockerLaneLeaseId: extra.dockerLaneLeaseId || null, + cellClass: extra.cellClass || null, + suiteClass: extra.suiteClass || null, + harnessKind: extra.harnessKind || null, + harnessInstanceId: extra.harnessInstanceId || null, + runtimeSurface: extra.runtimeSurface || null, + processModelClass: extra.processModelClass || null, + operatorAuthorizationRef: extra.operatorAuthorizationRef || null, + premiumSaganMode: extra.premiumSaganMode ?? null + }, + summary: { + status, + outcome: extra.outcome || null, + blocker: extra.blocker || null, + nextAction: extra.nextAction || null, + detail: extra.detail || null + }, + evidence: { + filesTouched: extra.filesTouched || [], + receipts: extra.receipts || [], + commands: [], + notes: [] + }, + cost: { + observedDurationSeconds: extra.durationSeconds || 60, + tokenUsd: extra.tokenUsd || 0.05, + operatorLaborUsd: extra.operatorLaborUsd || 4.166667, + blendedLowerBoundUsd: extra.blendedLowerBoundUsd || 4.216667 + } + }; +} + +test('runSaganContextConcentrator builds hot and warm memory from episodes and governor state', async () => { + const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'sagan-context-concentrator-')); + writeJson(path.join(repoRoot, '.agent_priority_cache.json'), { + number: 1877, + repository: 'LabVIEW-Community-CI-CD/compare-vi-cli-action', + title: '[release]: publish CompareVI.Tools bundle with native vi-history capability contract', + url: 'https://github.com/LabVIEW-Community-CI-CD/compare-vi-cli-action/issues/1877', + state: 'OPEN' + }); + writeJson( + path.join(repoRoot, 'tests', 'results', '_agent', 'handoff', 'autonomous-governor-summary.json'), + createGovernorSummary() + ); + writeJson( + path.join(repoRoot, 'tests', 'results', '_agent', 'handoff', 'autonomous-governor-portfolio-summary.json'), + createGovernorPortfolioSummary() + ); + writeJson( + path.join(repoRoot, 'tests', 'results', '_agent', 'handoff', 'monitoring-mode.json'), + createMonitoringMode() + ); + writeJson( + path.join(repoRoot, 'tests', 'results', '_agent', 'memory', 'subagent-episodes', 'euler.json'), + createEpisode('Euler', 'reported', '2026-03-23T22:20:00Z', { + blocker: 'handoff-seam-open', + nextAction: 'patch Print-AgentHandoff', + dockerLaneId: 'docker-euler-001', + cellId: 'cell-euler-001', + executionCellLeaseId: 'cell-lease-euler-001', + dockerLaneLeaseId: 'docker-lease-euler-001', + cellClass: 'worker-cell', + suiteClass: 'handoff-analysis', + harnessKind: 'teststand-instance', + harnessInstanceId: 'ts-euler-001', + runtimeSurface: 'windows-native-teststand', + processModelClass: 'sequential', + premiumSaganMode: false, + tokenUsd: 0.08, + operatorLaborUsd: 6.25, + blendedLowerBoundUsd: 6.33 + }) + ); + writeJson( + path.join(repoRoot, 'tests', 'results', '_agent', 'memory', 'subagent-episodes', 'euclid.json'), + createEpisode('Euclid', 'reported', '2026-03-23T22:25:00Z', { + nextAction: 'reuse governor schema style', + tokenUsd: 0.05, + operatorLaborUsd: 4.166667, + blendedLowerBoundUsd: 4.216667 + }) + ); + writeJson( + path.join(repoRoot, 'tests', 'results', '_agent', 'memory', 'subagent-episodes', 'hooke.json'), + createEpisode('Hooke', 'completed', '2026-03-23T22:10:00Z', { + outcome: 'template-blocker-confirmed', + nextAction: 'hold template #18 until compare publication', + executionPlane: 'docker-lane', + dockerLaneId: 'docker-hooke-001' + }) + ); + + const { report, outputPath } = await runSaganContextConcentrator( + { + repoRoot + }, + { + now: new Date('2026-03-23T22:35:00Z') + } + ); + + assert.equal(report.schema, 'priority/sagan-context-concentrator-report@v1'); + assert.equal(report.focus.activeIssue.number, 1877); + assert.equal(report.summary.currentOwnerRepository, 'LabVIEW-Community-CI-CD/compare-vi-cli-action'); + assert.equal(report.summary.nextAction, 'publish-producer-native-vi-history-bundle'); + assert.equal(report.summary.hotWorkingSetCount, report.memory.hotWorkingSet.length); + assert.equal(report.episodes.validCount, 3); + assert.ok(report.episodes.byAgent.some((entry) => entry.agentName === 'Euler')); + assert.equal(report.cost.tokenUsd, 0.18); + assert.equal(report.cost.blendedLowerBoundUsd, 14.763334); + assert.ok(report.memory.hotWorkingSet.some((entry) => entry.kind === 'dependency')); + assert.ok(report.memory.hotWorkingSet.some((entry) => entry.agentName === 'Euler')); + assert.ok(report.memory.warmMemory.some((entry) => entry.agentName === 'Hooke')); + assert.ok(report.memory.hotWorkingSet.some((entry) => entry.executionOwnershipLabel === 'cell cell-euler-001 / docker docker-euler-001 / harness ts-euler-001 / windows-native-teststand / sequential')); + assert.ok(report.episodes.recent.some((entry) => entry.cellId === 'cell-euler-001')); + assert.ok(fs.existsSync(outputPath)); +}); + +test('runSaganContextConcentrator tolerates missing optional episode directory', async () => { + const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'sagan-context-concentrator-empty-')); + writeJson( + path.join(repoRoot, 'tests', 'results', '_agent', 'handoff', 'autonomous-governor-summary.json'), + createGovernorSummary() + ); + writeJson( + path.join(repoRoot, 'tests', 'results', '_agent', 'handoff', 'autonomous-governor-portfolio-summary.json'), + createGovernorPortfolioSummary() + ); + writeJson( + path.join(repoRoot, 'tests', 'results', '_agent', 'handoff', 'monitoring-mode.json'), + createMonitoringMode() + ); + + const { report } = await runSaganContextConcentrator( + { + repoRoot + }, + { + now: new Date('2026-03-23T22:40:00Z') + } + ); + + assert.equal(report.episodes.totalCount, 0); + assert.equal(report.summary.concentrationStatus, 'pass'); + assert.equal(report.summary.hotWorkingSetCount >= 2, true); +}); diff --git a/tools/priority/__tests__/subagent-episode-schema.test.mjs b/tools/priority/__tests__/subagent-episode-schema.test.mjs new file mode 100644 index 000000000..d211fe526 --- /dev/null +++ b/tools/priority/__tests__/subagent-episode-schema.test.mjs @@ -0,0 +1,98 @@ +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; + +import { Ajv2020 } from 'ajv/dist/2020.js'; +import addFormats from 'ajv-formats'; + +import { runSubagentEpisode } from '../subagent-episode.mjs'; + +const repoRoot = path.resolve(process.cwd()); + +function readJson(filePath) { + return JSON.parse(fs.readFileSync(filePath, 'utf8')); +} + +test('subagent episode report matches schema', async () => { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'subagent-episode-schema-')); + const inputPath = path.join(tmpDir, 'episode-input.json'); + const outputPath = path.join(tmpDir, 'subagent-episode.json'); + + fs.writeFileSync( + inputPath, + `${JSON.stringify( + { + repository: 'LabVIEW-Community-CI-CD/compare-vi-cli-action', + generatedAt: '2026-03-23T22:10:00Z', + agent: { + id: '019d1ba6-746c-72a2-b73c-3ebc239843f1', + name: 'Hooke', + role: 'explorer', + model: 'gpt-5.4-mini' + }, + task: { + summary: 'Verify template blocker state', + class: 'verification', + issueNumber: 18, + issueUrl: 'https://github.com/LabVIEW-Community-CI-CD/LabviewGitHubCiTemplate/issues/18' + }, + execution: { + status: 'completed', + lane: 'LabviewGitHubCiTemplate-18-producer-native-consumer', + executionPlane: 'windows-host', + dockerLaneId: null, + hostCapabilityLeaseId: 'lease-hooke-001', + cellId: 'cell-hooke-001', + executionCellLeaseId: 'cell-lease-hooke-001', + dockerLaneLeaseId: null, + cellClass: 'worker-cell', + suiteClass: 'template-verification', + harnessKind: 'teststand-instance', + harnessInstanceId: 'ts-hooke-001', + runtimeSurface: 'windows-native-teststand', + processModelClass: 'sequential', + operatorAuthorizationRef: null, + premiumSaganMode: false + }, + summary: { + status: 'reported', + outcome: 'template-blocker-confirmed', + blocker: 'compare-publication-pending', + nextAction: 'wait for producer-native release publication' + }, + evidence: { + receipts: ['tests/results/_agent/release/release-published-bundle-observer.json'] + }, + cost: { + observedDurationSeconds: 120, + tokenUsd: 0.08, + operatorLaborUsd: 8.333333, + blendedLowerBoundUsd: 8.413333 + } + }, + null, + 2 + )}\n`, + 'utf8' + ); + + const { report } = await runSubagentEpisode( + { + repoRoot: tmpDir, + inputPath, + outputPath + }, + { + now: new Date('2026-03-23T22:10:30Z') + } + ); + + const schema = readJson(path.join(repoRoot, 'docs', 'schemas', 'subagent-episode-report-v1.schema.json')); + const ajv = new Ajv2020({ allErrors: true, strict: false }); + addFormats(ajv); + const validate = ajv.compile(schema); + const valid = validate(report); + assert.equal(valid, true, JSON.stringify(validate.errors, null, 2)); +}); diff --git a/tools/priority/__tests__/subagent-episode.test.mjs b/tools/priority/__tests__/subagent-episode.test.mjs new file mode 100644 index 000000000..cc6881621 --- /dev/null +++ b/tools/priority/__tests__/subagent-episode.test.mjs @@ -0,0 +1,163 @@ +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; + +import { + REPORT_SCHEMA, + buildSubagentEpisodeReport, + parseArgs, + runSubagentEpisode +} from '../subagent-episode.mjs'; + +function writeJson(filePath, payload) { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8'); +} + +test('parseArgs requires input and keeps defaults', () => { + const parsed = parseArgs([ + 'node', + 'subagent-episode.mjs', + '--input', + 'tmp/episode.json' + ]); + + assert.equal(parsed.inputPath, 'tmp/episode.json'); + assert.equal(parsed.outputPath, null); +}); + +test('buildSubagentEpisodeReport normalizes source payload', () => { + const report = buildSubagentEpisodeReport( + { + repository: 'LabVIEW-Community-CI-CD/compare-vi-cli-action', + generatedAt: '2026-03-23T21:00:00Z', + agent: { + id: '019d11a9-3e6b-7073-b602-7a0a2085f106', + name: 'Euler', + role: 'explorer', + model: 'gpt-5.4-mini' + }, + task: { + summary: 'Inspect handoff seams', + class: 'exploration', + issueNumber: 1909, + issueUrl: 'https://github.com/LabVIEW-Community-CI-CD/compare-vi-cli-action/issues/1909' + }, + execution: { + status: 'completed', + lane: '1909-sagan-context-concentrator', + branch: 'issue/upstream-1909-sagan-context-concentrator', + executionPlane: 'windows-host', + dockerLaneId: 'docker-euler-001', + hostCapabilityLeaseId: 'lease-euler-001', + cellId: 'cell-euler-001', + executionCellLeaseId: 'cell-lease-euler-001', + dockerLaneLeaseId: 'docker-lease-euler-001', + cellClass: 'worker-cell', + suiteClass: 'handoff-analysis', + harnessKind: 'teststand-instance', + harnessInstanceId: 'ts-euler-001', + runtimeSurface: 'windows-native-teststand', + processModelClass: 'sequential', + operatorAuthorizationRef: null, + premiumSaganMode: false + }, + summary: { + status: 'reported', + outcome: 'handoff-seams-identified', + blocker: null, + nextAction: 'wire concentrator into Print-AgentHandoff', + detail: 'Print-AgentHandoff and Import-HandoffState are the right seams.' + }, + evidence: { + filesTouched: ['tools/Print-AgentHandoff.ps1'], + receipts: ['tests/results/_agent/handoff/autonomous-governor-summary.json'], + commands: ['rg -n governor tools/Print-AgentHandoff.ps1'], + notes: ['Focus on handoff refresh and render path.'] + }, + cost: { + observedDurationSeconds: 90, + tokenUsd: 0.14, + operatorLaborUsd: 6.25, + blendedLowerBoundUsd: 6.39 + } + }, + { + repoRoot: 'E:/comparevi-lanes/1909-sagan-context-concentrator', + inputPath: 'tmp/subagent-input.json', + now: new Date('2026-03-23T21:05:00Z') + } + ); + + assert.equal(report.schema, REPORT_SCHEMA); + assert.equal(report.agent.name, 'Euler'); + assert.equal(report.task.issueNumber, 1909); + assert.equal(report.execution.dockerLaneId, 'docker-euler-001'); + assert.equal(report.execution.cellId, 'cell-euler-001'); + assert.equal(report.execution.harnessInstanceId, 'ts-euler-001'); + assert.equal(report.execution.runtimeSurface, 'windows-native-teststand'); + assert.equal(report.execution.premiumSaganMode, false); + assert.equal(report.summary.nextAction, 'wire concentrator into Print-AgentHandoff'); + assert.equal(report.cost.blendedLowerBoundUsd, 6.39); +}); + +test('runSubagentEpisode writes a normalized episode report', async () => { + const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'subagent-episode-')); + const inputPath = path.join(repoRoot, 'tmp', 'episode-input.json'); + writeJson(inputPath, { + repository: 'LabVIEW-Community-CI-CD/compare-vi-cli-action', + agent: { + id: '019d11a9-3e7f-7331-a692-63a1c6c78904', + name: 'Euclid', + role: 'explorer', + model: 'gpt-5.4-mini' + }, + task: { + summary: 'Catalog analogous receipt patterns', + class: 'exploration', + issueNumber: 1909 + }, + summary: { + status: 'reported', + outcome: 'receipt-templates-found', + nextAction: 'reuse governor summary schema style' + }, + execution: { + cellId: 'cell-euclid-001', + executionCellLeaseId: 'cell-lease-euclid-001', + dockerLaneLeaseId: null, + cellClass: 'worker-cell', + suiteClass: 'receipt-catalog', + harnessKind: 'teststand-instance', + harnessInstanceId: 'ts-euclid-001', + runtimeSurface: 'windows-native-teststand', + processModelClass: 'sequential', + operatorAuthorizationRef: null, + premiumSaganMode: false + }, + evidence: { + notes: ['Use autonomous-governor-summary as the main receipt template.'] + } + }); + + const { report, outputPath } = await runSubagentEpisode( + { + repoRoot, + inputPath + }, + { + now: new Date('2026-03-23T22:00:00Z') + } + ); + + assert.ok(outputPath.includes('subagent-episodes')); + assert.equal(report.schema, REPORT_SCHEMA); + assert.equal(report.agent.name, 'Euclid'); + assert.equal(report.summary.status, 'reported'); + assert.equal(report.execution.status, 'completed'); + assert.equal(report.execution.cellClass, 'worker-cell'); + assert.equal(report.execution.harnessInstanceId, 'ts-euclid-001'); + assert.ok(fs.existsSync(outputPath)); +}); diff --git a/tools/priority/autonomous-governor-summary.mjs b/tools/priority/autonomous-governor-summary.mjs index 788523ddb..0742d148b 100644 --- a/tools/priority/autonomous-governor-summary.mjs +++ b/tools/priority/autonomous-governor-summary.mjs @@ -186,6 +186,15 @@ function parseBoolean(value) { return value === true; } +function coalesceBoolean(...values) { + for (const value of values) { + if (typeof value === 'boolean') { + return value; + } + } + return false; +} + function normalizeLower(value) { return typeof value === 'string' ? value.trim().toLowerCase() : ''; } @@ -242,13 +251,181 @@ function deriveExecutionTopology({ deliveryRuntimeState, activeLane, executionBu const providerDispatch = normalizeOptionalObject(activeLane?.providerDispatch) ?? normalizeOptionalObject(deliveryRuntimeState?.artifacts?.providerDispatch); - const processModel = deriveExecutionTopologyProcessModel(executionBundle); - const activeLogicalLaneCount = Number.isInteger(logicalLaneActivation?.activeLaneCount) + const activeLaneExecutionTopology = normalizeOptionalObject(activeLane?.executionTopology); + const activeLogicalLaneCountFallback = Number.isInteger(logicalLaneActivation?.activeLaneCount) ? logicalLaneActivation.activeLaneCount : null; - const seededLogicalLaneCount = Number.isInteger(logicalLaneActivation?.seededLaneCount) + const seededLogicalLaneCountFallback = Number.isInteger(logicalLaneActivation?.seededLaneCount) ? logicalLaneActivation.seededLaneCount : null; + + if (activeLaneExecutionTopology) { + const topologyLogicalLaneActivation = normalizeOptionalObject(activeLaneExecutionTopology.logicalLaneActivation); + const topologyProviderDispatch = normalizeOptionalObject(activeLaneExecutionTopology.providerDispatch); + const topologyExecutionBundle = normalizeOptionalObject(activeLaneExecutionTopology.executionBundle); + const effectiveProviderDispatch = topologyProviderDispatch ?? providerDispatch; + const effectiveExecutionBundle = topologyExecutionBundle ?? executionBundle; + const processModel = deriveExecutionTopologyProcessModel(effectiveExecutionBundle); + const activeLogicalLaneCount = Number.isInteger(activeLaneExecutionTopology.activeLogicalLaneCount) + ? activeLaneExecutionTopology.activeLogicalLaneCount + : Number.isInteger(topologyLogicalLaneActivation?.activeLaneCount) + ? topologyLogicalLaneActivation.activeLaneCount + : activeLogicalLaneCountFallback; + const seededLogicalLaneCount = Number.isInteger(activeLaneExecutionTopology.seededLogicalLaneCount) + ? activeLaneExecutionTopology.seededLogicalLaneCount + : Number.isInteger(topologyLogicalLaneActivation?.seededLaneCount) + ? topologyLogicalLaneActivation.seededLaneCount + : seededLogicalLaneCountFallback; + const catalogCount = Number.isInteger(activeLaneExecutionTopology.catalogCount) + ? activeLaneExecutionTopology.catalogCount + : Number.isInteger(topologyLogicalLaneActivation?.catalogCount) + ? topologyLogicalLaneActivation.catalogCount + : logicalLaneCatalog.length; + const executionPlane = + asOptional(activeLaneExecutionTopology.executionPlane) || + asOptional(topologyProviderDispatch?.executionPlane) || + asOptional(providerDispatch?.executionPlane) || + asOptional(activeLaneExecutionTopology.planeBinding) || + asOptional(topologyExecutionBundle?.planeBinding) || + asOptional(executionBundle?.planeBinding); + + return { + status: + asOptional(activeLaneExecutionTopology.status) || + deriveExecutionTopologyStatus({ + activeLogicalLaneCount, + seededLogicalLaneCount, + providerDispatch: effectiveProviderDispatch, + executionBundle: effectiveExecutionBundle + }), + executionPlane, + providerId: + asOptional(activeLaneExecutionTopology.providerId) || + asOptional(topologyProviderDispatch?.providerId) || + asOptional(providerDispatch?.providerId), + workerSlotId: + asOptional(activeLaneExecutionTopology.workerSlotId) || + asOptional(topologyProviderDispatch?.workerSlotId) || + asOptional(providerDispatch?.workerSlotId), + activeLogicalLaneCount, + seededLogicalLaneCount, + catalogCount, + runtimeSurface: asOptional(activeLaneExecutionTopology.runtimeSurface) || processModel.runtimeSurface, + processModelClass: asOptional(activeLaneExecutionTopology.processModelClass) || processModel.processModelClass, + windowsOnly: coalesceBoolean(activeLaneExecutionTopology.windowsOnly, processModel.windowsOnly), + requestedSimultaneous: coalesceBoolean( + activeLaneExecutionTopology.requestedSimultaneous, + processModel.requestedSimultaneous + ), + cellClass: + asOptional(activeLaneExecutionTopology.cellClass) || + asOptional(topologyExecutionBundle?.cellClass) || + asOptional(executionBundle?.cellClass), + suiteClass: + asOptional(activeLaneExecutionTopology.suiteClass) || + asOptional(topologyExecutionBundle?.suiteClass) || + asOptional(executionBundle?.suiteClass), + operatorAuthorizationRef: + asOptional(activeLaneExecutionTopology.operatorAuthorizationRef) || + asOptional(topologyExecutionBundle?.operatorAuthorizationRef) || + asOptional(executionBundle?.operatorAuthorizationRef), + premiumSaganMode: coalesceBoolean( + activeLaneExecutionTopology.premiumSaganMode, + topologyExecutionBundle?.premiumSaganMode, + executionBundle?.premiumSaganMode + ), + reciprocalLinkReady: coalesceBoolean( + activeLaneExecutionTopology.reciprocalLinkReady, + topologyExecutionBundle?.reciprocalLinkReady, + executionBundle?.reciprocalLinkReady + ), + logicalLaneActivation: { + activeLaneCount: activeLogicalLaneCount, + seededLaneCount: seededLogicalLaneCount, + catalogCount + }, + providerDispatch: { + providerId: asOptional(topologyProviderDispatch?.providerId) || asOptional(providerDispatch?.providerId), + providerKind: asOptional(topologyProviderDispatch?.providerKind) || asOptional(providerDispatch?.providerKind), + executionPlane, + assignmentMode: + asOptional(topologyProviderDispatch?.assignmentMode) || asOptional(providerDispatch?.assignmentMode), + dispatchSurface: + asOptional(topologyProviderDispatch?.dispatchSurface) || asOptional(providerDispatch?.dispatchSurface), + completionMode: + asOptional(topologyProviderDispatch?.completionMode) || asOptional(providerDispatch?.completionMode), + workerSlotId: + asOptional(topologyProviderDispatch?.workerSlotId) || asOptional(providerDispatch?.workerSlotId), + dispatchStatus: + asOptional(topologyProviderDispatch?.dispatchStatus) || asOptional(providerDispatch?.dispatchStatus), + completionStatus: + asOptional(topologyProviderDispatch?.completionStatus) || asOptional(providerDispatch?.completionStatus), + failureClass: + asOptional(topologyProviderDispatch?.failureClass) || asOptional(providerDispatch?.failureClass) + }, + executionBundle: { + status: asOptional(topologyExecutionBundle?.status) || asOptional(executionBundle?.status), + planeBinding: + asOptional(activeLaneExecutionTopology.planeBinding) || + asOptional(topologyExecutionBundle?.planeBinding) || + asOptional(executionBundle?.planeBinding), + cellClass: + asOptional(topologyExecutionBundle?.cellClass) || asOptional(executionBundle?.cellClass), + suiteClass: + asOptional(topologyExecutionBundle?.suiteClass) || asOptional(executionBundle?.suiteClass), + premiumSaganMode: coalesceBoolean( + topologyExecutionBundle?.premiumSaganMode, + executionBundle?.premiumSaganMode + ), + reciprocalLinkReady: coalesceBoolean( + topologyExecutionBundle?.reciprocalLinkReady, + executionBundle?.reciprocalLinkReady + ), + effectiveBillableRateUsdPerHour: Number.isFinite(topologyExecutionBundle?.effectiveBillableRateUsdPerHour) + ? topologyExecutionBundle.effectiveBillableRateUsdPerHour + : Number.isFinite(executionBundle?.effectiveBillableRateUsdPerHour) + ? executionBundle.effectiveBillableRateUsdPerHour + : null, + executionCellLeaseId: + asOptional(activeLaneExecutionTopology.executionCellLeaseId) || + asOptional(topologyExecutionBundle?.executionCellLeaseId) || + asOptional(executionBundle?.executionCellLeaseId), + dockerLaneLeaseId: + asOptional(activeLaneExecutionTopology.dockerLaneLeaseId) || + asOptional(topologyExecutionBundle?.dockerLaneLeaseId) || + asOptional(executionBundle?.dockerLaneLeaseId), + harnessKind: + asOptional(activeLaneExecutionTopology.harnessKind) || + asOptional(topologyExecutionBundle?.harnessKind) || + asOptional(executionBundle?.harnessKind), + harnessInstanceId: + asOptional(activeLaneExecutionTopology.harnessInstanceId) || + asOptional(topologyExecutionBundle?.harnessInstanceId) || + asOptional(executionBundle?.harnessInstanceId), + operatorAuthorizationRef: + asOptional(topologyExecutionBundle?.operatorAuthorizationRef) || + asOptional(executionBundle?.operatorAuthorizationRef), + cellId: + asOptional(activeLaneExecutionTopology.cellId) || + asOptional(topologyExecutionBundle?.cellId) || + asOptional(executionBundle?.cellId), + laneId: + asOptional(activeLaneExecutionTopology.laneId) || + asOptional(topologyExecutionBundle?.laneId) || + asOptional(executionBundle?.laneId), + isolatedLaneGroupId: + asOptional(topologyExecutionBundle?.isolatedLaneGroupId) || + asOptional(executionBundle?.isolatedLaneGroupId), + fingerprintSha256: + asOptional(topologyExecutionBundle?.fingerprintSha256) || + asOptional(executionBundle?.fingerprintSha256) + } + }; + } + + const processModel = deriveExecutionTopologyProcessModel(executionBundle); + const activeLogicalLaneCount = activeLogicalLaneCountFallback; + const seededLogicalLaneCount = seededLogicalLaneCountFallback; const executionPlane = asOptional(providerDispatch?.executionPlane) || asOptional(executionBundle?.planeBinding); return { diff --git a/tools/priority/delivery-agent.mjs b/tools/priority/delivery-agent.mjs index 64e69fd3c..d07ab0db4 100644 --- a/tools/priority/delivery-agent.mjs +++ b/tools/priority/delivery-agent.mjs @@ -956,6 +956,120 @@ function buildWorkerProviderDispatchReceipt(providerSelection, { }; } +function coerceNonNegativeInteger(value) { + const parsed = Number(value); + if (!Number.isInteger(parsed) || parsed < 0) { + return null; + } + return parsed; +} + +function deriveExecutionTopologyProcessModel(executionBundle = null) { + const planeBinding = normalizeText(executionBundle?.planeBinding).toLowerCase(); + const harnessKind = normalizeText(executionBundle?.harnessKind); + const requestedSimultaneous = planeBinding === 'dual-plane-parity'; + const windowsNativeTestStand = + (!harnessKind || harnessKind === 'teststand-compare-harness') && + (requestedSimultaneous || planeBinding.startsWith('native-labview-')); + const runtimeSurface = windowsNativeTestStand ? 'windows-native-teststand' : null; + const processModelClass = !runtimeSurface + ? null + : requestedSimultaneous + ? 'parallel-process-model' + : 'sequential-process-model'; + + return { + runtimeSurface, + processModelClass, + windowsOnly: runtimeSurface === 'windows-native-teststand', + requestedSimultaneous + }; +} + +function deriveExecutionTopologyStatus({ + activeLogicalLaneCount = null, + seededLogicalLaneCount = null, + providerDispatch = null, + executionBundle = null +} = {}) { + const bundleStatus = normalizeText(executionBundle?.status); + if (bundleStatus) { + return `bundle-${bundleStatus}`; + } + + const completionStatus = normalizeText(providerDispatch?.completionStatus); + if (completionStatus) { + return `provider-${completionStatus}`; + } + + const dispatchStatus = normalizeText(providerDispatch?.dispatchStatus); + if (dispatchStatus) { + return `provider-${dispatchStatus}`; + } + + if ((activeLogicalLaneCount ?? 0) > 0 || (seededLogicalLaneCount ?? 0) > 0) { + return 'logical-lanes-tracked'; + } + + return 'none'; +} + +export function buildExecutionTopologyRuntimeState({ + logicalLaneActivation = null, + providerDispatch = null, + providerSelection = null, + workerSlotId = null, + concurrentLaneStatus = null +} = {}) { + const logicalLaneState = normalizeOptionalObject(logicalLaneActivation); + const executionBundle = normalizeOptionalObject(concurrentLaneStatus?.executionBundle); + const effectiveProviderDispatch = + normalizeOptionalObject(providerDispatch) ?? + buildWorkerProviderDispatchReceipt(providerSelection, { workerSlotId }); + + const activeLogicalLaneCount = coerceNonNegativeInteger(logicalLaneState?.activeLaneCount); + const seededLogicalLaneCount = coerceNonNegativeInteger(logicalLaneState?.seededLaneCount); + + if (!executionBundle && !effectiveProviderDispatch && activeLogicalLaneCount == null && seededLogicalLaneCount == null) { + return null; + } + + const processModel = deriveExecutionTopologyProcessModel(executionBundle); + + return { + status: deriveExecutionTopologyStatus({ + activeLogicalLaneCount, + seededLogicalLaneCount, + providerDispatch: effectiveProviderDispatch, + executionBundle + }), + executionPlane: + normalizeText(effectiveProviderDispatch?.executionPlane) || + normalizeText(executionBundle?.planeBinding) || + null, + providerId: normalizeText(effectiveProviderDispatch?.providerId) || null, + workerSlotId: normalizeText(effectiveProviderDispatch?.workerSlotId) || null, + cellId: normalizeText(executionBundle?.cellId) || null, + laneId: normalizeText(executionBundle?.laneId) || null, + cellClass: normalizeText(executionBundle?.cellClass) || null, + suiteClass: normalizeText(executionBundle?.suiteClass) || null, + planeBinding: normalizeText(executionBundle?.planeBinding) || null, + harnessKind: normalizeText(executionBundle?.harnessKind) || null, + harnessInstanceId: normalizeText(executionBundle?.harnessInstanceId) || null, + executionCellLeaseId: normalizeText(executionBundle?.executionCellLeaseId) || null, + dockerLaneLeaseId: normalizeText(executionBundle?.dockerLaneLeaseId) || null, + premiumSaganMode: executionBundle?.premiumSaganMode === true, + reciprocalLinkReady: executionBundle?.reciprocalLinkReady === true, + operatorAuthorizationRef: normalizeText(executionBundle?.operatorAuthorizationRef) || null, + activeLogicalLaneCount, + seededLogicalLaneCount, + runtimeSurface: processModel.runtimeSurface, + processModelClass: processModel.processModelClass, + windowsOnly: processModel.windowsOnly, + requestedSimultaneous: processModel.requestedSimultaneous + }; +} + function commandUsesLocalCollabOrchestrator(command = []) { return Array.isArray(command) ? command.some((entry) => normalizeText(entry).replace(/\\/g, '/').includes('tools/local-collab/orchestrator/run-phase.mjs')) @@ -3392,6 +3506,13 @@ export function buildDeliveryAgentRuntimeRecord({ schedulerDecision, taskPacket }); + const executionTopology = buildExecutionTopologyRuntimeState({ + logicalLaneActivation, + providerDispatch, + providerSelection: workerProviderSelection, + workerSlotId: normalizeText(providerDispatch?.workerSlotId) || normalizeText(workerProviderSelection?.selectedSlotId) || null, + concurrentLaneStatus + }); const activeLane = { schema: DELIVERY_AGENT_LANE_STATE_SCHEMA, generatedAt: toIso(now), @@ -3423,6 +3544,7 @@ export function buildDeliveryAgentRuntimeRecord({ readyValidationClearance, concurrentLaneApply, concurrentLaneStatus, + executionTopology, liveAgentModelSelection, workerProviderSelection, providerDispatch diff --git a/tools/priority/runtime-supervisor.mjs b/tools/priority/runtime-supervisor.mjs index 8b869e8bb..0294bb767 100644 --- a/tools/priority/runtime-supervisor.mjs +++ b/tools/priority/runtime-supervisor.mjs @@ -59,6 +59,7 @@ import { } from './sync-standing-priority.mjs'; import { buildWorkerProviderSelectionRequest, + buildExecutionTopologyRuntimeState, buildLocalReviewLoopRequest, buildCanonicalDeliveryDecision, selectWorkerProviderAssignment, @@ -1421,6 +1422,15 @@ async function buildCompareviTaskPacket({ repoRoot, schedulerDecision, preparedW deps, selectedProviderId: workerProviderSelection.selectedProviderId }); + const executionTopology = buildExecutionTopologyRuntimeState({ + providerSelection: workerProviderSelection, + workerSlotId: + normalizeText(workerBranch?.slotId) || + normalizeText(workerReady?.slotId) || + normalizeText(preparedWorker?.slotId) || + null, + concurrentLaneStatus + }); return { source: 'comparevi-runtime', @@ -1511,6 +1521,7 @@ async function buildCompareviTaskPacket({ repoRoot, schedulerDecision, preparedW backlog: artifacts.backlogRepair ?? null, concurrentLaneApply, concurrentLaneStatus, + executionTopology, planeTransition, localReviewLoop, liveAgentModelSelection, diff --git a/tools/priority/sagan-context-concentrator.mjs b/tools/priority/sagan-context-concentrator.mjs new file mode 100644 index 000000000..a65138abb --- /dev/null +++ b/tools/priority/sagan-context-concentrator.mjs @@ -0,0 +1,835 @@ +#!/usr/bin/env node + +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url)); +const DEFAULT_REPO_ROOT = path.resolve(MODULE_DIR, '..', '..'); + +export const REPORT_SCHEMA = 'priority/sagan-context-concentrator-report@v1'; +export const SUBAGENT_EPISODE_SCHEMA = 'priority/subagent-episode-report@v1'; +export const DEFAULT_PRIORITY_CACHE_PATH = '.agent_priority_cache.json'; +export const DEFAULT_GOVERNOR_SUMMARY_PATH = path.join( + 'tests', + 'results', + '_agent', + 'handoff', + 'autonomous-governor-summary.json' +); +export const DEFAULT_GOVERNOR_PORTFOLIO_SUMMARY_PATH = path.join( + 'tests', + 'results', + '_agent', + 'handoff', + 'autonomous-governor-portfolio-summary.json' +); +export const DEFAULT_MONITORING_MODE_PATH = path.join( + 'tests', + 'results', + '_agent', + 'handoff', + 'monitoring-mode.json' +); +export const DEFAULT_OPERATOR_STEERING_EVENT_PATH = path.join( + 'tests', + 'results', + '_agent', + 'handoff', + 'operator-steering-event.json' +); +export const DEFAULT_EPISODE_DIR = path.join( + 'tests', + 'results', + '_agent', + 'memory', + 'subagent-episodes' +); +export const DEFAULT_OUTPUT_PATH = path.join( + 'tests', + 'results', + '_agent', + 'handoff', + 'sagan-context-concentrator.json' +); + +function normalizeText(value) { + if (value == null) { + return null; + } + const normalized = String(value).trim(); + return normalized.length > 0 ? normalized : null; +} + +function normalizeFiniteNumber(value) { + if (value == null || value === '') { + return null; + } + const parsed = Number(value); + return Number.isFinite(parsed) ? parsed : null; +} + +function normalizeInteger(value) { + if (value == null || value === '') { + return null; + } + const parsed = Number(value); + return Number.isInteger(parsed) && parsed > 0 ? parsed : null; +} + +function normalizeBoolean(value) { + if (value === true || value === false) { + return value; + } + if (typeof value === 'string') { + const normalized = value.trim().toLowerCase(); + if (normalized === 'true') { + return true; + } + if (normalized === 'false') { + return false; + } + } + return null; +} + +function toPortablePath(filePath) { + return String(filePath).replace(/\\/g, '/'); +} + +function toRelative(repoRoot, targetPath) { + return path.relative(repoRoot, path.resolve(targetPath)).replace(/\\/g, '/'); +} + +function toDisplayPath(repoRoot, targetPath) { + if (!targetPath) { + return null; + } + const relative = path.relative(path.resolve(repoRoot), path.resolve(targetPath)); + if (!relative.startsWith('..') && !path.isAbsolute(relative)) { + return toPortablePath(relative); + } + return toPortablePath(path.resolve(targetPath)); +} + +function writeJson(filePath, payload) { + const resolvedPath = path.resolve(filePath); + fs.mkdirSync(path.dirname(resolvedPath), { recursive: true }); + fs.writeFileSync(resolvedPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8'); + return resolvedPath; +} + +function readOptionalJson(filePath) { + const resolvedPath = path.resolve(filePath); + if (!fs.existsSync(resolvedPath)) { + return null; + } + return JSON.parse(fs.readFileSync(resolvedPath, 'utf8')); +} + +function ensureSchema(payload, filePath, schema) { + if (payload?.schema !== schema) { + throw new Error(`Expected ${schema} at ${filePath}.`); + } + return payload; +} + +function isEpisodeActive(episode) { + const status = normalizeText(episode?.summary?.status)?.toLowerCase(); + return !['completed', 'pass', 'success', 'closed', 'retired'].includes(status || ''); +} + +function buildExecutionOwnershipLabel(record) { + const parts = []; + const cellId = normalizeText(record?.cellId); + const dockerLaneId = normalizeText(record?.dockerLaneId); + const harnessInstanceId = normalizeText(record?.harnessInstanceId); + const runtimeSurface = normalizeText(record?.runtimeSurface); + const processModelClass = normalizeText(record?.processModelClass); + const premiumSaganMode = normalizeBoolean(record?.premiumSaganMode); + + if (cellId) { + parts.push(`cell ${cellId}`); + } + if (dockerLaneId) { + parts.push(`docker ${dockerLaneId}`); + } + if (harnessInstanceId) { + parts.push(`harness ${harnessInstanceId}`); + } + if (runtimeSurface) { + parts.push(runtimeSurface); + } + if (processModelClass) { + parts.push(processModelClass); + } + if (premiumSaganMode === true) { + parts.push('premium-sagan'); + } + + return parts.length > 0 ? parts.join(' / ') : null; +} + +function buildEpisodeDigest(episode, repoRoot, filePath) { + const execution = episode?.execution || {}; + return { + episodeId: normalizeText(episode?.episodeId), + generatedAt: normalizeText(episode?.generatedAt), + agentId: normalizeText(episode?.agent?.id), + agentName: normalizeText(episode?.agent?.name), + agentRole: normalizeText(episode?.agent?.role), + status: normalizeText(episode?.summary?.status), + taskSummary: normalizeText(episode?.task?.summary), + nextAction: normalizeText(episode?.summary?.nextAction), + blocker: normalizeText(episode?.summary?.blocker), + executionPlane: normalizeText(execution.executionPlane), + dockerLaneId: normalizeText(execution.dockerLaneId), + cellId: normalizeText(execution.cellId), + executionCellLeaseId: normalizeText(execution.executionCellLeaseId), + dockerLaneLeaseId: normalizeText(execution.dockerLaneLeaseId), + cellClass: normalizeText(execution.cellClass), + suiteClass: normalizeText(execution.suiteClass), + harnessKind: normalizeText(execution.harnessKind), + harnessInstanceId: normalizeText(execution.harnessInstanceId), + runtimeSurface: normalizeText(execution.runtimeSurface), + processModelClass: normalizeText(execution.processModelClass), + operatorAuthorizationRef: normalizeText(execution.operatorAuthorizationRef), + premiumSaganMode: normalizeBoolean(execution.premiumSaganMode), + executionOwnershipLabel: buildExecutionOwnershipLabel(execution), + sourcePath: toDisplayPath(repoRoot, filePath) + }; +} + +function makeMemoryItem({ + id, + kind, + label, + status, + detail = null, + executionPlane = null, + cellId = null, + dockerLaneId = null, + harnessInstanceId = null, + runtimeSurface = null, + processModelClass = null, + premiumSaganMode = null, + executionOwnershipLabel = null, + sourcePath = null, + updatedAt = null, + issueNumber = null, + repository = null, + agentName = null, + nextAction = null +}) { + return { + id: normalizeText(id), + kind: normalizeText(kind), + label: normalizeText(label), + status: normalizeText(status), + detail: normalizeText(detail), + executionPlane: normalizeText(executionPlane), + cellId: normalizeText(cellId), + dockerLaneId: normalizeText(dockerLaneId), + harnessInstanceId: normalizeText(harnessInstanceId), + runtimeSurface: normalizeText(runtimeSurface), + processModelClass: normalizeText(processModelClass), + premiumSaganMode: normalizeBoolean(premiumSaganMode), + executionOwnershipLabel: normalizeText(executionOwnershipLabel), + sourcePath: normalizeText(sourcePath), + updatedAt: normalizeText(updatedAt), + issueNumber: normalizeInteger(issueNumber), + repository: normalizeText(repository), + agentName: normalizeText(agentName), + nextAction: normalizeText(nextAction) + }; +} + +function addUniqueMemoryItem(collection, item, seenIds) { + if (!item?.id || seenIds.has(item.id)) { + return false; + } + collection.push(item); + seenIds.add(item.id); + return true; +} + +function sortEpisodesDescending(entries) { + return [...entries].sort((left, right) => { + const leftTime = Date.parse(left.episode.generatedAt || 0); + const rightTime = Date.parse(right.episode.generatedAt || 0); + return rightTime - leftTime; + }); +} + +function listEpisodeFiles(directoryPath) { + if (!fs.existsSync(directoryPath) || !fs.statSync(directoryPath).isDirectory()) { + return []; + } + return fs + .readdirSync(directoryPath, { withFileTypes: true }) + .filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.json')) + .map((entry) => path.join(directoryPath, entry.name)) + .sort(); +} + +function readEpisodes(repoRoot, episodeDirPath, readJsonFn = readOptionalJson) { + const episodeFiles = listEpisodeFiles(episodeDirPath); + const validEpisodes = []; + const invalidEpisodes = []; + + for (const episodePath of episodeFiles) { + try { + const payload = readJsonFn(episodePath); + ensureSchema(payload, episodePath, SUBAGENT_EPISODE_SCHEMA); + validEpisodes.push({ path: episodePath, episode: payload }); + } catch (error) { + invalidEpisodes.push({ + path: toDisplayPath(repoRoot, episodePath), + error: error.message + }); + } + } + + return { + files: episodeFiles, + validEpisodes, + invalidEpisodes + }; +} + +function countByStatus(entries) { + const counts = new Map(); + for (const entry of entries) { + const status = normalizeText(entry.episode?.summary?.status) || 'unknown'; + counts.set(status, (counts.get(status) || 0) + 1); + } + return [...counts.entries()] + .map(([status, count]) => ({ status, count })) + .sort((left, right) => left.status.localeCompare(right.status)); +} + +function countByAgent(entries) { + const counts = new Map(); + for (const entry of entries) { + const agentId = normalizeText(entry.episode?.agent?.id) || 'unknown'; + const agentName = normalizeText(entry.episode?.agent?.name); + const key = `${agentId}::${agentName || ''}`; + const current = counts.get(key) || { + agentId, + agentName, + count: 0 + }; + current.count += 1; + counts.set(key, current); + } + return [...counts.values()].sort((left, right) => { + if (right.count !== left.count) { + return right.count - left.count; + } + return (left.agentName || left.agentId).localeCompare(right.agentName || right.agentId); + }); +} + +function sumEpisodeCost(entries, fieldName) { + return entries.reduce((sum, entry) => { + const value = normalizeFiniteNumber(entry.episode?.cost?.[fieldName]); + return sum + (value ?? 0); + }, 0); +} + +function deriveOwnerSummary(governorSummary, governorPortfolioSummary, monitoringMode) { + return { + currentOwnerRepository: + normalizeText(governorPortfolioSummary?.summary?.currentOwnerRepository) || + normalizeText(governorSummary?.summary?.currentOwnerRepository) || + normalizeText(monitoringMode?.policy?.compareRepository), + nextOwnerRepository: + normalizeText(governorPortfolioSummary?.summary?.nextOwnerRepository) || + normalizeText(governorSummary?.summary?.nextOwnerRepository), + nextAction: + normalizeText(governorPortfolioSummary?.summary?.nextAction) || + normalizeText(governorSummary?.summary?.nextAction), + governorMode: + normalizeText(governorSummary?.summary?.governorMode) || + normalizeText(governorPortfolioSummary?.summary?.governorMode), + monitoringStatus: + normalizeText(governorSummary?.summary?.monitoringStatus) || + normalizeText(monitoringMode?.summary?.status) + }; +} + +function deriveFocus(priorityCache, ownerSummary) { + const number = normalizeInteger(priorityCache?.number); + return { + activeIssue: number + ? { + number, + title: normalizeText(priorityCache?.title), + url: normalizeText(priorityCache?.url), + state: normalizeText(priorityCache?.state), + repository: normalizeText(priorityCache?.repository) + } + : null, + currentOwnerRepository: ownerSummary.currentOwnerRepository, + nextOwnerRepository: ownerSummary.nextOwnerRepository, + nextAction: ownerSummary.nextAction, + governorMode: ownerSummary.governorMode, + monitoringStatus: ownerSummary.monitoringStatus + }; +} + +function deriveSystemMemoryItems({ + repoRoot, + priorityCachePath, + governorSummaryPath, + governorSummary, + governorPortfolioSummaryPath, + governorPortfolioSummary, + focus +}) { + const items = []; + const seenIds = new Set(); + + if (focus.activeIssue) { + addUniqueMemoryItem( + items, + makeMemoryItem({ + id: `issue-${focus.activeIssue.number}`, + kind: 'active-issue', + label: `#${focus.activeIssue.number}: ${focus.activeIssue.title || 'standing priority'}`, + status: focus.activeIssue.state || 'open', + detail: 'Current standing-priority objective', + sourcePath: toDisplayPath(repoRoot, priorityCachePath), + updatedAt: normalizeText(priorityCachePath ? focus.activeIssue?.updatedAt : null), + issueNumber: focus.activeIssue.number, + repository: focus.activeIssue.repository, + nextAction: focus.nextAction + }), + seenIds + ); + } + + addUniqueMemoryItem( + items, + makeMemoryItem({ + id: 'governor-owner-decision', + kind: 'owner-decision', + label: focus.currentOwnerRepository + ? `Owner: ${focus.currentOwnerRepository}` + : 'Owner decision unavailable', + status: focus.governorMode || 'unknown', + detail: focus.nextOwnerRepository + ? `Next owner ${focus.nextOwnerRepository}` + : 'No next-owner decision recorded', + sourcePath: toDisplayPath(repoRoot, governorPortfolioSummaryPath || governorSummaryPath), + updatedAt: + normalizeText(governorPortfolioSummary?.generatedAt) || normalizeText(governorSummary?.generatedAt), + repository: focus.currentOwnerRepository, + nextAction: focus.nextAction + }), + seenIds + ); + + const releaseBlocker = normalizeText(governorSummary?.summary?.releaseSigningExternalBlocker); + const releasePublishedBundleState = normalizeText(governorSummary?.summary?.releasePublishedBundleState); + if (releaseBlocker || releasePublishedBundleState) { + addUniqueMemoryItem( + items, + makeMemoryItem({ + id: 'release-publication-blocker', + kind: 'blocker', + label: releaseBlocker || `Published bundle ${releasePublishedBundleState}`, + status: normalizeText(governorSummary?.summary?.releaseSigningStatus) || 'warn', + detail: normalizeText(governorSummary?.summary?.releasePublicationState), + sourcePath: toDisplayPath(repoRoot, governorSummaryPath), + updatedAt: normalizeText(governorSummary?.generatedAt), + issueNumber: focus.activeIssue?.number, + repository: focus.currentOwnerRepository, + nextAction: focus.nextAction + }), + seenIds + ); + } + + const dependencyStatus = normalizeText(governorPortfolioSummary?.summary?.viHistoryDistributorDependencyStatus); + if (dependencyStatus) { + addUniqueMemoryItem( + items, + makeMemoryItem({ + id: 'vi-history-distributor-dependency', + kind: 'dependency', + label: `vi-history dependency ${dependencyStatus}`, + status: dependencyStatus, + detail: + normalizeText(governorPortfolioSummary?.summary?.viHistoryDistributorDependencyExternalBlocker) || + normalizeText(governorPortfolioSummary?.summary?.viHistoryDistributorDependencyPublishedBundleState), + sourcePath: toDisplayPath(repoRoot, governorPortfolioSummaryPath), + updatedAt: normalizeText(governorPortfolioSummary?.generatedAt), + repository: + normalizeText(governorPortfolioSummary?.summary?.viHistoryDistributorDependencyTargetRepository), + nextAction: focus.nextAction + }), + seenIds + ); + } + + return { items, seenIds }; +} + +function deriveEpisodeMemoryItems(sortedEpisodes, repoRoot, seenIds) { + const hotEpisodes = []; + const warmEpisodes = []; + const usedEpisodeIds = new Set(); + + for (const entry of sortedEpisodes) { + const digest = buildEpisodeDigest(entry.episode, repoRoot, entry.path); + const detail = + digest.blocker || + digest.executionOwnershipLabel || + digest.executionPlane; + const item = makeMemoryItem({ + id: `episode-${digest.episodeId || digest.agentId || digest.generatedAt}`, + kind: 'subagent-episode', + label: `${digest.agentName || digest.agentId || 'subagent'}: ${digest.taskSummary || 'task'}`, + status: digest.status || 'reported', + detail, + executionPlane: digest.executionPlane, + cellId: digest.cellId, + dockerLaneId: digest.dockerLaneId, + harnessInstanceId: digest.harnessInstanceId, + runtimeSurface: digest.runtimeSurface, + processModelClass: digest.processModelClass, + premiumSaganMode: digest.premiumSaganMode, + executionOwnershipLabel: digest.executionOwnershipLabel, + sourcePath: digest.sourcePath, + updatedAt: digest.generatedAt, + issueNumber: normalizeInteger(entry.episode?.task?.issueNumber), + repository: normalizeText(entry.episode?.repository), + agentName: digest.agentName, + nextAction: digest.nextAction + }); + + if (usedEpisodeIds.has(item.id) || seenIds.has(item.id)) { + continue; + } + + if (isEpisodeActive(entry.episode) && hotEpisodes.length < 3) { + hotEpisodes.push(item); + usedEpisodeIds.add(item.id); + seenIds.add(item.id); + continue; + } + + if (warmEpisodes.length < 5) { + warmEpisodes.push(item); + usedEpisodeIds.add(item.id); + continue; + } + } + + const archiveCount = Math.max(sortedEpisodes.length - usedEpisodeIds.size, 0); + return { hotEpisodes, warmEpisodes, archiveCount }; +} + +function buildReport({ + repoRoot, + priorityCachePath, + priorityCache, + governorSummaryPath, + governorSummary, + governorPortfolioSummaryPath, + governorPortfolioSummary, + monitoringModePath, + monitoringMode, + operatorSteeringEventPath, + operatorSteeringEvent, + episodeDirPath, + episodes, + now +}) { + const ownerSummary = deriveOwnerSummary(governorSummary, governorPortfolioSummary, monitoringMode); + const focus = deriveFocus(priorityCache, ownerSummary); + const { items: systemItems, seenIds } = deriveSystemMemoryItems({ + repoRoot, + priorityCachePath, + governorSummaryPath, + governorSummary, + governorPortfolioSummaryPath, + governorPortfolioSummary, + focus + }); + const sortedEpisodes = sortEpisodesDescending(episodes.validEpisodes); + const { hotEpisodes, warmEpisodes, archiveCount } = deriveEpisodeMemoryItems(sortedEpisodes, repoRoot, seenIds); + const hotWorkingSet = [...systemItems, ...hotEpisodes]; + const byStatus = countByStatus(episodes.validEpisodes); + const byAgent = countByAgent(episodes.validEpisodes); + const cost = { + episodeCountWithCost: episodes.validEpisodes.filter( + (entry) => + normalizeFiniteNumber(entry.episode?.cost?.tokenUsd) != null || + normalizeFiniteNumber(entry.episode?.cost?.operatorLaborUsd) != null || + normalizeFiniteNumber(entry.episode?.cost?.blendedLowerBoundUsd) != null + ).length, + tokenUsd: Number(sumEpisodeCost(episodes.validEpisodes, 'tokenUsd').toFixed(6)), + operatorLaborUsd: Number(sumEpisodeCost(episodes.validEpisodes, 'operatorLaborUsd').toFixed(6)), + blendedLowerBoundUsd: Number(sumEpisodeCost(episodes.validEpisodes, 'blendedLowerBoundUsd').toFixed(6)), + observedDurationSeconds: Number(sumEpisodeCost(episodes.validEpisodes, 'observedDurationSeconds').toFixed(3)) + }; + const blockerCount = hotWorkingSet.filter((item) => + ['blocked', 'warn', 'fail', 'unknown', 'producer-native-incomplete'].includes((item.status || '').toLowerCase()) + ).length; + const concentrationStatus = + episodes.invalidEpisodes.length > 0 + ? 'warn' + : governorSummary || governorPortfolioSummary + ? 'pass' + : 'incomplete'; + + return { + schema: REPORT_SCHEMA, + generatedAt: now.toISOString(), + repository: + normalizeText(governorSummary?.repository) || + normalizeText(governorPortfolioSummary?.repository) || + normalizeText(priorityCache?.repository), + inputs: { + priorityCachePath: toDisplayPath(repoRoot, priorityCachePath), + governorSummaryPath: toDisplayPath(repoRoot, governorSummaryPath), + governorPortfolioSummaryPath: toDisplayPath(repoRoot, governorPortfolioSummaryPath), + monitoringModePath: toDisplayPath(repoRoot, monitoringModePath), + operatorSteeringEventPath: toDisplayPath(repoRoot, operatorSteeringEventPath), + episodeDirectoryPath: toDisplayPath(repoRoot, episodeDirPath) + }, + sources: { + priorityCache: { + path: toDisplayPath(repoRoot, priorityCachePath), + exists: Boolean(priorityCache) + }, + governorSummary: { + path: toDisplayPath(repoRoot, governorSummaryPath), + exists: Boolean(governorSummary) + }, + governorPortfolioSummary: { + path: toDisplayPath(repoRoot, governorPortfolioSummaryPath), + exists: Boolean(governorPortfolioSummary) + }, + monitoringMode: { + path: toDisplayPath(repoRoot, monitoringModePath), + exists: Boolean(monitoringMode) + }, + operatorSteeringEvent: { + path: toDisplayPath(repoRoot, operatorSteeringEventPath), + exists: Boolean(operatorSteeringEvent) + }, + episodeDirectory: { + path: toDisplayPath(repoRoot, episodeDirPath), + exists: fs.existsSync(episodeDirPath), + fileCount: episodes.files.length, + validEpisodeCount: episodes.validEpisodes.length, + invalidEpisodeCount: episodes.invalidEpisodes.length + } + }, + focus, + memory: { + hotWorkingSet, + warmMemory: warmEpisodes, + archiveCount + }, + episodes: { + totalCount: episodes.files.length, + validCount: episodes.validEpisodes.length, + invalidCount: episodes.invalidEpisodes.length, + invalidEpisodes: episodes.invalidEpisodes, + byStatus, + byAgent, + recent: sortedEpisodes.slice(0, 5).map((entry) => buildEpisodeDigest(entry.episode, repoRoot, entry.path)) + }, + cost, + summary: { + status: + normalizeText(governorSummary?.summary?.governorMode) === 'monitoring-active' ? 'monitoring' : 'active', + concentrationStatus, + currentOwnerRepository: focus.currentOwnerRepository, + nextOwnerRepository: focus.nextOwnerRepository, + nextAction: focus.nextAction, + activeIssueNumber: normalizeInteger(focus.activeIssue?.number), + hotWorkingSetCount: hotWorkingSet.length, + warmMemoryCount: warmEpisodes.length, + archiveCount, + blockerCount, + recentEpisodeCount: episodes.validEpisodes.length, + blendedLowerBoundUsd: cost.blendedLowerBoundUsd + } + }; +} + +export function parseArgs(argv = process.argv) { + const args = argv.slice(2); + const options = { + repoRoot: DEFAULT_REPO_ROOT, + priorityCachePath: DEFAULT_PRIORITY_CACHE_PATH, + governorSummaryPath: DEFAULT_GOVERNOR_SUMMARY_PATH, + governorPortfolioSummaryPath: DEFAULT_GOVERNOR_PORTFOLIO_SUMMARY_PATH, + monitoringModePath: DEFAULT_MONITORING_MODE_PATH, + operatorSteeringEventPath: DEFAULT_OPERATOR_STEERING_EVENT_PATH, + episodeDirectoryPath: DEFAULT_EPISODE_DIR, + outputPath: DEFAULT_OUTPUT_PATH, + help: false + }; + + const stringFlags = new Map([ + ['--repo-root', 'repoRoot'], + ['--priority-cache', 'priorityCachePath'], + ['--governor-summary', 'governorSummaryPath'], + ['--governor-portfolio-summary', 'governorPortfolioSummaryPath'], + ['--monitoring-mode', 'monitoringModePath'], + ['--operator-steering-event', 'operatorSteeringEventPath'], + ['--episode-directory', 'episodeDirectoryPath'], + ['--output', 'outputPath'] + ]); + + for (let index = 0; index < args.length; index += 1) { + const token = args[index]; + if (token === '-h' || token === '--help') { + options.help = true; + continue; + } + + if (stringFlags.has(token)) { + const next = args[index + 1]; + if (!next || next.startsWith('-')) { + throw new Error(`Missing value for ${token}.`); + } + index += 1; + options[stringFlags.get(token)] = next; + continue; + } + + throw new Error(`Unknown option: ${token}`); + } + + return options; +} + +function printHelp() { + console.log('Usage: node tools/priority/sagan-context-concentrator.mjs [options]'); + console.log(''); + console.log('Options:'); + console.log(' --repo-root Repository root.'); + console.log(` --priority-cache Priority cache (default: ${DEFAULT_PRIORITY_CACHE_PATH}).`); + console.log(` --governor-summary Governor summary (default: ${DEFAULT_GOVERNOR_SUMMARY_PATH}).`); + console.log( + ` --governor-portfolio-summary Governor portfolio summary (default: ${DEFAULT_GOVERNOR_PORTFOLIO_SUMMARY_PATH}).` + ); + console.log(` --monitoring-mode Monitoring mode report (default: ${DEFAULT_MONITORING_MODE_PATH}).`); + console.log( + ` --operator-steering-event Operator steering event (default: ${DEFAULT_OPERATOR_STEERING_EVENT_PATH}).` + ); + console.log(` --episode-directory Subagent episode directory (default: ${DEFAULT_EPISODE_DIR}).`); + console.log(` --output Output path (default: ${DEFAULT_OUTPUT_PATH}).`); + console.log(' -h, --help Show this help text.'); +} + +export async function runSaganContextConcentrator(options = {}, deps = {}) { + const repoRoot = path.resolve(options.repoRoot || DEFAULT_REPO_ROOT); + const priorityCachePath = path.resolve(repoRoot, options.priorityCachePath || DEFAULT_PRIORITY_CACHE_PATH); + const governorSummaryPath = path.resolve(repoRoot, options.governorSummaryPath || DEFAULT_GOVERNOR_SUMMARY_PATH); + const governorPortfolioSummaryPath = path.resolve( + repoRoot, + options.governorPortfolioSummaryPath || DEFAULT_GOVERNOR_PORTFOLIO_SUMMARY_PATH + ); + const monitoringModePath = path.resolve(repoRoot, options.monitoringModePath || DEFAULT_MONITORING_MODE_PATH); + const operatorSteeringEventPath = path.resolve( + repoRoot, + options.operatorSteeringEventPath || DEFAULT_OPERATOR_STEERING_EVENT_PATH + ); + const episodeDirPath = path.resolve(repoRoot, options.episodeDirectoryPath || DEFAULT_EPISODE_DIR); + const outputPath = path.resolve(repoRoot, options.outputPath || DEFAULT_OUTPUT_PATH); + + const readOptionalJsonFn = deps.readOptionalJsonFn || readOptionalJson; + const writeJsonFn = deps.writeJsonFn || writeJson; + const now = deps.now || new Date(); + + const priorityCache = readOptionalJsonFn(priorityCachePath); + const governorSummary = readOptionalJsonFn(governorSummaryPath); + const governorPortfolioSummary = readOptionalJsonFn(governorPortfolioSummaryPath); + const monitoringMode = readOptionalJsonFn(monitoringModePath); + const operatorSteeringEvent = readOptionalJsonFn(operatorSteeringEventPath); + const episodes = readEpisodes(repoRoot, episodeDirPath, readOptionalJsonFn); + + if (governorSummary) { + ensureSchema(governorSummary, governorSummaryPath, 'priority/autonomous-governor-summary-report@v1'); + } + if (governorPortfolioSummary) { + ensureSchema( + governorPortfolioSummary, + governorPortfolioSummaryPath, + 'priority/autonomous-governor-portfolio-summary-report@v1' + ); + } + if (monitoringMode) { + ensureSchema(monitoringMode, monitoringModePath, 'agent-handoff/monitoring-mode-v1'); + } + + const report = buildReport({ + repoRoot, + priorityCachePath, + priorityCache, + governorSummaryPath, + governorSummary, + governorPortfolioSummaryPath, + governorPortfolioSummary, + monitoringModePath, + monitoringMode, + operatorSteeringEventPath, + operatorSteeringEvent, + episodeDirPath, + episodes, + now + }); + + const writtenPath = writeJsonFn(outputPath, report); + return { report, outputPath: writtenPath }; +} + +export async function main(argv = process.argv) { + let options; + try { + options = parseArgs(argv); + } catch (error) { + console.error(`[sagan-context-concentrator] ${error.message}`); + printHelp(); + return 1; + } + + if (options.help) { + printHelp(); + return 0; + } + + try { + const { report, outputPath } = await runSaganContextConcentrator(options); + console.log( + `[sagan-context-concentrator] wrote ${outputPath} (${report.summary.concentrationStatus}, hot=${report.summary.hotWorkingSetCount})` + ); + return 0; + } catch (error) { + console.error(`[sagan-context-concentrator] ${error.message}`); + return 1; + } +} + +const modulePath = path.resolve(fileURLToPath(import.meta.url)); +const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : null; +if (invokedPath && invokedPath === modulePath) { + main(process.argv) + .then((code) => { + if (code !== 0) { + process.exitCode = code; + } + }) + .catch((error) => { + console.error(`[sagan-context-concentrator] ${error.message}`); + process.exitCode = 1; + }); +} diff --git a/tools/priority/subagent-episode.mjs b/tools/priority/subagent-episode.mjs new file mode 100644 index 000000000..b9c511655 --- /dev/null +++ b/tools/priority/subagent-episode.mjs @@ -0,0 +1,319 @@ +#!/usr/bin/env node + +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url)); +const DEFAULT_REPO_ROOT = path.resolve(MODULE_DIR, '..', '..'); + +export const REPORT_SCHEMA = 'priority/subagent-episode-report@v1'; +export const DEFAULT_OUTPUT_DIR = path.join( + 'tests', + 'results', + '_agent', + 'memory', + 'subagent-episodes' +); + +function normalizeText(value) { + if (value == null) { + return null; + } + const normalized = String(value).trim(); + return normalized.length > 0 ? normalized : null; +} + +function sanitizeSegment(value, fallback = 'episode') { + return String(value || '') + .trim() + .replace(/[^A-Za-z0-9._-]+/g, '-') + .replace(/^-+|-+$/g, '') || fallback; +} + +function normalizeStringArray(value) { + if (!Array.isArray(value)) { + return []; + } + return value.map((entry) => normalizeText(entry)).filter(Boolean); +} + +function normalizeFiniteNumber(value) { + if (value == null || value === '') { + return null; + } + const parsed = Number(value); + return Number.isFinite(parsed) ? parsed : null; +} + +function normalizeInteger(value) { + if (value == null || value === '') { + return null; + } + const parsed = Number(value); + return Number.isInteger(parsed) && parsed > 0 ? parsed : null; +} + +function normalizeBoolean(value) { + if (value === true || value === false) { + return value; + } + if (typeof value === 'string') { + const normalized = value.trim().toLowerCase(); + if (normalized === 'true') { + return true; + } + if (normalized === 'false') { + return false; + } + } + return null; +} + +function normalizeObject(value) { + return value && typeof value === 'object' && !Array.isArray(value) ? value : null; +} + +function toPortablePath(filePath) { + return String(filePath).replace(/\\/g, '/'); +} + +function toDisplayPath(repoRoot, filePath) { + if (!filePath) { + return null; + } + const resolvedRepoRoot = path.resolve(repoRoot); + const resolvedPath = path.resolve(filePath); + const relative = path.relative(resolvedRepoRoot, resolvedPath); + if (!relative.startsWith('..') && !path.isAbsolute(relative)) { + return toPortablePath(relative); + } + return toPortablePath(resolvedPath); +} + +function isValidDateTime(value) { + return typeof value === 'string' && !Number.isNaN(Date.parse(value)); +} + +function readJsonFile(filePath) { + const resolvedPath = path.resolve(filePath); + return JSON.parse(fs.readFileSync(resolvedPath, 'utf8')); +} + +function writeJsonFile(filePath, payload) { + const resolvedPath = path.resolve(filePath); + fs.mkdirSync(path.dirname(resolvedPath), { recursive: true }); + fs.writeFileSync(resolvedPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8'); + return resolvedPath; +} + +export function parseArgs(argv = process.argv) { + const args = argv.slice(2); + const options = { + repoRoot: DEFAULT_REPO_ROOT, + inputPath: null, + outputPath: null, + help: false + }; + + for (let index = 0; index < args.length; index += 1) { + const token = args[index]; + if (token === '-h' || token === '--help') { + options.help = true; + continue; + } + + if (token === '--repo-root' || token === '--input' || token === '--output') { + const next = args[index + 1]; + if (!next || next.startsWith('-')) { + throw new Error(`Missing value for ${token}.`); + } + index += 1; + if (token === '--repo-root') { + options.repoRoot = next; + } else if (token === '--input') { + options.inputPath = next; + } else if (token === '--output') { + options.outputPath = next; + } + continue; + } + + throw new Error(`Unknown option: ${token}`); + } + + if (!options.help && !normalizeText(options.inputPath)) { + throw new Error('--input is required.'); + } + + return options; +} + +function buildDefaultOutputPath(repoRoot, report) { + const timestamp = report.generatedAt.replace(/[:.]/g, '-'); + const agentSlug = sanitizeSegment(report.agent.name || report.agent.id || 'subagent', 'subagent'); + const issueSlug = Number.isInteger(report.task.issueNumber) ? `issue-${report.task.issueNumber}` : 'no-issue'; + return path.resolve( + repoRoot, + DEFAULT_OUTPUT_DIR, + `${timestamp}-${agentSlug}-${issueSlug}.json` + ); +} + +export function buildSubagentEpisodeReport(input, options = {}) { + const repoRoot = path.resolve(options.repoRoot || DEFAULT_REPO_ROOT); + const now = options.now || new Date(); + const source = normalizeObject(input) || {}; + const agent = normalizeObject(source.agent) || {}; + const task = normalizeObject(source.task) || {}; + const execution = normalizeObject(source.execution) || {}; + const summary = normalizeObject(source.summary) || {}; + const evidence = normalizeObject(source.evidence) || {}; + const cost = normalizeObject(source.cost) || {}; + const generatedAt = isValidDateTime(source.generatedAt) ? source.generatedAt : now.toISOString(); + const issueNumber = + normalizeInteger(task.issueNumber) ?? + normalizeInteger(source.issueNumber) ?? + null; + + const report = { + schema: REPORT_SCHEMA, + generatedAt, + repository: normalizeText(source.repository), + inputs: { + sourcePath: toDisplayPath(repoRoot, options.inputPath || null) + }, + episodeId: + normalizeText(source.episodeId) || + `${sanitizeSegment(agent.name || agent.id || 'subagent', 'subagent')}-${generatedAt.replace(/[:.]/g, '-')}`, + agent: { + id: normalizeText(agent.id), + name: normalizeText(agent.name), + role: normalizeText(agent.role), + model: normalizeText(agent.model) + }, + task: { + summary: normalizeText(task.summary) || '(unspecified task)', + class: normalizeText(task.class), + issueNumber, + issueUrl: normalizeText(task.issueUrl) || normalizeText(source.issueUrl) + }, + execution: { + status: normalizeText(execution.status) || 'completed', + lane: normalizeText(execution.lane), + branch: normalizeText(execution.branch), + executionPlane: normalizeText(execution.executionPlane), + dockerLaneId: normalizeText(execution.dockerLaneId), + hostCapabilityLeaseId: normalizeText(execution.hostCapabilityLeaseId), + cellId: normalizeText(execution.cellId), + executionCellLeaseId: normalizeText(execution.executionCellLeaseId), + dockerLaneLeaseId: normalizeText(execution.dockerLaneLeaseId), + cellClass: normalizeText(execution.cellClass), + suiteClass: normalizeText(execution.suiteClass), + harnessKind: normalizeText(execution.harnessKind), + harnessInstanceId: normalizeText(execution.harnessInstanceId), + runtimeSurface: normalizeText(execution.runtimeSurface), + processModelClass: normalizeText(execution.processModelClass), + operatorAuthorizationRef: normalizeText(execution.operatorAuthorizationRef), + premiumSaganMode: normalizeBoolean(execution.premiumSaganMode) + }, + summary: { + status: normalizeText(summary.status) || normalizeText(source.status) || 'reported', + outcome: normalizeText(summary.outcome), + blocker: normalizeText(summary.blocker), + nextAction: normalizeText(summary.nextAction), + detail: normalizeText(summary.detail) + }, + evidence: { + filesTouched: normalizeStringArray(evidence.filesTouched), + receipts: normalizeStringArray(evidence.receipts), + commands: normalizeStringArray(evidence.commands), + notes: normalizeStringArray(evidence.notes) + }, + cost: { + observedDurationSeconds: + normalizeFiniteNumber(cost.observedDurationSeconds) ?? + normalizeFiniteNumber(cost.elapsedSeconds), + tokenUsd: normalizeFiniteNumber(cost.tokenUsd), + operatorLaborUsd: normalizeFiniteNumber(cost.operatorLaborUsd), + blendedLowerBoundUsd: + normalizeFiniteNumber(cost.blendedLowerBoundUsd) ?? + normalizeFiniteNumber(cost.blendedUsd) + } + }; + + return report; +} + +export async function runSubagentEpisode(options = {}, deps = {}) { + const repoRoot = path.resolve(options.repoRoot || DEFAULT_REPO_ROOT); + const inputPath = path.resolve(repoRoot, options.inputPath); + const readJsonFn = deps.readJsonFn || readJsonFile; + const writeJsonFn = deps.writeJsonFn || writeJsonFile; + const now = deps.now || new Date(); + + const input = readJsonFn(inputPath); + const report = buildSubagentEpisodeReport(input, { + repoRoot, + inputPath, + now + }); + const outputPath = path.resolve( + repoRoot, + options.outputPath || buildDefaultOutputPath(repoRoot, report) + ); + const writtenPath = writeJsonFn(outputPath, report); + return { report, outputPath: writtenPath }; +} + +function printHelp() { + console.log('Usage: node tools/priority/subagent-episode.mjs [options]'); + console.log(''); + console.log('Options:'); + console.log(' --repo-root Repository root (default: current repo).'); + console.log(' --input Required input JSON path describing a subagent episode.'); + console.log(` --output Optional output path (default under ${DEFAULT_OUTPUT_DIR}).`); + console.log(' -h, --help Show this help text.'); +} + +export async function main(argv = process.argv) { + let options; + try { + options = parseArgs(argv); + } catch (error) { + console.error(`[subagent-episode] ${error.message}`); + printHelp(); + return 1; + } + + if (options.help) { + printHelp(); + return 0; + } + + try { + const { report, outputPath } = await runSubagentEpisode(options); + console.log( + `[subagent-episode] wrote ${outputPath} (${report.agent.name || report.agent.id || 'subagent'} -> ${report.summary.status})` + ); + return 0; + } catch (error) { + console.error(`[subagent-episode] ${error.message}`); + return 1; + } +} + +const modulePath = path.resolve(fileURLToPath(import.meta.url)); +const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : null; +if (invokedPath && invokedPath === modulePath) { + main(process.argv) + .then((code) => { + if (code !== 0) { + process.exitCode = code; + } + }) + .catch((error) => { + console.error(`[subagent-episode] ${error.message}`); + process.exitCode = 1; + }); +}