diff --git a/docs/architecture/decisions/001-registry-pattern.md b/docs/architecture/decisions/001-registry-pattern.md index dc3ba67..cab8bc6 100644 --- a/docs/architecture/decisions/001-registry-pattern.md +++ b/docs/architecture/decisions/001-registry-pattern.md @@ -27,7 +27,7 @@ This follows the pattern established by open-memory, which exposes 9 operations - `op` field name is unambiguous in OpenCode's context **Negative:** -- The `op` and `args` fields are not individually validated by the outer schema — validation happens inside the dispatch handler +- The `op` and `args` fields are not individually validated by the outer Zod schema — validation happens inside the dispatch handler - Agent must call help to discover operations; the tool description can only hint - Slightly more overhead per call (string dispatch vs direct function call) @@ -36,6 +36,10 @@ This follows the pattern established by open-memory, which exposes 9 operations - Validation errors are clear and include usage guidance - The help operation provides complete reference with examples +## Zod Requirement + +The tool args schema **must** use Zod because OpenCode's registry calls `z.object(def.args)` on every plugin tool definition. There is no JSON Schema alternative for tool definitions — this is the SDK's contract. TypeBox is used for internal config validation where we control the schema, but the tool definition boundary requires Zod. This is a small surface area (one schema for one tool) and doesn't extend beyond the `taskgraph` tool definition. + ## Note on Schema Libraries The tool's outer parameter schema uses **Zod** (from `@opencode-ai/plugin`'s `tool()` helper) because that's what OpenCode's plugin SDK provides for tool definitions. The plugin's internal config schema uses **TypeBox** (from `@alkdev/typebox`, already a dependency via `@alkdev/taskgraph`) for compile-time types and runtime `Value.Check()`. These are two different concerns: Zod for OpenCode's tool interface, TypeBox for our own config. No conflict — each is used where it's the native choice. diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index 858f57c..9787eff 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -379,7 +379,14 @@ No hooks in v1. Future: task status injection into system prompt (similar to ope Single tool with `{op: string, args?: Record}` schema. The `op` field dispatches to an operation handler via the registry. Unknown operation names produce a friendly error directing to `taskgraph({op: "help"})`. -The tool's parameter schema uses **Zod** (from `@opencode-ai/plugin`'s `tool()` helper) because that's what OpenCode's plugin SDK provides for tool definitions. The plugin's internal config schema uses **TypeBox** for compile-time types and runtime `Value.Check()`. These are two different concerns: Zod for the tool's external interface (what the LLM sees), TypeBox for our own config (what we validate at startup). +**Zod is required for the tool args schema.** OpenCode's plugin SDK defines `tool()` with `args: z.ZodRawShape`, and OpenCode's registry does `z.object(def.args)` to construct the parameter schema. There is no JSON Schema escape hatch — Zod is the only option for tool definitions. This is a small surface area (one schema for one tool), not a design choice. + +The plugin's internal config validation uses **TypeBox** (`@alkdev/typebox/value`) for `Value.Check()` and `Value.Cast()`. These are two different concerns at two different layers: + +| Layer | Concern | Library | Why | +|-------|----------|---------|-----| +| Tool definition (what the LLM sees) | Parameter schema for `taskgraph({op, args})` | Zod | Required by OpenCode's registry — it calls `z.object(def.args)` | +| Plugin config (what opencode.json provides) | Validate and apply defaults to `{source: {type, tasksPath}}` | TypeBox | Better JSON Schema ergonomics, already a dependency, matches `@alkdev/taskgraph`'s validation pattern | The `source` is passed from the plugin entry to `createTools()` and stored in the registry for all operations to use.