feat: agent definition v2 (registry, persistence, developerInstructions channel)

Registry: POST /tasks accepts agent_name, loading <name>.json
from repo-root agents/ (traversal-guarded, skill-pack re-validated);
agent and agent_name together is a 400. Persistence: tasks gains
agent_name/agent_version/agent_instructions (migrate + PG schema),
returned by GET /tasks; stored prompt stays clean. Channel: local
and docker dispatch pass --developer-instructions to host.py
dispatch -> create_thread(developerInstructions=...); run/fork do
not re-send. Cloud executor has no such channel, so the worker
falls back to the v1 marked header in the in-memory dispatch
prompt only (DB prompt stays clean). Goal/coordinator tasks
still reject instructions.

Tests: test_agent_def_v2.py (7 tests) + updated v1 suite.
This commit is contained in:
host committed 2026-11-04 18:01:44 +00:00
1 parent 7bb3c37a10
commit 1672ab9ff2
8 files changed
+361 -35

No files matched your search

+20 -4
View File
@@ -351,7 +351,8 @@ class LocalExecutor(Executor):
self.model = model
def _base_command(self, model=None, effort=None, approval=None,
sandbox=None, model_provider=None):
sandbox=None, model_provider=None,
developer_instructions=None):
cmd = [sys.executable, self.host_py_path]
if self.config_path is not None:
cmd += ["--config", self.config_path]
@@ -367,6 +368,13 @@ class LocalExecutor(Executor):
if effort:
cmd += ["--effort", effort]
cmd += _approval_cli_args(approval)
# agent 定义 v2:仅 dispatch_task 传,run/fork/goal
# 不传(线程已建好指令,续跑不重复发;fork 靠线程
# 分叉继承,goal 不接受指令)。空值不传参,旧命令
# 逐字节不变。
if developer_instructions:
cmd += ["--developer-instructions",
developer_instructions]
# 显式沙箱放最后:argparse 里后出现的 --sandbox 覆盖前面
# 的(approval=full 自带 danger-full-access);server 侧
# 已拒绝 plan+full 同存,这里是第二道防线。
@@ -545,7 +553,9 @@ class LocalExecutor(Executor):
approval=task.get("approval"),
sandbox=("read-only"
if task.get("plan") else None),
model_provider=model_provider)
model_provider=model_provider,
developer_instructions=task.get(
"agent_instructions"))
cmd += ["dispatch", "--name", self._host_name(task),
"--task", task["prompt"]]
repo = task.get("repo") or self.repo
@@ -1162,7 +1172,8 @@ class DockerExecutor(Executor):
def _inner_host_command(self, *args, model=None, effort=None,
approval=None, sandbox=None,
model_provider=None):
model_provider=None,
developer_instructions=None):
"""容器内 host.py 命令:python3 /app/host.py [args...]。
config_path 若已给,容器内用 /app/<basename> 指代它——
@@ -1191,6 +1202,10 @@ class DockerExecutor(Executor):
cmd += _approval_cli_args(approval)
if sandbox:
cmd += ["--sandbox", sandbox]
# agent 定义 v2:仅 dispatch_task 传,语义同本地版。
if developer_instructions:
cmd += ["--developer-instructions",
developer_instructions]
cmd += list(args)
return cmd
@@ -1421,7 +1436,8 @@ class DockerExecutor(Executor):
"--task", task["prompt"], model=task.get("model"),
effort=task.get("effort"), approval=task.get("approval"),
sandbox=("read-only" if task.get("plan") else None),
model_provider=model_provider)
model_provider=model_provider,
developer_instructions=task.get("agent_instructions"))
repo = task.get("repo")
if repo:
inner += ["--repo", repo]