Skip to content
This repository was archived by the owner on Aug 10, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ The bootstrap helper writes two canonical artifacts under:
"env": {
"SCENARIO_SLUG": "release-qa",
"WORKSPACE_PATH": "/abs/path/to/lab",
"RUNTIME_WORKSPACE_PATH": "/abs/path/to/lab/project",
"QA_OUTPUT_PATH": "/abs/path/to/lab/qa-artifacts",
"AGH_HOME": "/abs/path/to/lab/.agh/runtime",
"AGH_HTTP_PORT": "2235",
Expand Down Expand Up @@ -73,10 +74,13 @@ The bootstrap helper additionally writes the following under `WORKSPACE_PATH`:

- `.agh/playbook.json` β€” the resolved playbook spec (the canonical structured JSON parsed from `references/playbooks/<ref>.md`).
- `.agh/agents/<agent-id>.json` β€” one file per agent declared by the playbook (id, role, persona, system_prompt, workspace_id, workspace_path, skills, playbook_ref).
- `.agh/tasks/open-tasks.json` β€” array of open tasks with owner_agent, owner_workspace_id, owner_workspace_path, deliverable_type, deliverable_path, review_required_by, channel, playbook_ref.
- `.agh/tasks/open-tasks.json` β€” array of open tasks with deterministic `runtime_id`, owner_agent, owner_workspace_id, owner_workspace_path, deliverable_type, deliverable_path, review_required_by, channel, playbook_ref.
- `.agh/disruption-seeds.json` β€” playbook disruption_probe_seeds for downstream consumers.
- `workspaces/<workspace-name>/README.md` β€” per-workspace stub README.
- `knowledge/<...>` β€” every knowledge file declared by the playbook.
- `project/` β€” the only root registered with AGH for agents under test; it excludes `qa-artifacts/`, manifests, audit contracts, and provider evidence.
- `project/workspaces/<workspace-name>/README.md` β€” per-workspace stub README.
- `knowledge/<...>` β€” canonical copy of every knowledge file declared by the playbook.
- `project/workspaces/<workspace-name>/knowledge/global/<...>` β€” every global knowledge file projected into each readable agent workspace.
- `project/workspaces/<workspace-name>/knowledge/<...>` β€” only the scoped knowledge files declared by that workspace.

`PLAYBOOK_REF` and `KICKOFF_POSTED=false` are written to the manifest env. `real-scenario-qa` Step 4 flips `KICKOFF_POSTED=true` and sets `KICKOFF_TIMESTAMP` after posting the single in-persona kickoff.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ def unavailable_tomllib(*args, **kwargs):
return module


def load_script_module(path: Path, name: str):
spec = importlib.util.spec_from_file_location(name, path)
if spec is None or spec.loader is None:
raise RuntimeError(f"failed to load module spec for {path}")
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
return module


def write_discovery_script(repo_root: Path, payload: dict) -> None:
script_path = repo_root / ".agents" / "skills" / "qa-execution" / "scripts" / "discover-project-contract.py"
script_path.parent.mkdir(parents=True)
Expand Down Expand Up @@ -92,6 +102,37 @@ def main() -> None:
summary = module.seed_playbook_workspace(repo_root, workspace_path, "northstar-pay")
if not Path(summary["playbook_snapshot"]).is_file():
raise AssertionError("seed_playbook_workspace() did not materialize the playbook")
runtime_workspace_path = Path(summary["runtime_workspace_path"])
if runtime_workspace_path.resolve() != (workspace_path / "project").resolve():
raise AssertionError(
f"runtime workspace = {runtime_workspace_path}, want isolated project root"
)
if (runtime_workspace_path / "qa-artifacts").exists():
raise AssertionError("runtime workspace exposes QA artifacts to agents under test")
global_knowledge = Path("knowledge/global/launch-week-brief.md")
for workspace_name in (
"launch-hq",
"product-studio",
"growth-studio",
"platform-control",
"finance-command",
"merchant-success",
"risk-ops",
):
projected = runtime_workspace_path / "workspaces" / workspace_name / global_knowledge
if not projected.is_file():
raise AssertionError(f"global knowledge was not projected into {workspace_name}")
risk_memo = Path("knowledge/workspace/executive-risk-memo.md")
if not (runtime_workspace_path / "workspaces" / "launch-hq" / risk_memo).is_file():
raise AssertionError("launch-hq is missing its declared scoped knowledge")
if (runtime_workspace_path / "workspaces" / "product-studio" / risk_memo).exists():
raise AssertionError("scoped launch-hq knowledge leaked into product-studio")

open_tasks = json.loads((workspace_path / ".agh" / "tasks" / "open-tasks.json").read_text())
runtime_ids = [task.get("runtime_id") for task in open_tasks]
expected_ids = [f"task-northstar-pay-{index:03d}" for index in range(1, 13)]
if runtime_ids != expected_ids:
raise AssertionError(f"runtime task ids = {runtime_ids!r}, want {expected_ids!r}")

qa_root = workspace_path / "qa-artifacts" / "qa"
qa_root.mkdir(parents=True)
Expand All @@ -101,6 +142,143 @@ def main() -> None:
if not evidence_paths["AUDIT_COMMAND"].is_file():
raise AssertionError("seed_qa_evidence_contracts() emitted a missing audit command")

manifest_path = qa_root / "bootstrap-manifest.json"
manifest = {
"env": {
"AGH_HOME": str(workspace_path / ".agh" / "runtime"),
"WORKSPACE_PATH": str(workspace_path),
"RUNTIME_WORKSPACE_PATH": str(runtime_workspace_path),
"KICKOFF_POSTED": "false",
"KICKOFF_TIMESTAMP": "",
},
"status": {"notes": []},
}
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
real_scenario_scripts = (
repo_root / ".agents" / "skills" / "agh" / "real-scenario-qa" / "scripts"
)
auditor = load_script_module(
real_scenario_scripts / "audit-qa-evidence.py",
"audit_qa_evidence",
)
lab_path, audited_runtime_path = auditor.workspace_paths_from_manifest(manifest_path)
if lab_path != workspace_path.resolve():
raise AssertionError(f"auditor lab root = {lab_path}, want {workspace_path.resolve()}")
if audited_runtime_path != runtime_workspace_path.resolve():
raise AssertionError(
f"auditor runtime root = {audited_runtime_path}, "
f"want {runtime_workspace_path.resolve()}"
)
if auditor.load_playbook_snapshot(lab_path) is None:
raise AssertionError("auditor did not load the playbook snapshot from the lab root")
if auditor.load_playbook_snapshot(audited_runtime_path) is not None:
raise AssertionError("runtime workspace unexpectedly exposes the playbook snapshot")
with tempfile.TemporaryDirectory() as deliverable_dir:
ts_test_dir = Path(deliverable_dir)
(ts_test_dir / "module.test.ts").write_text(
'test("module", () => {});\n',
encoding="utf-8",
)
(ts_test_dir / "component.test.tsx").write_text(
'test("component", () => {});\n',
encoding="utf-8",
)
deliverable_findings, deliverable_summary = auditor.check_required_deliverables(
ts_test_dir,
{"required_deliverables": {"ts_test": 2}},
)
valid_ts_tests = deliverable_summary["deliverable_counts"]["ts_test"]["valid"]
ts_test_findings = [
finding
for finding in deliverable_findings
if finding.message.startswith("deliverable ts_test")
]
if ts_test_findings or valid_ts_tests != 2:
raise AssertionError(
"auditor must accept both .test.ts and .test.tsx as TypeScript tests"
)
activation = load_script_module(
real_scenario_scripts / "activate-playbook-tasks.py",
"activate_playbook_tasks",
)
commands: list[list[str]] = []
recorded: list[dict] = []
paused = False

def fake_runner(_agh_bin: str, args: list[str], _env: dict[str, str]) -> dict:
nonlocal paused
commands.append(args)
if args[:2] == ["scheduler", "status"]:
return {"scheduler": {"paused": paused}}
if args[:2] == ["scheduler", "pause"]:
paused = True
return {"scheduler": {"paused": True}}
if args[:2] == ["scheduler", "resume"]:
paused = False
return {"scheduler": {"paused": False}}
if args[:2] == ["task", "start"]:
task_id = args[2]
return {"task": {"id": task_id}, "run": {"id": f"run-{task_id}"}}
raise AssertionError(f"unexpected fake AGH command: {args!r}")

def fake_recorder(_helper: Path, _log: Path, row: dict) -> None:
recorded.append(row)

prepared = activation.prepare_activation(
workspace_path,
workspace_path / "qa-artifacts",
manifest_path,
"agh-test",
runner=fake_runner,
recorder=fake_recorder,
)
if prepared.get("status") != "prepared" or len(prepared.get("tasks", [])) != 12:
raise AssertionError("task activation did not prepare all 12 playbook runs")
if commands[1][:2] != ["scheduler", "pause"]:
raise AssertionError(f"scheduler was not paused before task starts: {commands!r}")
if any(command[:2] == ["scheduler", "resume"] for command in commands):
raise AssertionError("prepare released the scheduler before kickoff confirmation")
if len([row for row in recorded if row.get("task_kind") == "run"]) != 12:
raise AssertionError("prepared runs were not recorded as real task-run evidence")

kickoff_evidence = qa_root / "operator-kickoff.jsonl"
kickoff_evidence.write_text('{"type":"result"}\n', encoding="utf-8")
post_kickoff = load_script_module(
real_scenario_scripts / "post-operator-kickoff.py",
"post_operator_kickoff",
)
post_kickoff.confirm_posted(
manifest_path,
qa_root / "journey-log.jsonl",
kickoff_evidence,
"Sofia Mendes",
"northstar-pay",
)
try:
post_kickoff.confirm_posted(
manifest_path,
qa_root / "journey-log.jsonl",
kickoff_evidence,
"Sofia Mendes",
"northstar-pay",
)
except post_kickoff.PlaybookError:
pass
else:
raise AssertionError("duplicate kickoff confirmation was not rejected")

released = activation.release_activation(
workspace_path,
workspace_path / "qa-artifacts",
manifest_path,
kickoff_evidence,
"agh-test",
runner=fake_runner,
recorder=fake_recorder,
)
if released.get("status") != "released" or paused:
raise AssertionError("confirmed kickoff did not release the scheduler barrier")

with tempfile.TemporaryDirectory() as raw_dir:
browser_bin = Path(raw_dir) / "browser-use"
browser_bin.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
Expand Down
11 changes: 11 additions & 0 deletions .agents/skills/agh/agh-qa-bootstrap/scripts/bootstrap-qa-env.py
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,7 @@ def main() -> int:
qa_root = qa_output_path / "qa"
ensure_lab_scaffold(workspace_path, qa_output_path)

seed_summary: dict = {}
if playbook_data is not None and not reused_lab:
try:
seed_summary = seed_playbook_workspace(repo_root, workspace_path, playbook_ref)
Expand All @@ -669,9 +670,15 @@ def main() -> int:

if reused_lab and existing_manifest is not None:
existing_env = existing_manifest.get("env", {})
runtime_workspace_path = Path(
str(existing_env.get("RUNTIME_WORKSPACE_PATH", workspace_path))
).resolve()
provider_home = Path(str(existing_env.get("PROVIDER_HOME", workspace_path / ".provider-home"))).resolve()
agh_home = Path(str(existing_env.get("AGH_HOME", workspace_path / ".agh" / "runtime"))).resolve()
else:
runtime_workspace_path = Path(
str(seed_summary.get("runtime_workspace_path", workspace_path))
).resolve()
provider_home = workspace_path / ".provider-home"
agh_home = workspace_path / ".agh" / "runtime"
violations = socket_limit_violations(socket_limited_paths(agh_home, provider_home))
Expand All @@ -698,6 +705,7 @@ def main() -> int:
env_block = {
"SCENARIO_SLUG": workspace_info["SCENARIO_SLUG"],
"WORKSPACE_PATH": str(workspace_path),
"RUNTIME_WORKSPACE_PATH": str(runtime_workspace_path),
"QA_OUTPUT_PATH": str(qa_output_path),
"AGH_HOME": str(agh_home),
"AGH_HTTP_PORT": str(pick_free_port()),
Expand All @@ -721,6 +729,7 @@ def main() -> int:

env_block["SCENARIO_SLUG"] = workspace_info["SCENARIO_SLUG"]
env_block["WORKSPACE_PATH"] = str(workspace_path)
env_block["RUNTIME_WORKSPACE_PATH"] = str(runtime_workspace_path)
env_block["QA_OUTPUT_PATH"] = str(qa_output_path)
env_block["PROVIDER_HOME"] = str(provider_home)
env_block["PROVIDER_CODEX_HOME"] = str(provider_codex_home)
Expand Down Expand Up @@ -762,6 +771,7 @@ def main() -> int:
},
"paths": {
"project_root": str(repo_root),
"runtime_workspace": str(runtime_workspace_path),
"qa_root": str(qa_root),
"provider_home": str(provider_home),
"provider_codex_home": str(provider_codex_home),
Expand All @@ -781,6 +791,7 @@ def main() -> int:
outputs = {
"SCENARIO_SLUG": workspace_info["SCENARIO_SLUG"],
"WORKSPACE_PATH": str(workspace_path),
"RUNTIME_WORKSPACE_PATH": env_block["RUNTIME_WORKSPACE_PATH"],
"QA_OUTPUT_PATH": str(qa_output_path),
"BOOTSTRAP_MANIFEST": str(manifest_path),
"BOOTSTRAP_ENV": str(env_path),
Expand Down
20 changes: 13 additions & 7 deletions .agents/skills/agh/real-scenario-qa/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ The skill rejects any prompt that frames the work as QA. See `references/forbidd

1. Activate `agh-qa-bootstrap` with scenario `$PLAYBOOK_REF` and `--playbook "$PLAYBOOK_REF"`; complete its full procedure instead of calling its helper directly.
2. Consume the canonical `BOOTSTRAP_MANIFEST` and its emitted paths. Never reconstruct provider, browser, proxy, audit, or teardown state here.
3. Confirm the selected playbook, agent registrations, open-task tree, knowledge files, required deliverables/collaboration, and populated charter all belong to the same healthy manifest.
3. Confirm the selected playbook, agent registrations, open-task tree, knowledge files, required deliverables/collaboration, and populated charter all belong to the same healthy manifest. Register only `RUNTIME_WORKSPACE_PATH` with AGH; agents must not see the lab's `qa-artifacts/` or audit contracts.

*Done when:* bootstrap's completion criteria pass and the charter has no placeholders.

Expand All @@ -47,15 +47,19 @@ The skill rejects any prompt that frames the work as QA. See `references/forbidd

**Step 4: Post the Operator Kickoff**

1. Render and validate the kickoff with the helper (mutating):
1. After runtime agents, sessions, channels, and the deterministic task ids from `.agh/tasks/open-tasks.json` exist under the shared `RUNTIME_WORKSPACE_PATH`, prepare task activation behind a scheduler barrier (mutating):
`python3 .agents/skills/agh/real-scenario-qa/scripts/activate-playbook-tasks.py prepare --workspace "$WORKSPACE_PATH" --qa-output-path "$QA_OUTPUT_PATH" --manifest "$BOOTSTRAP_MANIFEST" --agh-bin "${AGH_BIN:-agh}"`
2. Render and validate the kickoff payload (mutating only the inspectable payload file):
`python3 .agents/skills/agh/real-scenario-qa/scripts/post-operator-kickoff.py --workspace "$WORKSPACE_PATH" --playbook "$PLAYBOOK_REF" --qa-output-path "$QA_OUTPUT_PATH" --manifest "$BOOTSTRAP_MANIFEST"`
2. The helper aborts with exit code 2 if the rendered kickoff contains any phrase from `references/forbidden-prompt-phrases.md`. Do not edit the helper to suppress the check; rewrite the playbook's `kickoff_brief` instead.
3. Read `<WORKSPACE_PATH>/.agh/operator-kickoff.txt` for inspection. Use the same text verbatim when the AGH CLI is invoked to deliver the kickoff to the operator session:
3. The helper aborts with exit code 2 if the rendered kickoff contains any phrase from `references/forbidden-prompt-phrases.md`. Rewrite the playbook's `kickoff_brief` when blocked.
4. Read `<WORKSPACE_PATH>/.agh/operator-kickoff.txt`. Deliver that text verbatim once and capture the provider stream:
`agh session prompt <operator-session-id> "$(cat $WORKSPACE_PATH/.agh/operator-kickoff.txt)" -o jsonl > $QA_OUTPUT_PATH/qa/operator-kickoff.jsonl`
4. Confirm the manifest now reports `KICKOFF_POSTED=true` and `KICKOFF_TIMESTAMP` is set.
5. From this point on, the QA observer must not send any further prompt to any agent under test. If an agent stalls, file a bug β€” do not patch over the stall with a prompt.
5. Confirm the successful post from its non-empty evidence (mutating), then release the queued task runs (mutating):
`python3 .agents/skills/agh/real-scenario-qa/scripts/post-operator-kickoff.py --workspace "$WORKSPACE_PATH" --playbook "$PLAYBOOK_REF" --qa-output-path "$QA_OUTPUT_PATH" --manifest "$BOOTSTRAP_MANIFEST" --confirm-posted "$QA_OUTPUT_PATH/qa/operator-kickoff.jsonl"`
`python3 .agents/skills/agh/real-scenario-qa/scripts/activate-playbook-tasks.py release --workspace "$WORKSPACE_PATH" --qa-output-path "$QA_OUTPUT_PATH" --manifest "$BOOTSTRAP_MANIFEST" --kickoff-evidence "$QA_OUTPUT_PATH/qa/operator-kickoff.jsonl" --agh-bin "${AGH_BIN:-agh}"`
6. Confirm the manifest reports `KICKOFF_POSTED=true`, `KICKOFF_TIMESTAMP` is set, task activation is `released`, and the scheduler is unpaused. Send no further prompt to any agent under test; a stall becomes a bug.

*Done when:* exactly one kickoff was posted, the manifest records it, and the observer has no path for a second agent prompt.
*Done when:* every declared task has one queued run behind the barrier, exactly one evidenced kickoff is confirmed, dispatch is released, and the observer has no path for a second agent prompt.

**Step 5: Observe the Runtime**

Expand Down Expand Up @@ -101,6 +105,8 @@ The skill rejects any prompt that frames the work as QA. See `references/forbidd

- If bootstrap fails to load the playbook, validate the playbook against `.agents/skills/agh/real-scenario-qa/references/playbook-schema.json`; a real-scenario run never falls back to a generic charter.
- If the kickoff helper aborts on a forbidden phrase, rewrite the playbook's `kickoff_brief`. Do not edit `references/forbidden-prompt-phrases.md` to remove the rule.
- If task activation preparation fails, keep the owned scheduler barrier paused, inspect `qa/task-activation.json`, and retry with the same idempotency keys. Never post the kickoff with a partial task tree.
- If kickoff delivery or confirmation fails, keep dispatch paused. Retry only the same unconfirmed delivery when no provider evidence exists; once evidence exists, confirmation is the only valid next step. Release refuses an empty kickoff transcript or an unconfirmed manifest.
- If `observe-runtime.py` reports a stall, do NOT inject a prompt to wake the agent. The runtime stall IS the bug under test. File it in `docs/qa/bugs/` against the AGH runtime.
- If a required deliverable type cannot be parsed by the auditor (e.g., a TSX file with non-standard exports), fix the artifact in the workspace via the agent that authored it (re-prompting in-persona is fine; new operator prompts are not). If the agent cannot fix it, that is a runtime bug.
- If `browser-use:browser` is unavailable, follow the `agent-browser` fallback per the bootstrap browser policy. Do not silently drop the Web surface.
Expand Down
Loading
Loading