mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-14 08:53:19 +01:00
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents read saved task documents through native tools.
> - Active-task tools use the task bound to the run. Other task tools
require a task UUID.
> - Agents sometimes call the second set with only a document key.
> - The validator rejects the call, but gives no instruction about the
active-task tool.
> - This pull request adds scope instructions to the tool schemas and
returns them for missing required fields.
> - Agents can correct a rejected call. Task selection and permission
checks stay in place.
## Linked Issues or Issue Description
**What happened?**
Two observed document reads sent only `{"key":"sentry-case"}` to
`read_task_document`. Each returned `Invalid tool input: / must have
required property 'taskId'`. Each later retry with a task UUID returned
without an error.
Pattern: [Task reads fail when agents omit
taskId](https://www.braintrust.dev/app/Paperclip/p/Paperclip%20Staging/patterns/703d8c29-6a40-4aa2-b2c6-e2c5c36b5657).
Related PR: #18824 changed document key validation. It does not address
task scope.
**Expected behavior**
The tool contract must state which task it reads. A missing-field error
must include the schema owner's recovery instructions. A retry must
still pass validation and task permission checks.
**Steps to reproduce**
1. Call `read_task_document` through the MCP bridge with only a document
key.
2. Inspect the error sent to the caller.
3. Retry with an authorized task UUID. Confirm the returned document
belongs to that task.
### Hypothesis and thesis for a fix
Hypothesis: generic tool descriptions contribute to confusion between
active-task and explicit-task tools. An agent can also ignore a correct
schema. These tests do not measure the rate of model mistakes.
Thesis: Paperclip owns task scope instructions. Its five explicit-task
tools now describe the required UUID and the active-task alternative.
The two MCP transports return direct missing-property descriptions from
the admitted schema. The transports do not infer task IDs or apply
Paperclip task policy.
Proof: the same HTTP MCP regression fails on baseline
`f673bd3cb5b5b65ec51781e37355e168c3fe8ae4` and passes with the fix. It
still rejects before handler dispatch. An explicit retry reaches the
requested task. An isolated real API and PostgreSQL test checks the
saved revision, active-task selection, malformed UUIDs, and
cross-company denial through both transports.
## What Changed
- Add UUID and active-task guidance to `get_task`, `comment_on_task`,
and the three explicit-task document tools.
- Return bounded, distinct descriptions for up to three direct missing
required fields. Keep ordinary validation errors unchanged.
- Share this formatter between the Runner and OpenCode MCP bridges.
- Add transport regressions, real route and database checks, and tool
documentation.
## Verification
- Baseline red: both transport regressions return only the
required-property error and fail the recovery instruction assertion.
- Candidate green: 58 Runner and OpenCode transport and error formatter
tests pass on Linux with Node 24.22.2.
- Final-head server verification passed all 115 tests: 113 document-key
checks and two real API integration cases.
- The two local integration cases use real MCP HTTP, agent authority,
API routes, and disposable PostgreSQL. They read a selected other task
and its saved revision, read the active task separately, reject missing
IDs for all five tools, reject malformed UUIDs, and return 404 for a
foreign company task.
- Runner TypeScript typecheck and build pass.
- The full server typecheck passed on head
`03a37e9e50896416cfd45be33bf6dbe3e13169df` with
`PAPERCLIP_TYPECHECK_LOW_MEMORY=1 node scripts/typecheck-layers.mjs`.
The first run ended without a compiler diagnostic. Final-head CI passes
and Greptile is 5/5.
- Full preflight stops because cargo is absent. Rust checks were not run
locally.
- One existing delayed-body fetch test failed with a Node 25 fetch
runtime error. All transport tests pass on Node 24. The first
integration attempt used the wrong fixture return shape; the test was
corrected before the passing run.
- CI follow-up: the Runner test program reported four TS18046 errors
because `Response.json()` returns `unknown` under CI typings. Both tests
now declare the response type. `pnpm exec tsc -p tsconfig.test.json
--noEmit` passes, and both transport suites pass again (56 tests). Final
head `ffccebca19fe9e21a097829adc020679ffa7d86c` passes CI and Greptile
5/5.
- Review follow-up: the foreign task now has a saved document with a
distinct body. Both integration cases passed again after this test-only
change. The read must return 404 and must not return that body.
Current-head CI passes and Greptile is 5/5.
- Final CI:
https://github.com/paperclipai/paperclip/actions/runs/42352822739 . All
required jobs passed for `ffccebca19fe9e21a097829adc020679ffa7d86c`.
Greptile is 5/5 with no unresolved review thread.
- Real local API behavior is verified. External model behavior and
deployed recovery are not verified. No merge or deployment is included.
## Risks
- Missing-field errors now contain schema descriptions. The text is
limited to direct properties in the admitted schema, at most three
distinct descriptions and 1,000 characters each.
- Nested schemas and references do not get recovery text. Their
validation errors remain intact.
- Clearer instructions can help callers recover. They do not guarantee
that a model will choose the correct tool.
## Model Used
- OpenAI GPT-6 via Codex, with tool use and code execution. The exact
deployment model ID and context window are not exposed to this session.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude <noreply@anthropic.com>
132 lines
6.4 KiB
Markdown
132 lines
6.4 KiB
Markdown
# Capability Semantic Tool Catalog
|
|
|
|
The semantic tool catalog is the transport-neutral surface an agent may call. It
|
|
is a frozen set of JSON-schema descriptors — no HTTP method, path, or provider
|
|
detail. Provider-specific shapes are produced only by *bindings*, so every
|
|
provider sees the same operation set.
|
|
|
|
Each protocol action is single-sourced in its own module under
|
|
`src/protocol-actions/`. That module owns the action's policy metadata,
|
|
documentation, examples, and its live and scenario JSON-schema presentations.
|
|
`src/catalog/canonical-operations.ts`, `src/semantic-tools/catalog.ts`, and
|
|
`src/tools/capability-semantic-tool-catalog.ts` are compatibility projections;
|
|
do not add action definitions to them. See
|
|
[catalog reconciliation](../spec/capability/catalog-reconciliation.md).
|
|
|
|
Sources: `src/protocol-actions/`, `src/catalog/canonical-operations.ts`,
|
|
`src/tools/capability-semantic-tool-catalog.ts`,
|
|
`src/tools/capability-semantic-tool-types.ts`, `src/tools/capability-tool-bindings.ts`,
|
|
barrel `src/tools/index.ts`.
|
|
|
|
## Two dispositions, plus a separate control-plane list
|
|
|
|
A tool descriptor's `disposition` is either **`always_agent_tool`** or
|
|
**`optional_agent_tool`**. Control-plane-owned operations are a separate frozen
|
|
list, not a disposition — they are never exposed as tools. See
|
|
[capability disposition](capability-disposition.md) and
|
|
[authorization and exposure](capability-authorization-and-exposure.md).
|
|
|
|
The catalog holds **40 tools**: 14 always-agent tools and 26 optional tools
|
|
across 10 groups.
|
|
|
|
<!-- These counts are drift-checked against src/tools/capability-semantic-tool-catalog.ts
|
|
by src/catalog/catalog-docs.test.ts; update the catalog, not the numbers. -->
|
|
|
|
### Always-agent tools (14)
|
|
|
|
`get_task_context`, `get_task_history`, `list_documents`, `read_document`,
|
|
`list_document_revisions`, `report_progress`, `answer_status_question`,
|
|
`finish_task`, `block_task`, `request_review`, `write_document`,
|
|
`request_human_input`, `register_deliverable`, `inspect_operation_result`.
|
|
|
|
Active-task document tools use the task bound to the run: call `list_documents`
|
|
with `{}` and `read_document` with `{key}`. The API bridge also exposes
|
|
`get_task`, `list_task_documents`, `read_task_document`, `write_task_document`,
|
|
and `comment_on_task` for explicitly selected authorized tasks. These require
|
|
`taskId`, a UUID from task context, search, or creation. They do not infer a task
|
|
from a document key. Their schemas identify the active-task alternative.
|
|
MCP bridges return direct required-property descriptions with missing-field
|
|
errors, so the caller can correct the request before execution. Validation and
|
|
task permissions still apply to every retry.
|
|
|
|
### Optional tools (26), by group
|
|
|
|
| Group | Tools |
|
|
| --- | --- |
|
|
| discovery | `search_tasks`, `list_agents`, `list_projects`, `list_goals` |
|
|
| delegation_dependencies | `create_task`, `reassign_task`, `set_dependencies` |
|
|
| governance | `list_approvals`, `request_approval`, `decide_approval`, `comment_on_approval` |
|
|
| cases | `list_cases`, `upsert_case` |
|
|
| workspace_runtime | `get_workspace_runtime`, `control_workspace_service` |
|
|
| routines | `list_routines`, `manage_routine` |
|
|
| company_skills | `create_skill`, `update_skill`, `list_company_skills`, `sync_company_skills` |
|
|
| secrets | `list_secret_metadata`, `read_secret_value` |
|
|
| portability_admin | `export_company`, `administer_company` |
|
|
| test_escape_hatch | `generic_api_request` |
|
|
|
|
## Descriptor shape
|
|
|
|
Each descriptor carries `operationId`, `version` (1), `title`, `description`,
|
|
input/output JSON schemas, `disposition`, an optional `optionalGroup`,
|
|
`requiredClaims`, and optionally `allowedRoles`, `taskModes`, a `sideEffectClass`
|
|
(`read`, `task_write`, `company_write`, `governance`, `workspace_control`,
|
|
`secret_read`, `admin`, `test_escape_hatch`), an `idempotency` level, `redaction`
|
|
rules, and an abstract `mockCommandMapping`.
|
|
|
|
The `mockCommandMapping` is one of `context_read`, `snapshot_read`,
|
|
`semantic_command`, `operation_result`, or `mock_extension` — describing *what*
|
|
the tool does against the mock, never *how* a transport would carry it.
|
|
|
|
## Transport neutrality
|
|
|
|
Bindings, not descriptors, produce provider shapes, both derived from the same
|
|
`visibleTools.tools` array:
|
|
|
|
- `CapabilityFakeAgentToolBinding` emits `{operationId, description, inputSchema,
|
|
outputSchema}`.
|
|
- `CapabilityCodexToolBinding` emits `{type: "function", name, description, strict:
|
|
true, parameters}`.
|
|
|
|
Because both bindings derive from one array, the fake-agent and Codex operation
|
|
surfaces are byte-identical. The [conformance suite](capability-eval-conformance.md)
|
|
asserts this parity (the 18/18 fake-agent/Codex operation matrix).
|
|
|
|
## Running the tests
|
|
|
|
```sh
|
|
pnpm --filter @paperclipai/paperclip-runner exec vitest run \
|
|
src/tools/capability-semantic-tools.test.ts
|
|
```
|
|
|
|
The "catalog" describe asserts a unique, versioned, provider-neutral catalog and
|
|
that every optional group is present.
|
|
|
|
## Related
|
|
|
|
- [Authorization and exposure](capability-authorization-and-exposure.md)
|
|
- [Mock ControlPlanePort](capability-mock-control-plane-port.md)
|
|
- [Eval conformance](capability-eval-conformance.md)
|
|
|
|
## Reassigning existing tasks
|
|
|
|
`reassign_task` moves another existing company task to an eligible agent. Read
|
|
`search_tasks` first and send the observed `expectedAssigneeActorId` (nullable)
|
|
and `expectedStatusVersion`, the new `assigneeActorId`, a handoff `reason`, and a
|
|
stable `idempotencyKey`. It requires `delegation:tasks:assign` and production
|
|
`tasks:assign` authorization in standard mode. The same task retains its
|
|
documents, dependencies, scope, priority, and history.
|
|
|
|
The old execution and any live goal must stop before ownership changes. Active
|
|
work returns to todo; backlog and blocked work retain their status and are not
|
|
started. An audited reassignment stop does not create a recovery blocker or
|
|
retry the outgoing run when it ends without a semantic completion result.
|
|
Assignment changes advance the observed version, including ordinary
|
|
API changes, to reject stale handoffs. A durable audit receipt makes retries
|
|
idempotent across caller runs; retrying after failed dispatch repairs the wake
|
|
with a stable key and an owner/status/version guard.
|
|
|
|
The current caller task, conversations, human-assigned tasks, completed or
|
|
cancelled tasks, and pending reviews cannot be reassigned with this tool. Use
|
|
`create_task` to delegate new work from the current task. A stop failure or
|
|
concurrent ownership change fails without applying the handoff.
|