Sync: Complete project state with all MEGA SPRINT V1-V3 features and Codex stubs

This commit is contained in:
renato97
2026-04-08 17:58:47 -03:00
parent c9d3528900
commit 6d080d43b3
372 changed files with 189715 additions and 8590 deletions

11
ralph/.gitignore vendored Normal file
View File

@@ -0,0 +1,11 @@
config/*.local.json
runs/*
!runs/.gitkeep
logs/*
!logs/.gitkeep
state/*.json
state/*.jsonl
state/*.md
!state/.gitkeep
worktrees/*
!worktrees/.gitkeep

176
ralph/README.md Normal file
View File

@@ -0,0 +1,176 @@
# Ralph
Ralph is a local swarm runner for this Ableton MCP project.
It is built for the stack that already exists on this machine:
- `codex` authenticated via app login
- `claude -p` routed to Anthropic-compatible providers
- Windows native PowerShell
- this repository as the shared source of truth
## Design
Ralph does not try to make every model edit the same tree at once.
The default flow is:
1. create a task pack in `ralph/tasks/current/`
2. create a dedicated implementer worktree
3. run one implementer model against that worktree
4. run multiple reviewers against the resulting diff
5. run `codex exec resume` against the persistent Codex master session
6. optionally run a fix pass in the same implementer worktree
7. leave a run folder with prompts, outputs, reviews and diffs
## Default roles
- Implementer: `opencode_glm5`
- Reviewer 1: `opencode_qwen3coder_plus`
- Reviewer 2: `opencode_glm47`
- Codex master: persistent session reviewer and sprint writer
## Important safety rule
Ralph does not auto-merge into the main working tree.
It creates a worktree under `ralph/worktrees/` and leaves the result there for review or manual cherry-pick.
## Local config
Sensitive config is stored in:
- `ralph/config/providers.local.json`
- `ralph/config/codex.local.json`
Both are gitignored.
## Quick start
Smoke test providers:
```powershell
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Test-RalphProviders.ps1
```
Smoke test Codex master:
```powershell
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Test-RalphCodex.ps1
```
Run one autonomous pass on the current task pack:
```powershell
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Start-RalphAutopilot.ps1
```
Start the localhost dashboard:
```powershell
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Start-RalphDashboard.ps1
```
Default URL:
```text
http://127.0.0.1:8765
```
Dry run only:
```powershell
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Start-RalphAutopilot.ps1 -DryRun
```
Submit a single markdown task into the inbox:
```powershell
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Submit-RalphTask.ps1 `
-SourceFile .\docs\SPRINT_v0.1.40_NEXT_GLM_OPEN_PROJECT_EDITING_AND_COHERENCE.md
```
Run the inbox daemon in the foreground:
```powershell
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Start-RalphInboxDaemon.ps1
```
Run the inbox daemon in the background:
```powershell
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Start-RalphInboxBackground.ps1
```
Stop the inbox daemon:
```powershell
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Stop-RalphInboxDaemon.ps1
```
Check daemon and queue status:
```powershell
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Get-RalphStatus.ps1
```
Install the inbox daemon as a Windows Scheduled Task:
```powershell
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Install-RalphScheduledTask.ps1
```
Run Codex master review only:
```powershell
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Invoke-CodexMaster.ps1 `
-PromptFile .\ralph\tasks\current\TASK.md `
-OutputFile .\ralph\state\codex_master_manual_review.md
```
## Task pack
The current swarm task lives here:
- `ralph/tasks/current/TASK.md`
- `ralph/tasks/current/ACCEPTANCE.md`
- `ralph/tasks/current/CONTEXT.md`
Update those files before each autonomous run.
## Codex master session
Ralph is configured to reuse the persistent Codex session supplied by the user.
That gives Codex memory across reviews without needing an OpenAI API key.
## 24/7 inbox mode
Ralph can now run as a local queue processor:
- drop a single `.md` into `ralph/tasks/inbox/`
- the daemon converts it into a task pack automatically
- the implementer runs in an isolated worktree
- the configured reviewers run next
- Codex master performs the final review through `codex exec resume`
- the run only counts as complete if Codex final verdict is `pass`
The queue folders are:
- `ralph/tasks/inbox`
- `ralph/tasks/processing`
- `ralph/tasks/completed`
- `ralph/tasks/failed`
Recommended default routing:
- implementer: `opencode_glm5`
- reviewers: `opencode_qwen3coder_plus`, `opencode_glm47`
- final reviewer: persistent Codex master session
## Notes
- If a token was ever exposed outside this machine, rotate it.
- If Ableton runtime work is involved, the swarm still has to validate against the real Live session and logs.
- Provider quality is not treated as equal. `glm-5.1` remains the preferred reviewer.
- The dashboard reads local state files and recent run folders. It does not execute runs on its own.

View File

@@ -0,0 +1,10 @@
{
"auto_followup": {
"enabled": true,
"trigger_on_failure": true,
"trigger_on_success": true,
"max_chain_depth": 6,
"target_directory": "docs\\autopilot",
"title_prefix": "AUTOFOLLOWUP"
}
}

View File

@@ -0,0 +1,5 @@
{
"session_id": "REPLACE_ME",
"model": "gpt-5.4",
"working_directory": "C:\\ProgramData\\Ableton\\Live 12 Suite\\Resources\\MIDI Remote Scripts"
}

View File

@@ -0,0 +1,104 @@
{
"default_implementer": "opencode_glm5",
"default_reviewers": [
"opencode_qwen3coder_plus",
"opencode_glm47",
"opencode_glm5_review"
],
"providers": {
"opencode_glm5": {
"runner": "opencode",
"model": "bailian-coding-plan/glm-5",
"timeout_ms": 3000000,
"variant": "high"
},
"opencode_qwen3coder_plus": {
"runner": "opencode",
"model": "bailian-coding-plan/qwen3-coder-plus",
"timeout_ms": 3000000,
"variant": "high"
},
"opencode_glm47": {
"runner": "opencode",
"model": "bailian-coding-plan/glm-4.7",
"timeout_ms": 3000000,
"variant": "high"
},
"opencode_glm5_review": {
"runner": "opencode",
"model": "bailian-coding-plan/glm-5",
"timeout_ms": 3000000,
"variant": "high"
},
"opencode_qwen35": {
"runner": "opencode",
"model": "bailian-coding-plan/qwen3.5-plus",
"timeout_ms": 3000000,
"variant": "high"
},
"zai_glm51": {
"runner": "claude",
"base_url": "https://api.z.ai/api/anthropic",
"auth_token": "REPLACE_ME",
"model": "glm-5.1",
"timeout_ms": 3000000,
"experimental_agent_teams": true
},
"zai_glm47": {
"runner": "claude",
"base_url": "https://api.z.ai/api/anthropic",
"auth_token": "REPLACE_ME",
"model": "glm-4.7",
"timeout_ms": 3000000,
"experimental_agent_teams": false
},
"dashscope_glm5": {
"runner": "claude",
"base_url": "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic",
"auth_token": "REPLACE_ME",
"model": "glm-5",
"timeout_ms": 3000000,
"experimental_agent_teams": true
},
"dashscope_qwen35": {
"runner": "claude",
"base_url": "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic",
"auth_token": "REPLACE_ME",
"model": "qwen3.5-plus",
"timeout_ms": 3000000,
"experimental_agent_teams": false
},
"dashscope_qwen3coder_next": {
"runner": "claude",
"base_url": "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic",
"auth_token": "REPLACE_ME",
"model": "qwen3-coder-next",
"timeout_ms": 3000000,
"experimental_agent_teams": false
},
"dashscope_qwen3coder_plus": {
"runner": "claude",
"base_url": "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic",
"auth_token": "REPLACE_ME",
"model": "qwen3-coder-plus",
"timeout_ms": 3000000,
"experimental_agent_teams": false
},
"dashscope_minimax25": {
"runner": "claude",
"base_url": "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic",
"auth_token": "REPLACE_ME",
"model": "MiniMax-M2.5",
"timeout_ms": 3000000,
"experimental_agent_teams": true
},
"fireworks_kimi25": {
"runner": "claude",
"base_url": "https://api.fireworks.ai/inference",
"auth_token": "REPLACE_ME",
"model": "accounts/fireworks/routers/kimi-k2p5-turbo",
"timeout_ms": 3000000,
"experimental_agent_teams": true
}
}
}

View File

@@ -0,0 +1,25 @@
{
"enabled": false,
"prefix": "Ralph",
"timeout_seconds": 8,
"chat_ids": [
"123456789"
],
"bot_token": "replace-me",
"events": {
"task_queued": true,
"task_processing": true,
"task_completed": true,
"task_failed": true,
"run_started": true,
"implementer_completed": true,
"reviewers_started": true,
"fix_pass_started": true,
"run_heartbeat": true,
"run_completed": true,
"run_failed": true,
"codex_failed": true,
"daemon_started": false,
"daemon_stopped": false
}
}

47
ralph/gui/README.md Normal file
View File

@@ -0,0 +1,47 @@
# Ralph GUI
This is a minimal localhost dashboard for Ralph.
It does not execute runs by itself.
It only reads:
- `ralph/state/current_run.json`
- `ralph/state/last_background_run.json`
- `ralph/state/events.jsonl`
- `ralph/state/provider_smoke.json`
- recent folders under `ralph/runs/`
## Start
```powershell
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Start-RalphDashboard.ps1
```
Default URL:
```text
http://127.0.0.1:8765
```
## What it shows
- current run id, stage and status
- implementer state
- reviewer state
- Codex master state
- fix-pass state
- recent event timeline
- latest run folders and summary excerpts
- last background runner metadata
## Note
The dashboard is intentionally simple.
It is meant to answer:
- who is working right now
- what stage the swarm is in
- whether Codex review already happened
- whether a run completed, failed or stopped

491
ralph/gui/app.py Normal file
View File

@@ -0,0 +1,491 @@
#!/usr/bin/env python3
"""Minimal Ralph dashboard for localhost monitoring."""
from __future__ import annotations
import argparse
import json
import os
from datetime import datetime
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from typing import Any, Dict, List
from urllib.parse import urlparse
RALPH_ROOT = Path(__file__).resolve().parents[1]
STATE_DIR = RALPH_ROOT / "state"
RUNS_DIR = RALPH_ROOT / "runs"
def _read_json(path: Path, default: Any) -> Any:
try:
return json.loads(path.read_text(encoding="utf-8"))
except Exception:
return default
def _read_text(path: Path) -> str:
try:
return path.read_text(encoding="utf-8")
except Exception:
return ""
def _tail_jsonl(path: Path, limit: int = 80) -> List[Dict[str, Any]]:
if not path.exists():
return []
try:
lines = path.read_text(encoding="utf-8").splitlines()
except Exception:
return []
events: List[Dict[str, Any]] = []
for line in lines[-limit:]:
line = line.strip()
if not line:
continue
try:
events.append(json.loads(line))
except Exception:
continue
events.reverse()
return events
def _is_pid_running(pid: Any) -> bool:
try:
pid_value = int(pid)
except Exception:
return False
if pid_value <= 0:
return False
try:
os.kill(pid_value, 0)
except PermissionError:
return True
except OSError:
return False
return True
def _iso_to_local(value: Any) -> str:
if not value:
return ""
try:
return datetime.fromisoformat(str(value).replace("Z", "+00:00")).strftime("%Y-%m-%d %H:%M:%S")
except Exception:
return str(value)
def _collect_recent_runs(limit: int = 8) -> List[Dict[str, Any]]:
runs: List[Dict[str, Any]] = []
if not RUNS_DIR.exists():
return runs
for run_dir in sorted([p for p in RUNS_DIR.iterdir() if p.is_dir()], key=lambda item: item.name, reverse=True)[:limit]:
summary_path = run_dir / "SUMMARY.md"
final_status_path = run_dir / "final_status.txt"
summary_text = _read_text(summary_path).strip()
final_status_text = _read_text(final_status_path).strip()
changes_exists = (run_dir / "CHANGES.md").exists()
implementer_patch = (run_dir / "implementer.patch").exists()
final_patch = (run_dir / "final.patch").exists()
runs.append(
{
"run_id": run_dir.name,
"path": str(run_dir),
"updated_at": datetime.fromtimestamp(run_dir.stat().st_mtime).strftime("%Y-%m-%d %H:%M:%S"),
"summary_excerpt": "\n".join(summary_text.splitlines()[:10]),
"final_status_excerpt": "\n".join(final_status_text.splitlines()[:8]),
"changes_exists": changes_exists,
"implementer_patch": implementer_patch,
"final_patch": final_patch,
}
)
return runs
def build_dashboard() -> Dict[str, Any]:
current_run = _read_json(STATE_DIR / "current_run.json", {})
background = _read_json(STATE_DIR / "last_background_run.json", {})
background["alive"] = _is_pid_running(background.get("pid"))
return {
"generated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"current_run": current_run,
"background": background,
"events": _tail_jsonl(STATE_DIR / "events.jsonl", limit=120),
"recent_runs": _collect_recent_runs(),
"provider_smoke": _read_json(STATE_DIR / "provider_smoke.json", {}),
}
HTML_TEMPLATE = """<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Ralph Dashboard</title>
<style>
:root {
--bg: #f3efe5;
--ink: #1a1a1a;
--muted: #5f5a52;
--card: #fffaf2;
--line: #d8cfbf;
--ok: #2a7f62;
--warn: #a96814;
--bad: #ad2e24;
--run: #144c7d;
--shadow: rgba(0, 0, 0, 0.08);
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: "Segoe UI", "Helvetica Neue", Arial, sans-serif;
background: linear-gradient(180deg, #efe7d8 0%, var(--bg) 100%);
color: var(--ink);
}
header {
padding: 20px 24px;
background: #17324d;
color: #fff8eb;
border-bottom: 4px solid #c98b2b;
}
header h1 { margin: 0 0 6px; font-size: 28px; }
header p { margin: 0; color: #d7e4ef; }
main { padding: 20px 24px 32px; }
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 16px;
margin-bottom: 18px;
}
.card {
background: var(--card);
border: 1px solid var(--line);
border-radius: 14px;
padding: 16px;
box-shadow: 0 8px 24px var(--shadow);
}
.card h2 {
margin: 0 0 10px;
font-size: 18px;
}
.muted { color: var(--muted); }
.badge {
display: inline-block;
padding: 4px 9px;
border-radius: 999px;
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.04em;
background: #ece4d6;
color: var(--ink);
}
.status-completed, .status-success, .status-running { color: var(--ok); }
.status-failed, .status-error { color: var(--bad); }
.status-pending, .status-warning { color: var(--warn); }
.agents { display: grid; gap: 10px; }
.agent {
border: 1px solid var(--line);
border-radius: 10px;
padding: 10px 12px;
background: #fffdf8;
}
.agent strong { display: block; margin-bottom: 4px; }
.timeline {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 10px;
max-height: 540px;
overflow: auto;
}
.timeline li {
border-left: 4px solid #c98b2b;
padding: 8px 10px 8px 12px;
background: #fffdf8;
border-radius: 8px;
border: 1px solid var(--line);
}
.run-list {
display: grid;
gap: 12px;
}
.run-item {
border: 1px solid var(--line);
border-radius: 12px;
padding: 12px;
background: #fffdf8;
}
pre {
white-space: pre-wrap;
word-break: break-word;
background: #f6f1e8;
border: 1px solid var(--line);
border-radius: 10px;
padding: 12px;
margin: 8px 0 0;
font-size: 12px;
}
.empty {
padding: 16px;
border: 1px dashed var(--line);
border-radius: 10px;
color: var(--muted);
background: #faf6ef;
}
</style>
</head>
<body>
<header>
<h1>Ralph Dashboard</h1>
<p>Simple local view of who is working, what stage the swarm is in, and what happened last.</p>
</header>
<main>
<div class="grid">
<section class="card">
<h2>Overview</h2>
<div id="overview" class="muted">Loading...</div>
</section>
<section class="card">
<h2>Background Runner</h2>
<div id="background" class="muted">Loading...</div>
</section>
<section class="card">
<h2>Provider Smoke</h2>
<div id="providers" class="muted">Loading...</div>
</section>
</div>
<div class="grid">
<section class="card">
<h2>Current Run</h2>
<div id="current-run" class="muted">Loading...</div>
</section>
<section class="card">
<h2>Agent Status</h2>
<div id="agents" class="agents"></div>
</section>
</div>
<div class="grid">
<section class="card">
<h2>Timeline</h2>
<ul id="timeline" class="timeline"></ul>
</section>
<section class="card">
<h2>Recent Runs</h2>
<div id="recent-runs" class="run-list"></div>
</section>
</div>
</main>
<script>
function statusBadge(value) {
const text = value || "unknown";
const cls = "badge status-" + String(text).toLowerCase();
return `<span class="${cls}">${text}</span>`;
}
function renderAgent(name, data) {
if (!data) return "";
const started = data.started_at ? new Date(data.started_at).toLocaleString() : "";
const finished = data.finished_at ? new Date(data.finished_at).toLocaleString() : "";
const output = data.output_file ? `<div class="muted">${data.output_file}</div>` : "";
return `
<div class="agent">
<strong>${name}</strong>
<div>${statusBadge(data.status)}</div>
${started ? `<div class="muted">Start: ${started}</div>` : ""}
${finished ? `<div class="muted">End: ${finished}</div>` : ""}
${output}
</div>
`;
}
function renderOverview(data) {
const run = data.current_run || {};
if (!run.run_id) {
return `<div class="empty">No current run state has been written yet.</div>`;
}
return `
<div><strong>Run ID:</strong> ${run.run_id}</div>
<div><strong>Stage:</strong> ${statusBadge(run.stage)}</div>
<div><strong>Status:</strong> ${statusBadge(run.status)}</div>
<div><strong>Message:</strong> ${run.latest_message || "-"}</div>
<div class="muted">Updated: ${data.generated_at}</div>
`;
}
function renderBackground(data) {
const bg = data.background || {};
if (!bg.pid) {
return `<div class="empty">No background launch metadata found.</div>`;
}
return `
<div><strong>PID:</strong> ${bg.pid}</div>
<div><strong>Alive:</strong> ${statusBadge(bg.alive ? "running" : "stopped")}</div>
<div><strong>Run label:</strong> ${bg.run_label || "-"}</div>
<div class="muted">${bg.stdout_log || ""}</div>
<div class="muted">${bg.stderr_log || ""}</div>
`;
}
function renderProviders(data) {
const smoke = data.provider_smoke || {};
const checks = smoke.checks || [];
if (!checks.length) {
return `<div class="empty">No provider smoke data found yet.</div>`;
}
return checks.map((item) => `
<div class="agent">
<strong>${item.provider || "provider"}</strong>
<div>${statusBadge(item.status || "unknown")}</div>
<div class="muted">${item.model || ""}</div>
</div>
`).join("");
}
function renderCurrentRun(data) {
const run = data.current_run || {};
if (!run.run_id) {
return `<div class="empty">No active or recent run state.</div>`;
}
return `
<div><strong>Run:</strong> ${run.run_id}</div>
<div><strong>Task dir:</strong> ${run.task_directory || "-"}</div>
<div><strong>Worktree:</strong> ${run.worktree || "-"}</div>
<div><strong>Started:</strong> ${run.started_at ? new Date(run.started_at).toLocaleString() : "-"}</div>
<div><strong>Finished:</strong> ${run.finished_at ? new Date(run.finished_at).toLocaleString() : "-"}</div>
${(run.errors || []).length ? `<pre>${(run.errors || []).join("\\n")}</pre>` : ""}
`;
}
function renderAgents(data) {
const run = data.current_run || {};
const html = [];
if (run.implementer) {
html.push(renderAgent("Implementer: " + (run.implementer.name || "unknown"), run.implementer));
}
(run.reviewers || []).forEach((reviewer) => {
html.push(renderAgent("Reviewer: " + (reviewer.name || "unknown"), reviewer));
});
if (run.codex_master) {
html.push(renderAgent("Codex Master", run.codex_master));
}
if (run.fix_pass) {
html.push(renderAgent("Fix Pass", run.fix_pass));
}
document.getElementById("agents").innerHTML = html.length ? html.join("") : `<div class="empty">No agent state yet.</div>`;
}
function renderTimeline(data) {
const events = data.events || [];
const target = document.getElementById("timeline");
if (!events.length) {
target.innerHTML = `<li class="empty">No events yet.</li>`;
return;
}
target.innerHTML = events.map((event) => `
<li>
<div><strong>${event.actor || "system"}</strong> ${statusBadge(event.status || "unknown")}</div>
<div>${event.message || "-"}</div>
<div class="muted">${event.stage || "-"} · ${new Date(event.timestamp).toLocaleString()}</div>
</li>
`).join("");
}
function renderRecentRuns(data) {
const runs = data.recent_runs || [];
const target = document.getElementById("recent-runs");
if (!runs.length) {
target.innerHTML = `<div class="empty">No run folders found.</div>`;
return;
}
target.innerHTML = runs.map((run) => `
<div class="run-item">
<div><strong>${run.run_id}</strong></div>
<div class="muted">${run.updated_at}</div>
<div>${run.changes_exists ? statusBadge("changes") : statusBadge("no changes")}</div>
<div>${run.final_patch ? statusBadge("final patch") : statusBadge("no final patch")}</div>
${run.final_status_excerpt ? `<pre>${run.final_status_excerpt}</pre>` : ""}
${run.summary_excerpt ? `<pre>${run.summary_excerpt}</pre>` : ""}
</div>
`).join("");
}
async function refresh() {
const response = await fetch("/api/dashboard", { cache: "no-store" });
const data = await response.json();
document.getElementById("overview").innerHTML = renderOverview(data);
document.getElementById("background").innerHTML = renderBackground(data);
document.getElementById("providers").innerHTML = renderProviders(data);
document.getElementById("current-run").innerHTML = renderCurrentRun(data);
renderAgents(data);
renderTimeline(data);
renderRecentRuns(data);
}
refresh();
setInterval(refresh, 2000);
</script>
</body>
</html>
"""
class RalphHandler(BaseHTTPRequestHandler):
def _send_json(self, payload: Dict[str, Any]) -> None:
body = json.dumps(payload, indent=2).encode("utf-8")
self.send_response(HTTPStatus.OK)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Cache-Control", "no-store")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def _send_html(self, payload: str) -> None:
body = payload.encode("utf-8")
self.send_response(HTTPStatus.OK)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.send_header("Cache-Control", "no-store")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self) -> None:
route = urlparse(self.path).path
if route == "/api/dashboard":
self._send_json(build_dashboard())
return
if route == "/" or route == "/index.html":
self._send_html(HTML_TEMPLATE)
return
self.send_error(HTTPStatus.NOT_FOUND, "Not found")
def log_message(self, fmt: str, *args: Any) -> None:
return
def main() -> None:
parser = argparse.ArgumentParser(description="Run the Ralph localhost dashboard.")
parser.add_argument("--host", default="127.0.0.1", help="Bind host (default: 127.0.0.1)")
parser.add_argument("--port", type=int, default=8765, help="Bind port (default: 8765)")
args = parser.parse_args()
server = ThreadingHTTPServer((args.host, args.port), RalphHandler)
print(f"Ralph dashboard listening on http://{args.host}:{args.port}")
server.serve_forever()
if __name__ == "__main__":
main()

1
ralph/logs/.gitkeep Normal file
View File

@@ -0,0 +1 @@

1
ralph/runs/.gitkeep Normal file
View File

@@ -0,0 +1 @@

664
ralph/scripts/Common.ps1 Normal file
View File

@@ -0,0 +1,664 @@
Set-StrictMode -Version 3.0
$ErrorActionPreference = "Stop"
function Get-RalphRoot {
return (Split-Path -Parent $PSScriptRoot)
}
function Get-RepoRoot {
return (Split-Path -Parent (Get-RalphRoot))
}
function Ensure-Directory {
param([Parameter(Mandatory = $true)][string]$Path)
if (-not (Test-Path $Path)) {
New-Item -ItemType Directory -Force -Path $Path | Out-Null
}
}
function Read-JsonFile {
param([Parameter(Mandatory = $true)][string]$Path)
if (-not (Test-Path $Path)) {
throw "JSON file not found: $Path"
}
return Get-Content -Raw -Path $Path | ConvertFrom-Json
}
function Get-ProvidersConfig {
$ralphRoot = Get-RalphRoot
$localPath = Join-Path $ralphRoot "config\providers.local.json"
$examplePath = Join-Path $ralphRoot "config\providers.example.json"
if (Test-Path $localPath) {
return Read-JsonFile -Path $localPath
}
return Read-JsonFile -Path $examplePath
}
function Get-ProviderConfig {
param([Parameter(Mandatory = $true)][string]$Name)
$config = Get-ProvidersConfig
if (-not $config.providers.PSObject.Properties.Name.Contains($Name)) {
throw "Provider '$Name' not found in providers config."
}
return $config.providers.$Name
}
function Get-DefaultImplementer {
$config = Get-ProvidersConfig
return [string]$config.default_implementer
}
function Get-DefaultReviewers {
$config = Get-ProvidersConfig
return @($config.default_reviewers)
}
function Get-CodexConfig {
$ralphRoot = Get-RalphRoot
$localPath = Join-Path $ralphRoot "config\codex.local.json"
$examplePath = Join-Path $ralphRoot "config\codex.example.json"
if (Test-Path $localPath) {
return Read-JsonFile -Path $localPath
}
return Read-JsonFile -Path $examplePath
}
function Get-RalphAutomationConfig {
$ralphRoot = Get-RalphRoot
$localPath = Join-Path $ralphRoot "config\automation.local.json"
$examplePath = Join-Path $ralphRoot "config\automation.example.json"
if (Test-Path $localPath) {
return Read-JsonFile -Path $localPath
}
if (Test-Path $examplePath) {
return Read-JsonFile -Path $examplePath
}
return $null
}
function Get-TelegramConfig {
$ralphRoot = Get-RalphRoot
$localPath = Join-Path $ralphRoot "config\telegram.local.json"
$examplePath = Join-Path $ralphRoot "config\telegram.example.json"
if (Test-Path $localPath) {
return Read-JsonFile -Path $localPath
}
if (Test-Path $examplePath) {
return Read-JsonFile -Path $examplePath
}
return $null
}
function Get-TelegramChatIds {
param($Config)
if ($null -eq $Config) {
return @()
}
if ($Config.PSObject.Properties.Name -contains "chat_ids") {
return @($Config.chat_ids | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) } | ForEach-Object { [string]$_ })
}
if ($Config.PSObject.Properties.Name -contains "chat_id" -and -not [string]::IsNullOrWhiteSpace([string]$Config.chat_id)) {
return @([string]$Config.chat_id)
}
return @()
}
function Test-TelegramEventEnabled {
param(
[Parameter(Mandatory = $true)]$Config,
[Parameter(Mandatory = $true)][string]$EventName
)
$enabled = $false
try { $enabled = [bool]$Config.enabled } catch { $enabled = $false }
if (-not $enabled) {
return $false
}
if ($Config.PSObject.Properties.Name -contains "events" -and $null -ne $Config.events) {
$eventProperty = $Config.events.PSObject.Properties[$EventName]
if ($null -ne $eventProperty) {
try { return [bool]$eventProperty.Value } catch { return $false }
}
}
return $true
}
function Send-TelegramNotification {
param(
[Parameter(Mandatory = $true)][string]$EventName,
[Parameter(Mandatory = $true)][string]$Title,
[Parameter(Mandatory = $true)][string]$Message,
[string]$RunId = "",
[string]$Stage = "",
[string]$Status = ""
)
try {
$config = Get-TelegramConfig
}
catch {
return [ordered]@{
sent = $false
reason = $_.Exception.Message
delivered = 0
errors = @()
}
}
if ($null -eq $config) {
return [ordered]@{
sent = $false
reason = "telegram config missing"
delivered = 0
errors = @()
}
}
if (-not (Test-TelegramEventEnabled -Config $config -EventName $EventName)) {
return [ordered]@{
sent = $false
reason = "event disabled"
delivered = 0
errors = @()
}
}
$botToken = ""
if ($config.PSObject.Properties.Name -contains "bot_token") {
$botToken = [string]$config.bot_token
}
if ([string]::IsNullOrWhiteSpace($botToken)) {
return [ordered]@{
sent = $false
reason = "bot token missing"
delivered = 0
errors = @()
}
}
$chatIds = @(Get-TelegramChatIds -Config $config)
if ($chatIds.Count -eq 0) {
return [ordered]@{
sent = $false
reason = "chat ids missing"
delivered = 0
errors = @()
}
}
$timeoutSeconds = 8
if ($config.PSObject.Properties.Name -contains "timeout_seconds") {
try {
$timeoutSeconds = [int]$config.timeout_seconds
}
catch {
$timeoutSeconds = 8
}
}
$prefix = "Ralph"
if ($config.PSObject.Properties.Name -contains "prefix" -and -not [string]::IsNullOrWhiteSpace([string]$config.prefix)) {
$prefix = [string]$config.prefix
}
$lines = @(
("{0} | {1}" -f $prefix, $Title.Trim())
$Message.Trim()
)
if (-not [string]::IsNullOrWhiteSpace($RunId)) {
$lines += ("run: " + $RunId)
}
if (-not [string]::IsNullOrWhiteSpace($Stage) -or -not [string]::IsNullOrWhiteSpace($Status)) {
$statusLine = @()
if (-not [string]::IsNullOrWhiteSpace($Stage)) {
$statusLine += ("stage=" + $Stage)
}
if (-not [string]::IsNullOrWhiteSpace($Status)) {
$statusLine += ("status=" + $Status)
}
if ($statusLine.Count -gt 0) {
$lines += ($statusLine -join " ")
}
}
$text = ($lines -join "`n").Trim()
$uri = "https://api.telegram.org/bot{0}/sendMessage" -f $botToken
$delivered = 0
$errors = New-Object System.Collections.Generic.List[string]
foreach ($chatId in $chatIds) {
try {
$body = @{
chat_id = $chatId
text = $text
disable_web_page_preview = $true
}
Invoke-RestMethod -Method Post -Uri $uri -Body $body -ContentType "application/x-www-form-urlencoded" -TimeoutSec $timeoutSeconds | Out-Null
$delivered += 1
}
catch {
$errors.Add(("{0}: {1}" -f $chatId, $_.Exception.Message))
}
}
return [ordered]@{
sent = ($delivered -gt 0)
reason = $(if ($delivered -gt 0) { "ok" } else { "delivery failed" })
delivered = $delivered
errors = @($errors)
}
}
function New-RunId {
param([string]$Label = "autopilot")
return "{0}-{1}" -f (Get-Date -Format "yyyyMMdd-HHmmss"), $Label
}
function Get-TaskFiles {
param([string]$TaskDirectory = $(Join-Path (Get-RalphRoot) "tasks\current"))
return @{
Task = Join-Path $TaskDirectory "TASK.md"
Acceptance = Join-Path $TaskDirectory "ACCEPTANCE.md"
Context = Join-Path $TaskDirectory "CONTEXT.md"
}
}
function Get-RalphInboxDirectory {
return (Join-Path (Get-RalphRoot) "tasks\inbox")
}
function Get-RalphProcessingDirectory {
return (Join-Path (Get-RalphRoot) "tasks\processing")
}
function Get-RalphArchiveDirectory {
return (Join-Path (Get-RalphRoot) "tasks\completed")
}
function Get-RalphFailedDirectory {
return (Join-Path (Get-RalphRoot) "tasks\failed")
}
function Read-TaskPack {
param([string]$TaskDirectory = $(Join-Path (Get-RalphRoot) "tasks\current"))
$files = Get-TaskFiles -TaskDirectory $TaskDirectory
foreach ($entry in $files.GetEnumerator()) {
if (-not (Test-Path $entry.Value)) {
throw "Task pack file missing: $($entry.Value)"
}
}
return @{
Files = $files
Task = Get-Content -Raw -Path $files.Task
Acceptance = Get-Content -Raw -Path $files.Acceptance
Context = Get-Content -Raw -Path $files.Context
}
}
function Convert-MarkdownToRalphTaskPack {
param(
[Parameter(Mandatory = $true)][string]$Path
)
if (-not (Test-Path $Path)) {
throw "Task markdown not found: $Path"
}
$raw = Get-Content -Raw -Path $Path
$lines = $raw -split "`r?`n"
$taskLines = New-Object System.Collections.Generic.List[string]
$acceptanceLines = New-Object System.Collections.Generic.List[string]
$contextLines = New-Object System.Collections.Generic.List[string]
$current = "task"
$title = ""
foreach ($line in $lines) {
if ($line -match '^\s*#{1,3}\s+(.*\S)\s*$') {
$heading = $matches[1].Trim()
if (-not $title -and $line -match '^\s*#\s+') {
$title = $heading
}
$headingLower = $heading.ToLowerInvariant()
if ($headingLower -match '^(acceptance|acceptance criteria|criteria|done|definition of done)\b') {
$current = "acceptance"
}
elseif ($headingLower -match '^(context|background|notes|references)\b') {
$current = "context"
}
else {
$current = "task"
$taskLines.Add($line)
}
continue
}
switch ($current) {
"acceptance" { $acceptanceLines.Add($line) }
"context" { $contextLines.Add($line) }
default { $taskLines.Add($line) }
}
}
$taskText = (($taskLines -join "`n").Trim())
$acceptanceText = (($acceptanceLines -join "`n").Trim())
$contextText = (($contextLines -join "`n").Trim())
if ([string]::IsNullOrWhiteSpace($taskText)) {
$taskText = $raw.Trim()
}
if ([string]::IsNullOrWhiteSpace($acceptanceText)) {
$acceptanceText = Get-Content -Raw -Path (Join-Path (Get-RalphRoot) "templates\ACCEPTANCE.md")
}
if ([string]::IsNullOrWhiteSpace($contextText)) {
$contextText = Get-Content -Raw -Path (Join-Path (Get-RalphRoot) "templates\CONTEXT.md")
}
if ([string]::IsNullOrWhiteSpace($title)) {
$title = [IO.Path]::GetFileNameWithoutExtension($Path)
}
return [ordered]@{
Title = $title
SourcePath = $Path
RawContent = $raw
Task = $taskText
Acceptance = $acceptanceText
Context = $contextText
}
}
function Write-RalphTaskPackDirectory {
param(
[Parameter(Mandatory = $true)][hashtable]$TaskPack,
[Parameter(Mandatory = $true)][string]$DestinationDirectory
)
Ensure-Directory -Path $DestinationDirectory
Write-Utf8File -Path (Join-Path $DestinationDirectory "TASK.md") -Content (($TaskPack.Task.Trim()) + "`n")
Write-Utf8File -Path (Join-Path $DestinationDirectory "ACCEPTANCE.md") -Content (($TaskPack.Acceptance.Trim()) + "`n")
Write-Utf8File -Path (Join-Path $DestinationDirectory "CONTEXT.md") -Content (($TaskPack.Context.Trim()) + "`n")
if ($TaskPack.ContainsKey("SourcePath") -and (Test-Path $TaskPack.SourcePath)) {
Copy-Item -Path $TaskPack.SourcePath -Destination (Join-Path $DestinationDirectory "SOURCE.md") -Force
}
}
function Write-Utf8File {
param(
[Parameter(Mandatory = $true)][string]$Path,
[Parameter(Mandatory = $true)][AllowEmptyString()][string]$Content
)
$encoding = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($Path, $Content, $encoding)
}
function Write-JsonFile {
param(
[Parameter(Mandatory = $true)][string]$Path,
[Parameter(Mandatory = $true)]$Object
)
$json = $Object | ConvertTo-Json -Depth 100
Write-Utf8File -Path $Path -Content ($json + "`n")
}
function Get-RalphStateFile {
param([Parameter(Mandatory = $true)][string]$Name)
return Join-Path (Get-RalphRoot) ("state\" + $Name)
}
function Get-RalphTaskRoots {
$ralphRoot = Get-RalphRoot
return [ordered]@{
TasksRoot = Join-Path $ralphRoot "tasks"
Inbox = Join-Path $ralphRoot "tasks\inbox"
Processing = Join-Path $ralphRoot "tasks\processing"
Completed = Join-Path $ralphRoot "tasks\completed"
Failed = Join-Path $ralphRoot "tasks\failed"
}
}
function Convert-ToRalphSlug {
param([Parameter(Mandatory = $true)][string]$Text)
$slug = [string]$Text
$slug = $slug.Trim().ToLowerInvariant()
if ([string]::IsNullOrWhiteSpace($slug)) {
return "task"
}
$slug = [regex]::Replace($slug, "[^a-z0-9]+", "-")
$slug = $slug.Trim("-")
if ([string]::IsNullOrWhiteSpace($slug)) {
return "task"
}
if ($slug.Length -gt 48) {
$slug = $slug.Substring(0, 48).Trim("-")
}
return $slug
}
function Get-RalphTaskTitleFromMarkdown {
param(
[string]$Markdown = "",
[string]$Fallback = "task"
)
$lines = @($Markdown -split "`r?`n")
foreach ($line in $lines) {
$trimmed = [string]$line
$trimmed = $trimmed.Trim()
if ([string]::IsNullOrWhiteSpace($trimmed)) {
continue
}
if ($trimmed.StartsWith("#")) {
return ($trimmed.TrimStart("#").Trim())
}
return $trimmed
}
return $Fallback
}
function New-RalphTaskPackFromMarkdown {
param(
[Parameter(Mandatory = $true)][string]$MarkdownPath,
[string]$TargetDirectory = "",
[string]$TaskId = "",
[string]$Title = "",
[hashtable]$AdditionalMetadata = @{}
)
if (-not (Test-Path $MarkdownPath)) {
throw "Markdown source not found: $MarkdownPath"
}
$roots = Get-RalphTaskRoots
foreach ($path in $roots.Values) {
Ensure-Directory -Path $path
}
$parsedTaskPack = Convert-MarkdownToRalphTaskPack -Path $MarkdownPath
$sourceText = [string]$parsedTaskPack.RawContent
$sourceName = [System.IO.Path]::GetFileNameWithoutExtension($MarkdownPath)
if ([string]::IsNullOrWhiteSpace($Title)) {
$Title = [string]$parsedTaskPack.Title
}
if ([string]::IsNullOrWhiteSpace($TaskId)) {
$TaskId = "{0}-{1}" -f (Get-Date -Format "yyyyMMdd-HHmmss"), (Convert-ToRalphSlug -Text $Title)
}
if ([string]::IsNullOrWhiteSpace($TargetDirectory)) {
$TargetDirectory = Join-Path $roots.Inbox $TaskId
}
Ensure-Directory -Path $TargetDirectory
$taskPath = Join-Path $TargetDirectory "TASK.md"
$acceptancePath = Join-Path $TargetDirectory "ACCEPTANCE.md"
$contextPath = Join-Path $TargetDirectory "CONTEXT.md"
$metadataPath = Join-Path $TargetDirectory "submission.json"
$sourceCopyPath = Join-Path $TargetDirectory "SOURCE.md"
$acceptance = [string]$parsedTaskPack.Acceptance
$context = [string]$parsedTaskPack.Context
$taskBody = [string]$parsedTaskPack.Task
Write-Utf8File -Path $taskPath -Content ($taskBody.Trim() + "`n")
Write-Utf8File -Path $sourceCopyPath -Content ($sourceText.Trim() + "`n")
Write-Utf8File -Path $acceptancePath -Content ($acceptance + "`n")
Write-Utf8File -Path $contextPath -Content ($context + "`n")
$metadata = [ordered]@{
id = $TaskId
title = $Title
submitted_at = (Get-Date).ToString("o")
source_path = (Resolve-Path $MarkdownPath).Path
task_directory = $TargetDirectory
state = "queued"
}
foreach ($key in $AdditionalMetadata.Keys) {
$metadata[$key] = $AdditionalMetadata[$key]
}
Write-JsonFile -Path $metadataPath -Object $metadata
return [ordered]@{
id = $TaskId
title = $Title
task_directory = $TargetDirectory
task_file = $taskPath
acceptance_file = $acceptancePath
context_file = $contextPath
metadata_file = $metadataPath
source_copy = $sourceCopyPath
}
}
function Read-CodexReviewVerdict {
param([Parameter(Mandatory = $true)][string]$Path)
if (-not (Test-Path $Path)) {
throw "Codex review file not found: $Path"
}
$raw = (Get-Content -Raw -Path $Path).Trim()
if ([string]::IsNullOrWhiteSpace($raw)) {
throw "Codex review file is empty: $Path"
}
$jsonText = $raw
if ($raw -match '(?s)```json\s*(\{.*?\})\s*```') {
$jsonText = $matches[1]
}
elseif ($raw -match '(?s)(\{.*\})') {
$jsonText = $matches[1]
}
try {
$parsed = $jsonText | ConvertFrom-Json
}
catch {
throw "Codex review output is not valid JSON: $Path"
}
$verdict = [string]$parsed.verdict
if ([string]::IsNullOrWhiteSpace($verdict)) {
throw "Codex review JSON missing 'verdict': $Path"
}
$normalizedVerdict = $verdict.Trim().ToLowerInvariant()
if ($normalizedVerdict -notin @("pass", "needs_fix", "fail")) {
throw "Codex review verdict '$verdict' is invalid. Expected: pass, needs_fix, fail."
}
$acceptancePassed = $false
try {
$acceptancePassed = [bool]$parsed.acceptance_passed
}
catch {
$acceptancePassed = ($normalizedVerdict -eq "pass")
}
return [ordered]@{
verdict = $normalizedVerdict
acceptance_passed = $acceptancePassed
fix_required = [bool]$parsed.fix_required
summary = [string]$parsed.summary
next_sprint_needed = [bool]$parsed.next_sprint_needed
next_sprint_brief = [string]$parsed.next_sprint_brief
highest_risk_issues = @($parsed.highest_risk_issues)
raw = $parsed
}
}
function Add-RalphEvent {
param(
[Parameter(Mandatory = $true)][string]$RunId,
[Parameter(Mandatory = $true)][string]$Stage,
[Parameter(Mandatory = $true)][string]$Status,
[Parameter(Mandatory = $true)][string]$Message,
[string]$Actor = "system",
[hashtable]$Data = @{}
)
$eventsPath = Get-RalphStateFile -Name "events.jsonl"
Ensure-Directory -Path (Split-Path -Parent $eventsPath)
$event = [ordered]@{
timestamp = (Get-Date).ToString("o")
run_id = $RunId
actor = $Actor
stage = $Stage
status = $Status
message = $Message
data = $Data
}
$encoding = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::AppendAllText($eventsPath, (($event | ConvertTo-Json -Depth 20 -Compress) + "`n"), $encoding)
}
function Set-RalphCurrentRunState {
param([Parameter(Mandatory = $true)][hashtable]$State)
$statePath = Get-RalphStateFile -Name "current_run.json"
Write-JsonFile -Path $statePath -Object $State
}
function New-PromptDocument {
param(
[Parameter(Mandatory = $true)][string]$TemplatePath,
[Parameter(Mandatory = $true)][hashtable]$TaskPack,
[Parameter(Mandatory = $true)][string]$OutputPath,
[string[]]$ExtraSections = @()
)
$template = Get-Content -Raw -Path $TemplatePath
$sections = @(
$template,
"## TASK`n$($TaskPack.Task)",
"## ACCEPTANCE`n$($TaskPack.Acceptance)",
"## CONTEXT`n$($TaskPack.Context)"
) + $ExtraSections
Write-Utf8File -Path $OutputPath -Content (($sections -join "`n`n").Trim() + "`n")
}

View File

@@ -0,0 +1,63 @@
. (Join-Path $PSScriptRoot "Common.ps1")
$roots = Get-RalphTaskRoots
$lockFile = Get-RalphStateFile -Name "inbox_daemon.lock.json"
$daemonStateFile = Get-RalphStateFile -Name "inbox_daemon_state.json"
$currentRunFile = Get-RalphStateFile -Name "current_run.json"
$backgroundFile = Join-Path (Get-RalphRoot) "state\last_inbox_background.json"
function Get-ItemCount {
param([object[]]$Items)
return [int](($Items | Measure-Object).Count)
}
$result = [ordered]@{
inbox = [ordered]@{
queued_taskpacks = Get-ItemCount -Items @(Get-ChildItem -LiteralPath $roots.Inbox -Directory -ErrorAction SilentlyContinue | Where-Object { Test-Path (Join-Path $_.FullName "TASK.md") })
queued_markdown_files = Get-ItemCount -Items @(Get-ChildItem -LiteralPath $roots.Inbox -File -Filter *.md -ErrorAction SilentlyContinue)
processing = Get-ItemCount -Items @(Get-ChildItem -LiteralPath $roots.Processing -Directory -ErrorAction SilentlyContinue)
completed = Get-ItemCount -Items @(Get-ChildItem -LiteralPath $roots.Completed -Directory -ErrorAction SilentlyContinue)
failed = Get-ItemCount -Items @(Get-ChildItem -LiteralPath $roots.Failed -Directory -ErrorAction SilentlyContinue)
}
daemon = $null
current_run = $null
background = $null
}
if (Test-Path $lockFile) {
try {
$result.daemon = Read-JsonFile -Path $lockFile
}
catch {
$result.daemon = @{ error = $_.Exception.Message }
}
}
if (Test-Path $daemonStateFile) {
try {
$result.daemon_state = Read-JsonFile -Path $daemonStateFile
}
catch {
$result.daemon_state = @{ error = $_.Exception.Message }
}
}
if (Test-Path $currentRunFile) {
try {
$result.current_run = Read-JsonFile -Path $currentRunFile
}
catch {
$result.current_run = @{ error = $_.Exception.Message }
}
}
if (Test-Path $backgroundFile) {
try {
$result.background = Read-JsonFile -Path $backgroundFile
}
catch {
$result.background = @{ error = $_.Exception.Message }
}
}
($result | ConvertTo-Json -Depth 100)

View File

@@ -0,0 +1,62 @@
param(
[string]$TaskName = "RalphInboxDaemon",
[int]$PollSeconds = 15,
[string]$Implementer = "",
[string[]]$Reviewers = @(),
[switch]$DisableCodexMaster,
[switch]$DisableAutoFix,
[switch]$DryRun,
[switch]$AtStartup,
[switch]$AtLogon = $true
)
. (Join-Path $PSScriptRoot "Common.ps1")
$daemonScript = Join-Path $PSScriptRoot "Start-RalphInboxDaemon.ps1"
$argParts = @(
"-NoProfile",
"-WindowStyle Hidden",
"-ExecutionPolicy Bypass",
('-File "{0}"' -f $daemonScript),
('-PollSeconds {0}' -f $PollSeconds)
)
if (-not [string]::IsNullOrWhiteSpace($Implementer)) {
$argParts += ('-Implementer "{0}"' -f $Implementer)
}
foreach ($reviewer in $Reviewers) {
$argParts += ('-Reviewers "{0}"' -f $reviewer)
}
if ($DisableCodexMaster) {
$argParts += "-DisableCodexMaster"
}
if ($DisableAutoFix) {
$argParts += "-DisableAutoFix"
}
if ($DryRun) {
$argParts += "-DryRun"
}
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument ($argParts -join " ")
$triggers = @()
if ($AtStartup) {
$triggers += New-ScheduledTaskTrigger -AtStartup
}
if ($AtLogon) {
$triggers += New-ScheduledTaskTrigger -AtLogOn
}
if ($triggers.Count -eq 0) {
throw "At least one trigger must be enabled."
}
$settings = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew -StartWhenAvailable -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive -RunLevel Highest
Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $triggers -Settings $settings -Principal $principal -Force | Out-Null
(@{
task_name = $TaskName
installed = $true
poll_seconds = $PollSeconds
dry_run = [bool]$DryRun
} | ConvertTo-Json -Depth 20)

View File

@@ -0,0 +1,137 @@
param(
[Parameter(Mandatory = $true)][string]$ProviderName,
[Parameter(Mandatory = $true)][string]$PromptFile,
[Parameter(Mandatory = $true)][string]$OutputFile,
[string]$WorkingDirectory = "",
[string[]]$AddDirectories = @(),
[ValidateSet("json", "text")][string]$OutputFormat = "json"
)
. (Join-Path $PSScriptRoot "Common.ps1")
if ([string]::IsNullOrWhiteSpace($WorkingDirectory)) {
$WorkingDirectory = Get-RepoRoot
}
$provider = Get-ProviderConfig -Name $ProviderName
$runner = [string]$provider.runner
$promptText = Get-Content -Raw -Path $PromptFile
Ensure-Directory -Path (Split-Path -Parent $OutputFile)
if ($runner -eq "opencode") {
$opencodeCommand = Get-Command opencode -ErrorAction SilentlyContinue
if ($null -eq $opencodeCommand) {
throw "opencode executable not found in PATH."
}
$arguments = @(
"run",
"--dir", $WorkingDirectory,
"--model", [string]$provider.model,
"--format", "json"
)
$agentProp = $provider.PSObject.Properties["agent"]
if ($null -ne $agentProp -and -not [string]::IsNullOrWhiteSpace([string]$agentProp.Value)) {
$arguments += @("--agent", [string]$agentProp.Value)
}
$variantProp = $provider.PSObject.Properties["variant"]
if ($null -ne $variantProp -and -not [string]::IsNullOrWhiteSpace([string]$variantProp.Value)) {
$arguments += @("--variant", [string]$variantProp.Value)
}
$arguments += @($promptText)
$output = @(& $opencodeCommand.Source @arguments 2>&1)
$exitCode = $LASTEXITCODE
$result = [ordered]@{
provider = $ProviderName
runner = "opencode"
model = [string]$provider.model
working_directory = $WorkingDirectory
prompt_file = $PromptFile
output_format = "json"
timestamp = (Get-Date).ToString("o")
exit_code = $exitCode
output = ($output -join [Environment]::NewLine)
}
Write-Utf8File -Path $OutputFile -Content (($result | ConvertTo-Json -Depth 100) + "`n")
if ($exitCode -ne 0) {
throw "Provider '$ProviderName' failed with exit code $exitCode. See $OutputFile"
}
return
}
$envNames = @(
"ANTHROPIC_BASE_URL",
"ANTHROPIC_AUTH_TOKEN",
"API_TIMEOUT_MS",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC",
"ANTHROPIC_MODEL",
"ANTHROPIC_SMALL_FAST_MODEL",
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
"ANTHROPIC_DEFAULT_SONNET_MODEL",
"ANTHROPIC_DEFAULT_OPUS_MODEL",
"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS"
)
$previous = @{}
foreach ($name in $envNames) {
$previous[$name] = [Environment]::GetEnvironmentVariable($name, "Process")
}
try {
[Environment]::SetEnvironmentVariable("ANTHROPIC_BASE_URL", [string]$provider.base_url, "Process")
[Environment]::SetEnvironmentVariable("ANTHROPIC_AUTH_TOKEN", [string]$provider.auth_token, "Process")
[Environment]::SetEnvironmentVariable("API_TIMEOUT_MS", [string]$provider.timeout_ms, "Process")
[Environment]::SetEnvironmentVariable("CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC", "1", "Process")
[Environment]::SetEnvironmentVariable("ANTHROPIC_MODEL", [string]$provider.model, "Process")
[Environment]::SetEnvironmentVariable("ANTHROPIC_SMALL_FAST_MODEL", [string]$provider.model, "Process")
[Environment]::SetEnvironmentVariable("ANTHROPIC_DEFAULT_HAIKU_MODEL", [string]$provider.model, "Process")
[Environment]::SetEnvironmentVariable("ANTHROPIC_DEFAULT_SONNET_MODEL", [string]$provider.model, "Process")
[Environment]::SetEnvironmentVariable("ANTHROPIC_DEFAULT_OPUS_MODEL", [string]$provider.model, "Process")
[Environment]::SetEnvironmentVariable(
"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS",
$(if ($provider.experimental_agent_teams) { "1" } else { "0" }),
"Process"
)
$claudeArgs = @(
"-p",
"--output-format", $OutputFormat,
"--dangerously-skip-permissions",
"--model", [string]$provider.model
)
foreach ($path in @($WorkingDirectory) + $AddDirectories) {
if (-not [string]::IsNullOrWhiteSpace($path)) {
$claudeArgs += @("--add-dir", $path)
}
}
$output = $promptText | & claude @claudeArgs "-" 2>&1
$exitCode = $LASTEXITCODE
}
finally {
foreach ($name in $envNames) {
[Environment]::SetEnvironmentVariable($name, $previous[$name], "Process")
}
}
$result = [ordered]@{
provider = $ProviderName
model = [string]$provider.model
working_directory = $WorkingDirectory
prompt_file = $PromptFile
output_format = $OutputFormat
timestamp = (Get-Date).ToString("o")
exit_code = $exitCode
output = ($output -join [Environment]::NewLine)
}
Write-Utf8File -Path $OutputFile -Content (($result | ConvertTo-Json -Depth 100) + "`n")
if ($exitCode -ne 0) {
throw "Provider '$ProviderName' failed with exit code $exitCode. See $OutputFile"
}

View File

@@ -0,0 +1,122 @@
param(
[Parameter(Mandatory = $true)][string]$PromptFile,
[Parameter(Mandatory = $true)][string]$OutputFile,
[string]$WorkingDirectory = "",
[switch]$SkipVerdictParse
)
. (Join-Path $PSScriptRoot "Common.ps1")
$codexConfig = Get-CodexConfig
if ([string]::IsNullOrWhiteSpace($WorkingDirectory)) {
$WorkingDirectory = [string]$codexConfig.working_directory
}
Ensure-Directory -Path (Split-Path -Parent $OutputFile)
$promptText = Get-Content -Raw -Path $PromptFile
$preferredCmd = Join-Path $env:APPDATA "npm\codex.cmd"
$preferredPs1 = Join-Path $env:APPDATA "npm\codex.ps1"
if (Test-Path $preferredCmd) {
$codexPath = $preferredCmd
}
elseif (Test-Path $preferredPs1) {
$codexPath = $preferredPs1
}
else {
$codexCommand = Get-Command codex -ErrorAction SilentlyContinue
if ($null -eq $codexCommand) {
throw "codex executable not found in PATH."
}
$codexPath = $codexCommand.Source
}
Write-Utf8File -Path $PromptFile -Content $promptText
$stdoutLog = $OutputFile + ".stdout.log"
$stderrLog = $OutputFile + ".stderr.log"
$model = [string]$codexConfig.model
$sessionId = [string]$codexConfig.session_id
if ([string]::IsNullOrWhiteSpace($sessionId)) {
throw "codex.local.json is missing session_id"
}
$arguments = @(
"-C", $WorkingDirectory,
"exec", "resume", $sessionId,
"--dangerously-bypass-approvals-and-sandbox"
)
if (-not [string]::IsNullOrWhiteSpace($model)) {
$arguments += @("--model", $model)
}
$arguments += @("--output-last-message", $OutputFile, "-")
$stdout = @()
$stderr = @()
try {
$mergedOutput = @($promptText | & $codexPath @arguments 2>&1)
$exitCode = $LASTEXITCODE
foreach ($entry in $mergedOutput) {
if ($entry -is [System.Management.Automation.ErrorRecord]) {
$stderr += $entry.ToString()
}
else {
$stdout += [string]$entry
}
}
}
catch {
$exitCode = 1
$stderr = @($_.Exception.Message)
}
if ($stdout) {
Write-Utf8File -Path $stdoutLog -Content ((@($stdout) -join [Environment]::NewLine) + [Environment]::NewLine)
}
elseif (-not (Test-Path $stdoutLog)) {
Write-Utf8File -Path $stdoutLog -Content ""
}
if ($stderr) {
Write-Utf8File -Path $stderrLog -Content ((@($stderr) -join [Environment]::NewLine) + [Environment]::NewLine)
}
elseif (-not (Test-Path $stderrLog)) {
Write-Utf8File -Path $stderrLog -Content ""
}
if (-not $SkipVerdictParse) {
if (Test-Path $OutputFile) {
try {
$parsedVerdict = Read-CodexReviewVerdict -Path $OutputFile
if ($exitCode -ne 0) {
$stderr += "Codex returned exit code $exitCode but produced a valid review output. Accepting the review output."
Write-Utf8File -Path $stderrLog -Content ((@($stderr) -join [Environment]::NewLine) + [Environment]::NewLine)
}
return $parsedVerdict
}
catch {
if ($exitCode -eq 0) {
throw
}
}
}
}
elseif (Test-Path $OutputFile) {
$rawOutput = (Get-Content -Raw -Path $OutputFile).Trim()
if (-not [string]::IsNullOrWhiteSpace($rawOutput)) {
if ($exitCode -ne 0) {
$stderr += "Codex returned exit code $exitCode but produced non-empty output. Accepting the generated output."
Write-Utf8File -Path $stderrLog -Content ((@($stderr) -join [Environment]::NewLine) + [Environment]::NewLine)
}
return $rawOutput
}
}
if ($exitCode -ne 0) {
$errorExcerpt = ""
$combined = ((@($stdout) + @($stderr)) -join " ").Trim()
if ($combined -match "usage limit") {
$errorExcerpt = " Codex local CLI is currently over its usage limit."
}
throw "Codex master review failed with exit code $exitCode.$errorExcerpt See $stdoutLog and $stderrLog"
}

View File

@@ -0,0 +1,44 @@
param(
[Parameter(Mandatory = $true)][string]$TaskDirectory,
[Parameter(Mandatory = $true)][string]$RunDirectory,
[Parameter(Mandatory = $true)][string]$OutputFile,
[Parameter(Mandatory = $true)][string]$RunStatus,
[string]$WorkingDirectory = "",
[string[]]$ExtraSections = @()
)
. (Join-Path $PSScriptRoot "Common.ps1")
if ([string]::IsNullOrWhiteSpace($WorkingDirectory)) {
$WorkingDirectory = Get-RepoRoot
}
$taskPack = Read-TaskPack -TaskDirectory $TaskDirectory
$ralphRoot = Get-RalphRoot
$promptPath = Join-Path $RunDirectory "prompts\codex_next_task.md"
$sections = @(
"## PREVIOUS RUN DIRECTORY`n$RunDirectory",
"## PREVIOUS RUN STATUS`n$RunStatus"
)
foreach ($section in $ExtraSections) {
$sections += $section
}
New-PromptDocument `
-TemplatePath (Join-Path $ralphRoot "templates\CODEX_NEXT_TASK_PROMPT.md") `
-TaskPack $taskPack `
-OutputPath $promptPath `
-ExtraSections $sections
& (Join-Path $PSScriptRoot "Invoke-CodexMaster.ps1") `
-PromptFile $promptPath `
-OutputFile $OutputFile `
-WorkingDirectory $WorkingDirectory `
-SkipVerdictParse | Out-Null
if (-not (Test-Path $OutputFile)) {
throw "Codex next-task generation did not produce output: $OutputFile"
}
Get-Content -Raw -Path $OutputFile

View File

@@ -0,0 +1,152 @@
param(
[string]$RunId = "",
[string]$RunStatus = "",
[switch]$Enqueue
)
. (Join-Path $PSScriptRoot "Common.ps1")
$ralphRoot = Get-RalphRoot
$repoRoot = Get-RepoRoot
$automationConfig = Get-RalphAutomationConfig
if ($null -eq $automationConfig -or -not ($automationConfig.PSObject.Properties.Name -contains "auto_followup")) {
throw "automation config missing auto_followup block"
}
if ([string]::IsNullOrWhiteSpace($RunId)) {
$current = Read-JsonFile -Path (Get-RalphStateFile -Name "current_run.json")
}
else {
$runDir = Join-Path $ralphRoot ("runs\" + $RunId)
if (-not (Test-Path $runDir)) {
throw "Run directory not found: $runDir"
}
$currentRunFile = Get-RalphStateFile -Name "current_run.json"
$current = Read-JsonFile -Path $currentRunFile
if ([string]$current.run_id -ne $RunId) {
$summaryPath = Join-Path $runDir "SUMMARY.md"
$taskPath = Join-Path $runDir "TASK.md"
$acceptancePath = Join-Path $runDir "ACCEPTANCE.md"
$contextPath = Join-Path $runDir "CONTEXT.md"
if (-not (Test-Path $taskPath)) {
throw "Run does not contain TASK.md: $runDir"
}
$current = [ordered]@{
run_id = $RunId
run_dir = $runDir
task_directory = ""
status = "unknown"
latest_message = $(if (Test-Path $summaryPath) { (Get-Content -Raw $summaryPath) } else { "" })
}
}
}
if ([string]::IsNullOrWhiteSpace($RunStatus)) {
$RunStatus = [string]$current.status
}
$runDirResolved = [string]$current.run_dir
if ([string]::IsNullOrWhiteSpace($runDirResolved)) {
throw "current run state is missing run_dir"
}
$taskDirectory = [string]$current.task_directory
if ([string]::IsNullOrWhiteSpace($taskDirectory) -or -not (Test-Path $taskDirectory)) {
$taskLeaf = ""
try {
$taskLeaf = Split-Path -Leaf ([string]$current.task_directory)
}
catch {
$taskLeaf = ""
}
$searchRoots = @(
(Join-Path $ralphRoot "tasks\\processing"),
(Join-Path $ralphRoot "tasks\\failed"),
(Join-Path $ralphRoot "tasks\\completed")
)
foreach ($root in $searchRoots) {
if (-not (Test-Path $root)) {
continue
}
$candidate = $null
if (-not [string]::IsNullOrWhiteSpace($taskLeaf)) {
$candidate = Join-Path $root $taskLeaf
if (Test-Path $candidate) {
$taskDirectory = $candidate
break
}
}
$matches = @(Get-ChildItem -Path $root -Directory -ErrorAction SilentlyContinue | Where-Object {
$_.Name -like ("*" + [string]$current.run_id + "*") -or $_.Name -like ("*" + $taskLeaf + "*")
} | Select-Object -First 1)
if ($matches.Count -gt 0) {
$taskDirectory = $matches[0].FullName
break
}
}
}
if ([string]::IsNullOrWhiteSpace($taskDirectory) -or -not (Test-Path $taskDirectory)) {
throw "could not resolve task directory for followup generation"
}
$targetRelative = "docs\\autopilot"
if ($automationConfig.auto_followup.PSObject.Properties.Name -contains "target_directory" -and -not [string]::IsNullOrWhiteSpace([string]$automationConfig.auto_followup.target_directory)) {
$targetRelative = [string]$automationConfig.auto_followup.target_directory
}
$targetDirectory = Join-Path $repoRoot $targetRelative
Ensure-Directory -Path $targetDirectory
$outputFile = Join-Path $runDirResolved "NEXT_TASK_MANUAL.md"
$extraSections = @(
"## PREVIOUS OUTCOME SUMMARY`n$([string]$current.latest_message)",
"## RUN SUMMARY FILE`n$(Join-Path $runDirResolved 'SUMMARY.md')",
"## RUN OUTPUTS DIRECTORY`n$(Join-Path $runDirResolved 'outputs')",
"## RUN REVIEWS DIRECTORY`n$(Join-Path $runDirResolved 'reviews')"
)
& (Join-Path $PSScriptRoot "Invoke-CodexNextTask.ps1") `
-TaskDirectory $taskDirectory `
-RunDirectory $runDirResolved `
-OutputFile $outputFile `
-RunStatus $RunStatus `
-WorkingDirectory $repoRoot `
-ExtraSections $extraSections | Out-Null
$slugBase = Convert-ToRalphSlug -Text ([IO.Path]::GetFileNameWithoutExtension($outputFile))
$finalMdPath = Join-Path $targetDirectory ((Get-Date -Format "yyyyMMdd-HHmmss") + "-" + $slugBase + ".md")
Copy-Item -Path $outputFile -Destination $finalMdPath -Force
$result = [ordered]@{
generated = $true
source_markdown = $finalMdPath
queued = $false
}
if ($Enqueue) {
$taskInfo = New-RalphTaskPackFromMarkdown `
-MarkdownPath $finalMdPath `
-AdditionalMetadata @{
auto_generated = $true
parent_run_id = [string]$current.run_id
parent_task_directory = $taskDirectory
followup_generation = 1
source_run_status = $RunStatus
}
$result.queued = $true
$result.task_directory = $taskInfo.task_directory
$result.title = $taskInfo.title
Add-RalphEvent -RunId ([string]$current.run_id) -Stage "auto_followup" -Status "queued" -Actor "codex_master" -Message ("Manual backfill followup queued: " + $taskInfo.title) -Data @{
task_directory = $taskInfo.task_directory
source_markdown = $finalMdPath
}
Send-TelegramNotification `
-EventName "task_queued" `
-Title "Auto-followup queued" `
-Message ($taskInfo.title + "`n" + $taskInfo.task_directory) `
-RunId ([string]$current.run_id) `
-Stage "auto_followup" `
-Status "queued" | Out-Null
}
($result | ConvertTo-Json -Depth 20)

View File

@@ -0,0 +1,714 @@
param(
[string]$TaskDirectory = "",
[string]$RunLabel = "autopilot",
[string]$Implementer = "",
[string[]]$Reviewers = @(),
[switch]$UseCodexMaster = $true,
[switch]$AutoFix = $true,
[switch]$DryRun
)
. (Join-Path $PSScriptRoot "Common.ps1")
$ralphRoot = Get-RalphRoot
$repoRoot = Get-RepoRoot
$roots = Get-RalphTaskRoots
$automationConfig = Get-RalphAutomationConfig
if ([string]::IsNullOrWhiteSpace($TaskDirectory)) {
$TaskDirectory = Join-Path $ralphRoot "tasks\current"
}
if ([string]::IsNullOrWhiteSpace($Implementer)) {
$Implementer = Get-DefaultImplementer
}
if ($Reviewers.Count -eq 0) {
$Reviewers = Get-DefaultReviewers
}
$taskPack = Read-TaskPack -TaskDirectory $TaskDirectory
$submissionPath = Join-Path $TaskDirectory "submission.json"
$submissionMetadata = $null
if (Test-Path $submissionPath) {
try {
$submissionMetadata = Read-JsonFile -Path $submissionPath
}
catch {
$submissionMetadata = $null
}
}
$runId = New-RunId -Label $RunLabel
$runDir = Join-Path $ralphRoot ("runs\" + $runId)
$worktreePath = Join-Path $ralphRoot ("worktrees\" + $runId + "-" + $Implementer)
Ensure-Directory -Path $runDir
Ensure-Directory -Path (Join-Path $runDir "prompts")
Ensure-Directory -Path (Join-Path $runDir "outputs")
Ensure-Directory -Path (Join-Path $runDir "reviews")
Copy-Item -Path $taskPack.Files.Task -Destination (Join-Path $runDir "TASK.md") -Force
Copy-Item -Path $taskPack.Files.Acceptance -Destination (Join-Path $runDir "ACCEPTANCE.md") -Force
Copy-Item -Path $taskPack.Files.Context -Destination (Join-Path $runDir "CONTEXT.md") -Force
$script:runState = [ordered]@{
run_id = $runId
status = "initializing"
stage = "initializing"
started_at = (Get-Date).ToString("o")
finished_at = $null
task_directory = $TaskDirectory
run_dir = $runDir
worktree = $worktreePath
implementer = [ordered]@{
name = $Implementer
status = "pending"
started_at = $null
finished_at = $null
output_file = ""
}
reviewers = @()
codex_master = [ordered]@{
enabled = [bool]$UseCodexMaster
status = $(if ($UseCodexMaster) { "pending" } else { "disabled" })
started_at = $null
finished_at = $null
output_file = ""
verdict = ""
acceptance_passed = $false
}
fix_pass = [ordered]@{
enabled = [bool]$AutoFix
status = $(if ($AutoFix) { "pending" } else { "disabled" })
started_at = $null
finished_at = $null
output_file = ""
}
latest_message = "Run initialized"
errors = @()
}
foreach ($reviewer in $Reviewers) {
$script:runState.reviewers += [ordered]@{
name = $reviewer
status = "pending"
started_at = $null
finished_at = $null
output_file = ""
}
}
function Get-FollowupGeneration {
if ($null -eq $submissionMetadata) {
return 0
}
if ($submissionMetadata.PSObject.Properties.Name -contains "followup_generation") {
try {
return [int]$submissionMetadata.followup_generation
}
catch {
return 0
}
}
return 0
}
function Save-RunState {
Set-RalphCurrentRunState -State $script:runState
}
function Set-RunPhase {
param(
[Parameter(Mandatory = $true)][string]$Stage,
[Parameter(Mandatory = $true)][string]$Status,
[Parameter(Mandatory = $true)][string]$Message
)
$script:runState.stage = $Stage
$script:runState.status = $Status
$script:runState.latest_message = $Message
Save-RunState
Add-RalphEvent -RunId $script:runState.run_id -Stage $Stage -Status $Status -Message $Message
}
function Set-ActorState {
param(
[Parameter(Mandatory = $true)][ValidateSet("implementer", "reviewer", "codex_master", "fix_pass")][string]$ActorType,
[Parameter(Mandatory = $true)][string]$Status,
[Parameter(Mandatory = $true)][string]$Message,
[string]$ActorName = "",
[string]$OutputFile = ""
)
$timestamp = (Get-Date).ToString("o")
$target = $null
$actorLabel = $ActorName
switch ($ActorType) {
"implementer" {
$target = $script:runState.implementer
$actorLabel = $script:runState.implementer.name
}
"reviewer" {
$target = $script:runState.reviewers | Where-Object { $_.name -eq $ActorName } | Select-Object -First 1
$actorLabel = $ActorName
}
"codex_master" {
$target = $script:runState.codex_master
$actorLabel = "codex_master"
}
"fix_pass" {
$target = $script:runState.fix_pass
$actorLabel = $script:runState.implementer.name
}
}
if ($null -ne $target) {
$target.status = $Status
if ($Status -eq "running" -and -not $target.started_at) {
$target.started_at = $timestamp
}
if ($Status -in @("completed", "failed", "skipped")) {
$target.finished_at = $timestamp
}
if ($OutputFile) {
$target.output_file = $OutputFile
}
}
$script:runState.latest_message = $Message
Save-RunState
Add-RalphEvent -RunId $script:runState.run_id -Stage $ActorType -Status $Status -Actor $actorLabel -Message $Message -Data @{
output_file = $OutputFile
}
}
function Invoke-CodexReviewPass {
param(
[Parameter(Mandatory = $true)][string]$StageName,
[Parameter(Mandatory = $true)][string]$PromptFileName,
[Parameter(Mandatory = $true)][string]$OutputFileName,
[Parameter(Mandatory = $true)][string]$RunningMessage,
[Parameter(Mandatory = $true)][string]$CompletedMessage,
[string[]]$ExtraSections = @()
)
$promptPath = Join-Path $runDir ("prompts\" + $PromptFileName)
$outputPath = Join-Path $runDir ("reviews\" + $OutputFileName)
New-PromptDocument `
-TemplatePath (Join-Path $ralphRoot "templates\CODEX_REVIEW_PROMPT.md") `
-TaskPack $taskPack `
-OutputPath $promptPath `
-ExtraSections $ExtraSections
Set-RunPhase -Stage $StageName -Status "running" -Message $RunningMessage
Set-ActorState -ActorType "codex_master" -Status "running" -Message $RunningMessage -OutputFile $outputPath
$verdict = & (Join-Path $PSScriptRoot "Invoke-CodexMaster.ps1") `
-PromptFile $promptPath `
-OutputFile $outputPath `
-WorkingDirectory $worktreePath
$script:runState.codex_master.verdict = [string]$verdict.verdict
$script:runState.codex_master.acceptance_passed = [bool]$verdict.acceptance_passed
Set-ActorState -ActorType "codex_master" -Status "completed" -Message ($CompletedMessage + " (verdict: " + $verdict.verdict + ")") -OutputFile $outputPath
Set-RunPhase -Stage $StageName -Status "completed" -Message ($CompletedMessage + " (verdict: " + $verdict.verdict + ")")
Save-RunState
return $verdict
}
function Invoke-AutoFollowupTask {
param(
[Parameter(Mandatory = $true)][string]$OutcomeStatus,
[Parameter(Mandatory = $true)][string]$OutcomeSummary
)
if ($null -eq $automationConfig) {
return $null
}
if (-not ($automationConfig.PSObject.Properties.Name -contains "auto_followup")) {
return $null
}
$autoFollowup = $automationConfig.auto_followup
$enabled = $false
try { $enabled = [bool]$autoFollowup.enabled } catch { $enabled = $false }
if (-not $enabled) {
return $null
}
if (-not $UseCodexMaster) {
return $null
}
$trigger = $false
if ($OutcomeStatus -eq "failed") {
try { $trigger = [bool]$autoFollowup.trigger_on_failure } catch { $trigger = $false }
}
elseif ($OutcomeStatus -eq "completed") {
try { $trigger = [bool]$autoFollowup.trigger_on_success } catch { $trigger = $false }
}
if (-not $trigger) {
return $null
}
$maxDepth = 0
try { $maxDepth = [int]$autoFollowup.max_chain_depth } catch { $maxDepth = 0 }
$currentDepth = Get-FollowupGeneration
if ($maxDepth -gt 0 -and $currentDepth -ge $maxDepth) {
return $null
}
$targetRelative = "docs\autopilot"
if ($autoFollowup.PSObject.Properties.Name -contains "target_directory" -and -not [string]::IsNullOrWhiteSpace([string]$autoFollowup.target_directory)) {
$targetRelative = [string]$autoFollowup.target_directory
}
$targetDirectory = Join-Path $repoRoot $targetRelative
Ensure-Directory -Path $targetDirectory
$prefix = "AUTOFOLLOWUP"
if ($autoFollowup.PSObject.Properties.Name -contains "title_prefix" -and -not [string]::IsNullOrWhiteSpace([string]$autoFollowup.title_prefix)) {
$prefix = [string]$autoFollowup.title_prefix
}
$nextPromptOutput = Join-Path $runDir "NEXT_TASK.md"
$extraSections = @(
"## PREVIOUS OUTCOME SUMMARY`n$OutcomeSummary",
"## RUN SUMMARY FILE`n$(Join-Path $runDir 'SUMMARY.md')",
"## RUN OUTPUTS DIRECTORY`n$(Join-Path $runDir 'outputs')",
"## RUN REVIEWS DIRECTORY`n$(Join-Path $runDir 'reviews')"
)
$null = & (Join-Path $PSScriptRoot "Invoke-CodexNextTask.ps1") `
-TaskDirectory $TaskDirectory `
-RunDirectory $runDir `
-OutputFile $nextPromptOutput `
-RunStatus $OutcomeStatus `
-WorkingDirectory $worktreePath `
-ExtraSections $extraSections
$dateLabel = Get-Date -Format "yyyyMMdd-HHmmss"
$fileName = "{0}-{1}.md" -f $dateLabel, (Convert-ToRalphSlug -Text ($prefix + "-" + $taskPack.Task.Substring(0, [Math]::Min($taskPack.Task.Length, 32))))
$finalMdPath = Join-Path $targetDirectory $fileName
Copy-Item -Path $nextPromptOutput -Destination $finalMdPath -Force
$taskInfo = New-RalphTaskPackFromMarkdown `
-MarkdownPath $finalMdPath `
-AdditionalMetadata @{
auto_generated = $true
parent_run_id = $runId
parent_task_directory = $TaskDirectory
followup_generation = ($currentDepth + 1)
source_run_status = $OutcomeStatus
}
Add-RalphEvent -RunId $runId -Stage "auto_followup" -Status "queued" -Actor "codex_master" -Message ("Auto-followup task queued: " + $taskInfo.title) -Data @{
task_directory = $taskInfo.task_directory
source_markdown = $finalMdPath
}
Send-TelegramNotification `
-EventName "task_queued" `
-Title "Auto-followup queued" `
-Message ($taskInfo.title + "`n" + $taskInfo.task_directory) `
-RunId $runId `
-Stage "auto_followup" `
-Status "queued" | Out-Null
return [ordered]@{
title = $taskInfo.title
task_directory = $taskInfo.task_directory
source_markdown = $finalMdPath
followup_generation = ($currentDepth + 1)
}
}
Save-RunState
Add-RalphEvent -RunId $runId -Stage "initializing" -Status "started" -Message "Ralph run initialized" -Data @{
implementer = $Implementer
reviewers = $Reviewers
worktree = $worktreePath
}
Send-TelegramNotification `
-EventName "run_started" `
-Title "Run started" `
-Message ("Implementer: " + $Implementer + "`nReviewers: " + ($Reviewers -join ", ")) `
-RunId $runId `
-Stage "initializing" `
-Status "started" | Out-Null
$gitStatus = & git -C $repoRoot status --short
Write-Utf8File -Path (Join-Path $runDir "repo_status_before.txt") -Content (($gitStatus -join "`n") + "`n")
$gitStat = & git -C $repoRoot diff --stat
Write-Utf8File -Path (Join-Path $runDir "repo_diff_stat_before.txt") -Content (($gitStat -join "`n") + "`n")
$implementerPrompt = Join-Path $runDir "prompts\implementer.md"
New-PromptDocument `
-TemplatePath (Join-Path $ralphRoot "templates\IMPLEMENTER_PROMPT.md") `
-TaskPack $taskPack `
-OutputPath $implementerPrompt `
-ExtraSections @(
"## WORKTREE`nEdit only inside this worktree:`n`n$worktreePath",
"## REQUIRED RUN OUTPUT`nWrite a file named CHANGES.md in this run directory:`n`n$runDir"
)
$summaryLines = @(
"# Ralph Run",
"",
"- Run ID: $runId",
"- Implementer: $Implementer",
"- Reviewers: $(($Reviewers -join ', '))",
"- Worktree: $worktreePath",
"- Codex master review: $(if ($UseCodexMaster) { 'enabled' } else { 'disabled' })",
"- Auto fix pass: $(if ($AutoFix) { 'enabled' } else { 'disabled' })",
"- Dry run: $(if ($DryRun) { 'yes' } else { 'no' })"
)
if ($DryRun) {
Set-RunPhase -Stage "dry_run" -Status "completed" -Message "Dry run prepared successfully"
$script:runState.finished_at = (Get-Date).ToString("o")
Save-RunState
Write-Utf8File -Path (Join-Path $runDir "SUMMARY.md") -Content (($summaryLines -join "`n") + "`n")
Get-Content -Raw -Path (Join-Path $runDir "SUMMARY.md")
return
}
Set-RunPhase -Stage "worktree" -Status "running" -Message "Creating isolated worktree"
& git -C $repoRoot worktree add --detach $worktreePath HEAD | Out-Null
Set-RunPhase -Stage "worktree" -Status "completed" -Message "Worktree ready"
$reviewOutputs = @()
$codexFinalVerdict = $null
$autoFollowupResult = $null
$script:heartbeatJob = $null
$script:heartbeatSignalPath = ""
$codexReviewSections = @(
"## IMPLEMENTER WORKTREE`n$worktreePath",
"## IMPLEMENTER DIFF FILE`n$(Join-Path $runDir 'implementer.patch')",
"## IMPLEMENTER STATUS FILE`n$(Join-Path $runDir 'implementer_status.txt')"
)
function Start-HeartbeatMonitor {
param(
[Parameter(Mandatory = $true)][string]$Stage,
[Parameter(Mandatory = $true)][string]$Title,
[Parameter(Mandatory = $true)][string]$Message,
[int]$IntervalSeconds = 300
)
Stop-HeartbeatMonitor
$signalPath = Join-Path $runDir ("heartbeat-" + $Stage + ".lock")
Write-Utf8File -Path $signalPath -Content ((Get-Date).ToString("o"))
$commonPath = Join-Path $PSScriptRoot "Common.ps1"
$script:heartbeatSignalPath = $signalPath
$script:heartbeatJob = Start-Job -ArgumentList $commonPath, $signalPath, $Stage, $Title, $Message, $runId, $IntervalSeconds -ScriptBlock {
param($CommonPath, $SignalPath, $StageName, $TitleText, $MessageText, $RunIdValue, $IntervalValue)
. $CommonPath
while (Test-Path $SignalPath) {
Start-Sleep -Seconds $IntervalValue
if (-not (Test-Path $SignalPath)) {
break
}
Send-TelegramNotification `
-EventName "run_heartbeat" `
-Title $TitleText `
-Message $MessageText `
-RunId $RunIdValue `
-Stage $StageName `
-Status "running" | Out-Null
}
}
}
function Stop-HeartbeatMonitor {
if (-not [string]::IsNullOrWhiteSpace($script:heartbeatSignalPath) -and (Test-Path $script:heartbeatSignalPath)) {
Remove-Item -LiteralPath $script:heartbeatSignalPath -Force -ErrorAction SilentlyContinue
}
$script:heartbeatSignalPath = ""
if ($null -ne $script:heartbeatJob) {
Wait-Job -Job $script:heartbeatJob -Timeout 1 | Out-Null
if ($script:heartbeatJob.State -eq "Running") {
Stop-Job -Job $script:heartbeatJob -Force | Out-Null
}
Remove-Job -Job $script:heartbeatJob -Force -ErrorAction SilentlyContinue
$script:heartbeatJob = $null
}
}
try {
$implementerOutput = Join-Path $runDir "outputs\implementer.json"
Set-RunPhase -Stage "implementer" -Status "running" -Message ("Implementer " + $Implementer + " started")
Set-ActorState -ActorType "implementer" -Status "running" -Message ("Implementer " + $Implementer + " received task pack") -OutputFile $implementerOutput
Start-HeartbeatMonitor -Stage "implementer" -Title "Run heartbeat" -Message ("Implementer running: " + $Implementer)
& (Join-Path $PSScriptRoot "Invoke-ClaudeProvider.ps1") `
-ProviderName $Implementer `
-PromptFile $implementerPrompt `
-OutputFile $implementerOutput `
-WorkingDirectory $worktreePath `
-AddDirectories @($runDir, $repoRoot)
Stop-HeartbeatMonitor
Set-ActorState -ActorType "implementer" -Status "completed" -Message ("Implementer " + $Implementer + " finished first pass") -OutputFile $implementerOutput
Send-TelegramNotification `
-EventName "implementer_completed" `
-Title "Implementer finished" `
-Message ("Implementer: " + $Implementer) `
-RunId $runId `
-Stage "implementer" `
-Status "completed" | Out-Null
$worktreeStatus = & git -C $worktreePath status --short
Write-Utf8File -Path (Join-Path $runDir "implementer_status.txt") -Content (($worktreeStatus -join "`n") + "`n")
$worktreeDiff = & git -C $worktreePath diff --no-ext-diff
Write-Utf8File -Path (Join-Path $runDir "implementer.patch") -Content (($worktreeDiff -join "`n") + "`n")
foreach ($reviewer in $Reviewers) {
if ($reviewer -eq $Reviewers[0]) {
Send-TelegramNotification `
-EventName "reviewers_started" `
-Title "Reviewers started" `
-Message (($Reviewers -join ", ")) `
-RunId $runId `
-Stage "review" `
-Status "running" | Out-Null
}
$promptPath = Join-Path $runDir ("prompts\review-" + $reviewer + ".md")
New-PromptDocument `
-TemplatePath (Join-Path $ralphRoot "templates\REVIEWER_PROMPT.md") `
-TaskPack $taskPack `
-OutputPath $promptPath `
-ExtraSections @(
"## IMPLEMENTER WORKTREE`n$worktreePath",
"## IMPLEMENTER DIFF FILE`n$(Join-Path $runDir 'implementer.patch')",
"## IMPLEMENTER STATUS FILE`n$(Join-Path $runDir 'implementer_status.txt')"
)
$reviewOutput = Join-Path $runDir ("reviews\" + $reviewer + ".json")
Set-RunPhase -Stage "review" -Status "running" -Message ("Reviewer " + $reviewer + " started")
Set-ActorState -ActorType "reviewer" -ActorName $reviewer -Status "running" -Message ("Reviewer " + $reviewer + " analyzing diff") -OutputFile $reviewOutput
& (Join-Path $PSScriptRoot "Invoke-ClaudeProvider.ps1") `
-ProviderName $reviewer `
-PromptFile $promptPath `
-OutputFile $reviewOutput `
-WorkingDirectory $worktreePath `
-AddDirectories @($runDir, $repoRoot)
Set-ActorState -ActorType "reviewer" -ActorName $reviewer -Status "completed" -Message ("Reviewer " + $reviewer + " completed review") -OutputFile $reviewOutput
$reviewOutputs += $reviewOutput
}
Set-RunPhase -Stage "review" -Status "completed" -Message "All provider reviews completed"
$codexReviewSections += "## REVIEW FILES`n$($reviewOutputs -join "`n")"
if ($UseCodexMaster -and $AutoFix) {
$null = Invoke-CodexReviewPass `
-StageName "codex_review_pre_fix" `
-PromptFileName "codex_master_review_pre_fix.md" `
-OutputFileName "codex_master_pre_fix.json" `
-RunningMessage "Codex master pre-fix review started" `
-CompletedMessage "Codex master pre-fix review completed" `
-ExtraSections $codexReviewSections
}
if ($AutoFix) {
$fixPrompt = Join-Path $runDir "prompts\fix_pass.md"
$fixExtraSections = @(
"## FIX PASS`nRead the reviewer outputs and fix the highest-signal issues only.",
"## REVIEW FILES`n$($reviewOutputs -join "`n")"
)
if ($UseCodexMaster) {
$fixExtraSections += "## CODEX REVIEW FILE`n$(Join-Path $runDir 'reviews\codex_master_pre_fix.json')"
}
else {
$fixExtraSections += "## CODEX REVIEW FILE`nCodex review disabled for this run."
}
$fixExtraSections += "## REQUIRED RUN OUTPUT`nUpdate CHANGES.md in this run directory after the fix pass:`n`n$runDir"
New-PromptDocument `
-TemplatePath (Join-Path $ralphRoot "templates\IMPLEMENTER_PROMPT.md") `
-TaskPack $taskPack `
-OutputPath $fixPrompt `
-ExtraSections $fixExtraSections
Set-RunPhase -Stage "fix_pass" -Status "running" -Message ("Fix pass started with " + $Implementer)
Set-ActorState -ActorType "fix_pass" -Status "running" -Message ("Implementer " + $Implementer + " applying review fixes") -OutputFile (Join-Path $runDir "outputs\fix_pass.json")
Send-TelegramNotification `
-EventName "fix_pass_started" `
-Title "Fix pass started" `
-Message ("Implementer: " + $Implementer) `
-RunId $runId `
-Stage "fix_pass" `
-Status "running" | Out-Null
Start-HeartbeatMonitor -Stage "fix_pass" -Title "Run heartbeat" -Message ("Fix pass running: " + $Implementer)
& (Join-Path $PSScriptRoot "Invoke-ClaudeProvider.ps1") `
-ProviderName $Implementer `
-PromptFile $fixPrompt `
-OutputFile (Join-Path $runDir "outputs\fix_pass.json") `
-WorkingDirectory $worktreePath `
-AddDirectories @($runDir, $repoRoot)
Stop-HeartbeatMonitor
Set-ActorState -ActorType "fix_pass" -Status "completed" -Message ("Implementer " + $Implementer + " finished fix pass") -OutputFile (Join-Path $runDir "outputs\fix_pass.json")
Set-RunPhase -Stage "fix_pass" -Status "completed" -Message "Fix pass completed"
$finalDiff = & git -C $worktreePath diff --no-ext-diff
Write-Utf8File -Path (Join-Path $runDir "final.patch") -Content (($finalDiff -join "`n") + "`n")
}
$finalStatus = & git -C $worktreePath status --short
Write-Utf8File -Path (Join-Path $runDir "final_status.txt") -Content (($finalStatus -join "`n") + "`n")
if ($UseCodexMaster) {
$finalDiffPath = Join-Path $runDir "final.patch"
if (-not (Test-Path $finalDiffPath)) {
$finalDiffPath = Join-Path $runDir "implementer.patch"
}
$codexFinalSections = @(
"## IMPLEMENTER WORKTREE`n$worktreePath",
"## FINAL DIFF FILE`n$finalDiffPath",
"## FINAL STATUS FILE`n$(Join-Path $runDir 'final_status.txt')",
"## REVIEW FILES`n$($reviewOutputs -join "`n")"
)
if ($AutoFix) {
$codexFinalSections += "## FIX PASS OUTPUT`n$(Join-Path $runDir 'outputs\\fix_pass.json')"
$codexFinalSections += "## PRE-FIX CODEX REVIEW`n$(Join-Path $runDir 'reviews\\codex_master_pre_fix.json')"
}
$codexFinalVerdict = Invoke-CodexReviewPass `
-StageName "codex_review_final" `
-PromptFileName "codex_master_review_final.md" `
-OutputFileName "codex_master_final.json" `
-RunningMessage "Codex master final review started" `
-CompletedMessage "Codex master final review completed" `
-ExtraSections $codexFinalSections
if (([string]$codexFinalVerdict.verdict) -ne "pass" -or -not [bool]$codexFinalVerdict.acceptance_passed) {
Send-TelegramNotification `
-EventName "codex_failed" `
-Title "Codex gate rejected run" `
-Message ([string]$codexFinalVerdict.summary) `
-RunId $runId `
-Stage "codex_review_final" `
-Status "failed" | Out-Null
throw ("Codex master rejected the run with verdict '{0}': {1}" -f $codexFinalVerdict.verdict, $codexFinalVerdict.summary)
}
}
$summaryLines += @(
"",
"## Outputs",
"",
"- Run directory: $runDir",
"- Worktree: $worktreePath",
"- Implementer output: $(Join-Path $runDir 'outputs\implementer.json')",
"- Final status: $(Join-Path $runDir 'final_status.txt')",
"- Codex final verdict: $(if ($UseCodexMaster -and $null -ne $codexFinalVerdict) { [string]$codexFinalVerdict.verdict } else { 'not-run' })"
)
try {
$autoFollowupResult = Invoke-AutoFollowupTask -OutcomeStatus "completed" -OutcomeSummary "Run completed successfully"
}
catch {
$script:runState.errors += ("Auto-followup generation error: " + $_.Exception.Message)
Save-RunState
Add-RalphEvent -RunId $runId -Stage "auto_followup" -Status "failed" -Actor "codex_master" -Message $_.Exception.Message
}
if ($null -ne $autoFollowupResult) {
$summaryLines += @(
"",
"## Auto Followup",
"",
"- Title: $($autoFollowupResult.title)",
"- Task directory: $($autoFollowupResult.task_directory)",
"- Source markdown: $($autoFollowupResult.source_markdown)"
)
}
Write-Utf8File -Path (Join-Path $runDir "SUMMARY.md") -Content (($summaryLines -join "`n") + "`n")
$script:runState.status = "completed"
$script:runState.stage = "completed"
$script:runState.finished_at = (Get-Date).ToString("o")
$script:runState.latest_message = "Run completed successfully"
Save-RunState
Add-RalphEvent -RunId $runId -Stage "completed" -Status "completed" -Message "Ralph run completed successfully" -Data @{
run_dir = $runDir
worktree = $worktreePath
}
Send-TelegramNotification `
-EventName "run_completed" `
-Title "Run completed" `
-Message ("Implementer: " + $Implementer + "`nCodex verdict: " + $(if ($UseCodexMaster -and $null -ne $codexFinalVerdict) { [string]$codexFinalVerdict.verdict } else { "not-run" })) `
-RunId $runId `
-Stage "completed" `
-Status "completed" | Out-Null
Get-Content -Raw -Path (Join-Path $runDir "SUMMARY.md")
}
catch {
Stop-HeartbeatMonitor
$failureMessage = $_.Exception.Message
if ($UseCodexMaster -and $null -eq $codexFinalVerdict) {
try {
$failureSections = @(
"## FAILURE CONTEXT`nThe autopilot run failed before final acceptance.",
"## FAILURE MESSAGE`n$failureMessage",
"## IMPLEMENTER WORKTREE`n$worktreePath",
"## IMPLEMENTER DIFF FILE`n$(Join-Path $runDir 'implementer.patch')",
"## IMPLEMENTER STATUS FILE`n$(Join-Path $runDir 'implementer_status.txt')",
"## REVIEW FILES`n$($reviewOutputs -join "`n")"
)
$null = Invoke-CodexReviewPass `
-StageName "codex_review_failure" `
-PromptFileName "codex_master_review_failure.md" `
-OutputFileName "codex_master_failure.json" `
-RunningMessage "Codex master failure review started" `
-CompletedMessage "Codex master failure review completed" `
-ExtraSections $failureSections
}
catch {
$script:runState.errors += ("Codex failure review error: " + $_.Exception.Message)
Add-RalphEvent -RunId $runId -Stage "codex_review_failure" -Status "failed" -Actor "codex_master" -Message $_.Exception.Message
}
}
$script:runState.status = "failed"
$script:runState.stage = "failed"
$script:runState.finished_at = (Get-Date).ToString("o")
$script:runState.latest_message = $failureMessage
$script:runState.errors += $failureMessage
Save-RunState
Add-RalphEvent -RunId $runId -Stage "failed" -Status "failed" -Message $failureMessage
Send-TelegramNotification `
-EventName "run_failed" `
-Title "Run failed" `
-Message $failureMessage `
-RunId $runId `
-Stage "failed" `
-Status "failed" | Out-Null
try {
$autoFollowupResult = Invoke-AutoFollowupTask -OutcomeStatus "failed" -OutcomeSummary $failureMessage
}
catch {
$script:runState.errors += ("Auto-followup generation error: " + $_.Exception.Message)
Save-RunState
Add-RalphEvent -RunId $runId -Stage "auto_followup" -Status "failed" -Actor "codex_master" -Message $_.Exception.Message
}
$summaryLines += @(
"",
"## Failure",
"",
$failureMessage
)
if ($null -ne $autoFollowupResult) {
$summaryLines += @(
"",
"## Auto Followup",
"",
"- Title: $($autoFollowupResult.title)",
"- Task directory: $($autoFollowupResult.task_directory)",
"- Source markdown: $($autoFollowupResult.source_markdown)"
)
}
Write-Utf8File -Path (Join-Path $runDir "SUMMARY.md") -Content (($summaryLines -join "`n") + "`n")
throw
}

View File

@@ -0,0 +1,68 @@
param(
[string]$TaskDirectory = "",
[string]$RunLabel = "background",
[string]$Implementer = "",
[string[]]$Reviewers = @(),
[switch]$DisableCodexMaster,
[switch]$DisableAutoFix
)
. (Join-Path $PSScriptRoot "Common.ps1")
$ralphRoot = Get-RalphRoot
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$stdoutLog = Join-Path $ralphRoot ("logs\ralph-" + $timestamp + ".out.log")
$stderrLog = Join-Path $ralphRoot ("logs\ralph-" + $timestamp + ".err.log")
$stateFile = Join-Path $ralphRoot "state\last_background_run.json"
$autopilotScript = Join-Path $PSScriptRoot "Start-RalphAutopilot.ps1"
$argumentParts = @(
"-ExecutionPolicy Bypass",
('-File "{0}"' -f $autopilotScript),
('-RunLabel "{0}"' -f $RunLabel)
)
if (-not [string]::IsNullOrWhiteSpace($TaskDirectory)) {
$argumentParts += ('-TaskDirectory "{0}"' -f $TaskDirectory)
}
if (-not [string]::IsNullOrWhiteSpace($Implementer)) {
$argumentParts += ('-Implementer "{0}"' -f $Implementer)
}
foreach ($reviewer in $Reviewers) {
$argumentParts += ('-Reviewers "{0}"' -f $reviewer)
}
if ($DisableCodexMaster) {
$argumentParts += '-UseCodexMaster:$false'
}
if ($DisableAutoFix) {
$argumentParts += '-AutoFix:$false'
}
$argumentList = $argumentParts -join ' '
$process = Start-Process `
-FilePath "powershell.exe" `
-ArgumentList $argumentList `
-WorkingDirectory (Get-RepoRoot) `
-WindowStyle Hidden `
-RedirectStandardOutput $stdoutLog `
-RedirectStandardError $stderrLog `
-PassThru
$state = [ordered]@{
pid = $process.Id
started_at = (Get-Date).ToString("o")
stdout_log = $stdoutLog
stderr_log = $stderrLog
run_label = $RunLabel
status = "started"
}
Write-JsonFile -Path $stateFile -Object $state
Add-RalphEvent -RunId ("background-" + $timestamp) -Stage "background" -Status "started" -Actor "launcher" -Message "Background Ralph process launched" -Data @{
pid = $process.Id
run_label = $RunLabel
stdout_log = $stdoutLog
stderr_log = $stderrLog
}
Get-Content -Raw -Path $stateFile

View File

@@ -0,0 +1,13 @@
param(
[string]$Host = "127.0.0.1",
[int]$Port = 8765
)
. (Join-Path $PSScriptRoot "Common.ps1")
$dashboardScript = Join-Path (Get-RalphRoot) "gui\app.py"
if (-not (Test-Path $dashboardScript)) {
throw "Dashboard script not found: $dashboardScript"
}
& python $dashboardScript --host $Host --port $Port

View File

@@ -0,0 +1,69 @@
param(
[int]$PollSeconds = 15,
[string]$Implementer = "",
[string[]]$Reviewers = @(),
[switch]$DisableCodexMaster,
[switch]$DisableAutoFix,
[switch]$DryRun
)
. (Join-Path $PSScriptRoot "Common.ps1")
$ralphRoot = Get-RalphRoot
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$stdoutLog = Join-Path $ralphRoot ("logs\ralph-inbox-" + $timestamp + ".out.log")
$stderrLog = Join-Path $ralphRoot ("logs\ralph-inbox-" + $timestamp + ".err.log")
$stateFile = Join-Path $ralphRoot "state\last_inbox_background.json"
$daemonScript = Join-Path $PSScriptRoot "Start-RalphInboxDaemon.ps1"
$argumentParts = @(
"-ExecutionPolicy Bypass",
('-File "{0}"' -f $daemonScript),
('-PollSeconds {0}' -f $PollSeconds)
)
if (-not [string]::IsNullOrWhiteSpace($Implementer)) {
$argumentParts += ('-Implementer "{0}"' -f $Implementer)
}
foreach ($reviewer in $Reviewers) {
$argumentParts += ('-Reviewers "{0}"' -f $reviewer)
}
if ($DisableCodexMaster) {
$argumentParts += '-DisableCodexMaster'
}
if ($DisableAutoFix) {
$argumentParts += '-DisableAutoFix'
}
if ($DryRun) {
$argumentParts += '-DryRun'
}
$argumentList = $argumentParts -join ' '
$process = Start-Process `
-FilePath "powershell.exe" `
-ArgumentList $argumentList `
-WorkingDirectory (Get-RepoRoot) `
-WindowStyle Hidden `
-RedirectStandardOutput $stdoutLog `
-RedirectStandardError $stderrLog `
-PassThru
$state = [ordered]@{
pid = $process.Id
started_at = (Get-Date).ToString("o")
stdout_log = $stdoutLog
stderr_log = $stderrLog
poll_seconds = $PollSeconds
dry_run = [bool]$DryRun
status = "started"
}
Write-JsonFile -Path $stateFile -Object $state
Add-RalphEvent -RunId ("inbox-background-" + $timestamp) -Stage "background" -Status "started" -Actor "launcher" -Message "Background Ralph inbox daemon launched" -Data @{
pid = $process.Id
stdout_log = $stdoutLog
stderr_log = $stderrLog
}
Get-Content -Raw -Path $stateFile

View File

@@ -0,0 +1,292 @@
param(
[int]$PollSeconds = 15,
[string]$Implementer = "",
[string[]]$Reviewers = @(),
[switch]$DisableCodexMaster,
[switch]$DisableAutoFix,
[switch]$DryRun,
[switch]$Once,
[int]$MaxTasks = 0,
[string]$InboxDirectory = ""
)
. (Join-Path $PSScriptRoot "Common.ps1")
$roots = Get-RalphTaskRoots
foreach ($path in $roots.Values) {
Ensure-Directory -Path $path
}
if ([string]::IsNullOrWhiteSpace($InboxDirectory)) {
$InboxDirectory = $roots.Inbox
}
Ensure-Directory -Path $InboxDirectory
$lockFile = Get-RalphStateFile -Name "inbox_daemon.lock.json"
$daemonStateFile = Get-RalphStateFile -Name "inbox_daemon_state.json"
$script:daemonRunId = "daemon-" + (Get-Date -Format "yyyyMMdd-HHmmss")
$script:processedCount = 0
function Save-DaemonState {
param(
[string]$Status,
[string]$Message,
[hashtable]$Extra = @{}
)
$state = [ordered]@{
pid = $PID
status = $Status
message = $Message
updated_at = (Get-Date).ToString("o")
poll_seconds = $PollSeconds
dry_run = [bool]$DryRun
inbox_directory = $InboxDirectory
processed_count = $script:processedCount
}
foreach ($key in $Extra.Keys) {
$state[$key] = $Extra[$key]
}
Write-JsonFile -Path $daemonStateFile -Object $state
}
function Acquire-DaemonLock {
if (Test-Path $lockFile) {
try {
$existing = Read-JsonFile -Path $lockFile
$existingPid = 0
try { $existingPid = [int]$existing.pid } catch { $existingPid = 0 }
if ($existingPid -gt 0) {
$proc = Get-Process -Id $existingPid -ErrorAction SilentlyContinue
if ($null -ne $proc) {
throw "Ralph inbox daemon already running with PID $existingPid."
}
}
}
catch {
# stale or malformed lock; overwrite it
}
}
Write-JsonFile -Path $lockFile -Object ([ordered]@{
pid = $PID
started_at = (Get-Date).ToString("o")
inbox_directory = $InboxDirectory
})
}
function Release-DaemonLock {
if (Test-Path $lockFile) {
Remove-Item -LiteralPath $lockFile -Force -ErrorAction SilentlyContinue
}
}
function Get-NextInboxItem {
$directories = @(Get-ChildItem -LiteralPath $InboxDirectory -Directory -ErrorAction SilentlyContinue | Sort-Object LastWriteTime, Name)
foreach ($dir in $directories) {
if (Test-Path (Join-Path $dir.FullName "TASK.md")) {
return [ordered]@{
kind = "taskpack"
path = $dir.FullName
name = $dir.Name
}
}
}
$files = @(Get-ChildItem -LiteralPath $InboxDirectory -File -Filter *.md -ErrorAction SilentlyContinue | Sort-Object LastWriteTime, Name)
foreach ($file in $files) {
return [ordered]@{
kind = "markdown"
path = $file.FullName
name = $file.Name
}
}
return $null
}
function Move-InboxItemToProcessing {
param([hashtable]$Item)
$id = "{0}-{1}" -f (Get-Date -Format "yyyyMMdd-HHmmss"), (Convert-ToRalphSlug -Text ([System.IO.Path]::GetFileNameWithoutExtension($Item.name)))
$targetDir = Join-Path $roots.Processing $id
Ensure-Directory -Path $targetDir
if ($Item.kind -eq "taskpack") {
$targetParent = Split-Path -Parent $targetDir
if (-not (Test-Path $targetParent)) {
Ensure-Directory -Path $targetParent
}
Remove-Item -LiteralPath $targetDir -Recurse -Force -ErrorAction SilentlyContinue
Move-Item -LiteralPath $Item.path -Destination $targetDir
return [ordered]@{
id = $id
task_directory = $targetDir
title = $Item.name
source = $Item.path
}
}
$taskInfo = New-RalphTaskPackFromMarkdown -MarkdownPath $Item.path -TaskId $id -TargetDirectory $targetDir
Remove-Item -LiteralPath $Item.path -Force
return [ordered]@{
id = $taskInfo.id
task_directory = $taskInfo.task_directory
title = $taskInfo.title
source = $Item.path
}
}
function Finalize-ProcessedItem {
param(
[hashtable]$Processed,
[string]$DestinationRoot,
[string]$State,
[string]$RunId = "",
[string]$Summary = ""
)
Ensure-Directory -Path $DestinationRoot
$destination = Join-Path $DestinationRoot ([System.IO.Path]::GetFileName($Processed.task_directory))
if (Test-Path $destination) {
Remove-Item -LiteralPath $destination -Recurse -Force -ErrorAction SilentlyContinue
}
Move-Item -LiteralPath $Processed.task_directory -Destination $destination
$submissionFile = Join-Path $destination "submission.json"
$submission = [ordered]@{
id = $Processed.id
title = $Processed.title
state = $State
finished_at = (Get-Date).ToString("o")
source = $Processed.source
run_id = $RunId
summary = $Summary
}
Write-JsonFile -Path $submissionFile -Object $submission
return $destination
}
Acquire-DaemonLock
Save-DaemonState -Status "running" -Message "Inbox daemon started"
Add-RalphEvent -RunId $script:daemonRunId -Stage "daemon" -Status "running" -Actor "daemon" -Message "Ralph inbox daemon started" -Data @{
inbox_directory = $InboxDirectory
}
Send-TelegramNotification `
-EventName "daemon_started" `
-Title "Ralph daemon started" `
-Message ("Inbox: " + $InboxDirectory) `
-RunId $script:daemonRunId `
-Stage "daemon" `
-Status "running" | Out-Null
try {
while ($true) {
$item = Get-NextInboxItem
if ($null -eq $item) {
if ($Once) {
Save-DaemonState -Status "idle" -Message "No tasks in inbox; exiting because -Once was used"
break
}
Save-DaemonState -Status "idle" -Message "Waiting for inbox tasks"
Start-Sleep -Seconds $PollSeconds
continue
}
$processed = Move-InboxItemToProcessing -Item $item
$script:processedCount += 1
Save-DaemonState -Status "running" -Message ("Processing task " + $processed.id) -Extra @{
current_task = $processed.id
current_task_directory = $processed.task_directory
}
Add-RalphEvent -RunId $script:daemonRunId -Stage "queue" -Status "started" -Actor "daemon" -Message ("Dequeued task " + $processed.id) -Data @{
task_directory = $processed.task_directory
}
Send-TelegramNotification `
-EventName "task_processing" `
-Title "Task processing" `
-Message ($processed.title + "`n" + $processed.task_directory) `
-RunId $processed.id `
-Stage "queue" `
-Status "started" | Out-Null
$runId = ""
$summary = ""
try {
$args = @(
"-ExecutionPolicy", "Bypass",
"-File", (Join-Path $PSScriptRoot "Start-RalphAutopilot.ps1"),
"-TaskDirectory", $processed.task_directory,
"-RunLabel", "queue"
)
if ($DryRun) {
$args += "-DryRun"
}
if (-not [string]::IsNullOrWhiteSpace($Implementer)) {
$args += @("-Implementer", $Implementer)
}
foreach ($reviewer in $Reviewers) {
$args += @("-Reviewers", $reviewer)
}
if ($DisableCodexMaster) {
$args += "-UseCodexMaster:$false"
}
if ($DisableAutoFix) {
$args += "-AutoFix:$false"
}
& powershell.exe @args
if ($LASTEXITCODE -ne 0) {
throw "Start-RalphAutopilot.ps1 failed with exit code $LASTEXITCODE"
}
$currentRunState = Read-JsonFile -Path (Get-RalphStateFile -Name "current_run.json")
$runId = [string]$currentRunState.run_id
$summary = [string]$currentRunState.latest_message
$finalPath = Finalize-ProcessedItem -Processed $processed -DestinationRoot $roots.Completed -State "completed" -RunId $runId -Summary $summary
Add-RalphEvent -RunId $script:daemonRunId -Stage "queue" -Status "completed" -Actor "daemon" -Message ("Completed task " + $processed.id) -Data @{
task_directory = $finalPath
run_id = $runId
}
Send-TelegramNotification `
-EventName "task_completed" `
-Title "Task completed" `
-Message ($processed.title + "`n" + $summary) `
-RunId $runId `
-Stage "queue" `
-Status "completed" | Out-Null
}
catch {
$summary = $_.Exception.Message
$failedPath = Finalize-ProcessedItem -Processed $processed -DestinationRoot $roots.Failed -State "failed" -RunId $runId -Summary $summary
Add-RalphEvent -RunId $script:daemonRunId -Stage "queue" -Status "failed" -Actor "daemon" -Message ("Failed task " + $processed.id + ": " + $summary) -Data @{
task_directory = $failedPath
run_id = $runId
}
Send-TelegramNotification `
-EventName "task_failed" `
-Title "Task failed" `
-Message ($processed.title + "`n" + $summary) `
-RunId $runId `
-Stage "queue" `
-Status "failed" | Out-Null
}
if ($MaxTasks -gt 0 -and $script:processedCount -ge $MaxTasks) {
Save-DaemonState -Status "completed" -Message "MaxTasks limit reached"
break
}
}
}
finally {
Save-DaemonState -Status "stopped" -Message "Inbox daemon stopped"
Add-RalphEvent -RunId $script:daemonRunId -Stage "daemon" -Status "stopped" -Actor "daemon" -Message "Ralph inbox daemon stopped"
Send-TelegramNotification `
-EventName "daemon_stopped" `
-Title "Ralph daemon stopped" `
-Message "Inbox daemon stopped" `
-RunId $script:daemonRunId `
-Stage "daemon" `
-Status "stopped" | Out-Null
Release-DaemonLock
}

View File

@@ -0,0 +1,54 @@
. (Join-Path $PSScriptRoot "Common.ps1")
$lockFile = Get-RalphStateFile -Name "inbox_daemon.lock.json"
$daemonStateFile = Get-RalphStateFile -Name "inbox_daemon_state.json"
if (-not (Test-Path $lockFile)) {
@{
stopped = $false
message = "No inbox daemon lock file found."
} | ConvertTo-Json -Depth 20
return
}
$lock = Read-JsonFile -Path $lockFile
$daemonPid = 0
try { $daemonPid = [int]$lock.pid } catch { $daemonPid = 0 }
if ($daemonPid -le 0) {
Remove-Item -LiteralPath $lockFile -Force -ErrorAction SilentlyContinue
@{
stopped = $false
message = "Lock file was invalid and has been removed."
} | ConvertTo-Json -Depth 20
return
}
$proc = Get-Process -Id $daemonPid -ErrorAction SilentlyContinue
if ($null -ne $proc) {
Stop-Process -Id $daemonPid -Force
}
Remove-Item -LiteralPath $lockFile -Force -ErrorAction SilentlyContinue
Write-JsonFile -Path $daemonStateFile -Object ([ordered]@{
pid = $daemonPid
status = "stopped"
message = "Inbox daemon stopped manually."
updated_at = (Get-Date).ToString("o")
})
Add-RalphEvent -RunId ("inbox-daemon-stop-" + (Get-Date -Format "yyyyMMdd-HHmmss")) -Stage "daemon" -Status "stopped" -Actor "stop" -Message "Inbox daemon stopped manually" -Data @{
pid = $daemonPid
}
Send-TelegramNotification `
-EventName "daemon_stopped" `
-Title "Ralph daemon stopped manually" `
-Message ("PID " + $daemonPid) `
-RunId ("inbox-daemon-stop-" + (Get-Date -Format "yyyyMMdd-HHmmss")) `
-Stage "daemon" `
-Status "stopped" | Out-Null
@{
stopped = $true
pid = $daemonPid
} | ConvertTo-Json -Depth 20

View File

@@ -0,0 +1,61 @@
param(
[string]$SourceFile = "",
[string]$Text = "",
[string]$Title = "",
[switch]$PassThru
)
. (Join-Path $PSScriptRoot "Common.ps1")
if ([string]::IsNullOrWhiteSpace($SourceFile) -and [string]::IsNullOrWhiteSpace($Text)) {
throw "Provide either -SourceFile or -Text."
}
$roots = Get-RalphTaskRoots
foreach ($path in $roots.Values) {
Ensure-Directory -Path $path
}
if (-not [string]::IsNullOrWhiteSpace($SourceFile)) {
$taskInfo = New-RalphTaskPackFromMarkdown -MarkdownPath $SourceFile -Title $Title
}
else {
$tempName = "{0}.md" -f ([guid]::NewGuid().ToString("N"))
$tempPath = Join-Path ([System.IO.Path]::GetTempPath()) $tempName
try {
Write-Utf8File -Path $tempPath -Content ($Text.Trim() + "`n")
$taskInfo = New-RalphTaskPackFromMarkdown -MarkdownPath $tempPath -Title $Title
}
finally {
if (Test-Path $tempPath) {
Remove-Item -LiteralPath $tempPath -Force -ErrorAction SilentlyContinue
}
}
}
Add-RalphEvent -RunId $taskInfo.id -Stage "submit" -Status "queued" -Actor "submit" -Message ("Task queued: " + $taskInfo.title) -Data @{
task_directory = $taskInfo.task_directory
}
Send-TelegramNotification `
-EventName "task_queued" `
-Title "Task queued" `
-Message ($taskInfo.title + "`n" + $taskInfo.task_directory) `
-RunId $taskInfo.id `
-Stage "submit" `
-Status "queued" | Out-Null
$result = [ordered]@{
id = $taskInfo.id
title = $taskInfo.title
state = "queued"
task_directory = $taskInfo.task_directory
task_file = $taskInfo.task_file
}
$json = ($result | ConvertTo-Json -Depth 20)
if ($PassThru) {
$result
}
else {
$json
}

View File

@@ -0,0 +1,80 @@
param(
[switch]$Full
)
. (Join-Path $PSScriptRoot "Common.ps1")
$codexConfig = Get-CodexConfig
$preferredCmd = Join-Path $env:APPDATA "npm\codex.cmd"
$preferredPs1 = Join-Path $env:APPDATA "npm\codex.ps1"
$codexPath = $null
if (Test-Path $preferredCmd) {
$codexPath = $preferredCmd
}
elseif (Test-Path $preferredPs1) {
$codexPath = $preferredPs1
}
else {
$codexCommand = Get-Command codex -ErrorAction SilentlyContinue
if ($null -ne $codexCommand) {
$codexPath = $codexCommand.Source
}
}
$result = [ordered]@{
ok = $false
binary_found = $false
binary_path = $codexPath
session_id = [string]$codexConfig.session_id
model = [string]$codexConfig.model
help_ok = $false
full_ok = $false
output = ""
}
if ([string]::IsNullOrWhiteSpace($codexPath)) {
$result.output = "codex executable not found"
}
else {
$result.binary_found = $true
$helpOutput = & $codexPath exec resume --help 2>&1
if ($LASTEXITCODE -eq 0) {
$result.help_ok = $true
}
$result.output = (@($helpOutput) -join [Environment]::NewLine)
if ($Full) {
$tempPromptPath = Join-Path ([System.IO.Path]::GetTempPath()) ("ralph-codex-smoke-" + [guid]::NewGuid().ToString("N") + ".md")
$tempOutput = Join-Path ([System.IO.Path]::GetTempPath()) ("ralph-codex-smoke-" + [guid]::NewGuid().ToString("N") + ".json")
$tempPrompt = @'
You are Codex.
Return exactly one JSON object and nothing else.
Use this exact payload:
{"verdict":"pass","acceptance_passed":true,"fix_required":false,"summary":"smoke","highest_risk_issues":[],"next_sprint_needed":false,"next_sprint_brief":""}
'@
try {
Write-Utf8File -Path $tempPromptPath -Content ($tempPrompt.Trim() + "`n")
$verdict = & (Join-Path $PSScriptRoot "Invoke-CodexMaster.ps1") -PromptFile $tempPromptPath -OutputFile $tempOutput
$result.output = ($result.output + [Environment]::NewLine + ($verdict | ConvertTo-Json -Depth 20)).Trim()
if ($null -ne $verdict -and [string]$verdict.verdict -eq "pass" -and [bool]$verdict.acceptance_passed) {
$result.full_ok = $true
}
}
catch {
$result.output = ($result.output + [Environment]::NewLine + $_.Exception.Message).Trim()
}
finally {
if (Test-Path $tempPromptPath) {
Remove-Item -LiteralPath $tempPromptPath -Force -ErrorAction SilentlyContinue
}
if (Test-Path $tempOutput) {
Remove-Item -LiteralPath $tempOutput -Force -ErrorAction SilentlyContinue
}
}
}
}
$result.ok = [bool]($result.binary_found -and $result.help_ok -and ((-not $Full) -or $result.full_ok))
$outputPath = Join-Path (Get-RalphRoot) "state\codex_smoke.json"
Write-Utf8File -Path $outputPath -Content ((@($result) | ConvertTo-Json -Depth 20) + "`n")
Get-Content -Raw -Path $outputPath

View File

@@ -0,0 +1,105 @@
param(
[string[]]$ProviderNames = @()
)
. (Join-Path $PSScriptRoot "Common.ps1")
function Get-ResponseText {
param($Response)
if ($null -eq $Response) {
return ""
}
if ($Response.content) {
$textBlocks = @($Response.content | Where-Object { $_.type -eq "text" -and $_.text })
if ($textBlocks.Count -gt 0) {
return (($textBlocks | ForEach-Object { [string]$_.text }) -join " ").Trim()
}
$fallbackBlocks = @($Response.content | Where-Object { $_.thinking -or $_.text })
if ($fallbackBlocks.Count -gt 0) {
$parts = foreach ($block in $fallbackBlocks) {
if ($block.text) { [string]$block.text }
elseif ($block.thinking) { [string]$block.thinking }
}
return (($parts | Where-Object { $_ }) -join " ").Trim()
}
}
return (($Response | ConvertTo-Json -Depth 20) -replace "\s+", " ").Trim()
}
$config = Get-ProvidersConfig
if ($ProviderNames.Count -eq 0) {
$ProviderNames = @($config.providers.PSObject.Properties.Name)
}
else {
$expanded = @()
foreach ($entry in $ProviderNames) {
if ($null -ne $entry) {
$expanded += ([string]$entry).Split(",") | ForEach-Object { $_.Trim() } | Where-Object { $_ }
}
}
$ProviderNames = $expanded
}
$results = @()
foreach ($name in $ProviderNames) {
$provider = Get-ProviderConfig -Name $name
$endpoint = ([string]$provider.base_url).TrimEnd("/") + "/v1/messages"
$headers = @{
"x-api-key" = [string]$provider.auth_token
"anthropic-version" = "2023-06-01"
"content-type" = "application/json"
}
$body = @{
model = [string]$provider.model
max_tokens = 16
messages = @(
@{
role = "user"
content = "Respond with exactly OK and nothing else."
}
)
} | ConvertTo-Json -Depth 10
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
try {
$response = Invoke-RestMethod -Method Post -Uri $endpoint -Headers $headers -Body $body -TimeoutSec 120
$stopwatch.Stop()
$text = Get-ResponseText -Response $response
$semanticOk = ($text.Trim() -eq "OK")
$results += [ordered]@{
provider = $name
model = [string]$provider.model
endpoint = $endpoint
http_ok = $true
semantic_ok = $semanticOk
ok = $semanticOk
latency_ms = $stopwatch.ElapsedMilliseconds
response = $text.Trim()
}
}
catch {
$stopwatch.Stop()
$results += [ordered]@{
provider = $name
model = [string]$provider.model
endpoint = $endpoint
http_ok = $false
semantic_ok = $false
ok = $false
latency_ms = $stopwatch.ElapsedMilliseconds
response = $_.Exception.Message
}
}
}
$outputPath = Join-Path (Get-RalphRoot) "state\provider_smoke.json"
Write-Utf8File -Path $outputPath -Content ((@($results) | ConvertTo-Json -Depth 10) + "`n")
Get-Content -Raw -Path $outputPath

View File

@@ -0,0 +1,16 @@
param(
[string]$Title = "Ralph Telegram test",
[string]$Message = "Telegram notifications are enabled."
)
. (Join-Path $PSScriptRoot "Common.ps1")
$result = Send-TelegramNotification `
-EventName "run_started" `
-Title $Title `
-Message $Message `
-RunId ("telegram-test-" + (Get-Date -Format "yyyyMMdd-HHmmss")) `
-Stage "test" `
-Status "manual"
($result | ConvertTo-Json -Depth 20)

View File

@@ -0,0 +1,18 @@
param(
[string]$TaskName = "RalphInboxDaemon"
)
try {
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -ErrorAction Stop
@{
task_name = $TaskName
removed = $true
} | ConvertTo-Json -Depth 20
}
catch {
@{
task_name = $TaskName
removed = $false
error = $_.Exception.Message
} | ConvertTo-Json -Depth 20
}

1
ralph/state/.gitkeep Normal file
View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,3 @@
- daemon exits cleanly
- a completed task pack is produced
- a run directory exists with SUMMARY.md

View File

@@ -0,0 +1,9 @@
# Context
List the canonical docs and code paths the worker must read first.
- active sprint doc
- current handoff
- relevant source files
- known evidence files

View File

@@ -0,0 +1,9 @@
# Ralph Dry Run Validation
## Goal
Validate that the inbox daemon can convert a markdown task, process it in dry-run mode, and archive it as completed.
## Acceptance Criteria
- daemon exits cleanly
- a completed task pack is produced
- a run directory exists with SUMMARY.md

View File

@@ -0,0 +1,4 @@
# Ralph Dry Run Validation
## Goal
Validate that the inbox daemon can convert a markdown task, process it in dry-run mode, and archive it as completed.

View File

@@ -0,0 +1,9 @@
{
"id": "20260403-181602-test-task",
"title": "Ralph Dry Run Validation",
"state": "completed",
"finished_at": "2026-04-03T18:16:02.8812410-03:00",
"source": "C:\\Users\\ren\\AppData\\Local\\Temp\\ralph-inbox-test-da9d628bc10b432da976cb4184661a1f\\TEST_TASK.md",
"run_id": "20260403-181602-queue",
"summary": "Dry run prepared successfully"
}

View File

@@ -0,0 +1,12 @@
# Acceptance
The task is only complete if all of these are true:
1. `kimi.md` exists in project root and is at least 200 lines
2. File includes Ralph workflow section (how to behave as Implementer)
3. File includes project architecture overview (MCP → Server → Remote Script)
4. File includes critical file paths with absolute Windows paths
5. File includes common commands (PowerShell syntax)
6. File includes debugging checklist (what to check when things fail)
7. File references existing KIMI_K2_*.md files appropriately
8. CHANGES.md documents what was created and any gaps

View File

@@ -0,0 +1,61 @@
# CHANGES.md - Desarrollo de kimi.md
**Fecha**: 2026-03-30
**Iteración**: Ralph Loop - Iteración 1
**Tarea**: Crear guía completa kimi.md para Implementer en Ralph Wiggum
## Qué se hizo
### 1. Análisis de archivos existentes
Leídos todos los archivos de handoff Kimi para sintetizar información:
- `KIMI_K2_START_HERE.md` (156 líneas) - orden de lectura y verdades del proyecto
- `KIMI_K2_ACTIVE_HANDOFF.md` (324 líneas) - estado actual detallado
- `KIMI_K2_BOOTSTRAP.md` (95 líneas) - orden de lectura extendido
- `KIMI_K2_CODEBASE_FIXES.md` (400 líneas) - fixes históricos
- `KIMI_K2_NOTE_API_FIX.md` (292 líneas) - fix específico de API de notas
### 2. Actualización de kimi.md
El archivo `kimi.md` ya existía (31KB, 674 líneas) como "Kimi Professional Iteration Playbook" con 400 tareas de iteración.
**Agregado al principio**: Sección completa de **Ralph Wiggum Workflow para Implementer** incluyendo:
- Flujo de trabajo del Implementer en Ralph
- Reglas de Ralph (no auto-merge, worktree aislado, CHANGES.md obligatorio)
- Scripts de Ralph disponibles
- Configuración de providers
- TL;DR para empezar rápido
- Metadatos de rol/modelo/proyecto
## Estado del archivo
- **Tamaño**: ~32KB
- **Líneas**: ~700
- **Cobertura**:
- ✅ Ralph workflow completo
- ✅ 400 tareas de iteración profesional
- ✅ Comandos PowerShell
- ✅ Checklist de debugging
- ✅ Referencias cruzadas a todos los KIMI_K2_*.md
## Qué queda por hacer
1. Validar que el archivo se cargue correctamente en futuras sesiones de Ralph
2. Posiblemente agregar sección de "Mensajes entre agentes" cuando Ralph tenga peer DMs
3. Documentar el formato exacto de eventos en `ralph/state/events.jsonl`
## Evidencia
```powershell
# Verificar archivo actualizado
Get-Content "C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\kimi.md" -Head 50
```
## Notas
El archivo `kimi.md` ahora sirve como:
1. **Playbook de iteración** para trabajo autónomo (400 tareas)
2. **Handoff para Ralph** con instrucciones específicas de workflow
No se rompió contenido existente - solo se agregó contexto de Ralph al inicio.

View File

@@ -0,0 +1,35 @@
# Context
## Ralph Role Context
You are the **Implementer** in a Ralph Wiggum loop. Your job is to:
1. Read the TASK.md and understand what needs to be done
2. Work in the assigned worktree (created by Ralph)
3. Make concrete, evidence-backed changes
4. Leave a CHANGES.md documenting what you did
5. Do NOT merge to main - leave results in worktree for review
## Read These First
1. `CLAUDE.md` - canonical project context (highest priority)
2. `KIMI_K2_START_HERE.md` - existing handoff for Kimi
3. `KIMI_K2_ACTIVE_HANDOFF.md` - current state from Kimi's perspective
4. `KIMI_K2_BOOTSTRAP.md` - bootstrap information
5. `KIMI_K2_CODEBASE_FIXES.md` - known fixes
6. `KIMI_K2_NOTE_API_FIX.md` - API-specific notes
## Project Structure (from CLAUDE.md)
- User-facing root: `C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts`
- MCP code root: `AbletonMCP_AI\AbletonMCP_AI`
- Active entrypoint: `abletonmcp_init.py`
- Remote Script: `AbletonMCP_AI\__init__.py`
- MCP Server: `AbletonMCP_AI\AbletonMCP_AI\MCP_Server\server.py`
## Key Constraints
- Always read files before modifying them
- Use PowerShell syntax (Windows native)
- Verify with logs and runtime evidence
- Do not trust stale docs over live code
- Keep each mutation small to avoid Audio queue timeout

View File

@@ -0,0 +1,30 @@
# Task
## Goal
Create a comprehensive `kimi.md` file from scratch that serves as the canonical handoff guide for the Kimi K2.5 model when acting as the **Implementer** in Ralph Wiggum loops.
This file should be the single source of truth for Kimi to understand and work effectively in the AbletonMCP-AI project.
## Files in scope
- `kimi.md` (to be created in project root)
- All existing KIMI_K2_*.md files for reference
- `CLAUDE.md` (project canonical context)
- `AbletonMCP_AI/AbletonMCP_AI/MCP_Server/` modules
- `AbletonMCP_AI/__init__.py` and `Remote_Script.py`
## Constraints
- use Windows native PowerShell
- read existing files before writing kimi.md
- do not copy-paste blindly - synthesize and improve
- keep sections actionable and concrete
- include Ralph-specific workflow guidance
- include common pitfalls and how to avoid them
## Expected output
- `kimi.md` in project root with complete handoff information
- `CHANGES.md` in the run directory documenting what was created
- clear indication of any gaps that need future work

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,14 @@
The sprint is complete only if all of these are true:
1. the open project `song.als` was used as the validation target
2. MCP editing tools were validated live, not just compiled
3. the open project received a real edit pass
4. harmonic MIDI backbone exists in Arrangement and covers materially more of the song
5. silence islands and mirrored symmetry were measured before and after
6. the result is less empty and less repetitive without losing structure
7. sound selection logic for aggressive snares became more selective instead of a blind blacklist
8. all changed Python files compile
9. relevant tests pass
10. the report includes exact MCP calls used on the open project
11. the report includes exact before/after coherence metrics
12. Codex final verdict is `pass`

View File

@@ -0,0 +1,9 @@
# Context
List the canonical docs and code paths the worker must read first.
- active sprint doc
- current handoff
- relevant source files
- known evidence files

View File

@@ -0,0 +1,316 @@
# SPRINT v0.1.41 - Ralph Swarm - Open Project Editing and Coherence
## Goal
Use the Ralph swarm to work on the already-open Ableton project:
- `C:\Users\ren\Desktop\song Project\song.als`
This is not a new-song generation sprint.
This sprint is about:
1. giving the MCP more real editing power over open projects
2. using those tools on the current open project
3. improving coherence, continuity and musical identity
4. reducing symmetry, repeated blocks and empty gaps
5. treating harmonic MIDI as a real backbone across the full arrangement
## Important Product Clarification
The user comes from FL Studio and may say `piano roll`.
In this project that means:
- harmonic MIDI backbone
- long-form arrangement MIDI that carries harmony/melodic identity
- editable MIDI clips in Arrangement
It does **not** mean:
- force piano timbre everywhere
- spam piano loops
- replace the user library with generic piano sounds
The right interpretation is:
- use `HARMONY_*_MIDI` as the musical spine
- blend that spine with the user's library
- keep the project library-first-hybrid
- make the MIDI audible, useful and persistent throughout the song
## Current Problems To Solve
The current system has improved, but the open-project result still tends to show:
- too much geometric symmetry
- repeated section shapes with near-identical spacing
- too many silence islands
- a good 4-second loop followed by dead air
- harmonic MIDI backbone still too weak or too local
- sections differentiated by removal instead of transformation
- sound selection still too conservative or too repetitive
- aggressive snares sometimes damaging coherence
The specific snare to watch carefully is:
- `SS_RNBL_Me_Gustas_One_Shot_Snare.wav`
Do **not** hard-ban it blindly.
Instead:
- analyze when it is appropriate
- score it more selectively by section energy, density and surrounding sources
- stop it from dominating softer sections or smoother grooves
## Scope
This sprint has two parallel tracks that must meet in one validated result.
### Track A - MCP Capability
Strengthen the public MCP editing workflow for an already-open project.
The target is not theoretical wrappers.
The target is:
- inspect open project
- inspect clips/devices/parameters
- edit Arrangement
- edit MIDI in Arrangement
- edit device parameters by name
- use repair tools meaningfully on a real project
### Track B - Project Editing
Apply those tools to `song.als` and improve the real set.
Do not stop at “tools exist”.
Use them.
## Required Outcomes
### A. Open-project editing must be real
At least these MCP abilities must be validated live on the open project:
- inspect tracks
- inspect clips on a track
- inspect a specific clip
- inspect devices on a track
- inspect device parameters
- set a device parameter by name
- create or duplicate Arrangement material
- add MIDI notes in Arrangement
- retrieve enough project state to support editing decisions
If any of these are still analysis-only or fallback-only, say so explicitly.
Do not mark them complete unless they were exercised against the open Live project.
### B. Harmonic MIDI backbone must become real arrangement content
The harmonic MIDI backbone must:
- exist in Arrangement, not just Session
- span much more of the song, not only one local region
- be audible and useful for continuity
- help fill gaps where the arrangement currently drops out
- support the user library rather than replacing it
It is acceptable if the timbre is pluck/keys/pad/synth instead of literal piano.
It is not acceptable if the MIDI exists only as metadata or one hidden clip.
### C. Coherence must improve without becoming sterile
The edit pass must reduce:
- mirrored section pairs
- dead gaps between phrases
- silence islands
- same-source overuse
- section-to-section copy-paste feel
At the same time, it must preserve:
- clear structure
- strong recognizable motif
- coherent sound family choices
- continuity across drums, bass and harmony
Do not solve “repetition” by randomizing everything.
Do not solve “coherence” by making every section identical.
### D. Sound freedom with discipline
The system should gain a bit more freedom in sound choice, but inside a coherent frame.
That means:
- allow controlled variation of support layers
- allow section-aware alternates
- keep identity layers constrained
- avoid collapsing everything into 3-4 sounds
- avoid hard lock to one exact symmetric loop
## Acceptance Criteria
The sprint is complete only if all of these are true:
1. the open project `song.als` was used as the validation target
2. MCP editing tools were validated live, not just compiled
3. the open project received a real edit pass
4. harmonic MIDI backbone exists in Arrangement and covers materially more of the song
5. silence islands and mirrored symmetry were measured before and after
6. the result is less empty and less repetitive without losing structure
7. sound selection logic for aggressive snares became more selective instead of a blind blacklist
8. all changed Python files compile
9. relevant tests pass
10. the report includes exact MCP calls used on the open project
11. the report includes exact before/after coherence metrics
12. Codex final verdict is `pass`
## Mandatory Validation
Validation must include all of the following:
### 1. Code validation
- `python -m py_compile` on every changed Python file
- relevant tests for MCP/runtime/coherence
### 2. Live validation
Against the open Ableton project:
- `get_session_info()`
- `get_tracks()`
- `get_track_info(...)`
- the editing tools exercised for real
### 3. Project audit
Run project-facing audits before and after the edit pass, including:
- silence islands
- mirrored section pairs
- harmonic coverage / backbone status
- same-source dominance or reuse
- repeated clip overuse if available
### 4. Audible sanity
The report must state clearly:
- whether the harmonic MIDI is actually audible
- whether gaps were reduced
- whether the project still feels too symmetric
- whether the snare selectivity improved
## Constraints
- Do not generate a new song from scratch.
- Do not replace the project with a fresh template.
- Do not rely on Session-only material and then claim Arrangement success.
- Do not treat wrappers or helper functions as success.
- Do not add vocals.
- Do not force literal piano timbre just because the user said “piano roll”.
- Do not hard-ban `SS_RNBL_Me_Gustas_One_Shot_Snare.wav`; score it contextually.
- Do not overclaim if the edit tools still depend on fragile transport hacks.
- Do not mark complete if the live project still looks obviously symmetrical and full of dead gaps.
## Implementation Guidance
### For MCP capability work
Prioritize tools that matter for editing open projects:
- clip inspection
- device inspection
- parameter editing by name
- Arrangement MIDI editing
- duplication and continuity repair
- project audit tools that reflect real editing pain
If a tool remains inherently limited by the Live API, document the exact limit and the exact fallback.
### For project editing work
Use the project audit as the source of truth.
Then make targeted edits:
- extend harmonic continuity
- reduce dead air
- break mirrored copy-paste shapes
- vary support layers across sections without losing family identity
- tighten selection of snare/clap layers by context
Preferred musical strategy:
- strong recurring identity
- long-form harmonic support
- fewer abrupt disappearances
- section evolution by mutation, layering and phrasing
- support layers changing more than core identity layers
## Required Deliverables
The implementing swarm must produce:
1. code changes
2. a validation report:
- `docs/SPRINT_v0.1.41_VALIDATION_REPORT.md`
3. if needed, one or more exported JSON artifacts under `temp/`
4. explicit list of changed files
5. exact MCP calls used
6. before/after metric table
7. a short section titled `Remaining Risks`
## Report Format
The validation report must contain these sections:
1. `Summary`
2. `Files Changed`
3. `MCP Tools Validated Live`
4. `Project Edits Applied`
5. `Before/After Metrics`
6. `Snare Selectivity`
7. `Harmonic MIDI Backbone`
8. `What Is Still Weak`
9. `Remaining Risks`
## Failure Conditions
The sprint automatically fails if any of these happen:
- the work validates against a new generated song instead of `song.als`
- `HARMONY_*_MIDI` is still absent from Arrangement in a meaningful way
- the project still has large obvious silence islands with no explanation
- the report claims success but the set still looks geometrically mirrored
- snare handling is “fixed” by crude blacklist instead of contextual scoring
- Codex final verdict is not `pass`
## Recommended Ralph Routing
Use the Ralph defaults already configured locally:
- implementer: `zai_glm51`
- reviewers: `dashscope_qwen3coder_plus`, `dashscope_glm5`
- Codex master: enabled
## Operator Note
This task is intended for the 24/7 Ralph queue.
Suggested submit command:
```powershell
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Submit-RalphTask.ps1 `
-SourceFile .\docs\SPRINT_v0.1.41_NEXT_RALPH_OPEN_PROJECT_EDITING.md
```

View File

@@ -0,0 +1,299 @@
# SPRINT v0.1.41 - Ralph Swarm - Open Project Editing and Coherence
## Goal
Use the Ralph swarm to work on the already-open Ableton project:
- `C:\Users\ren\Desktop\song Project\song.als`
This is not a new-song generation sprint.
This sprint is about:
1. giving the MCP more real editing power over open projects
2. using those tools on the current open project
3. improving coherence, continuity and musical identity
4. reducing symmetry, repeated blocks and empty gaps
5. treating harmonic MIDI as a real backbone across the full arrangement
## Important Product Clarification
The user comes from FL Studio and may say `piano roll`.
In this project that means:
- harmonic MIDI backbone
- long-form arrangement MIDI that carries harmony/melodic identity
- editable MIDI clips in Arrangement
It does **not** mean:
- force piano timbre everywhere
- spam piano loops
- replace the user library with generic piano sounds
The right interpretation is:
- use `HARMONY_*_MIDI` as the musical spine
- blend that spine with the user's library
- keep the project library-first-hybrid
- make the MIDI audible, useful and persistent throughout the song
## Current Problems To Solve
The current system has improved, but the open-project result still tends to show:
- too much geometric symmetry
- repeated section shapes with near-identical spacing
- too many silence islands
- a good 4-second loop followed by dead air
- harmonic MIDI backbone still too weak or too local
- sections differentiated by removal instead of transformation
- sound selection still too conservative or too repetitive
- aggressive snares sometimes damaging coherence
The specific snare to watch carefully is:
- `SS_RNBL_Me_Gustas_One_Shot_Snare.wav`
Do **not** hard-ban it blindly.
Instead:
- analyze when it is appropriate
- score it more selectively by section energy, density and surrounding sources
- stop it from dominating softer sections or smoother grooves
## Scope
This sprint has two parallel tracks that must meet in one validated result.
### Track A - MCP Capability
Strengthen the public MCP editing workflow for an already-open project.
The target is not theoretical wrappers.
The target is:
- inspect open project
- inspect clips/devices/parameters
- edit Arrangement
- edit MIDI in Arrangement
- edit device parameters by name
- use repair tools meaningfully on a real project
### Track B - Project Editing
Apply those tools to `song.als` and improve the real set.
Do not stop at “tools exist”.
Use them.
## Required Outcomes
### A. Open-project editing must be real
At least these MCP abilities must be validated live on the open project:
- inspect tracks
- inspect clips on a track
- inspect a specific clip
- inspect devices on a track
- inspect device parameters
- set a device parameter by name
- create or duplicate Arrangement material
- add MIDI notes in Arrangement
- retrieve enough project state to support editing decisions
If any of these are still analysis-only or fallback-only, say so explicitly.
Do not mark them complete unless they were exercised against the open Live project.
### B. Harmonic MIDI backbone must become real arrangement content
The harmonic MIDI backbone must:
- exist in Arrangement, not just Session
- span much more of the song, not only one local region
- be audible and useful for continuity
- help fill gaps where the arrangement currently drops out
- support the user library rather than replacing it
It is acceptable if the timbre is pluck/keys/pad/synth instead of literal piano.
It is not acceptable if the MIDI exists only as metadata or one hidden clip.
### C. Coherence must improve without becoming sterile
The edit pass must reduce:
- mirrored section pairs
- dead gaps between phrases
- silence islands
- same-source overuse
- section-to-section copy-paste feel
At the same time, it must preserve:
- clear structure
- strong recognizable motif
- coherent sound family choices
- continuity across drums, bass and harmony
Do not solve “repetition” by randomizing everything.
Do not solve “coherence” by making every section identical.
### D. Sound freedom with discipline
The system should gain a bit more freedom in sound choice, but inside a coherent frame.
That means:
- allow controlled variation of support layers
- allow section-aware alternates
- keep identity layers constrained
- avoid collapsing everything into 3-4 sounds
- avoid hard lock to one exact symmetric loop
## Mandatory Validation
Validation must include all of the following:
### 1. Code validation
- `python -m py_compile` on every changed Python file
- relevant tests for MCP/runtime/coherence
### 2. Live validation
Against the open Ableton project:
- `get_session_info()`
- `get_tracks()`
- `get_track_info(...)`
- the editing tools exercised for real
### 3. Project audit
Run project-facing audits before and after the edit pass, including:
- silence islands
- mirrored section pairs
- harmonic coverage / backbone status
- same-source dominance or reuse
- repeated clip overuse if available
### 4. Audible sanity
The report must state clearly:
- whether the harmonic MIDI is actually audible
- whether gaps were reduced
- whether the project still feels too symmetric
- whether the snare selectivity improved
## Constraints
- Do not generate a new song from scratch.
- Do not replace the project with a fresh template.
- Do not rely on Session-only material and then claim Arrangement success.
- Do not treat wrappers or helper functions as success.
- Do not add vocals.
- Do not force literal piano timbre just because the user said “piano roll”.
- Do not hard-ban `SS_RNBL_Me_Gustas_One_Shot_Snare.wav`; score it contextually.
- Do not overclaim if the edit tools still depend on fragile transport hacks.
- Do not mark complete if the live project still looks obviously symmetrical and full of dead gaps.
## Implementation Guidance
### For MCP capability work
Prioritize tools that matter for editing open projects:
- clip inspection
- device inspection
- parameter editing by name
- Arrangement MIDI editing
- duplication and continuity repair
- project audit tools that reflect real editing pain
If a tool remains inherently limited by the Live API, document the exact limit and the exact fallback.
### For project editing work
Use the project audit as the source of truth.
Then make targeted edits:
- extend harmonic continuity
- reduce dead air
- break mirrored copy-paste shapes
- vary support layers across sections without losing family identity
- tighten selection of snare/clap layers by context
Preferred musical strategy:
- strong recurring identity
- long-form harmonic support
- fewer abrupt disappearances
- section evolution by mutation, layering and phrasing
- support layers changing more than core identity layers
## Required Deliverables
The implementing swarm must produce:
1. code changes
2. a validation report:
- `docs/SPRINT_v0.1.41_VALIDATION_REPORT.md`
3. if needed, one or more exported JSON artifacts under `temp/`
4. explicit list of changed files
5. exact MCP calls used
6. before/after metric table
7. a short section titled `Remaining Risks`
## Report Format
The validation report must contain these sections:
1. `Summary`
2. `Files Changed`
3. `MCP Tools Validated Live`
4. `Project Edits Applied`
5. `Before/After Metrics`
6. `Snare Selectivity`
7. `Harmonic MIDI Backbone`
8. `What Is Still Weak`
9. `Remaining Risks`
## Failure Conditions
The sprint automatically fails if any of these happen:
- the work validates against a new generated song instead of `song.als`
- `HARMONY_*_MIDI` is still absent from Arrangement in a meaningful way
- the project still has large obvious silence islands with no explanation
- the report claims success but the set still looks geometrically mirrored
- snare handling is “fixed” by crude blacklist instead of contextual scoring
- Codex final verdict is not `pass`
## Recommended Ralph Routing
Use the Ralph defaults already configured locally:
- implementer: `zai_glm51`
- reviewers: `dashscope_qwen3coder_plus`, `dashscope_glm5`
- Codex master: enabled
## Operator Note
This task is intended for the 24/7 Ralph queue.
Suggested submit command:
```powershell
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Submit-RalphTask.ps1 `
-SourceFile .\docs\SPRINT_v0.1.41_NEXT_RALPH_OPEN_PROJECT_EDITING.md
```

View File

@@ -0,0 +1,9 @@
{
"id": "20260403-193730-20260403-193721-sprint-v0-1-41-ralph-swarm-open",
"title": "20260403-193721-sprint-v0-1-41-ralph-swarm-open-project-editing",
"state": "failed",
"finished_at": "2026-04-03T19:42:06.4487683-03:00",
"source": "C:\\ProgramData\\Ableton\\Live 12 Suite\\Resources\\MIDI Remote Scripts\\ralph\\tasks\\inbox\\20260403-193721-sprint-v0-1-41-ralph-swarm-open-project-editing",
"run_id": "",
"summary": "Start-RalphAutopilot.ps1 failed with exit code 1"
}

View File

@@ -0,0 +1,14 @@
The sprint is complete only if all of these are true:
1. the open project `song.als` was used as the validation target
2. MCP editing tools were validated live, not just compiled
3. the open project received a real edit pass
4. harmonic MIDI backbone exists in Arrangement and covers materially more of the song
5. silence islands and mirrored symmetry were measured before and after
6. the result is less empty and less repetitive without losing structure
7. sound selection logic for aggressive snares became more selective instead of a blind blacklist
8. all changed Python files compile
9. relevant tests pass
10. the report includes exact MCP calls used on the open project
11. the report includes exact before/after coherence metrics
12. Codex final verdict is `pass`

View File

@@ -0,0 +1,9 @@
# Context
List the canonical docs and code paths the worker must read first.
- active sprint doc
- current handoff
- relevant source files
- known evidence files

View File

@@ -0,0 +1,316 @@
# SPRINT v0.1.41 - Ralph Swarm - Open Project Editing and Coherence
## Goal
Use the Ralph swarm to work on the already-open Ableton project:
- `C:\Users\ren\Desktop\song Project\song.als`
This is not a new-song generation sprint.
This sprint is about:
1. giving the MCP more real editing power over open projects
2. using those tools on the current open project
3. improving coherence, continuity and musical identity
4. reducing symmetry, repeated blocks and empty gaps
5. treating harmonic MIDI as a real backbone across the full arrangement
## Important Product Clarification
The user comes from FL Studio and may say `piano roll`.
In this project that means:
- harmonic MIDI backbone
- long-form arrangement MIDI that carries harmony/melodic identity
- editable MIDI clips in Arrangement
It does **not** mean:
- force piano timbre everywhere
- spam piano loops
- replace the user library with generic piano sounds
The right interpretation is:
- use `HARMONY_*_MIDI` as the musical spine
- blend that spine with the user's library
- keep the project library-first-hybrid
- make the MIDI audible, useful and persistent throughout the song
## Current Problems To Solve
The current system has improved, but the open-project result still tends to show:
- too much geometric symmetry
- repeated section shapes with near-identical spacing
- too many silence islands
- a good 4-second loop followed by dead air
- harmonic MIDI backbone still too weak or too local
- sections differentiated by removal instead of transformation
- sound selection still too conservative or too repetitive
- aggressive snares sometimes damaging coherence
The specific snare to watch carefully is:
- `SS_RNBL_Me_Gustas_One_Shot_Snare.wav`
Do **not** hard-ban it blindly.
Instead:
- analyze when it is appropriate
- score it more selectively by section energy, density and surrounding sources
- stop it from dominating softer sections or smoother grooves
## Scope
This sprint has two parallel tracks that must meet in one validated result.
### Track A - MCP Capability
Strengthen the public MCP editing workflow for an already-open project.
The target is not theoretical wrappers.
The target is:
- inspect open project
- inspect clips/devices/parameters
- edit Arrangement
- edit MIDI in Arrangement
- edit device parameters by name
- use repair tools meaningfully on a real project
### Track B - Project Editing
Apply those tools to `song.als` and improve the real set.
Do not stop at “tools exist”.
Use them.
## Required Outcomes
### A. Open-project editing must be real
At least these MCP abilities must be validated live on the open project:
- inspect tracks
- inspect clips on a track
- inspect a specific clip
- inspect devices on a track
- inspect device parameters
- set a device parameter by name
- create or duplicate Arrangement material
- add MIDI notes in Arrangement
- retrieve enough project state to support editing decisions
If any of these are still analysis-only or fallback-only, say so explicitly.
Do not mark them complete unless they were exercised against the open Live project.
### B. Harmonic MIDI backbone must become real arrangement content
The harmonic MIDI backbone must:
- exist in Arrangement, not just Session
- span much more of the song, not only one local region
- be audible and useful for continuity
- help fill gaps where the arrangement currently drops out
- support the user library rather than replacing it
It is acceptable if the timbre is pluck/keys/pad/synth instead of literal piano.
It is not acceptable if the MIDI exists only as metadata or one hidden clip.
### C. Coherence must improve without becoming sterile
The edit pass must reduce:
- mirrored section pairs
- dead gaps between phrases
- silence islands
- same-source overuse
- section-to-section copy-paste feel
At the same time, it must preserve:
- clear structure
- strong recognizable motif
- coherent sound family choices
- continuity across drums, bass and harmony
Do not solve “repetition” by randomizing everything.
Do not solve “coherence” by making every section identical.
### D. Sound freedom with discipline
The system should gain a bit more freedom in sound choice, but inside a coherent frame.
That means:
- allow controlled variation of support layers
- allow section-aware alternates
- keep identity layers constrained
- avoid collapsing everything into 3-4 sounds
- avoid hard lock to one exact symmetric loop
## Acceptance Criteria
The sprint is complete only if all of these are true:
1. the open project `song.als` was used as the validation target
2. MCP editing tools were validated live, not just compiled
3. the open project received a real edit pass
4. harmonic MIDI backbone exists in Arrangement and covers materially more of the song
5. silence islands and mirrored symmetry were measured before and after
6. the result is less empty and less repetitive without losing structure
7. sound selection logic for aggressive snares became more selective instead of a blind blacklist
8. all changed Python files compile
9. relevant tests pass
10. the report includes exact MCP calls used on the open project
11. the report includes exact before/after coherence metrics
12. Codex final verdict is `pass`
## Mandatory Validation
Validation must include all of the following:
### 1. Code validation
- `python -m py_compile` on every changed Python file
- relevant tests for MCP/runtime/coherence
### 2. Live validation
Against the open Ableton project:
- `get_session_info()`
- `get_tracks()`
- `get_track_info(...)`
- the editing tools exercised for real
### 3. Project audit
Run project-facing audits before and after the edit pass, including:
- silence islands
- mirrored section pairs
- harmonic coverage / backbone status
- same-source dominance or reuse
- repeated clip overuse if available
### 4. Audible sanity
The report must state clearly:
- whether the harmonic MIDI is actually audible
- whether gaps were reduced
- whether the project still feels too symmetric
- whether the snare selectivity improved
## Constraints
- Do not generate a new song from scratch.
- Do not replace the project with a fresh template.
- Do not rely on Session-only material and then claim Arrangement success.
- Do not treat wrappers or helper functions as success.
- Do not add vocals.
- Do not force literal piano timbre just because the user said “piano roll”.
- Do not hard-ban `SS_RNBL_Me_Gustas_One_Shot_Snare.wav`; score it contextually.
- Do not overclaim if the edit tools still depend on fragile transport hacks.
- Do not mark complete if the live project still looks obviously symmetrical and full of dead gaps.
## Implementation Guidance
### For MCP capability work
Prioritize tools that matter for editing open projects:
- clip inspection
- device inspection
- parameter editing by name
- Arrangement MIDI editing
- duplication and continuity repair
- project audit tools that reflect real editing pain
If a tool remains inherently limited by the Live API, document the exact limit and the exact fallback.
### For project editing work
Use the project audit as the source of truth.
Then make targeted edits:
- extend harmonic continuity
- reduce dead air
- break mirrored copy-paste shapes
- vary support layers across sections without losing family identity
- tighten selection of snare/clap layers by context
Preferred musical strategy:
- strong recurring identity
- long-form harmonic support
- fewer abrupt disappearances
- section evolution by mutation, layering and phrasing
- support layers changing more than core identity layers
## Required Deliverables
The implementing swarm must produce:
1. code changes
2. a validation report:
- `docs/SPRINT_v0.1.41_VALIDATION_REPORT.md`
3. if needed, one or more exported JSON artifacts under `temp/`
4. explicit list of changed files
5. exact MCP calls used
6. before/after metric table
7. a short section titled `Remaining Risks`
## Report Format
The validation report must contain these sections:
1. `Summary`
2. `Files Changed`
3. `MCP Tools Validated Live`
4. `Project Edits Applied`
5. `Before/After Metrics`
6. `Snare Selectivity`
7. `Harmonic MIDI Backbone`
8. `What Is Still Weak`
9. `Remaining Risks`
## Failure Conditions
The sprint automatically fails if any of these happen:
- the work validates against a new generated song instead of `song.als`
- `HARMONY_*_MIDI` is still absent from Arrangement in a meaningful way
- the project still has large obvious silence islands with no explanation
- the report claims success but the set still looks geometrically mirrored
- snare handling is “fixed” by crude blacklist instead of contextual scoring
- Codex final verdict is not `pass`
## Recommended Ralph Routing
Use the Ralph defaults already configured locally:
- implementer: `zai_glm51`
- reviewers: `dashscope_qwen3coder_plus`, `dashscope_glm5`
- Codex master: enabled
## Operator Note
This task is intended for the 24/7 Ralph queue.
Suggested submit command:
```powershell
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Submit-RalphTask.ps1 `
-SourceFile .\docs\SPRINT_v0.1.41_NEXT_RALPH_OPEN_PROJECT_EDITING.md
```

View File

@@ -0,0 +1,299 @@
# SPRINT v0.1.41 - Ralph Swarm - Open Project Editing and Coherence
## Goal
Use the Ralph swarm to work on the already-open Ableton project:
- `C:\Users\ren\Desktop\song Project\song.als`
This is not a new-song generation sprint.
This sprint is about:
1. giving the MCP more real editing power over open projects
2. using those tools on the current open project
3. improving coherence, continuity and musical identity
4. reducing symmetry, repeated blocks and empty gaps
5. treating harmonic MIDI as a real backbone across the full arrangement
## Important Product Clarification
The user comes from FL Studio and may say `piano roll`.
In this project that means:
- harmonic MIDI backbone
- long-form arrangement MIDI that carries harmony/melodic identity
- editable MIDI clips in Arrangement
It does **not** mean:
- force piano timbre everywhere
- spam piano loops
- replace the user library with generic piano sounds
The right interpretation is:
- use `HARMONY_*_MIDI` as the musical spine
- blend that spine with the user's library
- keep the project library-first-hybrid
- make the MIDI audible, useful and persistent throughout the song
## Current Problems To Solve
The current system has improved, but the open-project result still tends to show:
- too much geometric symmetry
- repeated section shapes with near-identical spacing
- too many silence islands
- a good 4-second loop followed by dead air
- harmonic MIDI backbone still too weak or too local
- sections differentiated by removal instead of transformation
- sound selection still too conservative or too repetitive
- aggressive snares sometimes damaging coherence
The specific snare to watch carefully is:
- `SS_RNBL_Me_Gustas_One_Shot_Snare.wav`
Do **not** hard-ban it blindly.
Instead:
- analyze when it is appropriate
- score it more selectively by section energy, density and surrounding sources
- stop it from dominating softer sections or smoother grooves
## Scope
This sprint has two parallel tracks that must meet in one validated result.
### Track A - MCP Capability
Strengthen the public MCP editing workflow for an already-open project.
The target is not theoretical wrappers.
The target is:
- inspect open project
- inspect clips/devices/parameters
- edit Arrangement
- edit MIDI in Arrangement
- edit device parameters by name
- use repair tools meaningfully on a real project
### Track B - Project Editing
Apply those tools to `song.als` and improve the real set.
Do not stop at “tools exist”.
Use them.
## Required Outcomes
### A. Open-project editing must be real
At least these MCP abilities must be validated live on the open project:
- inspect tracks
- inspect clips on a track
- inspect a specific clip
- inspect devices on a track
- inspect device parameters
- set a device parameter by name
- create or duplicate Arrangement material
- add MIDI notes in Arrangement
- retrieve enough project state to support editing decisions
If any of these are still analysis-only or fallback-only, say so explicitly.
Do not mark them complete unless they were exercised against the open Live project.
### B. Harmonic MIDI backbone must become real arrangement content
The harmonic MIDI backbone must:
- exist in Arrangement, not just Session
- span much more of the song, not only one local region
- be audible and useful for continuity
- help fill gaps where the arrangement currently drops out
- support the user library rather than replacing it
It is acceptable if the timbre is pluck/keys/pad/synth instead of literal piano.
It is not acceptable if the MIDI exists only as metadata or one hidden clip.
### C. Coherence must improve without becoming sterile
The edit pass must reduce:
- mirrored section pairs
- dead gaps between phrases
- silence islands
- same-source overuse
- section-to-section copy-paste feel
At the same time, it must preserve:
- clear structure
- strong recognizable motif
- coherent sound family choices
- continuity across drums, bass and harmony
Do not solve “repetition” by randomizing everything.
Do not solve “coherence” by making every section identical.
### D. Sound freedom with discipline
The system should gain a bit more freedom in sound choice, but inside a coherent frame.
That means:
- allow controlled variation of support layers
- allow section-aware alternates
- keep identity layers constrained
- avoid collapsing everything into 3-4 sounds
- avoid hard lock to one exact symmetric loop
## Mandatory Validation
Validation must include all of the following:
### 1. Code validation
- `python -m py_compile` on every changed Python file
- relevant tests for MCP/runtime/coherence
### 2. Live validation
Against the open Ableton project:
- `get_session_info()`
- `get_tracks()`
- `get_track_info(...)`
- the editing tools exercised for real
### 3. Project audit
Run project-facing audits before and after the edit pass, including:
- silence islands
- mirrored section pairs
- harmonic coverage / backbone status
- same-source dominance or reuse
- repeated clip overuse if available
### 4. Audible sanity
The report must state clearly:
- whether the harmonic MIDI is actually audible
- whether gaps were reduced
- whether the project still feels too symmetric
- whether the snare selectivity improved
## Constraints
- Do not generate a new song from scratch.
- Do not replace the project with a fresh template.
- Do not rely on Session-only material and then claim Arrangement success.
- Do not treat wrappers or helper functions as success.
- Do not add vocals.
- Do not force literal piano timbre just because the user said “piano roll”.
- Do not hard-ban `SS_RNBL_Me_Gustas_One_Shot_Snare.wav`; score it contextually.
- Do not overclaim if the edit tools still depend on fragile transport hacks.
- Do not mark complete if the live project still looks obviously symmetrical and full of dead gaps.
## Implementation Guidance
### For MCP capability work
Prioritize tools that matter for editing open projects:
- clip inspection
- device inspection
- parameter editing by name
- Arrangement MIDI editing
- duplication and continuity repair
- project audit tools that reflect real editing pain
If a tool remains inherently limited by the Live API, document the exact limit and the exact fallback.
### For project editing work
Use the project audit as the source of truth.
Then make targeted edits:
- extend harmonic continuity
- reduce dead air
- break mirrored copy-paste shapes
- vary support layers across sections without losing family identity
- tighten selection of snare/clap layers by context
Preferred musical strategy:
- strong recurring identity
- long-form harmonic support
- fewer abrupt disappearances
- section evolution by mutation, layering and phrasing
- support layers changing more than core identity layers
## Required Deliverables
The implementing swarm must produce:
1. code changes
2. a validation report:
- `docs/SPRINT_v0.1.41_VALIDATION_REPORT.md`
3. if needed, one or more exported JSON artifacts under `temp/`
4. explicit list of changed files
5. exact MCP calls used
6. before/after metric table
7. a short section titled `Remaining Risks`
## Report Format
The validation report must contain these sections:
1. `Summary`
2. `Files Changed`
3. `MCP Tools Validated Live`
4. `Project Edits Applied`
5. `Before/After Metrics`
6. `Snare Selectivity`
7. `Harmonic MIDI Backbone`
8. `What Is Still Weak`
9. `Remaining Risks`
## Failure Conditions
The sprint automatically fails if any of these happen:
- the work validates against a new generated song instead of `song.als`
- `HARMONY_*_MIDI` is still absent from Arrangement in a meaningful way
- the project still has large obvious silence islands with no explanation
- the report claims success but the set still looks geometrically mirrored
- snare handling is “fixed” by crude blacklist instead of contextual scoring
- Codex final verdict is not `pass`
## Recommended Ralph Routing
Use the Ralph defaults already configured locally:
- implementer: `zai_glm51`
- reviewers: `dashscope_qwen3coder_plus`, `dashscope_glm5`
- Codex master: enabled
## Operator Note
This task is intended for the 24/7 Ralph queue.
Suggested submit command:
```powershell
powershell -ExecutionPolicy Bypass -File .\ralph\scripts\Submit-RalphTask.ps1 `
-SourceFile .\docs\SPRINT_v0.1.41_NEXT_RALPH_OPEN_PROJECT_EDITING.md
```

View File

@@ -0,0 +1,9 @@
{
"id": "20260403-195932-20260403-195932-sprint-v0-1-41-ralph-swarm-open",
"title": "20260403-195932-sprint-v0-1-41-ralph-swarm-open-project-editing",
"state": "failed",
"finished_at": "2026-04-03T20:22:44.1704526-03:00",
"source": "C:\\ProgramData\\Ableton\\Live 12 Suite\\Resources\\MIDI Remote Scripts\\ralph\\tasks\\inbox\\20260403-195932-sprint-v0-1-41-ralph-swarm-open-project-editing",
"run_id": "",
"summary": "Start-RalphAutopilot.ps1 failed with exit code 1"
}

View File

@@ -0,0 +1,22 @@
This sprint passes only if all of the following are true:
1. The live validation target is the already-open song.als project.
2. Exact MCP calls and exact raw results are recorded for the validated live tool sequence.
3. At least one real Arrangement edit is applied through the MCP path on the open project.
4. At least one real Arrangement MIDI operation is applied on the open project, or the report proves the exact runtime limitation with raw evidence.
5. Harmonic MIDI backbone exists in Arrangement as meaningful content over materially more of the song than before.
6. Before and after project-audit metrics are captured from the real open project with exact values.
7. The after state shows at least one concrete improvement in coherence metrics, such as fewer silence islands, fewer mirrored pairs, stronger harmonic coverage, or reduced repeated-source dominance.
8. Snare selectivity is validated in the real selection path, not only described from source code.
9. All changed Python files compile.
10. Relevant tests for touched MCP/runtime/coherence code pass.
11. The validation report is strict and honest about what was and was not validated live.
12. Codex can reasonably return pass from repository evidence alone.
Automatic fail conditions:
- validation remains code-only
- no exact MCP call log exists
- no before/after metrics exist
- no live edit was applied to song.als
- the run validates against a new generated song or different session
- the report claims completion without runtime evidence

View File

@@ -0,0 +1,28 @@
Read these first:
- previous failed run directory:
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-195932-queue
- previous summary:
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-195932-queue\SUMMARY.md
- previous reviews:
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-195932-queue\reviews\codex_master_pre_fix.json
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-195932-queue\reviews\opencode_qwen3coder_plus.json
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-195932-queue\reviews\opencode_glm47.json
- previous validation report:
- docs/SPRINT_v0.1.41_VALIDATION_REPORT.md
Primary source files:
- AbletonMCP_AI/MCP_Server/server.py
- AbletonMCP_AI/MCP_Server/sample_selector.py
- abletonmcp_init.py
- AbletonMCP_AI/abletonmcp_runtime.py
Execution priority for this sprint:
1. prove live target and MCP connectivity
2. validate existing tools live
3. minimally fix only the runtime blockers that prevent validation
4. apply one bounded real edit pass
5. capture before/after evidence
6. report strictly from repository truth
Do not overclaim. This sprint exists to turn unvalidated open-project editing infrastructure into demonstrated live behavior on the real Ableton session.

View File

@@ -0,0 +1,219 @@
# SPRINT v0.1.42 - Live Proof of Open-Project Editing
## Goal
Validate and use the existing MCP editing path on the already-open Ableton project:
- C:\Users\ren\Desktop\song Project\song.als
This sprint is not a generation sprint and not a wrapper-expansion sprint.
It is a runtime-proof sprint:
- prove the MCP can inspect the real open project
- prove the MCP can apply real Arrangement edits on that project
- prove the edits improve coherence in measurable ways
- document exact evidence or fail explicitly
## Why This Task Exists
The previous run failed.
Repository truth from the failed run:
- infrastructure was added in AbletonMCP_AI/MCP_Server/server.py
- contextual snare scoring was added in AbletonMCP_AI/MCP_Server/sample_selector.py
- docs/SPRINT_v0.1.41_VALIDATION_REPORT.md was corrected to an honest fail
- no live MCP calls were exercised against song.als
- no real edit pass was applied
- no before/after coherence metrics were captured
- no proof exists that the new editing tools work through the real Ableton runtime path
The highest-signal blocker is now obvious:
- stop adding unvalidated infrastructure
- prove runtime behavior on the open project
- if runtime behavior is blocked, isolate the exact blocker with raw evidence
## Required Work
1. Prove the live target is the real open project.
- Connect through the MCP to the active Ableton session.
- Capture enough session evidence to prove the target is the already-open song.als session and not a generated set or blank template.
- Save raw outputs under temp/.
2. Validate the existing MCP tool path live before adding more tools.
- Exercise these tools against the open project and record exact calls plus raw outputs:
- get_session_info()
- get_tracks()
- get_track_info(...)
- get_clips(...)
- get_clip_info(...)
- get_devices(...)
- get_device_parameters(...)
- set_device_parameter_by_name(...)
- one arrangement creation or duplication path
- one arrangement MIDI note insertion path
- For each tool, classify it as:
- live validated
- failed live
- blocked by backend/runtime limitation
- Do not mark a tool complete just because it compiles.
3. If a live tool path fails, fix only the minimal blocker.
- Use the existing code added in v0.1.41 as the starting point.
- If a backend handler or runtime path is missing, add the smallest fix needed in the real runtime path.
- Re-run the exact same MCP call after the fix and save the before/after evidence.
- Do not add unrelated new feature surface.
4. Audit the open project before editing.
- Run project-facing audits on the real song.als session.
- Capture exact before metrics for:
- silence islands
- mirrored section pairs
- harmonic coverage / harmonic backbone status
- same-source dominance or repeated-source overuse
- repeated clip overuse if available
- Save raw outputs under temp/.
- Use these audits to choose the edit targets.
5. Apply a bounded real edit pass on song.als.
- Use the validated MCP editing tools on the real open project.
- The edit pass must be small, targeted, and measurable.
- Prioritize:
- extending or repairing harmonic MIDI backbone in Arrangement
- reducing dead gaps
- breaking at least one mirrored or obviously repeated arrangement shape
- improving continuity without destroying identity
- The harmonic MIDI backbone must become real Arrangement content intended to be audible.
- It is acceptable to use keys, pluck, pad, or synth timbre.
- It is not acceptable to leave the backbone as metadata, a hidden clip, or an empty placeholder.
6. Validate snare selectivity in the real path.
- Do not hard-ban SS_RNBL_Me_Gustas_One_Shot_Snare.wav.
- Prove whether the new contextual snare scoring actually affects selection in real use.
- If the logic exists but is not wired into the live path, wire the minimal real path and validate it.
- Record exact evidence showing whether lower-energy sections are treated more conservatively than higher-energy sections.
7. Re-audit after editing.
- Run the same project-facing audits again.
- Save raw after-state outputs under temp/.
- Produce an exact before/after table using measured values, not estimates.
- State clearly what improved, what stayed flat, and what regressed.
8. Keep the scope disciplined.
- Touch only the files directly required to validate or minimally fix the runtime editing path.
- Likely files:
- AbletonMCP_AI/MCP_Server/server.py
- AbletonMCP_AI/MCP_Server/sample_selector.py
- abletonmcp_init.py
- AbletonMCP_AI/abletonmcp_runtime.py
- docs/SPRINT_v0.1.42_VALIDATION_REPORT.md
- Do not add broad new capability areas unless they are the direct blocker to a required live validation step.
9. Fail fast if the runtime is not reachable.
- If Ableton, the control surface, or the MCP connection is unavailable, do not continue building infrastructure.
- Capture the exact failing step and raw evidence.
- Mark the sprint failed with specific blocker evidence.
- A code-only partial success is not acceptable for this sprint.
## Acceptance Criteria
This sprint passes only if all of the following are true:
1. The live validation target is the already-open song.als project.
2. Exact MCP calls and exact raw results are recorded for the validated live tool sequence.
3. At least one real Arrangement edit is applied through the MCP path on the open project.
4. At least one real Arrangement MIDI operation is applied on the open project, or the report proves the exact runtime limitation with raw evidence.
5. Harmonic MIDI backbone exists in Arrangement as meaningful content over materially more of the song than before.
6. Before and after project-audit metrics are captured from the real open project with exact values.
7. The after state shows at least one concrete improvement in coherence metrics, such as fewer silence islands, fewer mirrored pairs, stronger harmonic coverage, or reduced repeated-source dominance.
8. Snare selectivity is validated in the real selection path, not only described from source code.
9. All changed Python files compile.
10. Relevant tests for touched MCP/runtime/coherence code pass.
11. The validation report is strict and honest about what was and was not validated live.
12. Codex can reasonably return pass from repository evidence alone.
Automatic fail conditions:
- validation remains code-only
- no exact MCP call log exists
- no before/after metrics exist
- no live edit was applied to song.als
- the run validates against a new generated song or different session
- the report claims completion without runtime evidence
## Validation
Produce all of the following artifacts:
1. docs/SPRINT_v0.1.42_VALIDATION_REPORT.md
Required sections:
- Summary
- Files Changed
- Live Target Proof
- MCP Tools Validated Live
- Project Audits Before
- Project Edits Applied
- Project Audits After
- Before/After Metrics
- Snare Selectivity
- Harmonic MIDI Backbone
- What Is Still Weak
- Remaining Risks
2. temp/v04142_live_target_proof.json
- raw evidence proving the active Ableton session is the intended open project
3. temp/v04142_mcp_calls.jsonl
- one JSON line per MCP call
- include tool name, arguments, success or failure, and raw result or raw error
4. temp/v04142_before_audit.json
- raw before-state audit outputs
5. temp/v04142_after_audit.json
- raw after-state audit outputs
6. temp/v04142_edit_actions.json
- exact live edits attempted and whether each succeeded
7. If blocked, replace missing success artifacts with:
- temp/v04142_blocker_evidence.json
- include the exact failing step, exact raw response, and why the sprint could not proceed
Required validation actions:
- python -m py_compile on every changed Python file
- relevant tests for touched runtime/MCP/coherence code
- live MCP calls against the open project
- before and after audits on the same open project session
## Context
Read these first:
- previous failed run directory:
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-195932-queue
- previous summary:
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-195932-queue\SUMMARY.md
- previous reviews:
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-195932-queue\reviews\codex_master_pre_fix.json
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-195932-queue\reviews\opencode_qwen3coder_plus.json
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-195932-queue\reviews\opencode_glm47.json
- previous validation report:
- docs/SPRINT_v0.1.41_VALIDATION_REPORT.md
Primary source files:
- AbletonMCP_AI/MCP_Server/server.py
- AbletonMCP_AI/MCP_Server/sample_selector.py
- abletonmcp_init.py
- AbletonMCP_AI/abletonmcp_runtime.py
Execution priority for this sprint:
1. prove live target and MCP connectivity
2. validate existing tools live
3. minimally fix only the runtime blockers that prevent validation
4. apply one bounded real edit pass
5. capture before/after evidence
6. report strictly from repository truth
Do not overclaim. This sprint exists to turn unvalidated open-project editing infrastructure into demonstrated live behavior on the real Ableton session.

View File

@@ -0,0 +1,163 @@
# SPRINT v0.1.42 - Live Proof of Open-Project Editing
## Goal
Validate and use the existing MCP editing path on the already-open Ableton project:
- C:\Users\ren\Desktop\song Project\song.als
This sprint is not a generation sprint and not a wrapper-expansion sprint.
It is a runtime-proof sprint:
- prove the MCP can inspect the real open project
- prove the MCP can apply real Arrangement edits on that project
- prove the edits improve coherence in measurable ways
- document exact evidence or fail explicitly
## Why This Task Exists
The previous run failed.
Repository truth from the failed run:
- infrastructure was added in AbletonMCP_AI/MCP_Server/server.py
- contextual snare scoring was added in AbletonMCP_AI/MCP_Server/sample_selector.py
- docs/SPRINT_v0.1.41_VALIDATION_REPORT.md was corrected to an honest fail
- no live MCP calls were exercised against song.als
- no real edit pass was applied
- no before/after coherence metrics were captured
- no proof exists that the new editing tools work through the real Ableton runtime path
The highest-signal blocker is now obvious:
- stop adding unvalidated infrastructure
- prove runtime behavior on the open project
- if runtime behavior is blocked, isolate the exact blocker with raw evidence
## Required Work
1. Prove the live target is the real open project.
- Connect through the MCP to the active Ableton session.
- Capture enough session evidence to prove the target is the already-open song.als session and not a generated set or blank template.
- Save raw outputs under temp/.
2. Validate the existing MCP tool path live before adding more tools.
- Exercise these tools against the open project and record exact calls plus raw outputs:
- get_session_info()
- get_tracks()
- get_track_info(...)
- get_clips(...)
- get_clip_info(...)
- get_devices(...)
- get_device_parameters(...)
- set_device_parameter_by_name(...)
- one arrangement creation or duplication path
- one arrangement MIDI note insertion path
- For each tool, classify it as:
- live validated
- failed live
- blocked by backend/runtime limitation
- Do not mark a tool complete just because it compiles.
3. If a live tool path fails, fix only the minimal blocker.
- Use the existing code added in v0.1.41 as the starting point.
- If a backend handler or runtime path is missing, add the smallest fix needed in the real runtime path.
- Re-run the exact same MCP call after the fix and save the before/after evidence.
- Do not add unrelated new feature surface.
4. Audit the open project before editing.
- Run project-facing audits on the real song.als session.
- Capture exact before metrics for:
- silence islands
- mirrored section pairs
- harmonic coverage / harmonic backbone status
- same-source dominance or repeated-source overuse
- repeated clip overuse if available
- Save raw outputs under temp/.
- Use these audits to choose the edit targets.
5. Apply a bounded real edit pass on song.als.
- Use the validated MCP editing tools on the real open project.
- The edit pass must be small, targeted, and measurable.
- Prioritize:
- extending or repairing harmonic MIDI backbone in Arrangement
- reducing dead gaps
- breaking at least one mirrored or obviously repeated arrangement shape
- improving continuity without destroying identity
- The harmonic MIDI backbone must become real Arrangement content intended to be audible.
- It is acceptable to use keys, pluck, pad, or synth timbre.
- It is not acceptable to leave the backbone as metadata, a hidden clip, or an empty placeholder.
6. Validate snare selectivity in the real path.
- Do not hard-ban SS_RNBL_Me_Gustas_One_Shot_Snare.wav.
- Prove whether the new contextual snare scoring actually affects selection in real use.
- If the logic exists but is not wired into the live path, wire the minimal real path and validate it.
- Record exact evidence showing whether lower-energy sections are treated more conservatively than higher-energy sections.
7. Re-audit after editing.
- Run the same project-facing audits again.
- Save raw after-state outputs under temp/.
- Produce an exact before/after table using measured values, not estimates.
- State clearly what improved, what stayed flat, and what regressed.
8. Keep the scope disciplined.
- Touch only the files directly required to validate or minimally fix the runtime editing path.
- Likely files:
- AbletonMCP_AI/MCP_Server/server.py
- AbletonMCP_AI/MCP_Server/sample_selector.py
- abletonmcp_init.py
- AbletonMCP_AI/abletonmcp_runtime.py
- docs/SPRINT_v0.1.42_VALIDATION_REPORT.md
- Do not add broad new capability areas unless they are the direct blocker to a required live validation step.
9. Fail fast if the runtime is not reachable.
- If Ableton, the control surface, or the MCP connection is unavailable, do not continue building infrastructure.
- Capture the exact failing step and raw evidence.
- Mark the sprint failed with specific blocker evidence.
- A code-only partial success is not acceptable for this sprint.
## Validation
Produce all of the following artifacts:
1. docs/SPRINT_v0.1.42_VALIDATION_REPORT.md
Required sections:
- Summary
- Files Changed
- Live Target Proof
- MCP Tools Validated Live
- Project Audits Before
- Project Edits Applied
- Project Audits After
- Before/After Metrics
- Snare Selectivity
- Harmonic MIDI Backbone
- What Is Still Weak
- Remaining Risks
2. temp/v04142_live_target_proof.json
- raw evidence proving the active Ableton session is the intended open project
3. temp/v04142_mcp_calls.jsonl
- one JSON line per MCP call
- include tool name, arguments, success or failure, and raw result or raw error
4. temp/v04142_before_audit.json
- raw before-state audit outputs
5. temp/v04142_after_audit.json
- raw after-state audit outputs
6. temp/v04142_edit_actions.json
- exact live edits attempted and whether each succeeded
7. If blocked, replace missing success artifacts with:
- temp/v04142_blocker_evidence.json
- include the exact failing step, exact raw response, and why the sprint could not proceed
Required validation actions:
- python -m py_compile on every changed Python file
- relevant tests for touched runtime/MCP/coherence code
- live MCP calls against the open project
- before and after audits on the same open project session

View File

@@ -0,0 +1,9 @@
{
"id": "20260403-203814-20260403-203809-sprint-v0-1-42-live-proof-of-ope",
"title": "20260403-203809-sprint-v0-1-42-live-proof-of-open-project-editin",
"state": "failed",
"finished_at": "2026-04-03T21:14:24.1239242-03:00",
"source": "C:\\ProgramData\\Ableton\\Live 12 Suite\\Resources\\MIDI Remote Scripts\\ralph\\tasks\\inbox\\20260403-203809-sprint-v0-1-42-live-proof-of-open-project-editin",
"run_id": "",
"summary": "Start-RalphAutopilot.ps1 failed with exit code 1"
}

View File

@@ -0,0 +1,26 @@
This sprint passes only if all of the following are true:
1. The live target is the already-open song.als session.
2. The run makes at least one real source-code change in the runtime or selection path, unless an already-existing code path is conclusively shown to be the canonical supported fallback and is validated end-to-end live.
3. At least one real Arrangement content edit is applied on the open project to improve harmonic continuity or fill a real weak span.
4. The backbone goal is met either by:
- meaningful Arrangement MIDI backbone content added live
- or a documented and validated supported fallback path that adds backbone-like Arrangement content when MIDI insertion is blocked
5. At least one coherence metric in the saved before/after evidence improves materially.
6. The run validates the previously missing live tool coverage:
- get_tracks()
- get_device_parameters(...)
- set_device_parameter_by_name(...)
7. Snare selectivity is validated through a real runtime path across at least two section contexts.
8. All changed Python files compile.
9. Relevant tests for touched code pass.
10. The validation report contains exact raw evidence references and does not overclaim.
11. Codex can reasonably return pass from repository evidence alone.
Automatic fail conditions:
- no source-code or real wiring change is made while blockers remain
- only property edits are applied again
- backbone remains absent and no validated fallback is delivered
- all saved coherence metrics remain flat again
- snare selectivity is still argued from inference instead of runtime evidence
- the report claims success without a measurable improvement

View File

@@ -0,0 +1,34 @@
Read these first:
- previous run directory:
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-203815-queue
- previous summary:
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-203815-queue\SUMMARY.md
- previous reviews:
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-203815-queue\reviews\codex_master_pre_fix.json
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-203815-queue\reviews\opencode_qwen3coder_plus.json
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-203815-queue\reviews\opencode_glm47.json
- previous validation report:
- docs/SPRINT_v0.1.42_VALIDATION_REPORT.md
Most important evidence files from the previous run:
- temp/v04142_mcp_calls_final.jsonl
- temp/v04142_comprehensive_validation.json
- temp/v04142_audio_pattern_results.json
- temp/v04142_edit_actions.json
Primary source files:
- AbletonMCP_AI/MCP_Server/server.py
- abletonmcp_init.py
- AbletonMCP_AI/abletonmcp_runtime.py
- AbletonMCP_AI/MCP_Server/sample_selector.py
Execution priority for this sprint:
1. preserve the proven live connection and working arrangement-audio path
2. make the blocked backbone path succeed, or formalize a supported fallback in code
3. validate the previously missing live MCP tools
4. deliver one measurable coherence improvement
5. validate snare selectivity in a real runtime path
6. report only what repository evidence proves
Do not overclaim. The previous run proved that live editing is partially possible. This sprint exists to turn that partial proof into a repeatable, code-backed, measurable project improvement.

View File

@@ -0,0 +1,238 @@
# SPRINT v0.1.43 - Unlock Backbone Editing and Measurable Coherence Gains
## Goal
Use the live MCP connection on the already-open Ableton project:
- C:\Users\ren\Desktop\song Project\song.als
to close the remaining blocker from v0.1.42:
- convert the currently proven live inspection and audio-pattern editing path into a path that produces a meaningful harmonic backbone improvement and at least one measurable coherence improvement
This sprint is not about proving connectivity again in isolation.
This sprint is about:
- fixing or formalizing the blocked backbone-edit path
- using that path on the live project
- producing measurable before/after improvement
- validating snare selectivity in a real runtime path
## Why This Task Exists
Repository truth from v0.1.42:
- the run did reach the correct open project
- real MCP-driven Arrangement audio edits succeeded through `create_arrangement_audio_pattern`
- no source-code fix was made
- the harmonic MIDI backbone requirement was still not met
- key coherence metrics did not materially improve
- snare selectivity was still inferential rather than proven in a real selection path
High-signal truths from the run artifacts:
- `temp/v04142_mcp_calls_final.jsonl` proves `create_arrangement_audio_pattern` works live
- `temp/v04142_comprehensive_validation.json` shows 3 arrangement audio-pattern edits succeeded
- the same artifact shows mirrored pairs stayed at 100 and clip overuse stayed high
- `docs/SPRINT_v0.1.42_VALIDATION_REPORT.md` admits MIDI note insertion is still blocked
- the worktree had no source-code diff, so the blocker was diagnosed but not fixed
The next step is therefore not “more validation.”
It is:
- implement the smallest real code fix or formal runtime fallback needed to make backbone extension and coherence improvement repeatable
- then prove it on song.als with exact metrics
## Required Work
1. Start from the live path that actually worked in v0.1.42.
- Reuse the live MCP connection approach that already validated the real open `song.als` session.
- Reuse the proven `create_arrangement_audio_pattern` path if MIDI editing remains blocked.
- Do not regress the working runtime path.
2. Resolve the backbone-edit blocker at code level.
- You must make a real code change in the runtime path this sprint unless the existing code already supports a better fallback and only wiring is missing.
- Priority order:
1. make a meaningful Arrangement MIDI backbone edit succeed
2. if that is genuinely blocked by the Live API, implement and validate a formal fallback path that creates backbone-like Arrangement content through a supported method
- A valid fallback is not random content.
- A valid fallback must:
- be intentional
- extend harmonic continuity
- be audibly useful
- be documented as the canonical path when MIDI insertion is blocked
3. Prove the fallback or fix is real on the live project.
- Apply at least one backbone-oriented Arrangement edit that increases continuity in a musically relevant span.
- The edit must target a real gap, weak span, or dead tail in song.als.
- The edit must not be only track property changes.
- It must be actual Arrangement content.
4. Require at least one measurable coherence improvement.
- Before editing, capture exact metrics.
- After editing, capture exact metrics again.
- At least one of these must improve in the saved evidence:
- silence islands
- mirrored section pairs
- harmonic coverage/backbone presence
- same-source dominance
- repeated clip overuse
- “3 patterns created” is not enough if the saved coherence metrics remain flat.
5. Validate the missing live MCP tools from v0.1.42.
- The previous run still under-validated the tool set.
- This sprint must exercise and record exact results for:
- get_tracks()
- get_device_parameters(...)
- set_device_parameter_by_name(...)
- Also re-confirm one arrangement creation/edit path and one audit path.
- If a tool is blocked, record the exact raw blocker and the exact fallback.
6. Validate snare selectivity in a real runtime path.
- Do not infer from the current project state.
- Do not cite older sprint text as proof.
- Run a real runtime path that exercises the selection logic or the relevant selection entry point.
- Record exact evidence showing whether the aggressive snare is penalized differently across at least two different section-energy contexts.
- If the scoring exists but is not wired into the runtime path, wire the minimum real path and validate it.
7. Keep scope tight and senior.
- Do not add broad new feature surfaces.
- Do not rewrite the generation system.
- Touch only the files required to:
- unlock the blocked edit path
- validate the missing tool coverage
- make the coherence improvement measurable
- Likely candidates:
- AbletonMCP_AI/MCP_Server/server.py
- abletonmcp_init.py
- AbletonMCP_AI/abletonmcp_runtime.py
- AbletonMCP_AI/MCP_Server/sample_selector.py
- docs/SPRINT_v0.1.43_VALIDATION_REPORT.md
8. Fail honestly if the blocker is fundamental and unfixed.
- If the MIDI path remains fundamentally blocked, the report must say so explicitly.
- But in that case the sprint still must either:
- ship a real validated fallback path with measurable improvement
- or fail
- A documentation-only explanation is not enough anymore.
## Acceptance Criteria
This sprint passes only if all of the following are true:
1. The live target is the already-open song.als session.
2. The run makes at least one real source-code change in the runtime or selection path, unless an already-existing code path is conclusively shown to be the canonical supported fallback and is validated end-to-end live.
3. At least one real Arrangement content edit is applied on the open project to improve harmonic continuity or fill a real weak span.
4. The backbone goal is met either by:
- meaningful Arrangement MIDI backbone content added live
- or a documented and validated supported fallback path that adds backbone-like Arrangement content when MIDI insertion is blocked
5. At least one coherence metric in the saved before/after evidence improves materially.
6. The run validates the previously missing live tool coverage:
- get_tracks()
- get_device_parameters(...)
- set_device_parameter_by_name(...)
7. Snare selectivity is validated through a real runtime path across at least two section contexts.
8. All changed Python files compile.
9. Relevant tests for touched code pass.
10. The validation report contains exact raw evidence references and does not overclaim.
11. Codex can reasonably return pass from repository evidence alone.
Automatic fail conditions:
- no source-code or real wiring change is made while blockers remain
- only property edits are applied again
- backbone remains absent and no validated fallback is delivered
- all saved coherence metrics remain flat again
- snare selectivity is still argued from inference instead of runtime evidence
- the report claims success without a measurable improvement
## Validation
Produce all of the following artifacts:
1. docs/SPRINT_v0.1.43_VALIDATION_REPORT.md
Required sections:
- Summary
- Files Changed
- Live Target Proof
- Runtime Fix or Canonical Fallback
- MCP Tools Validated Live
- Project Audits Before
- Project Edits Applied
- Project Audits After
- Before/After Metrics
- Snare Selectivity
- Harmonic Backbone Outcome
- What Is Still Weak
- Remaining Risks
2. temp/v04143_live_target_proof.json
- raw proof that the active session is the intended open project
3. temp/v04143_mcp_calls.jsonl
- one JSON line per MCP call
- include tool name, arguments, success/failure, and raw result/error
4. temp/v04143_before_audit.json
- raw before-state audit outputs
5. temp/v04143_after_audit.json
- raw after-state audit outputs
6. temp/v04143_edit_actions.json
- exact live edits attempted and whether each succeeded
7. temp/v04143_snare_selectivity_validation.json
- runtime evidence for at least two section contexts
- include the exact sample candidates or scoring evidence used
8. temp/v04143_blocker_or_fallback.json
- if MIDI remains blocked, document:
- exact failing call
- exact raw response
- exact supported fallback used instead
- proof that the fallback was applied live
9. temp/v04143_metric_delta.json
- explicit before/after delta summary for the coherence metrics
Required validation actions:
- python -m py_compile on every changed Python file
- relevant tests for every touched runtime/MCP/selection file
- live MCP calls against the open project
- before and after audits from the same live session
- exact evidence for either a fixed backbone path or a validated fallback path
## Context
Read these first:
- previous run directory:
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-203815-queue
- previous summary:
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-203815-queue\SUMMARY.md
- previous reviews:
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-203815-queue\reviews\codex_master_pre_fix.json
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-203815-queue\reviews\opencode_qwen3coder_plus.json
- C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\ralph\runs\20260403-203815-queue\reviews\opencode_glm47.json
- previous validation report:
- docs/SPRINT_v0.1.42_VALIDATION_REPORT.md
Most important evidence files from the previous run:
- temp/v04142_mcp_calls_final.jsonl
- temp/v04142_comprehensive_validation.json
- temp/v04142_audio_pattern_results.json
- temp/v04142_edit_actions.json
Primary source files:
- AbletonMCP_AI/MCP_Server/server.py
- abletonmcp_init.py
- AbletonMCP_AI/abletonmcp_runtime.py
- AbletonMCP_AI/MCP_Server/sample_selector.py
Execution priority for this sprint:
1. preserve the proven live connection and working arrangement-audio path
2. make the blocked backbone path succeed, or formalize a supported fallback in code
3. validate the previously missing live MCP tools
4. deliver one measurable coherence improvement
5. validate snare selectivity in a real runtime path
6. report only what repository evidence proves
Do not overclaim. The previous run proved that live editing is partially possible. This sprint exists to turn that partial proof into a repeatable, code-backed, measurable project improvement.

View File

@@ -0,0 +1,172 @@
# SPRINT v0.1.43 - Unlock Backbone Editing and Measurable Coherence Gains
## Goal
Use the live MCP connection on the already-open Ableton project:
- C:\Users\ren\Desktop\song Project\song.als
to close the remaining blocker from v0.1.42:
- convert the currently proven live inspection and audio-pattern editing path into a path that produces a meaningful harmonic backbone improvement and at least one measurable coherence improvement
This sprint is not about proving connectivity again in isolation.
This sprint is about:
- fixing or formalizing the blocked backbone-edit path
- using that path on the live project
- producing measurable before/after improvement
- validating snare selectivity in a real runtime path
## Why This Task Exists
Repository truth from v0.1.42:
- the run did reach the correct open project
- real MCP-driven Arrangement audio edits succeeded through `create_arrangement_audio_pattern`
- no source-code fix was made
- the harmonic MIDI backbone requirement was still not met
- key coherence metrics did not materially improve
- snare selectivity was still inferential rather than proven in a real selection path
High-signal truths from the run artifacts:
- `temp/v04142_mcp_calls_final.jsonl` proves `create_arrangement_audio_pattern` works live
- `temp/v04142_comprehensive_validation.json` shows 3 arrangement audio-pattern edits succeeded
- the same artifact shows mirrored pairs stayed at 100 and clip overuse stayed high
- `docs/SPRINT_v0.1.42_VALIDATION_REPORT.md` admits MIDI note insertion is still blocked
- the worktree had no source-code diff, so the blocker was diagnosed but not fixed
The next step is therefore not “more validation.”
It is:
- implement the smallest real code fix or formal runtime fallback needed to make backbone extension and coherence improvement repeatable
- then prove it on song.als with exact metrics
## Required Work
1. Start from the live path that actually worked in v0.1.42.
- Reuse the live MCP connection approach that already validated the real open `song.als` session.
- Reuse the proven `create_arrangement_audio_pattern` path if MIDI editing remains blocked.
- Do not regress the working runtime path.
2. Resolve the backbone-edit blocker at code level.
- You must make a real code change in the runtime path this sprint unless the existing code already supports a better fallback and only wiring is missing.
- Priority order:
1. make a meaningful Arrangement MIDI backbone edit succeed
2. if that is genuinely blocked by the Live API, implement and validate a formal fallback path that creates backbone-like Arrangement content through a supported method
- A valid fallback is not random content.
- A valid fallback must:
- be intentional
- extend harmonic continuity
- be audibly useful
- be documented as the canonical path when MIDI insertion is blocked
3. Prove the fallback or fix is real on the live project.
- Apply at least one backbone-oriented Arrangement edit that increases continuity in a musically relevant span.
- The edit must target a real gap, weak span, or dead tail in song.als.
- The edit must not be only track property changes.
- It must be actual Arrangement content.
4. Require at least one measurable coherence improvement.
- Before editing, capture exact metrics.
- After editing, capture exact metrics again.
- At least one of these must improve in the saved evidence:
- silence islands
- mirrored section pairs
- harmonic coverage/backbone presence
- same-source dominance
- repeated clip overuse
- “3 patterns created” is not enough if the saved coherence metrics remain flat.
5. Validate the missing live MCP tools from v0.1.42.
- The previous run still under-validated the tool set.
- This sprint must exercise and record exact results for:
- get_tracks()
- get_device_parameters(...)
- set_device_parameter_by_name(...)
- Also re-confirm one arrangement creation/edit path and one audit path.
- If a tool is blocked, record the exact raw blocker and the exact fallback.
6. Validate snare selectivity in a real runtime path.
- Do not infer from the current project state.
- Do not cite older sprint text as proof.
- Run a real runtime path that exercises the selection logic or the relevant selection entry point.
- Record exact evidence showing whether the aggressive snare is penalized differently across at least two different section-energy contexts.
- If the scoring exists but is not wired into the runtime path, wire the minimum real path and validate it.
7. Keep scope tight and senior.
- Do not add broad new feature surfaces.
- Do not rewrite the generation system.
- Touch only the files required to:
- unlock the blocked edit path
- validate the missing tool coverage
- make the coherence improvement measurable
- Likely candidates:
- AbletonMCP_AI/MCP_Server/server.py
- abletonmcp_init.py
- AbletonMCP_AI/abletonmcp_runtime.py
- AbletonMCP_AI/MCP_Server/sample_selector.py
- docs/SPRINT_v0.1.43_VALIDATION_REPORT.md
8. Fail honestly if the blocker is fundamental and unfixed.
- If the MIDI path remains fundamentally blocked, the report must say so explicitly.
- But in that case the sprint still must either:
- ship a real validated fallback path with measurable improvement
- or fail
- A documentation-only explanation is not enough anymore.
## Validation
Produce all of the following artifacts:
1. docs/SPRINT_v0.1.43_VALIDATION_REPORT.md
Required sections:
- Summary
- Files Changed
- Live Target Proof
- Runtime Fix or Canonical Fallback
- MCP Tools Validated Live
- Project Audits Before
- Project Edits Applied
- Project Audits After
- Before/After Metrics
- Snare Selectivity
- Harmonic Backbone Outcome
- What Is Still Weak
- Remaining Risks
2. temp/v04143_live_target_proof.json
- raw proof that the active session is the intended open project
3. temp/v04143_mcp_calls.jsonl
- one JSON line per MCP call
- include tool name, arguments, success/failure, and raw result/error
4. temp/v04143_before_audit.json
- raw before-state audit outputs
5. temp/v04143_after_audit.json
- raw after-state audit outputs
6. temp/v04143_edit_actions.json
- exact live edits attempted and whether each succeeded
7. temp/v04143_snare_selectivity_validation.json
- runtime evidence for at least two section contexts
- include the exact sample candidates or scoring evidence used
8. temp/v04143_blocker_or_fallback.json
- if MIDI remains blocked, document:
- exact failing call
- exact raw response
- exact supported fallback used instead
- proof that the fallback was applied live
9. temp/v04143_metric_delta.json
- explicit before/after delta summary for the coherence metrics
Required validation actions:
- python -m py_compile on every changed Python file
- relevant tests for every touched runtime/MCP/selection file
- live MCP calls against the open project
- before and after audits from the same live session
- exact evidence for either a fixed backbone path or a validated fallback path

View File

@@ -0,0 +1,9 @@
{
"id": "20260403-211425-20260403-211423-sprint-v0-1-43-unlock-backbone-e",
"title": "20260403-211423-sprint-v0-1-43-unlock-backbone-editing-and-measu",
"state": "failed",
"finished_at": "2026-04-03T21:28:37.1850500-03:00",
"source": "C:\\ProgramData\\Ableton\\Live 12 Suite\\Resources\\MIDI Remote Scripts\\ralph\\tasks\\inbox\\20260403-211423-sprint-v0-1-43-unlock-backbone-editing-and-measu",
"run_id": "",
"summary": "Start-RalphAutopilot.ps1 failed with exit code 1"
}

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,9 @@
# Acceptance
The task is only complete if all of these are true:
1. implementation compiles
2. relevant tests pass
3. runtime evidence exists if the task touches Live or MCP integration
4. no inflated claims in docs
5. remaining risks are written explicitly

View File

@@ -0,0 +1,35 @@
Write the next autonomous Ralph task as a single Markdown document.
Requirements:
- Output only the Markdown task document, with no preface and no code fences.
- This task is for Ralph swarm automation, not a human chat response.
- The task must be actionable, specific, and based only on repository truth and run artifacts.
- The task must continue the current line of development.
- Prefer runtime validation, exact evidence, and concrete acceptance criteria.
- If the previous run failed, focus on the highest-signal blockers first.
- If the previous run succeeded, focus on the next most valuable improvement.
- Keep the task self-contained so it can be submitted directly into the inbox.
Required Markdown structure:
# <short title>
## Goal
## Why This Task Exists
## Required Work
## Acceptance Criteria
## Validation
## Context
Rules:
- Be strict and senior.
- Do not overclaim completion.
- Do not ask for manual handoff or manual reasoning from the operator.
- Assume Ralph will run this automatically.

View File

@@ -0,0 +1,41 @@
You are Codex running as the persistent master reviewer for this repository.
Read the task pack, reviewer outputs and current worktree diff.
Your job:
- identify the real state of the implementation
- separate infrastructure from validated integration
- point out the highest-risk issues
- say whether the task meets acceptance or not
- write the next sprint if the task is not fully complete
Do not overclaim. Use the repository truth, not the worker's self-report.
Output rules:
- Output exactly one JSON object and nothing else.
- Do not wrap the JSON in markdown fences.
- Use this schema exactly:
{
"verdict": "pass | needs_fix | fail",
"acceptance_passed": true,
"fix_required": false,
"summary": "short single-paragraph summary",
"highest_risk_issues": [
{
"severity": "high | medium | low",
"title": "short issue title",
"details": "one concise paragraph"
}
],
"next_sprint_needed": false,
"next_sprint_brief": "empty string if not needed"
}
Verdict policy:
- Use `pass` only if the task truly satisfies acceptance and no blocking issue remains.
- Use `needs_fix` if the implementation is promising but still misses acceptance or needs another implementer pass.
- Use `fail` if the task is blocked, broken, or validated against the wrong runtime path.

View File

@@ -0,0 +1,8 @@
# Context
List the canonical docs and code paths the worker must read first.
- active sprint doc
- current handoff
- relevant source files
- known evidence files

View File

@@ -0,0 +1,18 @@
You are the implementer.
Read the task pack files carefully and work only inside the assigned worktree.
Rules:
- use PowerShell and Windows paths
- do not invent success
- compile and run the smallest useful validation
- write a short `CHANGES.md` in the run directory
- if runtime validation is impossible, say so explicitly
- do not edit outside the assigned worktree
Output:
- apply code changes
- leave the worktree in a reviewable state
- write a concise final summary

View File

@@ -0,0 +1,15 @@
You are the reviewer.
Review the task pack and the implementer diff.
Primary focus:
- bugs
- behavioral regressions
- fake completion claims
- missing tests
- wrong assumptions about runtime behavior
Write findings first, ordered by severity.
Do not praise. Do not rewrite the task. Do not fix code unless explicitly asked.

21
ralph/templates/TASK.md Normal file
View File

@@ -0,0 +1,21 @@
# Task
## Goal
Describe the concrete engineering goal here.
## Files in scope
- list exact files
## Constraints
- keep Windows-native paths
- validate with runtime when the task touches Ableton integration
- do not declare success from compilation alone
## Expected output
- code changes
- short changes report
- remaining risks

1
ralph/worktrees/.gitkeep Normal file
View File

@@ -0,0 +1 @@