diff --git a/.gitignore b/.gitignore index ad50ed10..e037ce18 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,4 @@ sessions/ kanban.db config/runtime-config/live/ config/runtime-config/private/ +tools/kurultai/buildroom/.generated/ diff --git a/tests/kurultai/test_buildroom_foundation.py b/tests/kurultai/test_buildroom_foundation.py index 7d02ac74..6a5c8609 100644 --- a/tests/kurultai/test_buildroom_foundation.py +++ b/tests/kurultai/test_buildroom_foundation.py @@ -176,6 +176,9 @@ def test_approved_build_plan_creates_kanban_task_packet_with_parent_refs(tmp_pat assert packet["title"] == "buildroom: Buildroom foundation demo" assert packet["assignee"] == "chagatai" assert packet["parents"] == ["t_parent_one", "t_parent_two"] + assert packet["workspace_kind"] == "dir" + assert Path(packet["workspace_path"]).is_absolute() + assert "${" not in packet["workspace_path"] assert packet["idempotency_key"] == "buildroom:build-buildroom-foundation-demo" assert packet["metadata"]["build_id"] == "build-buildroom-foundation-demo" assert "Allowed paths:" in packet["body"] @@ -267,6 +270,9 @@ def test_independent_qa_task_packet_uses_build_plan_and_receipt_parent(tmp_path: assert packet["title"] == "buildroom-qa: Buildroom foundation demo" assert packet["assignee"] == "ogedei" assert packet["parents"] == ["t_build123"] + assert packet["workspace_kind"] == "dir" + assert Path(packet["workspace_path"]).is_absolute() + assert "${" not in packet["workspace_path"] assert packet["idempotency_key"] == "buildroom-qa:build-buildroom-foundation-demo" assert packet["metadata"]["build_id"] == "build-buildroom-foundation-demo" assert packet["metadata"]["independent_of"] == "chagatai" @@ -521,3 +527,145 @@ def test_control_room_cron_fail_on_actionable_exit_code(tmp_path): "--fail-on-actionable", ) assert result.returncode == 2 + + + +def test_apply_kanban_drafts_dry_run_dedupes(tmp_path: Path) -> None: + drafts = tmp_path / "drafts.json" + state = tmp_path / "state.json" + ledger = tmp_path / "ledger.json" + write_json(drafts, {"drafts": [ + {"title": "Review room", "metadata": {"attention_item_id": "same", "room_id": "demo-room", "severity": "high", "reason": "watch"}}, + {"title": "Review room duplicate", "metadata": {"attention_item_id": "same", "room_id": "demo-room", "severity": "high", "reason": "watch"}}, + ]}) + result = run_script("tools/kurultai/buildroom/scripts/apply_kanban_drafts.py", "--drafts", str(drafts), "--state", str(state), "--ledger", str(ledger)) + assert result.returncode == 0, result.stdout + payload = read_json(ledger) + assert payload["mode"] == "dry-run" + assert payload["summary"]["drafts"] == 2 + assert payload["summary"]["unique"] == 1 + assert payload["summary"]["new"] == 1 + task = payload["tasks"][0] + assert task["workspace_kind"] == "dir" + assert Path(task["workspace_path"]).is_absolute() + assert "${" not in task["workspace_path"] + + +def test_apply_kanban_drafts_apply_is_idempotent(tmp_path: Path) -> None: + import sqlite3 + db = tmp_path / "kanban.db" + con = sqlite3.connect(db) + con.execute("CREATE TABLE tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL, body TEXT, assignee TEXT, status TEXT NOT NULL, priority INTEGER DEFAULT 0, created_by TEXT, created_at INTEGER NOT NULL, started_at INTEGER, completed_at INTEGER, workspace_kind TEXT NOT NULL DEFAULT 'scratch', workspace_path TEXT, claim_lock TEXT, claim_expires INTEGER, tenant TEXT, result TEXT, idempotency_key TEXT, spawn_failures INTEGER NOT NULL DEFAULT 0, worker_pid INTEGER, last_spawn_error TEXT, max_runtime_seconds INTEGER, last_heartbeat_at INTEGER, current_run_id INTEGER, workflow_template_id TEXT, current_step_key TEXT, skills TEXT)") + con.execute("CREATE TABLE task_events (id INTEGER PRIMARY KEY AUTOINCREMENT, task_id TEXT NOT NULL, run_id INTEGER, kind TEXT NOT NULL, payload TEXT, created_at INTEGER NOT NULL)") + con.commit() + con.close() + drafts = tmp_path / "drafts.json" + state = tmp_path / "state.json" + ledger = tmp_path / "ledger.json" + write_json(drafts, {"drafts": [{"title": "Review room", "metadata": {"attention_item_id": "same", "room_id": "demo-room"}}]}) + first = run_script("tools/kurultai/buildroom/scripts/apply_kanban_drafts.py", "--drafts", str(drafts), "--state", str(state), "--ledger", str(ledger), "--kanban-db", str(db), "--apply") + second = run_script("tools/kurultai/buildroom/scripts/apply_kanban_drafts.py", "--drafts", str(drafts), "--state", str(state), "--ledger", str(ledger), "--kanban-db", str(db), "--apply") + assert first.returncode == 0, first.stdout + assert second.returncode == 0, second.stdout + con = sqlite3.connect(db) + assert con.execute("select count(*) from tasks").fetchone()[0] == 1 + workspace_kind, workspace_path = con.execute("select workspace_kind, workspace_path from tasks").fetchone() + assert workspace_kind == "dir" + assert Path(workspace_path).is_absolute() + assert "${" not in workspace_path + assert con.execute("select count(*) from task_events").fetchone()[0] == 1 + con.close() + + +def test_retention_review_emits_non_destructive_followups(tmp_path: Path) -> None: + room = copy_demo_room(tmp_path, "retention-room") + retention = room / "retention" / "retention-review.json" + data = read_json(retention) + data["recommendation"] = "improve" + data["status"] = "pending" + write_json(retention, data) + output = tmp_path / "retention-items.json" + result = run_script("tools/kurultai/buildroom/scripts/retention_review.py", "--rooms", str(tmp_path), "--output", str(output)) + assert result.returncode == 0, result.stdout + payload = read_json(output) + assert payload["summary"]["items"] == 1 + assert payload["items"][0]["needs_receipt"] is True + assert payload["items"][0]["room_id"] == "retention-room" + + +def test_buildroom_healthcheck_quick_mode_passes() -> None: + result = run_script("tools/kurultai/buildroom/scripts/buildroom_healthcheck.py", "--skip-pytest") + assert result.returncode == 0, result.stdout + assert "buildroom healthcheck: PASS" in result.stdout + + + +def test_opportunity_radar_generates_ranked_candidates(tmp_path: Path) -> None: + brain = tmp_path / "brain" + (brain / "docs" / "designs").mkdir(parents=True) + (brain / "docs" / "designs" / "radar.md").write_text( + "# Opportunity Radar\n\nAuto Think should rank what should we build next.\n", + encoding="utf-8", + ) + json_out = tmp_path / "opportunity-radar.json" + md_out = tmp_path / "opportunity-radar.md" + drafts_out = tmp_path / "drafts.json" + result = subprocess.run( + [ + "python3", + str(BUILDROOM / "scripts" / "opportunity_radar.py"), + "--brain-root", + str(brain), + "--buildroom-root", + str(BUILDROOM), + "--json-output", + str(json_out), + "--markdown-output", + str(md_out), + "--kanban-drafts-output", + str(drafts_out), + "--json", + ], + cwd=ROOT, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + check=True, + ) + payload = json.loads(json_out.read_text()) + assert payload["recommended_next_action"] == "Buildroom Opportunity Radar v0" + assert payload["candidates"][0]["scores"]["total"] >= 38 + assert "Buildroom Opportunity Radar" in md_out.read_text() + drafts = json.loads(drafts_out.read_text()) + assert drafts["draft_count"] >= 1 + assert "candidate_count" in result.stdout + + +def test_run_buildroom_cycle_completes_with_dry_run_receipt(tmp_path: Path) -> None: + receipt = tmp_path / "cycle-receipt.json" + result = subprocess.run( + [ + "python3", + str(BUILDROOM / "scripts" / "run_buildroom_cycle.py"), + "--skip-pytest", + "--receipt-output", + str(receipt), + "--json", + ], + cwd=ROOT, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + check=True, + ) + summary = json.loads(result.stdout) + payload = json.loads(receipt.read_text()) + assert summary["ok"] is True + assert payload["mode"] == "dry-run" + assert payload["ok"] is True + assert payload["step_count"] >= 6 + assert "proposal_summary" in summary + assert "proposal_summary" in payload + assert summary["proposal_summary"]["outcome"]["opportunity_radar_candidates"] >= 1 + assert summary["proposal_summary"]["proposals"][0]["title"] == "Buildroom Opportunity Radar v0" + assert summary["proposal_summary"]["proposals"][0]["summary"] diff --git a/tools/kurultai/buildroom/control-room-attention-items.json b/tools/kurultai/buildroom/control-room-attention-items.json index 624d1574..cdb106ef 100644 --- a/tools/kurultai/buildroom/control-room-attention-items.json +++ b/tools/kurultai/buildroom/control-room-attention-items.json @@ -25,7 +25,7 @@ "summary": "Room is in watch state." } ], - "generated_at": "2026-05-11T02:09:12Z", + "generated_at": "2026-05-11T02:29:48Z", "items": [ { "category": "operator_decision", diff --git a/tools/kurultai/buildroom/control-room-kanban-drafts.json b/tools/kurultai/buildroom/control-room-kanban-drafts.json index bc69cd1f..d8c23ec1 100644 --- a/tools/kurultai/buildroom/control-room-kanban-drafts.json +++ b/tools/kurultai/buildroom/control-room-kanban-drafts.json @@ -28,6 +28,6 @@ "title": "Buildroom follow-up: 2026-05-10-gkisokay-auto-think-auto-build \u2014 trust" } ], - "generated_at": "2026-05-11T02:09:12Z", + "generated_at": "2026-05-11T02:29:48Z", "source": "buildroom-control-room-cron-v0" } diff --git a/tools/kurultai/buildroom/control-room-retention-items.json b/tools/kurultai/buildroom/control-room-retention-items.json new file mode 100644 index 00000000..2a8766c1 --- /dev/null +++ b/tools/kurultai/buildroom/control-room-retention-items.json @@ -0,0 +1,31 @@ +{ + "items": [ + { + "id": "retention:2026-05-10-gkisokay-auto-think-auto-build", + "needs_receipt": true, + "next_action": "review retention recommendation and write receipt", + "receipt_template": "brain/receipts/kurultai/{date}-2026-05-10-gkisokay-auto-think-auto-build-retention-review.md", + "recommendation": "keep", + "room_id": "2026-05-10-gkisokay-auto-think-auto-build", + "severity": "medium", + "status": "pending", + "trust_state": "watch" + }, + { + "id": "retention:demo-room", + "needs_receipt": true, + "next_action": "review retention recommendation and write receipt", + "receipt_template": "brain/receipts/kurultai/{date}-demo-room-retention-review.md", + "recommendation": "keep", + "room_id": "demo-room", + "severity": "low", + "status": "pending", + "trust_state": "clean" + } + ], + "rooms_root": "rooms", + "summary": { + "items": 2, + "needs_receipt": 2 + } +} diff --git a/tools/kurultai/buildroom/control-room.html b/tools/kurultai/buildroom/control-room.html index 5cf4b43b..97173a9a 100644 --- a/tools/kurultai/buildroom/control-room.html +++ b/tools/kurultai/buildroom/control-room.html @@ -31,7 +31,7 @@

Kurultai / Buildroom

Control Room

-

Generated 2026-05-11T02:09:12Z. Static, public-safe dashboard for buildroom trust and evidence.

+

Generated 2026-05-11T02:29:48Z. Static, public-safe dashboard for buildroom trust and evidence.

@@ -239,7 +239,7 @@

Install a typed buildroom foundation for autonomous improvement work

} ], "summary": { - "generated_at": "2026-05-11T02:09:12Z", + "generated_at": "2026-05-11T02:29:48Z", "room_count": 2, "rooms_needing_decision": 1, "rooms_with_missing_evidence": 0, diff --git a/tools/kurultai/buildroom/control-room.json b/tools/kurultai/buildroom/control-room.json index fae9375e..342dfc29 100644 --- a/tools/kurultai/buildroom/control-room.json +++ b/tools/kurultai/buildroom/control-room.json @@ -135,7 +135,7 @@ } ], "summary": { - "generated_at": "2026-05-11T02:09:12Z", + "generated_at": "2026-05-11T02:29:48Z", "room_count": 2, "rooms_needing_decision": 1, "rooms_with_missing_evidence": 0, diff --git a/tools/kurultai/buildroom/control-room.md b/tools/kurultai/buildroom/control-room.md index 17d55d7c..c3ec3ff6 100644 --- a/tools/kurultai/buildroom/control-room.md +++ b/tools/kurultai/buildroom/control-room.md @@ -1,6 +1,6 @@ # Kurultai Buildroom Control Room -Generated: 2026-05-11T02:09:12Z +Generated: 2026-05-11T02:29:48Z ## Summary diff --git a/tools/kurultai/buildroom/docs/control-room-operating-loop.md b/tools/kurultai/buildroom/docs/control-room-operating-loop.md new file mode 100644 index 00000000..faf5a8df --- /dev/null +++ b/tools/kurultai/buildroom/docs/control-room-operating-loop.md @@ -0,0 +1,52 @@ +# Buildroom Control Room Operating Loop + +The Control Room loop turns buildroom contracts into recurring caretaker work. + +## Commands + +Run from the Kurultai repo root: + +- `python3 tools/kurultai/buildroom/scripts/control_room_cron.py` +- `python3 tools/kurultai/buildroom/scripts/apply_kanban_drafts.py` +- `python3 tools/kurultai/buildroom/scripts/retention_review.py` +- `python3 tools/kurultai/buildroom/scripts/buildroom_healthcheck.py` + +## Loop phases + +1. Regenerate the dashboard: `control-room.md/json/html`. +2. Emit attention items: `control-room-attention-items.json`. +3. Emit Kanban drafts: `control-room-kanban-drafts.json`. +4. Dry-run the Kanban bridge and write an apply ledger under `.generated/`. +5. Emit retention review items: `control-room-retention-items.json`. +6. Run the healthcheck canary before any PR or scheduled rollout. + +## Kanban bridge safety + +`apply_kanban_drafts.py` is dry-run by default. It only creates real Hermes Kanban tasks when `--apply` is passed. + +Dedupe is based on a stable attention-item key and the Hermes task `idempotency_key`, prefixed with `buildroom-control-room:`. This prevents repeated cron runs from spamming duplicate tasks. + +## Retention safety + +`retention_review.py` never deletes buildroom artifacts. It only emits follow-up items and receipt templates for keep/improve/park/prune decisions. + +## Healthcheck contract + +`buildroom_healthcheck.py` verifies: + +- demo room validates +- real gkisokay room validates +- Control Room generation succeeds +- Control Room Cron succeeds +- Kanban bridge dry-run succeeds +- generated files exist +- buildroom scripts compile +- focused buildroom pytest passes unless `--skip-pytest` is used + +## Suggested Hermes cron + +Conservative schedule: every 60 minutes. + +Command shape: run healthcheck in quick mode, run Control Room Cron, dry-run the Kanban bridge, and emit retention review items. + +Default behavior remains proposal-only. Add `--apply` to the Kanban bridge only after the dedupe ledger and task bodies are reviewed. diff --git a/tools/kurultai/buildroom/docs/opportunity-radar.md b/tools/kurultai/buildroom/docs/opportunity-radar.md new file mode 100644 index 00000000..74b218c9 --- /dev/null +++ b/tools/kurultai/buildroom/docs/opportunity-radar.md @@ -0,0 +1,31 @@ +# Buildroom Opportunity Radar + +Opportunity Radar is the Auto Think layer for the buildroom system. It ranks evidence-backed build candidates from Brain and buildroom state, emits inspectable JSON/markdown reports, and drafts follow-up work without applying side effects by default. + +Run: + +```bash +python3 tools/kurultai/buildroom/scripts/opportunity_radar.py +``` + +Outputs: + +- `tools/kurultai/buildroom/opportunity-radar.json` +- `tools/kurultai/buildroom/opportunity-radar.md` +- `tools/kurultai/buildroom/opportunity-radar-kanban-drafts.json` + +Buildroom cycle command: + +```bash +python3 tools/kurultai/buildroom/scripts/run_buildroom_cycle.py +``` + +This validates rooms, regenerates Control Room outputs, runs cron extraction, runs Opportunity Radar, performs a Kanban bridge dry-run, runs retention review, runs healthcheck, and writes `buildroom-cycle-receipt.json`. + +Real Kanban creation requires: + +```bash +python3 tools/kurultai/buildroom/scripts/run_buildroom_cycle.py --apply-kanban +``` + +Safety defaults: local-first, dry-run by default, no external side effects, no hard deletes, inspectable generated artifacts, and explicit apply modes. diff --git a/tools/kurultai/buildroom/opportunity-radar-kanban-drafts.json b/tools/kurultai/buildroom/opportunity-radar-kanban-drafts.json new file mode 100644 index 00000000..b0b5f7f9 --- /dev/null +++ b/tools/kurultai/buildroom/opportunity-radar-kanban-drafts.json @@ -0,0 +1,46 @@ +{ + "draft_count": 3, + "drafts": [ + { + "body": "Candidate: Buildroom Opportunity Radar v0\nType: system-improvement\nScore: 49.9\nDisposition: buildroom-candidate\nWhy now: Kurultai has a strong Auto Build loop, but candidate selection is still less inspectable than execution.\nExpected leverage: Turns Brain artifacts into recurring ranked build intent and reduces ad hoc operator steering.\nAcceptance criteria:\n- emit JSON and markdown reports\n- score candidates explainably\n- emit dry-run Kanban drafts\n- link recommendations to source refs\n- pass tests and leakage scan", + "candidate_id": "e5b14f92b7ca", + "labels": [ + "buildroom", + "opportunity-radar", + "system-improvement", + "buildroom-candidate" + ], + "priority": "high", + "source": "buildroom-opportunity-radar-v0", + "title": "Buildroom opportunity: Buildroom Opportunity Radar v0" + }, + { + "body": "Candidate: Brain Compiler Coverage Ledger\nType: brain-compiler\nScore: 47.9\nDisposition: buildroom-candidate\nWhy now: The Brain has more durable artifacts; now it needs coverage accounting to prevent passive storage.\nExpected leverage: Enforces the invariant that memory only matters when it changes future behavior or gets a no-op receipt.\nAcceptance criteria:\n- scan queue/generated/reviews/proposals/receipts\n- identify missing dispositions\n- emit coverage report", + "candidate_id": "19eca945eb9b", + "labels": [ + "buildroom", + "opportunity-radar", + "brain-compiler", + "buildroom-candidate" + ], + "priority": "high", + "source": "buildroom-opportunity-radar-v0", + "title": "Buildroom opportunity: Brain Compiler Coverage Ledger" + }, + { + "body": "Candidate: Agent Harness Maturity Scoreboard\nType: evaluation-candidate\nScore: 46.7\nDisposition: buildroom-candidate\nWhy now: The agent-engineering research argues that agent engineering is harness engineering.\nExpected leverage: Makes agent trust and hardening gaps visible before autonomy expands.\nAcceptance criteria:\n- produce explainable maturity scores\n- surface improvement recommendations\n- integrate with Control Room", + "candidate_id": "05b418059610", + "labels": [ + "buildroom", + "opportunity-radar", + "evaluation-candidate", + "buildroom-candidate" + ], + "priority": "high", + "source": "buildroom-opportunity-radar-v0", + "title": "Buildroom opportunity: Agent Harness Maturity Scoreboard" + } + ], + "generated_at": "2026-05-11T03:21:25Z", + "source": "buildroom-opportunity-radar-v0" +} diff --git a/tools/kurultai/buildroom/opportunity-radar.json b/tools/kurultai/buildroom/opportunity-radar.json new file mode 100644 index 00000000..49511447 --- /dev/null +++ b/tools/kurultai/buildroom/opportunity-radar.json @@ -0,0 +1,603 @@ +{ + "candidates": [ + { + "acceptance_criteria": [ + "emit JSON and markdown reports", + "score candidates explainably", + "emit dry-run Kanban drafts", + "link recommendations to source refs", + "pass tests and leakage scan" + ], + "candidate_type": "system-improvement", + "dependencies": [ + "Brain artifacts", + "buildroom Control Room outputs" + ], + "expected_leverage": "Turns Brain artifacts into recurring ranked build intent and reduces ad hoc operator steering.", + "explainability": { + "counterarguments": [ + "Could create noisy recommendations if reports are not capped", + "Scoring must not replace verified user value" + ], + "top_negative_factors": [ + "requires calibration against outcomes" + ], + "top_positive_factors": [ + "compounds future agent behavior", + "local-first and reversible", + "8 supporting source artifacts" + ] + }, + "id": "e5b14f92b7ca", + "proposed_artifacts": [], + "recommended_disposition": "buildroom-candidate", + "risks": [ + "scope creep", + "duplicate work", + "weak outcome feedback" + ], + "scores": { + "compounding_value": 5, + "dependency_readiness": 5, + "future_behavior_improvement": 5, + "risk_reversibility": 5, + "total": 49.9, + "tractability": 4, + "urgency_freshness": 4, + "user_leverage": 5, + "verification_clarity": 4 + }, + "source_evidence": [ + { + "hit_count": 4, + "kind": "docs/designs", + "ref": "brain/docs/designs/2026-05-10-buildroom-opportunity-radar-intelligent-design.md", + "title": "Buildroom Opportunity Radar intelligent design and SWOT" + }, + { + "hit_count": 2, + "kind": "docs/proposals", + "ref": "brain/docs/proposals/2026-05-11-build-proposals-from-compiled-research.md", + "title": "Build proposals from compiled Brain research" + }, + { + "hit_count": 1, + "kind": "docs/plans", + "ref": "brain/docs/plans/2026-05-10-opportunity-radar-and-buildroom-cycle-implementation-plan.md", + "title": "Opportunity Radar and buildroom cycle command implementation plan" + }, + { + "hit_count": 1, + "kind": "generated", + "ref": "brain/generated/2026-05-10-gkisokay-auto-think-auto-build-synthesis.md", + "title": "Graeme gkisokay Auto Think Auto Build Synthesis" + }, + { + "hit_count": 1, + "kind": "generated", + "ref": "brain/generated/reviews/2026-05-10-gkisokay-auto-think-auto-build-review.md", + "title": "Review - Graeme gkisokay Auto Think Auto Build Synthesis" + }, + { + "hit_count": 1, + "kind": "queue", + "ref": "brain/queue/2026-05-10-gkisokay-hermes-auto-think-auto-build.md", + "title": "Graeme gkisokay Hermes Auto Think Auto Build Research Packet" + }, + { + "hit_count": 1, + "kind": "receipts", + "ref": "brain/receipts/kurultai/2026-05-10-gkisokay-auto-think-auto-build-research-to-brain.md", + "title": "Receipt - gkisokay Auto Think Auto Build Research Compiled Into Brain" + }, + { + "hit_count": 1, + "kind": "receipts", + "ref": "brain/receipts/kurultai/2026-05-10-kurultai-buildroom-contract-slice-implemented.md", + "title": "Receipt - Kurultai Buildroom Contract Slice Implemented" + } + ], + "source_refs": [ + "brain/docs/designs/2026-05-10-buildroom-opportunity-radar-intelligent-design.md", + "brain/docs/proposals/2026-05-11-build-proposals-from-compiled-research.md", + "brain/docs/plans/2026-05-10-opportunity-radar-and-buildroom-cycle-implementation-plan.md", + "brain/generated/2026-05-10-gkisokay-auto-think-auto-build-synthesis.md", + "brain/generated/reviews/2026-05-10-gkisokay-auto-think-auto-build-review.md", + "brain/queue/2026-05-10-gkisokay-hermes-auto-think-auto-build.md", + "brain/receipts/kurultai/2026-05-10-gkisokay-auto-think-auto-build-research-to-brain.md", + "brain/receipts/kurultai/2026-05-10-kurultai-buildroom-contract-slice-implemented.md" + ], + "summary": "Build a local-first strategy layer that ranks next buildroom candidates from Brain and buildroom evidence.", + "title": "Buildroom Opportunity Radar v0", + "why_now": "Kurultai has a strong Auto Build loop, but candidate selection is still less inspectable than execution." + }, + { + "acceptance_criteria": [ + "scan queue/generated/reviews/proposals/receipts", + "identify missing dispositions", + "emit coverage report" + ], + "candidate_type": "brain-compiler", + "dependencies": [ + "Brain artifacts", + "buildroom Control Room outputs" + ], + "expected_leverage": "Enforces the invariant that memory only matters when it changes future behavior or gets a no-op receipt.", + "explainability": { + "counterarguments": [ + "Could create noisy recommendations if reports are not capped", + "Scoring must not replace verified user value" + ], + "top_negative_factors": [ + "requires calibration against outcomes" + ], + "top_positive_factors": [ + "compounds future agent behavior", + "local-first and reversible", + "8 supporting source artifacts" + ] + }, + "id": "19eca945eb9b", + "proposed_artifacts": [], + "recommended_disposition": "buildroom-candidate", + "risks": [ + "scope creep", + "duplicate work", + "weak outcome feedback" + ], + "scores": { + "compounding_value": 5, + "dependency_readiness": 4, + "future_behavior_improvement": 5, + "risk_reversibility": 5, + "total": 47.9, + "tractability": 4, + "urgency_freshness": 3, + "user_leverage": 4, + "verification_clarity": 5 + }, + "source_evidence": [ + { + "hit_count": 5, + "kind": "docs/proposals", + "ref": "brain/docs/proposals/2026-05-11-build-proposals-from-compiled-research.md", + "title": "Build proposals from compiled Brain research" + }, + { + "hit_count": 4, + "kind": "docs/designs", + "ref": "brain/docs/designs/2026-05-10-buildroom-opportunity-radar-intelligent-design.md", + "title": "Buildroom Opportunity Radar intelligent design and SWOT" + }, + { + "hit_count": 3, + "kind": "docs/kurultai", + "ref": "brain/docs/kurultai/command-doctrine-v1.md", + "title": "Kurultai Command Doctrine v1" + }, + { + "hit_count": 3, + "kind": "docs/plans", + "ref": "brain/docs/plans/2026-05-10-entangled-agent-capability-compiler-implementation-plan.md", + "title": "Entangled Agent Capability Compiler Implementation Plan" + }, + { + "hit_count": 3, + "kind": "docs/plans", + "ref": "brain/docs/plans/2026-05-10-kurultai-learn-retro-command-doctrine-vertical-slice.md", + "title": "Kurultai Learn/Retro Command Doctrine Vertical Slice" + }, + { + "hit_count": 2, + "kind": "docs/plans", + "ref": "brain/docs/plans/2026-05-10-memory-is-leverage-doctrine-implementation-plan.md", + "title": "Memory Is Leverage \u2014 Doctrine Implementation Plan" + }, + { + "hit_count": 2, + "kind": "generated", + "ref": "brain/generated/2026-05-10-gkisokay-auto-think-auto-build-synthesis.md", + "title": "Graeme gkisokay Auto Think Auto Build Synthesis" + }, + { + "hit_count": 1, + "kind": "docs/kurultai", + "ref": "brain/docs/kurultai/agent-operable-brain-os.md", + "title": "Agent-operable Brain OS" + } + ], + "source_refs": [ + "brain/docs/proposals/2026-05-11-build-proposals-from-compiled-research.md", + "brain/docs/designs/2026-05-10-buildroom-opportunity-radar-intelligent-design.md", + "brain/docs/kurultai/command-doctrine-v1.md", + "brain/docs/plans/2026-05-10-entangled-agent-capability-compiler-implementation-plan.md", + "brain/docs/plans/2026-05-10-kurultai-learn-retro-command-doctrine-vertical-slice.md", + "brain/docs/plans/2026-05-10-memory-is-leverage-doctrine-implementation-plan.md", + "brain/generated/2026-05-10-gkisokay-auto-think-auto-build-synthesis.md", + "brain/docs/kurultai/agent-operable-brain-os.md" + ], + "summary": "Report which meaningful sources changed behavior and which still need synthesis, skill, Kanban, receipt, or no-op disposition.", + "title": "Brain Compiler Coverage Ledger", + "why_now": "The Brain has more durable artifacts; now it needs coverage accounting to prevent passive storage." + }, + { + "acceptance_criteria": [ + "produce explainable maturity scores", + "surface improvement recommendations", + "integrate with Control Room" + ], + "candidate_type": "evaluation-candidate", + "dependencies": [ + "Brain artifacts", + "buildroom Control Room outputs" + ], + "expected_leverage": "Makes agent trust and hardening gaps visible before autonomy expands.", + "explainability": { + "counterarguments": [ + "Could create noisy recommendations if reports are not capped", + "Scoring must not replace verified user value" + ], + "top_negative_factors": [ + "requires calibration against outcomes" + ], + "top_positive_factors": [ + "compounds future agent behavior", + "local-first and reversible", + "8 supporting source artifacts" + ] + }, + "id": "05b418059610", + "proposed_artifacts": [], + "recommended_disposition": "buildroom-candidate", + "risks": [ + "scope creep", + "duplicate work", + "weak outcome feedback" + ], + "scores": { + "compounding_value": 5, + "dependency_readiness": 4, + "future_behavior_improvement": 5, + "risk_reversibility": 5, + "total": 46.7, + "tractability": 4, + "urgency_freshness": 3, + "user_leverage": 4, + "verification_clarity": 4 + }, + "source_evidence": [ + { + "hit_count": 6, + "kind": "docs/proposals", + "ref": "brain/docs/proposals/2026-05-11-build-proposals-from-compiled-research.md", + "title": "Build proposals from compiled Brain research" + }, + { + "hit_count": 5, + "kind": "generated", + "ref": "brain/generated/2026-05-11-av1dlive-agent-engineering-roadmap-synthesis.md", + "title": "Synthesis \u2014 Avid 2026 AI Agent Engineering Roadmap" + }, + { + "hit_count": 5, + "kind": "generated", + "ref": "brain/generated/reviews/2026-05-11-av1dlive-agent-engineering-roadmap-review.md", + "title": "Review \u2014 Avid 2026 AI Agent Engineering Roadmap" + }, + { + "hit_count": 4, + "kind": "docs/plans", + "ref": "brain/docs/plans/2026-04-21-parse-post-training-kickoff-prompt.md", + "title": "Kickoff prompt \u2014 Parse post-training deployment + narrative push" + }, + { + "hit_count": 4, + "kind": "queue", + "ref": "brain/queue/2026-05-11-av1dlive-agent-engineering-roadmap.md", + "title": "Avid / @Av1dlive \u2014 2026 AI Agent Engineering Roadmap" + }, + { + "hit_count": 4, + "kind": "receipts", + "ref": "brain/receipts/kurultai/2026-05-11-av1dlive-agent-engineering-roadmap-research.md", + "title": "Receipt \u2014 Avid 2026 AI Agent Engineering Roadmap Research" + }, + { + "hit_count": 3, + "kind": "docs/plans", + "ref": "brain/docs/plans/2026-04-21-parse-soft-launch-option-a-kickoff-prompt.md", + "title": "Kickoff prompt \u2014 Parse for Agents soft launch (Option A)" + }, + { + "hit_count": 3, + "kind": "docs/plans", + "ref": "brain/docs/plans/2026-04-22-meta-harness-option-b-design-v2.md", + "title": "2026-04-22-meta-harness-option-b-design-v2" + } + ], + "source_refs": [ + "brain/docs/proposals/2026-05-11-build-proposals-from-compiled-research.md", + "brain/generated/2026-05-11-av1dlive-agent-engineering-roadmap-synthesis.md", + "brain/generated/reviews/2026-05-11-av1dlive-agent-engineering-roadmap-review.md", + "brain/docs/plans/2026-04-21-parse-post-training-kickoff-prompt.md", + "brain/queue/2026-05-11-av1dlive-agent-engineering-roadmap.md", + "brain/receipts/kurultai/2026-05-11-av1dlive-agent-engineering-roadmap-research.md", + "brain/docs/plans/2026-04-21-parse-soft-launch-option-a-kickoff-prompt.md", + "brain/docs/plans/2026-04-22-meta-harness-option-b-design-v2.md" + ], + "summary": "Score projects, agents, and buildrooms against harness maturity primitives.", + "title": "Agent Harness Maturity Scoreboard", + "why_now": "The agent-engineering research argues that agent engineering is harness engineering." + }, + { + "acceptance_criteria": [ + "extract claims/evidence", + "scan public/private boundary", + "draft publish/no-op recommendation" + ], + "candidate_type": "content-candidate", + "dependencies": [ + "Brain artifacts", + "buildroom Control Room outputs" + ], + "expected_leverage": "Turns proven internal work into public learning when safe.", + "explainability": { + "counterarguments": [ + "Could create noisy recommendations if reports are not capped", + "Scoring must not replace verified user value" + ], + "top_negative_factors": [ + "requires calibration against outcomes" + ], + "top_positive_factors": [ + "compounds future agent behavior", + "local-first and reversible", + "8 supporting source artifacts" + ] + }, + "id": "cad7ee042c11", + "proposed_artifacts": [], + "recommended_disposition": "watch", + "risks": [ + "scope creep", + "duplicate work", + "weak outcome feedback" + ], + "scores": { + "compounding_value": 4, + "dependency_readiness": 3, + "future_behavior_improvement": 3, + "risk_reversibility": 4, + "total": 35.0, + "tractability": 3, + "urgency_freshness": 2, + "user_leverage": 3, + "verification_clarity": 3 + }, + "source_evidence": [ + { + "hit_count": 5, + "kind": "docs/proposals", + "ref": "brain/docs/proposals/2026-05-11-build-proposals-from-compiled-research.md", + "title": "Build proposals from compiled Brain research" + }, + { + "hit_count": 2, + "kind": "docs/plans", + "ref": "brain/docs/plans/2026-04-27-kublai-brain-v4-merged-design.md", + "title": "v4 Sketch (final): v3 backend + claude-obsidian-style human surface + graphify as the corpus extractor + Karpathy second-brain patterns + private/public tier carve-out + JARVIS content production + Research Skill Graph" + }, + { + "hit_count": 2, + "kind": "docs/plans", + "ref": "brain/docs/plans/2026-05-02-llm-survivor-paid-multiplayer-design.md", + "title": "LLM Survivor Paid Multiplayer Design" + }, + { + "hit_count": 2, + "kind": "docs/proposals", + "ref": "brain/docs/proposals/2026-05-10-agent-operable-brain-and-bounty-proof-loops.md", + "title": "Agent-operable Brain OS and bounty/proof loop proposals" + }, + { + "hit_count": 2, + "kind": "queue", + "ref": "brain/queue/2026-05-10-cyrilxbt-obsidian-business-os.md", + "title": "CyrilXBT Obsidian business operating system signal" + }, + { + "hit_count": 2, + "kind": "queue", + "ref": "brain/queue/2026-05-10-gkisokay-hermes-auto-think-auto-build.md", + "title": "Graeme gkisokay Hermes Auto Think Auto Build Research Packet" + }, + { + "hit_count": 2, + "kind": "queue", + "ref": "brain/queue/2026-05-11-av1dlive-agent-engineering-roadmap.md", + "title": "Avid / @Av1dlive \u2014 2026 AI Agent Engineering Roadmap" + }, + { + "hit_count": 2, + "kind": "receipts", + "ref": "brain/receipts/kurultai/2026-05-11-av1dlive-agent-engineering-roadmap-research.md", + "title": "Receipt \u2014 Avid 2026 AI Agent Engineering Roadmap Research" + } + ], + "source_refs": [ + "brain/docs/proposals/2026-05-11-build-proposals-from-compiled-research.md", + "brain/docs/plans/2026-04-27-kublai-brain-v4-merged-design.md", + "brain/docs/plans/2026-05-02-llm-survivor-paid-multiplayer-design.md", + "brain/docs/proposals/2026-05-10-agent-operable-brain-and-bounty-proof-loops.md", + "brain/queue/2026-05-10-cyrilxbt-obsidian-business-os.md", + "brain/queue/2026-05-10-gkisokay-hermes-auto-think-auto-build.md", + "brain/queue/2026-05-11-av1dlive-agent-engineering-roadmap.md", + "brain/receipts/kurultai/2026-05-11-av1dlive-agent-engineering-roadmap-research.md" + ], + "summary": "Convert completed buildrooms into evidence-backed content candidates.", + "title": "Buildroom-to-Content OS Bridge", + "why_now": "Buildrooms are starting to produce reusable doctrine and receipts." + }, + { + "acceptance_criteria": [ + "evaluate five opportunities on paper", + "classify proceed/no-op/needs approval", + "perform no external actions" + ], + "candidate_type": "external-opportunity", + "dependencies": [ + "Brain artifacts", + "buildroom Control Room outputs" + ], + "expected_leverage": "Creates a safe path to evaluate economic opportunities without uncontrolled commitments.", + "explainability": { + "counterarguments": [ + "Could create noisy recommendations if reports are not capped", + "Scoring must not replace verified user value" + ], + "top_negative_factors": [ + "external-side-effect-approval-required" + ], + "top_positive_factors": [ + "compounds future agent behavior", + "local-first and reversible", + "8 supporting source artifacts" + ] + }, + "id": "ea047f2d69ea", + "proposed_artifacts": [], + "recommended_disposition": "needs-human-approval", + "risks": [ + "external-side-effect-approval-required" + ], + "scores": { + "compounding_value": 4, + "dependency_readiness": 3, + "future_behavior_improvement": 3, + "risk_reversibility": 2, + "total": 32.6, + "tractability": 3, + "urgency_freshness": 2, + "user_leverage": 3, + "verification_clarity": 3 + }, + "source_evidence": [ + { + "hit_count": 5, + "kind": "docs/proposals", + "ref": "brain/docs/proposals/2026-05-10-agent-operable-brain-and-bounty-proof-loops.md", + "title": "Agent-operable Brain OS and bounty/proof loop proposals" + }, + { + "hit_count": 3, + "kind": "docs/proposals", + "ref": "brain/docs/proposals/2026-05-11-build-proposals-from-compiled-research.md", + "title": "Build proposals from compiled Brain research" + }, + { + "hit_count": 2, + "kind": "docs/designs", + "ref": "brain/docs/designs/2026-05-10-buildroom-opportunity-radar-intelligent-design.md", + "title": "Buildroom Opportunity Radar intelligent design and SWOT" + }, + { + "hit_count": 2, + "kind": "docs/plans", + "ref": "brain/docs/plans/2026-05-02-turo-cybertruck-ops-rebuild-plan.md", + "title": "Turo Cybertruck Ops Functional Metrics Plan" + }, + { + "hit_count": 2, + "kind": "receipts", + "ref": "brain/receipts/kurultai/2026-05-10-agent-operable-brain-os-vertical-slice.md", + "title": "Agent-operable Brain OS vertical slice receipt" + }, + { + "hit_count": 1, + "kind": "docs/kurultai", + "ref": "brain/docs/kurultai/agent-operable-brain-os.md", + "title": "Agent-operable Brain OS" + }, + { + "hit_count": 1, + "kind": "docs/plans", + "ref": "brain/docs/plans/2026-04-21-parse-soft-launch-option-a-kickoff-prompt.md", + "title": "Kickoff prompt \u2014 Parse for Agents soft launch (Option A)" + }, + { + "hit_count": 1, + "kind": "docs/plans", + "ref": "brain/docs/plans/2026-04-21-parse-soft-launch-option-a.md", + "title": "Parse for Agents \u2014 Soft-Launch Plan (Option A)" + } + ], + "source_refs": [ + "brain/docs/proposals/2026-05-10-agent-operable-brain-and-bounty-proof-loops.md", + "brain/docs/proposals/2026-05-11-build-proposals-from-compiled-research.md", + "brain/docs/designs/2026-05-10-buildroom-opportunity-radar-intelligent-design.md", + "brain/docs/plans/2026-05-02-turo-cybertruck-ops-rebuild-plan.md", + "brain/receipts/kurultai/2026-05-10-agent-operable-brain-os-vertical-slice.md", + "brain/docs/kurultai/agent-operable-brain-os.md", + "brain/docs/plans/2026-04-21-parse-soft-launch-option-a-kickoff-prompt.md", + "brain/docs/plans/2026-04-21-parse-soft-launch-option-a.md" + ], + "summary": "Design a paper-only workflow for evaluating external opportunities without external side effects.", + "title": "Paper-only Bounty Proof Loop", + "why_now": "There is upside, but reputation and payment surfaces require stronger proof gates first." + } + ], + "generated_at": "2026-05-11T03:21:25Z", + "input_summary": { + "attention_count": 3, + "brain_artifact_count": 60, + "buildroom_count": 2, + "control_room_present": true, + "kanban_draft_count": 2 + }, + "kanban_drafts": [ + { + "body": "Candidate: Buildroom Opportunity Radar v0\nType: system-improvement\nScore: 49.9\nDisposition: buildroom-candidate\nWhy now: Kurultai has a strong Auto Build loop, but candidate selection is still less inspectable than execution.\nExpected leverage: Turns Brain artifacts into recurring ranked build intent and reduces ad hoc operator steering.\nAcceptance criteria:\n- emit JSON and markdown reports\n- score candidates explainably\n- emit dry-run Kanban drafts\n- link recommendations to source refs\n- pass tests and leakage scan", + "candidate_id": "e5b14f92b7ca", + "labels": [ + "buildroom", + "opportunity-radar", + "system-improvement", + "buildroom-candidate" + ], + "priority": "high", + "source": "buildroom-opportunity-radar-v0", + "title": "Buildroom opportunity: Buildroom Opportunity Radar v0" + }, + { + "body": "Candidate: Brain Compiler Coverage Ledger\nType: brain-compiler\nScore: 47.9\nDisposition: buildroom-candidate\nWhy now: The Brain has more durable artifacts; now it needs coverage accounting to prevent passive storage.\nExpected leverage: Enforces the invariant that memory only matters when it changes future behavior or gets a no-op receipt.\nAcceptance criteria:\n- scan queue/generated/reviews/proposals/receipts\n- identify missing dispositions\n- emit coverage report", + "candidate_id": "19eca945eb9b", + "labels": [ + "buildroom", + "opportunity-radar", + "brain-compiler", + "buildroom-candidate" + ], + "priority": "high", + "source": "buildroom-opportunity-radar-v0", + "title": "Buildroom opportunity: Brain Compiler Coverage Ledger" + }, + { + "body": "Candidate: Agent Harness Maturity Scoreboard\nType: evaluation-candidate\nScore: 46.7\nDisposition: buildroom-candidate\nWhy now: The agent-engineering research argues that agent engineering is harness engineering.\nExpected leverage: Makes agent trust and hardening gaps visible before autonomy expands.\nAcceptance criteria:\n- produce explainable maturity scores\n- surface improvement recommendations\n- integrate with Control Room", + "candidate_id": "05b418059610", + "labels": [ + "buildroom", + "opportunity-radar", + "evaluation-candidate", + "buildroom-candidate" + ], + "priority": "high", + "source": "buildroom-opportunity-radar-v0", + "title": "Buildroom opportunity: Agent Harness Maturity Scoreboard" + } + ], + "mode": "dry-run", + "recommended_next_action": "Buildroom Opportunity Radar v0", + "rejected": [], + "schema_version": "0.1.0", + "warnings": [] +} diff --git a/tools/kurultai/buildroom/opportunity-radar.md b/tools/kurultai/buildroom/opportunity-radar.md new file mode 100644 index 00000000..700a4b96 --- /dev/null +++ b/tools/kurultai/buildroom/opportunity-radar.md @@ -0,0 +1,92 @@ +# Buildroom Opportunity Radar + +Generated: 2026-05-11T03:21:25Z + +## Summary + +Candidates: 5 +Kanban drafts: 3 + +## Top candidates + +### Buildroom Opportunity Radar v0 + +- Type: `system-improvement` +- Score: `49.9` +- Disposition: `buildroom-candidate` +- Why now: Kurultai has a strong Auto Build loop, but candidate selection is still less inspectable than execution. +- Expected leverage: Turns Brain artifacts into recurring ranked build intent and reduces ad hoc operator steering. +- Sources: 8 +- Acceptance criteria: + - emit JSON and markdown reports + - score candidates explainably + - emit dry-run Kanban drafts + - link recommendations to source refs + - pass tests and leakage scan +- Counterarguments: + - Could create noisy recommendations if reports are not capped + - Scoring must not replace verified user value + +### Brain Compiler Coverage Ledger + +- Type: `brain-compiler` +- Score: `47.9` +- Disposition: `buildroom-candidate` +- Why now: The Brain has more durable artifacts; now it needs coverage accounting to prevent passive storage. +- Expected leverage: Enforces the invariant that memory only matters when it changes future behavior or gets a no-op receipt. +- Sources: 8 +- Acceptance criteria: + - scan queue/generated/reviews/proposals/receipts + - identify missing dispositions + - emit coverage report +- Counterarguments: + - Could create noisy recommendations if reports are not capped + - Scoring must not replace verified user value + +### Agent Harness Maturity Scoreboard + +- Type: `evaluation-candidate` +- Score: `46.7` +- Disposition: `buildroom-candidate` +- Why now: The agent-engineering research argues that agent engineering is harness engineering. +- Expected leverage: Makes agent trust and hardening gaps visible before autonomy expands. +- Sources: 8 +- Acceptance criteria: + - produce explainable maturity scores + - surface improvement recommendations + - integrate with Control Room +- Counterarguments: + - Could create noisy recommendations if reports are not capped + - Scoring must not replace verified user value + +### Buildroom-to-Content OS Bridge + +- Type: `content-candidate` +- Score: `35.0` +- Disposition: `watch` +- Why now: Buildrooms are starting to produce reusable doctrine and receipts. +- Expected leverage: Turns proven internal work into public learning when safe. +- Sources: 8 +- Acceptance criteria: + - extract claims/evidence + - scan public/private boundary + - draft publish/no-op recommendation +- Counterarguments: + - Could create noisy recommendations if reports are not capped + - Scoring must not replace verified user value + +### Paper-only Bounty Proof Loop + +- Type: `external-opportunity` +- Score: `32.6` +- Disposition: `needs-human-approval` +- Why now: There is upside, but reputation and payment surfaces require stronger proof gates first. +- Expected leverage: Creates a safe path to evaluate economic opportunities without uncontrolled commitments. +- Sources: 8 +- Acceptance criteria: + - evaluate five opportunities on paper + - classify proceed/no-op/needs approval + - perform no external actions +- Counterarguments: + - Could create noisy recommendations if reports are not capped + - Scoring must not replace verified user value diff --git a/tools/kurultai/buildroom/schemas/opportunity-radar.schema.json b/tools/kurultai/buildroom/schemas/opportunity-radar.schema.json new file mode 100644 index 00000000..b7e10b11 --- /dev/null +++ b/tools/kurultai/buildroom/schemas/opportunity-radar.schema.json @@ -0,0 +1 @@ +{"$schema":"https://json-schema.org/draft/2020-12/schema","title":"Buildroom Opportunity Radar","type":"object","required":["schema_version","generated_at","mode","input_summary","candidates","recommended_next_action"],"properties":{"schema_version":{"type":"string"},"generated_at":{"type":"string"},"mode":{"type":"string"},"input_summary":{"type":"object"},"recommended_next_action":{"type":["string","null"]},"warnings":{"type":"array","items":{"type":"string"}},"candidates":{"type":"array","items":{"type":"object","required":["id","title","candidate_type","source_refs","summary","recommended_disposition","scores"],"properties":{"id":{"type":"string"},"title":{"type":"string"},"candidate_type":{"type":"string"},"source_refs":{"type":"array","items":{"type":"string"}},"summary":{"type":"string"},"recommended_disposition":{"type":"string"},"scores":{"type":"object","required":["total"]}}}}}} diff --git a/tools/kurultai/buildroom/scripts/apply_kanban_drafts.py b/tools/kurultai/buildroom/scripts/apply_kanban_drafts.py new file mode 100644 index 00000000..fee4e7ed --- /dev/null +++ b/tools/kurultai/buildroom/scripts/apply_kanban_drafts.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import hashlib +import json +import sqlite3 +import sys +import time +from pathlib import Path +from typing import Any + +from common import default_workspace_path + +ROOT = Path(__file__).resolve().parents[1] +DEFAULT_DRAFTS = ROOT / "control-room-kanban-drafts.json" +DEFAULT_STATE = ROOT / ".generated" / "kanban-draft-state.json" +DEFAULT_LEDGER = ROOT / ".generated" / "kanban-apply-ledger.json" + + +def load_json(path: Path) -> Any: + return json.loads(path.read_text()) + + +def write_json(path: Path, payload: Any) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n") + + +def drafts_from_payload(payload: Any) -> list[dict[str, Any]]: + if isinstance(payload, dict): + value = payload.get("drafts") or payload.get("tasks") or [] + if isinstance(value, list): + return [item for item in value if isinstance(item, dict)] + if isinstance(payload, list): + return [item for item in payload if isinstance(item, dict)] + return [] + + +def stable_key(draft: dict[str, Any]) -> str: + metadata = draft.get("metadata") if isinstance(draft.get("metadata"), dict) else {} + for field in ("attention_item_id", "attention_id", "item_id", "idempotency_key"): + value = metadata.get(field) or draft.get(field) + if isinstance(value, str) and value.strip(): + return value.strip() + room = str(metadata.get("room_id") or draft.get("room_id") or "unknown-room") + reason = str(metadata.get("reason") or draft.get("reason") or draft.get("title") or draft.get("source") or "unknown-reason") + return hashlib.sha256(f"{room}\n{reason}".encode()).hexdigest()[:24] + + +def normalize_draft(draft: dict[str, Any]) -> dict[str, Any]: + key = stable_key(draft) + title = str(draft.get("title") or f"buildroom attention: {key}").strip() + body = str(draft.get("body") or draft.get("description") or "").strip() + metadata = draft.get("metadata") if isinstance(draft.get("metadata"), dict) else {} + severity = str(metadata.get("severity") or draft.get("severity") or "medium") + room_id = str(metadata.get("room_id") or draft.get("room_id") or "") + reason = str(metadata.get("reason") or draft.get("reason") or draft.get("source") or "") + acceptance = draft.get("acceptance_criteria") or metadata.get("acceptance_criteria") or [] + if not isinstance(acceptance, list): + acceptance = [str(acceptance)] + verification = draft.get("verification_commands") or metadata.get("verification_commands") or [] + if not isinstance(verification, list): + verification = [str(verification)] + lines = [body] if body else [] + lines.extend([ + "", + f"Source: buildroom Control Room attention item `{key}`", + f"Room: `{room_id or 'unknown'}`", + f"Severity: `{severity}`", + f"Reason: {reason or 'see generated attention item payload'}`" if reason else "Reason: see generated attention item payload", + "", + "Acceptance criteria:", + ]) + lines.extend(f"- {item}" for item in acceptance or ["attention item is resolved or explicitly receipted as no-op"]) + lines.append("") + lines.append("Verification commands:") + lines.extend(f"- {item}" for item in verification or ["python3 tools/kurultai/buildroom/scripts/buildroom_healthcheck.py"]) + return { + "dedupe_key": key, + "title": title, + "body": "\n".join(lines).strip() + "\n", + "assignee": str(draft.get("assignee") or metadata.get("assignee") or "kublai"), + "priority": coerce_priority(draft.get("priority") or metadata.get("priority"), severity), + "workspace_kind": str(draft.get("workspace_kind") or "dir"), + "workspace_path": str(draft.get("workspace_path") or default_workspace_path()), + "idempotency_key": f"buildroom-control-room:{key}", + "metadata": {**metadata, "dedupe_key": key, "room_id": room_id, "severity": severity, "reason": reason}, + } + + +def priority_for_severity(severity: str) -> int: + return {"critical": 90, "high": 70, "medium": 50, "low": 30}.get(severity.lower(), 50) + + + +def coerce_priority(value: Any, severity: str) -> int: + if isinstance(value, int): + return value + if isinstance(value, str) and value.strip().isdigit(): + return int(value.strip()) + if isinstance(value, str) and value.strip().lower() in {"critical", "high", "medium", "normal", "low"}: + mapped = value.strip().lower().replace("normal", "medium") + return priority_for_severity(mapped) + return priority_for_severity(severity) + +def load_state(path: Path) -> dict[str, Any]: + if not path.exists(): + return {"applied": {}} + data = load_json(path) + return data if isinstance(data, dict) else {"applied": {}} + + +def existing_task_by_idempotency(con: sqlite3.Connection, key: str) -> str | None: + row = con.execute("select id from tasks where idempotency_key = ? limit 1", (key,)).fetchone() + return str(row[0]) if row else None + + +def insert_task(con: sqlite3.Connection, task: dict[str, Any], created_by: str) -> str: + existing = existing_task_by_idempotency(con, task["idempotency_key"]) + if existing: + return existing + task_id = "t_" + hashlib.sha256(task["idempotency_key"].encode()).hexdigest()[:8] + now = int(time.time()) + con.execute( + """ + insert into tasks ( + id, title, body, assignee, status, priority, created_by, created_at, + workspace_kind, workspace_path, idempotency_key, skills + ) values (?, ?, ?, ?, 'todo', ?, ?, ?, ?, ?, ?, ?) + """, + ( + task_id, + task["title"], + task["body"], + task["assignee"], + task["priority"], + created_by, + now, + task["workspace_kind"], + task["workspace_path"], + task["idempotency_key"], + json.dumps(["kanban-worker", "kurultai-operations"]), + ), + ) + con.execute( + "insert into task_events (task_id, kind, payload, created_at) values (?, 'created', ?, ?)", + (task_id, json.dumps({"source": "buildroom-control-room", "metadata": task["metadata"]}, sort_keys=True), now), + ) + return task_id + + +def build_plan(drafts_path: Path, state_path: Path) -> dict[str, Any]: + payload = load_json(drafts_path) + state = load_state(state_path) + applied = state.get("applied") if isinstance(state.get("applied"), dict) else {} + normalized = [normalize_draft(item) for item in drafts_from_payload(payload)] + unique: dict[str, dict[str, Any]] = {} + for task in normalized: + unique.setdefault(task["dedupe_key"], task) + tasks = [] + for task in unique.values(): + prior = applied.get(task["dedupe_key"]) + tasks.append({**task, "already_applied": bool(prior), "existing_task_id": prior.get("task_id") if isinstance(prior, dict) else None}) + return {"source": str(drafts_path), "state": str(state_path), "tasks": tasks, "summary": {"drafts": len(normalized), "unique": len(unique), "new": sum(1 for task in tasks if not task["already_applied"])}} + + +def main() -> int: + parser = argparse.ArgumentParser(description="Apply buildroom Control Room Kanban drafts with idempotent dry-run default.") + parser.add_argument("--drafts", type=Path, default=DEFAULT_DRAFTS) + parser.add_argument("--state", type=Path, default=DEFAULT_STATE) + parser.add_argument("--ledger", type=Path, default=DEFAULT_LEDGER) + parser.add_argument("--kanban-db", type=Path, default=Path.home() / ".hermes" / "kanban.db") + parser.add_argument("--apply", action="store_true", help="Create real Hermes Kanban tasks. Default is dry-run only.") + parser.add_argument("--created-by", default="kublai-control-room") + parser.add_argument("--output", type=Path) + args = parser.parse_args() + + plan = build_plan(args.drafts, args.state) + if args.apply: + state = load_state(args.state) + state.setdefault("applied", {}) + con = sqlite3.connect(args.kanban_db) + try: + with con: + for task in plan["tasks"]: + task_id = insert_task(con, task, args.created_by) + task["created_task_id"] = task_id + task["already_applied"] = bool(task.get("existing_task_id")) or task_id == task.get("existing_task_id") + state["applied"][task["dedupe_key"]] = {"task_id": task_id, "idempotency_key": task["idempotency_key"], "applied_at": int(time.time())} + finally: + con.close() + write_json(args.state, state) + write_json(args.ledger, {"mode": "apply", **plan}) + else: + write_json(args.ledger, {"mode": "dry-run", **plan}) + if args.output: + write_json(args.output, plan) + print(json.dumps({"mode": "apply" if args.apply else "dry-run", **plan["summary"]}, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/kurultai/buildroom/scripts/buildroom_healthcheck.py b/tools/kurultai/buildroom/scripts/buildroom_healthcheck.py new file mode 100644 index 00000000..f9cd220e --- /dev/null +++ b/tools/kurultai/buildroom/scripts/buildroom_healthcheck.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +import subprocess +import sys +from pathlib import Path +from typing import Any + +ROOT = Path(__file__).resolve().parents[4] +BUILDROOM = ROOT / "tools" / "kurultai" / "buildroom" + + +def run(command: list[str]) -> dict[str, Any]: + proc = subprocess.run(command, cwd=ROOT, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + return {"command": command, "exit_code": proc.returncode, "output": proc.stdout[-4000:]} + + +def generated_files_present() -> dict[str, Any]: + expected = [ + BUILDROOM / "control-room-attention-items.json", + BUILDROOM / "control-room-kanban-drafts.json", + BUILDROOM / "control-room.json", + BUILDROOM / "control-room.html", + BUILDROOM / "control-room.md", + ] + missing = [str(path.relative_to(ROOT)) for path in expected if not path.exists()] + return {"name": "generated-files-present", "exit_code": 0 if not missing else 1, "missing": missing, "output": ""} + + +def main() -> int: + parser = argparse.ArgumentParser(description="Run public-safe buildroom canaries and regressions.") + parser.add_argument("--json", action="store_true", help="Emit machine-readable JSON.") + parser.add_argument("--skip-pytest", action="store_true", help="Skip focused pytest run for quick cron checks.") + args = parser.parse_args() + + checks = [] + rooms = [BUILDROOM / "rooms" / "demo-room", BUILDROOM / "rooms" / "2026-05-10-gkisokay-auto-think-auto-build"] + for room in rooms: + checks.append({"name": f"validate:{room.name}", **run([sys.executable, "tools/kurultai/buildroom/scripts/validate_room.py", str(room)])}) + checks.append({"name": "control-room-generate", **run([sys.executable, "tools/kurultai/buildroom/scripts/control_room.py"])}) + checks.append({"name": "control-room-cron", **run([sys.executable, "tools/kurultai/buildroom/scripts/control_room_cron.py"])}) + checks.append({"name": "kanban-bridge-dry-run", **run([sys.executable, "tools/kurultai/buildroom/scripts/apply_kanban_drafts.py"])}) + checks.append(generated_files_present()) + checks.append({"name": "compileall-buildroom", **run([sys.executable, "-m", "compileall", "-q", "tools/kurultai/buildroom/scripts"])}) + if not args.skip_pytest: + checks.append({"name": "pytest-buildroom", **run([sys.executable, "-m", "pytest", "tests/kurultai/test_buildroom_foundation.py", "--no-cov", "-q"])}) + passed = all(check["exit_code"] == 0 for check in checks) + payload = {"passed": passed, "checks": checks} + if args.json: + print(json.dumps(payload, indent=2, sort_keys=True)) + else: + print(f"buildroom healthcheck: {'PASS' if passed else 'FAIL'}") + for check in checks: + print(f"- {check['name']}: exit {check['exit_code']}") + return 0 if passed else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/kurultai/buildroom/scripts/common.py b/tools/kurultai/buildroom/scripts/common.py index 04e9e523..9e27a9fc 100644 --- a/tools/kurultai/buildroom/scripts/common.py +++ b/tools/kurultai/buildroom/scripts/common.py @@ -1,6 +1,7 @@ from __future__ import annotations import json +import os import re import shutil from datetime import datetime, timezone @@ -29,6 +30,13 @@ def utc_now() -> str: return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") + +def default_workspace_path() -> str: + configured = os.environ.get("KURULTAI_HOME", "").strip() + if configured: + return str(Path(configured).expanduser().resolve()) + return str(BUILDROOM_ROOT.parents[2].resolve()) + def load_json(path: Path) -> Any: with path.open("r", encoding="utf-8") as handle: return json.load(handle) diff --git a/tools/kurultai/buildroom/scripts/kanban_adapter.py b/tools/kurultai/buildroom/scripts/kanban_adapter.py index 22c0678b..753cf4f8 100644 --- a/tools/kurultai/buildroom/scripts/kanban_adapter.py +++ b/tools/kurultai/buildroom/scripts/kanban_adapter.py @@ -6,7 +6,7 @@ from pathlib import Path from typing import Any -from common import load_json, resolve_room_path, utc_now, write_json +from common import default_workspace_path, load_json, resolve_room_path, utc_now, write_json def _kanban_parent_ids(task_refs: list[Any]) -> list[str]: @@ -85,7 +85,7 @@ def build_task_packet(room: Path) -> dict[str, Any]: "body": body, "parents": _kanban_parent_ids(task_refs), "workspace_kind": "dir", - "workspace_path": "${KURULTAI_HOME}", + "workspace_path": default_workspace_path(), "idempotency_key": f"buildroom:{build_id}", "metadata": { "room_id": room.name, diff --git a/tools/kurultai/buildroom/scripts/opportunity_radar.py b/tools/kurultai/buildroom/scripts/opportunity_radar.py new file mode 100644 index 00000000..6671bb0e --- /dev/null +++ b/tools/kurultai/buildroom/scripts/opportunity_radar.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +from __future__ import annotations +import argparse, hashlib, json, os +from datetime import datetime, timezone +from pathlib import Path +from typing import Any +ROOT = Path(__file__).resolve().parents[4] +BUILDROOM = ROOT / "tools" / "kurultai" / "buildroom" +DEFAULT_BRAIN = Path(os.environ.get("BRAIN_ROOT", "/Users/kublai/brain")) +WEIGHTS = {"user_leverage":1.4,"compounding_value":1.6,"tractability":1.1,"verification_clarity":1.2,"risk_reversibility":1.2,"dependency_readiness":1.0,"future_behavior_improvement":1.7,"urgency_freshness":0.8} +SCAN_DIRS = ["queue","generated","docs/proposals","docs/plans","docs/designs","docs/kurultai","receipts"] +TOPIC_RULES: list[dict[str, Any]] = [ + {"title":"Buildroom Opportunity Radar v0","candidate_type":"system-improvement","aliases":["opportunity radar","auto think","what should we build next","ranked build intent"],"summary":"Build a local-first strategy layer that ranks next buildroom candidates from Brain and buildroom evidence.","why_now":"Kurultai has a strong Auto Build loop, but candidate selection is still less inspectable than execution.","expected_leverage":"Turns Brain artifacts into recurring ranked build intent and reduces ad hoc operator steering.","acceptance_criteria":["emit JSON and markdown reports","score candidates explainably","emit dry-run Kanban drafts","link recommendations to source refs","pass tests and leakage scan"],"base_scores":{"user_leverage":5,"compounding_value":5,"tractability":4,"verification_clarity":4,"risk_reversibility":5,"dependency_readiness":5,"future_behavior_improvement":5,"urgency_freshness":4}}, + {"title":"Agent Harness Maturity Scoreboard","candidate_type":"evaluation-candidate","aliases":["harness maturity","agent engineering","eval","regression","sandbox","observability"],"summary":"Score projects, agents, and buildrooms against harness maturity primitives.","why_now":"The agent-engineering research argues that agent engineering is harness engineering.","expected_leverage":"Makes agent trust and hardening gaps visible before autonomy expands.","acceptance_criteria":["produce explainable maturity scores","surface improvement recommendations","integrate with Control Room"],"base_scores":{"user_leverage":4,"compounding_value":5,"tractability":4,"verification_clarity":4,"risk_reversibility":5,"dependency_readiness":4,"future_behavior_improvement":5,"urgency_freshness":3}}, + {"title":"Brain Compiler Coverage Ledger","candidate_type":"brain-compiler","aliases":["compiler coverage","memory is leverage","no-op receipt","changed future behavior","processed until"],"summary":"Report which meaningful sources changed behavior and which still need synthesis, skill, Kanban, receipt, or no-op disposition.","why_now":"The Brain has more durable artifacts; now it needs coverage accounting to prevent passive storage.","expected_leverage":"Enforces the invariant that memory only matters when it changes future behavior or gets a no-op receipt.","acceptance_criteria":["scan queue/generated/reviews/proposals/receipts","identify missing dispositions","emit coverage report"],"base_scores":{"user_leverage":4,"compounding_value":5,"tractability":4,"verification_clarity":5,"risk_reversibility":5,"dependency_readiness":4,"future_behavior_improvement":5,"urgency_freshness":3}}, + {"title":"Paper-only Bounty Proof Loop","candidate_type":"external-opportunity","aliases":["bounty","proof loop","revenue","external acceptance","payment"],"summary":"Design a paper-only workflow for evaluating external opportunities without external side effects.","why_now":"There is upside, but reputation and payment surfaces require stronger proof gates first.","expected_leverage":"Creates a safe path to evaluate economic opportunities without uncontrolled commitments.","acceptance_criteria":["evaluate five opportunities on paper","classify proceed/no-op/needs approval","perform no external actions"],"base_scores":{"user_leverage":3,"compounding_value":4,"tractability":3,"verification_clarity":3,"risk_reversibility":2,"dependency_readiness":3,"future_behavior_improvement":3,"urgency_freshness":2},"gates":["external-side-effect-approval-required"]}, + {"title":"Buildroom-to-Content OS Bridge","candidate_type":"content-candidate","aliases":["content os","public artifact","x article","launch","article"],"summary":"Convert completed buildrooms into evidence-backed content candidates.","why_now":"Buildrooms are starting to produce reusable doctrine and receipts.","expected_leverage":"Turns proven internal work into public learning when safe.","acceptance_criteria":["extract claims/evidence","scan public/private boundary","draft publish/no-op recommendation"],"base_scores":{"user_leverage":3,"compounding_value":4,"tractability":3,"verification_clarity":3,"risk_reversibility":4,"dependency_readiness":3,"future_behavior_improvement":3,"urgency_freshness":2}}, +] +def utc_now() -> str: return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00","Z") +def rel_ref(path: Path, base: Path, prefix: str) -> str: + try: return f"{prefix}/{path.relative_to(base).as_posix()}" + except ValueError: return path.name +def discover_brain(brain_root: Path) -> list[dict[str, Any]]: + artifacts=[] + if not brain_root.exists(): return artifacts + for sub in SCAN_DIRS: + folder=brain_root/sub + if not folder.exists(): continue + for path in sorted(folder.rglob("*.md")): + try: text=path.read_text(encoding="utf-8", errors="ignore") + except OSError: continue + title=path.stem + for line in text.splitlines()[:30]: + s=line.strip() + if s.lower().startswith("title:"): title=s.split(":",1)[1].strip().strip('"') or title; break + if s.startswith("# "): title=s[2:].strip() or title; break + artifacts.append({"ref":rel_ref(path, brain_root, "brain"),"title":title,"text":text.lower(),"kind":sub}) + return artifacts +def discover_buildroom(buildroom_root: Path) -> dict[str, Any]: + summary={"rooms":[],"control_room_present":False,"attention_count":0,"kanban_draft_count":0} + rooms=buildroom_root/"rooms" + if rooms.exists(): summary["rooms"]=[p.name for p in sorted(rooms.iterdir()) if p.is_dir()] + summary["control_room_present"]=(buildroom_root/"control-room.json").exists() + for name,key in [("control-room-attention-items.json","attention_count"),("control-room-kanban-drafts.json","kanban_draft_count")]: + try: + payload=json.loads((buildroom_root/name).read_text()) + summary[key]=int(payload.get("attention_count") or payload.get("draft_count") or len(payload.get("items") or payload.get("drafts") or [])) + except Exception: pass + return summary +def stable_id(title: str, candidate_type: str, refs: list[str]) -> str: + raw=json.dumps({"title":title.lower(),"type":candidate_type,"refs":sorted(refs)[:8]}, sort_keys=True) + return hashlib.sha256(raw.encode()).hexdigest()[:12] +def score_total(scores: dict[str,int]) -> float: return round(sum(scores.get(k,0)*w for k,w in WEIGHTS.items()),1) +def source_matches(rule: dict[str, Any], artifacts: list[dict[str, Any]]) -> list[dict[str, str]]: + aliases=[a.lower() for a in rule["aliases"]]; matches=[] + for artifact in artifacts: + hay=artifact["text"]+" "+artifact["title"].lower()+" "+artifact["ref"].lower() + hits=sum(1 for a in aliases if a in hay) + if hits: matches.append({"ref":artifact["ref"],"title":artifact["title"],"kind":artifact["kind"],"hit_count":hits}) + return sorted(matches, key=lambda m:(-m["hit_count"], m["ref"]))[:8] +def build_candidates(artifacts: list[dict[str, Any]], buildroom_summary: dict[str, Any]) -> list[dict[str, Any]]: + candidates=[] + for rule in TOPIC_RULES: + matches=source_matches(rule, artifacts); refs=[m["ref"] for m in matches]; scores=dict(rule["base_scores"]) + if not matches: + scores["verification_clarity"]=max(0,scores["verification_clarity"]-1); scores["dependency_readiness"]=max(0,scores["dependency_readiness"]-1) + total=score_total(scores)+min(3,len(matches)); gates=list(rule.get("gates", [])); disposition="buildroom-candidate" + if gates: disposition="needs-human-approval" + elif total < 20: disposition="no-op" + elif total < 30: disposition="backlog" + elif total < 38: disposition="watch" + candidates.append({"id":stable_id(rule["title"],rule["candidate_type"],refs),"title":rule["title"],"candidate_type":rule["candidate_type"],"source_refs":refs,"summary":rule["summary"],"why_now":rule["why_now"],"expected_leverage":rule["expected_leverage"],"acceptance_criteria":rule["acceptance_criteria"],"risks":gates or ["scope creep","duplicate work","weak outcome feedback"],"dependencies":["Brain artifacts","buildroom Control Room outputs"],"recommended_disposition":disposition,"scores":{**scores,"total":total},"explainability":{"top_positive_factors":["compounds future agent behavior","local-first and reversible",f"{len(matches)} supporting source artifacts"],"top_negative_factors":gates or (["limited direct source evidence in this run"] if not matches else ["requires calibration against outcomes"]),"counterarguments":["Could create noisy recommendations if reports are not capped","Scoring must not replace verified user value"]},"source_evidence":matches,"proposed_artifacts":[]}) + return sorted(candidates, key=lambda c:(-float(c["scores"]["total"]), c["title"])) +def kanban_drafts(candidates: list[dict[str, Any]], threshold: float) -> list[dict[str, Any]]: + drafts=[] + for c in candidates: + if float(c["scores"]["total"]) < threshold or c["recommended_disposition"] not in {"buildroom-candidate","watch"}: continue + body="\n".join([f"Candidate: {c['title']}",f"Type: {c['candidate_type']}",f"Score: {c['scores']['total']}",f"Disposition: {c['recommended_disposition']}",f"Why now: {c['why_now']}",f"Expected leverage: {c['expected_leverage']}","Acceptance criteria:",*[f"- {a}" for a in c["acceptance_criteria"]]]) + drafts.append({"title":f"Buildroom opportunity: {c['title']}","body":body,"labels":["buildroom","opportunity-radar",c["candidate_type"],c["recommended_disposition"]],"priority":"high" if float(c["scores"]["total"])>=42 else "normal","source":"buildroom-opportunity-radar-v0","candidate_id":c["id"]}) + return drafts +def markdown_report(payload: dict[str, Any]) -> str: + lines=["# Buildroom Opportunity Radar","",f"Generated: {payload['generated_at']}","","## Summary","",f"Candidates: {len(payload['candidates'])}",f"Kanban drafts: {len(payload['kanban_drafts'])}","","## Top candidates",""] + for c in payload["candidates"][:8]: + lines += [f"### {c['title']}","",f"- Type: `{c['candidate_type']}`",f"- Score: `{c['scores']['total']}`",f"- Disposition: `{c['recommended_disposition']}`",f"- Why now: {c['why_now']}",f"- Expected leverage: {c['expected_leverage']}",f"- Sources: {len(c['source_refs'])}","- Acceptance criteria:"] + lines += [f" - {a}" for a in c["acceptance_criteria"]] + lines += ["- Counterarguments:"]+[f" - {a}" for a in c["explainability"]["counterarguments"]]+[""] + if payload.get("warnings"): lines += ["## Warnings",""]+[f"- {w}" for w in payload["warnings"]] + return "\n".join(lines).rstrip()+"\n" +def main() -> int: + parser=argparse.ArgumentParser(description="Rank buildroom opportunities from Brain and buildroom evidence.") + parser.add_argument("--brain-root", default=str(DEFAULT_BRAIN)); parser.add_argument("--buildroom-root", default=str(BUILDROOM)); parser.add_argument("--json-output", default=str(BUILDROOM/"opportunity-radar.json")); parser.add_argument("--markdown-output", default=str(BUILDROOM/"opportunity-radar.md")); parser.add_argument("--kanban-drafts-output", default=str(BUILDROOM/"opportunity-radar-kanban-drafts.json")); parser.add_argument("--draft-threshold", type=float, default=38.0); parser.add_argument("--json", action="store_true") + args=parser.parse_args(); artifacts=discover_brain(Path(args.brain_root).expanduser()); summary=discover_buildroom(Path(args.buildroom_root).expanduser()); candidates=build_candidates(artifacts,summary); drafts=kanban_drafts(candidates,args.draft_threshold); warnings=[] + if not artifacts: warnings.append("No Brain artifacts discovered; output is based on canonical fallback candidates only.") + if not summary.get("control_room_present"): warnings.append("Control Room JSON not found; buildroom state evidence is partial.") + payload={"schema_version":"0.1.0","generated_at":utc_now(),"mode":"dry-run","input_summary":{"brain_artifact_count":len(artifacts),"buildroom_count":len(summary.get("rooms",[])),"control_room_present":summary.get("control_room_present",False),"attention_count":summary.get("attention_count",0),"kanban_draft_count":summary.get("kanban_draft_count",0)},"candidates":candidates,"rejected":[],"warnings":warnings,"recommended_next_action":candidates[0]["title"] if candidates else None,"kanban_drafts":drafts} + for path,data in [(Path(args.json_output),payload),(Path(args.kanban_drafts_output),{"source":"buildroom-opportunity-radar-v0","generated_at":payload["generated_at"],"draft_count":len(drafts),"drafts":drafts})]: path.parent.mkdir(parents=True, exist_ok=True); path.write_text(json.dumps(data, indent=2, sort_keys=True)+"\n") + md=Path(args.markdown_output); md.parent.mkdir(parents=True, exist_ok=True); md.write_text(markdown_report(payload)) + if args.json: print(json.dumps({"candidate_count":len(candidates),"draft_count":len(drafts),"recommended_next_action":payload["recommended_next_action"],"warnings":warnings}, indent=2, sort_keys=True)) + else: print(f"Wrote {args.json_output}, {args.markdown_output}, and {args.kanban_drafts_output}") + return 0 +if __name__ == "__main__": raise SystemExit(main()) diff --git a/tools/kurultai/buildroom/scripts/qa_trust.py b/tools/kurultai/buildroom/scripts/qa_trust.py index 44df2653..11b442ca 100644 --- a/tools/kurultai/buildroom/scripts/qa_trust.py +++ b/tools/kurultai/buildroom/scripts/qa_trust.py @@ -7,7 +7,7 @@ from pathlib import Path from typing import Any -from common import load_json, resolve_room_path, utc_now, write_json +from common import default_workspace_path, load_json, resolve_room_path, utc_now, write_json def _as_list(value: Any) -> list[Any]: @@ -97,7 +97,7 @@ def build_qa_packet(room: Path) -> dict[str, Any]: "body": body, "parents": [parent] if parent else [], "workspace_kind": "dir", - "workspace_path": "${KURULTAI_HOME}", + "workspace_path": default_workspace_path(), "idempotency_key": f"buildroom-qa:{build_id}", "metadata": { "room_id": room.name, diff --git a/tools/kurultai/buildroom/scripts/retention_review.py b/tools/kurultai/buildroom/scripts/retention_review.py new file mode 100644 index 00000000..e4ebedc7 --- /dev/null +++ b/tools/kurultai/buildroom/scripts/retention_review.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +from pathlib import Path +from typing import Any + +ROOT = Path(__file__).resolve().parents[1] +ROOMS = ROOT / "rooms" +OUTPUT = ROOT / "control-room-retention-items.json" + + +def load_json(path: Path) -> dict[str, Any]: + if not path.exists(): + return {} + data = json.loads(path.read_text()) + return data if isinstance(data, dict) else {} + + +def write_json(path: Path, payload: Any) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n") + + +def room_dirs(root: Path) -> list[Path]: + if not root.exists(): + return [] + return sorted(path for path in root.iterdir() if path.is_dir()) + + +def retention_item(room: Path) -> dict[str, Any] | None: + retention = load_json(room / "retention" / "retention-review.json") + trust = load_json(room / "trust" / "trust-report.json") + summary = load_json(room / "operator" / "operator-summary.json") + recommendation = str(retention.get("recommendation") or retention.get("retention_recommendation") or "").lower() + status = str(retention.get("status") or retention.get("decision_status") or "pending").lower() + if recommendation in {"", "keep"} and status in {"resolved", "complete", "completed"}: + return None + needs_receipt = status not in {"resolved", "complete", "completed"} + trust_state = str(trust.get("trust_state") or trust.get("state") or "unknown") + next_action = str(summary.get("next_action") or "review retention recommendation and write receipt") + severity = "medium" if recommendation in {"improve", "prune"} or trust_state in {"watch", "investigate"} else "low" + return { + "id": f"retention:{room.name}", + "room_id": room.name, + "recommendation": recommendation or "review", + "status": status, + "trust_state": trust_state, + "severity": severity, + "needs_receipt": needs_receipt, + "next_action": next_action, + "receipt_template": f"brain/receipts/kurultai/{{date}}-{room.name}-retention-review.md", + } + + +def build_payload(rooms_root: Path) -> dict[str, Any]: + items = [item for room in room_dirs(rooms_root) if (item := retention_item(room))] + try: + rooms_ref = str(rooms_root.resolve().relative_to(ROOT.resolve())) + except ValueError: + rooms_ref = "${BUILDROOM_ROOMS}" + return {"rooms_root": rooms_ref, "items": items, "summary": {"items": len(items), "needs_receipt": sum(1 for item in items if item["needs_receipt"])}} + + +def main() -> int: + parser = argparse.ArgumentParser(description="Emit buildroom retention follow-up items without deleting anything.") + parser.add_argument("--rooms", type=Path, default=ROOMS) + parser.add_argument("--output", type=Path, default=OUTPUT) + parser.add_argument("--fail-on-items", action="store_true") + args = parser.parse_args() + payload = build_payload(args.rooms) + write_json(args.output, payload) + print(json.dumps(payload["summary"], sort_keys=True)) + return 1 if args.fail_on_items and payload["items"] else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/kurultai/buildroom/scripts/run_buildroom_cycle.py b/tools/kurultai/buildroom/scripts/run_buildroom_cycle.py new file mode 100644 index 00000000..9b30847b --- /dev/null +++ b/tools/kurultai/buildroom/scripts/run_buildroom_cycle.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +from __future__ import annotations +import argparse, json, subprocess +from datetime import datetime, timezone +from pathlib import Path +from typing import Any +ROOT = Path(__file__).resolve().parents[4] +BUILDROOM = ROOT / "tools" / "kurultai" / "buildroom" +PYTHON = "/opt/homebrew/opt/python@3.14/bin/python3.14" +def now() -> str: return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00","Z") +def run(cmd: list[str], cwd: Path = ROOT, allow_codes: set[int] | None = None) -> dict[str, Any]: + allow_codes=allow_codes or {0}; proc=subprocess.run(cmd, cwd=cwd, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + return {"command":cmd,"exit_code":proc.returncode,"ok":proc.returncode in allow_codes,"output_tail":"\n".join(proc.stdout.splitlines()[-20:])} +def room_dirs() -> list[Path]: + rooms=BUILDROOM/"rooms"; return [p for p in sorted(rooms.iterdir()) if p.is_dir()] if rooms.exists() else [] + +def load_json(path: Path) -> Any: + if not path.exists(): + return None + return json.loads(path.read_text()) + +def proposal_summary() -> dict[str, Any]: + radar = load_json(BUILDROOM / "opportunity-radar.json") or {} + radar_drafts = load_json(BUILDROOM / "opportunity-radar-kanban-drafts.json") or {} + control_drafts = load_json(BUILDROOM / "control-room-kanban-drafts.json") or {} + attention = load_json(BUILDROOM / "control-room-attention-items.json") or {} + retention = load_json(BUILDROOM / "control-room-retention-items.json") or {} + candidates = radar.get("candidates", []) if isinstance(radar, dict) else [] + def compact_candidate(candidate: dict[str, Any]) -> dict[str, Any]: + scores = candidate.get("scores", {}) if isinstance(candidate.get("scores"), dict) else {} + return { + "title": candidate.get("title"), + "disposition": candidate.get("disposition") or candidate.get("recommended_disposition"), + "summary": candidate.get("summary"), + "score": scores.get("total"), + "score_breakdown": {k: v for k, v in scores.items() if k != "total"}, + } + return { + "outcome": { + "attention_items": len(attention.get("items", [])) if isinstance(attention, dict) else 0, + "control_room_kanban_drafts": len(control_drafts.get("drafts", [])) if isinstance(control_drafts, dict) else 0, + "opportunity_radar_candidates": len(candidates), + "opportunity_radar_kanban_drafts": len(radar_drafts.get("drafts", [])) if isinstance(radar_drafts, dict) else 0, + "retention_items": len(retention.get("items", [])) if isinstance(retention, dict) else 0, + }, + "recommended_next_action": radar.get("recommended_next_action") if isinstance(radar, dict) else None, + "proposals": [compact_candidate(c) for c in candidates if isinstance(c, dict)], + "control_room_drafts": control_drafts.get("drafts", []) if isinstance(control_drafts, dict) else [], + } + +def print_proposal_report(summary: dict[str, Any], ok: bool, receipt: Path) -> None: + outcome = summary.get("outcome", {}) + print(f"Buildroom cycle {'passed' if ok else 'failed'}; receipt: {receipt}") + print("") + print("Outcome:") + for key, value in outcome.items(): + print(f"- {key.replace('_', ' ')}: {value}") + print("") + print("Proposals:") + proposals = summary.get("proposals", []) + if not proposals: + print("- none") + for index, proposal in enumerate(proposals, 1): + print(f"{index}. {proposal.get('title')} — {proposal.get('disposition')} — score {proposal.get('score')}") + if proposal.get("summary"): + print(f" {proposal['summary']}") + if summary.get("recommended_next_action"): + print("") + print(f"Recommended next action: {summary['recommended_next_action']}") + +def main() -> int: + parser=argparse.ArgumentParser(description="Run a local buildroom cycle to completion on command.") + parser.add_argument("--apply-kanban", action="store_true"); parser.add_argument("--skip-pytest", action="store_true"); parser.add_argument("--json", action="store_true"); parser.add_argument("--receipt-output", default=str(BUILDROOM/".generated"/"buildroom-cycle-receipt.json")); args=parser.parse_args(); steps=[] + for room in room_dirs(): steps.append(run(["python3", str(BUILDROOM/"scripts/validate_room.py"), str(room)])) + steps.append(run(["python3", str(BUILDROOM/"scripts/control_room.py")])) + steps.append(run(["python3", str(BUILDROOM/"scripts/control_room_cron.py")])) + steps.append(run(["python3", str(BUILDROOM/"scripts/opportunity_radar.py")])) + kanban_cmd=["python3", str(BUILDROOM/"scripts/apply_kanban_drafts.py"), "--output", str(BUILDROOM/".generated"/"kanban-apply-result.json")] + if args.apply_kanban: kanban_cmd.append("--apply") + steps.append(run(kanban_cmd)) + steps.append(run(["python3", str(BUILDROOM/"scripts/retention_review.py")])) + health=[PYTHON, str(BUILDROOM/"scripts/buildroom_healthcheck.py"), "--json"] + if args.skip_pytest: health.append("--skip-pytest") + steps.append(run(health)); ok=all(s["ok"] for s in steps); out=Path(args.receipt_output); proposals=proposal_summary() + receipt={"schema_version":"0.1.0","generated_at":now(),"source":"run-buildroom-cycle-v0","mode":"apply-kanban" if args.apply_kanban else "dry-run","ok":ok,"step_count":len(steps),"steps":steps,"proposal_summary":proposals,"outputs":{"control_room":"tools/kurultai/buildroom/control-room.md","opportunity_radar":"tools/kurultai/buildroom/opportunity-radar.md","kanban_apply_result":"tools/kurultai/buildroom/.generated/kanban-apply-result.json","retention_items":"tools/kurultai/buildroom/control-room-retention-items.json"}} + out.parent.mkdir(parents=True, exist_ok=True); out.write_text(json.dumps(receipt, indent=2, sort_keys=True)+"\n") + if args.json: print(json.dumps({"ok":ok,"step_count":len(steps),"receipt":str(out),"failed_steps":[s for s in steps if not s["ok"]],"proposal_summary":proposals}, indent=2, sort_keys=True)) + else: print_proposal_report(proposals, ok, out) + return 0 if ok else 1 +if __name__ == "__main__": raise SystemExit(main())