chore(harness): isolate pi stage execution
This commit is contained in:
@@ -4,8 +4,9 @@ Orquestra se ejecuta desde Pi como **un solo parent session secuencial**. No ins
|
||||
|
||||
## Requisitos obligatorios
|
||||
- `pi` debe existir en `PATH` antes de instalar Orquestra.
|
||||
- `gentle-engram` debe estar instalado: Orquestra usa Engram como memoria durable externa; no escribe memoria propia.
|
||||
- El proyecto instalado debe abrirse desde su raíz.
|
||||
- Arrancar con `./scripts/pi_orquestra.sh`, que ejecuta `pi --no-extensions` y carga solo extensiones Orquestra.
|
||||
- Arrancar con `./scripts/pi_orquestra.sh`, que ejecuta `pi --no-extensions`, carga Engram explícitamente y carga solo extensiones Orquestra.
|
||||
- Extensiones project-local declaradas: `.pi/extensions/orquestra-status/` y `.pi/extensions/orquestra-web-fetch.ts`.
|
||||
- El código de producto vive en `project/`; archivos de código en la raíz son inválidos.
|
||||
|
||||
@@ -16,12 +17,19 @@ Cuando Orquestra se instala en un repo de proyecto, el instalador debe copiar:
|
||||
|
||||
No debe crear `.pi/subagents/` ni `.pi/subagents.json`.
|
||||
|
||||
## Técnica de memoria
|
||||
- Engram es la única memoria persistente del harness.
|
||||
- `verify.sh` falla si `~/.pi/agent/npm/node_modules/gentle-engram/index.ts` no existe.
|
||||
- `pi_orquestra.sh` carga Engram con `-e` aunque Pi arranque con `--no-extensions`; así se evita cargar extensiones globales no declaradas sin perder memoria.
|
||||
- Cada rol trabaja desde los `input` declarados en `harness/workflow.stages.yml`; el chat completo no es un handoff válido.
|
||||
- Para aislamiento real, ejecutar cada stage con `python3 scripts/run_stage.py <stage> --feature-id <feature_id>`; usa `pi --no-session --no-context-files` y carga solo Engram + extensiones Orquestra.
|
||||
|
||||
## Flujo secuencial
|
||||
1. Ejecutar `./scripts/verify.sh`.
|
||||
2. Abrir Pi limpio desde la raíz con `./scripts/pi_orquestra.sh`.
|
||||
3. Confirmar el widget con `/orquestra-status`.
|
||||
4. El mismo parent session cambia de rol siguiendo `harness/workflow.stages.yml`.
|
||||
5. Antes de cada stage, actualizar estado con `python3 scripts/agent_status.py set ...`.
|
||||
4. Ejecutar cada stage como proceso fresco: `python3 scripts/run_stage.py <stage> --feature-id <feature_id>`.
|
||||
5. `run_stage.py` genera un prompt mínimo con las rutas `input`/`output` del stage y no hereda la sesión anterior.
|
||||
6. `agent_status.py` rechaza saltos de stage sin artefactos previos obligatorios.
|
||||
7. Durante `build`, escribir producto en `project/` y tests en `tests/`; requiere `feature_id`, `stage=build`, `agent=implementer` y `state=running` en `work/runtime-status.json`.
|
||||
8. Al terminar cada stage, escribir el artefacto esperado en `work/artifacts/<feature_id>/`.
|
||||
|
||||
@@ -7,6 +7,7 @@ const MATRIX_FILE = "harness/agents.matrix.yml";
|
||||
const ARTIFACTS_DIR = "work/artifacts";
|
||||
const WIDGET_KEY = "orquestra-runtime";
|
||||
const STATUS_KEY = "orquestra-runtime";
|
||||
const STATUS_GAP = " ";
|
||||
|
||||
const DEFAULT_EMOJIS: Record<string, string> = {
|
||||
leader: "🧭",
|
||||
@@ -100,8 +101,8 @@ function gateSummary(root: string, featureId: string | null | undefined, emojis:
|
||||
const gates = gateState(root, featureId);
|
||||
if (!featureId) return "Gates: —";
|
||||
return `Gates: ${["reviewer", "security", "qa", "leader"]
|
||||
.map((gate) => `${emojis[gate] || "•"}${gates[gate] === "APPROVED" ? "✅" : gates[gate] === "PRESENT" ? "⚠️" : "⏳"}`)
|
||||
.join(" ")}`;
|
||||
.map((gate) => `${emojis[gate] || "•"} ${gates[gate] === "APPROVED" ? "✅" : gates[gate] === "PRESENT" ? "⚠️" : "⏳"}`)
|
||||
.join(STATUS_GAP)}`;
|
||||
}
|
||||
|
||||
function artifactStatus(root: string, featureId: string | null | undefined, agent: string): string {
|
||||
@@ -127,9 +128,9 @@ function agentSummary(root: string, status: RuntimeStatus, emojis: Record<string
|
||||
.map((agent) => {
|
||||
const state = artifactStatus(root, featureId, agent);
|
||||
const mark = state === "DONE" ? "✅" : state === "PRESENT" ? "⚠️" : agent === currentAgent ? "▶️" : "⏳";
|
||||
return `${emojis[agent] || "•"}${mark}`;
|
||||
return `${emojis[agent] || "•"} ${mark}`;
|
||||
})
|
||||
.join(" ")}`;
|
||||
.join(STATUS_GAP)}`;
|
||||
}
|
||||
|
||||
function isInsideRel(relPath: string, dirname: string): boolean {
|
||||
@@ -229,7 +230,6 @@ function render(root: string, status: RuntimeStatus | null, emojis: Record<strin
|
||||
export default function orquestraStatusExtension(pi: ExtensionAPI) {
|
||||
let currentCtx: ExtensionContext | null = null;
|
||||
let watching = false;
|
||||
let refreshTimer: ReturnType<typeof setInterval> | null = null;
|
||||
let lastRendered = "";
|
||||
|
||||
function apply(ctx: ExtensionContext) {
|
||||
@@ -251,16 +251,13 @@ export default function orquestraStatusExtension(pi: ExtensionAPI) {
|
||||
function startWatching() {
|
||||
if (watching) return;
|
||||
if (!currentCtx) return;
|
||||
watchFile(join(currentCtx.cwd, STATUS_FILE), { interval: 1000 }, () => refresh());
|
||||
refreshTimer = setInterval(refresh, 1000);
|
||||
watchFile(join(currentCtx.cwd, STATUS_FILE), { interval: 1000, persistent: false }, () => refresh());
|
||||
watching = true;
|
||||
}
|
||||
|
||||
function stopWatching() {
|
||||
if (!watching) return;
|
||||
if (currentCtx) unwatchFile(join(currentCtx.cwd, STATUS_FILE));
|
||||
if (refreshTimer) clearInterval(refreshTimer);
|
||||
refreshTimer = null;
|
||||
watching = false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user