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.
249 lines
10 KiB
Python
249 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
# Harness: persistent tasks -- goals that outlive any single conversation.
|
|
"""
|
|
s07_task_system.py - Tasks
|
|
|
|
Tasks persist as JSON files in .tasks/ so they survive context compression.
|
|
Each task has a dependency graph (blockedBy/blocks).
|
|
|
|
.tasks/
|
|
task_1.json {"id":1, "subject":"...", "status":"completed", ...}
|
|
task_2.json {"id":2, "blockedBy":[1], "status":"pending", ...}
|
|
task_3.json {"id":3, "blockedBy":[2], "blocks":[], ...}
|
|
|
|
Dependency resolution:
|
|
+----------+ +----------+ +----------+
|
|
| task 1 | --> | task 2 | --> | task 3 |
|
|
| complete | | blocked | | blocked |
|
|
+----------+ +----------+ +----------+
|
|
| ^
|
|
+--- completing task 1 removes it from task 2's blockedBy
|
|
|
|
Key insight: "State that survives compression -- because it's outside the conversation."
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
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"]
|
|
TASKS_DIR = WORKDIR / ".tasks"
|
|
|
|
SYSTEM = f"You are a coding agent at {WORKDIR}. Use task tools to plan and track work."
|
|
|
|
|
|
# -- TaskManager: CRUD with dependency graph, persisted as JSON files --
|
|
class TaskManager:
|
|
def __init__(self, tasks_dir: Path):
|
|
self.dir = tasks_dir
|
|
self.dir.mkdir(exist_ok=True)
|
|
self._next_id = self._max_id() + 1
|
|
|
|
def _max_id(self) -> int:
|
|
ids = [int(f.stem.split("_")[1]) for f in self.dir.glob("task_*.json")]
|
|
return max(ids) if ids else 0
|
|
|
|
def _load(self, task_id: int) -> dict:
|
|
path = self.dir / f"task_{task_id}.json"
|
|
if not path.exists():
|
|
raise ValueError(f"Task {task_id} not found")
|
|
return json.loads(path.read_text())
|
|
|
|
def _save(self, task: dict):
|
|
path = self.dir / f"task_{task['id']}.json"
|
|
path.write_text(json.dumps(task, indent=2))
|
|
|
|
def create(self, subject: str, description: str = "") -> str:
|
|
task = {
|
|
"id": self._next_id, "subject": subject, "description": description,
|
|
"status": "pending", "blockedBy": [], "blocks": [], "owner": "",
|
|
}
|
|
self._save(task)
|
|
self._next_id += 1
|
|
return json.dumps(task, indent=2)
|
|
|
|
def get(self, task_id: int) -> str:
|
|
return json.dumps(self._load(task_id), indent=2)
|
|
|
|
def update(self, task_id: int, status: str = None,
|
|
add_blocked_by: list = None, add_blocks: list = None) -> str:
|
|
task = self._load(task_id)
|
|
if status:
|
|
if status not in ("pending", "in_progress", "completed"):
|
|
raise ValueError(f"Invalid status: {status}")
|
|
task["status"] = status
|
|
# When a task is completed, remove it from all other tasks' blockedBy
|
|
if status == "completed":
|
|
self._clear_dependency(task_id)
|
|
if add_blocked_by:
|
|
task["blockedBy"] = list(set(task["blockedBy"] + add_blocked_by))
|
|
if add_blocks:
|
|
task["blocks"] = list(set(task["blocks"] + add_blocks))
|
|
# Bidirectional: also update the blocked tasks' blockedBy lists
|
|
for blocked_id in add_blocks:
|
|
try:
|
|
blocked = self._load(blocked_id)
|
|
if task_id not in blocked["blockedBy"]:
|
|
blocked["blockedBy"].append(task_id)
|
|
self._save(blocked)
|
|
except ValueError:
|
|
pass
|
|
self._save(task)
|
|
return json.dumps(task, indent=2)
|
|
|
|
def _clear_dependency(self, completed_id: int):
|
|
"""Remove completed_id from all other tasks' blockedBy lists."""
|
|
for f in self.dir.glob("task_*.json"):
|
|
task = json.loads(f.read_text())
|
|
if completed_id in task.get("blockedBy", []):
|
|
task["blockedBy"].remove(completed_id)
|
|
self._save(task)
|
|
|
|
def list_all(self) -> str:
|
|
tasks = []
|
|
for f in sorted(self.dir.glob("task_*.json")):
|
|
tasks.append(json.loads(f.read_text()))
|
|
if not tasks:
|
|
return "No tasks."
|
|
lines = []
|
|
for t in tasks:
|
|
marker = {"pending": "[ ]", "in_progress": "[>]", "completed": "[x]"}.get(t["status"], "[?]")
|
|
blocked = f" (blocked by: {t['blockedBy']})" if t.get("blockedBy") else ""
|
|
lines.append(f"{marker} #{t['id']}: {t['subject']}{blocked}")
|
|
return "\n".join(lines)
|
|
|
|
|
|
TASKS = TaskManager(TASKS_DIR)
|
|
|
|
|
|
# -- Base 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"]),
|
|
"task_create": lambda **kw: TASKS.create(kw["subject"], kw.get("description", "")),
|
|
"task_update": lambda **kw: TASKS.update(kw["task_id"], kw.get("status"), kw.get("addBlockedBy"), kw.get("addBlocks")),
|
|
"task_list": lambda **kw: TASKS.list_all(),
|
|
"task_get": lambda **kw: TASKS.get(kw["task_id"]),
|
|
}
|
|
|
|
TOOLS = [
|
|
{"name": "bash", "description": "Run a shell command.",
|
|
"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": "task_create", "description": "Create a new task.",
|
|
"input_schema": {"type": "object", "properties": {"subject": {"type": "string"}, "description": {"type": "string"}}, "required": ["subject"]}},
|
|
{"name": "task_update", "description": "Update a task's status or dependencies.",
|
|
"input_schema": {"type": "object", "properties": {"task_id": {"type": "integer"}, "status": {"type": "string", "enum": ["pending", "in_progress", "completed"]}, "addBlockedBy": {"type": "array", "items": {"type": "integer"}}, "addBlocks": {"type": "array", "items": {"type": "integer"}}}, "required": ["task_id"]}},
|
|
{"name": "task_list", "description": "List all tasks with status summary.",
|
|
"input_schema": {"type": "object", "properties": {}}},
|
|
{"name": "task_get", "description": "Get full details of a task by ID.",
|
|
"input_schema": {"type": "object", "properties": {"task_id": {"type": "integer"}}, "required": ["task_id"]}},
|
|
]
|
|
|
|
|
|
def agent_loop(messages: list):
|
|
while True:
|
|
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[36ms07 >> \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()
|