@orkestrel/scaffold 0.0.67 → 0.0.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/bin/main.js +67 -44
  2. package/dist/bin/main.js.map +1 -1
  3. package/dist/host/agents/templates/brief.md +9 -0
  4. package/dist/host/claude/agents/orkestrel.md +4 -4
  5. package/dist/host/claude/rules/names.md +15 -0
  6. package/dist/host/claude/rules/tests.md +33 -4
  7. package/dist/host/claude/rules/workspace.md +14 -2
  8. package/dist/host/dotfiles/prettierignore +3 -0
  9. package/dist/host/guides/README.md +65 -0
  10. package/dist/host/guides/abort.md +169 -0
  11. package/dist/host/guides/agent.md +1567 -0
  12. package/dist/host/guides/brief.md +1266 -0
  13. package/dist/host/guides/browser.md +2200 -0
  14. package/dist/host/guides/budget.md +196 -0
  15. package/dist/host/guides/codec.md +519 -0
  16. package/dist/host/guides/console.md +785 -0
  17. package/dist/host/guides/contract.md +1193 -0
  18. package/dist/host/guides/csv.md +541 -0
  19. package/dist/host/guides/database.md +2518 -0
  20. package/dist/host/guides/emitter.md +233 -0
  21. package/dist/host/guides/form.md +1791 -0
  22. package/dist/host/guides/html.md +717 -0
  23. package/dist/host/guides/indexeddb.md +505 -0
  24. package/dist/host/guides/interpret.md +1029 -0
  25. package/dist/host/guides/lsp.md +515 -0
  26. package/dist/host/guides/markdown.md +964 -0
  27. package/dist/host/guides/mcp.md +5554 -0
  28. package/dist/host/guides/middleware.md +927 -0
  29. package/dist/host/guides/msg.md +440 -0
  30. package/dist/host/guides/ndjson.md +120 -0
  31. package/dist/host/guides/ollama.md +380 -0
  32. package/dist/host/guides/pool.md +280 -0
  33. package/dist/host/guides/probe.md +1210 -0
  34. package/dist/host/guides/process.md +1620 -0
  35. package/dist/host/guides/program.md +1110 -0
  36. package/dist/host/guides/qualifier.md +854 -0
  37. package/dist/host/guides/queue.md +370 -0
  38. package/dist/host/guides/rater.md +330 -0
  39. package/dist/host/guides/reason.md +1122 -0
  40. package/dist/host/guides/relation.md +373 -0
  41. package/dist/host/guides/router.md +753 -0
  42. package/dist/host/guides/scaffold.md +192 -31
  43. package/dist/host/guides/sea.md +383 -0
  44. package/dist/host/guides/server.md +752 -0
  45. package/dist/host/guides/sqlite.md +330 -0
  46. package/dist/host/guides/sse.md +187 -0
  47. package/dist/host/guides/supervisor.md +4890 -0
  48. package/dist/host/guides/table.md +1556 -0
  49. package/dist/host/guides/template.md +280 -0
  50. package/dist/host/guides/terminal.md +1145 -0
  51. package/dist/host/guides/test.md +2969 -0
  52. package/dist/host/guides/timeout.md +252 -0
  53. package/dist/host/guides/tool.md +507 -0
  54. package/dist/host/guides/toolbox.md +1038 -0
  55. package/dist/host/guides/websocket.md +282 -0
  56. package/dist/host/guides/worker.md +615 -0
  57. package/dist/host/guides/workflow.md +1507 -0
  58. package/dist/host/guides/workspace.md +595 -0
  59. package/dist/host/manifest.json +1218 -10
  60. package/dist/host/tests/policy.test.ts +279 -2
  61. package/dist/host/tests/setupPolicy.ts +445 -6
  62. package/dist/src/core/index.cjs +38 -16
  63. package/dist/src/core/index.cjs.map +1 -1
  64. package/dist/src/core/index.d.cts +33 -9
  65. package/dist/src/core/index.d.ts +33 -9
  66. package/dist/src/core/index.js +37 -17
  67. package/dist/src/core/index.js.map +1 -1
  68. package/dist/src/server/index.cjs +1750 -1567
  69. package/dist/src/server/index.cjs.map +1 -1
  70. package/dist/src/server/index.d.cts +106 -24
  71. package/dist/src/server/index.d.ts +106 -24
  72. package/dist/src/server/index.js +1751 -1570
  73. package/dist/src/server/index.js.map +1 -1
  74. package/package.json +3 -3
@@ -0,0 +1,1038 @@
1
+ # Toolbox
2
+
3
+ > Concrete, LLM-callable tools for the `@orkestrel` line — workflow authoring, workspace editing, sub-agent delegation, terminal-mediated prompting, database and relation access, schema inference, and endpoint wrapping — over the `@orkestrel/tool` runtime, with pluggable stores.
4
+
5
+ The runtime supplies `ToolInterface`, registry execution, and result isolation; see [`tool.md`](tool.md). This package supplies the concrete behavior through one factory per tool.
6
+
7
+ `createWorkflowTool` and `createWorkspaceTool` own their full handler logic (the workflow authoring surface and the workspace editing surface respectively). The workflow tool composes opaque host `functions` plus raw live `agents` through `createWorkflowFunctions`, then forwards that frozen target registry and the optional `store` to `@orkestrel/workflow`, whose runner owns named-run drivability, checkpoint persistence, and `durable` / `fault`; the workspace tool retains its distinct manager/store composition. `createAgentTool` (sub-agent delegation over an `AgentRegistryInterface`) has its own `ConversationStoreInterface` persistence slot. The workflow, workspace, and agent tools additionally advertise a lean `summary` (`@orkestrel/tool`'s `ToolInterface.summary` / `ToolManagerInterface.definitions()` projection) in place of their full teaching `description`; `createDescribeTool` is the on-demand expansion seam. `createToolFunction` adapts an ordinary registered runtime tool, while `createAgentFunction` returns a frozen metadata-bearing adapter and uses Agent-owned `agentResultToJSON` for the exact result projection. The authoring umbrella (`WorkflowSteps` / `WorkflowDraft` shapes, `createWorkflowDraftContract`, lineage helpers, `expandSteps` / `completeDraft`, `summarizeWorkflow`, `MAX_WORKFLOW_CHAIN`) lets a small model author a whole recursion-safe tree in one call.
8
+
9
+ `createPromptTool` / `createAnswerTool` are the ask and answer halves of a terminal-mediated human-in-the-loop seam over a live `TerminalManagerInterface` (`@orkestrel/terminal`). One call asks a whole multi-field form, not a single question. `createPromptTool` takes `{ to, schema }` per call, parses the model-supplied `schema` with `@orkestrel/form`'s `parseForm`, constructs the live form with `createForm`, and blocks the calling agent turn until the addressed terminal answers (`from` fixed at construction) — resolving with one `FormValues` record keyed by field name, re-surfacing a prompt cycle as `DEADLOCK` and an unanswered expiry as `EXPIRE`. `createAnswerTool` lists the forms addressed to a fixed `to` terminal as `{ id, from, schema }` records and answers one by `{ id, values }`, narrowing the model-supplied `values` with `@orkestrel/form`'s `isFormValues` before applying it, re-surfacing a failed apply as `ANSWER`. `createTerminalRoutes` ([`src/server`](../src/server), the `@src/server` barrel) is the wire bridge for the same manager — structural `{ method, path, handler }` route records, a GET SSE stream and a POST answer over one shared `:name`-templated path, carrying no dependency on `@orkestrel/router`'s own `Route` type so a consumer mounts them against any router accepting that two-arg handler shape, and byte-compatible with `@orkestrel/terminal`'s own `PromptClient` (same GET url streams, same POST url answers, same `{ id, values }` body, same JSON answer `Result` body, same `x-orkestrel-token` header).
10
+
11
+ `createInferTool` / `createEndpointTool` bridge an existing API/DB surface into an LLM-callable `ToolInterface` over `@orkestrel/contract`'s sample-based schema inference — `createInferTool` a standalone utility a model calls to learn a JSON Schema from example values, `createEndpointTool` wrapping one concrete endpoint whose inferred `parameters` steer the model and, by default, are enforced at `execute` time (`EndpointToolOptions.validate`, default `true`; `validate: false` restores raw passthrough — see Contract invariant 23).
12
+
13
+ Source: [`src/core`](../src/core) (the tool factories) and [`src/server`](../src/server) (the terminal-routes wire bridge). Surfaced through the `@src/core` and `@src/server` barrels respectively.
14
+
15
+ ## Surface
16
+
17
+ ### Factories
18
+
19
+ | API | Kind | Summary |
20
+ | ------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
21
+ | `createToolFunction` | function | Wraps a registered tool as a `WorkflowFunction` (`@orkestrel/workflow`) — the opt-in adapter that lets a `function`-form task run a `@orkestrel/tool` tool by name. |
22
+ | `createAgentFunction` | function | Wraps a live `AgentInterface` (`@orkestrel/agent`) as a `WorkflowFunction` (`@orkestrel/workflow`) — the opt-in adapter that runs the agent to a settled result and carries immutable lineage metadata for contextual Toolbox composition. |
23
+ | `createWorkflowFunctions` | function | Composes opaque host functions and raw agents into one immutable workflow registry. |
24
+ | `createWorkflowDraftContract` | function | Compiles the lenient workflow draft contract `createWorkflowTool` parses an authored tree with — a `ContractInterface` over a `WorkflowDraft`, whose `id` and `name` are optional at the workflow, phase, and task levels. |
25
+ | `createWorkflowTool` | function | Wraps a `WorkflowDefinition` as an LLM-callable tool — it advertises the flat authoring shape (`{ name?, steps: [{ name }] }`) as its `parameters`, and its handler completes the authored blob, validates it against the strict contract, and runs it through `runner`, forwarding the caller's optional named functions and native checkpoint store. |
26
+ | `createWorkspaceTool` | function | Builds an LLM-callable workspace-editing tool — it advertises the `operation`-discriminated union (`workspaceToolShape`) as its `parameters`, and its handler parses the model-supplied args against that contract and dispatches the matched operation against the manager's active workspace, returning the plain result. |
27
+ | `createAgentTool` | function | Builds an LLM-callable sub-agent delegation tool — it resolves a live, seeded `AgentInterface` from `registry` and runs it to completion for one delegated `task`. |
28
+ | `createDescribeTool` | function | Builds an LLM-callable tool that returns the full `description` of another registered tool by name — the counterpart to the lean `summary` the other tools in this package advertise. |
29
+ | `createPromptTool` | function | Builds an LLM-callable form tool — the ask side of the terminal seam. It asks a multi-field form and blocks until the addressed terminal answers, returning the resolved values record. |
30
+ | `createAnswerTool` | function | Builds an LLM-callable answer tool — the answer side of the terminal seam. It lists the forms addressed to `AnswerToolOptions.to`, or answers one of them by id. |
31
+ | `createDatabaseTool` | function | Builds an LLM-callable database tool — it creates, queries, and mutates `@orkestrel/database` databases through one `operation`-discriminated call (matching `createWorkspaceTool`'s single-tool-many-operations shape). |
32
+ | `createRelationTool` | function | Builds an LLM-callable relation tool — it traverses and edits `@orkestrel/relation` relationships through one `operation`-discriminated call (matching `createDatabaseTool`'s single-tool-many-operations shape). |
33
+ | `createMemoryDefinitionStore` | function | Creates the in-memory `DefinitionStoreInterface` — a process-lifetime `Map` of database definitions, the default store the database and relation tools persist their `DatabaseDefinition` configs through. |
34
+ | `createDatabaseDefinitionStore` | function | Creates a `DefinitionStoreInterface` backed by one table of the `@orkestrel/database` layer — the driver-pluggable twin of `createMemoryDefinitionStore`, storing each database's definition as one opaque JSON column. |
35
+ | `createInferTool` | function | Builds a standalone LLM-callable tool that infers a JSON Schema from example values — the utility half of the bridge from an existing API or database into an MCP tool (the other half, `createEndpointTool`, wraps one concrete endpoint). |
36
+ | `createEndpointTool` | function | Wraps one concrete endpoint (`EndpointDefinition`) as an LLM-callable `ToolInterface` — the endpoint half of the bridge from an existing API or database into an MCP tool (the other half, `createInferTool`, is a standalone inference utility). |
37
+
38
+ ### Stores
39
+
40
+ Concrete `DefinitionStoreInterface` implementations (AGENTS' Stores rule, point-access mold): `MemoryDefinitionStore` the in-memory default, `DatabaseDefinitionStore` the driver-pluggable twin over one `@orkestrel/database` table. They implement the same `get` / `set` / `delete` contract over different backing storage, and differ within it: the memory store copies on write and on read, while the table-backed store narrows an untrusted stored blob and reports `undefined` for a malformed one.
41
+
42
+ | API | Kind | Summary |
43
+ | ------------------------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
44
+ | `MemoryDefinitionStore` | class | Represents the in-memory `DefinitionStoreInterface` — a process-lifetime `Map` of `DatabaseDefinition`s keyed by database id, the default store `createMemoryDefinitionStore` builds. It implements the same `DefinitionStoreInterface` contract as `DatabaseDefinitionStore`: this store copies on write and on read; the table-backed store narrows an untrusted stored blob and reports `undefined` for a malformed one. |
45
+ | `DatabaseDefinitionStore` | class | Represents a `DefinitionStoreInterface` backed by one table of the `@orkestrel/database` layer — a database's durable config state is a row, so persistence reduces to keyed point-access (`get` / `set` / `delete`) over a `TableInterface`, the driver-pluggable twin of the plain-`Map` `MemoryDefinitionStore`. |
46
+
47
+ ### Resolvers
48
+
49
+ `DatabaseResolver` is the implementation class a consumer composes directly. The factories
50
+ `createDatabaseTool` and `createTerminalRoutes` remain the compact entry points.
51
+
52
+ | API | Kind | Summary |
53
+ | ------------------ | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
54
+ | `DatabaseResolver` | class | Resolves a database definition into a cached live handle for a database tool, over the tool's live handles, its stored definitions, its driver registry, and an optional key generator. |
55
+
56
+ ### Errors
57
+
58
+ | API | Kind | Summary |
59
+ | ---------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
60
+ | `ToolboxError` | class | Represents a package-owned tool-call failure: malformed input or unresolved configuration (`TOOL`), delegation depth/cycle rejection (`DEPTH`), prompt failure (`DEADLOCK` / `EXPIRE` / `ANSWER`), or a translated upstream database/relation failure (`DATABASE` / `RELATION`). |
61
+ | `isToolboxError` | function | Narrows an unknown caught value to a `ToolboxError`. |
62
+
63
+ ### Validators
64
+
65
+ The total `(value: unknown) => value is T` guards this package applies at its untrusted boundaries — an authored lineage, a frozen agent adapter, the small-model column DSL, and a persisted database definition read back from a store.
66
+
67
+ In a guard table a `Shape` cell holds the type the guard narrows to.
68
+
69
+ | API | Kind | Shape | Summary |
70
+ | ---------------------- | -------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
71
+ | `isWorkflowLineage` | function | `WorkflowLineage` | Narrows an unknown value to a valid alternating workflow lineage. |
72
+ | `isAgentFunction` | function | `AgentFunction` | Narrows an unknown callable to Toolbox's frozen contextual agent adapter metadata. |
73
+ | `isColumnPrimitive` | function | `ColumnPrimitive` | Narrows an unknown value to a `ColumnPrimitive`. |
74
+ | `isColumnSpec` | function | `ColumnSpec` | Narrows an unknown value to a `ColumnSpec`. |
75
+ | `isDatabaseDefinition` | function | `DatabaseDefinition` | Narrows an unknown value to a `DatabaseDefinition` — a non-empty `id` and `driver`, a `tables` record whose every value is `{ columns: record of valid ColumnSpec }`, plus optional `primary`, `indexes`, and finite `version` schema configuration. The boundary guard a `DefinitionStoreInterface` applies to an untrusted persisted blob before trusting it as a definition (never an `as`). |
76
+
77
+ ### Helpers
78
+
79
+ Pure, side-effect-free, exhaustively unit-tested under AGENTS' export-and-test-reusable-logic law and narrow-untrusted-input-with-guards rule — the lenient-authoring synthesis path and the ancestry tags `createAgentTool` and `createAgentFunction` share.
80
+
81
+ | API | Kind | Summary |
82
+ | ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
83
+ | `tagWorkflow` | function | Returns the ancestry identifier of a workflow in a run chain — `workflow:<id>`. |
84
+ | `tagAgent` | function | Returns the ancestry identifier of an agent in a run chain — `agent:<name>`. |
85
+ | `summarizeWorkflow` | function | Builds the plain success summary `createWorkflowTool` returns on a completed run — the universal tool-handler contract: return a plain value on success, appearing identically over both the agent loop and MCP. |
86
+ | `extendLineage` | function | Appends one tag to a workflow lineage and returns a frozen copy. |
87
+ | `normalizeLineage` | function | Returns the canonical workflow lineage — validated, copied, and frozen. |
88
+ | `deriveWorkflowDepth` | function | Derives the zero-based workflow nesting depth from a valid lineage. |
89
+ | `completeDraft` | function | Completes a `WorkflowDraft` into a strict `WorkflowDefinition` — synthesizes a missing `id` deterministically and positionally, and defaults a missing `name` to its resolved `id`. |
90
+ | `completePhaseDraft` | function | Completes one `PhaseDraft` into a strict phase definition — the per-phase step of `completeDraft` (phase `index` → `phase-<index>` when its id is omitted). |
91
+ | `completeTaskDraft` | function | Completes one `TaskDraft` into a strict task definition — the per-task leaf step of `completeDraft` (task `index` of phase `<phaseId>` → `<phaseId>-task-<index>` when its id is omitted). |
92
+ | `expandSteps` | function | Expands a flat `WorkflowSteps` blob into a strict `WorkflowDefinition` — each step becomes a one-task phase, in order. |
93
+ | `inferTerminalCode` | function | Maps a caught error to the `ToolboxErrorCode` the terminal-tool factory throws with — the pure classification step of that factory's error handling. |
94
+ | `inferDatabaseCode` | function | Maps a caught error to the granular `DatabaseErrorCode` (`@orkestrel/database`) the code `createDatabaseTool` throws with — the pure classification step of that factory's error handling, mirroring `inferTerminalCode`'s idiom for `@orkestrel/database`. |
95
+ | `inferRelationCode` | function | Maps a caught error to the granular `RelationErrorCode` (`@orkestrel/relation`) the code `createRelationTool` throws with — the pure classification step of that factory's error handling, mirroring `inferTerminalCode`'s idiom for `@orkestrel/relation`. |
96
+ | `expandInclude` | function | Expands the relation tool's flat dot-path `include` list into a live `@orkestrel/relation` `Include` tree — the pure leaf `createRelationTool` calls before a `'load'` / `'find'` call. |
97
+ | `resolveRelationManager` | function | Resolves which registered `RelationManagerInterface` a relation-tool call addresses — the pure manager-resolution leaf `createRelationTool` calls on every operation. |
98
+ | `resolveRelationModel` | function | Resolves a `model` name against a live `RelationManagerInterface` — the pure model-lookup leaf `createRelationTool` calls on every operation, mirroring `resolveRelationManager`'s guard shape. |
99
+ | `clampQuery` | function | Clamps a `'records'` call's query to a row cap, and builds the probe query the caller reads with — the pure leaf `createDatabaseTool`'s `'records'` operation uses to detect truncation without a separate `count` round trip. |
100
+ | `resolveLimit` | function | Picks the effective row limit a tool reads with — the requested count when it sits inside the cap, the cap when it exceeds it, and `0` when either falls below zero. |
101
+ | `normalizeQuery` | function | Returns the canonical live `@orkestrel/database` `QueryInput` for the database tool's parsed serialized query — each condition's omitted `connector` defaults to `'and'`. |
102
+
103
+ ### Compilers
104
+
105
+ The `TableSpec` column DSL compiled into the live `@orkestrel/database` `TableMap` a `createDatabase` call accepts — one composite walk over the spec, and the `compileColumn` / `compileColumnPrimitive` leaves it maps with.
106
+
107
+ | API | Kind | Summary |
108
+ | ------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
109
+ | `expandTables` | function | Compiles a `TableSpec` into the `@orkestrel/database` `TableMap` it configures — each `ColumnSpec` maps to the matching primitive shaper (`'string'` → `stringShape()`, `'integer'` → `integerShape()`, `'number'` → `numberShape()`, `'boolean'` → `booleanShape()`), wrapped in `optionalShape` when the column declares `optional: true`. Total, pure. |
110
+ | `compileColumn` | function | Compiles one `ColumnSpec` into its `@orkestrel/database` column shape — the per-column leaf `expandTables` maps over. |
111
+ | `compileColumnPrimitive` | function | Compiles one `ColumnPrimitive` into its primitive `@orkestrel/database` shape — the leaf `compileColumn` wraps. |
112
+
113
+ ### Shapes
114
+
115
+ The shape values each `create*Tool` factory (and `createWorkflowDraftContract`) compiles into the lockstep guard / parser / JSON Schema outputs under AGENTS' narrow-untrusted-input-with-guards rule; `agentToolShape` agrees with the hand-written `AgentToolArguments`, the source of truth.
116
+
117
+ A `Shape` cell holds the constant's declared type.
118
+
119
+ | API | Kind | Shape | Summary |
120
+ | ---------------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
121
+ | `agentToolShape` | const | `ObjectShape<{ task, provider?, tools?, system? }>` | Describes the shape of `AgentToolArguments` — `createAgentTool`'s advertised `parameters`. |
122
+ | `taskDraftShape` | const | `ObjectShape<{ id?, name?, description?, behavior?, retries?, timeout? }>` | Describes the shape of a `TaskDraft` — identical to a strict task shape except that `id` and `name` are optional. |
123
+ | `phaseDraftShape` | const | `ObjectShape<{ id?, name?, description?, tasks, concurrency?, bail? }>` | Describes the shape of a phase in a draft workflow — identical to a strict phase shape except that `id` and `name` are optional and each task takes `taskDraftShape`. |
124
+ | `workflowDraftShape` | const | `ObjectShape<{ id?, name?, description?, phases, bail? }>` | Describes the shape of a draft workflow — identical to a strict workflow shape except that `id` and `name` are optional at the workflow, phase, and task levels, so a small model can omit every identity string and let the tool synthesize them positionally. |
125
+ | `stepShape` | const | `ObjectShape<{ name }>` | Describes the shape of one flat step — `{ name }` — the building block of `workflowStepsShape`. |
126
+ | `workflowStepsShape` | const | `ObjectShape<{ name?, steps }>` | Describes the flat authoring shape `createWorkflowTool` advertises as its `parameters` — the simplest surface a small model can fill: `{ name?, steps: [{ name }] }`. |
127
+ | `workspaceToolShape` | const | `UnionShape<[{ operation: 'read', path }, { operation: 'list' }, { operation: 'has', path }, { operation: 'search', query, regex?, sensitive?, limit? }, { operation: 'replace', query, replacement, regex?, sensitive?, limit? }, { operation: 'write', path, content }, { operation: 'splice', path, content, fromLine, fromColumn, toLine, toColumn }, { operation: 'prepend', path, content }, { operation: 'append', path, content }, { operation: 'move', from, to }, { operation: 'remove', path }, { operation: 'workspaces' }, { operation: 'switch', id }]>` | Describes the shape of a `WorkspaceOperation` — a descriptive tagged union over the workspace edit, read, and navigation operations, discriminated by the `operation` literal (never a bare `kind`). Each variant leads with its `operation` discriminant then its flat fields, every field through `stringShape` / `optionalShape` / `integerShape({ min: 1 })` / `booleanShape`, each carrying a strong field-level `description`. |
128
+ | `describeToolShape` | const | `ObjectShape<{ name }>` | Describes the shape of `DescribeToolArguments` — `createDescribeTool`'s advertised `parameters`. |
129
+ | `promptToolShape` | const | `ObjectShape<{ to, schema }>` | Describes the shape of `createPromptTool`'s call arguments — `to` names the terminal identity and `schema` carries the complete multi-field form document. |
130
+ | `answerToolShape` | const | `UnionShape<[{ operation: 'pending' }, { operation: 'answer', id, values }]>` | Describes the shape of `createAnswerTool`'s call arguments — discriminated by `operation`: `'pending'` lists the forms addressed to this tool's terminal, while `'answer'` resolves one by `id` with a complete `values` record. |
131
+ | `databaseToolShape` | const | `UnionShape<[{ operation: 'create', id, tables, driver?, primary?, indexes?, version? }, { operation: 'tables', id }, { operation: 'get', id, table, key }, { operation: 'records', id, table, query? }, { operation: 'count', id, table, query? }, { operation: 'aggregate', id, table, function, column, query? }, { operation: 'add', id, table, row }, { operation: 'set', id, table, row }, { operation: 'update', id, table, key, changes }, { operation: 'remove', id, table, key }, { operation: 'destroy', id }]>` | Describes the shape of `createDatabaseTool`'s call arguments — discriminated by `operation` into the database operations `'create'`, `'tables'`, `'get'`, `'records'`, `'count'`, `'aggregate'`, `'add'`, `'set'`, `'update'`, `'remove'`, and `'destroy'`. |
132
+ | `columnPrimitiveShape` | const | `LiteralShape<'string' \| 'integer' \| 'number' \| 'boolean'>` | Describes a `ColumnPrimitive` literal — the leaf `columnSpecShape` wraps. |
133
+ | `columnSpecShape` | const | `UnionShape<[columnPrimitiveShape, { primitive, optional? }]>` | Describes a `ColumnSpec` — a bare `columnPrimitiveShape`, or `{ primitive, optional }`. |
134
+ | `tableSpecShape` | const | `ObjectShape<Record<never, never>, ObjectShape<{ columns }>>` | Describes a `TableSpec` — table name to `{ columns }`, each column a `columnSpecShape`. |
135
+ | `keyShape` | const | `UnionShape<[ArrayShape<UnionShape<[StringShape, NumberShape]>>, StringShape, NumberShape]>` | Describes one key value for the database tool and the relation tool — a string or number; the array form (multiple keys, positional) resolves first, so an array argument is read as many keys rather than one. |
136
+ | `rowShape` | const | `ObjectShape<Record<never, never>, JSONShape>` | Describes a loose row — a flat object of column name to JSON value. |
137
+ | `rowsShape` | const | `UnionShape<[ArrayShape<rowShape>, rowShape]>` | Describes one or many loose rows — the array form resolves first, so an array argument is read as many rows rather than one. |
138
+ | `conditionShape` | const | `ObjectShape<{ column, operator, values, connector? }>` | Describes one serialized where condition — `values` is always an array, even for a single-value operator. |
139
+ | `orderShape` | const | `ObjectShape<{ column, direction }>` | Describes one sort term — a `column` to sort by and the `direction` to sort it in. |
140
+ | `queryShape` | const | `ObjectShape<{ conditions?, order?, limit?, offset? }>` | Describes the serialized query form — conditions, order, and pagination. |
141
+ | `relationToolShape` | const | `UnionShape<[{ operation: 'load', manager?, model, key, include? }, { operation: 'find', manager?, model, include?, limit?, offset?, sort?, direction? }, { operation: 'link', manager?, model, key, relation, target }, { operation: 'unlink', manager?, model, key, relation, target }, { operation: 'links', manager?, model, key, relation }]>` | Describes the shape of `createRelationTool`'s call arguments — discriminated by `operation` into the relation operations `'load'`, `'find'`, `'link'`, `'unlink'`, and `'links'`. |
142
+ | `singleKeyShape` | const | `UnionShape<[StringShape, NumberShape]>` | Describes a single row key (not an array) — used by `'link'` / `'unlink'` / `'links'`, which address exactly one owning row. |
143
+ | `includeShape` | const | `OptionalShape<ArrayShape<StringShape>>` | Describes the flat dot-path relation include list, expanded through `expandInclude`. |
144
+ | `managerShape` | const | `OptionalShape<StringShape>` | Describes which registered relation manager to address — omitted resolves to the sole registered manager. |
145
+ | `inferToolShape` | const | `ObjectShape<{ samples, format?, enum?, candidates? }>` | Describes the shape of `createInferTool`'s call arguments — one or more example `samples` to infer a JSON Schema from, plus per-call `format` / `enum` toggles and an optional `candidates` array to check against the inferred schema. |
146
+
147
+ ### Constants
148
+
149
+ A `Shape` cell holds the constant's declared type.
150
+
151
+ | Constant | Kind | Shape | Summary |
152
+ | ------------------------------ | ----- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
153
+ | `AGENT_TOOL_NAME` | const | `string` | Holds the name `createAgentTool` advertises by default, `'agent'` — the key a model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers under. |
154
+ | `AGENT_TOOL_DEPTH` | const | `number` | Holds the maximum nesting depth a delegation chain (agent tool → sub-agent → agent tool → …) may reach, `8` — the bound `createAgentTool`'s depth/cycle guard enforces. |
155
+ | `AGENT_TOOL_DESCRIPTION` | const | `string` | Holds the description `createAgentTool` advertises — a short guide covering the required task and optional provider, tools, and system overrides. |
156
+ | `AGENT_TOOL_SUMMARY` | const | `string` | Holds the lean `ToolInterface.summary` `createAgentTool` advertises in place of `AGENT_TOOL_DESCRIPTION` — one sentence offering sub-agent delegation and pointing at `describe` for the optional overrides. |
157
+ | `MAX_WORKFLOW_CHAIN` | const | `number` | Holds the maximum nesting depth a workflow → agent → workflow chain may reach, `8` — the bound `createAgentFunction` and `createWorkflowTool`'s depth/cycle guards enforce. |
158
+ | `WORKFLOW_TOOL_NAME` | const | `string` | Holds the name `createWorkflowTool` advertises by default, `'workflow'` — the key a model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers under, and the name `createAgentFunction` binds the depth/cycle-aware workflow tool under onto a wrapped agent's `context.tools`. |
159
+ | `WORKFLOW_TOOL_FLAT_EXAMPLE` | const | `WorkflowSteps` | Holds a complete flat authoring example — the primary way a small model authors a workflow through `createWorkflowTool`: `{ name, steps: [{ name }] }`. |
160
+ | `WORKFLOW_TOOL_NESTED_EXAMPLE` | const | `WorkflowDefinition` | Holds a minimal nested authoring example — the advanced escape-hatch form a model can use instead of the flat shape: a full `WorkflowDefinition` (`@orkestrel/workflow`). |
161
+ | `WORKFLOW_TOOL_DESCRIPTION` | const | `string` | Holds the description `createWorkflowTool` advertises — the flat authoring form, its worked example, and the advanced nested definition form. |
162
+ | `WORKFLOW_TOOL_SUMMARY` | const | `string` | Holds the lean `ToolInterface.summary` `createWorkflowTool` advertises in place of `WORKFLOW_TOOL_DESCRIPTION` — one sentence offering multi-phase authoring and pointing at `describe` for the authoring schema and its examples. |
163
+ | `WORKSPACE_TOOL_NAME` | const | `string` | Holds the name `createWorkspaceTool` advertises by default, `'workspace'` — the key a model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers under. |
164
+ | `WORKSPACE_TOOL_EXAMPLE` | const | `WorkspaceOperation` | Holds a valid `WorkspaceOperation` object — the canonical example embedded verbatim in `WORKSPACE_TOOL_DESCRIPTION`. |
165
+ | `WORKSPACE_TOOL_DESCRIPTION` | const | `string` | Holds the description `createWorkspaceTool` advertises — the operation-keyed workspace protocol, all supported operations, and worked examples. |
166
+ | `WORKSPACE_TOOL_SUMMARY` | const | `string` | Holds the lean `ToolInterface.summary` `createWorkspaceTool` advertises in place of `WORKSPACE_TOOL_DESCRIPTION` — one sentence offering the `operation`-keyed file editing and pointing at `describe` for the operation list and its fields. |
167
+ | `DESCRIBE_TOOL_NAME` | const | `string` | Holds the name `createDescribeTool` advertises by default, `'describe'` — the key a model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers under. |
168
+ | `DESCRIBE_TOOL_SUMMARY` | const | `string` | Holds the lean `ToolInterface.summary` `createDescribeTool` advertises — this tool needs no teaching of its own, so its summary and description are both short. |
169
+ | `DESCRIBE_TOOL_DESCRIPTION` | const | `string` | Holds the description `createDescribeTool` advertises — the registered tool `name` it requires, and the full description of that tool it returns. |
170
+ | `PROMPT_TOOL_NAME` | const | `string` | Holds the name `createPromptTool` advertises by default, `'ask'` — the key a model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers under. |
171
+ | `PROMPT_TOOL_SUMMARY` | const | `string` | Holds the lean `ToolInterface.summary` `createPromptTool` advertises in place of `PROMPT_TOOL_DESCRIPTION` — one sentence offering the blocking multi-field ask and pointing at `describe` for the schema. |
172
+ | `PROMPT_TOOL_DESCRIPTION` | const | `string` | Holds the form protocol `createPromptTool` advertises — the required `to` and `schema`, how a field declares its control and its rules, and a worked example. |
173
+ | `ANSWER_TOOL_NAME` | const | `string` | Holds the name `createAnswerTool` advertises by default, `'answer'` — the key a model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers under. |
174
+ | `ANSWER_TOOL_SUMMARY` | const | `string` | Holds the lean `ToolInterface.summary` `createAnswerTool` advertises in place of `ANSWER_TOOL_DESCRIPTION` — one sentence offering the pending listing and the answer call, and pointing at `describe` for the fields each takes. |
175
+ | `ANSWER_TOOL_DESCRIPTION` | const | `string` | Holds the protocol `createAnswerTool` advertises — the `pending` and `answer` operations and the `values` record an answer supplies, each with a worked example. |
176
+ | `DATABASE_TOOL_NAME` | const | `string` | Holds the name `createDatabaseTool` advertises by default, `'database'` — the key a model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers under. |
177
+ | `DATABASE_TOOL_SUMMARY` | const | `string` | Holds the lean `ToolInterface.summary` `createDatabaseTool` advertises in place of `DATABASE_TOOL_DESCRIPTION` — one sentence offering the `operation`-keyed database call and pointing at `describe` for the operation list, the query form, and the column DSL. |
178
+ | `DATABASE_TOOL_DESCRIPTION` | const | `string` | Holds the description `createDatabaseTool` advertises — a multi-line guide that teaches a small model the operation list, the serialized query form, and the `TableSpec` column DSL. |
179
+ | `DATABASE_TOOL_LIMIT` | const | `number` | Holds the default cap on rows a `records` call returns when the caller omits `query.limit`, `1000` — the database tool's default row ceiling. |
180
+ | `DATABASE_TOOL_MUTATIONS` | const | `readonly string[]` | Lists the runtime-frozen database-tool mutation names disabled by `DatabaseToolOptions.readonly`. |
181
+ | `RELATION_TOOL_NAME` | const | `string` | Holds the name `createRelationTool` advertises by default, `'relation'` — the key a model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers under. |
182
+ | `RELATION_TOOL_SUMMARY` | const | `string` | Holds the lean `ToolInterface.summary` `createRelationTool` advertises in place of `RELATION_TOOL_DESCRIPTION` — one sentence offering the `operation`-keyed relationship traversal and pointing at `describe` for the include-path syntax. |
183
+ | `RELATION_TOOL_DESCRIPTION` | const | `string` | Holds the description `createRelationTool` advertises — a multi-line guide that teaches a small model the operation list and the flat dot-path `include` syntax. |
184
+ | `RELATION_TOOL_LIMIT` | const | `number` | Holds the default cap on rows a `find` or `links` call returns when the caller omits `limit`, `1000` — the relation tool's default row ceiling. |
185
+ | `RELATION_TOOL_DEPTH` | const | `number` | Holds the default cap on how many `include` path segments deep a `load` or `find` call may traverse, `3` — the relation tool's default include-depth ceiling. |
186
+ | `INFER_TOOL_NAME` | const | `string` | Holds the name `createInferTool` advertises by default, `'infer'` — the key a model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers under. |
187
+ | `INFER_TOOL_SUMMARY` | const | `string` | Holds the lean `ToolInterface.summary` `createInferTool` advertises in place of `INFER_TOOL_DESCRIPTION` — one sentence offering schema inference from example values and pointing at `describe` for the fields it takes. |
188
+ | `INFER_TOOL_DESCRIPTION` | const | `string` | Holds the schema-inference protocol `createInferTool` advertises — the required `samples`, the optional `format`, `enum`, and `candidates` arguments, and a worked example of the bare return and of the `candidates`-wrapped return. |
189
+
190
+ ### Types
191
+
192
+ A `Shape` cell holds an interface's data members as bare names in braces, `?` marking an optional member and `plus` introducing its call-signature members, and a type alias's own type literal with a union's arms escaped as `\|`.
193
+
194
+ | Type | Kind | Shape | Summary |
195
+ | -------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
196
+ | `TaskDraft` | interface | `{ id?, name?, description?, behavior?, retries?, timeout? }` | Represents a draft task — a `TaskDefinition` (`@orkestrel/workflow`) with optional `id` and `name`. |
197
+ | `PhaseDraft` | interface | `{ id?, name?, description?, tasks, concurrency?, bail? }` | Represents a draft phase — a `PhaseDefinition` (`@orkestrel/workflow`) with optional `id` and `name` and `TaskDraft` tasks. |
198
+ | `WorkflowDraft` | interface | `{ id?, name?, description?, phases, bail? }` | Represents a draft workflow — a `WorkflowDefinition` (`@orkestrel/workflow`) with optional `id` and `name` at the workflow, phase, and task levels. |
199
+ | `WorkflowStep` | interface | `{ name }` | Represents one flat step — `{ name }` — the building block of a `WorkflowSteps` blob. |
200
+ | `WorkflowSteps` | interface | `{ name?, steps }` | Represents the flat authoring blob `createWorkflowTool` advertises — `{ name?, steps }` — the simplest surface a small model can fill. |
201
+ | `WorkflowToolResult` | interface | `{ status, count, durable?, fault? }` | Represents the JSON-safe run summary returned by `createWorkflowTool`. |
202
+ | `WorkflowLineage` | type | `readonly string[]` | Represents one immutable workflow/agent call chain — strictly alternating tags, each unique, beginning with a workflow tag. |
203
+ | `WorkflowAgents` | type | `Readonly<Record<string, AgentInterface>>` | Represents raw live agents keyed by the workflow function names that invoke them. |
204
+ | `AgentFunction` | type | `WorkflowFunction & { category, lineage }` | Represents a contextual agent adapter carrying immutable metadata for Toolbox composition. |
205
+ | `AgentFunctionOptions` | interface | `{ runner?, lineage?, functions?, agents?, store? }` | Represents the options for `createAgentFunction` — the opt-in adapter that wraps a live `AgentInterface` (`@orkestrel/agent`) as an `AgentFunction` with immutable lineage metadata and optional nested-workflow composition. |
206
+ | `WorkflowToolOptions` | interface | `{ lineage?, functions?, agents?, store? }` | Represents the options for `createWorkflowTool` and `createWorkflowFunctions` — lineage-aware composition of opaque leaves, raw agents, and native workflow persistence. |
207
+ | `WorkspaceToolOptions` | interface | `{ name?, description?, manager?, store? }` | Represents the options for `createWorkspaceTool` — either a caller-built `WorkspaceManagerInterface` to drive directly, or a `WorkspaceStoreInterface` the tool constructs a fresh manager over; neither given constructs a manager over `@orkestrel/workspace`'s in-memory store. |
208
+ | `WorkspaceOperation` | type | `{ operation: 'read', path } \| { operation: 'list' } \| { operation: 'has', path } \| { operation: 'search', query, regex?, sensitive?, limit? } \| { operation: 'replace', query, replacement, regex?, sensitive?, limit? } \| { operation: 'write', path, content } \| { operation: 'splice', path, content, fromLine, fromColumn, toLine, toColumn } \| { operation: 'prepend', path, content } \| { operation: 'append', path, content } \| { operation: 'move', from, to } \| { operation: 'remove', path } \| { operation: 'workspaces' } \| { operation: 'switch', id }` | Represents one operation an agent invokes through `createWorkspaceTool` — a flat, descriptive tagged union over the workspace edit, read, and navigation actions, discriminated by the `operation` literal (a discriminant is named for its axis — the action being performed — never `kind`). |
209
+ | `AgentToolOptions` | interface | `{ name?, description?, provider?, tools?, system?, depth?, ancestry?, store? }` | Represents the options for `createAgentTool` — the sub-agent delegation defaults, the nesting-depth / cycle guard bookkeeping, and the advertised tool overrides. |
210
+ | `AgentToolArguments` | interface | `{ task, provider?, tools?, system? }` | Represents the flat args `createAgentTool` accepts — a delegated `task` plus the minimal optional `AgentJobInput` (`@orkestrel/agent`) fields a caller may override per-call. |
211
+ | `ToolboxErrorCode` | type | `'TOOL' \| 'DEPTH' \| 'DEADLOCK' \| 'EXPIRE' \| 'ANSWER' \| 'DATABASE' \| 'RELATION'` | Represents the machine-readable code a thrown `ToolboxError` carries — a thrown, typed, code-bearing error, never a `{ error }` return. |
212
+ | `DescribeToolArguments` | interface | `{ name }` | Represents the flat args `createDescribeTool` accepts — the registered tool `name` whose full `description` a model wants back. |
213
+ | `PromptToolOptions` | interface | `{ manager, from, name?, description? }` | Represents the options for `createPromptTool` — the live `TerminalManagerInterface` (`@orkestrel/terminal`) to `ask` through, the terminal name `from`, and the advertised tool overrides. |
214
+ | `AnswerToolOptions` | interface | `{ manager, to, name?, description? }` | Represents the options for `createAnswerTool` — the live `TerminalManagerInterface` (`@orkestrel/terminal`) to list / answer prompts through, the terminal name `to`, and the advertised tool overrides. |
215
+ | `ColumnPrimitive` | type | `'string' \| 'integer' \| 'number' \| 'boolean'` | Represents one column's declared primitive — a shorthand, or `integer` for a whole-number `number`. |
216
+ | `ColumnSpec` | type | `ColumnPrimitive \| { primitive, optional? }` | Represents one table column's spec — either a bare `ColumnPrimitive` shorthand, or `{ primitive, optional }` when the column may be absent from a row. |
217
+ | `TableSpec` | type | `Readonly<Record<string, { columns }>>` | Represents a database's table layout — one entry per table, each a flat map of column name to `ColumnSpec`. The small-model-facing DSL `expandTables` compiles into an `@orkestrel/database` `TableMap`. |
218
+ | `DatabaseDefinition` | interface | `{ id, driver, tables, primary?, indexes?, version? }` | Represents one database's config-only definition — an `id`, a `driver`, and a `TableSpec`, with optional `primary`, `indexes`, and `version` schema configuration. |
219
+ | `DatabaseDefinitionRow` | interface | `{ id, definition }` | Represents one opaque persisted row — the shape a definition-row-backed `TableInterface` store reads/writes; `definition` is narrowed with `isDatabaseDefinition` on read. |
220
+ | `DefinitionStoreInterface` | interface | `{} plus get, set, delete` | Represents the point-access persistence seam for `DatabaseDefinition` configs — the twin of `@orkestrel/terminal`'s `TerminalStoreInterface`, storing a database's config-only blueprint rather than a live handle. Every primitive is async; `delete` of an absent id is a no-op. |
221
+ | `DatabaseQueryInput` | interface | `{ conditions?, order?, limit?, offset? }` | Represents the serialized wire query a database-tool call carries — the parsed form of `queryShape`, which `normalizeQuery` normalizes into a live `@orkestrel/database` `QueryInput`. |
222
+ | `ClampedQuery` | interface | `{ query, limit }` | Represents the probe query and effective row limit `clampQuery` returns. |
223
+ | `DatabaseToolOptions` | interface | `{ name?, description?, databases?, store?, drivers?, generator?, limit?, timeout?, readonly? }` | Represents the options for `createDatabaseTool` — the live handles, definition store, driver registry, key generator, row cap, timeout, and readonly gate the tool composes. |
224
+ | `RelationToolOptions` | interface | `{ name?, description?, managers, limit?, depth? }` | Represents the options for `createRelationTool` — the required live `RelationManagerInterface` registry a call addresses, the row cap, and the `include` depth cap. |
225
+ | `InferToolOptions` | interface | `{ name?, description? }` | Represents the options for `createInferTool` — advertised name and description overrides only; `format` and `enum` are runtime call arguments (see `inferToolShape`), not construction-time options, because a model chooses them per call. |
226
+ | `EndpointHandler` | type | `(args: Readonly<Record<string, unknown>>) => Promise<unknown> \| unknown` | Represents the handler `EndpointDefinition.execute` implements — it mirrors `@orkestrel/tool`'s `ToolOptions.execute` signature exactly (the same `Readonly<Record<string, unknown>>` argument, the same `Promise<unknown> \| unknown` return), so `execute: (args) => definition.execute(args)` typechecks with zero assertions in `createEndpointTool`. |
227
+ | `EndpointDefinition` | interface | `{ name, description, samples, execute }` | Represents one concrete endpoint `createEndpointTool` wraps as an LLM-callable `ToolInterface` — the advertised identity, a non-empty set of example values its `parameters` are inferred from, and the local handler that runs a call. |
228
+ | `EndpointToolOptions` | interface | `{ format?, enum?, validate? }` | Represents the construction-time tuning for `createEndpointTool` — the inferred `parameters` schema's `format` and `enum` constraints, and whether that same schema is enforced at `execute` time. |
229
+
230
+ ### Server routes
231
+
232
+ The wire bridge for a `TerminalManagerInterface` — a GET SSE stream and a POST answer endpoint, both mounted on the same `:name`-templated path, returned as plain structural records carrying no dependency on `@orkestrel/router`'s own `Route` type ([`src/server`](../src/server), surfaced through `@src/server`).
233
+
234
+ The POST endpoint takes `{ id, values }`, admits the body only when `id` is a nonempty string and `values` passes `@orkestrel/form`'s `isFormValues`, and returns the manager's own answer `Result` as a JSON body — `200` accepted, `422` an `'unknown'` / `'rejected'` outcome, `404` a `'target'` outcome. The body is what carries a rejection's per-field `errors` back to the answering client, so `PromptClient` can re-render the failed fields instead of guessing from a status code.
235
+
236
+ A `Shape` cell holds an interface's data members as bare names in braces, `?` marking an optional member and `plus` introducing its call-signature members, and a type alias's own type literal with a union's arms escaped as `\|`. A function row's `Shape` cell holds its signature, and a guard row's the type it narrows to. A `Shape` cell holds the constant's declared type.
237
+
238
+ | API | Kind | Shape | Summary |
239
+ | ----------------------- | --------- | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
240
+ | `createTerminalRoutes` | function | `(manager: TerminalManagerInterface, options?: TerminalRoutesOptions) => readonly TerminalRoute[]` | Builds the GET SSE stream and POST answer routes that bridge a terminal manager onto the wire. |
241
+ | `TerminalRouteMethod` | type | `'GET' \| 'POST' \| 'PUT' \| 'PATCH' \| 'DELETE' \| 'HEAD' \| 'OPTIONS'` | Represents the HTTP method literal a `TerminalRoute` declares — the same union `@orkestrel/router`'s `Method` type accepts. |
242
+ | `TerminalRouteContext` | interface | `{ params }` | Represents the minimal route-dispatch context a `TerminalRoute` handler reads — exactly the frozen, URL-decoded `:name` path param slice a router hands a matched handler. |
243
+ | `TerminalRoute` | interface | `{ method, path, handler }` | Represents one structural route record `createTerminalRoutes` returns — a plain `{ method, path, handler }` shape carrying no dependency on `@orkestrel/router`'s own `Route` type, so a consumer mounts it against any router that accepts a two-arg `(request, context) => Response \| Promise<Response>` handler keyed by `method` and `path`. |
244
+ | `TerminalRoutesOptions` | interface | `{ path?, token?, keepalive?, timer?, limit? }` | Represents the options `createTerminalRoutes` takes — the shared route, authorization, keepalive, timer, and body-limit configuration both routes read. |
245
+ | `TerminalToken` | type | `string \| ((value: string \| undefined) => boolean)` | Represents the `token` gate `TerminalRoutesOptions` may configure — a plain string compared for equality against the `x-orkestrel-token` header, or a validator function the consumer fully controls, enabling the expiry and rotation a fixed string cannot express (a JWT `exp` check, a revocation-list lookup, anything time-varying). `undefined` disables the auth check entirely. |
246
+ | `TERMINAL_ROUTES_PATH` | const | `string` | Holds the default `:name`-templated path `createTerminalRoutes` mounts its GET (SSE) and POST (answer) routes under, `/terminals/:name`. |
247
+ | `TERMINAL_KEEPALIVE_MS` | const | `number` | Holds the default SSE keepalive interval `createTerminalRoutes` arms per open connection, `15_000` ms — a `:` comment ping a conforming SSE parser ignores, keeping intermediary proxies from timing out an otherwise-idle stream. |
248
+
249
+ > A reconnecting SSE client replays every pending form from the top on every (re)connect (the GET handler's replay loop), so a raw `EventSource` (or any hand-rolled consumer that isn't `PromptClient`) must dedupe `pending` frames by their SSE `id` — the same form id may arrive more than once across a reconnect. `PromptClient` (`@orkestrel/terminal`) already does this; a consumer bypassing it does not get the dedupe for free.
250
+ >
251
+ > Fan-out is `O(total connections)` per `pending` / `expire` event on one manager — every open GET stream for every endpoint on that manager runs its scoped listener on each emit (filtered by `to === name` inside the handler, not before). This is fine at ordinary connection counts; a workload with very many concurrently-open streams across many endpoints on one manager is the lever to reach for — per-endpoint sharding (one manager, or one emitter subscription, per endpoint) — if fan-out cost ever becomes material. Not implemented here; noted as the scaling lever, not a current limitation.
252
+
253
+ ## Methods
254
+
255
+ Every `create*Tool` factory returns a plain `ToolInterface` (`@orkestrel/tool`'s type — its method surface is documented in [`tool.md`](tool.md), not re-documented here). `createToolFunction` returns a plain `WorkflowFunction`; `createAgentFunction` returns the compatible metadata-bearing `AgentFunction`; `createWorkflowFunctions` returns the frozen composed registry. The `WorkspaceManagerInterface` / `WorkflowRunnerInterface` / `AgentRegistryInterface` a caller supplies are likewise defined and documented upstream. `DefinitionStoreInterface` is the point-access persistence seam `MemoryDefinitionStore` / `DatabaseDefinitionStore` implement; the lifecycle classes expose their minimal orchestration methods directly.
256
+
257
+ #### `DefinitionStoreInterface`
258
+
259
+ | Method | Returns | Summary |
260
+ | -------- | ------------------------------------------ | ------------------------------------------------------------------------------------ |
261
+ | `get` | `Promise<DatabaseDefinition \| undefined>` | Resolves the persisted definition for `id`, or `undefined` when none is stored. |
262
+ | `set` | `Promise<void>` | Inserts or replaces a definition under its own `id`, taking no separate id argument. |
263
+ | `delete` | `Promise<void>` | Drops the definition for `id`, treating an absent id as a no-op that never throws. |
264
+
265
+ #### `MemoryDefinitionStore`
266
+
267
+ | Method | Returns | Summary |
268
+ | -------- | ------------------------------------------ | ----------------------------------------------------------------------------------- |
269
+ | `get` | `Promise<DatabaseDefinition \| undefined>` | Resolves the persisted definition for `id`, copied out of the backing `Map`. |
270
+ | `set` | `Promise<void>` | Inserts or replaces a definition under its own `id`, copied into the backing `Map`. |
271
+ | `delete` | `Promise<void>` | Drops the definition stored under `id`. |
272
+
273
+ #### `DatabaseDefinitionStore`
274
+
275
+ | Method | Returns | Summary |
276
+ | -------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
277
+ | `get` | `Promise<DatabaseDefinition \| undefined>` | Resolves the persisted definition for `id`, narrowing the opaque JSON column back to a `DatabaseDefinition`. |
278
+ | `set` | `Promise<void>` | Inserts or replaces a definition under its own `id` — the written row is `{ id, definition }`. |
279
+ | `delete` | `Promise<void>` | Drops the definition stored under `id`. |
280
+
281
+ #### `DatabaseResolver`
282
+
283
+ | Method | Returns | Summary |
284
+ | --------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
285
+ | `has` | `boolean` | Determines whether a live database is cached by id. |
286
+ | `get` | `DatabaseInterface \| undefined` | Reads a cached database without consulting the definition store. |
287
+ | `set` | `void` | Caches a live database by id. |
288
+ | `delete` | `void` | Removes a cached live database by id. |
289
+ | `resolve` | `Promise<DatabaseInterface>` | Resolves a live database by id — the cached handle when one exists, otherwise a database constructed from its stored definition. |
290
+
291
+ ### Composing `DatabaseResolver` directly
292
+
293
+ Construct the resolver over a caller-owned handle map, a driver registry, and a definition store, then drive its cache calls and resolve a database by id:
294
+
295
+ ```ts
296
+ import type { DatabaseInterface } from '@orkestrel/database'
297
+ import type { DefinitionStoreInterface } from '@orkestrel/toolbox'
298
+ import { createMemoryDriver } from '@orkestrel/database'
299
+ import { DatabaseResolver } from '@orkestrel/toolbox'
300
+
301
+ declare const database: DatabaseInterface
302
+ declare const store: DefinitionStoreInterface
303
+
304
+ const resolver = new DatabaseResolver(new Map(), { memory: createMemoryDriver }, undefined, store)
305
+ resolver.has('shop')
306
+ resolver.set('shop', database)
307
+ resolver.get('shop')
308
+ resolver.delete('shop')
309
+ await resolver.resolve('shop')
310
+ ```
311
+
312
+ ## Contract
313
+
314
+ These invariants hold across `src/core` ↔ `toolbox.md`:
315
+
316
+ 1. **doc ↔ source bijection.** Every `function` / `class` / `const` / `interface` / `type` row in the `## Surface` tables is a real export of `src/core`, and every export appears as a Surface row — exhaustive, both directions under AGENTS' documentation contract.
317
+
318
+ 2. **One tool = one behavior; the runtime supplies the envelope, this package supplies the handler.** Each `create*Tool` factory returns a plain `ToolInterface` (`@orkestrel/tool`). Under AGENTS' narrow-untrusted-input-with-guards rule, its handler parses the model-supplied `args` against a compiled [contract](contract.md), dispatches, and either returns a plain value on success or throws a typed error on every failure path — it never builds a `ToolResult` itself. The runtime supplies registry execution and result isolation; see [`tool.md`](tool.md). Through that registry, a failure's flattened message text appears exactly once, identically, over both the agent loop and MCP.
319
+
320
+ 3. **Contract-compiled schemas throughout.** Every advertised `parameters` is `schemaToParameters(contract.schema)` off a `createContract`-compiled shape (`workflowStepsShape` / `workflowDraftShape` / `workspaceToolShape` / `agentToolShape`) — never a hand-written JSON Schema — so the guard / parser / schema the handler validates against can never drift from what the tool advertises.
321
+
322
+ 4. **Workflow/agent recursion is lineage-derived and contextually composed.** `WorkflowLineage` is a copied, frozen chain of nonempty unique tags, strictly alternating `workflow:` then `agent:`. Malformed configured lineage or a factory-incompatible final tag throws `ToolboxError('TOOL')`; runtime workflow mismatch, repeated workflow/agent id, or over-depth target throws `ToolboxError('DEPTH')` before agent, tool, or runner activity. Depth is zero-based and derived only from workflow-tag count minus one (empty/root is `0`): root plus eight nested workflows is allowed, the ninth nested workflow is rejected. `createWorkflowFunctions` is the sole composition engine: it snapshots opaque `functions`, rejects a marked `AgentFunction` supplied through that opaque channel, rejects function/agent key collisions, and contextually adapts raw `agents` for the exact target lineage. Its frozen registry has a null prototype, so the workflow runner's native bracket lookup cannot resolve unregistered inherited names such as `toString`, `constructor`, `valueOf`, `hasOwnProperty`, or `__proto__`; those remain genuine native `WorkflowError('TRANSITION')` failures. Opaque wrappers or spoofed metadata that hide their real agent behavior are not covered; hosts must not use them to share one live agent concurrently.
323
+
324
+ 5. **`createWorkflowTool` widens the authoring surface additively; the strict contract stays the soundness gate.** It advertises the simple flat shape (`{ name?, steps: [{ name }] }`) as its `parameters` so a small model can author a whole tree in one call, but its handler accepts empty args (the wrapped `definition`), a `steps` array (the flat form, `expandSteps`'d), or a nested draft/full definition (`createWorkflowDraftContract`-parsed and `completeDraft`'d, or accepted as-is when already strict) — and every path converges on the byte-for-byte-unchanged `createWorkflowContract().is` gate before it runs. A blob that fails to parse, expand, or complete, or whose result fails that strict gate, throws a `TOOL` `ToolboxError`; the leniency never reaches the runner. An omitted task `behavior` is the native JSON-`null` no-op. A present unresolved name reaches the runner and is rejected by the native drivability gate as a genuine `WorkflowError('TRANSITION')`; Toolbox does not duplicate that preflight.
325
+
326
+ Before shape branching or property reads, the handler snapshots untrusted `args` through Contract's `attempt` and `cloneJSONRecord`; hostile traversal or inexact JSON becomes `ToolboxError('TOOL', 'malformed workflow definition', ...)` before runner functions or persistence begin, never a raw Proxy/Contract error. A standalone `createWorkflowTool` called with empty args runs its wrapped definition. The tool bound onto an agent deliberately wraps the containing workflow id, so its empty-args call is a repeated-lineage cycle and throws `ToolboxError('DEPTH')`; a bound nested call must author a new target rather than receive an invented id.
327
+
328
+ 6. **`createWorkflowTool` delegates composition and persistence without inventing identity.** Configured lineage/functions/agents registries are copied at construction. At invocation the strict target tag is appended, repeated ids and over-depth are rejected, and `createWorkflowFunctions` builds the contextual registry only when functions or agents were supplied. The resulting registry and optional store are forwarded to `runner.execute(target, { functions?, store? })`. The runner owns initial/attempt/settlement/final checkpoints, coalescing, restore-ready final snapshots, and persistence failures as data. `WorkflowToolResult` projects `{ status, count, durable?, fault? }`: `durable`/`fault` appear exactly when the native result supplies them. A flat authoring `name` is the deterministic workflow id; repeated runs with the same name address and replace the same store snapshot rather than minting a hidden id.
329
+
330
+ 7. **`createWorkspaceTool` is manager-driven, with a no-active ergonomic seam.** `options.manager` (drive directly) takes priority over `options.store` (build a fresh manager over it through `@orkestrel/workspace`'s `createWorkspaceManager`); neither given constructs a manager over `@orkestrel/workspace`'s in-memory default. The `store` slot deliberately diverges from invariant 6: the workspace tool's `store` only backs the constructed manager's `open` / `save` — the tool's edits are not auto-persisted (durability requires an explicit caller `save`), whereas the workflow tool forwards its store to native run-wide checkpoint persistence. Every edit / read arm targets `manager.active` — never a specific workspace by id directly — so a host repoints which workspace the model edits through the registry arms (`workspaces` lists them, `switch` re-points `active`, lenient on an unknown id). A writing arm (write / splice / prepend / append / move / remove / replace) run with no active workspace auto-creates and activates one (`manager.add()`); a pure-read arm (read / list / has / search) against no active workspace returns the empty result, never creating one and never throwing. `search` / `replace` pass the workspace vocabulary through unchanged: `regex` chooses pattern-vs-literal matching, `sensitive` controls case sensitivity, and `limit` caps occurrences; `replace` returns the dependency's own `ReplaceResult` directly as `{ occurrences, files }`.
331
+
332
+ 8. **`createAgentTool` carries its own `store` slot, composable with a registry-level store.** `AgentToolOptions.store` is an optional `ConversationStoreInterface` (`@orkestrel/agent`): when supplied, the handler `await`s `store.set(agent.context.conversations.active.snapshot())` after `agent.generate()` settles successfully, before returning — one snapshot per delegation (each `registry.build` mints a fresh conversation id through its seeded `add`, so a shared store never collides, it accumulates one snapshot per delegated call). A `store.set` failure propagates as the tool call's own failure (isolated by `ToolManagerInterface` into the canonical `error`, per invariant 2) — persistence is not best-effort. Omitted, the handler persists nothing from this tool. This composes with, and is independent of, `AgentRegistryOptions.store` (`@orkestrel/agent`): a registry built with its own `store` backs every agent it builds with a store-backed `ConversationManagerInterface`, including ones built through this tool — a caller may use either seam alone or both together.
333
+
334
+ 9. **A delegated sub-agent's lifecycle is a single `generate()` call.** `createAgentTool`'s handler resolves a live agent through `registry.build`, awaits one `agent.generate()`, and returns its settled `content` — `AgentInterface` (`@orkestrel/agent`) exposes no teardown method, so there is nothing to release afterwards; the agent's state lives entirely in the resolved `AgentContextInterface`, owned by the caller's registry.
335
+
336
+ 10. **Provider-agnostic delegation.** `createAgentTool` never imports or references a concrete `ProviderInterface` implementation — `options.provider` / a per-call `call.provider` is a registry key resolved by `registry.build`, so swapping the provider behind that key changes nothing about the tool. A missing / unresolvable provider (neither the call nor the tool's own default supplies one) throws a `TOOL` `ToolboxError` before any agent is built.
337
+
338
+ 11. **`ToolboxError` owns Toolbox boundary failures; upstream errors own genuine domain failures.** It carries a machine-readable `ToolboxErrorCode` and an optional `context` and is always thrown, never returned as `{ error }`: `TOOL` covers malformed authoring, missing tool bindings, invalid tool/agent JSON, and the other package-owned resolution/configuration guards; `DEPTH` covers Toolbox workflow/agent nesting refusal; the remaining codes retain their terminal/database/relation meanings. A genuine error thrown by an executed tool passes through unchanged. The native workflow runner's genuine `WorkflowError` also passes through unchanged, including `TRANSITION` for an unresolved named run; Toolbox never invents invalid workflow `TOOL`/`DEPTH` codes and never relabels runner errors. `WorkspaceError` similarly retains its upstream domain ownership. An in-process direct call can inspect typed codes/context; `ToolManagerInterface.execute` flattens the error to its message.
339
+
340
+ 12. **The workflow-function adapters are opt-in and exact at the JSON boundary.** `createToolFunction(tools, name)` resolves the live tool, awaits `tool.execute(controller.input)`, then deep-gates the unknown return through `parseJSONValue`; a missing binding or non-JSON result throws `ToolboxError('TOOL')`, while a genuine tool throw passes through by identity. It rejects `name === WORKFLOW_TOOL_NAME` at construction so the reserved live workflow tool cannot be adapted back into a workflow registry. `createAgentFunction(agent, options?)` returns a frozen `AgentFunction` with frozen category/lineage metadata. It starts `agent.generate({ signal: controller.signal })` synchronously after any runner-bound tool installation, using Agent's native per-run cancellation seam: an already-aborted signal starts no provider call, and cancellation settles as a partial result. The full `AgentResult` is projected through Agent-owned `agentResultToJSON`; malformed structural results become `ToolboxError('TOOL')`. With a runner, an already-running real agent is rejected before its workflow-tool binding can be replaced, so another Toolbox branch observes the running state. Genuine agent/provider errors retain identity.
341
+
342
+ 13. **The lean `summary` / full `description` split, and `createDescribeTool`'s expansion seam.** `createWorkflowTool`, `createWorkspaceTool`, and `createAgentTool` each set `ToolInterface.summary` (`@orkestrel/tool`) to a frozen one-sentence constant (`WORKFLOW_TOOL_SUMMARY` / `WORKSPACE_TOOL_SUMMARY` / `AGENT_TOOL_SUMMARY`) alongside their unchanged full teaching `description`; `ToolManagerInterface.definitions()` advertises `summary ?? description`, so a model sees the lean text by default. `createDescribeTool(tools)` is the on-demand expansion: given a registered `name`, it looks the tool up through `tools.tool(name)` and returns its full `tool.description` (falling back to `tool.summary`, then a placeholder, when a tool has neither) — never truncated, never re-derived. Each summary's text points the model at `describe('<name>')` for the full schema.
343
+
344
+ 14. **`createDatabaseTool` and `createRelationTool` are single-tool-many-operations, matching `createWorkspaceTool`'s shape.** `createDatabaseTool` dispatches `create` / `tables` / `get` / `records` / `count` / `aggregate` / `add` / `set` / `update` / `remove` / `destroy` off `databaseToolShape`; `createRelationTool` dispatches `load` / `find` / `link` / `unlink` / `links` off `relationToolShape`. Both set `ToolInterface.summary` (`DATABASE_TOOL_SUMMARY` / `RELATION_TOOL_SUMMARY`) alongside their full teaching `description`, retrievable through `createDescribeTool`, per invariant 13. Every `'get'` / `'add'` / `'set'` / `'update'` / `'remove'` and `'load'` operation's `key` field takes either a single key or an array of keys (AGENTS' batch overload mold, with the array form resolving first); a single-key call returns a singular result field (`row` / `key` / `updated` / `removed`), an array-key call returns the plural (`rows` / `keys` / `updated` / `removed` as arrays).
345
+
346
+ 15. **The database tool's query form is serialized, never fluent.** `databaseToolShape`'s `query` is a flat object — `{ conditions?: [{ column, operator, values, connector? }], order?, limit?, offset? }` — where `values` is always an array, even for a single-value operator (`{ column: 'age', operator: 'from', values: [18] }`), so a small model never chains method calls or guesses arity. `normalizeQuery` normalizes the parsed form into a live `@orkestrel/database` `QueryInput`, defaulting an omitted condition `connector` to `'and'` (the wire form lets a caller drop `connector` on the last condition, because it joins nothing forward).
347
+
348
+ 16. **The `TableSpec` column DSL is bounded to the primitives `'string'` / `'integer'` / `'number'` / `'boolean'`.** A `ColumnSpec` is either a bare `ColumnPrimitive` shorthand or `{ primitive, optional? }`; `expandTables` compiles a `TableSpec` into the `@orkestrel/database` `TableMap` `createDatabase` accepts through `compileColumn` / `compileColumnPrimitive`, wrapping an `optional: true` column in `optionalShape`. There is no nested/composite column primitive — a table's shape is a flat map of column name to `ColumnSpec`, never an object/array column.
349
+
350
+ 17. **`'records'` / `'find'` / `'links'` truncate against a configured cap, never silently.** `createDatabaseTool`'s `'records'` (cap: `DatabaseToolOptions.limit`, default `DATABASE_TOOL_LIMIT`) and `createRelationTool`'s `'find'` / `'links'` (cap: `RelationToolOptions.limit`, default `RELATION_TOOL_LIMIT`) each probe one row past the effective limit (`clampQuery` for the database tool; `resolveLimit` plus the same probe for `'find'`) to detect truncation without a separate count round trip, returning `{ rows, count, truncated, limit }` (`'links'`: `{ keys, count, truncated, limit }`) — `truncated` is `true` exactly when storage held more than `limit` matching rows/keys. A caller's own `query.limit` / `limit` can only lower the effective cap, never raise it past the configured ceiling.
351
+
352
+ 18. **A typed upstream failure re-surfaces in-process as a typed `ToolboxError`, never passes through raw.** `createDatabaseTool` catches a `@orkestrel/database` `DatabaseError` and re-throws a typed `DATABASE` `ToolboxError` carrying the original `DatabaseErrorCode` in `context.code` (`inferDatabaseCode`); `createRelationTool` does the same for a `@orkestrel/relation` `RelationError` → `RELATION` (`inferRelationCode`, checked first) and, underneath it, a `DatabaseError` → `DATABASE` (mirroring the database tool's own mapping) — so an in-process catch or direct `tool.execute(args)` call sees exactly one of `TOOL` (this tool's own guards: malformed args, unknown manager/model/database/driver), `RELATION`, or `DATABASE`, never an unwrapped upstream error. A `ToolboxError` already thrown by this tool's own guards passes through unwrapped (never re-mapped a second time). A `ToolManagerInterface.execute` registry caller does not receive that code or context; it receives only the flattened message string.
353
+
354
+ 19. **`DatabaseToolOptions.readonly` gates every mutating operation up front.** When `true`, `createDatabaseTool` throws a typed `TOOL` `ToolboxError` for `'create'` / `'add'` / `'set'` / `'update'` / `'remove'` / `'destroy'` before resolving a database or touching storage — the non-mutating operations (`'tables'` / `'get'` / `'records'` / `'count'` / `'aggregate'`) are unaffected. The exported `DATABASE_TOOL_MUTATIONS` membership list is a runtime-frozen readonly array, so a consumer cannot mutate it to bypass this gate. There is no equivalent gate on `createRelationTool` — its `'link'` / `'unlink'` writes are ungated (relation-tool callers rely on the underlying database's own access controls, if any).
355
+
356
+ 20. **A `DatabaseDefinition` is config-only and round-trips through a `DefinitionStoreInterface` — never a live handle.** `createDatabaseTool`'s `'create'` persists `{ id, driver, tables, primary?, indexes?, version? }` (never the constructed `DatabaseInterface`) when `options.store` is supplied, and publishes the new live handle to its resolver only after persistence succeeds. Every other operation lazily `resolve`s an uncached id by reading the definition back and reconstructing a live database from it (`createDatabase` and `expandTables`) — the live handle itself is cached only in-process (`Map<string, DatabaseInterface>`), reconstructed fresh on the next process from the stored config. `primary` and `indexes` are paired schema metadata, while `version` is the target stamp a versioning driver writes after first use when it implements paired `metadata` / `stamp` capabilities. `isDatabaseDefinition` is the boundary guard a `DefinitionStoreInterface` applies to an untrusted persisted blob before trusting it. `MemoryDefinitionStore` structured-clones definitions on copy-in and copy-out; `DatabaseDefinitionStore` stores one opaque JSON column in a `@orkestrel/database` table and narrows it back with `isDatabaseDefinition` on read, reporting `undefined` for a malformed blob. They implement the same `get` / `set` / `delete` contract; only the memory store prevents caller mutation from aliasing stored state on its own, because the table-backed store's isolation follows from its driver.
357
+
358
+ 21. **The relation tool's `include` is a flat dot-path list, capped by `RelationToolOptions.depth`.** `'load'` / `'find'` accept `include?: string[]` — each path a dot-separated chain of relation names (`'contacts.account'`) — expanded by `expandInclude` into a live `@orkestrel/relation` `Include` tree; a longer path subsumes a shorter sibling's bare `true` (`['contacts', 'contacts.account']` → `{ contacts: { account: true } }`). A path exceeding `depth` segments (default `RELATION_TOOL_DEPTH`), or carrying an empty segment (a leading/trailing/doubled `.`), throws a typed `TOOL` `ToolboxError` before any query runs. `resolveRelationManager` resolves which registered `RelationManagerInterface` a call addresses (an explicit `manager` miss, or an omitted one with other-than-exactly-one registered, throws typed `TOOL`); `resolveRelationModel` resolves `model` against it the same way.
359
+
360
+ 22. **`createDatabaseTool` durability and ownership are narrower than they look.** A lazily re-minted database over the default in-memory driver yields an empty database — only the `DatabaseDefinition` schema persists in `store`, never rows; durable rows need a persistent driver factory registered in `DatabaseToolOptions.drivers`. A cached live database is never evolved in place. To adopt a new target `version`, create a new database id backed by a versioned persistent driver, or close the old tool lifecycle and construct a new tool whose stored definition carries the new schema metadata and stamp. `'destroy'` closes whatever handle is cached for the id, including an embedder-supplied `DatabaseToolOptions.databases` handle — the embedder relinquishes that handle's lifecycle to this tool for any id it wires in. `timeout`, when present, must be a nonnegative safe integer and is validated when the tool is constructed. Its fresh abort signal is passed only to `records`, `count`, `aggregate`, `add`, `set`, `update`, and `remove`, whose current table APIs accept operation options; it does not bound store resolution, construction, schema inspection, `get`, or `close`, and is not an outer deadline. The tool assumes the single-writer, non-reentrant model `@orkestrel/database` itself assumes — concurrent tool calls against one id are not serialized by this tool. Unlike `'records'` / `'find'` / `'links'`, `'get'` is uncapped by `DatabaseToolOptions.limit` (bounded only by the caller's `key` array size).
361
+
362
+ 23. **`createEndpointTool` enforces its advertised inferred schema through a normalizing parse by default (`@orkestrel/contract`'s `schemaToShape`), with an explicit `validate: false` opt-out.** `parameters` is inferred once at construction (`samplesToSchema` and `schemaToObject` over `definition.samples`, tuned by `EndpointToolOptions.format`/`enum`) — the same object-rooted schema is compiled once (`schemaToShape` → `createContract`) into the contract the tool's `execute` `parse`s every call's `args` through before `definition.execute` runs. The parse coerces a scalar to its inferred type where the house parsers coerce (a number to/from a numeric string, a boolean from `'1'`/`'0'`/`'true'`/`'false'`/`1`/`0`) — `definition.execute` receives the coerced values (for example `7` sent for a string slot arrives as `'7'`), not the raw call args. A call whose `args` fails to parse into a record — a required key missing, or a value not coercible to its slot's type — throws a typed `TOOL` `ToolboxError` carrying the compiled contract's structured `explain` faults (through `contract.explain(args)`), and `definition.execute` is never called. Beyond that coercion, enforcement is structural: required keys, `enum` membership, and numeric bounds — `format` annotations (`email`, `date-time`, `uuid`, `uri`, ...) are never asserted, mirroring `@orkestrel/contract`'s own widening-only law for `schemaToShape` (a `format: true`-tuned endpoint still accepts a non-conforming string in that slot); a key outside the closed inferred schema is silently dropped, not rejected — `@orkestrel/contract`'s own `parse` grants that same leniency to any closed object. `EndpointToolOptions.validate: false` disables that enforcement: the tool's `execute` calls `definition.execute(args)` with the model-supplied `args` exactly as received, never re-parsed, coerced, or checked against the advertised schema. `createInferTool`'s own call args are, as before, always validated against `inferToolShape` (a hand-written shape) regardless — it is the schema inference's caller, not an inferred schema's consumer. `createEndpointTool`'s and `createInferTool`'s inferred schemas surface sample-derived strings verbatim (property names, and enum entries when opted in), so treat sample data intended for schema inference as untrusted content whenever the resulting schema will be advertised to other agents. `createInferTool`'s optional `candidates` call arg is the opposite seam: it checks values against the same call's freshly inferred schema (compiled per-call, because the schema itself is derived from that call's `samples`) and returns a uniform `{ index, valid, coercible, faults? }` entry per candidate. `valid` is a strict `.is` guard verdict, not a normalizing `.parse` — `7` against an inferred string slot is invalid (no coercion), where the same value would be silently coerced to `'7'` by `createEndpointTool`'s enforcement. `coercible` (`checker.parse(candidate) !== undefined`) answers that separate question directly — would `createEndpointTool`'s default enforcement admit this value — and, by the house parse/guard round-trip, is always `true` on a `valid: true` entry. Because `@orkestrel/contract`'s `.explain` mirrors `.parse`'s leniency, not `.is`'s strictness, a strictly-invalid but coercible candidate (`7` against a string slot) yields `{ valid: false, coercible: true, faults: [] }` — empty faults, because the mismatch normalization would silently fix is not one `.explain` reports; `faults` populates only for a non-coercible mismatch (`coercible: false`). `checker.is` / `.parse` / `.explain` are total over JSON-safe input — a JSON-safe hostile candidate (a `__proto__`-carrying object, deep nesting) reaches each of them and yields a bounded, non-throwing per-candidate verdict; a non-JSON-safe candidate (a throwing-getter `Proxy`) never reaches the checker — it fails the outer `args` parse and rejects the whole call as a `TOOL` error, with no per-candidate verdict. `candidates` is uncapped in count (any array length is accepted), but each individual check is bounded (`.explain`'s fault list and the checker's own schema are both bounded by the inference limits already governing `samples`), so the total per-call cost is linear in `candidates.length`.
363
+
364
+ 24. **The terminal seam speaks whole forms, and `@orkestrel/form` owns their shape.** `promptToolShape` bounds a call to `{ to, schema }` and `answerToolShape`'s `'answer'` arm to `{ id, values }`, each of `schema` and `values` admitted only as exact JSON — Toolbox does not restate the form schema as a second contract. `createPromptTool` then hands `schema` to `parseForm`; a refusal (an unknown control, a duplicate field name, a `'select'` / `'checkbox'` field without usable choices, any other schema fault) throws a typed `TOOL` `ToolboxError` and nothing parks, so a malformed schema can never become a form nobody can answer. A parsed schema becomes a live form through `createForm` and is passed to `manager.ask(from, to, form)`, whose settled `FormValues` is the tool's return value — one record keyed by field name, however many fields the form declared. `createAnswerTool` narrows `values` with `isFormValues` before `manager.answer`, so a non-form payload is refused as `TOOL` rather than reaching the parked form. Expiry reaches one code from either side: a `TerminalError('EXPIRE')` from the broker and a `FormError('ABANDONED')` from the form itself both re-surface as `EXPIRE`, so a caller branches on Toolbox's own code and never on which layer timed the form out. A failed apply carries the manager's `TerminalAnswerError` through: `context.reason` is `'unknown'` / `'rejected'` / `'target'`, and a `'rejected'` apply also carries the per-field `errors` in `context.errors` so the asking side can name which field failed.
365
+
366
+ ## Patterns
367
+
368
+ These patterns follow the arc — author and run a workflow through the tool; persist its snapshot; drive a workspace through the tool; delegate to a sub-agent; compose the adapters into a workflow's own registry.
369
+
370
+ ### Authoring and running a workflow through the tool with a real `ToolManager`
371
+
372
+ Register the workflow tool on a real manager, then let a small model author the flat step list the tool advertises:
373
+
374
+ ```ts
375
+ import { createWorkflowTool } from '@orkestrel/toolbox'
376
+ import { createToolManager } from '@orkestrel/tool'
377
+ import { createWorkflowRunner } from '@orkestrel/workflow'
378
+ import type { WorkflowDefinition } from '@orkestrel/workflow'
379
+
380
+ const definition: WorkflowDefinition = { id: 'release', name: 'Release', phases: [] }
381
+ const runner = createWorkflowRunner()
382
+ const functions = {
383
+ compile: () => 'compiled',
384
+ publish: () => 'published',
385
+ }
386
+ const tool = createWorkflowTool(definition, runner, { functions })
387
+
388
+ const tools = createToolManager()
389
+ tools.add(tool)
390
+
391
+ // A small model authors the simple flat shape — no ids/names required.
392
+ const result = await tools.execute({
393
+ id: 'call-1',
394
+ name: 'workflow',
395
+ arguments: { name: 'release', steps: [{ name: 'compile' }, { name: 'publish' }] },
396
+ })
397
+ if (!result.success) throw new Error(result.error)
398
+ result.value // { status: 'completed', count: 2 } — the single-level envelope; no nested { id, name, value }
399
+ ```
400
+
401
+ ### Plugging a `WorkflowStoreInterface` and retrieving the persisted snapshot
402
+
403
+ Hand the tool a native workflow store, run the wrapped definition, and rebuild the finished run from the snapshot the runner checkpointed:
404
+
405
+ ```ts
406
+ import { createWorkflowTool } from '@orkestrel/toolbox'
407
+ import {
408
+ createMemoryWorkflowStore,
409
+ createWorkflowRunner,
410
+ createRestoredWorkflow,
411
+ } from '@orkestrel/workflow'
412
+ import type { WorkflowDefinition } from '@orkestrel/workflow'
413
+
414
+ const definition: WorkflowDefinition = {
415
+ id: 'ingest',
416
+ name: 'Ingest',
417
+ phases: [{ id: 'load', name: 'Load', tasks: [{ id: 'read', name: 'Read' }] }],
418
+ }
419
+ const store = createMemoryWorkflowStore()
420
+ const runner = createWorkflowRunner()
421
+ const tool = createWorkflowTool(definition, runner, { store })
422
+
423
+ const summary = await tool.execute({}) // native runner checkpoints through `store`
424
+ // summary: { status: 'completed', count: 1, durable: true }
425
+ const snapshot = await store.get('ingest')
426
+ const restored = snapshot === undefined ? undefined : createRestoredWorkflow(snapshot)
427
+ restored?.status // 'completed' — the persisted run, rebuilt from its own snapshot
428
+ ```
429
+
430
+ ### Driving the workspace tool with a plugged store
431
+
432
+ Build the workspace tool over a store, then write a file and read it back through the manager the tool constructed:
433
+
434
+ ```ts
435
+ import { createWorkspaceTool } from '@orkestrel/toolbox'
436
+ import { createMemoryWorkspaceStore } from '@orkestrel/workspace'
437
+ import { createToolManager } from '@orkestrel/tool'
438
+
439
+ const store = createMemoryWorkspaceStore()
440
+ const tool = createWorkspaceTool({ store }) // builds a fresh manager over `store`
441
+
442
+ const tools = createToolManager()
443
+ tools.add(tool)
444
+
445
+ await tools.execute({
446
+ id: 'w1',
447
+ name: 'workspace',
448
+ arguments: { operation: 'write', path: 'notes.txt', content: 'hello' },
449
+ })
450
+ const read = await tools.execute({
451
+ id: 'w2',
452
+ name: 'workspace',
453
+ arguments: { operation: 'read', path: 'notes.txt' },
454
+ })
455
+ read.value // 'hello'
456
+ ```
457
+
458
+ ### Delegating to a sub-agent through the agent tool
459
+
460
+ Register the agent tool over a seeded registry and delegate one task to a sub-agent:
461
+
462
+ ```ts
463
+ import { createAgentTool } from '@orkestrel/toolbox'
464
+ import { createAgentRegistry } from '@orkestrel/agent'
465
+ import { createToolManager } from '@orkestrel/tool'
466
+
467
+ declare const registry: ReturnType<typeof createAgentRegistry> // seeded with a `providers` pool
468
+
469
+ const tool = createAgentTool(registry, { provider: 'openai' })
470
+ const tools = createToolManager()
471
+ tools.add(tool)
472
+
473
+ const result = await tools.execute({
474
+ id: 'delegate-1',
475
+ name: 'agent',
476
+ arguments: { task: 'Summarize the attached notes in three bullet points.' },
477
+ })
478
+ result.value // the sub-agent's settled `AgentResult.content`
479
+ ```
480
+
481
+ ### Persisting a delegation's conversation through the agent tool's own `store` slot
482
+
483
+ Supply a conversation store, and each delegation's conversation is persisted under its own id:
484
+
485
+ ```ts
486
+ import { createAgentTool } from '@orkestrel/toolbox'
487
+ import { createAgentRegistry, createMemoryConversationStore } from '@orkestrel/agent'
488
+ import { createToolManager } from '@orkestrel/tool'
489
+
490
+ declare const registry: ReturnType<typeof createAgentRegistry> // seeded with a `providers` pool
491
+
492
+ const store = createMemoryConversationStore()
493
+ const tool = createAgentTool(registry, { provider: 'openai', store }) // persists each delegation
494
+
495
+ const tools = createToolManager()
496
+ tools.add(tool)
497
+
498
+ await tools.execute({
499
+ id: 'delegate-1',
500
+ name: 'agent',
501
+ arguments: { task: 'Summarize the attached notes in three bullet points.' },
502
+ })
503
+ // The delegated sub-agent's conversation snapshot now lives in `store` — one entry per
504
+ // delegation (a fresh conversation id per `registry.build`, so concurrent calls never collide).
505
+ ```
506
+
507
+ ### Lean advertisement and on-demand expansion through `createDescribeTool`
508
+
509
+ Register several tools on one manager, then expand a lean `summary` into its full teaching description on demand:
510
+
511
+ ```ts
512
+ import { createDescribeTool, createWorkflowTool, createWorkspaceTool } from '@orkestrel/toolbox'
513
+ import { createToolManager } from '@orkestrel/tool'
514
+ import { createWorkflowRunner } from '@orkestrel/workflow'
515
+ import type { WorkflowDefinition } from '@orkestrel/workflow'
516
+
517
+ const definition: WorkflowDefinition = { id: 'release', name: 'Release', phases: [] }
518
+ const tools = createToolManager()
519
+ tools.add(createWorkflowTool(definition, createWorkflowRunner()))
520
+ tools.add(createWorkspaceTool())
521
+ tools.add(createDescribeTool(tools)) // the tool describes the same manager it is registered on
522
+
523
+ tools.definitions().map((entry) => entry.description)
524
+ // each entry is the lean summary (for example "Author and run a multi-phase workflow in one call — …")
525
+
526
+ const full = await tools.execute({
527
+ id: 'd1',
528
+ name: 'describe',
529
+ arguments: { name: 'workflow' },
530
+ })
531
+ full.value // the workflow tool's full multi-line teaching description
532
+ ```
533
+
534
+ ### Composing opaque leaves and raw agents into a workflow registry
535
+
536
+ Split a workflow target registry into opaque host leaves and raw agents, and hand the same split to the authoring tool:
537
+
538
+ ```ts
539
+ import type { AgentInterface } from '@orkestrel/agent'
540
+ import type { WorkflowDefinition } from '@orkestrel/workflow'
541
+ import { createToolFunction, createWorkflowFunctions, createWorkflowTool } from '@orkestrel/toolbox'
542
+ import { createToolManager } from '@orkestrel/tool'
543
+ import { createWorkflowRunner } from '@orkestrel/workflow'
544
+
545
+ declare const publishTool: Parameters<ReturnType<typeof createToolManager>['add']>[0]
546
+ declare const reviewAgent: AgentInterface
547
+
548
+ const tools = createToolManager()
549
+ tools.add(publishTool)
550
+
551
+ const definition: WorkflowDefinition = {
552
+ id: 'ship',
553
+ name: 'Ship',
554
+ phases: [
555
+ { id: 'review', name: 'Review', tasks: [{ id: 'r', name: 'Review', behavior: 'review' }] },
556
+ { id: 'publish', name: 'Publish', tasks: [{ id: 'p', name: 'Publish', behavior: 'publish' }] },
557
+ ],
558
+ }
559
+ const runner = createWorkflowRunner()
560
+ const leaves = { publish: createToolFunction(tools, 'publish') }
561
+ const agents = { review: reviewAgent }
562
+ const functions = createWorkflowFunctions(runner, { functions: leaves, agents })
563
+ await runner.execute(definition, { functions })
564
+
565
+ // The authoring tool accepts the same split. Opaque leaves remain unchanged; raw agents are
566
+ // rebuilt for each exact target lineage. The native runner owns optional checkpoint persistence.
567
+ createWorkflowTool(definition, runner, { functions: leaves, agents })
568
+ ```
569
+
570
+ ### The lenient-authoring helpers, standalone
571
+
572
+ Call the lineage, draft-completion, and step-expansion helpers directly, outside any tool:
573
+
574
+ ```ts
575
+ import {
576
+ completeDraft,
577
+ completePhaseDraft,
578
+ completeTaskDraft,
579
+ createWorkflowDraftContract,
580
+ deriveWorkflowDepth,
581
+ expandSteps,
582
+ extendLineage,
583
+ isAgentFunction,
584
+ isWorkflowLineage,
585
+ normalizeLineage,
586
+ summarizeWorkflow,
587
+ tagAgent,
588
+ tagWorkflow,
589
+ } from '@orkestrel/toolbox'
590
+
591
+ tagWorkflow('release') // 'workflow:release'
592
+ tagAgent('reviewer') // 'agent:reviewer'
593
+
594
+ const root = normalizeLineage(['workflow:release'])
595
+ const agent = extendLineage(root, tagAgent('reviewer'))
596
+ isWorkflowLineage(agent) // true
597
+ deriveWorkflowDepth(root) // 0
598
+ isAgentFunction(() => 'opaque') // false
599
+
600
+ createWorkflowDraftContract().parse({ phases: [{ tasks: [{ behavior: 'compile' }] }] })
601
+
602
+ completeTaskDraft({ behavior: 'compile' }, 'phase-0', 0) // { id: 'phase-0-task-0', name: 'phase-0-task-0', behavior: 'compile' }
603
+ completePhaseDraft({ tasks: [{ behavior: 'compile' }] }, 0) // { id: 'phase-0', name: 'phase-0', tasks: [...] }
604
+ completeDraft({ phases: [{ tasks: [{ behavior: 'compile' }] }] }) // a complete WorkflowDefinition, ids/names filled positionally
605
+
606
+ expandSteps({ steps: [{ name: 'compile' }] }) // one one-task phase whose task's `behavior` is 'compile'
607
+
608
+ // summarizeWorkflow preserves the runner's optional durability/fault fields exactly:
609
+ declare const result: Parameters<typeof summarizeWorkflow>[0]
610
+ summarizeWorkflow(result) // { status, count, durable?, fault? }
611
+ ```
612
+
613
+ ### Recovering a typed `ToolboxError`
614
+
615
+ Catch a thrown failure and narrow it with the package's own guard to read its `code`:
616
+
617
+ ```ts
618
+ import { ToolboxError, isToolboxError } from '@orkestrel/toolbox'
619
+
620
+ try {
621
+ throw new ToolboxError('TOOL', 'task is required')
622
+ } catch (error) {
623
+ if (isToolboxError(error)) console.log(error.code) // 'TOOL'
624
+ }
625
+ ```
626
+
627
+ ### Asking and answering through the terminal seam
628
+
629
+ Wire the ask and answer halves over one live terminal manager, and settle a form across them:
630
+
631
+ ```ts
632
+ import { createAnswerTool, createPromptTool } from '@orkestrel/toolbox'
633
+ import { createToolManager } from '@orkestrel/tool'
634
+ import { createTerminalManager } from '@orkestrel/terminal'
635
+
636
+ const manager = createTerminalManager()
637
+ manager.add('agent')
638
+ manager.add('reviewer')
639
+
640
+ const askTool = createPromptTool({ manager, from: 'agent' })
641
+ const answerTool = createAnswerTool({ manager, to: 'reviewer' })
642
+
643
+ const tools = createToolManager()
644
+ tools.add(askTool)
645
+ tools.add(answerTool)
646
+
647
+ // One call asks a whole form — every field is answered together.
648
+ const asked = tools.execute({
649
+ id: 'ask-1',
650
+ name: 'ask',
651
+ arguments: {
652
+ to: 'reviewer',
653
+ schema: {
654
+ label: 'Release review',
655
+ fields: [
656
+ { control: 'confirm', name: 'approved', label: 'Approve the release?' },
657
+ { control: 'editor', name: 'notes', label: 'Review notes' },
658
+ ],
659
+ },
660
+ },
661
+ }) // blocks until 'reviewer' answers
662
+
663
+ const listed = await tools.execute({
664
+ id: 'p-1',
665
+ name: 'answer',
666
+ arguments: { operation: 'pending' },
667
+ })
668
+ listed.value // [{ id, from: 'agent', schema: { label: 'Release review', fields: [...] } }]
669
+
670
+ // The same parked records, typed, straight off the manager.
671
+ const [form] = manager.pending('reviewer')
672
+ if (form === undefined) throw new Error('expected one parked form')
673
+
674
+ await tools.execute({
675
+ id: 'a-1',
676
+ name: 'answer',
677
+ arguments: {
678
+ operation: 'answer',
679
+ id: form.id,
680
+ values: { approved: true, notes: 'Ship it.' },
681
+ },
682
+ })
683
+
684
+ const result = await asked
685
+ result.value // { approved: true, notes: 'Ship it.' } — one record keyed by field name
686
+ ```
687
+
688
+ ### The terminal error-classification helper, standalone
689
+
690
+ Map a caught terminal failure to the code the terminal tools throw with:
691
+
692
+ ```ts
693
+ import { inferTerminalCode } from '@orkestrel/toolbox'
694
+ import { TerminalError } from '@orkestrel/terminal'
695
+
696
+ inferTerminalCode(new TerminalError('DEADLOCK', 'cycle')) // 'DEADLOCK'
697
+ inferTerminalCode(new TerminalError('EXPIRE', 'timed out')) // 'EXPIRE'
698
+ inferTerminalCode(new TerminalError('TARGET', 'unknown')) // 'TOOL'
699
+ inferTerminalCode(new Error('not a terminal error')) // undefined
700
+ ```
701
+
702
+ ### Bridging a `TerminalManagerInterface` onto the wire
703
+
704
+ Build the route records that carry a terminal manager over the wire, and mount them on any router that accepts a structural handler:
705
+
706
+ ```ts
707
+ import { createTerminalRoutes } from '@orkestrel/toolbox/server'
708
+ import { createTerminalManager } from '@orkestrel/terminal'
709
+
710
+ const manager = createTerminalManager()
711
+ manager.add('assistant')
712
+ const routes = createTerminalRoutes(manager, { token: 'secret' })
713
+ // mount `routes` (a GET SSE form stream and a POST `{ id, values }` answer, one shared
714
+ // `:name`-templated path) against any router that accepts a `{ method, path, handler }`
715
+ // structural record — byte-compatible with `@orkestrel/terminal`'s own `PromptClient`,
716
+ // down to the JSON answer `Result` the POST returns.
717
+ ```
718
+
719
+ ### Driving the database tool: create with metadata, add a row, query with a serialized condition
720
+
721
+ Create a database from the column DSL, add a row, and query it with a serialized condition:
722
+
723
+ ```ts
724
+ import { createDatabaseTool } from '@orkestrel/toolbox'
725
+ import { createToolManager } from '@orkestrel/tool'
726
+
727
+ const tool = createDatabaseTool() // in-memory `memory` driver, no store — created databases live for the tool's lifetime
728
+
729
+ const tools = createToolManager()
730
+ tools.add(tool)
731
+
732
+ await tools.execute({
733
+ id: 'c1',
734
+ name: 'database',
735
+ arguments: {
736
+ operation: 'create',
737
+ id: 'shop',
738
+ tables: {
739
+ products: {
740
+ columns: {
741
+ id: 'string',
742
+ name: 'string',
743
+ price: 'number',
744
+ notes: { primitive: 'string', optional: true },
745
+ },
746
+ },
747
+ },
748
+ primary: { products: 'id' },
749
+ indexes: { products: [['name'], ['price', 'name']] },
750
+ version: 1,
751
+ },
752
+ })
753
+
754
+ await tools.execute({
755
+ id: 'a1',
756
+ name: 'database',
757
+ arguments: {
758
+ operation: 'add',
759
+ id: 'shop',
760
+ table: 'products',
761
+ row: { name: 'Widget', price: 25 },
762
+ },
763
+ })
764
+
765
+ // Serialized query — a condition is a flat object; "values" is always an array.
766
+ const records = await tools.execute({
767
+ id: 'r1',
768
+ name: 'database',
769
+ arguments: {
770
+ operation: 'records',
771
+ id: 'shop',
772
+ table: 'products',
773
+ query: { conditions: [{ column: 'price', operator: 'below', values: [50] }] },
774
+ },
775
+ })
776
+ records.value // { rows: [{ id: '...', name: 'Widget', price: 25 }], count: 1, truncated: false, limit: 1000 }
777
+ ```
778
+
779
+ ### Persisting database definitions through `DefinitionStoreInterface`
780
+
781
+ Swap the in-memory definition store for the table-backed twin without changing the tool's calls:
782
+
783
+ ```ts
784
+ import {
785
+ createDatabaseDefinitionStore,
786
+ createDatabaseTool,
787
+ createMemoryDefinitionStore,
788
+ } from '@orkestrel/toolbox'
789
+ import { createToolManager } from '@orkestrel/tool'
790
+
791
+ const memory = createMemoryDefinitionStore() // in-memory Map-backed default
792
+ const durable = createDatabaseDefinitionStore() // one @orkestrel/database table (in-memory driver by default)
793
+
794
+ const tool = createDatabaseTool({ store: memory })
795
+ const tools = createToolManager()
796
+ tools.add(tool)
797
+
798
+ await tools.execute({
799
+ id: 'c1',
800
+ name: 'database',
801
+ arguments: {
802
+ operation: 'create',
803
+ id: 'shop',
804
+ tables: { products: { columns: { name: 'string' } } },
805
+ },
806
+ })
807
+
808
+ const definition = await memory.get('shop')
809
+ definition?.driver // 'memory' — the config-only blueprint, never a live handle
810
+
811
+ await durable.set({ id: 'audit', driver: 'memory', tables: {} })
812
+ const restored = await durable.get('audit')
813
+ await durable.delete('audit')
814
+ restored?.id // 'audit'
815
+ ```
816
+
817
+ ### Wiring the relation tool over a live `RelationManagerInterface` and loading nested includes
818
+
819
+ Register a live relation manager and load a row with a nested dot-path `include` list:
820
+
821
+ ```ts
822
+ import { createRelationTool } from '@orkestrel/toolbox'
823
+ import { createToolManager } from '@orkestrel/tool'
824
+ import type { RelationManagerInterface } from '@orkestrel/relation'
825
+
826
+ declare const manager: RelationManagerInterface // built with createRelationManager({ database, relations: { ... } })
827
+
828
+ const tool = createRelationTool({ managers: { shop: manager } }) // omit "manager" in a call when only one is registered
829
+
830
+ const tools = createToolManager()
831
+ tools.add(tool)
832
+
833
+ const loaded = await tools.execute({
834
+ id: 'l1',
835
+ name: 'relation',
836
+ arguments: {
837
+ operation: 'load',
838
+ model: 'accounts',
839
+ key: 'acc1',
840
+ include: ['contacts.account'], // a flat dot-path — one segment per level of nested relations
841
+ },
842
+ })
843
+ loaded.value // { row: { ...account fields, contacts: [{ ...contact fields, account: {...} }] } }
844
+
845
+ // link / unlink / links manage a many-to-many junction through a "through" relation.
846
+ await tools.execute({
847
+ id: 'k1',
848
+ name: 'relation',
849
+ arguments: {
850
+ operation: 'link',
851
+ model: 'accounts',
852
+ key: 'acc1',
853
+ relation: 'representatives',
854
+ target: 'rep1',
855
+ },
856
+ })
857
+ const linked = await tools.execute({
858
+ id: 'k2',
859
+ name: 'relation',
860
+ arguments: { operation: 'links', model: 'accounts', key: 'acc1', relation: 'representatives' },
861
+ })
862
+ linked.value // { keys: ['rep1'], count: 1, truncated: false, limit: 1000 }
863
+ ```
864
+
865
+ ### The database and relation helpers, standalone
866
+
867
+ Call the column compilers, the error classifiers, the query normalizer, and the relation resolvers directly:
868
+
869
+ ```ts
870
+ import {
871
+ compileColumn,
872
+ compileColumnPrimitive,
873
+ expandTables,
874
+ inferDatabaseCode,
875
+ inferRelationCode,
876
+ isColumnPrimitive,
877
+ isColumnSpec,
878
+ isDatabaseDefinition,
879
+ normalizeQuery,
880
+ resolveRelationManager,
881
+ resolveRelationModel,
882
+ } from '@orkestrel/toolbox'
883
+ import { DatabaseError } from '@orkestrel/database'
884
+ import { RelationError } from '@orkestrel/relation'
885
+
886
+ isColumnPrimitive('string') // true
887
+ isColumnSpec({ primitive: 'string', optional: true }) // true
888
+
889
+ const shapes = expandTables({
890
+ products: { columns: { name: 'string', price: { primitive: 'number', optional: true } } },
891
+ })
892
+ compileColumn('integer') // the integerShape() ContractShape
893
+ compileColumnPrimitive('boolean') // the booleanShape() ContractShape
894
+
895
+ isDatabaseDefinition({ id: 'shop', driver: 'memory', tables: {} }) // true
896
+
897
+ inferDatabaseCode(new DatabaseError('NOT_FOUND', 'row not found')) // 'NOT_FOUND'
898
+ inferRelationCode(new RelationError('UNKNOWN_RELATION', 'unknown relation')) // 'UNKNOWN_RELATION'
899
+
900
+ normalizeQuery({ conditions: [{ column: 'age', operator: 'from', values: [18] }] })
901
+ // { conditions: [{ column: 'age', operator: 'from', values: [18], connector: 'and' }] }
902
+
903
+ declare const managers: Readonly<
904
+ Record<string, import('@orkestrel/relation').RelationManagerInterface>
905
+ >
906
+ const resolved = resolveRelationManager(managers, undefined) // the sole registered manager, or throws
907
+ resolveRelationModel(resolved, 'accounts') // the resolved model, or throws on an unknown name
908
+ ```
909
+
910
+ ### Inferring a JSON Schema from example values, through a real `ToolManager`
911
+
912
+ Infer a schema from example values and check candidate values against that schema in the same call:
913
+
914
+ ```ts
915
+ import { createInferTool } from '@orkestrel/toolbox'
916
+ import { createToolManager } from '@orkestrel/tool'
917
+
918
+ const tool = createInferTool()
919
+ const tools = createToolManager()
920
+ tools.add(tool)
921
+
922
+ const result = await tools.execute({
923
+ id: 'call-1',
924
+ name: 'infer',
925
+ arguments: {
926
+ samples: [
927
+ { id: 1, name: 'Ada' },
928
+ { id: 2, name: 'Bob' },
929
+ ],
930
+ },
931
+ })
932
+ // result.value -> {
933
+ // type: 'object',
934
+ // properties: { id: { type: 'integer' }, name: { type: 'string' } },
935
+ // required: ['id', 'name'],
936
+ // additionalProperties: false,
937
+ // }
938
+
939
+ // pass `candidates` to check values against the freshly inferred schema — the result is wrapped
940
+ // as `{ parameters, checks }` instead of the bare parameters record, one check per candidate.
941
+ const checked = await tools.execute({
942
+ id: 'call-2',
943
+ name: 'infer',
944
+ arguments: {
945
+ samples: [{ id: 1, name: 'Ada' }],
946
+ candidates: [
947
+ { id: 2, name: 'Bob' },
948
+ { id: 'x', name: 'Cy' },
949
+ { id: 1, name: 7 },
950
+ ],
951
+ },
952
+ })
953
+ // checked.value -> {
954
+ // parameters: {
955
+ // type: 'object',
956
+ // properties: { id: { type: 'integer' }, name: { type: 'string' } },
957
+ // required: ['id', 'name'],
958
+ // additionalProperties: false,
959
+ // },
960
+ // checks: [
961
+ // { index: 0, valid: true, coercible: true },
962
+ // { index: 1, valid: false, coercible: false, faults: [{ reason: 'type', path: ['id'], expected: 'integer', received: '"x"' }] },
963
+ // { index: 2, valid: false, coercible: true, faults: [] },
964
+ // ],
965
+ // }
966
+ // Note: `valid` is a strict guard verdict (`.is`), not a normalizing parse — the opposite of
967
+ // `createEndpointTool`'s enforcement (Contract invariant 23), which coerces (`7` becomes `'7'`
968
+ // for a string slot); here `7` against a string slot is `valid: false`. `coercible` answers that
969
+ // separate question directly (would the endpoint tool's normalizing parse accept it) — candidate 2
970
+ // is a strict mismatch that is still coercible, so it carries empty `faults`: `.explain` mirrors
971
+ // `.parse`'s leniency, not `.is`'s strictness, so faults populate only for a non-coercible mismatch
972
+ // (candidate 1's wrong, non-coercible `id` type).
973
+ ```
974
+
975
+ ### Bridging an existing API endpoint into an LLM-callable tool
976
+
977
+ Wrap one concrete endpoint whose advertised `parameters` are inferred from its samples and enforced at call time:
978
+
979
+ ```ts
980
+ import { createEndpointTool } from '@orkestrel/toolbox'
981
+ import { createToolManager } from '@orkestrel/tool'
982
+
983
+ // A real handler over an existing API/DB call — samples teach the inferred `parameters`.
984
+ const tool = createEndpointTool({
985
+ name: 'lookupUser',
986
+ description: 'Look up a user by id.',
987
+ samples: [
988
+ { id: '1', name: 'Ada' },
989
+ { id: '2', name: 'Bob' },
990
+ ],
991
+ execute: async (args) => ({ id: args.id, name: 'Ada' }), // a real endpoint call goes here
992
+ })
993
+
994
+ const tools = createToolManager()
995
+ tools.add(tool)
996
+
997
+ const result = await tools.execute({
998
+ id: 'call-1',
999
+ name: 'lookupUser',
1000
+ arguments: { id: '1', name: 'Ada' },
1001
+ })
1002
+ // result.value -> { id: '1', name: 'Ada' }
1003
+ // Note: by default `args` is parsed and validated against the advertised schema before the
1004
+ // definition's `execute` runs (see Contract invariant 23) — a nonconforming call throws a typed
1005
+ // `TOOL` `ToolboxError` with structured faults instead of reaching the handler. Pass
1006
+ // `{ validate: false }` as the second argument to `createEndpointTool` for raw passthrough.
1007
+ ```
1008
+
1009
+ ## Tests
1010
+
1011
+ - [`tests/guides.test.ts`](../tests/guides.test.ts) — the `## Surface` ↔ `src/core` and `src/server` bijection (value and type exports, spanning both barrels), this guide's `## Patterns` fences resolving to real exports (per-specifier) with resolving imports, the `DefinitionStoreInterface` and `DatabaseResolver` method bijections, and the equality gate: every `Summary` cell against its declaration's description paragraph, the titled `Authoring and running a workflow through the tool with a real ToolManager` fence against the `@example` block of that title (pinned so the titled pair cannot be retired silently), and the README pitch against this guide's tagline. It also runs the flagship fences and asserts the values their comments claim.
1012
+ - [`tests/src/core/factories.test.ts`](../tests/src/core/factories.test.ts) — every factory returning a working instance or value; workflow coverage composes real runners, agents, tools, stores, and scripted providers across direct roots, top-level authoring tools, opaque-leaf propagation, frozen null-prototype registry isolation/collisions, inherited-name `TRANSITION` refusals, explicitly registered dangerous own keys, depth 8/9 boundaries, repeated workflow ids, bound no-arg cycle refusal, self-recursion, A→B→A, same-agent concurrency, native per-run cancellation (including already-aborted provider exclusion), hostile argument containment before runner/store entry, Agent-owned projection, malformed structural results, genuine error identity, and deterministic named-store replacement. Database coverage includes every operation, frozen readonly-mutation membership, persistence-before-cache publication through a real failing database store, timeout validation, query truncation, and typed failures. Creation and resolver coverage prove `primary` / `indexes` reach real memory-driver metadata and `version` is stamped after first use. Terminal coverage drives a real `TerminalManagerInterface` through the ask/answer pair: a multi-field schema parking once and settling into one values record, the pending listing carrying `{ id, from, schema }`, a schema `parseForm` refuses (an unknown control, duplicate field names, missing or non-array choices) throwing typed `TOOL` with nothing parked, the fixed `from` surviving a spoofing attempt, and the `DEADLOCK` / `EXPIRE` (injected timer) / unknown-terminal / unknown-id classifications.
1013
+ - [`tests/src/core/databases/DatabaseResolver.test.ts`](../tests/src/core/databases/DatabaseResolver.test.ts) — caller-map isolation, explicit cache operations, stored-definition construction and reuse, and the typed unknown-database failure.
1014
+ - [`tests/src/core/helpers.test.ts`](../tests/src/core/helpers.test.ts) — the reusable workflow, terminal, database, and relation helpers; lineage coverage proves copy/freeze isolation, extension, and zero-based depth derivation. Database coverage includes `normalizeQuery` connector defaults, `resolveLimit` clamping a request to the cap and flooring a negative one at `0`, and `clampQuery` probe limits.
1015
+ - [`tests/src/core/validators.test.ts`](../tests/src/core/validators.test.ts) — every guard at its untrusted boundary: `isWorkflowLineage` alternating/nonempty/unique validation and hostile-boundary totality, `isAgentFunction` metadata narrowing, `isColumnPrimitive` and `isColumnSpec` over the column DSL, and strict `isDatabaseDefinition` validation for `primary` / `indexes` / finite `version` with obsolete-field rejection.
1016
+ - [`tests/src/core/compilers.test.ts`](../tests/src/core/compilers.test.ts) — the `TableSpec` column DSL compiled through a real `createContract` gate: every `ColumnPrimitive` accepting its own values and rejecting the others, `optional: true` admitting an absent column, `optional: false` staying required, integer separated from number, and multiple tables compiled independently.
1017
+ - [`tests/src/core/shapers.test.ts`](../tests/src/core/shapers.test.ts) — every advertised shape, including valid samples of every database operation arm, create-time `primary` / `indexes` / `version`, query inputs, single/array row-key forms, and malformed metadata rejection.
1018
+ - [`tests/src/core/errors.test.ts`](../tests/src/core/errors.test.ts) — `ToolboxError` carrying its `code` and its optional `context`, and `isToolboxError` narrowing a caught value (accepting a real instance, rejecting a plain `Error` / non-error value).
1019
+ - [`tests/src/core/stores/MemoryDefinitionStore.test.ts`](../tests/src/core/stores/MemoryDefinitionStore.test.ts) — the memory twin against the shared `DefinitionStoreInterface` contract: round-trip, replacement, deletion, absent-id no-op, optional-metadata, and nested copy-isolation scenarios.
1020
+ - [`tests/src/core/stores/DatabaseDefinitionStore.test.ts`](../tests/src/core/stores/DatabaseDefinitionStore.test.ts) — the database twin against the same contract scenarios, plus database-backed-only cases covering the default in-memory driver and malformed stored blobs.
1021
+ - [`tests/src/server/factories.test.ts`](../tests/src/server/factories.test.ts) — `createTerminalRoutes` returning exactly the GET and POST records, in that order, sharing one path.
1022
+ - [`tests/src/server/terminals/TerminalBridge.test.ts`](../tests/src/server/terminals/TerminalBridge.test.ts) — the bridge's own handlers, driven through the routes the factory projects: the GET route replaying every pending form as a `pending` frame then live-forwarding `pending` / `expire` events scoped to `name`, arming a keepalive `: ` comment ping through an injected `timer` that re-validates the connection's presented token on every tick (a `TerminalToken` function that starts rejecting mid-stream tears the stream down through the shared teardown and does not re-arm; a static string token is a no-op across ticks), ending the stream (unsubscribing and cancelling the keepalive) on the request's `AbortSignal` firing, and `401`/`404` on a token mismatch / unknown `name`; the POST route reading the body capped at `options.limit` bytes and parsing the JSON body and routing it through `manager.answer` — `200` and the JSON `Result` on success, `413` an over-limit body (a lying small `Content-Length` on a big streamed body still capped, `manager.answer` never called), `400` invalid JSON, `422` a non-`{ id, values }` body or a `'unknown'`/`'rejected'` answer result, `404` an unknown `name` or a `'target'` answer result, `401` on a token mismatch; mount-churn pressure (50 sequential GET connect→abort cycles) proving zero leaked keepalive timers / manager listener subscriptions and no ghost duplicate `pending` frames; POST fuzz pressure over malformed/invalid-shape bodies, unknown endpoint, bad token, and an expired id; and consumer-side stream-close self-heal — a live `pending` event or a keepalive tick arriving on a stream closed without the request `AbortSignal` ever firing runs the same teardown the abort path runs (listeners detached, keepalive cancelled), never re-arming or leaking.
1023
+ - [`tests/src/server/terminals/TerminalConnection.test.ts`](../tests/src/server/terminals/TerminalConnection.test.ts) — idempotent direct opening, already-aborted request teardown, and fail-closed handling when a direct token validator throws.
1024
+
1025
+ ## See also
1026
+
1027
+ - [`tool.md`](tool.md) — the `ToolInterface` / `ToolManager` runtime every tool here plugs into.
1028
+ - [`workspace.md`](workspace.md) — the `WorkspaceManagerInterface` / `WorkspaceStoreInterface`, workspace errors, search options, and replace result the workspace tool drives.
1029
+ - [`agent.md`](agent.md) — the `AgentRegistryInterface` / `AgentInterface` the agent tool and `createAgentFunction` resolve and run.
1030
+ - [`workflow.md`](workflow.md) — the `WorkflowDefinition` / `WorkflowRunnerInterface` / `WorkflowStoreInterface` / `WorkflowFunction` primitives the workflow-authoring tool and adapters consume; native runner `WorkflowError`s pass through unchanged, while Toolbox authoring/depth/JSON guards use `ToolboxError`.
1031
+ - [`contract.md`](contract.md) — the shape DSL (`createContract`, `objectShape` / `unionShape` / …) every advertised `parameters` compiles through, and `schemaToParameters`.
1032
+ - [`terminal.md`](terminal.md) — a byte-identical mirror of the guide for `@orkestrel/terminal`, the `TerminalManagerInterface` / `PendingForm` / `TerminalError` primitives `createPromptTool` / `createAnswerTool` / `createTerminalRoutes` are built over, and the `PromptClient` `createTerminalRoutes` stays byte-compatible with.
1033
+ - `@orkestrel/form` — the form package this seam speaks: `FormSchema` and `parseForm` / `createForm` shape what `createPromptTool` asks, `FormValues` and `isFormValues` bound what `createAnswerTool` and the POST route apply. This guide set carries no mirror of it.
1034
+ - [`server.md`](server.md) — a byte-identical mirror of the guide for `@orkestrel/server`, the `createStream` SSE primitive `createTerminalRoutes`'s GET route is built over.
1035
+ - [`database.md`](database.md) — a byte-identical mirror of the guide for `@orkestrel/database`, the `DatabaseInterface` / `DriverInterface` / `QueryInput` / `TableMap` primitives `createDatabaseTool` (and, underneath it, `createRelationTool`) is built over.
1036
+ - [`relation.md`](relation.md) — a byte-identical mirror of the guide for `@orkestrel/relation`, the `RelationManagerInterface` / `ModelInterface` / `Include` primitives `createRelationTool` is built over.
1037
+ - [`AGENTS.md`](../AGENTS.md) — the rules; narrow untrusted input with guards, and keep documentation as an enforced contract.
1038
+ - [`README.md`](README.md) — the guides index.