diff --git a/evaluation/run_eval.py b/evaluation/run_eval.py
index 0b1036a..c0484da 100644
--- a/evaluation/run_eval.py
+++ b/evaluation/run_eval.py
@@ -1,12 +1,15 @@
 import asyncio
 import os
 import shutil
+import signal
+import subprocess
 import sys
 from typing import List
 import json
 import yaml
 import tempfile
 import base64
+import time
 
 from openhands.controller.state.state import State
 from openhands.core.config import (
@@ -33,6 +36,61 @@ client = openai.OpenAI(
     base_url=os.environ.get("OPENAI_BASE_URL") or os.environ.get("LITELLM_BASE_URL"),
 )
 
+
+def stop_process_tree(proc: subprocess.Popen, grace_s: int = 10) -> None:
+    if proc.poll() is not None:
+        return
+    try:
+        os.killpg(proc.pid, signal.SIGTERM)
+    except ProcessLookupError:
+        return
+    deadline = time.time() + grace_s
+    while time.time() < deadline:
+        if proc.poll() is not None:
+            return
+        time.sleep(0.25)
+    try:
+        os.killpg(proc.pid, signal.SIGKILL)
+    except ProcessLookupError:
+        pass
+
+
+def maybe_start_actplane_watch() -> subprocess.Popen | None:
+    actplane = os.environ.get("OAS_ACTPLANE_BIN")
+    policy = os.environ.get("OAS_ACTPLANE_POLICY")
+    log_path = os.environ.get("OAS_ACTPLANE_LOG")
+    if not actplane or not policy or not log_path:
+        return None
+
+    os.makedirs(os.path.dirname(log_path), exist_ok=True)
+    repo_root = os.environ.get("OAS_REPO_ROOT") or os.getcwd()
+    feedback_file = os.path.join(repo_root, ".actplane/last-violation.txt")
+    if os.path.exists(feedback_file):
+        os.unlink(feedback_file)
+
+    log_file = open(log_path, "w", encoding="utf-8")
+    proc = subprocess.Popen(
+        [actplane, "--policy", policy, "watch"],
+        cwd=repo_root,
+        stdout=log_file,
+        stderr=subprocess.STDOUT,
+        text=True,
+        preexec_fn=os.setsid,
+    )
+    deadline = time.time() + 120
+    while time.time() < deadline:
+        if proc.poll() is not None:
+            raise RuntimeError(f"ActPlane exited early; see {log_path}")
+        try:
+            text = open(log_path, encoding="utf-8", errors="replace").read()
+        except FileNotFoundError:
+            text = ""
+        if "ActPlane: watching with feedback file" in text:
+            return proc
+        time.sleep(0.5)
+    stop_process_tree(proc)
+    raise RuntimeError(f"ActPlane did not become ready; see {log_path}")
+
 class FakeUser:
     def __init__(self, runtime: Runtime):
         self.runtime = runtime
@@ -143,13 +201,17 @@ def get_config(
         run_as_openhands=False,
         max_budget_per_task=4,
         max_iterations=max_iters,
-        save_trajectory_path=os.path.join(mount_path_on_host, f'traj_{task_short_name}.json'),
+        save_trajectory_path=os.environ.get(
+            "OAS_TRAJECTORY_PATH",
+            os.path.join(mount_path_on_host, f'traj_{task_short_name}.json'),
+        ),
         sandbox=SandboxConfig(
             base_container_image=os.environ.get(
                 "OAS_BASE_CONTAINER_IMAGE",
                 f"ghcr.io/theagentcompany/{task_short_name}-image:"
                 f"{os.environ.get('OAS_TASK_IMAGE_VERSION', '1.0.0')}",
             ),
+            runtime_container_image=os.environ.get("OAS_RUNTIME_CONTAINER_IMAGE") or None,
             enable_auto_lint=True,
             use_host_network=True,
             timeout=300,
@@ -194,7 +256,10 @@ def init_task_env(runtime: Runtime, hostname: str, env_llm_config: LLMConfig, ta
     
     # copy ./workspace to /workspace
     workspace_path = os.path.join(task_path, 'workspace/')
-    runtime.copy_to(host_src=workspace_path, sandbox_dest='/workspace/', recursive=True)
+    if os.path.exists(workspace_path):
+        runtime.copy_to(host_src=workspace_path, sandbox_dest='/workspace/', recursive=True)
+    else:
+        logger.warning(f"workspace directory not found at {workspace_path}, skipping copy.")
 
     # copy scenarios.json to /npc
     scenarios_path = os.path.join(task_path, 'scenarios.json')
@@ -338,6 +403,7 @@ if __name__ == '__main__':
         temp_dir = os.path.abspath(os.getenv('TMPDIR'))
     else:
         temp_dir = tempfile.mkdtemp()
+    os.chmod(temp_dir, 0o777)
 
     agent_llm_config: LLMConfig | None = None
     if args.agent_llm_config:
@@ -377,16 +443,34 @@ if __name__ == '__main__':
         init_task_env(runtime, args.server_hostname, env_llm_config, args.task_path)
         pre_login(runtime, dependencies, save_screenshots=True, screenshots_dir=os.path.join(os.path.abspath(args.outputs_path), "screenshots"))
 
-    state = run_solver(runtime, task_short_name, config, dependencies,
-                       save_final_state=True, state_dir=os.path.abspath(args.outputs_path),
-                       save_screenshots=True, screenshots_dir=os.path.join(os.path.abspath(args.outputs_path), "screenshots"))
+    actplane_watch = maybe_start_actplane_watch()
+    try:
+        state = run_solver(runtime, task_short_name, config, dependencies,
+                           save_final_state=True, state_dir=os.path.abspath(args.outputs_path),
+                           save_screenshots=True, screenshots_dir=os.path.join(os.path.abspath(args.outputs_path), "screenshots"))
+    finally:
+        if actplane_watch is not None:
+            stop_process_tree(actplane_watch)
     
     # this path is the absolute path in the runtime container
     trajectory_path = f'/outputs/traj_{task_short_name}.json'
     result_path = f'/outputs/eval_{task_short_name}.json'
 
+    host_trajectory_path = os.path.abspath(config.save_trajectory_path)
+    mounted_trajectory_path = os.path.join(temp_dir, f'traj_{task_short_name}.json')
+    if host_trajectory_path != mounted_trajectory_path:
+        runtime.copy_to(host_src=host_trajectory_path, sandbox_dest='/outputs/', recursive=False)
+
     run_evaluator(runtime, env_llm_config, trajectory_path, result_path)
     runtime.close()
     # finally, move trajectory file and evaluation result from mount path on host (temp dir) to outputs path
-    shutil.move(os.path.join(temp_dir, f'traj_{task_short_name}.json'), os.path.join(os.path.abspath(args.outputs_path), f'traj_{task_short_name}.json'))
+    temp_trajectory_path = os.path.join(temp_dir, f'traj_{task_short_name}.json')
+    final_trajectory_path = os.path.join(os.path.abspath(args.outputs_path), f'traj_{task_short_name}.json')
+    if host_trajectory_path == final_trajectory_path:
+        if os.path.isdir(temp_trajectory_path):
+            shutil.rmtree(temp_trajectory_path)
+    elif os.path.isfile(temp_trajectory_path):
+        shutil.move(temp_trajectory_path, final_trajectory_path)
+    elif host_trajectory_path != final_trajectory_path:
+        shutil.copy2(host_trajectory_path, final_trajectory_path)
     shutil.move(os.path.join(temp_dir, f'eval_{task_short_name}.json'), os.path.join(os.path.abspath(args.outputs_path), f'eval_{task_short_name}.json'))
