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:
1 parent
e364970b46
commit
7bb3c37a10
4 files changed
+552
-1
No files matched your search
+55
-1
@@ -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):
|
||||
|
||||
Reference in new issue
Block a user