diff --git a/agents/openai_code/s01_agent_loop_openai.py b/agents/openai_code/s01_agent_loop_openai.py
new file mode 100644
index 000000000..16ead1b3e
--- /dev/null
+++ b/agents/openai_code/s01_agent_loop_openai.py
@@ -0,0 +1,180 @@
+#!/usr/bin/env python3
+# Harness: the loop -- the model's first connection to the real world.
+"""
+s01_agent_loop.py - The Agent Loop
+
+"""
+
+import os
+import subprocess
+import json
+import sys
+
+try:
+ import readline
+ readline.parse_and_bind('set bind-tty-special-chars off')
+ readline.parse_and_bind('set input-meta on')
+ readline.parse_and_bind('set output-meta on')
+ readline.parse_and_bind('set convert-meta off')
+ readline.parse_and_bind('set enable-meta-keybindings on')
+except ImportError:
+ pass
+
+from openai import OpenAI
+from dotenv import load_dotenv
+
+load_dotenv(override=True)
+
+# 在文件开头设置
+os.environ['PYTHONPATH'] = r"E:\ai_pycode\learn-claude-code-main"
+sys.path.insert(0, r"E:\ai_pycode\learn-claude-code-main")
+
+
+# 初始化 OpenAI 客户端(兼容阿里云百炼)
+client = OpenAI(
+ base_url=os.getenv("OPENAI_BASE_URL"),
+ api_key=os.getenv("OPENAI_API_KEY")
+)
+MODEL = os.getenv("OPENAI_MODEL_ID")
+
+SYSTEM = f"You are a coding agent at {os.getcwd()}. Use bash to solve tasks. Act, don't explain."
+
+# 转换工具格式为 OpenAI function calling 格式
+TOOLS = [{
+ "type": "function",
+ "function": {
+ "name": "bash",
+ "description": "Run a shell command.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "command": {
+ "type": "string",
+ "description": "The shell command to execute"
+ }
+ },
+ "required": ["command"],
+ },
+ }
+}]
+
+
+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:
+
+ # Windows 下使用 cmd,其他系统使用 bash
+ if sys.platform == "win32":
+ # Windows 系统
+ r = subprocess.run(
+ command,
+ shell=True,
+ cwd=os.getcwd(),
+ capture_output=True,
+ text=True,
+ timeout=120,
+ encoding='utf-8', # 明确指定编码
+ errors='replace' # 遇到无法解码的字符时替换
+ )
+ else:
+ # Linux/Mac 系统
+ r = subprocess.run(
+ command,
+ shell=True,
+ cwd=os.getcwd(),
+ capture_output=True,
+ text=True,
+ timeout=120,
+ executable='/bin/bash'
+ )
+
+ # 安全地获取输出,处理 None 的情况
+ stdout = r.stdout if r.stdout is not None else ""
+ stderr = r.stderr if r.stderr is not None else ""
+ out = (stdout + stderr).strip()
+
+ return out[:50000] if out else "(no output)"
+ except subprocess.TimeoutExpired:
+ return "Error: Timeout (120s)"
+ except (FileNotFoundError, OSError) as e:
+ return f"Error: {e}"
+
+
+def agent_loop(messages: list):
+ while True:
+ # 调用阿里云百炼 API
+ response = client.chat.completions.create(
+ model=MODEL,
+ messages=[
+ {"role": "system", "content": SYSTEM},
+ *messages
+ ],
+ tools=TOOLS,
+ tool_choice="auto",
+ max_tokens=8000,
+ temperature=0.7,
+ )
+
+ # 获取 assistant 的回复
+ assistant_message = response.choices[0].message
+ messages.append({
+ "role": "assistant",
+ "content": assistant_message.content,
+ "tool_calls": assistant_message.tool_calls
+ })
+
+ # 如果没有 tool_calls,结束循环
+ if not assistant_message.tool_calls:
+ return
+ # 执行每个 tool call
+ for tool_call in assistant_message.tool_calls:
+ if tool_call.function.name == "bash":
+ # 解析命令
+ try:
+ command_args = json.loads(tool_call.function.arguments)
+ command = command_args.get("command", "")
+
+ if not command:
+ output = "Error: No command provided"
+ else:
+ print(f"\033[33m$ {command}\033[0m")
+ output = run_bash(command)
+ print(output[:200])
+
+ # 添加 tool result
+ messages.append({
+ "role": "tool",
+ "tool_call_id": tool_call.id,
+ "content": output
+ })
+ except json.JSONDecodeError as e:
+ error_msg = f"Error parsing command arguments: {e}"
+ messages.append({
+ "role": "tool",
+ "tool_call_id": tool_call.id,
+ "content": error_msg
+ })
+
+
+if __name__ == "__main__":
+ history = []
+ while True:
+ try:
+ query = input("\033[36ms01 >> \033[0m")
+ except (EOFError, KeyboardInterrupt):
+ break
+ if query.strip().lower() in ("q", "exit", ""):
+ break
+
+ history.append({"role": "user", "content": query})
+ agent_loop(history)
+
+ # 打印最终回复(跳过 tool 消息)
+ if history and history[-1]["role"] == "assistant":
+ if history[-1]["content"]:
+ print(history[-1]["content"])
+
+ print()
\ No newline at end of file
diff --git a/agents/openai_code/s02_tool_use_openai.py b/agents/openai_code/s02_tool_use_openai.py
new file mode 100644
index 000000000..c8513767a
--- /dev/null
+++ b/agents/openai_code/s02_tool_use_openai.py
@@ -0,0 +1,284 @@
+#!/usr/bin/env python3
+# Harness: tool dispatch -- expanding what the model can reach.
+"""
+s02_tool_use.py - Tools (OpenAI 版本)
+
+将 Anthropic 接口转换为 OpenAI 接口,支持多工具调用。
+"""
+
+import os
+import subprocess
+import json
+import sys
+from pathlib import Path
+from typing import List, Dict, Any
+
+from openai import OpenAI
+from dotenv import load_dotenv
+
+# 加载环境变量
+load_dotenv(override=True)
+
+# 在文件开头设置
+os.environ['PYTHONPATH'] = r"E:\ai_pycode\learn-claude-code-main"
+sys.path.insert(0, r"E:\ai_pycode\learn-claude-code-main")
+
+# 工作目录
+WORKDIR = Path.cwd()
+
+# 初始化 OpenAI 客户端(兼容阿里云百炼)
+client = OpenAI(
+ base_url=os.getenv("OPENAI_BASE_URL"),
+ api_key=os.getenv("OPENAI_API_KEY")
+)
+MODEL = os.getenv("OPENAI_MODEL_ID")
+
+SYSTEM = f"You are a coding agent at {WORKDIR}. Use tools to solve tasks. Act, don't explain."
+
+
+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:
+ """执行 shell 命令"""
+ dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
+ if any(d in command for d in dangerous):
+ return "Error: Dangerous command blocked"
+
+ try:
+ if sys.platform == "win32":
+ r = subprocess.run(
+ command, shell=True, cwd=WORKDIR,
+ capture_output=True, text=True, timeout=120,
+ encoding='utf-8', errors='replace'
+ )
+ else:
+ r = subprocess.run(
+ command, shell=True, cwd=WORKDIR,
+ capture_output=True, text=True, timeout=120,
+ executable='/bin/bash'
+ )
+
+ stdout = r.stdout if r.stdout is not None else ""
+ stderr = r.stderr if r.stderr is not None else ""
+ out = (stdout + stderr).strip()
+ return out[:50000] if out else "(no output)"
+
+ except subprocess.TimeoutExpired:
+ return "Error: Timeout (120s)"
+ except Exception as e:
+ return f"Error: {e}"
+
+
+def run_read(path: str, limit: int = None) -> str:
+ """读取文件内容"""
+ try:
+ text = safe_path(path).read_text(encoding='utf-8', errors='replace')
+ lines = text.splitlines()
+ if limit and limit < len(lines):
+ lines = lines[:limit] + [f"... ({len(lines) - limit} more lines)"]
+ 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, encoding='utf-8')
+ return f"Wrote {len(content)} bytes to {path}"
+ 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)
+ content = fp.read_text(encoding='utf-8')
+ if old_text not in content:
+ return f"Error: Text not found in {path}"
+ fp.write_text(content.replace(old_text, new_text, 1), encoding='utf-8')
+ 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"]),
+}
+
+# -- OpenAI function calling 格式的工具定义 --
+TOOLS = [
+ {
+ "type": "function",
+ "function": {
+ "name": "bash",
+ "description": "Run a shell command.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "command": {"type": "string", "description": "The shell command to execute"}
+ },
+ "required": ["command"],
+ },
+ }
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "read_file",
+ "description": "Read file contents.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "path": {"type": "string", "description": "Path to the file"},
+ "limit": {"type": "integer", "description": "Maximum number of lines to read"}
+ },
+ "required": ["path"],
+ },
+ }
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "write_file",
+ "description": "Write content to file.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "path": {"type": "string", "description": "Path to the file"},
+ "content": {"type": "string", "description": "Content to write"}
+ },
+ "required": ["path", "content"],
+ },
+ }
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "edit_file",
+ "description": "Replace exact text in file.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "path": {"type": "string", "description": "Path to the file"},
+ "old_text": {"type": "string", "description": "Text to replace"},
+ "new_text": {"type": "string", "description": "New text to insert"}
+ },
+ "required": ["path", "old_text", "new_text"],
+ },
+ }
+ },
+]
+
+
+
+
+
+def agent_loop(messages: List[Dict[str, Any]]):
+ """
+ Agent 主循环:调用模型,执行工具,直到模型不再请求工具
+ """
+ while True:
+ # 调用 OpenAI API
+ response = client.chat.completions.create(
+ model=MODEL,
+ messages=messages,
+ tools=TOOLS,
+ tool_choice="auto",
+ max_tokens=8000,
+ temperature=0.7,
+ )
+
+ # 获取 assistant 的回复
+ assistant_message = response.choices[0].message
+
+ # 将 assistant 消息添加到历史
+ messages.append({
+ "role": "assistant",
+ "content": assistant_message.content,
+ "tool_calls": assistant_message.tool_calls
+ })
+
+ # 如果没有工具调用,结束循环
+ if not assistant_message.tool_calls:
+ return
+
+ # 执行每个工具调用
+ results = []
+ for tool_call in assistant_message.tool_calls:
+ # 获取工具名称和参数
+ tool_name = tool_call.function.name
+ arguments = json.loads(tool_call.function.arguments)
+
+ # 查找并执行对应的处理函数
+ handler = TOOL_HANDLERS.get(tool_name)
+ if handler:
+ print(f"> {tool_name}:")
+ output = handler(**arguments)
+ print(output[:200])
+ else:
+ output = f"Unknown tool: {tool_name}"
+ print(f"> {tool_name}: Unknown tool")
+
+ # 添加工具结果
+ results.append({
+ "role": "tool",
+ "tool_call_id": tool_call.id,
+ "content": output
+ })
+
+ # 将所有工具结果添加到消息历史
+ messages.extend(results)
+
+
+def print_response(messages: List[Dict[str, Any]]):
+ """打印最终回复内容"""
+ if not messages:
+ return
+
+ last_message = messages[-1]
+ if last_message.get("role") == "assistant":
+ content = last_message.get("content")
+ if content:
+ print(content)
+ elif last_message.get("role") == "tool":
+ # 如果最后一个是工具结果,打印前一条 assistant 消息
+ for msg in reversed(messages):
+ if msg.get("role") == "assistant" and msg.get("content"):
+ print(msg["content"])
+ break
+
+
+if __name__ == "__main__":
+ history = []
+ while True:
+ try:
+ query = input("\033[36ms02 >> \033[0m")
+ except (EOFError, KeyboardInterrupt):
+ break
+
+ if query.strip().lower() in ("q", "exit", ""):
+ break
+
+ # 添加用户消息
+ history.append({"role": "user", "content": query})
+
+ # 运行 agent 循环
+ agent_loop(history)
+
+ # 打印回复
+ print_response(history)
+ print()
\ No newline at end of file
diff --git a/agents/openai_code/s03_todo_write_openai.py b/agents/openai_code/s03_todo_write_openai.py
new file mode 100644
index 000000000..e1b990b72
--- /dev/null
+++ b/agents/openai_code/s03_todo_write_openai.py
@@ -0,0 +1,281 @@
+#!/usr/bin/env python3
+# Harness: planning -- keeping the model on course without scripting the route.
+"""
+s03_todo_write_openai.py - TodoWrite (OpenAI version)
+
+The model tracks its own progress via a TodoManager. A nag reminder
+forces it to keep updating when it forgets.
+"""
+
+import json
+import os
+import subprocess
+from pathlib import Path
+
+from openai import OpenAI
+from dotenv import load_dotenv
+import sys
+load_dotenv(override=True)
+# 在文件开头设置
+os.environ['PYTHONPATH'] = r"E:\ai_pycode\learn-claude-code-main"
+sys.path.insert(0, r"E:\ai_pycode\learn-claude-code-main")
+
+
+
+WORKDIR = Path.cwd()
+client = OpenAI(
+ base_url=os.getenv("OPENAI_BASE_URL"),
+ api_key=os.getenv("OPENAI_API_KEY"),
+)
+MODEL = os.getenv("OPENAI_MODEL_ID")
+
+SYSTEM = f"""You are a coding agent at {WORKDIR}.
+Use the todo tool to plan multi-step tasks. Mark in_progress before starting, completed when done.
+Prefer tools over prose."""
+
+
+# -- TodoManager: structured state the LLM writes to --
+class TodoManager:
+ def __init__(self):
+ self.items = []
+
+ def update(self, items: list) -> str:
+ if len(items) > 20:
+ raise ValueError("Max 20 todos allowed")
+ validated = []
+ in_progress_count = 0
+ for i, item in enumerate(items):
+ text = str(item.get("text", "")).strip()
+ status = str(item.get("status", "pending")).lower()
+ item_id = str(item.get("id", str(i + 1)))
+ if not text:
+ raise ValueError(f"Item {item_id}: text required")
+ if status not in ("pending", "in_progress", "completed"):
+ raise ValueError(f"Item {item_id}: invalid status '{status}'")
+ if status == "in_progress":
+ in_progress_count += 1
+ validated.append({"id": item_id, "text": text, "status": status})
+ if in_progress_count > 1:
+ raise ValueError("Only one task can be in_progress at a time")
+ self.items = validated
+ return self.render()
+
+ def render(self) -> str:
+ if not self.items:
+ return "No todos."
+ lines = []
+ for item in self.items:
+ marker = {"pending": "[ ]", "in_progress": "[>]", "completed": "[x]"}[item["status"]]
+ lines.append(f"{marker} #{item['id']}: {item['text']}")
+ done = sum(1 for t in self.items if t["status"] == "completed")
+ lines.append(f"\n({done}/{len(self.items)} completed)")
+ return "\n".join(lines)
+
+
+TODO = TodoManager()
+
+
+# -- 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)
+ content = fp.read_text()
+ if old_text not in content:
+ return f"Error: Text not found in {path}"
+ fp.write_text(content.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"]),
+ "todo": lambda **kw: TODO.update(kw["items"]),
+}
+
+TOOLS = [
+ {
+ "type": "function",
+ "function": {
+ "name": "bash",
+ "description": "Run a shell command.",
+ "parameters": {
+ "type": "object",
+ "properties": {"command": {"type": "string"}},
+ "required": ["command"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "read_file",
+ "description": "Read file contents.",
+ "parameters": {
+ "type": "object",
+ "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}},
+ "required": ["path"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "write_file",
+ "description": "Write content to file.",
+ "parameters": {
+ "type": "object",
+ "properties": {"path": {"type": "string"}, "content": {"type": "string"}},
+ "required": ["path", "content"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "edit_file",
+ "description": "Replace exact text in file.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "path": {"type": "string"},
+ "old_text": {"type": "string"},
+ "new_text": {"type": "string"},
+ },
+ "required": ["path", "old_text", "new_text"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "todo",
+ "description": "Update task list. Track progress on multi-step tasks.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "items": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {"type": "string"},
+ "text": {"type": "string"},
+ "status": {
+ "type": "string",
+ "enum": ["pending", "in_progress", "completed"],
+ },
+ },
+ "required": ["id", "text", "status"],
+ },
+ }
+ },
+ "required": ["items"],
+ },
+ },
+ },
+]
+
+
+# -- Agent loop with nag reminder injection --
+def agent_loop(messages: list):
+ rounds_since_todo = 0
+ while True:
+ response = client.chat.completions.create(
+ model=MODEL,
+ messages=[{"role": "system", "content": SYSTEM},*messages],
+ tools=TOOLS,
+ tool_choice="auto",
+ max_tokens=8000,
+ temperature=0.7,
+ )
+ assistant_message = response.choices[0].message
+ messages.append(
+ {"role": "assistant", "content": assistant_message.content or "", "tool_calls": assistant_message.tool_calls}
+ )
+
+ if not assistant_message.tool_calls:
+ return
+
+ used_todo = False
+ for tool_call in assistant_message.tool_calls:
+ tool_name = tool_call.function.name
+ handler = TOOL_HANDLERS.get(tool_name)
+ try:
+ args = json.loads(tool_call.function.arguments or "{}")
+ output = handler(**args) if handler else f"Unknown tool: {tool_name}"
+ except Exception as e:
+ output = f"Error: {e}"
+ print(f"> {tool_name}:")
+ print(str(output)[:200])
+ messages.append(
+ {
+ "role": "tool",
+ "tool_call_id": tool_call.id,
+ "content": str(output),
+ }
+ )
+ if tool_name == "todo":
+ used_todo = True
+
+ rounds_since_todo = 0 if used_todo else rounds_since_todo + 1
+ if rounds_since_todo >= 3:
+ messages.append({"role": "user", "content": "Update your todos."})
+
+
+if __name__ == "__main__":
+ history = []
+ while True:
+ try:
+ query = input("\033[36ms03-openai >> \033[0m")
+ except (EOFError, KeyboardInterrupt):
+ break
+ if query.strip().lower() in ("q", "exit", ""):
+ break
+ history.append({"role": "user", "content": query})
+ agent_loop(history)
+ if history and history[-1]["role"] == "assistant" and history[-1].get("content"):
+ print(history[-1]["content"])
+ print()
diff --git a/agents/openai_code/s04_subagent_openai.py b/agents/openai_code/s04_subagent_openai.py
new file mode 100644
index 000000000..510b65a40
--- /dev/null
+++ b/agents/openai_code/s04_subagent_openai.py
@@ -0,0 +1,328 @@
+#!/usr/bin/env python3
+# Harness: context isolation -- protecting the model's clarity of thought.
+"""
+s04_subagent.py - Subagents
+
+Spawn a child agent with fresh messages=[]. The child works in its own
+context, sharing the filesystem, then returns only a summary to the parent.
+
+ Parent agent Subagent
+ +------------------+ +------------------+
+ | messages=[...] | | messages=[] | <-- fresh
+ | | dispatch | |
+ | tool: task | ---------->| while tool_use: |
+ | prompt="..." | | call tools |
+ | description="" | | append results |
+ | | summary | |
+ | result = "..." | <--------- | return last text |
+ +------------------+ +------------------+
+ |
+ Parent context stays clean.
+ Subagent context is discarded.
+
+Key insight: "Process isolation gives context isolation for free."
+"""
+
+import json
+import os
+import subprocess
+from pathlib import Path
+
+from openai import OpenAI
+from dotenv import load_dotenv
+
+import sys
+load_dotenv(override=True)
+# 在文件开头设置
+os.environ['PYTHONPATH'] = r"E:\ai_pycode\learn-claude-code-main"
+sys.path.insert(0, r"E:\ai_pycode\learn-claude-code-main")
+
+
+# 尽量让控制台输出按 utf-8 编码,避免 Windows 默认编码导致的编码异常。
+try:
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
+ sys.stderr.reconfigure(encoding="utf-8", errors="replace")
+except Exception:
+ pass
+
+WORKDIR = Path.cwd()
+client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL"))
+MODEL = os.environ.get("OPENAI_MODEL_ID")
+
+SYSTEM = f"You are a coding agent at {WORKDIR}. Use the task tool to delegate exploration or subtasks."
+SUBAGENT_SYSTEM = f"You are a coding subagent at {WORKDIR}. Complete the given task, then summarize your findings."
+
+
+# -- Tool implementations shared by parent and child --
+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:
+ # Windows 下 subprocess(text=True) 默认编码可能是 gbk,遇到非 gbk 字节会解码失败。
+ # 这里强制 utf-8 并用 errors='replace' 保证 stdout/stderr 至少是字符串。
+ if sys.platform == "win32":
+ r = subprocess.run(
+ command,
+ shell=True,
+ cwd=WORKDIR,
+ capture_output=True,
+ text=True,
+ timeout=120,
+ encoding="utf-8",
+ errors="backslashreplace",
+ )
+ else:
+ r = subprocess.run(
+ command,
+ shell=True,
+ cwd=WORKDIR,
+ capture_output=True,
+ text=True,
+ timeout=120,
+ executable="/bin/bash",
+ encoding="utf-8",
+ errors="backslashreplace",
+ )
+
+ stdout = r.stdout or ""
+ stderr = r.stderr or ""
+ out = (stdout + stderr).strip()
+ return out[:50000] if out else "(no output)"
+ except subprocess.TimeoutExpired:
+ return "Error: Timeout (120s)"
+ except (FileNotFoundError, OSError) as e:
+ return f"Error: {e}"
+
+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)
+ content = fp.read_text()
+ if old_text not in content:
+ return f"Error: Text not found in {path}"
+ fp.write_text(content.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"]),
+}
+
+# Child gets all base tools except task (no recursive spawning)
+CHILD_TOOLS = [
+ {
+ "type": "function",
+ "function": {
+ "name": "bash",
+ "description": "Run a shell command.",
+ "parameters": {
+ "type": "object",
+ "properties": {"command": {"type": "string"}},
+ "required": ["command"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "read_file",
+ "description": "Read file contents.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "path": {"type": "string"},
+ "limit": {"type": "integer"},
+ },
+ "required": ["path"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "write_file",
+ "description": "Write content to file.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "path": {"type": "string"},
+ "content": {"type": "string"},
+ },
+ "required": ["path", "content"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "edit_file",
+ "description": "Replace exact text in file.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "path": {"type": "string"},
+ "old_text": {"type": "string"},
+ "new_text": {"type": "string"},
+ },
+ "required": ["path", "old_text", "new_text"],
+ },
+ },
+ },
+]
+
+
+# -- Subagent: fresh context, filtered tools, summary-only return --
+def run_subagent(prompt: str) -> str:
+ # fresh context: system+user only
+ sub_messages = [
+ {"role": "system", "content": SUBAGENT_SYSTEM},
+ {"role": "user", "content": prompt},
+ ]
+ last_assistant_content = "(no summary)"
+
+ for _ in range(30): # safety limit
+ response = client.chat.completions.create(
+ model=MODEL,
+ messages=sub_messages,
+ tools=CHILD_TOOLS,
+ tool_choice="auto",
+ max_tokens=8000,
+ temperature=0.7,
+ )
+ assistant_message = response.choices[0].message
+ last_assistant_content = assistant_message.content or last_assistant_content
+
+ sub_messages.append(
+ {
+ "role": "assistant",
+ "content": assistant_message.content or "",
+ "tool_calls": assistant_message.tool_calls,
+ }
+ )
+ if not assistant_message.tool_calls:
+ break
+
+ for tool_call in assistant_message.tool_calls:
+ tool_name = tool_call.function.name
+ args = json.loads(tool_call.function.arguments or "{}")
+ handler = TOOL_HANDLERS.get(tool_name)
+ output = handler(**args) if handler else f"Unknown tool: {tool_name}"
+
+ sub_messages.append(
+ {
+ "role": "tool",
+ "tool_call_id": tool_call.id,
+ "content": str(output)[:50000],
+ }
+ )
+
+ # Only the final text returns to the parent -- child context is discarded
+ return last_assistant_content
+
+
+# -- Parent tools: base tools + task dispatcher --
+PARENT_TOOLS = CHILD_TOOLS + [
+ {
+ "type": "function",
+ "function": {
+ "name": "task",
+ "description": "Spawn a subagent with fresh context. It shares the filesystem but not conversation history.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "prompt": {"type": "string"},
+ "description": {"type": "string"},
+ },
+ "required": ["prompt"],
+ },
+ },
+ },
+]
+
+
+def agent_loop(messages: list):
+ while True:
+ response = client.chat.completions.create(
+ model=MODEL,
+ messages=[{"role": "system", "content": SYSTEM}, *messages],
+ tools=PARENT_TOOLS,
+ tool_choice="auto",
+ max_tokens=8000,
+ temperature=0.7,
+ )
+ assistant_message = response.choices[0].message
+ messages.append(
+ {
+ "role": "assistant",
+ "content": assistant_message.content or "",
+ "tool_calls": assistant_message.tool_calls,
+ }
+ )
+ if not assistant_message.tool_calls:
+ return
+ for tool_call in assistant_message.tool_calls:
+ tool_name = tool_call.function.name
+ args = json.loads(tool_call.function.arguments or "{}")
+
+ if tool_name == "task":
+ desc = args.get("description", "subtask")
+ prompt = args.get("prompt", "")
+ print(f"> task ({desc}): {prompt[:80]}")
+ output = run_subagent(prompt)
+ else:
+ handler = TOOL_HANDLERS.get(tool_name)
+ output = handler(**args) if handler else f"Unknown tool: {tool_name}"
+
+ print(f" {str(output)[:200]}")
+ messages.append(
+ {
+ "role": "tool",
+ "tool_call_id": tool_call.id,
+ "content": str(output)[:50000],
+ }
+ )
+# 其实还可以弄一个机制,父agent能够指定分发给子agent的工具
+
+if __name__ == "__main__":
+ history = []
+ while True:
+ try:
+ query = input("\033[36ms04 >> \033[0m")
+ except (EOFError, KeyboardInterrupt):
+ break
+ if query.strip().lower() in ("q", "exit", ""):
+ break
+ history.append({"role": "user", "content": query})
+ agent_loop(history)
+ if history and history[-1]["role"] == "assistant" and history[-1].get("content"):
+ print(history[-1]["content"])
+ print()
diff --git a/agents/openai_code/s05_skill_loading_openai.py b/agents/openai_code/s05_skill_loading_openai.py
new file mode 100644
index 000000000..390b66ebf
--- /dev/null
+++ b/agents/openai_code/s05_skill_loading_openai.py
@@ -0,0 +1,335 @@
+#!/usr/bin/env python3
+# Harness: on-demand knowledge -- domain expertise, loaded when the model asks.
+"""
+s05_subagent_openai.py - Skills (OpenAI version)
+
+This file is an OpenAI adaptation of `agents/s05_skill_loading.py`.
+
+The model loads specialized knowledge only when it calls `load_skill("...")`.
+"""
+
+import json
+import os
+import re
+import subprocess
+import sys
+from pathlib import Path
+
+import yaml
+from dotenv import load_dotenv
+from openai import OpenAI
+
+load_dotenv(override=True)
+
+# Keep imports stable when running from this directory
+os.environ["PYTHONPATH"] = r"E:\ai_pycode\learn-claude-code-main"
+sys.path.insert(0, r"E:\ai_pycode\learn-claude-code-main")
+
+
+# Try to prevent Windows console encoding issues during tool output printing.
+try:
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
+ sys.stderr.reconfigure(encoding="utf-8", errors="replace")
+except Exception:
+ pass
+
+
+WORKDIR = Path.cwd()
+client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL"))
+MODEL = os.environ.get("OPENAI_MODEL_ID") or os.environ.get("MODEL_ID")
+
+SKILLS_DIR = WORKDIR / "skills"
+
+SYSTEM = "" # will be filled after SKILL_LOADER is created
+
+
+class SkillLoader:
+ """Load skills from skills//SKILL.md using YAML frontmatter."""
+
+ def __init__(self, skills_dir: Path):
+ self.skills_dir = skills_dir
+ self.skills = {}
+ self._load_all()
+
+ def _load_all(self):
+ if not self.skills_dir.exists():
+ return
+ for f in sorted(self.skills_dir.rglob("SKILL.md")):
+ text = f.read_text(encoding="utf-8", errors="backslashreplace")
+ meta, body = self._parse_frontmatter(text)
+ name = meta.get("name", f.parent.name)
+ self.skills[name] = {"meta": meta, "body": body, "path": str(f)}
+
+ def _parse_frontmatter(self, text: str) -> tuple:
+ """Parse YAML frontmatter between --- delimiters."""
+ match = re.match(r"^---\n(.*?)\n---\n(.*)", text, re.DOTALL)
+ if not match:
+ return {}, text
+ try:
+ meta = yaml.safe_load(match.group(1)) or {}
+ except yaml.YAMLError:
+ meta = {}
+ return meta, match.group(2).strip()
+
+ def get_descriptions(self) -> str:
+ """Layer 1: short descriptions for the system prompt."""
+ if not self.skills:
+ return "(no skills available)"
+ lines = []
+ for name, skill in self.skills.items():
+ desc = skill["meta"].get("description", "No description")
+ tags = skill["meta"].get("tags", "")
+ line = f" - {name}: {desc}"
+ if tags:
+ line += f" [{tags}]"
+ lines.append(line)
+ return "\n".join(lines)
+
+ def get_content(self, name: str) -> str:
+ """Layer 2: full skill body returned in tool output."""
+ skill = self.skills.get(name)
+ if not skill:
+ avail = ", ".join(self.skills.keys())
+ return f"Error: Unknown skill '{name}'. Available: {avail}"
+ return f"\n{skill['body']}\n"
+
+
+SKILL_LOADER = SkillLoader(SKILLS_DIR)
+
+SYSTEM = f"""You are a coding agent at {WORKDIR}.
+Use load_skill to access specialized knowledge before tackling unfamiliar topics.
+
+Skills available:
+{SKILL_LOADER.get_descriptions()}"""
+
+
+# -- 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:
+ if sys.platform == "win32":
+ r = subprocess.run(
+ command,
+ shell=True,
+ cwd=WORKDIR,
+ capture_output=True,
+ text=True,
+ timeout=120,
+ encoding="utf-8",
+ errors="backslashreplace",
+ )
+ else:
+ r = subprocess.run(
+ command,
+ shell=True,
+ cwd=WORKDIR,
+ capture_output=True,
+ text=True,
+ timeout=120,
+ executable="/bin/bash",
+ encoding="utf-8",
+ errors="backslashreplace",
+ )
+ stdout = r.stdout or ""
+ stderr = r.stderr or ""
+ out = (stdout + stderr).strip()
+ return out[:50000] if out else "(no output)"
+ except subprocess.TimeoutExpired:
+ return "Error: Timeout (120s)"
+ except Exception as e:
+ return f"Error: {e}"
+
+
+def run_read(path: str, limit: int = None) -> str:
+ try:
+ text = safe_path(path).read_text(encoding="utf-8", errors="backslashreplace")
+ lines = 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, encoding="utf-8", errors="backslashreplace")
+ 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)
+ content = fp.read_text(encoding="utf-8", errors="backslashreplace")
+ if old_text not in content:
+ return f"Error: Text not found in {path}"
+ fp.write_text(content.replace(old_text, new_text, 1), encoding="utf-8", errors="backslashreplace")
+ 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"]),
+ "load_skill": lambda **kw: SKILL_LOADER.get_content(kw["name"]),
+}
+
+
+TOOLS = [
+ {
+ "type": "function",
+ "function": {
+ "name": "bash",
+ "description": "Run a shell command.",
+ "parameters": {
+ "type": "object",
+ "properties": {"command": {"type": "string"}},
+ "required": ["command"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "read_file",
+ "description": "Read file contents.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "path": {"type": "string"},
+ "limit": {"type": "integer"},
+ },
+ "required": ["path"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "write_file",
+ "description": "Write content to file.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "path": {"type": "string"},
+ "content": {"type": "string"},
+ },
+ "required": ["path", "content"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "edit_file",
+ "description": "Replace exact text in file.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "path": {"type": "string"},
+ "old_text": {"type": "string"},
+ "new_text": {"type": "string"},
+ },
+ "required": ["path", "old_text", "new_text"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "load_skill",
+ "description": "Load specialized knowledge by name.",
+ "parameters": {
+ "type": "object",
+ "properties": {"name": {"type": "string", "description": "Skill name to load"}},
+ "required": ["name"],
+ },
+ },
+ },
+]
+
+
+def agent_loop(messages: list):
+ while True:
+ response = client.chat.completions.create(
+ model=MODEL,
+ messages=[{"role": "system", "content": SYSTEM}, *messages],
+ tools=TOOLS,
+ tool_choice="auto",
+ max_tokens=8000,
+ temperature=0.7,
+ )
+
+ assistant_message = response.choices[0].message
+ messages.append(
+ {
+ "role": "assistant",
+ "content": assistant_message.content or "",
+ "tool_calls": assistant_message.tool_calls,
+ }
+ )
+
+ if not assistant_message.tool_calls:
+ return
+
+ for tool_call in assistant_message.tool_calls:
+ tool_name = tool_call.function.name
+ args = {}
+ try:
+ args = json.loads(tool_call.function.arguments or "{}") # type: ignore[name-defined]
+ except Exception:
+ args = {}
+
+ handler = TOOL_HANDLERS.get(tool_name)
+ try:
+ output = handler(**args) if handler else f"Unknown tool: {tool_name}"
+ except Exception as e:
+ output = f"Error: {e}"
+
+ print(f"> {tool_name}:")
+ print(str(output)[:200])
+
+ messages.append(
+ {
+ "role": "tool",
+ "tool_call_id": tool_call.id,
+ "content": str(output)[:50000],
+ }
+ )
+
+
+if __name__ == "__main__":
+ history = []
+ while True:
+ try:
+ query = input("\033[36ms05 >> \033[0m")
+ except (EOFError, KeyboardInterrupt):
+ break
+
+ if query.strip().lower() in ("q", "exit", ""):
+ break
+
+ history.append({"role": "user", "content": query})
+ agent_loop(history)
+
+ if history and history[-1]["role"] == "assistant" and history[-1].get("content"):
+ print(history[-1]["content"])
+
+ print()
+
diff --git a/agents/openai_code/s06_context_compact_openai.py b/agents/openai_code/s06_context_compact_openai.py
new file mode 100644
index 000000000..167e3adb0
--- /dev/null
+++ b/agents/openai_code/s06_context_compact_openai.py
@@ -0,0 +1,343 @@
+#!/usr/bin/env python3
+# Harness: compression -- clean memory for infinite sessions.
+"""
+s06_subagent_openai.py - Compact (OpenAI version)
+
+OpenAI adaptation of s06_context_compact.py.
+"""
+
+import json
+import os
+import subprocess
+import sys
+import time
+from pathlib import Path
+
+from dotenv import load_dotenv
+from openai import OpenAI
+
+load_dotenv(override=True)
+
+# Keep imports stable when running from this directory
+os.environ["PYTHONPATH"] = r"E:\ai_pycode\learn-claude-code-main"
+sys.path.insert(0, r"E:\ai_pycode\learn-claude-code-main")
+
+try:
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
+ sys.stderr.reconfigure(encoding="utf-8", errors="replace")
+except Exception:
+ pass
+
+WORKDIR = Path.cwd()
+client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL"))
+MODEL = os.environ.get("OPENAI_MODEL_ID") or os.environ.get("MODEL_ID")
+
+SYSTEM = f"You are a coding agent at {WORKDIR}. Use tools to solve tasks."
+
+THRESHOLD = 50000
+TRANSCRIPT_DIR = WORKDIR / ".transcripts"
+KEEP_RECENT = 3
+PRESERVE_RESULT_TOOLS = {"read_file"}
+
+# 估计token数量:每个token大约4个字符
+def estimate_tokens(messages: list) -> int:
+ """Rough token count: ~4 chars per token."""
+ return len(str(messages)) // 4
+
+
+# -- Layer 1: micro_compact - replace old tool results with placeholders --
+def micro_compact(messages: list) -> list:
+ # Collect all tool messages.
+ tool_results = []
+ for idx, msg in enumerate(messages):
+ if msg.get("role") == "tool":
+ tool_results.append((idx, msg))
+
+ if len(tool_results) <= KEEP_RECENT:
+ return messages
+
+ # Build tool_call_id -> tool_name map from assistant tool_calls.
+ tool_name_map = {}
+ for msg in messages:
+ if msg.get("role") != "assistant":
+ continue
+ tool_calls = msg.get("tool_calls") or []
+ if not isinstance(tool_calls, list):
+ continue
+ for call in tool_calls:
+ try:
+ call_id = call.get("id")
+ fn = call.get("function") or {}
+ name = fn.get("name", "unknown")
+ if call_id:
+ tool_name_map[call_id] = name
+ except Exception:
+ continue
+
+ to_clear = tool_results[:-KEEP_RECENT]
+ for _, result in to_clear:
+ content = result.get("content")
+ if not isinstance(content, str) or len(content) <= 100:
+ continue
+ tool_call_id = result.get("tool_call_id", "")
+ tool_name = tool_name_map.get(tool_call_id, "unknown")
+ if tool_name in PRESERVE_RESULT_TOOLS:
+ continue
+ result["content"] = f"[Previous: used {tool_name}]"
+
+ return messages
+
+
+# -- Layer 2: auto_compact - save transcript, summarize, replace messages --
+def auto_compact(messages: list) -> list:
+ TRANSCRIPT_DIR.mkdir(exist_ok=True)
+ transcript_path = TRANSCRIPT_DIR / f"transcript_{int(time.time())}.jsonl"
+ with open(transcript_path, "w", encoding="utf-8", errors="backslashreplace") as f:
+ for msg in messages:
+ f.write(json.dumps(msg, ensure_ascii=False, default=str) + "\n")
+ print(f"[transcript saved: {transcript_path}]")
+
+ conversation_text = json.dumps(messages, ensure_ascii=False, default=str)[-80000:]
+ response = client.chat.completions.create(
+ model=MODEL,
+ messages=[
+ {
+ "role": "user",
+ "content": (
+ "Summarize this conversation for continuity. Include: "
+ "1) What was accomplished, 2) Current state, 3) Key decisions made. "
+ "Be concise but preserve critical details.\n\n" + conversation_text
+ ),
+ }
+ ],
+ max_tokens=2000,
+ temperature=0.3,
+ )
+ summary = response.choices[0].message.content or "No summary generated."
+
+ return [
+ {
+ "role": "user",
+ "content": f"[Conversation compressed. Transcript: {transcript_path}]\n\n{summary}",
+ }
+ ]
+
+
+# -- 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:
+ if sys.platform == "win32":
+ r = subprocess.run(
+ command,
+ shell=True,
+ cwd=WORKDIR,
+ capture_output=True,
+ text=True,
+ timeout=120,
+ encoding="utf-8",
+ errors="backslashreplace",
+ )
+ else:
+ r = subprocess.run(
+ command,
+ shell=True,
+ cwd=WORKDIR,
+ capture_output=True,
+ text=True,
+ timeout=120,
+ executable="/bin/bash",
+ encoding="utf-8",
+ errors="backslashreplace",
+ )
+ out = ((r.stdout or "") + (r.stderr or "")).strip()
+ return out[:50000] if out else "(no output)"
+ except subprocess.TimeoutExpired:
+ return "Error: Timeout (120s)"
+ except Exception as e:
+ return f"Error: {e}"
+
+
+def run_read(path: str, limit: int = None) -> str:
+ try:
+ lines = safe_path(path).read_text(encoding="utf-8", errors="backslashreplace").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, encoding="utf-8", errors="backslashreplace")
+ 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)
+ content = fp.read_text(encoding="utf-8", errors="backslashreplace")
+ if old_text not in content:
+ return f"Error: Text not found in {path}"
+ fp.write_text(content.replace(old_text, new_text, 1), encoding="utf-8", errors="backslashreplace")
+ 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"]),
+ "compact": lambda **kw: "Manual compression requested.",
+}
+
+TOOLS = [
+ {
+ "type": "function",
+ "function": {
+ "name": "bash",
+ "description": "Run a shell command.",
+ "parameters": {
+ "type": "object",
+ "properties": {"command": {"type": "string"}},
+ "required": ["command"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "read_file",
+ "description": "Read file contents.",
+ "parameters": {
+ "type": "object",
+ "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}},
+ "required": ["path"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "write_file",
+ "description": "Write content to file.",
+ "parameters": {
+ "type": "object",
+ "properties": {"path": {"type": "string"}, "content": {"type": "string"}},
+ "required": ["path", "content"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "edit_file",
+ "description": "Replace exact text in file.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "path": {"type": "string"},
+ "old_text": {"type": "string"},
+ "new_text": {"type": "string"},
+ },
+ "required": ["path", "old_text", "new_text"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "compact",
+ "description": "Trigger manual conversation compression.",
+ "parameters": {
+ "type": "object",
+ "properties": {"focus": {"type": "string", "description": "What to preserve in the summary"}},
+ },
+ },
+ },
+]
+
+
+def agent_loop(messages: list):
+ while True:
+ micro_compact(messages)
+ if estimate_tokens(messages) > THRESHOLD:
+ print("[auto_compact triggered]")
+ messages[:] = auto_compact(messages)
+
+ response = client.chat.completions.create(
+ model=MODEL,
+ messages=[{"role": "system", "content": SYSTEM}, *messages],
+ tools=TOOLS,
+ tool_choice="auto",
+ max_tokens=8000,
+ temperature=0.7,
+ )
+
+ assistant_message = response.choices[0].message
+ messages.append(
+ {
+ "role": "assistant",
+ "content": assistant_message.content or "",
+ "tool_calls": assistant_message.tool_calls,
+ }
+ )
+
+ if not assistant_message.tool_calls:
+ return
+
+ manual_compact = False
+ for tool_call in assistant_message.tool_calls:
+ tool_name = tool_call.function.name
+ args = json.loads(tool_call.function.arguments or "{}")
+ if tool_name == "compact":
+ manual_compact = True
+ output = "Compressing..."
+ else:
+ handler = TOOL_HANDLERS.get(tool_name)
+ try:
+ output = handler(**args) if handler else f"Unknown tool: {tool_name}"
+ except Exception as e:
+ output = f"Error: {e}"
+
+ print(f"> {tool_name}:")
+ print(str(output)[:200])
+ messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": str(output)})
+
+ if manual_compact:
+ print("[manual compact]")
+ messages[:] = auto_compact(messages)
+ return
+
+
+if __name__ == "__main__":
+ history = []
+ while True:
+ try:
+ query = input("\033[36ms06 >> \033[0m")
+ except (EOFError, KeyboardInterrupt):
+ break
+ if query.strip().lower() in ("q", "exit", ""):
+ break
+ history.append({"role": "user", "content": query})
+ agent_loop(history)
+ if history and history[-1].get("role") == "assistant" and history[-1].get("content"):
+ print(history[-1]["content"])
+ print()
+
diff --git a/agents/openai_code/s07_task_system_openai.py b/agents/openai_code/s07_task_system_openai.py
new file mode 100644
index 000000000..65436b50c
--- /dev/null
+++ b/agents/openai_code/s07_task_system_openai.py
@@ -0,0 +1,356 @@
+#!/usr/bin/env python3
+# Harness: persistent tasks -- goals that outlive any single conversation.
+
+
+import json
+import os
+import subprocess
+import sys
+from pathlib import Path
+
+from dotenv import load_dotenv
+from openai import OpenAI
+
+load_dotenv(override=True)
+
+# Keep imports stable when running from this directory
+os.environ["PYTHONPATH"] = r"E:\ai_pycode\learn-claude-code-main"
+sys.path.insert(0, r"E:\ai_pycode\learn-claude-code-main")
+
+try:
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
+ sys.stderr.reconfigure(encoding="utf-8", errors="replace")
+except Exception:
+ pass
+
+WORKDIR = Path.cwd()
+client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL"))
+MODEL = os.environ.get("OPENAI_MODEL_ID")
+TASKS_DIR = WORKDIR / ".tasks"
+
+SYSTEM = f"你是一个编程助手,工作目录是 {WORKDIR},请使用任务工具来规划和跟踪工作。任务有三种状态,一种是pending待完成,一种是blockedBy被阻塞,一种是completed已完成。"
+
+
+# 应该补充代码,初始化所有任务,如果刚开始任务1是完成的,那么任务2和3依赖于任务1,输出却依赖显示任务2和3被阻塞
+# -- 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, ensure_ascii=False))
+
+ def create(self, subject: str, description: str = "") -> str:
+ # pending表示可以做,blockedBy表示依赖任务未解除
+ task = {
+ "id": self._next_id, "subject": subject, "description": description,
+ "status": "pending", "blockedBy": [], "owner": "",
+ }
+ self._save(task)
+ self._next_id += 1
+ return json.dumps(task, indent=2, ensure_ascii=False)
+
+ def get(self, task_id: int) -> str:
+ return json.dumps(self._load(task_id), indent=2, ensure_ascii=False)
+
+ def update(self, task_id: int, status: str = None,
+ add_blocked_by: list = None, remove_blocked_by: 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
+ if status == "completed":
+ self._clear_dependency(task_id)
+ if add_blocked_by:
+ task["blockedBy"] = list(set(task["blockedBy"] + add_blocked_by))
+ if remove_blocked_by:
+ task["blockedBy"] = [x for x in task["blockedBy"] if x not in remove_blocked_by]
+ self._save(task)
+ return json.dumps(task, indent=2, ensure_ascii=False)
+
+ def _clear_dependency(self, completed_id: int):
+ """Remove completed_id from all other tasks' blockedBy lists.把已完成的任务id从其他任务的依赖列表中移除"""
+ 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 = []
+ files = sorted(
+ self.dir.glob("task_*.json"),
+ key=lambda f: int(f.stem.split("_")[1])
+ )
+ for f in files:
+ 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:
+ if sys.platform == "win32":
+ r = subprocess.run(
+ command,
+ shell=True,
+ cwd=WORKDIR,
+ capture_output=True,
+ text=True,
+ timeout=120,
+ encoding="utf-8",
+ errors="backslashreplace",
+ )
+ else:
+ r = subprocess.run(
+ command,
+ shell=True,
+ cwd=WORKDIR,
+ capture_output=True,
+ text=True,
+ timeout=120,
+ executable="/bin/bash",
+ encoding="utf-8",
+ errors="backslashreplace",
+ )
+ out = ((r.stdout or "") + (r.stderr or "")).strip()
+ return out[:50000] if out else "(no output)"
+ except subprocess.TimeoutExpired:
+ return "Error: Timeout (120s)"
+ except Exception as e:
+ return f"Error: {e}"
+
+def run_read(path: str, limit: int = None) -> str:
+ try:
+ lines = safe_path(path).read_text(encoding="utf-8", errors="backslashreplace").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, encoding="utf-8", errors="backslashreplace")
+ 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(encoding="utf-8", errors="backslashreplace")
+ if old_text not in c:
+ return f"Error: Text not found in {path}"
+ fp.write_text(c.replace(old_text, new_text, 1), encoding="utf-8", errors="backslashreplace")
+ 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("removeBlockedBy")),
+ "task_list": lambda **kw: TASKS.list_all(),
+ "task_get": lambda **kw: TASKS.get(kw["task_id"]),
+}
+
+TOOLS = [
+ {
+ "type": "function",
+ "function": {
+ "name": "bash",
+ "description": "Run a shell command.",
+ "parameters": {
+ "type": "object",
+ "properties": {"command": {"type": "string"}},
+ "required": ["command"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "read_file",
+ "description": "Read file contents.",
+ "parameters": {
+ "type": "object",
+ "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}},
+ "required": ["path"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "write_file",
+ "description": "Write content to file.",
+ "parameters": {
+ "type": "object",
+ "properties": {"path": {"type": "string"}, "content": {"type": "string"}},
+ "required": ["path", "content"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "edit_file",
+ "description": "Replace exact text in file.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "path": {"type": "string"},
+ "old_text": {"type": "string"},
+ "new_text": {"type": "string"},
+ },
+ "required": ["path", "old_text", "new_text"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "task_create",
+ "description": "Create a new task.",
+ "parameters": {
+ "type": "object",
+ "properties": {"subject": {"type": "string"}, "description": {"type": "string"}},
+ "required": ["subject"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "task_update",
+ "description": "Update a task's status or dependencies.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "task_id": {"type": "integer"},
+ "status": {"type": "string", "enum": ["pending", "in_progress", "completed"]},
+ "addBlockedBy": {"type": "array", "items": {"type": "integer"}},
+ "removeBlockedBy": {"type": "array", "items": {"type": "integer"}},
+ },
+ "required": ["task_id"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "task_list",
+ "description": "List all tasks with status summary.",
+ "parameters": {"type": "object", "properties": {}},
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "task_get",
+ "description": "Get full details of a task by ID.",
+ "parameters": {
+ "type": "object",
+ "properties": {"task_id": {"type": "integer"}},
+ "required": ["task_id"],
+ },
+ },
+ },
+]
+
+
+def agent_loop(messages: list):
+ while True:
+ response = client.chat.completions.create(
+ model=MODEL,
+ messages=[{"role": "system", "content": SYSTEM}, *messages],
+ tools=TOOLS,
+ tool_choice="auto",
+ max_tokens=8000,
+ temperature=0.7,
+ )
+
+ assistant_message = response.choices[0].message
+ messages.append(
+ {
+ "role": "assistant",
+ "content": assistant_message.content or "",
+ "tool_calls": assistant_message.tool_calls,
+ }
+ )
+
+ if not assistant_message.tool_calls:
+ return
+
+ for tool_call in assistant_message.tool_calls:
+ tool_name = tool_call.function.name
+ try:
+ args = json.loads(tool_call.function.arguments or "{}")
+ except Exception:
+ args = {}
+
+ handler = TOOL_HANDLERS.get(tool_name)
+ try:
+ output = handler(**args) if handler else f"Unknown tool: {tool_name}"
+ except Exception as e:
+ output = f"Error: {e}"
+
+ print(f"> {tool_name}:")
+ print(str(output)[:200])
+ messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": str(output)})
+
+
+if __name__ == "__main__":
+ history = []
+ while True:
+ try:
+ query = input("\033[36ms07-openai >> \033[0m")
+ except (EOFError, KeyboardInterrupt):
+ break
+ if query.strip().lower() in ("q", "exit", ""):
+ break
+ history.append({"role": "user", "content": query})
+ agent_loop(history)
+ if history and history[-1].get("role") == "assistant" and history[-1].get("content"):
+ print(history[-1]["content"])
+ print()
diff --git a/agents/openai_code/s08_background_tasks_openai.py b/agents/openai_code/s08_background_tasks_openai.py
new file mode 100644
index 000000000..6bab21c89
--- /dev/null
+++ b/agents/openai_code/s08_background_tasks_openai.py
@@ -0,0 +1,342 @@
+#!/usr/bin/env python3
+# Harness: background execution -- the model thinks while the harness waits.
+
+
+import json
+import os
+import subprocess
+import sys
+import threading
+import uuid
+from pathlib import Path
+
+from dotenv import load_dotenv
+from openai import OpenAI
+
+load_dotenv(override=True)
+
+# Keep imports stable when running from this directory
+os.environ["PYTHONPATH"] = r"E:\ai_pycode\learn-claude-code-main"
+sys.path.insert(0, r"E:\ai_pycode\learn-claude-code-main")
+
+try:
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
+ sys.stderr.reconfigure(encoding="utf-8", errors="replace")
+except Exception:
+ pass
+
+WORKDIR = Path.cwd()
+client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL"))
+MODEL = os.environ.get("OPENAI_MODEL_ID") or os.environ.get("MODEL_ID")
+
+SYSTEM = f"你是一个编程助手,工作目录是 {WORKDIR},请使用background_run工具来执行长期运行的命令。"
+
+
+# -- 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:
+ if sys.platform == "win32":
+ r = subprocess.run(
+ command,
+ shell=True,
+ cwd=WORKDIR,
+ capture_output=True,
+ text=True,
+ timeout=300,
+ encoding="utf-8",
+ errors="backslashreplace",
+ )
+ else:
+ r = subprocess.run(
+ command,
+ shell=True,
+ cwd=WORKDIR,
+ capture_output=True,
+ text=True,
+ timeout=300,
+ executable="/bin/bash",
+ encoding="utf-8",
+ errors="backslashreplace",
+ )
+ 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)'}"
+ # 不传入task_id时,列出所有任务
+ 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:
+ if sys.platform == "win32":
+ r = subprocess.run(
+ command,
+ shell=True,
+ cwd=WORKDIR,
+ capture_output=True,
+ text=True,
+ timeout=120,
+ encoding="utf-8",
+ errors="backslashreplace",
+ )
+ else:
+ r = subprocess.run(
+ command,
+ shell=True,
+ cwd=WORKDIR,
+ capture_output=True,
+ text=True,
+ timeout=120,
+ executable="/bin/bash",
+ encoding="utf-8",
+ errors="backslashreplace",
+ )
+ out = ((r.stdout or "") + (r.stderr or "")).strip()
+ return out[:50000] if out else "(no output)"
+ except subprocess.TimeoutExpired:
+ return "Error: Timeout (120s)"
+ except Exception as e:
+ return f"Error: {e}"
+
+def run_read(path: str, limit: int = None) -> str:
+ try:
+ lines = safe_path(path).read_text(encoding="utf-8", errors="backslashreplace").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, encoding="utf-8", errors="backslashreplace")
+ 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(encoding="utf-8", errors="backslashreplace")
+ if old_text not in c:
+ return f"Error: Text not found in {path}"
+ fp.write_text(c.replace(old_text, new_text, 1), encoding="utf-8", errors="backslashreplace")
+ 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 = [
+ {
+ "type": "function",
+ "function": {
+ "name": "bash",
+ "description": "Run a shell command (blocking).",
+ "parameters": {
+ "type": "object",
+ "properties": {"command": {"type": "string"}},
+ "required": ["command"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "read_file",
+ "description": "Read file contents.",
+ "parameters": {
+ "type": "object",
+ "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}},
+ "required": ["path"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "write_file",
+ "description": "Write content to file.",
+ "parameters": {
+ "type": "object",
+ "properties": {"path": {"type": "string"}, "content": {"type": "string"}},
+ "required": ["path", "content"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "edit_file",
+ "description": "Replace exact text in file.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "path": {"type": "string"},
+ "old_text": {"type": "string"},
+ "new_text": {"type": "string"},
+ },
+ "required": ["path", "old_text", "new_text"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "background_run",
+ "description": "在后台执行命令。返回任务id。",
+ "parameters": {
+ "type": "object",
+ "properties": {"command": {"type": "string"}},
+ "required": ["command"],
+ },
+ },
+ },
+ {
+ "type": "function",
+ "function": {
+ "name": "check_background",
+ "description": "检查后台任务状态;无task_id时列出全部任务。",
+ "parameters": {
+ "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"\n{notif_text}\n"})
+ response = client.chat.completions.create(
+ model=MODEL,
+ messages=[{"role": "system", "content": SYSTEM}, *messages],
+ tools=TOOLS,
+ tool_choice="auto",
+ max_tokens=8000,
+ temperature=0.7,
+ )
+
+ assistant_message = response.choices[0].message
+ messages.append(
+ {
+ "role": "assistant",
+ "content": assistant_message.content or "",
+ "tool_calls": assistant_message.tool_calls,
+ }
+ )
+
+ if not assistant_message.tool_calls:
+ return
+
+ for tool_call in assistant_message.tool_calls:
+ tool_name = tool_call.function.name
+ try:
+ args = json.loads(tool_call.function.arguments or "{}")
+ except Exception:
+ args = {}
+
+ handler = TOOL_HANDLERS.get(tool_name)
+ try:
+ output = handler(**args) if handler else f"Unknown tool: {tool_name}"
+ except Exception as e:
+ output = f"Error: {e}"
+
+ print(f"> {tool_name}:")
+ print(str(output)[:200])
+ messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": str(output)})
+
+
+if __name__ == "__main__":
+ history = []
+ while True:
+ try:
+ query = input("\033[36ms08-openai >> \033[0m")
+ except (EOFError, KeyboardInterrupt):
+ break
+ if query.strip().lower() in ("q", "exit", ""):
+ break
+ history.append({"role": "user", "content": query})
+ agent_loop(history)
+ if history and history[-1].get("role") == "assistant" and history[-1].get("content"):
+ print(history[-1]["content"])
+ print()
diff --git a/agents/openai_code/s09_agent_teams_openai.py b/agents/openai_code/s09_agent_teams_openai.py
new file mode 100644
index 000000000..58bd60579
--- /dev/null
+++ b/agents/openai_code/s09_agent_teams_openai.py
@@ -0,0 +1,441 @@
+#!/usr/bin/env python3
+# Harness: team mailboxes -- multiple models, coordinated through files.
+"""
+s09_agent_teams.py - Agent Teams
+
+Persistent named agents with file-based JSONL inboxes. Each teammate runs
+its own agent loop in a separate thread. Communication via append-only inboxes.
+
+ Subagent (s04): spawn -> execute -> return summary -> destroyed
+ Teammate (s09): spawn -> work -> idle -> work -> ... -> shutdown
+
+ .team/config.json .team/inbox/
+ +----------------------------+ +------------------+
+ | {"team_name": "default", | | alice.jsonl |
+ | "members": [ | | bob.jsonl |
+ | {"name":"alice", | | lead.jsonl |
+ | "role":"coder", | +------------------+
+ | "status":"idle"} |
+ | ]} | send_message("alice", "fix bug"):
+ +----------------------------+ open("alice.jsonl", "a").write(msg)
+
+ read_inbox("alice"):
+ spawn_teammate("alice","coder",...) msgs = [json.loads(l) for l in ...]
+ | open("alice.jsonl", "w").close()
+ v return msgs # drain
+ Thread: alice Thread: bob
+ +------------------+ +------------------+
+ | agent_loop | | agent_loop |
+ | status: working | | status: idle |
+ | ... runs tools | | ... waits ... |
+ | status -> idle | | |
+ +------------------+ +------------------+
+
+ 5 message types (all declared, not all handled here):
+ +-------------------------+-----------------------------------+
+ | message | Normal text message |
+ | broadcast | Sent to all teammates |
+ | shutdown_request | Request graceful shutdown (s10) |
+ | shutdown_response | Approve/reject shutdown (s10) |
+ | plan_approval_response | Approve/reject plan (s10) |
+ +-------------------------+-----------------------------------+
+
+Key insight: "Teammates that can talk to each other."
+"""
+
+import json
+import os
+import subprocess
+import sys
+import threading
+import time
+from pathlib import Path
+
+from dotenv import load_dotenv
+from openai import OpenAI
+
+load_dotenv(override=True)
+
+# Keep imports stable when running from this directory
+os.environ["PYTHONPATH"] = r"E:\ai_pycode\learn-claude-code-main"
+sys.path.insert(0, r"E:\ai_pycode\learn-claude-code-main")
+
+try:
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
+ sys.stderr.reconfigure(encoding="utf-8", errors="replace")
+except Exception:
+ pass
+
+WORKDIR = Path.cwd()
+client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL"))
+MODEL = os.environ.get("OPENAI_MODEL_ID") or os.environ.get("MODEL_ID")
+TEAM_DIR = WORKDIR / ".team"
+INBOX_DIR = TEAM_DIR / "inbox"
+
+SYSTEM = f"You are a team lead at {WORKDIR}. Spawn teammates and communicate via inboxes."
+
+VALID_MSG_TYPES = {
+ "message",
+ "broadcast",
+ "shutdown_request",
+ "shutdown_response",
+ "plan_approval_response",
+}
+
+
+# -- MessageBus: JSONL inbox per teammate --
+class MessageBus:
+ def __init__(self, inbox_dir: Path):
+ self.dir = inbox_dir
+ self.dir.mkdir(parents=True, exist_ok=True)
+
+ def send(self, sender: str, to: str, content: str,
+ msg_type: str = "message", extra: dict = None) -> str:
+ if msg_type not in VALID_MSG_TYPES:
+ return f"Error: Invalid type '{msg_type}'. Valid: {VALID_MSG_TYPES}"
+ msg = {
+ "type": msg_type,
+ "from": sender,
+ "content": content,
+ "timestamp": time.time(),
+ }
+ if extra:
+ msg.update(extra)
+ inbox_path = self.dir / f"{to}.jsonl"
+ with open(inbox_path, "a") as f:
+ f.write(json.dumps(msg) + "\n")
+ return f"Sent {msg_type} to {to}"
+
+ def read_inbox(self, name: str) -> list:
+ inbox_path = self.dir / f"{name}.jsonl"
+ if not inbox_path.exists():
+ return []
+ messages = []
+ for line in inbox_path.read_text().strip().splitlines():
+ if line:
+ messages.append(json.loads(line))
+ inbox_path.write_text("")
+ return messages
+
+ def broadcast(self, sender: str, content: str, teammates: list) -> str:
+ count = 0
+ for name in teammates:
+ if name != sender:
+ self.send(sender, name, content, "broadcast")
+ count += 1
+ return f"Broadcast to {count} teammates"
+
+
+BUS = MessageBus(INBOX_DIR)
+
+
+# -- TeammateManager: persistent named agents with config.json --
+class TeammateManager:
+ def __init__(self, team_dir: Path):
+ self.dir = team_dir
+ self.dir.mkdir(exist_ok=True)
+ self.config_path = self.dir / "config.json"
+ self.config = self._load_config()
+ self.threads = {}
+
+ def _load_config(self) -> dict:
+ if self.config_path.exists():
+ return json.loads(self.config_path.read_text())
+ return {"team_name": "default", "members": []}
+
+ def _save_config(self):
+ self.config_path.write_text(json.dumps(self.config, indent=2))
+
+ def _find_member(self, name: str) -> dict:
+ for m in self.config["members"]:
+ if m["name"] == name:
+ return m
+ return None
+
+ def spawn(self, name: str, role: str, prompt: str) -> str:
+ member = self._find_member(name)
+ if member:
+ if member["status"] not in ("idle", "shutdown"):
+ return f"Error: '{name}' is currently {member['status']}"
+ member["status"] = "working"
+ member["role"] = role
+ else:
+ member = {"name": name, "role": role, "status": "working"}
+ self.config["members"].append(member)
+ self._save_config()
+ thread = threading.Thread(
+ target=self._teammate_loop,
+ args=(name, role, prompt),
+ daemon=True,
+ )
+ self.threads[name] = thread
+ thread.start()
+ return f"Spawned '{name}' (role: {role})"
+
+ def _teammate_loop(self, name: str, role: str, prompt: str):
+ sys_prompt = (
+ f"You are '{name}', role: {role}, at {WORKDIR}. "
+ f"使用 send_message 工具与队友通信. 完成你的任务."
+ )
+ messages = [{"role": "user", "content": prompt}]
+ tools = self._teammate_tools()
+ for _ in range(50):
+ inbox = BUS.read_inbox(name)
+ for msg in inbox:
+ messages.append({"role": "user", "content": json.dumps(msg)})
+ try:
+ response = client.chat.completions.create(
+ model=MODEL,
+ messages=[{"role": "system", "content": sys_prompt}, *messages],
+ tools=tools,
+ max_tokens=8000,
+ tool_choice="auto",
+ temperature=0.7,
+ )
+ except Exception:
+ break
+ assistant_message = response.choices[0].message
+ messages.append(
+ {
+ "role": "assistant",
+ "content": assistant_message.content or "",
+ "tool_calls": assistant_message.tool_calls,
+ }
+ )
+ if not assistant_message.tool_calls:
+ break
+ for tool_call in assistant_message.tool_calls:
+ tool_name = tool_call.function.name
+ try:
+ args = json.loads(tool_call.function.arguments or "{}")
+ except Exception:
+ args = {}
+ output = self._exec(name, tool_name, args)
+ print(f" [{name}] {tool_name}: {str(output)[:120]}")
+ messages.append(
+ {
+ "role": "tool",
+ "tool_call_id": tool_call.id,
+ "content": str(output),
+ }
+ )
+ member = self._find_member(name)
+ if member and member["status"] != "shutdown":
+ member["status"] = "idle"
+ self._save_config()
+
+ def _exec(self, sender: str, tool_name: str, args: dict) -> str:
+ # these base tools are unchanged from s02
+ if tool_name == "bash":
+ return _run_bash(args["command"])
+ if tool_name == "read_file":
+ return _run_read(args["path"])
+ if tool_name == "write_file":
+ return _run_write(args["path"], args["content"])
+ if tool_name == "edit_file":
+ return _run_edit(args["path"], args["old_text"], args["new_text"])
+ if tool_name == "send_message":
+ return BUS.send(sender, args["to"], args["content"], args.get("msg_type", "message"))
+ if tool_name == "read_inbox":
+ return json.dumps(BUS.read_inbox(sender), indent=2)
+ return f"Unknown tool: {tool_name}"
+
+ def _teammate_tools(self) -> list:
+ # these base tools are unchanged from s02
+ return [
+ {"type": "function", "function": {"name": "bash", "description": "Run a shell command.",
+ "parameters": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}}},
+ {"type": "function", "function": {"name": "read_file", "description": "Read file contents.",
+ "parameters": {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}}},
+ {"type": "function", "function": {"name": "write_file", "description": "Write content to file.",
+ "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}},
+ {"type": "function", "function": {"name": "edit_file", "description": "Replace exact text in file.",
+ "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}}},
+ {"type": "function", "function": {"name": "send_message", "description": "Send message to a teammate.",
+ "parameters": {"type": "object", "properties": {"to": {"type": "string"}, "content": {"type": "string"}, "msg_type": {"type": "string", "enum": list(VALID_MSG_TYPES)}}, "required": ["to", "content"]}}},
+ {"type": "function", "function": {"name": "read_inbox", "description": "Read and drain your inbox.",
+ "parameters": {"type": "object", "properties": {}}}},
+ ]
+
+ def list_all(self) -> str:
+ if not self.config["members"]:
+ return "No teammates."
+ lines = [f"Team: {self.config['team_name']}"]
+ for m in self.config["members"]:
+ lines.append(f" {m['name']} ({m['role']}): {m['status']}")
+ return "\n".join(lines)
+
+ def member_names(self) -> list:
+ return [m["name"] for m in self.config["members"]]
+
+
+TEAM = TeammateManager(TEAM_DIR)
+
+
+# -- Base tool implementations (these base tools are unchanged from s02) --
+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"]
+ if any(d in command for d in dangerous):
+ return "Error: Dangerous command blocked"
+ try:
+ if sys.platform == "win32":
+ r = subprocess.run(
+ command, shell=True, cwd=WORKDIR, capture_output=True, text=True, timeout=120,
+ encoding="utf-8", errors="backslashreplace",
+ )
+ else:
+ r = subprocess.run(
+ command, shell=True, cwd=WORKDIR, capture_output=True, text=True, timeout=120,
+ executable="/bin/bash", encoding="utf-8", errors="backslashreplace",
+ )
+ out = ((r.stdout or "") + (r.stderr or "")).strip()
+ return out[:50000] if out else "(no output)"
+ except subprocess.TimeoutExpired:
+ return "Error: Timeout (120s)"
+ except Exception as e:
+ return f"Error: {e}"
+
+
+def _run_read(path: str, limit: int = None) -> str:
+ try:
+ lines = _safe_path(path).read_text(encoding="utf-8", errors="backslashreplace").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, encoding="utf-8", errors="backslashreplace")
+ 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(encoding="utf-8", errors="backslashreplace")
+ if old_text not in c:
+ return f"Error: Text not found in {path}"
+ fp.write_text(c.replace(old_text, new_text, 1), encoding="utf-8", errors="backslashreplace")
+ return f"Edited {path}"
+ except Exception as e:
+ return f"Error: {e}"
+
+
+# -- Lead tool dispatch (9 tools) --
+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"]),
+ "spawn_teammate": lambda **kw: TEAM.spawn(kw["name"], kw["role"], kw["prompt"]),
+ "list_teammates": lambda **kw: TEAM.list_all(),
+ "send_message": lambda **kw: BUS.send("lead", kw["to"], kw["content"], kw.get("msg_type", "message")),
+ "read_inbox": lambda **kw: json.dumps(BUS.read_inbox("lead"), indent=2),
+ "broadcast": lambda **kw: BUS.broadcast("lead", kw["content"], TEAM.member_names()),
+}
+
+# these base tools are unchanged from s02
+TOOLS = [
+ {"type": "function", "function": {"name": "bash", "description": "Run a shell command.",
+ "parameters": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}}},
+ {"type": "function", "function": {"name": "read_file", "description": "Read file contents.",
+ "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}}, "required": ["path"]}}},
+ {"type": "function", "function": {"name": "write_file", "description": "Write content to file.",
+ "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}},
+ {"type": "function", "function": {"name": "edit_file", "description": "Replace exact text in file.",
+ "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}}},
+ {"type": "function", "function": {"name": "spawn_teammate", "description": "Spawn a persistent teammate that runs in its own thread.",
+ "parameters": {"type": "object", "properties": {"name": {"type": "string"}, "role": {"type": "string"}, "prompt": {"type": "string"}}, "required": ["name", "role", "prompt"]}}},
+ {"type": "function", "function": {"name": "list_teammates", "description": "List all teammates with name, role, status.",
+ "parameters": {"type": "object", "properties": {}}}},
+ {"type": "function", "function": {"name": "send_message", "description": "Send a message to a teammate's inbox.",
+ "parameters": {"type": "object", "properties": {"to": {"type": "string"}, "content": {"type": "string"}, "msg_type": {"type": "string", "enum": list(VALID_MSG_TYPES)}}, "required": ["to", "content"]}}},
+ {"type": "function", "function": {"name": "read_inbox", "description": "Read and drain the lead's inbox.",
+ "parameters": {"type": "object", "properties": {}}}},
+ {"type": "function", "function": {"name": "broadcast", "description": "Send a message to all teammates.",
+ "parameters": {"type": "object", "properties": {"content": {"type": "string"}}, "required": ["content"]}}},
+]
+
+
+def agent_loop(messages: list):
+ while True:
+ inbox = BUS.read_inbox("lead")
+ if inbox:
+ messages.append({
+ "role": "user",
+ "content": f"{json.dumps(inbox, indent=2)}",
+ })
+ response = client.chat.completions.create(
+ model=MODEL,
+ messages=[{"role": "system", "content": SYSTEM}, *messages],
+ tools=TOOLS,
+ max_tokens=8000,
+ tool_choice="auto",
+ temperature=0.7,
+ )
+ assistant_message = response.choices[0].message
+ messages.append(
+ {
+ "role": "assistant",
+ "content": assistant_message.content or "",
+ "tool_calls": assistant_message.tool_calls,
+ }
+ )
+ if not assistant_message.tool_calls:
+ return
+ for tool_call in assistant_message.tool_calls:
+ tool_name = tool_call.function.name
+ try:
+ args = json.loads(tool_call.function.arguments or "{}")
+ except Exception:
+ args = {}
+ handler = TOOL_HANDLERS.get(tool_name)
+ try:
+ output = handler(**args) if handler else f"Unknown tool: {tool_name}"
+ except Exception as e:
+ output = f"Error: {e}"
+ print(f"> {tool_name}:")
+ print(str(output)[:200])
+ messages.append(
+ {
+ "role": "tool",
+ "tool_call_id": tool_call.id,
+ "content": str(output),
+ }
+ )
+
+
+if __name__ == "__main__":
+ history = []
+ while True:
+ try:
+ query = input("\033[36ms09-openai >> \033[0m")
+ except (EOFError, KeyboardInterrupt):
+ break
+ if query.strip().lower() in ("q", "exit", ""):
+ break
+ if query.strip() == "/team":
+ print(TEAM.list_all())
+ continue
+ if query.strip() == "/inbox":
+ print(json.dumps(BUS.read_inbox("lead"), indent=2))
+ continue
+ history.append({"role": "user", "content": query})
+ agent_loop(history)
+ if history and history[-1].get("role") == "assistant" and history[-1].get("content"):
+ print(history[-1]["content"])
+ print()
diff --git a/agents/openai_code/s10_team_protocols_openai.py b/agents/openai_code/s10_team_protocols_openai.py
new file mode 100644
index 000000000..8547aadfa
--- /dev/null
+++ b/agents/openai_code/s10_team_protocols_openai.py
@@ -0,0 +1,522 @@
+#!/usr/bin/env python3
+# Harness: protocols -- structured handshakes between models.
+"""
+s10_team_protocols.py - Team Protocols
+
+Shutdown protocol and plan approval protocol, both using the same
+request_id correlation pattern. Builds on s09's team messaging.
+
+ Shutdown FSM: pending -> approved | rejected
+
+ Lead Teammate
+ +---------------------+ +---------------------+
+ | shutdown_request | | |
+ | { | -------> | receives request |
+ | request_id: abc | | decides: approve? |
+ | } | | |
+ +---------------------+ +---------------------+
+ |
+ +---------------------+ +-------v-------------+
+ | shutdown_response | <------- | shutdown_response |
+ | { | | { |
+ | request_id: abc | | request_id: abc |
+ | approve: true | | approve: true |
+ | } | | } |
+ +---------------------+ +---------------------+
+ |
+ v
+ status -> "shutdown", thread stops
+
+ Plan approval FSM: pending -> approved | rejected
+
+ Teammate Lead
+ +---------------------+ +---------------------+
+ | plan_approval | | |
+ | submit: {plan:"..."}| -------> | reviews plan text |
+ +---------------------+ | approve/reject? |
+ +---------------------+
+ |
+ +---------------------+ +-------v-------------+
+ | plan_approval_resp | <------- | plan_approval |
+ | {approve: true} | | review: {req_id, |
+ +---------------------+ | approve: true} |
+ +---------------------+
+
+ Trackers: {request_id: {"target|from": name, "status": "pending|..."}}
+
+Key insight: "Same request_id correlation pattern, two domains."
+"""
+
+import json
+import os
+import subprocess
+import sys
+import threading
+import time
+import uuid
+from pathlib import Path
+
+from dotenv import load_dotenv
+from openai import OpenAI
+
+load_dotenv(override=True)
+
+# Keep imports stable when running from this directory
+os.environ["PYTHONPATH"] = r"E:\ai_pycode\learn-claude-code-main"
+sys.path.insert(0, r"E:\ai_pycode\learn-claude-code-main")
+
+try:
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
+ sys.stderr.reconfigure(encoding="utf-8", errors="replace")
+except Exception:
+ pass
+
+WORKDIR = Path.cwd()
+client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL"))
+MODEL = os.environ.get("OPENAI_MODEL_ID") or os.environ.get("MODEL_ID")
+TEAM_DIR = WORKDIR / ".team"
+INBOX_DIR = TEAM_DIR / "inbox"
+
+SYSTEM = f"You are a team lead at {WORKDIR}. Manage teammates with shutdown and plan approval protocols."
+
+VALID_MSG_TYPES = {
+ "message",
+ "broadcast",
+ "shutdown_request",
+ "shutdown_response",
+ "plan_approval_response",
+}
+
+# -- Request trackers: correlate by request_id --
+shutdown_requests = {}
+plan_requests = {}
+_tracker_lock = threading.Lock()
+
+
+# -- MessageBus: JSONL inbox per teammate --
+class MessageBus:
+ def __init__(self, inbox_dir: Path):
+ self.dir = inbox_dir
+ self.dir.mkdir(parents=True, exist_ok=True)
+
+ def send(self, sender: str, to: str, content: str,
+ msg_type: str = "message", extra: dict = None) -> str:
+ if msg_type not in VALID_MSG_TYPES:
+ return f"Error: Invalid type '{msg_type}'. Valid: {VALID_MSG_TYPES}"
+ msg = {
+ "type": msg_type,
+ "from": sender,
+ "content": content,
+ "timestamp": time.time(),
+ }
+ if extra:
+ msg.update(extra)
+ inbox_path = self.dir / f"{to}.jsonl"
+ with open(inbox_path, "a") as f:
+ f.write(json.dumps(msg) + "\n")
+ return f"Sent {msg_type} to {to}"
+
+ def read_inbox(self, name: str) -> list:
+ inbox_path = self.dir / f"{name}.jsonl"
+ if not inbox_path.exists():
+ return []
+ messages = []
+ for line in inbox_path.read_text().strip().splitlines():
+ if line:
+ messages.append(json.loads(line))
+ inbox_path.write_text("")
+ return messages
+
+ def broadcast(self, sender: str, content: str, teammates: list) -> str:
+ count = 0
+ for name in teammates:
+ if name != sender:
+ self.send(sender, name, content, "broadcast")
+ count += 1
+ return f"Broadcast to {count} teammates"
+
+
+BUS = MessageBus(INBOX_DIR)
+
+
+# -- TeammateManager with shutdown + plan approval --
+class TeammateManager:
+ def __init__(self, team_dir: Path):
+ self.dir = team_dir
+ self.dir.mkdir(exist_ok=True)
+ self.config_path = self.dir / "config.json"
+ self.config = self._load_config()
+ self.threads = {}
+
+ def _load_config(self) -> dict:
+ if self.config_path.exists():
+ return json.loads(self.config_path.read_text())
+ return {"team_name": "default", "members": []}
+
+ def _save_config(self):
+ self.config_path.write_text(json.dumps(self.config, indent=2))
+
+ def _find_member(self, name: str) -> dict:
+ for m in self.config["members"]:
+ if m["name"] == name:
+ return m
+ return None
+
+ def spawn(self, name: str, role: str, prompt: str) -> str:
+ member = self._find_member(name)
+ if member:
+ if member["status"] not in ("idle", "shutdown"):
+ return f"Error: '{name}' is currently {member['status']}"
+ member["status"] = "working"
+ member["role"] = role
+ else:
+ member = {"name": name, "role": role, "status": "working"}
+ self.config["members"].append(member)
+ self._save_config()
+ thread = threading.Thread(
+ target=self._teammate_loop,
+ args=(name, role, prompt),
+ daemon=True,
+ )
+ self.threads[name] = thread
+ thread.start()
+ return f"Spawned '{name}' (role: {role})"
+
+ def _teammate_loop(self, name: str, role: str, prompt: str):
+ sys_prompt = (
+ f"You are '{name}', role: {role}, at {WORKDIR}. "
+ f"Submit plans via plan_approval before major work. "
+ f"Respond to shutdown_request with shutdown_response."
+ )
+ messages = [{"role": "user", "content": prompt}]
+ tools = self._teammate_tools()
+ should_exit = False
+ for _ in range(50):
+ inbox = BUS.read_inbox(name)
+ for msg in inbox:
+ messages.append({"role": "user", "content": json.dumps(msg)})
+ if should_exit:
+ break
+ try:
+ response = client.chat.completions.create(
+ model=MODEL,
+ messages=[{"role": "system", "content": sys_prompt}, *messages],
+ tools=tools,
+ max_tokens=8000,
+ tool_choice="auto",
+ temperature=0.7,
+ )
+ except Exception:
+ break
+ assistant_message = response.choices[0].message
+ messages.append(
+ {
+ "role": "assistant",
+ "content": assistant_message.content or "",
+ "tool_calls": assistant_message.tool_calls,
+ }
+ )
+ if not assistant_message.tool_calls:
+ break
+ for tool_call in assistant_message.tool_calls:
+ tool_name = tool_call.function.name
+ try:
+ args = json.loads(tool_call.function.arguments or "{}")
+ except Exception:
+ args = {}
+ output = self._exec(name, tool_name, args)
+ print(f" [{name}] {tool_name}: {str(output)[:120]}")
+ if tool_name == "shutdown_response" and args.get("approve"):
+ should_exit = True
+ messages.append(
+ {
+ "role": "tool",
+ "tool_call_id": tool_call.id,
+ "content": str(output),
+ }
+ )
+ member = self._find_member(name)
+ if member:
+ member["status"] = "shutdown" if should_exit else "idle"
+ self._save_config()
+
+ def _exec(self, sender: str, tool_name: str, args: dict) -> str:
+ # these base tools are unchanged from s02
+ if tool_name == "bash":
+ return _run_bash(args["command"])
+ if tool_name == "read_file":
+ return _run_read(args["path"])
+ if tool_name == "write_file":
+ return _run_write(args["path"], args["content"])
+ if tool_name == "edit_file":
+ return _run_edit(args["path"], args["old_text"], args["new_text"])
+ if tool_name == "send_message":
+ return BUS.send(sender, args["to"], args["content"], args.get("msg_type", "message"))
+ if tool_name == "read_inbox":
+ return json.dumps(BUS.read_inbox(sender), indent=2)
+ if tool_name == "shutdown_response":
+ req_id = args["request_id"]
+ approve = args["approve"]
+ with _tracker_lock:
+ if req_id in shutdown_requests:
+ shutdown_requests[req_id]["status"] = "approved" if approve else "rejected"
+ BUS.send(
+ sender, "lead", args.get("reason", ""),
+ "shutdown_response", {"request_id": req_id, "approve": approve},
+ )
+ return f"Shutdown {'approved' if approve else 'rejected'}"
+ if tool_name == "plan_approval":
+ plan_text = args.get("plan", "")
+ req_id = str(uuid.uuid4())[:8]
+ with _tracker_lock:
+ plan_requests[req_id] = {"from": sender, "plan": plan_text, "status": "pending"}
+ BUS.send(
+ sender, "lead", plan_text, "plan_approval_response",
+ {"request_id": req_id, "plan": plan_text},
+ )
+ return f"Plan submitted (request_id={req_id}). Waiting for lead approval."
+ return f"Unknown tool: {tool_name}"
+
+ def _teammate_tools(self) -> list:
+ # these base tools are unchanged from s02
+ return [
+ {"type": "function", "function": {"name": "bash", "description": "Run a shell command.",
+ "parameters": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}}},
+ {"type": "function", "function": {"name": "read_file", "description": "Read file contents.",
+ "parameters": {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}}},
+ {"type": "function", "function": {"name": "write_file", "description": "Write content to file.",
+ "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}},
+ {"type": "function", "function": {"name": "edit_file", "description": "Replace exact text in file.",
+ "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}}},
+ {"type": "function", "function": {"name": "send_message", "description": "Send message to a teammate.",
+ "parameters": {"type": "object", "properties": {"to": {"type": "string"}, "content": {"type": "string"}, "msg_type": {"type": "string", "enum": list(VALID_MSG_TYPES)}}, "required": ["to", "content"]}}},
+ {"type": "function", "function": {"name": "read_inbox", "description": "Read and drain your inbox.",
+ "parameters": {"type": "object", "properties": {}}}},
+ {"type": "function", "function": {"name": "shutdown_response", "description": "Respond to a shutdown request. Approve to shut down, reject to keep working.",
+ "parameters": {"type": "object", "properties": {"request_id": {"type": "string"}, "approve": {"type": "boolean"}, "reason": {"type": "string"}}, "required": ["request_id", "approve"]}}},
+ {"type": "function", "function": {"name": "plan_approval", "description": "Submit a plan for lead approval. Provide plan text.",
+ "parameters": {"type": "object", "properties": {"plan": {"type": "string"}}, "required": ["plan"]}}},
+ ]
+
+ def list_all(self) -> str:
+ if not self.config["members"]:
+ return "No teammates."
+ lines = [f"Team: {self.config['team_name']}"]
+ for m in self.config["members"]:
+ lines.append(f" {m['name']} ({m['role']}): {m['status']}")
+ return "\n".join(lines)
+
+ def member_names(self) -> list:
+ return [m["name"] for m in self.config["members"]]
+
+
+TEAM = TeammateManager(TEAM_DIR)
+
+
+# -- Base tool implementations (these base tools are unchanged from s02) --
+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"]
+ if any(d in command for d in dangerous):
+ return "Error: Dangerous command blocked"
+ try:
+ if sys.platform == "win32":
+ r = subprocess.run(
+ command, shell=True, cwd=WORKDIR, capture_output=True, text=True, timeout=120,
+ encoding="utf-8", errors="backslashreplace",
+ )
+ else:
+ r = subprocess.run(
+ command, shell=True, cwd=WORKDIR, capture_output=True, text=True, timeout=120,
+ executable="/bin/bash", encoding="utf-8", errors="backslashreplace",
+ )
+ out = ((r.stdout or "") + (r.stderr or "")).strip()
+ return out[:50000] if out else "(no output)"
+ except subprocess.TimeoutExpired:
+ return "Error: Timeout (120s)"
+ except Exception as e:
+ return f"Error: {e}"
+
+
+def _run_read(path: str, limit: int = None) -> str:
+ try:
+ lines = _safe_path(path).read_text(encoding="utf-8", errors="backslashreplace").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, encoding="utf-8", errors="backslashreplace")
+ 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(encoding="utf-8", errors="backslashreplace")
+ if old_text not in c:
+ return f"Error: Text not found in {path}"
+ fp.write_text(c.replace(old_text, new_text, 1), encoding="utf-8", errors="backslashreplace")
+ return f"Edited {path}"
+ except Exception as e:
+ return f"Error: {e}"
+
+
+# -- Lead-specific protocol handlers --
+def handle_shutdown_request(teammate: str) -> str:
+ req_id = str(uuid.uuid4())[:8]
+ with _tracker_lock:
+ shutdown_requests[req_id] = {"target": teammate, "status": "pending"}
+ BUS.send(
+ "lead", teammate, "Please shut down gracefully.",
+ "shutdown_request", {"request_id": req_id},
+ )
+ return f"Shutdown request {req_id} sent to '{teammate}' (status: pending)"
+
+
+def handle_plan_review(request_id: str, approve: bool, feedback: str = "") -> str:
+ with _tracker_lock:
+ req = plan_requests.get(request_id)
+ if not req:
+ return f"Error: Unknown plan request_id '{request_id}'"
+ with _tracker_lock:
+ req["status"] = "approved" if approve else "rejected"
+ BUS.send(
+ "lead", req["from"], feedback, "plan_approval_response",
+ {"request_id": request_id, "approve": approve, "feedback": feedback},
+ )
+ return f"Plan {req['status']} for '{req['from']}'"
+
+
+def _check_shutdown_status(request_id: str) -> str:
+ with _tracker_lock:
+ return json.dumps(shutdown_requests.get(request_id, {"error": "not found"}))
+
+
+# -- Lead tool dispatch (12 tools) --
+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"]),
+ "spawn_teammate": lambda **kw: TEAM.spawn(kw["name"], kw["role"], kw["prompt"]),
+ "list_teammates": lambda **kw: TEAM.list_all(),
+ "send_message": lambda **kw: BUS.send("lead", kw["to"], kw["content"], kw.get("msg_type", "message")),
+ "read_inbox": lambda **kw: json.dumps(BUS.read_inbox("lead"), indent=2),
+ "broadcast": lambda **kw: BUS.broadcast("lead", kw["content"], TEAM.member_names()),
+ "shutdown_request": lambda **kw: handle_shutdown_request(kw["teammate"]),
+ "shutdown_response": lambda **kw: _check_shutdown_status(kw.get("request_id", "")),
+ "plan_approval": lambda **kw: handle_plan_review(kw["request_id"], kw["approve"], kw.get("feedback", "")),
+}
+
+# these base tools are unchanged from s02
+TOOLS = [
+ {"type": "function", "function": {"name": "bash", "description": "Run a shell command.",
+ "parameters": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}}},
+ {"type": "function", "function": {"name": "read_file", "description": "Read file contents.",
+ "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}}, "required": ["path"]}}},
+ {"type": "function", "function": {"name": "write_file", "description": "Write content to file.",
+ "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}},
+ {"type": "function", "function": {"name": "edit_file", "description": "Replace exact text in file.",
+ "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}}},
+ {"type": "function", "function": {"name": "spawn_teammate", "description": "Spawn a persistent teammate.",
+ "parameters": {"type": "object", "properties": {"name": {"type": "string"}, "role": {"type": "string"}, "prompt": {"type": "string"}}, "required": ["name", "role", "prompt"]}}},
+ {"type": "function", "function": {"name": "list_teammates", "description": "List all teammates.",
+ "parameters": {"type": "object", "properties": {}}}},
+ {"type": "function", "function": {"name": "send_message", "description": "Send a message to a teammate.",
+ "parameters": {"type": "object", "properties": {"to": {"type": "string"}, "content": {"type": "string"}, "msg_type": {"type": "string", "enum": list(VALID_MSG_TYPES)}}, "required": ["to", "content"]}}},
+ {"type": "function", "function": {"name": "read_inbox", "description": "Read and drain the lead's inbox.",
+ "parameters": {"type": "object", "properties": {}}}},
+ {"type": "function", "function": {"name": "broadcast", "description": "Send a message to all teammates.",
+ "parameters": {"type": "object", "properties": {"content": {"type": "string"}}, "required": ["content"]}}},
+ {"type": "function", "function": {"name": "shutdown_request", "description": "Request a teammate to shut down gracefully. Returns a request_id for tracking.",
+ "parameters": {"type": "object", "properties": {"teammate": {"type": "string"}}, "required": ["teammate"]}}},
+ {"type": "function", "function": {"name": "shutdown_response", "description": "Check the status of a shutdown request by request_id.",
+ "parameters": {"type": "object", "properties": {"request_id": {"type": "string"}}, "required": ["request_id"]}}},
+ {"type": "function", "function": {"name": "plan_approval", "description": "Approve or reject a teammate's plan. Provide request_id + approve + optional feedback.",
+ "parameters": {"type": "object", "properties": {"request_id": {"type": "string"}, "approve": {"type": "boolean"}, "feedback": {"type": "string"}}, "required": ["request_id", "approve"]}}},
+]
+
+
+def agent_loop(messages: list):
+ while True:
+ inbox = BUS.read_inbox("lead")
+ if inbox:
+ messages.append({
+ "role": "user",
+ "content": f"{json.dumps(inbox, indent=2)}",
+ })
+ response = client.chat.completions.create(
+ model=MODEL,
+ messages=[{"role": "system", "content": SYSTEM}, *messages],
+ tools=TOOLS,
+ max_tokens=8000,
+ tool_choice="auto",
+ temperature=0.7,
+ )
+ assistant_message = response.choices[0].message
+ messages.append(
+ {
+ "role": "assistant",
+ "content": assistant_message.content or "",
+ "tool_calls": assistant_message.tool_calls,
+ }
+ )
+ if not assistant_message.tool_calls:
+ return
+ for tool_call in assistant_message.tool_calls:
+ tool_name = tool_call.function.name
+ try:
+ args = json.loads(tool_call.function.arguments or "{}")
+ except Exception:
+ args = {}
+ handler = TOOL_HANDLERS.get(tool_name)
+ try:
+ output = handler(**args) if handler else f"Unknown tool: {tool_name}"
+ except Exception as e:
+ output = f"Error: {e}"
+ print(f"> {tool_name}:")
+ print(str(output)[:200])
+ messages.append(
+ {
+ "role": "tool",
+ "tool_call_id": tool_call.id,
+ "content": str(output),
+ }
+ )
+
+
+if __name__ == "__main__":
+ history = []
+ while True:
+ try:
+ query = input("\033[36ms10-openai >> \033[0m")
+ except (EOFError, KeyboardInterrupt):
+ break
+ if query.strip().lower() in ("q", "exit", ""):
+ break
+ if query.strip() == "/team":
+ print(TEAM.list_all())
+ continue
+ if query.strip() == "/inbox":
+ print(json.dumps(BUS.read_inbox("lead"), indent=2))
+ continue
+ history.append({"role": "user", "content": query})
+ agent_loop(history)
+ if history and history[-1].get("role") == "assistant" and history[-1].get("content"):
+ print(history[-1]["content"])
+ print()
diff --git a/agents/openai_code/s11_autonomous_agents_openai.py b/agents/openai_code/s11_autonomous_agents_openai.py
new file mode 100644
index 000000000..168c3d10c
--- /dev/null
+++ b/agents/openai_code/s11_autonomous_agents_openai.py
@@ -0,0 +1,624 @@
+#!/usr/bin/env python3
+# Harness: autonomy -- models that find work without being told.
+"""
+s11_autonomous_agents.py - Autonomous Agents
+
+Idle cycle with task board polling, auto-claiming unclaimed tasks, and
+identity re-injection after context compression. Builds on s10's protocols.
+
+ Teammate lifecycle:
+ +-------+
+ | spawn |
+ +---+---+
+ |
+ v
+ +-------+ tool_use +-------+
+ | WORK | <----------- | LLM |
+ +---+---+ +-------+
+ |
+ | stop_reason != tool_use
+ v
+ +--------+
+ | IDLE | poll every 5s for up to 60s
+ +---+----+
+ |
+ +---> check inbox -> message? -> resume WORK
+ |
+ +---> scan .tasks/ -> unclaimed? -> claim -> resume WORK
+ |
+ +---> timeout (60s) -> shutdown
+
+ Identity re-injection after compression:
+ messages = [identity_block, ...remaining...]
+ "You are 'coder', role: backend, team: my-team"
+
+Key insight: "The agent finds work itself."
+"""
+
+import json
+import os
+import subprocess
+import sys
+import threading
+import time
+import uuid
+from pathlib import Path
+
+from dotenv import load_dotenv
+from openai import OpenAI
+
+load_dotenv(override=True)
+
+# Keep imports stable when running from this directory
+os.environ["PYTHONPATH"] = r"E:\ai_pycode\learn-claude-code-main"
+sys.path.insert(0, r"E:\ai_pycode\learn-claude-code-main")
+
+try:
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
+ sys.stderr.reconfigure(encoding="utf-8", errors="replace")
+except Exception:
+ pass
+
+WORKDIR = Path.cwd()
+client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL"))
+MODEL = os.environ.get("OPENAI_MODEL_ID") or os.environ.get("MODEL_ID")
+TEAM_DIR = WORKDIR / ".team"
+INBOX_DIR = TEAM_DIR / "inbox"
+TASKS_DIR = WORKDIR / ".tasks"
+
+POLL_INTERVAL = 5
+IDLE_TIMEOUT = 60
+
+SYSTEM = f"You are a team lead at {WORKDIR}. Teammates are autonomous -- they find work themselves."
+
+VALID_MSG_TYPES = {
+ "message",
+ "broadcast",
+ "shutdown_request",
+ "shutdown_response",
+ "plan_approval_response",
+}
+
+# -- Request trackers --
+shutdown_requests = {}
+plan_requests = {}
+_tracker_lock = threading.Lock()
+_claim_lock = threading.Lock()
+
+
+# -- MessageBus: JSONL inbox per teammate --
+class MessageBus:
+ def __init__(self, inbox_dir: Path):
+ self.dir = inbox_dir
+ self.dir.mkdir(parents=True, exist_ok=True)
+
+ def send(self, sender: str, to: str, content: str,
+ msg_type: str = "message", extra: dict = None) -> str:
+ if msg_type not in VALID_MSG_TYPES:
+ return f"Error: Invalid type '{msg_type}'. Valid: {VALID_MSG_TYPES}"
+ msg = {
+ "type": msg_type,
+ "from": sender,
+ "content": content,
+ "timestamp": time.time(),
+ }
+ if extra:
+ msg.update(extra)
+ inbox_path = self.dir / f"{to}.jsonl"
+ with open(inbox_path, "a") as f:
+ f.write(json.dumps(msg) + "\n")
+ return f"Sent {msg_type} to {to}"
+
+ def read_inbox(self, name: str) -> list:
+ inbox_path = self.dir / f"{name}.jsonl"
+ if not inbox_path.exists():
+ return []
+ messages = []
+ for line in inbox_path.read_text().strip().splitlines():
+ if line:
+ messages.append(json.loads(line))
+ inbox_path.write_text("")
+ return messages
+
+ def broadcast(self, sender: str, content: str, teammates: list) -> str:
+ count = 0
+ for name in teammates:
+ if name != sender:
+ self.send(sender, name, content, "broadcast")
+ count += 1
+ return f"Broadcast to {count} teammates"
+
+
+BUS = MessageBus(INBOX_DIR)
+
+
+# -- Task board scanning --
+def scan_unclaimed_tasks() -> list:
+ TASKS_DIR.mkdir(exist_ok=True)
+ unclaimed = []
+ for f in sorted(TASKS_DIR.glob("task_*.json")):
+ task = json.loads(f.read_text())
+ if (task.get("status") == "pending"
+ and not task.get("owner")
+ and not task.get("blockedBy")):
+ unclaimed.append(task)
+ return unclaimed
+
+
+def claim_task(task_id: int, owner: str) -> str:
+ with _claim_lock:
+ path = TASKS_DIR / f"task_{task_id}.json"
+ if not path.exists():
+ return f"Error: Task {task_id} not found"
+ task = json.loads(path.read_text())
+ if task.get("owner"):
+ existing_owner = task.get("owner") or "someone else"
+ return f"Error: Task {task_id} has already been claimed by {existing_owner}"
+ if task.get("status") != "pending":
+ status = task.get("status")
+ return f"Error: Task {task_id} cannot be claimed because its status is '{status}'"
+ if task.get("blockedBy"):
+ return f"Error: Task {task_id} is blocked by other task(s) and cannot be claimed yet"
+ task["owner"] = owner
+ task["status"] = "in_progress"
+ path.write_text(json.dumps(task, indent=2))
+ return f"Claimed task #{task_id} for {owner}"
+
+
+# -- Identity re-injection after compression --
+def make_identity_block(name: str, role: str, team_name: str) -> dict:
+ return {
+ "role": "user",
+ "content": f"You are '{name}', role: {role}, team: {team_name}. Continue your work.",
+ }
+
+
+# -- Autonomous TeammateManager --
+class TeammateManager:
+ def __init__(self, team_dir: Path):
+ self.dir = team_dir
+ self.dir.mkdir(exist_ok=True)
+ self.config_path = self.dir / "config.json"
+ self.config = self._load_config()
+ self.threads = {}
+
+ def _load_config(self) -> dict:
+ if self.config_path.exists():
+ return json.loads(self.config_path.read_text())
+ return {"team_name": "default", "members": []}
+
+ def _save_config(self):
+ self.config_path.write_text(json.dumps(self.config, indent=2))
+
+ def _find_member(self, name: str) -> dict:
+ for m in self.config["members"]:
+ if m["name"] == name:
+ return m
+ return None
+
+ def _set_status(self, name: str, status: str):
+ member = self._find_member(name)
+ if member:
+ member["status"] = status
+ self._save_config()
+
+ def spawn(self, name: str, role: str, prompt: str) -> str:
+ member = self._find_member(name)
+ if member:
+ if member["status"] not in ("idle", "shutdown"):
+ return f"Error: '{name}' is currently {member['status']}"
+ member["status"] = "working"
+ member["role"] = role
+ else:
+ member = {"name": name, "role": role, "status": "working"}
+ self.config["members"].append(member)
+ self._save_config()
+ thread = threading.Thread(
+ target=self._loop,
+ args=(name, role, prompt),
+ daemon=True,
+ )
+ self.threads[name] = thread
+ thread.start()
+ return f"Spawned '{name}' (role: {role})"
+
+ def _loop(self, name: str, role: str, prompt: str):
+ team_name = self.config["team_name"]
+ sys_prompt = (
+ f"You are '{name}', role: {role}, team: {team_name}, at {WORKDIR}. "
+ f"Use idle tool when you have no more work. You will auto-claim new tasks."
+ )
+ messages = [{"role": "user", "content": prompt}]
+ tools = self._teammate_tools()
+
+ while True:
+ # -- WORK PHASE: standard agent loop --
+ for _ in range(50):
+ inbox = BUS.read_inbox(name)
+ for msg in inbox:
+ if msg.get("type") == "shutdown_request":
+ self._set_status(name, "shutdown")
+ return
+ messages.append({"role": "user", "content": json.dumps(msg)})
+ try:
+ response = client.chat.completions.create(
+ model=MODEL,
+ messages=[{"role": "system", "content": sys_prompt}, *messages],
+ tools=tools,
+ max_tokens=8000,
+ tool_choice="auto",
+ temperature=0.7,
+ )
+ except Exception:
+ self._set_status(name, "idle")
+ return
+ assistant_message = response.choices[0].message
+ messages.append(
+ {
+ "role": "assistant",
+ "content": assistant_message.content or "",
+ "tool_calls": assistant_message.tool_calls,
+ }
+ )
+ if not assistant_message.tool_calls:
+ break
+ idle_requested = False
+ for tool_call in assistant_message.tool_calls:
+ tool_name = tool_call.function.name
+ try:
+ args = json.loads(tool_call.function.arguments or "{}")
+ except Exception:
+ args = {}
+ if tool_name == "idle":
+ idle_requested = True
+ output = "Entering idle phase. Will poll for new tasks."
+ else:
+ output = self._exec(name, tool_name, args)
+ print(f" [{name}] {tool_name}: {str(output)[:120]}")
+ messages.append(
+ {
+ "role": "tool",
+ "tool_call_id": tool_call.id,
+ "content": str(output),
+ }
+ )
+ if idle_requested:
+ break
+
+ # -- IDLE PHASE: poll for inbox messages and unclaimed tasks --
+ self._set_status(name, "idle")
+ resume = False
+ polls = IDLE_TIMEOUT // max(POLL_INTERVAL, 1)
+ for _ in range(polls):
+ time.sleep(POLL_INTERVAL)
+ inbox = BUS.read_inbox(name)
+ if inbox:
+ for msg in inbox:
+ if msg.get("type") == "shutdown_request":
+ self._set_status(name, "shutdown")
+ return
+ messages.append({"role": "user", "content": json.dumps(msg)})
+ resume = True
+ break
+ unclaimed = scan_unclaimed_tasks()
+ if unclaimed:
+ task = unclaimed[0]
+ result = claim_task(task["id"], name)
+ if result.startswith("Error:"):
+ continue
+ task_prompt = (
+ f"Task #{task['id']}: {task['subject']}\n"
+ f"{task.get('description', '')}"
+ )
+ if len(messages) <= 3:
+ messages.insert(0, make_identity_block(name, role, team_name))
+ messages.insert(1, {"role": "assistant", "content": f"I am {name}. Continuing."})
+ messages.append({"role": "user", "content": task_prompt})
+ messages.append({"role": "assistant", "content": f"Claimed task #{task['id']}. Working on it."})
+ resume = True
+ break
+
+ if not resume:
+ self._set_status(name, "shutdown")
+ return
+ self._set_status(name, "working")
+
+ def _exec(self, sender: str, tool_name: str, args: dict) -> str:
+ # these base tools are unchanged from s02
+ if tool_name == "bash":
+ return _run_bash(args["command"])
+ if tool_name == "read_file":
+ return _run_read(args["path"])
+ if tool_name == "write_file":
+ return _run_write(args["path"], args["content"])
+ if tool_name == "edit_file":
+ return _run_edit(args["path"], args["old_text"], args["new_text"])
+ if tool_name == "send_message":
+ return BUS.send(sender, args["to"], args["content"], args.get("msg_type", "message"))
+ if tool_name == "read_inbox":
+ return json.dumps(BUS.read_inbox(sender), indent=2)
+ if tool_name == "shutdown_response":
+ req_id = args["request_id"]
+ with _tracker_lock:
+ if req_id in shutdown_requests:
+ shutdown_requests[req_id]["status"] = "approved" if args["approve"] else "rejected"
+ BUS.send(
+ sender, "lead", args.get("reason", ""),
+ "shutdown_response", {"request_id": req_id, "approve": args["approve"]},
+ )
+ return f"Shutdown {'approved' if args['approve'] else 'rejected'}"
+ if tool_name == "plan_approval":
+ plan_text = args.get("plan", "")
+ req_id = str(uuid.uuid4())[:8]
+ with _tracker_lock:
+ plan_requests[req_id] = {"from": sender, "plan": plan_text, "status": "pending"}
+ BUS.send(
+ sender, "lead", plan_text, "plan_approval_response",
+ {"request_id": req_id, "plan": plan_text},
+ )
+ return f"Plan submitted (request_id={req_id}). Waiting for approval."
+ if tool_name == "claim_task":
+ return claim_task(args["task_id"], sender)
+ return f"Unknown tool: {tool_name}"
+
+ def _teammate_tools(self) -> list:
+ # these base tools are unchanged from s02
+ return [
+ {"type": "function", "function": {"name": "bash", "description": "Run a shell command.",
+ "parameters": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}}},
+ {"type": "function", "function": {"name": "read_file", "description": "Read file contents.",
+ "parameters": {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}}},
+ {"type": "function", "function": {"name": "write_file", "description": "Write content to file.",
+ "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}},
+ {"type": "function", "function": {"name": "edit_file", "description": "Replace exact text in file.",
+ "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}}},
+ {"type": "function", "function": {"name": "send_message", "description": "Send message to a teammate.",
+ "parameters": {"type": "object", "properties": {"to": {"type": "string"}, "content": {"type": "string"}, "msg_type": {"type": "string", "enum": list(VALID_MSG_TYPES)}}, "required": ["to", "content"]}}},
+ {"type": "function", "function": {"name": "read_inbox", "description": "Read and drain your inbox.",
+ "parameters": {"type": "object", "properties": {}}}},
+ {"type": "function", "function": {"name": "shutdown_response", "description": "Respond to a shutdown request.",
+ "parameters": {"type": "object", "properties": {"request_id": {"type": "string"}, "approve": {"type": "boolean"}, "reason": {"type": "string"}}, "required": ["request_id", "approve"]}}},
+ {"type": "function", "function": {"name": "plan_approval", "description": "Submit a plan for lead approval.",
+ "parameters": {"type": "object", "properties": {"plan": {"type": "string"}}, "required": ["plan"]}}},
+ {"type": "function", "function": {"name": "idle", "description": "Signal that you have no more work. Enters idle polling phase.",
+ "parameters": {"type": "object", "properties": {}}}},
+ {"type": "function", "function": {"name": "claim_task", "description": "Claim a task from the task board by ID.",
+ "parameters": {"type": "object", "properties": {"task_id": {"type": "integer"}}, "required": ["task_id"]}}},
+ ]
+
+ def list_all(self) -> str:
+ if not self.config["members"]:
+ return "No teammates."
+ lines = [f"Team: {self.config['team_name']}"]
+ for m in self.config["members"]:
+ lines.append(f" {m['name']} ({m['role']}): {m['status']}")
+ return "\n".join(lines)
+
+ def member_names(self) -> list:
+ return [m["name"] for m in self.config["members"]]
+
+
+TEAM = TeammateManager(TEAM_DIR)
+
+
+# -- Base tool implementations (these base tools are unchanged from s02) --
+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"]
+ if any(d in command for d in dangerous):
+ return "Error: Dangerous command blocked"
+ try:
+ if sys.platform == "win32":
+ r = subprocess.run(
+ command, shell=True, cwd=WORKDIR, capture_output=True, text=True, timeout=120,
+ encoding="utf-8", errors="backslashreplace",
+ )
+ else:
+ r = subprocess.run(
+ command, shell=True, cwd=WORKDIR, capture_output=True, text=True, timeout=120,
+ executable="/bin/bash", encoding="utf-8", errors="backslashreplace",
+ )
+ out = ((r.stdout or "") + (r.stderr or "")).strip()
+ return out[:50000] if out else "(no output)"
+ except subprocess.TimeoutExpired:
+ return "Error: Timeout (120s)"
+ except Exception as e:
+ return f"Error: {e}"
+
+
+def _run_read(path: str, limit: int = None) -> str:
+ try:
+ lines = _safe_path(path).read_text(encoding="utf-8", errors="backslashreplace").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, encoding="utf-8", errors="backslashreplace")
+ 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(encoding="utf-8", errors="backslashreplace")
+ if old_text not in c:
+ return f"Error: Text not found in {path}"
+ fp.write_text(c.replace(old_text, new_text, 1), encoding="utf-8", errors="backslashreplace")
+ return f"Edited {path}"
+ except Exception as e:
+ return f"Error: {e}"
+
+
+# -- Lead-specific protocol handlers --
+def handle_shutdown_request(teammate: str) -> str:
+ req_id = str(uuid.uuid4())[:8]
+ with _tracker_lock:
+ shutdown_requests[req_id] = {"target": teammate, "status": "pending"}
+ BUS.send(
+ "lead", teammate, "Please shut down gracefully.",
+ "shutdown_request", {"request_id": req_id},
+ )
+ return f"Shutdown request {req_id} sent to '{teammate}'"
+
+
+def handle_plan_review(request_id: str, approve: bool, feedback: str = "") -> str:
+ with _tracker_lock:
+ req = plan_requests.get(request_id)
+ if not req:
+ return f"Error: Unknown plan request_id '{request_id}'"
+ with _tracker_lock:
+ req["status"] = "approved" if approve else "rejected"
+ BUS.send(
+ "lead", req["from"], feedback, "plan_approval_response",
+ {"request_id": request_id, "approve": approve, "feedback": feedback},
+ )
+ return f"Plan {req['status']} for '{req['from']}'"
+
+
+def _check_shutdown_status(request_id: str) -> str:
+ with _tracker_lock:
+ return json.dumps(shutdown_requests.get(request_id, {"error": "not found"}))
+
+
+# -- Lead tool dispatch (14 tools) --
+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"]),
+ "spawn_teammate": lambda **kw: TEAM.spawn(kw["name"], kw["role"], kw["prompt"]),
+ "list_teammates": lambda **kw: TEAM.list_all(),
+ "send_message": lambda **kw: BUS.send("lead", kw["to"], kw["content"], kw.get("msg_type", "message")),
+ "read_inbox": lambda **kw: json.dumps(BUS.read_inbox("lead"), indent=2),
+ "broadcast": lambda **kw: BUS.broadcast("lead", kw["content"], TEAM.member_names()),
+ "shutdown_request": lambda **kw: handle_shutdown_request(kw["teammate"]),
+ "shutdown_response": lambda **kw: _check_shutdown_status(kw.get("request_id", "")),
+ "plan_approval": lambda **kw: handle_plan_review(kw["request_id"], kw["approve"], kw.get("feedback", "")),
+ "idle": lambda **kw: "Lead does not idle.",
+ "claim_task": lambda **kw: claim_task(kw["task_id"], "lead"),
+}
+
+# these base tools are unchanged from s02
+TOOLS = [
+ {"type": "function", "function": {"name": "bash", "description": "Run a shell command.",
+ "parameters": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}}},
+ {"type": "function", "function": {"name": "read_file", "description": "Read file contents.",
+ "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}}, "required": ["path"]}}},
+ {"type": "function", "function": {"name": "write_file", "description": "Write content to file.",
+ "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}},
+ {"type": "function", "function": {"name": "edit_file", "description": "Replace exact text in file.",
+ "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}}},
+ {"type": "function", "function": {"name": "spawn_teammate", "description": "Spawn an autonomous teammate.",
+ "parameters": {"type": "object", "properties": {"name": {"type": "string"}, "role": {"type": "string"}, "prompt": {"type": "string"}}, "required": ["name", "role", "prompt"]}}},
+ {"type": "function", "function": {"name": "list_teammates", "description": "List all teammates.",
+ "parameters": {"type": "object", "properties": {}}}},
+ {"type": "function", "function": {"name": "send_message", "description": "Send a message to a teammate.",
+ "parameters": {"type": "object", "properties": {"to": {"type": "string"}, "content": {"type": "string"}, "msg_type": {"type": "string", "enum": list(VALID_MSG_TYPES)}}, "required": ["to", "content"]}}},
+ {"type": "function", "function": {"name": "read_inbox", "description": "Read and drain the lead's inbox.",
+ "parameters": {"type": "object", "properties": {}}}},
+ {"type": "function", "function": {"name": "broadcast", "description": "Send a message to all teammates.",
+ "parameters": {"type": "object", "properties": {"content": {"type": "string"}}, "required": ["content"]}}},
+ {"type": "function", "function": {"name": "shutdown_request", "description": "Request a teammate to shut down.",
+ "parameters": {"type": "object", "properties": {"teammate": {"type": "string"}}, "required": ["teammate"]}}},
+ {"type": "function", "function": {"name": "shutdown_response", "description": "Check shutdown request status.",
+ "parameters": {"type": "object", "properties": {"request_id": {"type": "string"}}, "required": ["request_id"]}}},
+ {"type": "function", "function": {"name": "plan_approval", "description": "Approve or reject a teammate's plan.",
+ "parameters": {"type": "object", "properties": {"request_id": {"type": "string"}, "approve": {"type": "boolean"}, "feedback": {"type": "string"}}, "required": ["request_id", "approve"]}}},
+ {"type": "function", "function": {"name": "idle", "description": "Enter idle state (for lead -- rarely used).",
+ "parameters": {"type": "object", "properties": {}}}},
+ {"type": "function", "function": {"name": "claim_task", "description": "Claim a task from the board by ID.",
+ "parameters": {"type": "object", "properties": {"task_id": {"type": "integer"}}, "required": ["task_id"]}}},
+]
+
+
+def agent_loop(messages: list):
+ while True:
+ inbox = BUS.read_inbox("lead")
+ if inbox:
+ messages.append({
+ "role": "user",
+ "content": f"{json.dumps(inbox, indent=2)}",
+ })
+ response = client.chat.completions.create(
+ model=MODEL,
+ messages=[{"role": "system", "content": SYSTEM}, *messages],
+ tools=TOOLS,
+ max_tokens=8000,
+ tool_choice="auto",
+ temperature=0.7,
+ )
+ assistant_message = response.choices[0].message
+ messages.append(
+ {
+ "role": "assistant",
+ "content": assistant_message.content or "",
+ "tool_calls": assistant_message.tool_calls,
+ }
+ )
+ if not assistant_message.tool_calls:
+ return
+ for tool_call in assistant_message.tool_calls:
+ tool_name = tool_call.function.name
+ try:
+ args = json.loads(tool_call.function.arguments or "{}")
+ except Exception:
+ args = {}
+ handler = TOOL_HANDLERS.get(tool_name)
+ try:
+ output = handler(**args) if handler else f"Unknown tool: {tool_name}"
+ except Exception as e:
+ output = f"Error: {e}"
+ print(f"> {tool_name}:")
+ print(str(output)[:200])
+ messages.append(
+ {
+ "role": "tool",
+ "tool_call_id": tool_call.id,
+ "content": str(output),
+ }
+ )
+
+
+if __name__ == "__main__":
+ history = []
+ while True:
+ try:
+ query = input("\033[36ms11-openai >> \033[0m")
+ except (EOFError, KeyboardInterrupt):
+ break
+ if query.strip().lower() in ("q", "exit", ""):
+ break
+ if query.strip() == "/team":
+ print(TEAM.list_all())
+ continue
+ if query.strip() == "/inbox":
+ print(json.dumps(BUS.read_inbox("lead"), indent=2))
+ continue
+ if query.strip() == "/tasks":
+ TASKS_DIR.mkdir(exist_ok=True)
+ for f in sorted(TASKS_DIR.glob("task_*.json")):
+ t = json.loads(f.read_text())
+ marker = {"pending": "[ ]", "in_progress": "[>]", "completed": "[x]"}.get(t["status"], "[?]")
+ owner = f" @{t['owner']}" if t.get("owner") else ""
+ print(f" {marker} #{t['id']}: {t['subject']}{owner}")
+ continue
+ history.append({"role": "user", "content": query})
+ agent_loop(history)
+ if history and history[-1].get("role") == "assistant" and history[-1].get("content"):
+ print(history[-1]["content"])
+ print()
diff --git a/agents/openai_code/s12_worktree_task_isolation_openai.py b/agents/openai_code/s12_worktree_task_isolation_openai.py
new file mode 100644
index 000000000..29a182a64
--- /dev/null
+++ b/agents/openai_code/s12_worktree_task_isolation_openai.py
@@ -0,0 +1,816 @@
+#!/usr/bin/env python3
+# Harness: directory isolation -- parallel execution lanes that never collide.
+"""
+s12_worktree_task_isolation.py - Worktree + Task Isolation
+
+Directory-level isolation for parallel task execution.
+Tasks are the control plane and worktrees are the execution plane.
+
+ .tasks/task_12.json
+ {
+ "id": 12,
+ "subject": "Implement auth refactor",
+ "status": "in_progress",
+ "worktree": "auth-refactor"
+ }
+
+ .worktrees/index.json
+ {
+ "worktrees": [
+ {
+ "name": "auth-refactor",
+ "path": ".../.worktrees/auth-refactor",
+ "branch": "wt/auth-refactor",
+ "task_id": 12,
+ "status": "active"
+ }
+ ]
+ }
+
+Key insight: "Isolate by directory, coordinate by task ID."
+"""
+
+import json
+import os
+import re
+import subprocess
+import sys
+import time
+from pathlib import Path
+
+from dotenv import load_dotenv
+from openai import OpenAI
+
+load_dotenv(override=True)
+
+# Keep imports stable when running from this directory
+os.environ["PYTHONPATH"] = r"E:\ai_pycode\learn-claude-code-main"
+sys.path.insert(0, r"E:\ai_pycode\learn-claude-code-main")
+
+try:
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
+ sys.stderr.reconfigure(encoding="utf-8", errors="replace")
+except Exception:
+ pass
+
+WORKDIR = Path.cwd()
+client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL"))
+MODEL = os.environ.get("OPENAI_MODEL_ID") or os.environ.get("MODEL_ID")
+
+
+def detect_repo_root(cwd: Path) -> Path | None:
+ """Return git repo root if cwd is inside a repo, else None."""
+ try:
+ r = subprocess.run(
+ ["git", "rev-parse", "--show-toplevel"],
+ cwd=cwd,
+ capture_output=True,
+ text=True,
+ timeout=10,
+ )
+ if r.returncode != 0:
+ return None
+ root = Path(r.stdout.strip())
+ return root if root.exists() else None
+ except Exception:
+ return None
+
+
+REPO_ROOT = detect_repo_root(WORKDIR) or WORKDIR
+
+SYSTEM = (
+ f"You are a coding agent at {WORKDIR}. "
+ "Use task + worktree tools for multi-task work. "
+ "For parallel or risky changes: create tasks, allocate worktree lanes, "
+ "run commands in those lanes, then choose keep/remove for closeout. "
+ "Use worktree_events when you need lifecycle visibility."
+)
+
+
+# -- EventBus: append-only lifecycle events for observability --
+class EventBus:
+ def __init__(self, event_log_path: Path):
+ self.path = event_log_path
+ self.path.parent.mkdir(parents=True, exist_ok=True)
+ if not self.path.exists():
+ self.path.write_text("")
+
+ def emit(
+ self,
+ event: str,
+ task: dict | None = None,
+ worktree: dict | None = None,
+ error: str | None = None,
+ ):
+ payload = {
+ "event": event,
+ "ts": time.time(),
+ "task": task or {},
+ "worktree": worktree or {},
+ }
+ if error:
+ payload["error"] = error
+ with self.path.open("a", encoding="utf-8") as f:
+ f.write(json.dumps(payload) + "\n")
+
+ def list_recent(self, limit: int = 20) -> str:
+ n = max(1, min(int(limit or 20), 200))
+ lines = self.path.read_text(encoding="utf-8").splitlines()
+ recent = lines[-n:]
+ items = []
+ for line in recent:
+ try:
+ items.append(json.loads(line))
+ except Exception:
+ items.append({"event": "parse_error", "raw": line})
+ return json.dumps(items, indent=2)
+
+
+# -- TaskManager: persistent task board with optional worktree binding --
+class TaskManager:
+ def __init__(self, tasks_dir: Path):
+ self.dir = tasks_dir
+ self.dir.mkdir(parents=True, exist_ok=True)
+ self._next_id = self._max_id() + 1
+
+ def _max_id(self) -> int:
+ ids = []
+ for f in self.dir.glob("task_*.json"):
+ try:
+ ids.append(int(f.stem.split("_")[1]))
+ except Exception:
+ pass
+ return max(ids) if ids else 0
+
+ def _path(self, task_id: int) -> Path:
+ return self.dir / f"task_{task_id}.json"
+
+ def _load(self, task_id: int) -> dict:
+ path = self._path(task_id)
+ if not path.exists():
+ raise ValueError(f"Task {task_id} not found")
+ return json.loads(path.read_text())
+
+ def _save(self, task: dict):
+ self._path(task["id"]).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",
+ "owner": "",
+ "worktree": "",
+ "blockedBy": [],
+ "created_at": time.time(),
+ "updated_at": time.time(),
+ }
+ 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 exists(self, task_id: int) -> bool:
+ return self._path(task_id).exists()
+
+ def update(self, task_id: int, status: str = None, owner: str = 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
+ if owner is not None:
+ task["owner"] = owner
+ task["updated_at"] = time.time()
+ self._save(task)
+ return json.dumps(task, indent=2)
+
+ def bind_worktree(self, task_id: int, worktree: str, owner: str = "") -> str:
+ task = self._load(task_id)
+ task["worktree"] = worktree
+ if owner:
+ task["owner"] = owner
+ if task["status"] == "pending":
+ task["status"] = "in_progress"
+ task["updated_at"] = time.time()
+ self._save(task)
+ return json.dumps(task, indent=2)
+
+ def unbind_worktree(self, task_id: int) -> str:
+ task = self._load(task_id)
+ task["worktree"] = ""
+ task["updated_at"] = time.time()
+ self._save(task)
+ return json.dumps(task, indent=2)
+
+ 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"], "[?]")
+ owner = f" owner={t['owner']}" if t.get("owner") else ""
+ wt = f" wt={t['worktree']}" if t.get("worktree") else ""
+ lines.append(f"{marker} #{t['id']}: {t['subject']}{owner}{wt}")
+ return "\n".join(lines)
+
+
+TASKS = TaskManager(REPO_ROOT / ".tasks")
+EVENTS = EventBus(REPO_ROOT / ".worktrees" / "events.jsonl")
+
+
+# -- WorktreeManager: create/list/run/remove git worktrees + lifecycle index --
+class WorktreeManager:
+ def __init__(self, repo_root: Path, tasks: TaskManager, events: EventBus):
+ self.repo_root = repo_root
+ self.tasks = tasks
+ self.events = events
+ self.dir = repo_root / ".worktrees"
+ self.dir.mkdir(parents=True, exist_ok=True)
+ self.index_path = self.dir / "index.json"
+ if not self.index_path.exists():
+ self.index_path.write_text(json.dumps({"worktrees": []}, indent=2))
+ self.git_available = self._is_git_repo()
+
+ def _is_git_repo(self) -> bool:
+ try:
+ r = subprocess.run(
+ ["git", "rev-parse", "--is-inside-work-tree"],
+ cwd=self.repo_root,
+ capture_output=True,
+ text=True,
+ timeout=10,
+ )
+ return r.returncode == 0
+ except Exception:
+ return False
+
+ def _run_git(self, args: list[str]) -> str:
+ if not self.git_available:
+ raise RuntimeError("Not in a git repository. worktree tools require git.")
+ r = subprocess.run(
+ ["git", *args],
+ cwd=self.repo_root,
+ capture_output=True,
+ text=True,
+ timeout=120,
+ )
+ if r.returncode != 0:
+ msg = (r.stdout + r.stderr).strip()
+ raise RuntimeError(msg or f"git {' '.join(args)} failed")
+ return (r.stdout + r.stderr).strip() or "(no output)"
+
+ def _load_index(self) -> dict:
+ return json.loads(self.index_path.read_text())
+
+ def _save_index(self, data: dict):
+ self.index_path.write_text(json.dumps(data, indent=2))
+
+ def _find(self, name: str) -> dict | None:
+ idx = self._load_index()
+ for wt in idx.get("worktrees", []):
+ if wt.get("name") == name:
+ return wt
+ return None
+
+ def _validate_name(self, name: str):
+ if not re.fullmatch(r"[A-Za-z0-9._-]{1,40}", name or ""):
+ raise ValueError(
+ "Invalid worktree name. Use 1-40 chars: letters, numbers, ., _, -"
+ )
+
+ def create(self, name: str, task_id: int = None, base_ref: str = "HEAD") -> str:
+ self._validate_name(name)
+ if self._find(name):
+ raise ValueError(f"Worktree '{name}' already exists in index")
+ if task_id is not None and not self.tasks.exists(task_id):
+ raise ValueError(f"Task {task_id} not found")
+
+ path = self.dir / name
+ branch = f"wt/{name}"
+ self.events.emit(
+ "worktree.create.before",
+ task={"id": task_id} if task_id is not None else {},
+ worktree={"name": name, "base_ref": base_ref},
+ )
+ try:
+ self._run_git(["worktree", "add", "-b", branch, str(path), base_ref])
+
+ entry = {
+ "name": name,
+ "path": str(path),
+ "branch": branch,
+ "task_id": task_id,
+ "status": "active",
+ "created_at": time.time(),
+ }
+
+ idx = self._load_index()
+ idx["worktrees"].append(entry)
+ self._save_index(idx)
+
+ if task_id is not None:
+ self.tasks.bind_worktree(task_id, name)
+
+ self.events.emit(
+ "worktree.create.after",
+ task={"id": task_id} if task_id is not None else {},
+ worktree={
+ "name": name,
+ "path": str(path),
+ "branch": branch,
+ "status": "active",
+ },
+ )
+ return json.dumps(entry, indent=2)
+ except Exception as e:
+ self.events.emit(
+ "worktree.create.failed",
+ task={"id": task_id} if task_id is not None else {},
+ worktree={"name": name, "base_ref": base_ref},
+ error=str(e),
+ )
+ raise
+
+ def list_all(self) -> str:
+ idx = self._load_index()
+ wts = idx.get("worktrees", [])
+ if not wts:
+ return "No worktrees in index."
+ lines = []
+ for wt in wts:
+ suffix = f" task={wt['task_id']}" if wt.get("task_id") else ""
+ lines.append(
+ f"[{wt.get('status', 'unknown')}] {wt['name']} -> "
+ f"{wt['path']} ({wt.get('branch', '-')}){suffix}"
+ )
+ return "\n".join(lines)
+
+ def status(self, name: str) -> str:
+ wt = self._find(name)
+ if not wt:
+ return f"Error: Unknown worktree '{name}'"
+ path = Path(wt["path"])
+ if not path.exists():
+ return f"Error: Worktree path missing: {path}"
+ r = subprocess.run(
+ ["git", "status", "--short", "--branch"],
+ cwd=path,
+ capture_output=True,
+ text=True,
+ timeout=60,
+ )
+ text = (r.stdout + r.stderr).strip()
+ return text or "Clean worktree"
+
+ def run(self, name: str, command: str) -> str:
+ dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
+ if any(d in command for d in dangerous):
+ return "Error: Dangerous command blocked"
+
+ wt = self._find(name)
+ if not wt:
+ return f"Error: Unknown worktree '{name}'"
+ path = Path(wt["path"])
+ if not path.exists():
+ return f"Error: Worktree path missing: {path}"
+
+ try:
+ r = subprocess.run(
+ command,
+ shell=True,
+ cwd=path,
+ capture_output=True,
+ text=True,
+ timeout=300,
+ )
+ out = (r.stdout + r.stderr).strip()
+ return out[:50000] if out else "(no output)"
+ except subprocess.TimeoutExpired:
+ return "Error: Timeout (300s)"
+
+ def remove(self, name: str, force: bool = False, complete_task: bool = False) -> str:
+ wt = self._find(name)
+ if not wt:
+ return f"Error: Unknown worktree '{name}'"
+
+ self.events.emit(
+ "worktree.remove.before",
+ task={"id": wt.get("task_id")} if wt.get("task_id") is not None else {},
+ worktree={"name": name, "path": wt.get("path")},
+ )
+ try:
+ args = ["worktree", "remove"]
+ if force:
+ args.append("--force")
+ args.append(wt["path"])
+ self._run_git(args)
+
+ if complete_task and wt.get("task_id") is not None:
+ task_id = wt["task_id"]
+ before = json.loads(self.tasks.get(task_id))
+ self.tasks.update(task_id, status="completed")
+ self.tasks.unbind_worktree(task_id)
+ self.events.emit(
+ "task.completed",
+ task={
+ "id": task_id,
+ "subject": before.get("subject", ""),
+ "status": "completed",
+ },
+ worktree={"name": name},
+ )
+
+ idx = self._load_index()
+ for item in idx.get("worktrees", []):
+ if item.get("name") == name:
+ item["status"] = "removed"
+ item["removed_at"] = time.time()
+ self._save_index(idx)
+
+ self.events.emit(
+ "worktree.remove.after",
+ task={"id": wt.get("task_id")} if wt.get("task_id") is not None else {},
+ worktree={"name": name, "path": wt.get("path"), "status": "removed"},
+ )
+ return f"Removed worktree '{name}'"
+ except Exception as e:
+ self.events.emit(
+ "worktree.remove.failed",
+ task={"id": wt.get("task_id")} if wt.get("task_id") is not None else {},
+ worktree={"name": name, "path": wt.get("path")},
+ error=str(e),
+ )
+ raise
+
+ def keep(self, name: str) -> str:
+ wt = self._find(name)
+ if not wt:
+ return f"Error: Unknown worktree '{name}'"
+
+ idx = self._load_index()
+ kept = None
+ for item in idx.get("worktrees", []):
+ if item.get("name") == name:
+ item["status"] = "kept"
+ item["kept_at"] = time.time()
+ kept = item
+ self._save_index(idx)
+
+ self.events.emit(
+ "worktree.keep",
+ task={"id": wt.get("task_id")} if wt.get("task_id") is not None else {},
+ worktree={
+ "name": name,
+ "path": wt.get("path"),
+ "status": "kept",
+ },
+ )
+ return json.dumps(kept, indent=2) if kept else f"Error: Unknown worktree '{name}'"
+
+
+WORKTREES = WorktreeManager(REPO_ROOT, TASKS, EVENTS)
+
+
+# -- Base tools (kept minimal, same style as previous sessions) --
+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_list": lambda **kw: TASKS.list_all(),
+ "task_get": lambda **kw: TASKS.get(kw["task_id"]),
+ "task_update": lambda **kw: TASKS.update(kw["task_id"], kw.get("status"), kw.get("owner")),
+ "task_bind_worktree": lambda **kw: TASKS.bind_worktree(kw["task_id"], kw["worktree"], kw.get("owner", "")),
+ "worktree_create": lambda **kw: WORKTREES.create(kw["name"], kw.get("task_id"), kw.get("base_ref", "HEAD")),
+ "worktree_list": lambda **kw: WORKTREES.list_all(),
+ "worktree_status": lambda **kw: WORKTREES.status(kw["name"]),
+ "worktree_run": lambda **kw: WORKTREES.run(kw["name"], kw["command"]),
+ "worktree_keep": lambda **kw: WORKTREES.keep(kw["name"]),
+ "worktree_remove": lambda **kw: WORKTREES.remove(kw["name"], kw.get("force", False), kw.get("complete_task", False)),
+ "worktree_events": lambda **kw: EVENTS.list_recent(kw.get("limit", 20)),
+}
+
+TOOLS = [
+ {
+ "name": "bash",
+ "description": "Run a shell command in the current workspace (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": "task_create",
+ "description": "Create a new task on the shared task board.",
+ "input_schema": {
+ "type": "object",
+ "properties": {
+ "subject": {"type": "string"},
+ "description": {"type": "string"},
+ },
+ "required": ["subject"],
+ },
+ },
+ {
+ "name": "task_list",
+ "description": "List all tasks with status, owner, and worktree binding.",
+ "input_schema": {"type": "object", "properties": {}},
+ },
+ {
+ "name": "task_get",
+ "description": "Get task details by ID.",
+ "input_schema": {
+ "type": "object",
+ "properties": {"task_id": {"type": "integer"}},
+ "required": ["task_id"],
+ },
+ },
+ {
+ "name": "task_update",
+ "description": "Update task status or owner.",
+ "input_schema": {
+ "type": "object",
+ "properties": {
+ "task_id": {"type": "integer"},
+ "status": {
+ "type": "string",
+ "enum": ["pending", "in_progress", "completed"],
+ },
+ "owner": {"type": "string"},
+ },
+ "required": ["task_id"],
+ },
+ },
+ {
+ "name": "task_bind_worktree",
+ "description": "Bind a task to a worktree name.",
+ "input_schema": {
+ "type": "object",
+ "properties": {
+ "task_id": {"type": "integer"},
+ "worktree": {"type": "string"},
+ "owner": {"type": "string"},
+ },
+ "required": ["task_id", "worktree"],
+ },
+ },
+ {
+ "name": "worktree_create",
+ "description": "Create a git worktree and optionally bind it to a task.",
+ "input_schema": {
+ "type": "object",
+ "properties": {
+ "name": {"type": "string"},
+ "task_id": {"type": "integer"},
+ "base_ref": {"type": "string"},
+ },
+ "required": ["name"],
+ },
+ },
+ {
+ "name": "worktree_list",
+ "description": "List worktrees tracked in .worktrees/index.json.",
+ "input_schema": {"type": "object", "properties": {}},
+ },
+ {
+ "name": "worktree_status",
+ "description": "Show git status for one worktree.",
+ "input_schema": {
+ "type": "object",
+ "properties": {"name": {"type": "string"}},
+ "required": ["name"],
+ },
+ },
+ {
+ "name": "worktree_run",
+ "description": "Run a shell command in a named worktree directory.",
+ "input_schema": {
+ "type": "object",
+ "properties": {
+ "name": {"type": "string"},
+ "command": {"type": "string"},
+ },
+ "required": ["name", "command"],
+ },
+ },
+ {
+ "name": "worktree_remove",
+ "description": "Remove a worktree and optionally mark its bound task completed.",
+ "input_schema": {
+ "type": "object",
+ "properties": {
+ "name": {"type": "string"},
+ "force": {"type": "boolean"},
+ "complete_task": {"type": "boolean"},
+ },
+ "required": ["name"],
+ },
+ },
+ {
+ "name": "worktree_keep",
+ "description": "Mark a worktree as kept in lifecycle state without removing it.",
+ "input_schema": {
+ "type": "object",
+ "properties": {"name": {"type": "string"}},
+ "required": ["name"],
+ },
+ },
+ {
+ "name": "worktree_events",
+ "description": "List recent worktree/task lifecycle events from .worktrees/events.jsonl.",
+ "input_schema": {
+ "type": "object",
+ "properties": {"limit": {"type": "integer"}},
+ },
+ },
+]
+
+
+def to_openai_tools(anthropic_tools: list) -> list:
+ converted = []
+ for t in anthropic_tools:
+ converted.append(
+ {
+ "type": "function",
+ "function": {
+ "name": t["name"],
+ "description": t.get("description", ""),
+ "parameters": t.get("input_schema", {"type": "object", "properties": {}}),
+ },
+ }
+ )
+ return converted
+
+
+OPENAI_TOOLS = to_openai_tools(TOOLS)
+
+
+def agent_loop(messages: list):
+ while True:
+ response = client.chat.completions.create(
+ model=MODEL,
+ messages=[{"role": "system", "content": SYSTEM}, *messages],
+ tools=OPENAI_TOOLS,
+ tool_choice="auto",
+ max_tokens=8000,
+ temperature=0.7,
+ )
+ assistant_message = response.choices[0].message
+ messages.append(
+ {
+ "role": "assistant",
+ "content": assistant_message.content or "",
+ "tool_calls": assistant_message.tool_calls,
+ }
+ )
+ if not assistant_message.tool_calls:
+ return
+
+ for tool_call in assistant_message.tool_calls:
+ tool_name = tool_call.function.name
+ try:
+ args = json.loads(tool_call.function.arguments or "{}")
+ except Exception:
+ args = {}
+ handler = TOOL_HANDLERS.get(tool_name)
+ try:
+ output = handler(**args) if handler else f"Unknown tool: {tool_name}"
+ except Exception as e:
+ output = f"Error: {e}"
+ print(f"> {tool_name}:")
+ print(str(output)[:200])
+ messages.append(
+ {
+ "role": "tool",
+ "tool_call_id": tool_call.id,
+ "content": str(output),
+ }
+ )
+
+
+if __name__ == "__main__":
+ print(f"Repo root for s12: {REPO_ROOT}")
+ if not WORKTREES.git_available:
+ print("Note: Not in a git repo. worktree_* tools will return errors.")
+
+ history = []
+ while True:
+ try:
+ query = input("\033[36ms12-openai >> \033[0m")
+ except (EOFError, KeyboardInterrupt):
+ break
+ if query.strip().lower() in ("q", "exit", ""):
+ break
+ history.append({"role": "user", "content": query})
+ agent_loop(history)
+ if history and history[-1].get("role") == "assistant" and history[-1].get("content"):
+ print(history[-1]["content"])
+ print()
diff --git a/agents/openai_code/s_full_openai.py b/agents/openai_code/s_full_openai.py
new file mode 100644
index 000000000..4ac90e80e
--- /dev/null
+++ b/agents/openai_code/s_full_openai.py
@@ -0,0 +1,169 @@
+#!/usr/bin/env python3
+# OpenAI wrapper for the full reference harness.
+#
+# This file keeps all s_full mechanisms, but swaps the Anthropic API client
+# with an OpenAI-compatible adapter.
+
+import json
+import os
+from types import SimpleNamespace
+
+from openai import OpenAI
+
+from agents import s_full as base
+
+
+def _stringify_content(content) -> str:
+ if isinstance(content, str):
+ return content
+ if isinstance(content, list):
+ parts = []
+ for item in content:
+ if hasattr(item, "text"):
+ parts.append(str(item.text))
+ elif isinstance(item, dict):
+ if item.get("type") == "tool_result":
+ parts.append(
+ f"\n"
+ f"{item.get('content', '')}\n"
+ )
+ elif item.get("type") == "text":
+ parts.append(str(item.get("text", "")))
+ else:
+ parts.append(json.dumps(item, ensure_ascii=False))
+ else:
+ parts.append(str(item))
+ return "\n".join(p for p in parts if p)
+ return str(content)
+
+
+def _convert_messages(messages: list, system: str = None) -> list:
+ out = []
+ if system:
+ out.append({"role": "system", "content": system})
+ for msg in messages:
+ role = msg.get("role", "user")
+ content = _stringify_content(msg.get("content", ""))
+ if role not in ("system", "user", "assistant"):
+ role = "user"
+ out.append({"role": role, "content": content})
+ return out
+
+
+def _convert_tools(tools: list) -> list:
+ converted = []
+ for t in tools or []:
+ if "function" in t:
+ converted.append(t)
+ continue
+ name = t.get("name")
+ if not name:
+ continue
+ converted.append(
+ {
+ "type": "function",
+ "function": {
+ "name": name,
+ "description": t.get("description", ""),
+ "parameters": t.get("input_schema", {"type": "object", "properties": {}}),
+ },
+ }
+ )
+ return converted
+
+
+class _OpenAIAnthropicCompatMessages:
+ def __init__(self, client: OpenAI):
+ self._client = client
+
+ def create(self, model: str, messages: list, tools: list = None, max_tokens: int = 8000, system: str = None):
+ oa_messages = _convert_messages(messages, system=system)
+ oa_tools = _convert_tools(tools)
+ kwargs = {
+ "model": model,
+ "messages": oa_messages,
+ "max_tokens": max_tokens,
+ "temperature": 0.7,
+ }
+ if oa_tools:
+ kwargs["tools"] = oa_tools
+ kwargs["tool_choice"] = "auto"
+ resp = self._client.chat.completions.create(**kwargs)
+ msg = resp.choices[0].message
+
+ blocks = []
+ if msg.content:
+ blocks.append(SimpleNamespace(type="text", text=msg.content))
+ for tc in msg.tool_calls or []:
+ try:
+ parsed = json.loads(tc.function.arguments or "{}")
+ except Exception:
+ parsed = {}
+ blocks.append(
+ SimpleNamespace(
+ type="tool_use",
+ id=tc.id,
+ name=tc.function.name,
+ input=parsed,
+ )
+ )
+ stop_reason = "tool_use" if (msg.tool_calls and len(msg.tool_calls) > 0) else "end_turn"
+ return SimpleNamespace(content=blocks, stop_reason=stop_reason)
+
+
+class _OpenAIAnthropicCompatClient:
+ def __init__(self, api_key: str = None, base_url: str = None):
+ self._client = OpenAI(api_key=api_key, base_url=base_url)
+ self.messages = _OpenAIAnthropicCompatMessages(self._client)
+
+
+def _configure_openai_backend():
+ base.client = _OpenAIAnthropicCompatClient(
+ api_key=os.getenv("OPENAI_API_KEY"),
+ base_url=os.getenv("OPENAI_BASE_URL"),
+ )
+ base.MODEL = (
+ os.getenv("OPENAI_MODEL_ID")
+ or os.getenv("OPENAI_MODEL")
+ or os.getenv("MODEL_ID")
+ )
+ if not base.MODEL:
+ raise RuntimeError("Missing model env var: set OPENAI_MODEL_ID / OPENAI_MODEL / MODEL_ID")
+
+
+def main():
+ _configure_openai_backend()
+ history = []
+ while True:
+ try:
+ query = input("\033[36ms_full_openai >> \033[0m")
+ except (EOFError, KeyboardInterrupt):
+ break
+ if query.strip().lower() in ("q", "exit", ""):
+ break
+ if query.strip() == "/compact":
+ if history:
+ print("[manual compact via /compact]")
+ history[:] = base.auto_compact(history)
+ continue
+ if query.strip() == "/tasks":
+ print(base.TASK_MGR.list_all())
+ continue
+ if query.strip() == "/team":
+ print(base.TEAM.list_all())
+ continue
+ if query.strip() == "/inbox":
+ print(json.dumps(base.BUS.read_inbox("lead"), indent=2))
+ continue
+ history.append({"role": "user", "content": query})
+ base.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()
+
+
+if __name__ == "__main__":
+ main()