feat: declarative agent definition v1 (inline agent on task creation)

Agent definition is a versioned JSON document bundling the task
fields this codebase already honours (model/effort/approval/
executor/plan), plus instructions and required skills. At task
creation, explicit body fields win per field over the definition;
unresolved fields stay NULL and follow the worker-default chain
(and rule routing v0 when model is left empty).

Instructions reach the model only as a marked header prepended
to the stored prompt, because the dispatch path has no
developerInstructions channel; goal and coordinator tasks reject
instructions. Skills are validated against the cloud/skills pack
at parse time; seeding itself stays worker-global, documented
as the v1 boundary. Routing policy is not part of a definition
in v1 (worker-global), and unknown fields are rejected so typos
cannot be silently dropped. No new tasks-table column in v1;
definition identity is echoed in the creation response and via
the prompt header.

Tests: test_agent_def.py (11 tests).
This commit is contained in:
host committed 2026-11-04 18:01:44 +00:00
1 parent e364970b46
commit 7bb3c37a10
4 files changed
+552 -1

No files matched your search

+55 -1
View File
@@ -57,6 +57,9 @@ from decimal import Decimal
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import parse_qs, quote, urlparse
from cloud.agent_def import format_instructions_header as _agent_format_header
from cloud.agent_def import list_known_skills as _agent_known_skills
from cloud.agent_def import parse_agent_definition as _parse_agent_definition
from cloud.auth import ROLES as _MEMBER_ROLES
from cloud.auth import AuthStore
from cloud.coordinator import parse_parts as _coord_parse_parts
@@ -4298,6 +4301,23 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
return
if prompt_goal_target:
prompt = prompt_goal_target
# 声明式 agent 定义 v1(见 cloud/agent_def.py):inline
# agent 对象,逐字段填充任务选项;显式 body 字段优先
# 于定义,定义本身不新开任务表列,解析出的字段落进
# 已有列,instructions 以带标记的头拼进 prompt(见后),
# 身份在创建响应里回显。
definition = None
if body.get("agent") is not None:
if not isinstance(body.get("agent"), dict):
self._send_json(400, {"error": "agent must be an object"})
return
try:
definition = _parse_agent_definition(
body.get("agent"),
known_skills=_agent_known_skills())
except ValueError as exc:
self._send_json(400, {"error": str(exc)})
return
depends_on = body.get("depends_on") or []
if isinstance(depends_on, str):
depends_on = [depends_on]
@@ -4311,7 +4331,11 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
return
# executor 可选:None 表示任意 worker 可认领;指定时只能是
# local/docker/cloud,非法值 400 且不建任务。
# 未指定时用 agent 定义的 executor(若有),再由下面
# 的校验与环境继承处理。
executor = body.get("executor")
if executor is None and definition is not None:
executor = definition.defaults.get("executor")
if executor is not None and executor not in (
"local", "docker", "cloud"):
self._send_json(400, {
@@ -4322,6 +4346,8 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
# 是 console 三档(ask/auto/full),非法值 400 且不建任务。
# 三者都缺省(NULL)时 worker 走配置默认值,行为与过去一致。
model = body.get("model")
if model is None and definition is not None:
model = definition.defaults.get("model")
if model is not None:
if not isinstance(model, str) or not model.strip():
self._send_json(400, {
@@ -4329,12 +4355,16 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
return
model = model.strip()
effort = body.get("effort")
if effort is None and definition is not None:
effort = definition.defaults.get("effort")
if effort is not None and effort not in (
"low", "medium", "high", "xhigh", "max"):
self._send_json(400, {
"error": "effort must be low, medium, high, xhigh or max"})
return
approval = body.get("approval")
if approval is None and definition is not None:
approval = definition.defaults.get("approval")
if approval is not None and approval not in ("ask", "auto", "full"):
self._send_json(400, {
"error": "approval must be ask, auto or full"})
@@ -4369,7 +4399,15 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
child_effort = parsed.get("effort")
# 计划模式:只读沙箱,不改任何文件;与“完全访问”
# (danger-full-access)语义直接冲突,拒绝建任务。
plan = 1 if body.get("plan") in (True, 1) else 0
# plan 未在 body 里出现时用 agent 定义的 plan(若有),
# 否则为 0;body 里显式给了 plan(含 False)时以 body 为准。
if "plan" in body:
plan = 1 if body.get("plan") in (True, 1) else 0
elif definition is not None \
and definition.defaults.get("plan") is not None:
plan = definition.defaults["plan"]
else:
plan = 0
if plan and approval == "full":
self._send_json(400, {
"error": "plan 模式与 approval=full 冲突(只读 vs 完全访问)"})
@@ -4577,6 +4615,18 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
if not bundle_files:
self._send_json(400, {"error": "附件包为空"})
return
# agent instructions:goal 与协调员任务不接受(见
# agent_def.py 的边界说明)。非这两种时以带标记的头
# 拼进 prompt,存库的 prompt 即带头的 prompt,worker
# 派发、回读看到的都是同一份;子任务派生默认值解析
# 在前面已用原始 prompt 跑过,不受头影响。
if definition is not None and definition.instructions:
if goal_mode or verify_command or coord_parts:
self._send_json(400, {
"error": "agent instructions cannot be used with"
" goal/coordinator tasks"})
return
prompt = _agent_format_header(definition, prompt)
now = time.time()
conn = self._db()
try:
@@ -4663,6 +4713,10 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
task["quota_reserved_usd_micros"] = (
len(coord_parts)
* COORDINATOR_RESERVE_PER_PART_USD_MICROS)
if definition is not None:
# 仅新建任务的 201 响应回显定义身份;幂等返回的
# 已有任务不补这个字段(那是另一条创建记录)。
task["agent"] = definition.to_summary()
self._send_json(201, task)
def _list_tasks(self, user_id, query):