## Thinking Path > - Paperclip lets people manage agents and assign saved company skills. > - Daily QA must prove that an agent loads the saved procedure it uses. > - The saved-skill journey accepts several native source-read paths. > - OpenCode's native proxy changes its built-in skill loader's normalized operation to unknown. > - The grader expected execute and rejected a real completed skill load. > - This PR accepts that one observed native shape with strict source and identity checks. > - Genuine loads can qualify while missing, foreign or incomplete evidence still fails. ## Linked Issues or Issue Description Refs: #18783. **What happened?** A combined QA run on `0884e73b46ee064b3d242e467035dde6c32e5cb5` failed `saved-skill-native-read-attributed`. The agent's successful native skill load exposed the complete saved procedure. The exact immutable assignment and source checks passed. Core recorded the built-in loader as `operation: unknown` and `readOnly: false`. The grader accepted this loader only with `operation: execute`. **Expected behavior** A complete native OpenCode skill load can qualify when its actual run, session, immutable source and paired receipt all match. A generic unknown operation cannot qualify. **Steps to reproduce** Run the saved-skill journey through Core `09162fe893b464dd2ccdafd14bd3ef1611202a86` with OpenCode. Let the agent use its native `skill` loader. The direct TypeScript converter emits execute, but the real proxy forwards a `builtinToolCall` without that operation. Core's native converter records unknown. The former TypeScript-only fixture missed this boundary. ## What Changed - Add one closed native OpenCode skill-loader branch. Require the actual run, runner, native session, item, turn and paired call. - Bind the API assignment, admitted native skill, immutable version map and materialized runtime directory to the same canonical skill. - Require the exact wrapper name, heading and complete saved procedure. Require an absolute local base directory and an empty file inventory. Reject traversal, URI encodings, backslashes, query or fragment markers and all ASCII controls. Keep valid spaces. - Require a successful, complete and untruncated native receipt with the exact output byte count. Reject missing, duplicate, reordered, foreign, failed, dynamic or MCP carriers. - Add 65 loader and boundary controls. Drive the real TypeScript-to-proxy input and use the observed Core native carrier shape. The JavaScript test does not claim to run the Rust converter. - Document the native loader path. Preserve every existing accepted execute, filesystem, first-party and Codex parser path byte-for-byte. Keep all seven checks, one-run limit, 500-cent guard, task prompt, model and ordinary Beta-off Latest assignment unchanged. ## Verification - The sealed original Core receipt reproduces RED: the former grader returns zero reads. Changing only its normalized operation to execute reaches the old accepted branch. The narrow correction returns one attributed read in separate local analysis. The original QA grade remains failed. - The actual TypeScript, proxy and native normalization source regions match between Core091 and the tested baseline0884. The proxy emits `builtinToolCall` without an operation. The native converter supplies unknown. - 334 focused tests pass across nine files, including 65 new loader controls and all existing saved-skill evidence, flow, contract and catalog controls. Run `node node_modules/vitest/vitest.mjs run --config tests/runner-e2e/vitest.config.ts tests/runner-e2e/product-journeys/saved-skill-use-native-loader.test.ts` for the narrow regression. - TAB, ESC and DEL controls fail before the review hardening and pass after it. The valid-space positive remains green. - Canonical Runner TypeScript prerequisites, own server declarations and E2E types pass. The final E2E-only check reuses those same-commit declarations after only the two test-scope files changed. The checked candidate blobs match this committed head. - Credential-free discovery lists 25 catalog journeys, including saved-skill use. Registration and selection do not change. - `git diff --check` passes. No Rust build or product-model call ran locally. Full CI [41773295359](https://github.com/paperclipai/paperclip/actions/runs/41773295359) passed on `fb537b598da40176fa1752b49a4f676f468de1f7`: 48 successful jobs and one skip. Fresh Greptile is 5/5 with zero unresolved threads. The earlier same-head canceled run remains retained; no retry was requested. The optional native review ended neutral with a 5/5 source assessment and explicit execution/qualification coverage limits. Fresh staging qualification [41777787338](https://pages.paperclip.ing/evals-qa-smoke-41777787338-1/) passed on this exact head: all seven checks and all five phases, exactly one native OpenCode run, independent saved-document reopen/source preservation, and normal cleanup complete. TestOps recorded a new passed result and the automatic Slack report succeeded. - The failed original stopped before independent document, reload and source-after checks. Those checks remain unqualified for that run. The original failed grade remains unchanged. A separate genuine fresh QA pass now verifies the complete journey before daily re-admission. ## Risks This path depends on OpenCode's observed native wrapper and materialized directory contract. Missing or changed evidence fails closed. The fix adds no runtime service, schema or API behavior. The native-loader branch is covered by strict local regression controls and the original sealed Core receipt; a separate fresh staging run also passed the complete saved-skill journey. ## Model Used OpenAI GPT-6 through Codex with reasoning, tool use and code execution. The runtime does not expose a more specific deployment ID or context-window size. Local checks made no product-model calls. ## 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>
7.1 KiB
Use a saved assigned skill
saved-skill-use is a reusable Product E2E journey for an existing target. It
uses one native work run on the target provider's campaign-owned agent. It does
not enroll itself in a daily plan. Local calibration and catalog discovery are
separate from a real staging pass.
The target supplies ordinary company skill-write access. Assignment uses
desiredSkills: [canonicalSkillKey], which selects Latest and returns a stored
versionId: null. This path works with Beta skills disabled. No experimental
setting is read or changed. Experimental version pins need separate qualification.
The fixture creates one synthetic company skill through the public skill API.
The journal name, API name and Markdown frontmatter name equal its slug. Each
contains the exact campaign ID, including its hyphens. Recovery can therefore
find the persisted skill by name if a successful creation response is lost.
It assigns the skill on the owned agent through the normal skill sync API.
The created skill must be managed, with the exact canonical company key. Its
creation receipt and separate reads must agree on the immutable current version.
A version read must return the exact SKILL.md bytes. The skill's
random receipt reference and two numeric inputs occur only in those saved bytes.
The slug and task prompt do not contain the reference. The task is created
through the production browser composer and explicitly names that skill.
A pass requires all seven closed checks:
saved-skill-assignment-bound: exact company, managed skill, creation version, source bytes and one ordinary Latest assignment before dispatch. The successful run must havecontextSnapshot.skillSelectionSnapshot: trueand an admittedskillVersions[canonicalSkillKey]equal to that immutable created version.saved-skill-input-only-in-source: the persisted request names the skill but does not expose its receipt reference; no human comment supplies that reference.saved-skill-native-read-attributed: a complete successful native skill load or source-file read, with start/completion identity bound to this company, agent, run, turn and runner. Its native receipt must contain or cryptographically commit to the complete saved procedure.saved-skill-result-authored-by-run: exactly one saved task document contains the reference and independently calculated sum. Its revision belongs to the successful native run and agent, without human authorship. The run's authoritativenativePhasemust becommittedbefore the result qualifies.saved-skill-result-reopened: the browser reload shows that document, and its independently read content and revision are unchanged.saved-skill-source-preserved: the original skill version, source bytes and assignment remain unchanged after execution.configured-model-and-environment: the one successful run matches the owned company, task, agent, configured native provider/model and execution environment.
The read oracle accepts a completed built-in file read with the exact skill
directory target, or OpenCode's native skill load with its exact named
skill_content wrapper. Deployed OpenCode can instead report a native read
with an unknown operation and null target. That path requires its complete file
result wrapper: the file path must name the exact materialized skill directory.
Both the saved assignment entry and the admitted native execution input must
bind that runtime name to the same canonical key and immutable created version.
The file read must remain read-only, successful, paired by call/turn/runner,
untruncated and byte-complete, and its content must contain the full procedure.
A targetless filesystem read without this bound file wrapper cannot qualify. Discovery,
shell output, model prose, failed or truncated reads, foreign run receipts and
marker-only output cannot pass.
Core's native OpenCode proxy forwards its built-in skill loader without an
operation. Core records that receipt as unknown with a false read-only hint.
This closed loader path requires the actual OpenCode run, runner, native session,
turn, item and paired call. Both assignment records and the admitted version map
must bind the exact immutable skill and materialized directory. Its complete
skill_content wrapper and heading must name that skill. The saved procedure
body must match exactly. The local base-directory path must be absolute and name
that bound runtime directory without traversal or URI encodings, and skill_files
must be empty. The receipt must be successful, untruncated and byte-complete.
Other unknown operations, raw dynamic calls and unbound loaders still fail.
The first-party paperclip_read_assigned_skill tool can also supply the read.
Its complete inputText result must contain the exact canonical skill key,
immutable created version, SKILL.md path, original Markdown, byte size and
SHA-256 checksum. nextOffset must be null. The same dual assignment and
admitted canonical key/version binding is required for every native provider.
The OpenCode materialized directory name is required only for its filesystem
wrapper; it is not a condition for the common first-party semantic read.
A semantic read failure can have a
completed tool status; it does not qualify. A partial page, copied tool result,
foreign version, missing start or unbound native event also fails closed.
Codex's dynamic tool display is not first-party authority. Its raw name,
optional namespace or read-only hint cannot qualify a read. For Codex, the
grader also accepts Core's retained semantic_tool.input and semantic_tool.result
for the closed read-only operation read_assigned_skill. Both must name the
actual run's runner, native session, call, turn and item. The input must match
its own digest and select the canonical skill or its exact admitted alias,
SKILL.md and offset zero (including Core's defaults). The successful
active_task result must commit to the exact complete immutable read object:
canonical key, version, file, all UTF-8 source text, SHA-256, byte size and a
null next-page offset. The digest uses Core's canonical JSON key ordering.
Missing, duplicate, reordered, failed, foreign or differently hashed carriers
fail. This path does not relax the existing OpenCode built-in or filesystem guards.
The existing campaign deadline bounds observations. Only failed read transport
operations can be retried; task input, skill creation and execution are not
replayed. Done status and a successful run alone cannot qualify incomplete native
finalization. The optional resultJson.finalizationPhase presentation field is
not required or accepted in place of nativePhase; compact or null result data
can qualify when the authoritative phase is committed.
The provider journals skill and task ownership before creation, then
collects evidence and stops/cleans only campaign-owned resources. The fixed skill
source, native events and saved document receipts stay in the private bundle.
The public report uses the existing closed checks and authenticated task capture.
No instance-wide experimental setting, shared source agent, global model, company budget or production workspace is changed by this journey.