mirror of
https://github.com/shareAI-lab/analysis_claude_code.git
synced 2026-03-22 02:15:42 +08:00
Comprehensive rewrite establishing the harness engineering narrative across the entire repository. README (EN/ZH/JA): added "The Model IS the Agent" manifesto with historical proof (DQN, OpenAI Five, AlphaStar, Tencent Jueyu), "What an Agent Is NOT" critique, harness engineer role definition, "Why Claude Code" as masterclass in harness design, and universe vision. Consistent framing: model = driver, harness = vehicle. docs (36 files, 3 languages): injected one-line "Harness layer" callout after the motto in every session document (s01-s12). agents (13 Python files): added harness framing comment before each module docstring. skills/agent-philosophy.md: full rewrite aligned with harness narrative.
235 lines
9.2 KiB
Python
235 lines
9.2 KiB
Python
#!/usr/bin/env python3
|
|
# Harness: background execution -- the model thinks while the harness waits.
|
|
"""
|
|
s08_background_tasks.py - Background Tasks
|
|
|
|
Run commands in background threads. A notification queue is drained
|
|
before each LLM call to deliver results.
|
|
|
|
Main thread Background thread
|
|
+-----------------+ +-----------------+
|
|
| agent loop | | task executes |
|
|
| ... | | ... |
|
|
| [LLM call] <---+------- | enqueue(result) |
|
|
| ^drain queue | +-----------------+
|
|
+-----------------+
|
|
|
|
Timeline:
|
|
Agent ----[spawn A]----[spawn B]----[other work]----
|
|
| |
|
|
v v
|
|
[A runs] [B runs] (parallel)
|
|
| |
|
|
+-- notification queue --> [results injected]
|
|
|
|
Key insight: "Fire and forget -- the agent doesn't block while the command runs."
|
|
"""
|
|
|
|
import os
|
|
import subprocess
|
|
import threading
|
|
import uuid
|
|
from pathlib import Path
|
|
|
|
from anthropic import Anthropic
|
|
from dotenv import load_dotenv
|
|
|
|
load_dotenv(override=True)
|
|
|
|
if os.getenv("ANTHROPIC_BASE_URL"):
|
|
os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)
|
|
|
|
WORKDIR = Path.cwd()
|
|
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
|
|
MODEL = os.environ["MODEL_ID"]
|
|
|
|
SYSTEM = f"You are a coding agent at {WORKDIR}. Use background_run for long-running commands."
|
|
|
|
|
|
# -- BackgroundManager: threaded execution + notification queue --
|
|
class BackgroundManager:
|
|
def __init__(self):
|
|
self.tasks = {} # task_id -> {status, result, command}
|
|
self._notification_queue = [] # completed task results
|
|
self._lock = threading.Lock()
|
|
|
|
def run(self, command: str) -> str:
|
|
"""Start a background thread, return task_id immediately."""
|
|
task_id = str(uuid.uuid4())[:8]
|
|
self.tasks[task_id] = {"status": "running", "result": None, "command": command}
|
|
thread = threading.Thread(
|
|
target=self._execute, args=(task_id, command), daemon=True
|
|
)
|
|
thread.start()
|
|
return f"Background task {task_id} started: {command[:80]}"
|
|
|
|
def _execute(self, task_id: str, command: str):
|
|
"""Thread target: run subprocess, capture output, push to queue."""
|
|
try:
|
|
r = subprocess.run(
|
|
command, shell=True, cwd=WORKDIR,
|
|
capture_output=True, text=True, timeout=300
|
|
)
|
|
output = (r.stdout + r.stderr).strip()[:50000]
|
|
status = "completed"
|
|
except subprocess.TimeoutExpired:
|
|
output = "Error: Timeout (300s)"
|
|
status = "timeout"
|
|
except Exception as e:
|
|
output = f"Error: {e}"
|
|
status = "error"
|
|
self.tasks[task_id]["status"] = status
|
|
self.tasks[task_id]["result"] = output or "(no output)"
|
|
with self._lock:
|
|
self._notification_queue.append({
|
|
"task_id": task_id,
|
|
"status": status,
|
|
"command": command[:80],
|
|
"result": (output or "(no output)")[:500],
|
|
})
|
|
|
|
def check(self, task_id: str = None) -> str:
|
|
"""Check status of one task or list all."""
|
|
if task_id:
|
|
t = self.tasks.get(task_id)
|
|
if not t:
|
|
return f"Error: Unknown task {task_id}"
|
|
return f"[{t['status']}] {t['command'][:60]}\n{t.get('result') or '(running)'}"
|
|
lines = []
|
|
for tid, t in self.tasks.items():
|
|
lines.append(f"{tid}: [{t['status']}] {t['command'][:60]}")
|
|
return "\n".join(lines) if lines else "No background tasks."
|
|
|
|
def drain_notifications(self) -> list:
|
|
"""Return and clear all pending completion notifications."""
|
|
with self._lock:
|
|
notifs = list(self._notification_queue)
|
|
self._notification_queue.clear()
|
|
return notifs
|
|
|
|
|
|
BG = BackgroundManager()
|
|
|
|
|
|
# -- Tool implementations --
|
|
def safe_path(p: str) -> Path:
|
|
path = (WORKDIR / p).resolve()
|
|
if not path.is_relative_to(WORKDIR):
|
|
raise ValueError(f"Path escapes workspace: {p}")
|
|
return path
|
|
|
|
def run_bash(command: str) -> str:
|
|
dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
|
|
if any(d in command for d in dangerous):
|
|
return "Error: Dangerous command blocked"
|
|
try:
|
|
r = subprocess.run(command, shell=True, cwd=WORKDIR,
|
|
capture_output=True, text=True, timeout=120)
|
|
out = (r.stdout + r.stderr).strip()
|
|
return out[:50000] if out else "(no output)"
|
|
except subprocess.TimeoutExpired:
|
|
return "Error: Timeout (120s)"
|
|
|
|
def run_read(path: str, limit: int = None) -> str:
|
|
try:
|
|
lines = safe_path(path).read_text().splitlines()
|
|
if limit and limit < len(lines):
|
|
lines = lines[:limit] + [f"... ({len(lines) - limit} more)"]
|
|
return "\n".join(lines)[:50000]
|
|
except Exception as e:
|
|
return f"Error: {e}"
|
|
|
|
def run_write(path: str, content: str) -> str:
|
|
try:
|
|
fp = safe_path(path)
|
|
fp.parent.mkdir(parents=True, exist_ok=True)
|
|
fp.write_text(content)
|
|
return f"Wrote {len(content)} bytes"
|
|
except Exception as e:
|
|
return f"Error: {e}"
|
|
|
|
def run_edit(path: str, old_text: str, new_text: str) -> str:
|
|
try:
|
|
fp = safe_path(path)
|
|
c = fp.read_text()
|
|
if old_text not in c:
|
|
return f"Error: Text not found in {path}"
|
|
fp.write_text(c.replace(old_text, new_text, 1))
|
|
return f"Edited {path}"
|
|
except Exception as e:
|
|
return f"Error: {e}"
|
|
|
|
|
|
TOOL_HANDLERS = {
|
|
"bash": lambda **kw: run_bash(kw["command"]),
|
|
"read_file": lambda **kw: run_read(kw["path"], kw.get("limit")),
|
|
"write_file": lambda **kw: run_write(kw["path"], kw["content"]),
|
|
"edit_file": lambda **kw: run_edit(kw["path"], kw["old_text"], kw["new_text"]),
|
|
"background_run": lambda **kw: BG.run(kw["command"]),
|
|
"check_background": lambda **kw: BG.check(kw.get("task_id")),
|
|
}
|
|
|
|
TOOLS = [
|
|
{"name": "bash", "description": "Run a shell command (blocking).",
|
|
"input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
|
|
{"name": "read_file", "description": "Read file contents.",
|
|
"input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}}, "required": ["path"]}},
|
|
{"name": "write_file", "description": "Write content to file.",
|
|
"input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}},
|
|
{"name": "edit_file", "description": "Replace exact text in file.",
|
|
"input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}},
|
|
{"name": "background_run", "description": "Run command in background thread. Returns task_id immediately.",
|
|
"input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
|
|
{"name": "check_background", "description": "Check background task status. Omit task_id to list all.",
|
|
"input_schema": {"type": "object", "properties": {"task_id": {"type": "string"}}}},
|
|
]
|
|
|
|
|
|
def agent_loop(messages: list):
|
|
while True:
|
|
# Drain background notifications and inject as system message before LLM call
|
|
notifs = BG.drain_notifications()
|
|
if notifs and messages:
|
|
notif_text = "\n".join(
|
|
f"[bg:{n['task_id']}] {n['status']}: {n['result']}" for n in notifs
|
|
)
|
|
messages.append({"role": "user", "content": f"<background-results>\n{notif_text}\n</background-results>"})
|
|
messages.append({"role": "assistant", "content": "Noted background results."})
|
|
response = client.messages.create(
|
|
model=MODEL, system=SYSTEM, messages=messages,
|
|
tools=TOOLS, max_tokens=8000,
|
|
)
|
|
messages.append({"role": "assistant", "content": response.content})
|
|
if response.stop_reason != "tool_use":
|
|
return
|
|
results = []
|
|
for block in response.content:
|
|
if block.type == "tool_use":
|
|
handler = TOOL_HANDLERS.get(block.name)
|
|
try:
|
|
output = handler(**block.input) if handler else f"Unknown tool: {block.name}"
|
|
except Exception as e:
|
|
output = f"Error: {e}"
|
|
print(f"> {block.name}: {str(output)[:200]}")
|
|
results.append({"type": "tool_result", "tool_use_id": block.id, "content": str(output)})
|
|
messages.append({"role": "user", "content": results})
|
|
|
|
|
|
if __name__ == "__main__":
|
|
history = []
|
|
while True:
|
|
try:
|
|
query = input("\033[36ms08 >> \033[0m")
|
|
except (EOFError, KeyboardInterrupt):
|
|
break
|
|
if query.strip().lower() in ("q", "exit", ""):
|
|
break
|
|
history.append({"role": "user", "content": query})
|
|
agent_loop(history)
|
|
response_content = history[-1]["content"]
|
|
if isinstance(response_content, list):
|
|
for block in response_content:
|
|
if hasattr(block, "text"):
|
|
print(block.text)
|
|
print()
|