Finish Epiphany live specialist MVP
This commit is contained in:
parent
f1771e11df
commit
da8b7f82a0
10 changed files with 329 additions and 32 deletions
|
|
@ -79,6 +79,7 @@ The landed machine now has:
|
|||
- explicit Phase 6 role launch/read-back through `thread/epiphany/roleLaunch` and read-only `thread/epiphany/roleResult`, limited to fixed modeling/checkpoint and verification/review templates over the existing job-control seam
|
||||
- first Phase 6 dogfood operator view through `tools/epiphany_mvp_status.py`
|
||||
- first auditable Phase 6 dogfood runner through `tools/epiphany_mvp_dogfood.py`, producing local status snapshots, raw app-server transcript, final status artifacts, vanilla-reference output, and comparison notes
|
||||
- first auditable Phase 6 live-specialist runner through `tools/epiphany_mvp_live_specialist.py`, proving `roleLaunch -> agent_jobs worker -> report_agent_job_result -> roleResult` without manual backend completion
|
||||
- live Phase 6 reorientation app-server smoke coverage in `tools/epiphany_phase6_reorient_smoke.py`
|
||||
- live Phase 6 reorient-launch app-server smoke coverage in `tools/epiphany_phase6_reorient_launch_smoke.py`
|
||||
- live Phase 6 MVP status smoke coverage in `tools/epiphany_mvp_status_smoke.py`
|
||||
|
|
@ -148,8 +149,8 @@ The next unknowns are:
|
|||
|
||||
- how the landed watcher-backed invalidation telemetry should be consumed without turning freshness into a secret worker
|
||||
- how far the read-only CRRC recommendation should go before explicit client/operator action takes over
|
||||
- whether future bounded CRRC consumers should keep reusing the landed `agent_jobs` backend through the explicit job-control seam or start defining a second backend contract
|
||||
- what Phase 6 should prove before specialist scheduling begins
|
||||
- how much specialist ergonomics can improve before crossing into broad ambient scheduling
|
||||
- what concrete operator friction appears when the CLI MVP is tested on real work
|
||||
|
||||
## MVP Cutline
|
||||
|
||||
|
|
@ -191,9 +192,12 @@ launch modeling/checkpoint or verification/review workers, and read their
|
|||
findings back without reading Rust. The first auditable dogfood pass then added
|
||||
`tools/epiphany_mvp_dogfood.py`, rendered modeling/verification findings in the
|
||||
status view, and fixed CRRC's repeat-acceptance recommendation after
|
||||
`reorientAccept`. The next MVP question is human operator testing, not another
|
||||
imagined organ: use the landed loop and artifact bundle, then fix only concrete
|
||||
blockers.
|
||||
`reorientAccept`. The live-specialist runner then proved the real worker path by
|
||||
launching a modeling/checkpoint specialist, letting it inspect the smoke
|
||||
workspace, report through `report_agent_job_result`, and return a completed
|
||||
`checkpoint-ready` finding through `roleResult`. The next MVP question is human
|
||||
operator testing, not another imagined organ: use the landed loop and artifact
|
||||
bundle, then fix only concrete blockers.
|
||||
|
||||
## Phase 6 Direction
|
||||
|
||||
|
|
@ -201,7 +205,7 @@ Phase 6 should grow observable harness state outward from the typed spine.
|
|||
|
||||
Useful candidates:
|
||||
|
||||
1. Put the dogfooded MVP loop in front of a human operator through status/artifact review, then fix concrete usability blockers.
|
||||
1. Put the CLI MVP in front of a human operator through status/artifact review, then fix concrete usability blockers.
|
||||
2. Keep accepted worker findings review-gated; do not convert acceptance into automatic promotion of arbitrary worker output.
|
||||
3. Add targeted operator-view fields only when dogfooding exposes a real gap.
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ Do not trust this file for the exact live HEAD. Always check git.
|
|||
- Phase 6 has read-only `thread/epiphany/scene`, `thread/epiphany/jobs`, `thread/epiphany/roles`, `thread/epiphany/freshness`, `thread/epiphany/context`, `thread/epiphany/pressure`, `thread/epiphany/reorient`, `thread/epiphany/crrc`, `thread/epiphany/reorientResult`, and `thread/epiphany/roleResult`; durable `jobBindings` now act as a thin Epiphany-owned launcher seam with launcher id, authority scope, and backend kind/job id, `thread/epiphany/jobLaunch` / `thread/epiphany/jobInterrupt` / `thread/epiphany/roleLaunch` now provide explicit bounded authority over that seam, jobs can overlay it onto live runtime `agent_jobs` progress for loaded threads, and `thread/epiphany/jobsUpdated` now translates live `agent_job_progress:{json}` background events into changed launcher-bound notifications without polling or scheduling. Freshness carries watcher-backed invalidation inputs, roles project implementation/modeling/verification/reorientation ownership from existing signals without becoming a scheduler, `roleLaunch` can launch only fixed modeling/checkpoint or verification/review specialists, `roleResult` reads those outputs back as reviewable findings without mutation, reorient turns checkpoint plus freshness/pressure/watcher signals into a read-only resume-versus-regather verdict, `thread/epiphany/reorientLaunch` is the explicit runtime consumer over that verdict, `thread/epiphany/reorientResult` reads the launched worker output back as a reviewable finding without mutation or promotion, `thread/epiphany/reorientAccept` explicitly banks completed findings into accepted observation/evidence plus optional scratch/checkpoint state, and `thread/epiphany/crrc` recommends the next explicit CRRC action without launching, accepting, compacting, scheduling, or mutating.
|
||||
- `tools/epiphany_mvp_status.py` is the first dogfood operator view. It starts or reads a thread through app-server and prints scene, pressure, reorient, jobs, roles, modeling/verification role result read-backs, reorient result, and CRRC recommendation as text or JSON.
|
||||
- `tools/epiphany_mvp_dogfood.py` is the first auditable dogfood runner. It drives a bounded state/role/CRRC/reorientation loop and writes local artifacts under `.epiphany-dogfood/mvp-loop`, including raw app-server transcript, rendered snapshots, final status, vanilla-reference prompt/output, and comparison notes.
|
||||
- `tools/epiphany_mvp_live_specialist.py` is the first auditable live-specialist runner. It launches a real role specialist through `thread/epiphany/roleLaunch`, lets the spawned worker report through `report_agent_job_result`, reads it back through `thread/epiphany/roleResult`, and writes local artifacts under `.epiphany-dogfood/live-specialist`.
|
||||
- Internal `agent_job:` workers now get the reporting tool independent of the user-facing CSV spawn feature, and ephemeral worker sessions can initialize the sqlite state runtime on demand so specialist reports land in the shared backend.
|
||||
- Durable in-flight investigation checkpointing is now landed in authoritative typed state, writable through `thread/epiphany/update` or accepted `thread/epiphany/promote`, rendered into the prompt, and reflected through scene/context.
|
||||
- The repo is an Epiphany fork of Codex, not a Codex preset.
|
||||
- `vendor/codex` is tracked directly, not a submodule.
|
||||
|
|
@ -93,6 +95,7 @@ The current spine:
|
|||
- live reorient-guided worker launch smoke through `tools/epiphany_phase6_reorient_launch_smoke.py`
|
||||
- live MVP operator status smoke through `tools/epiphany_mvp_status_smoke.py`
|
||||
- live job-control app-server smoke through `tools/epiphany_phase6_job_control_smoke.py`
|
||||
- live specialist MVP pass through `tools/epiphany_mvp_live_specialist.py`
|
||||
|
||||
The exact current control flow is documented in
|
||||
`notes/epiphany-current-algorithmic-map.md`.
|
||||
|
|
@ -268,10 +271,17 @@ produced `.epiphany-dogfood/mvp-loop` artifacts, ran a read-only Vanilla Codex
|
|||
reference, and fixed the concrete blocker where CRRC recommended accepting the
|
||||
same reorientation result again after `reorientAccept`.
|
||||
|
||||
When the user asks to continue, put the testable MVP in front of a human
|
||||
operator through `tools/epiphany_mvp_status.py` and the dogfood artifact bundle.
|
||||
Fix concrete blockers in that operator loop; do not build a broad hidden
|
||||
scheduler, marketplace, or GUI-as-source-of-truth.
|
||||
The first live-specialist pass has also run. It produced
|
||||
`.epiphany-dogfood/live-specialist` artifacts and proved the real worker path:
|
||||
`roleLaunch` created a bound `agent_jobs` row, the spawned modeling worker
|
||||
inspected the smoke workspace, called `report_agent_job_result`, and
|
||||
`roleResult` projected a completed `checkpoint-ready` finding.
|
||||
|
||||
The CLI MVP is now testable. Put it in front of a human operator through
|
||||
`tools/epiphany_mvp_status.py`, `.epiphany-dogfood/mvp-loop`, and
|
||||
`.epiphany-dogfood/live-specialist`. Fix concrete blockers in that operator
|
||||
loop; do not build a broad hidden scheduler, marketplace, or
|
||||
GUI-as-source-of-truth.
|
||||
|
||||
Live `thread/epiphany/scene`, `thread/epiphany/jobs`, `thread/epiphany/roles`,
|
||||
`thread/epiphany/freshness`, `thread/epiphany/context`,
|
||||
|
|
@ -283,7 +293,7 @@ guardrails, not the next organs.
|
|||
|
||||
- automatic watcher-driven semantic invalidation
|
||||
- automatic observation promotion
|
||||
- specialist-agent scheduling
|
||||
- broad specialist-agent scheduling beyond explicit roleLaunch
|
||||
- GUI-as-source-of-truth
|
||||
- automatic runtime CRRC execution acting on the landed recommendation/verdict and launch/read-back path
|
||||
- Epiphany-owned long-running job execution beyond the current runtime `agent_jobs` seam
|
||||
|
|
|
|||
|
|
@ -56,3 +56,4 @@
|
|||
{"ts": "2026-04-28T13:55:36+00:00", "type": "implementation", "status": "ok", "note": "Landed read-only thread/epiphany/roles as the harness-native MVP role ownership surface. It projects implementation, modeling/checkpoint, verification/review, and reorientation lanes from typed state plus jobs/pressure/reorient/CRRC/result signals without scheduling or mutation; the MVP status tool now consumes that surface. Focused protocol/app-server tests, schema fixtures, app-server build, and live MVP status smoke passed."}
|
||||
{"ts": "2026-04-28T14:48:10+00:00", "type": "implementation", "status": "ok", "note": "Landed explicit thread/epiphany/roleLaunch and read-only thread/epiphany/roleResult for fixed modeling/checkpoint and verification/review specialists over the existing job-control seam. Launch mutates only jobBindings; result projects reviewable findings without mutation. Focused protocol/app-server tests, schema fixtures, app-server build, and tools/epiphany_phase6_role_smoke.py passed."}
|
||||
{"ts": "2026-04-28T15:32:47+00:00", "type": "implementation", "status": "ok", "note": "Ran the first auditable Epiphany MVP dogfood pass through state, modeling/verification roleLaunch/roleResult, CRRC drift, reorientLaunch/reorientResult/reorientAccept, final status rendering, and a read-only Vanilla Codex reference. Dogfood artifacts live under .epiphany-dogfood/mvp-loop. The pass exposed and fixed a concrete operator-loop blocker: after reorientAccept, CRRC no longer recommends accepting the same completed reorientation finding again; it recognizes the accepted evidence and recommends regatherManually for accepted regather findings."}
|
||||
{"ts": "2026-04-28T16:33:43+00:00", "type": "implementation", "status": "ok", "note": "Landed live-specialist MVP execution: internal agent_job workers now expose report_agent_job_result independently of the CSV front door, ephemeral workers can initialize sqlite state runtime on demand, and tools/epiphany_mvp_live_specialist.py proved roleLaunch -> agent_jobs worker -> report_agent_job_result -> roleResult with auditable artifacts under .epiphany-dogfood/live-specialist."}
|
||||
|
|
|
|||
|
|
@ -90,6 +90,8 @@ accepted_design:
|
|||
- `thread/epiphany/reorientAccept` is the first explicit acceptance write for completed reorient-worker findings: it appends accepted observation/evidence records, can optionally update scratch and investigation checkpoint state, and emits `stateUpdated` with source `reorientAccept` without accepting pending work or launching follow-up work.
|
||||
- After `thread/epiphany/reorientAccept`, CRRC recognizes the accepted reorientation finding and does not recommend accepting the same completed result again.
|
||||
- `tools/epiphany_mvp_dogfood.py` is the auditable MVP dogfood runner: it drives state, role launch/result, CRRC drift, reorient launch/result/accept, rendered status snapshots, and raw app-server transcript into a local artifact bundle for comparison against a vanilla Codex reference.
|
||||
- `tools/epiphany_mvp_live_specialist.py` is the auditable live-specialist MVP runner: it launches a real modeling/checkpoint or verification/review specialist through `thread/epiphany/roleLaunch`, lets the spawned worker report through `report_agent_job_result`, reads the finding back through `thread/epiphany/roleResult`, and writes transcript/status artifacts without manually completing backend rows.
|
||||
- Internal `agent_job:` workers expose `report_agent_job_result` even when the user-facing CSV spawn feature is disabled, and ephemeral worker sessions can initialize the sqlite state runtime on demand so role specialists can report into the shared `agent_jobs` backend.
|
||||
- Epiphany safety doctrine is to separate cognition from actuation, keep authority explicit, make interruption sacred, stage capability release, and treat operator misuse as a normal design input rather than a rude hypothetical.
|
||||
- Epiphany is built in the open, but publication mode is not one-size-fits-all: use FOSS where viable, source-available licensing where economics or capability risk demand it, and commercial terms where organizations extract real enterprise value.
|
||||
- `thread/epiphany/jobLaunch` and `thread/epiphany/jobInterrupt` are the first bounded authority surfaces over the thin job seam: they mutate durable `job_bindings`, create or cancel the current `agent_jobs` backend binding, and preserve explicit launcher identity and interruption without turning `thread/epiphany/jobs` into a scheduler.
|
||||
|
|
@ -112,6 +114,7 @@ landed_baseline:
|
|||
- Phase 6 acceptance: explicit reorient-worker finding acceptance through `thread/epiphany/reorientAccept`.
|
||||
- Phase 6 dogfood view: first CLI operator status over scene/pressure/reorient/jobs/result/CRRC.
|
||||
- Phase 6 dogfood runner: auditable scripted MVP loop over state, role results, CRRC, reorientation acceptance, status snapshots, and transcript artifacts.
|
||||
- Phase 6 live specialist runner: auditable real worker loop over roleLaunch, agent_jobs worker execution, report_agent_job_result, roleResult, status rendering, and transcript artifacts.
|
||||
- Phase 6 role ownership: read-only `thread/epiphany/roles` over implementation, modeling/checkpoint, verification/review, and reorientation lanes.
|
||||
- Phase 6 role specialists: explicit `thread/epiphany/roleLaunch` and read-only `thread/epiphany/roleResult` templates for modeling/checkpoint and verification/review.
|
||||
guardrails:
|
||||
|
|
@ -129,6 +132,7 @@ landed_baseline:
|
|||
- Use `tools/epiphany_phase6_reorient_launch_smoke.py` before modifying the read-only CRRC coordinator recommendation surface.
|
||||
- Use `tools/epiphany_mvp_status_smoke.py` before modifying the MVP operator status view.
|
||||
- Use `tools/epiphany_phase6_role_smoke.py` before modifying explicit role launch/read-back behavior.
|
||||
- Use `tools/epiphany_mvp_live_specialist.py` before claiming live specialist execution still works end to end.
|
||||
- Use focused scene/jobs/context/pressure/reorient/protocol/schema checks for reflection-only changes.
|
||||
- Use `tools/epiphany_prepare_compaction.py` before and after imminent-compaction persistence passes.
|
||||
- Use `CARGO_TARGET_DIR=C:\Users\Meta\.cargo-target-codex` for Codex Rust work on this Windows machine.
|
||||
|
|
@ -147,5 +151,5 @@ rejected_paths:
|
|||
reason: persistent state that accumulates every local truth becomes memory cruft and weakens rehydration
|
||||
|
||||
current_status:
|
||||
summary: Phase 1 durable state, Phase 2 prompt integration, Phase 3 typed read, Phase 4 retrieval/indexing, Phase 5 update/distill/propose/promote/read/notification, and Phase 6 read-only scene, jobs, roles, freshness, targeted context, pressure, reorient, CRRC, reorient launch/result/accept, explicit modeling/checkpoint and verification/review role launch/result templates, first dogfood MVP status, and auditable MVP dogfood runner are landed. The dogfood runner exercised state, role launch/result, CRRC drift, reorient launch/result/accept, and rendered status snapshots; it exposed and fixed the repeat-acceptance CRRC recommendation after `reorientAccept`. Implementation remains main-agent work; reorientation remains on the CRRC-specific launch/result path. Persistent state remains distilled; evidence is a belief ledger, not an activity feed.
|
||||
next_action: Put the testable MVP in front of a human operator using the dogfood artifact bundle and `tools/epiphany_mvp_status.py`; fix only concrete blockers in the operator loop. Automatic broad specialist scheduling, GUI-as-source-of-truth, a specialist marketplace, autonomous specialist quality, and a second job backend remain future work.
|
||||
summary: Phase 1 durable state, Phase 2 prompt integration, Phase 3 typed read, Phase 4 retrieval/indexing, Phase 5 update/distill/propose/promote/read/notification, and Phase 6 read-only scene, jobs, roles, freshness, targeted context, pressure, reorient, CRRC, reorient launch/result/accept, explicit modeling/checkpoint and verification/review role launch/result templates, first dogfood MVP status, auditable MVP dogfood runner, and live specialist MVP runner are landed. The live specialist runner proved a real modeling specialist can launch, inspect, report through `report_agent_job_result`, and be read back through `roleResult`; the earlier deterministic dogfood runner exercised state, role launch/result, CRRC drift, reorient launch/result/accept, rendered status snapshots, and Vanilla Codex comparison. Implementation remains main-agent work; reorientation remains on the CRRC-specific launch/result path. Persistent state remains distilled; evidence is a belief ledger, not an activity feed.
|
||||
next_action: Treat the CLI MVP as testable: run `tools/epiphany_mvp_status.py`, inspect `.epiphany-dogfood/mvp-loop` and `.epiphany-dogfood/live-specialist`, and iterate only on concrete operator-loop blockers. Automatic broad specialist scheduling, GUI-as-source-of-truth, a specialist marketplace, autonomous specialist quality, and a second job backend remain future work.
|
||||
|
|
|
|||
213
tools/epiphany_mvp_live_specialist.py
Normal file
213
tools/epiphany_mvp_live_specialist.py
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from epiphany_mvp_status import DEFAULT_APP_SERVER
|
||||
from epiphany_mvp_status import collect_status
|
||||
from epiphany_mvp_status import render_status
|
||||
from epiphany_phase5_smoke import AppServerClient
|
||||
from epiphany_phase5_smoke import ROOT
|
||||
from epiphany_phase5_smoke import require
|
||||
from epiphany_phase6_reorient_smoke import prepare_workspace
|
||||
from epiphany_phase6_reorient_smoke import reorient_patch
|
||||
|
||||
|
||||
DEFAULT_ARTIFACT_DIR = ROOT / ".epiphany-dogfood" / "live-specialist"
|
||||
DEFAULT_CODEX_HOME = Path(os.environ.get("CODEX_HOME", Path.home() / ".codex"))
|
||||
DEFAULT_ROLES = ("modeling",)
|
||||
TERMINAL_STATUSES = {"completed", "failed", "cancelled"}
|
||||
|
||||
|
||||
def reset_artifact_dir(path: Path) -> None:
|
||||
root = (ROOT / ".epiphany-dogfood").resolve()
|
||||
resolved = path.resolve()
|
||||
if resolved == root or root not in resolved.parents:
|
||||
raise ValueError(f"refusing to delete non-dogfood artifact dir: {path}")
|
||||
if path.exists():
|
||||
shutil.rmtree(path)
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def write_json(path: Path, value: Any) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(value, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def write_text(path: Path, value: str) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(value, encoding="utf-8")
|
||||
|
||||
|
||||
def launch_role(
|
||||
client: AppServerClient,
|
||||
*,
|
||||
thread_id: str,
|
||||
role_id: str,
|
||||
expected_revision: int,
|
||||
max_runtime_seconds: int,
|
||||
) -> dict[str, Any]:
|
||||
launch = client.send(
|
||||
"thread/epiphany/roleLaunch",
|
||||
{
|
||||
"threadId": thread_id,
|
||||
"roleId": role_id,
|
||||
"expectedRevision": expected_revision,
|
||||
"maxRuntimeSeconds": max_runtime_seconds,
|
||||
},
|
||||
)
|
||||
assert launch is not None
|
||||
require(launch["roleId"] == role_id, f"{role_id} launch should echo role id")
|
||||
require(
|
||||
launch["changedFields"] == ["jobBindings"],
|
||||
f"{role_id} launch should only mutate job bindings",
|
||||
)
|
||||
return launch
|
||||
|
||||
|
||||
def wait_for_role_result(
|
||||
client: AppServerClient,
|
||||
*,
|
||||
thread_id: str,
|
||||
role_id: str,
|
||||
timeout_seconds: int,
|
||||
poll_seconds: float,
|
||||
) -> tuple[dict[str, Any], list[str]]:
|
||||
deadline = time.time() + timeout_seconds
|
||||
seen_statuses: list[str] = []
|
||||
result: dict[str, Any] | None = None
|
||||
while time.time() < deadline:
|
||||
response = client.send(
|
||||
"thread/epiphany/roleResult",
|
||||
{"threadId": thread_id, "roleId": role_id},
|
||||
)
|
||||
assert response is not None
|
||||
result = response
|
||||
status = response.get("status", "<missing>")
|
||||
seen_statuses.append(status)
|
||||
if status in TERMINAL_STATUSES:
|
||||
return response, seen_statuses
|
||||
time.sleep(poll_seconds)
|
||||
|
||||
assert result is not None
|
||||
return result, seen_statuses
|
||||
|
||||
|
||||
def run_live_specialist(args: argparse.Namespace) -> dict[str, Any]:
|
||||
app_server = args.app_server.resolve()
|
||||
if not app_server.exists():
|
||||
raise FileNotFoundError(f"codex app-server binary not found: {app_server}")
|
||||
|
||||
codex_home = args.codex_home.resolve()
|
||||
if not codex_home.exists():
|
||||
raise FileNotFoundError(f"codex home not found: {codex_home}")
|
||||
|
||||
artifact_dir = args.artifact_dir.resolve()
|
||||
reset_artifact_dir(artifact_dir)
|
||||
workspace = artifact_dir / "workspace"
|
||||
transcript_path = artifact_dir / "epiphany-transcript.jsonl"
|
||||
stderr_path = artifact_dir / "epiphany-server.stderr.log"
|
||||
prepare_workspace(workspace)
|
||||
|
||||
role_results: list[dict[str, Any]] = []
|
||||
|
||||
with AppServerClient(app_server, codex_home, transcript_path, stderr_path) as client:
|
||||
client.send(
|
||||
"initialize",
|
||||
{
|
||||
"clientInfo": {
|
||||
"name": "epiphany-mvp-live-specialist",
|
||||
"title": "Epiphany MVP Live Specialist",
|
||||
"version": "0.1.0",
|
||||
},
|
||||
"capabilities": {"experimentalApi": True},
|
||||
},
|
||||
)
|
||||
client.send("initialized", expect_response=False)
|
||||
started = client.send("thread/start", {"cwd": str(workspace), "ephemeral": True})
|
||||
assert started is not None
|
||||
thread_id = started["thread"]["id"]
|
||||
|
||||
update = client.send(
|
||||
"thread/epiphany/update",
|
||||
{"threadId": thread_id, "expectedRevision": 0, "patch": reorient_patch()},
|
||||
)
|
||||
assert update is not None
|
||||
revision = update["revision"]
|
||||
|
||||
for role_id in args.roles:
|
||||
launch = launch_role(
|
||||
client,
|
||||
thread_id=thread_id,
|
||||
role_id=role_id,
|
||||
expected_revision=revision,
|
||||
max_runtime_seconds=args.max_runtime_seconds,
|
||||
)
|
||||
revision = launch["revision"]
|
||||
result, seen_statuses = wait_for_role_result(
|
||||
client,
|
||||
thread_id=thread_id,
|
||||
role_id=role_id,
|
||||
timeout_seconds=args.timeout_seconds,
|
||||
poll_seconds=args.poll_seconds,
|
||||
)
|
||||
role_results.append(
|
||||
{
|
||||
"roleId": role_id,
|
||||
"launch": launch,
|
||||
"seenStatuses": seen_statuses,
|
||||
"result": result,
|
||||
}
|
||||
)
|
||||
|
||||
final_status = collect_status(client, thread_id=thread_id, cwd=workspace, ephemeral=True)
|
||||
final_rendered = render_status(final_status)
|
||||
|
||||
summary = {
|
||||
"objective": "Run a real Epiphany role specialist through roleLaunch, agent_jobs, report_agent_job_result, and roleResult.",
|
||||
"artifactDir": str(artifact_dir),
|
||||
"codexHome": str(codex_home),
|
||||
"threadId": thread_id,
|
||||
"workspace": str(workspace),
|
||||
"roles": role_results,
|
||||
"finalStatus": final_status,
|
||||
"artifactManifest": [
|
||||
"live-specialist-summary.json",
|
||||
"epiphany-final-status.json",
|
||||
"epiphany-final-status.txt",
|
||||
"epiphany-transcript.jsonl",
|
||||
"epiphany-server.stderr.log",
|
||||
],
|
||||
}
|
||||
|
||||
write_json(artifact_dir / "live-specialist-summary.json", summary)
|
||||
write_json(artifact_dir / "epiphany-final-status.json", final_status)
|
||||
write_text(artifact_dir / "epiphany-final-status.txt", final_rendered)
|
||||
return summary
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run an auditable live Epiphany specialist through the real agent_jobs worker path."
|
||||
)
|
||||
parser.add_argument("--app-server", type=Path, default=DEFAULT_APP_SERVER)
|
||||
parser.add_argument("--artifact-dir", type=Path, default=DEFAULT_ARTIFACT_DIR)
|
||||
parser.add_argument("--codex-home", type=Path, default=DEFAULT_CODEX_HOME)
|
||||
parser.add_argument("--roles", nargs="+", choices=["modeling", "verification"], default=list(DEFAULT_ROLES))
|
||||
parser.add_argument("--max-runtime-seconds", type=int, default=180)
|
||||
parser.add_argument("--timeout-seconds", type=int, default=240)
|
||||
parser.add_argument("--poll-seconds", type=float, default=5.0)
|
||||
args = parser.parse_args()
|
||||
result = run_live_specialist(args)
|
||||
print(json.dumps(result, indent=2, ensure_ascii=False))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -236,7 +236,7 @@ mod spawn_agents_on_csv {
|
|||
));
|
||||
}
|
||||
|
||||
let db = required_state_db(&session)?;
|
||||
let db = required_state_db(&session).await?;
|
||||
let input_path = turn.resolve_path(Some(args.csv_path));
|
||||
let input_path_display = input_path.display().to_string();
|
||||
let csv_content = tokio::fs::read_to_string(&input_path)
|
||||
|
|
@ -478,7 +478,7 @@ mod report_agent_job_result {
|
|||
"result must be a JSON object".to_string(),
|
||||
));
|
||||
}
|
||||
let db = required_state_db(&session)?;
|
||||
let db = required_state_db(&session).await?;
|
||||
let reporting_thread_id = session.conversation_id.to_string();
|
||||
let accepted = db
|
||||
.report_agent_job_item_result(
|
||||
|
|
@ -511,11 +511,23 @@ mod report_agent_job_result {
|
|||
}
|
||||
}
|
||||
|
||||
fn required_state_db(
|
||||
async fn required_state_db(
|
||||
session: &Arc<Session>,
|
||||
) -> Result<Arc<codex_state::StateRuntime>, FunctionCallError> {
|
||||
session.state_db().ok_or_else(|| {
|
||||
FunctionCallError::Fatal("sqlite state db is unavailable for this session".to_string())
|
||||
if let Some(state_db) = session.state_db() {
|
||||
return Ok(state_db);
|
||||
}
|
||||
|
||||
let config = session.get_config().await;
|
||||
codex_state::StateRuntime::init(
|
||||
config.codex_home.to_path_buf(),
|
||||
config.model_provider_id.clone(),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
FunctionCallError::Fatal(format!(
|
||||
"sqlite state db is unavailable for this session and fallback init failed: {err}"
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
11
vendor/codex/codex-rs/tools/src/tool_config.rs
vendored
11
vendor/codex/codex-rs/tools/src/tool_config.rs
vendored
|
|
@ -195,12 +195,11 @@ impl ToolsConfig {
|
|||
None => include_apply_patch_tool.then_some(ApplyPatchToolType::Freeform),
|
||||
};
|
||||
|
||||
let agent_jobs_worker_tools = include_agent_jobs
|
||||
&& matches!(
|
||||
session_source,
|
||||
SessionSource::SubAgent(SubAgentSource::Other(label))
|
||||
if label.starts_with("agent_job:")
|
||||
);
|
||||
let agent_jobs_worker_tools = matches!(
|
||||
session_source,
|
||||
SessionSource::SubAgent(SubAgentSource::Other(label))
|
||||
if label.starts_with("agent_job:")
|
||||
);
|
||||
|
||||
Self {
|
||||
available_models: available_models.to_vec(),
|
||||
|
|
|
|||
|
|
@ -157,6 +157,29 @@ fn subagents_keep_request_user_input_mode_config_and_agent_jobs_workers_opt_in_b
|
|||
assert!(tools_config.agent_jobs_worker_tools);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn agent_job_workers_get_report_tool_even_when_csv_front_door_disabled() {
|
||||
let model_info = model_info();
|
||||
let features = Features::with_defaults();
|
||||
|
||||
let available_models = Vec::new();
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
available_models: &available_models,
|
||||
features: &features,
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::SubAgent(SubAgentSource::Other(
|
||||
"agent_job:test".to_string(),
|
||||
)),
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
assert!(!tools_config.agent_jobs_tools);
|
||||
assert!(tools_config.agent_jobs_worker_tools);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_generation_requires_feature_and_supported_model() {
|
||||
let supported_model_info = model_info();
|
||||
|
|
|
|||
|
|
@ -484,14 +484,15 @@ pub fn build_tool_registry_plan(
|
|||
config.code_mode_enabled,
|
||||
);
|
||||
plan.register_handler("spawn_agents_on_csv", ToolHandlerKind::AgentJobs);
|
||||
if config.agent_jobs_worker_tools {
|
||||
plan.push_spec(
|
||||
create_report_agent_job_result_tool(),
|
||||
/*supports_parallel_tool_calls*/ false,
|
||||
config.code_mode_enabled,
|
||||
);
|
||||
plan.register_handler("report_agent_job_result", ToolHandlerKind::AgentJobs);
|
||||
}
|
||||
}
|
||||
|
||||
if config.agent_jobs_worker_tools {
|
||||
plan.push_spec(
|
||||
create_report_agent_job_result_tool(),
|
||||
/*supports_parallel_tool_calls*/ false,
|
||||
config.code_mode_enabled,
|
||||
);
|
||||
plan.register_handler("report_agent_job_result", ToolHandlerKind::AgentJobs);
|
||||
}
|
||||
|
||||
if let Some(mcp_tools) = params.mcp_tools {
|
||||
|
|
|
|||
|
|
@ -511,6 +511,36 @@ fn test_build_specs_agent_job_worker_tools_enabled() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_specs_agent_job_worker_report_tool_without_csv_front_door() {
|
||||
let model_info = model_info();
|
||||
let mut features = Features::with_defaults();
|
||||
features.normalize_dependencies();
|
||||
features.enable(Feature::Sqlite);
|
||||
let available_models = Vec::new();
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
available_models: &available_models,
|
||||
features: &features,
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::SubAgent(SubAgentSource::Other(
|
||||
"agent_job:test".to_string(),
|
||||
)),
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
|
||||
assert_contains_tool_names(&tools, &["report_agent_job_result"]);
|
||||
assert_lacks_tool_name(&tools, "spawn_agents_on_csv");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_user_input_description_reflects_default_mode_feature_flag() {
|
||||
let model_info = model_info();
|
||||
|
|
|
|||
Reference in a new issue