1212import asyncio
1313import json
1414import re
15+ import shutil
1516import time
1617import urllib .request
1718from pathlib import Path
1819from typing import Protocol
1920
2021from . import agent_image , docker_ops , sandbox as agent_sandbox
21- from .agent import parse_xml_tag , run_agent
22+ from .agent import parse_xml_tag , run_agent , run_agent_process
2223from .events import NULL_EVENT_SINK
2324from .runtime_artifacts import (
2425 RUNTIME_BLOCKED ,
@@ -946,6 +947,34 @@ def _run_pi_agent(
946947 return self ._blocked (finding , plan , "verification agent provider/model is missing" )
947948 with EphemeralAppSandbox (self .profile , finding_dir ) as app :
948949 assert app .repo_copy is not None
950+ assert app .root is not None
951+ transcript = finding_dir / "live-agent-transcript.jsonl"
952+ if app .backend == "local" :
953+ result = asyncio .run (
954+ run_agent_process (
955+ _localize_live_agent_paths (
956+ _live_agent_prompt (self .profile , finding ),
957+ repo_path = app .repo_copy ,
958+ artifacts_path = finding_dir ,
959+ ),
960+ command_prefix = ["pi" ],
961+ provider = provider ,
962+ model = model ,
963+ session_dir = str (app .root / ".pi-sessions" ),
964+ cwd = str (app .root ),
965+ env = _local_pi_env (app , agent_cfg ),
966+ transcript_path = str (transcript ),
967+ progress_prefix = f"[verify { finding .id } ]" ,
968+ tools = ["read" , "bash" ],
969+ system_prompt = _localize_live_agent_paths (
970+ _live_agent_system_prompt (),
971+ repo_path = app .repo_copy ,
972+ artifacts_path = finding_dir ,
973+ ),
974+ event_sink = getattr (self , "event_sink" , NULL_EVENT_SINK ),
975+ )
976+ )
977+ return self ._verdict_from_agent_result (finding , finding_dir , plan , result )
949978 mounts = agent_sandbox .provider_mounts (
950979 Path (str (agent_cfg ["pi_config_dir" ])) if agent_cfg .get ("pi_config_dir" ) else None
951980 )
@@ -968,7 +997,6 @@ def _run_pi_agent(
968997 mounts = mounts ,
969998 )
970999 try :
971- transcript = finding_dir / "live-agent-transcript.jsonl"
9721000 result = asyncio .run (
9731001 run_agent (
9741002 _live_agent_prompt (self .profile , finding ),
@@ -984,6 +1012,15 @@ def _run_pi_agent(
9841012 )
9851013 finally :
9861014 docker_ops .rm (container )
1015+ return self ._verdict_from_agent_result (finding , finding_dir , plan , result )
1016+
1017+ def _verdict_from_agent_result (
1018+ self ,
1019+ finding : RuntimeFinding ,
1020+ finding_dir : Path ,
1021+ plan : VerificationPlan ,
1022+ result ,
1023+ ) -> RuntimeVerdict :
9871024 output = result .find_tagged_message ("runtime_verdict_json" )
9881025 (finding_dir / "live-agent-output.txt" ).write_text (
9891026 result .last_assistant_message or output ,
@@ -1223,6 +1260,30 @@ def _unsafe_without_mock(profile: AppRuntimeProfile, finding: RuntimeFinding) ->
12231260 return False
12241261
12251262
1263+ def _local_pi_env (app : EphemeralAppSandbox , agent_cfg : dict [str , object ]) -> dict [str , str ]:
1264+ env = app .env ()
1265+ env ["PI_OFFLINE" ] = "1"
1266+ env ["PI_SKIP_VERSION_CHECK" ] = "1"
1267+ env ["PI_TELEMETRY" ] = "0"
1268+ if agent_cfg .get ("pi_config_dir" ):
1269+ assert app .root is not None
1270+ pi_config_dir = Path (str (agent_cfg ["pi_config_dir" ]))
1271+ target = app .root / "home" / ".pi" / "agent"
1272+ target .mkdir (parents = True , exist_ok = True )
1273+ for name in ("auth.json" , "models.json" ):
1274+ source = pi_config_dir / name
1275+ if source .is_file ():
1276+ shutil .copy2 (source , target / name )
1277+ env ["HOME" ] = str (app .root / "home" )
1278+ return env
1279+
1280+
1281+ def _localize_live_agent_paths (text : str , * , repo_path : Path , artifacts_path : Path ) -> str :
1282+ return text .replace ("/work/repo" , str (repo_path )).replace (
1283+ "/work/artifacts" , str (artifacts_path )
1284+ )
1285+
1286+
12261287def _extract_verdict_payload (text : str ) -> dict [str , object ] | None :
12271288 raw = parse_xml_tag (text , "runtime_verdict_json" ) or text .strip ()
12281289 if raw .startswith ("```" ):
0 commit comments