Skip to content

feat(compose): add agent input and output schemas - #623

Open
kingfs wants to merge 6 commits into
mainfrom
feat/agent-io-schema
Open

feat(compose): add agent input and output schemas#623
kingfs wants to merge 6 commits into
mainfrom
feat/agent-io-schema

Conversation

@kingfs

@kingfs kingfs commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add optional input_schema and output_schema fields to agent definitions while preserving existing behavior when they are omitted.
  • Accept inline JSON Schema objects or booleans, plus the same file, HTTP, and Git source descriptors used by scheduler scripts.
  • Resolve referenced schemas into canonical snapshots so project hashes and revisions remain stable across protobuf round trips.
  • Expose both schemas through the v2 project API and managed agent configuration for external discovery and management.
  • Treat the schemas as interface metadata; this change does not automatically validate run payloads or agent output.
  • Document the new fields in the English and Chinese Compose manuals.

Testing

  • go test ./pkg/compose ./pkg/projects ./pkg/agentcompose/api
  • task lint
  • task build
  • task docs:build
  • Manual config --json verification with an inline input schema and file-backed output schema.
  • Manual daemon apply verification: the first up created the project and a second identical up returned unchanged with the same revision.
  • task test completed protobuf, deployment, installer, and script checks, then hit the existing unrelated TestEnsurePromptAttachLLMFacadeEnvClaudeUsesControllerStore failure. The same focused test fails on unmodified origin/main.

Checklist

  • Documentation updated when behavior or configuration changed.
  • Tests added or updated for user-visible behavior.
  • No secrets, private endpoints, internal certificates, or local runtime state included.

@monkeyscan

monkeyscan Bot commented Aug 21, 2026

Copy link
Copy Markdown

PR Title: feat(compose): add agent input and output schemas

Commit: 747963f

本次变更为 agent-compose 的 agent 定义新增可选的 input_schema / output_schema(JSON Schema)能力:支持内联(YAML/JSON)或复用 scheduler.script 的 source descriptor(file/http/git)。核心实现是 pkg/compose 新增的 JSONSchemaSource/JSONSchema 类型与 normalizeJSONSchemaSource 解析/归一化逻辑,并贯通到 NormalizeOptions.ResolveSchemaURLs、proto AgentSpec.input_schema_json/output_schema_json、ProjectSpecToProto/ProjectSpecYAMLShape 映射、canonical JSON/YAML 输出,以及 projects records 的 managed agent config 持久化。整体设计与现有 script source 机制保持一致,未解析 source 的持久化保护(ValidateResolvedScriptURLs)、布尔/对象 schema、proto 往返哈希均有测试覆盖。主要风险集中在两处:(1) schema 规范化经 float64 中转导致大整数精度静默丢失(涉及 canonicalJSONSchemaDocument、JSONSchema.UnmarshalJSON/yamlValue、agentJSONSchemaYAMLValue,文件源 schema 在 normalize 时即被破坏);(2) 通过文件引用规避 provider 关键字冲突的 schema 经 proto→YAML 导出后会再次与 source descriptor 形状混淆,无法往返解析。

@kingfs

kingfs commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

补充说明一下本 PR 增加的能力及其影响。

增加的内容

Agent 定义新增两个彼此独立、完全可选的字段:

  • input_schema:描述 Agent 可接收的输入结构;
  • output_schema:描述 Agent 对外声明的输出结构。

两个字段都接受标准 JSON Schema object 或 boolean schema,并支持两种配置方式:

  1. 直接在 agent-compose.yml 中内联;
  2. 使用与 scheduler.script 相同的 source descriptor,从 filehttpgit 来源加载。

已有的 description 字段继续用于描述 Agent 的整体职责;JSON Schema 中各属性的 description 则可供外部平台展示参数用途。

配置示例

下面的例子内联声明输入 Schema,并从项目内文件加载输出 Schema:

agents:
  researcher:
    description: 调研指定主题并返回带引用的结论。
    input_schema:
      type: object
      additionalProperties: false
      required: [query]
      properties:
        query:
          type: string
          description: 需要调研的主题或问题。
        max_findings:
          type: integer
          minimum: 1
          maximum: 10
          default: 3
          description: 最多返回多少条结论。
    output_schema:
      provider: file
      path: ./schemas/research-result.schema.json

schemas/research-result.schema.json

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "required": ["summary", "findings"],
  "properties": {
    "summary": {
      "type": "string",
      "description": "对调研问题的简要回答。"
    },
    "findings": {
      "type": "array",
      "description": "有依据的调研结论。",
      "items": {
        "type": "object",
        "required": ["claim", "source"],
        "properties": {
          "claim": {
            "type": "string",
            "description": "事实性结论。"
          },
          "source": {
            "type": "string",
            "description": "支持该结论的来源。"
          }
        }
      }
    }
  }
}

执行 agent-compose config --json 或应用项目时,文件引用会被解析并保存为规范化快照,而不是把本地路径作为运行时依赖保留下来。快照会进入 project revision、spec hash、v2 AgentSpec API 和 managed agent 的 config_json,便于外部平台稳定读取。

一个完整的公开示例也已放在:

https://github.com/kingfs/agent-compose-sample/tree/master/agents/json-schema-agent

兼容性与影响

  • 未配置时无行为变化:两个字段均为可选字段;已有 Compose 配置的规范化结果及 hash 保持不变。
  • 两个字段互相独立:可以只声明输入、只声明输出、同时声明,或都不声明。
  • 引用内容参与版本管理:source 在 apply 前解析为 canonical JSON 快照;Schema 内容变化会形成新的 spec hash/revision。
  • API 可发现:v2 AgentSpec 通过 input_schema_jsonoutput_schema_json 返回完整 Schema。
  • 当前定位是接口元数据:本 PR 不会自动使用 input_schema 校验某次调用的 prompt/payload_json,也不会自动使用 Agent 定义中的 output_schema 校验实际输出。
  • 现有单次调用级 RunAgentRequest.output_schema_json 行为不变;调用方显式传入时,仍走原有结构化输出链路。

如果后续需要把声明升级为强制运行时契约,建议另行设计:输入侧明确只校验 payload_json(自然语言 prompt 不适合直接套 JSON Schema),输出侧则可考虑在请求未显式提供 output schema 时继承 Agent 定义中的声明。

Comment thread pkg/agentcompose/api/project_yaml_shape.go
Comment thread pkg/compose/json_schema_source.go
@monkeyscan

monkeyscan Bot commented Aug 21, 2026

Copy link
Copy Markdown

PR Title: feat(compose): add agent input and output schemas

Commit: cff2f82

本次变更旨在修复 JSON Schema 在序列化往返时大整数精度静默丢失的问题(对应历史 finding 4aba060c)。核心改动:(1) pkg/compose/json_schema_source.go 中 yamlValue() 与 canonicalJSONSchemaDocument() 改用 json.Decoder.UseNumber(),新增 jsonNumbersForYAML() 将 json.Number 转为带显式 !!int/!!float 标签的 yaml.Node;(2) pkg/agentcompose/api/project_yaml_shape.go 中 agentJSONSchemaYAMLValue() 同步改用 UseNumber(),新增 jsonNumbersForProjectYAML() 实现相同逻辑;(3) 将 source 判定从 mappingHasKey(value,"provider") 收窄为 mappingHasSourceProvider()(provider 值须为 file/http/git),避免 JSON Schema 内含 provider 扩展关键字时被误判为外部 source。整体上,int64/uint64 范围内的大整数字面量现在可以稳定往返,历史精度丢失缺陷已解决,新增测试(大整数保留、provider 关键字往返、proto→YAML→Parse 的 hash 保持)覆盖了主要路径。发现的遗留问题:(1) 对小数/指数形式字面量(如 0.50、1e-3、高精度小数)存在新的不对称——canonical JSON 经 UseNumber 保留原始字面量,但 YAML 导出→导入经 yaml.v3 必然解析为 float64 后再 json.Marshal 归一化,导致精度被截断、表示被改写、项目 hash 不稳定(低严重度,数据完整性);(2) json.Unmarshal 改为 Decoder.Decode 后,顶层值之后的尾随非法字符被静默忽略,校验被放宽(低严重度,功能正确性)。

@monkeyscan

monkeyscan Bot commented Aug 21, 2026

Copy link
Copy Markdown

PR Title: feat(compose): add agent input and output schemas

Commit: f2c16bb

本次改动为纯文档/注释澄清,共 3 个文件(+5/-4):pkg/compose/json_schema_source.go 的 JSONSchemaSource 注释、英文与中文的 agent-compose-yaml-manual 文档。改动将 source descriptor 的判定语义从"包含 provider 键的 mapping"细化为"顶层 provider 值为 file/http/git 的 mapping;其他 mapping 与 boolean 视为内联 schema"。

核查结论:

  1. 代码行为在本 PR 中未变。mappingHasSourceProvider 早已实现"provider 值精确等于 file/http/git 才判定为 source descriptor"的语义,diff 仅改注释;sources 包也仅定义 file/http/git 三种 provider,注释与实现一致、无遗漏。
  2. 该语义已被既有测试覆盖:TestAgentJSONSchemaProviderKeywordRoundTripsAsInlineSchema(自定义 provider 关键字按内联处理)、TestAgentJSONSchemaFileSourceIsSnapshotted、TestAgentJSONSchemaRejectsNonSchemaDocument、TestAgentJSONSchemaPreservesLargeNumbers。
  3. 中英文文档翻译准确、与代码行为一致;"top-level provider 值"的限定也正确对应实现(仅检查映射顶层键)。
  4. 历史 finding(canonicalJSONSchemaDocument 经 float64 中转导致大整数精度丢失)已被修复:当前实现使用 decoder.UseNumber() 保留 json.Number,且有 TestAgentJSONSchemaPreservesLargeNumbers 验证,不再复现,故不重复上报。
  5. 边缘情况(provider 大写或环境变量引用不会命中 source descriptor 判定)属于 base 既有行为,非本次 diff 引入,不构成 actionable finding。

总体评估:无代码行为变化、无回归风险、无安全问题,文档与实现一致,未发现需要上报的缺陷。

Comment thread pkg/compose/json_schema_source.go
Comment thread pkg/compose/json_schema_source.go Outdated
@monkeyscan

monkeyscan Bot commented Aug 21, 2026

Copy link
Copy Markdown

PR Title: feat(compose): add agent input and output schemas

Commit: 2d27035

本变更(pkg/compose/json_schema_source.go 及对应测试)包含两项修复:1) canonicalJSONSchemaDocument 在解码首个 JSON 值后追加第二次 Decode,拒绝顶层值之后的尾随数据(对尾随空白仍放行),恢复与 json.Unmarshal 一致的严格解析语义,修复了历史问题 d273a93d(校验放宽);2) 新增 canonicalizeSchemaNumbers,把含 .eE 的 json.Number 统一转为 float64 后再 json.Marshal,使 canonical JSON 与 YAML 导出→导入的 float64 归一化行为一致,修复历史问题 5cbd4ea3(往返不稳定、项目 hash 变化)。同时删除了未使用的 mappingHasKey。

评估:尾随数据检查正确且完整(能拒绝第二 JSON 值、非法 token、null,且不影响尾随空白)。数值归一化方向能恢复往返稳定,但以牺牲精度为代价——来自纯 JSON 来源(file/http/git 外部 source、ParseCanonicalJSON)的高精度小数/指数数字(如 0.12345678901234567890123)现在会被静默截断到 float64 精度,而 base 中原样保留字面量,属于静默的数据语义改变。新增测试仅覆盖尾随数据拒绝,未覆盖数值归一化及 YAML 往返稳定性,存在回归测试缺口。

Comment thread pkg/compose/json_schema_source.go Outdated
Comment thread pkg/compose/json_schema_source_test.go
@monkeyscan

monkeyscan Bot commented Aug 21, 2026

Copy link
Copy Markdown

PR Title: feat(compose): add agent input and output schemas

Commit: ca325aa

本次变更移除了 canonicalJSONSchemaDocument 中对 canonicalizeSchemaNumbers 的调用(该函数会把含 .eE 的 json.Number 经 Float64() 转成 float64),使纯 JSON 来源(file/http/git 外部 schema 解析与 ParseCanonicalJSON 读回)的 schema 数字字面量如 0.12345678901234567890123、1e-3 得以原样保留,不再被静默截断或改写为 float64 最短表示。这正好修复了先前已确认的历史精度丢失问题。同时新增 TestAgentJSONSchemaPreservesJSONNumberLiterals,直接通过 UnmarshalJSON 验证字面量保留。整体评估:改动范围小、方向正确——json.Marshal 对 json.Number 会按字面量原样输出,因此 JSON 路径行为符合预期,且该测试在移除规范化后可通过、在旧逻辑下会失败,是有效的回归测试。需要注意的缺口:内联 YAML 入口(UnmarshalYAML)仍通过 value.Decode 把 YAML 标量解码为 float64,高精度数字字面量依旧丢失,新测试也未覆盖该入口,导致“保留数字字面量”的行为在不同入口间不一致(低严重度,已在发现中说明)。

Comment thread pkg/compose/json_schema_source_test.go
@monkeyscan

monkeyscan Bot commented Aug 21, 2026

Copy link
Copy Markdown

PR Title: feat(compose): add agent input and output schemas

Commit: a0fe80b

本次改动对 pkg/compose/json_schema_source.go 的 JSON Schema 处理做了加固与增强:引入 github.com/santhosh-tekuri/jsonschema/v6,用 jsonschema.UnmarshalJSON 替换原先基于 json.Decoder 的解析,并通过 compiler.Compile 在保存快照前对 schema 做真实编译校验(拒绝 type: unknown、非法正则等);新增 rejectJSONSchemaResourceLoader 拒绝所有外部 $ref 资源加载,确保应用已存储快照时不会隐式访问文件系统或网络;新增 jsonSchemaYAMLValue 将内联 YAML schema 转为 JSON 时保留数字字面量(如 0.12345678901234567890123、1e-3)。测试新增了内联 YAML 数字保留、非法关键字拒绝、外部 $ref 拒绝三组用例,文档同步更新。整体方向正确,安全加固到位;但需注意两点回归风险:canonicalJSONSchemaDocument 删除了原有的尾部多余数据(io.EOF)校验,可能使现有测试 TestAgentJSONSchemaRejectsTrailingJSON 失效并静默接受含尾部垃圾的来源内容;jsonSchemaYAMLValue 强制 mapping key 必须为 !!str,导致内联 YAML 中使用 << 合并键(锚点复用)的写法不再被支持。

}

func canonicalJSONSchemaDocument(data []byte) ([]byte, error) {
value, err := jsonschema.UnmarshalJSON(bytes.NewReader(data))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

canonicalJSONSchemaDocument 移除了尾部多余数据校验,可能使现有测试失败并静默接受畸形 schema 内容

旧实现使用 json.Decoder 解码后用 decoder.Decode(&struct{}{}) != io.EOF 显式拒绝尾部多余数据(如 {"type":"object"} trailing)。本次改动将解析替换为 jsonschema.UnmarshalJSON,并删除了该 EOF 检查(同时移除了 io 导入)。santhosh-tekuri/jsonschema/v6 的 UnmarshalJSON 仅对输入做单次 json.Decoder.Decode,不会检查首个 JSON 值之后的尾部内容,因此含尾部垃圾数据的 schema 会被静默截断接受,且现有的 TestAgentJSONSchemaRejectsTrailingJSON 用例将失败(该用例本次未修改)。

Problem code:

Changed code at pkg/compose/json_schema_source.go:120

Recommendation:
在调用 jsonschema.UnmarshalJSON 后重新加入尾部数据校验(保留原先的双 Decode + io.EOF 检查,或等价地读取剩余字节确认仅为空白),以避免静默接受畸形来源内容并保持现有测试通过。

Suggested diff:

--- a/pkg/compose/json_schema_source.go
+++ b/pkg/compose/json_schema_source.go
@@
 import (
 	"bytes"
 	"context"
 	"encoding/json"
 	"fmt"
+	"io"
 
 	"agent-compose/pkg/sources"
@@
 func canonicalJSONSchemaDocument(data []byte) ([]byte, error) {
 	value, err := jsonschema.UnmarshalJSON(bytes.NewReader(data))
 	if err != nil {
 		return nil, fmt.Errorf("invalid JSON Schema: %w", err)
 	}
+	decoder := json.NewDecoder(bytes.NewReader(data))
+	decoder.UseNumber()
+	if err := decoder.Decode(&struct{}{}); err != io.EOF {
+		return nil, fmt.Errorf("invalid JSON Schema: unexpected trailing data")
+	}
 	switch value.(type) {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant