mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 00:05:49 +02:00
Merge origin/master into provider routing connections
This commit is contained in:
commit
b6bc28667e
627 files changed
+97335
-5708
No files matched your search
@@ -43,6 +43,26 @@ action on the left and the primary action on the right. A step owns its whole
|
||||
footer: do not render Save & exit in a separate parent block below it. Check this
|
||||
alignment in every step and conditional state, not just the first screen.
|
||||
|
||||
## Mobile navigation and text fields
|
||||
|
||||
The fixed bottom navigation uses an opaque surface so scrolling content cannot
|
||||
show through its labels. On touch devices, editable controls use at least the
|
||||
16px base typography token to prevent Safari's automatic focus zoom. Larger
|
||||
title sizes remain larger.
|
||||
|
||||
The bottom navigation responds to accumulated scrolling, ignoring small
|
||||
reversals and Safari's edge bounce. It glides out and eases back in with shared
|
||||
motion tokens; the task composer follows the same motion. Keep page padding
|
||||
stable while the navigation moves, and honor reduced-motion preferences.
|
||||
|
||||
Task conversations reveal their initial comments, interaction cards, plan, and
|
||||
relevant run history together after positioning the latest message. Keep the
|
||||
mobile loading surface at a stable viewport height while that history loads;
|
||||
concealed content must not stretch the document. Background refreshes keep an
|
||||
already revealed conversation and composer mounted and visible.
|
||||
Bound the initial wait to 15 seconds. If a request stalls, reveal the available
|
||||
conversation and composer with a notice that some history is still loading.
|
||||
|
||||
## Contextual feedback
|
||||
|
||||
Task chat shows execution errors and waits only while they remain relevant.
|
||||
@@ -72,8 +92,10 @@ rejection and revision remain available. Live tool reviews keep their own approv
|
||||
|
||||
Pending questions, confirmations, and other task-thread inputs appear in a separate
|
||||
card directly above the ordinary composer. The composer stays available for new
|
||||
messages while the card is open. Dismissing a card leaves a pending indicator that
|
||||
can reopen it; resolving or skipping the input removes that indicator.
|
||||
messages while the card is open. Questions use their compact history entry as the
|
||||
reminder; dismissing one clears the composer and stays effective after reload for
|
||||
that person and task. Other inputs keep a pending indicator that can reopen them;
|
||||
resolving or skipping the input removes that indicator.
|
||||
|
||||
## Enforcement (what "compliant" means for the extraction run)
|
||||
|
||||
@@ -99,6 +121,8 @@ How-to guide for day-to-day UI changes: see `doc/design/CHANGING-THE-UI.md`.
|
||||
|
||||
## Motion tokens (Task Chat Redesign)
|
||||
|
||||
Mobile task panels fill the viewport within the safe area. Their top toolbar shows the current tab title, an open-tab count and selector, an add action, and an X to return to the feed. The selector lists tabs vertically with wrapping titles, an explicit current-tab check, and visible close controls. Each touch control uses the 44px size token. Desktop tabs keep their horizontal layout. Document links within the current task open through the router and retain the feed's reading position and query cache.
|
||||
|
||||
The redesigned task thread (flag `enableTaskChatRedesign`) is the first surface to
|
||||
tokenize motion. Principles — reasoning only; values live in `ui/src/index.css`:
|
||||
|
||||
@@ -123,4 +147,4 @@ tokenize motion. Principles — reasoning only; values live in `ui/src/index.css
|
||||
block collapses the duration/stagger tokens to zero, cascading to every scoped token,
|
||||
in addition to each animation's own component-level guard.
|
||||
|
||||
Agent Chat keeps pending questions as compact “Unanswered question” entries at their original position in history. A newer user message dismisses the old question form without resolving it. Opening the history entry restores the original form and its draft; submitting later uses the same durable question response path. Actual permission reviews retain their permission checks.
|
||||
Agent Chat and regular task chats keep pending questions as compact “Unanswered question” entries at their original position in history. Dismissing the form or sending a newer user message clears it from the composer without resolving it. Questions never contribute to composer pending counts or navigation. Opening the history entry restores the original form and its draft; submitting later uses the same durable question response path. Actual permission reviews retain their permission checks.
|
||||
+39
-6
@@ -177,12 +177,14 @@ idempotent actor synchronization operations, not arbitrary transactions. A
|
||||
persistent outage still fails the request after the bounded retries; each
|
||||
connection attempt remains subject to the configured database connect timeout.
|
||||
|
||||
The dashboard's company lookup, task counts, pending approval count, and
|
||||
monthly spend each retry these connection errors at most twice. Each callback
|
||||
is read-only and rebuilds its query for each attempt. A failed read
|
||||
does not replay completed reads or the budget workflow. Missing companies,
|
||||
authentication errors, and other database errors propagate without retry.
|
||||
This does not enable general SQL replay.
|
||||
The dashboard's company lookup, agent and task counts, pending approval count,
|
||||
monthly spend, and run activity aggregate each retry these connection errors
|
||||
at most twice. The heartbeat run list and base issue lookup by UUID or identifier
|
||||
use the same bounded retries. Each callback is read-only and rebuilds its query
|
||||
for each attempt. A failed read does not replay completed reads, the budget
|
||||
workflow, or issue label and watchdog enrichment. Missing resources,
|
||||
authentication errors, and other database errors retain their usual behavior.
|
||||
This does not enable general SQL replay or retry a full request.
|
||||
|
||||
## Execution identity row locks
|
||||
|
||||
@@ -450,6 +452,37 @@ Hosted AWS provider notes live in [SECRETS-AWS-PROVIDER.md](./SECRETS-AWS-PROVID
|
||||
|
||||
Migration `0274_agent_chat.sql` adds conversation identity/state and session generation/boundary columns to `issues`, plus idempotent client request IDs and processed session-boundary generations to `issue_comments`. The company/agent/user unique index resolves concurrent first writes to one issue. A check constraint preserves the assigned-agent identity and prevents terminal conversation status. Comment request IDs are unique per issue and user. There is no separate chat/message store. Provider sessions continue to use `agent_task_sessions`; `/new` removes only the matching conversation session, and session writers fence stale generations against the issue row.
|
||||
|
||||
## Resource lifecycle events
|
||||
|
||||
`resource_lifecycle_events` records content-free lifecycle hooks in the same
|
||||
transaction as the resource change. Hired agents and new projects emit `create`.
|
||||
Pending hires emit creation only when `activatePendingApproval` succeeds.
|
||||
Rejected hires emit termination without creation. Agents created as terminated
|
||||
emit no creation event. Capture is generic and works on self-hosted and managed
|
||||
instances; recording an event does not authorize a provider operation.
|
||||
|
||||
A partial unique `(company_id, resource_type, resource_id)` index deduplicates
|
||||
creation. Each actual agent pause, resume, or termination appends another event,
|
||||
including budget actions and generic status updates. The agent row stays locked
|
||||
until status and event commit, so concurrent repeat requests emit one hook.
|
||||
Termination commits API-key revocation in that same transaction.
|
||||
Hire approval and rejection commit with agent activation or termination, so a
|
||||
failed event write leaves the decision pending and retryable.
|
||||
|
||||
The numeric event ID orders transitions for a resource. Future plugin delivery
|
||||
must enforce company scope, preserve resource order, and track acknowledgments
|
||||
per plugin. A global high-water mark can skip transactions that have not yet
|
||||
committed; it is not a safe delivery cursor. The journal stores only identity,
|
||||
action, and timestamps, not repository snapshots, credentials, provider config,
|
||||
or resource health. Company deletion cascades to its events. Resource deletion
|
||||
retains events, so consumers must revalidate existence and eligibility and load
|
||||
current authorized repository data. A termination hook does not authorize
|
||||
removing persistent VM or project data.
|
||||
|
||||
The migration creates an empty table. It does not scan or backfill existing
|
||||
installs. Plugin delivery, retention, retries, and provider integration are
|
||||
separate work. This change makes no provider calls and adds no plugin read API.
|
||||
|
||||
## Legacy controller ownership
|
||||
|
||||
Legacy run claims atomically record `controller_boot_id`, a database-clock
|
||||
|
||||
@@ -103,6 +103,12 @@ pnpm build-storybook
|
||||
|
||||
These run the `@paperclipai/ui` Storybook on port `6006` and build the static output to `ui/storybook-static/`.
|
||||
|
||||
**Composer → New task** includes agent/user/project/task mentions, skill and
|
||||
routine slash commands, and populated rich chips on desktop and mobile. Agent
|
||||
mentions use the same avatars in suggestions, inserted chips, and the mocked
|
||||
creation receipt. The creation stories preserve the original Markdown references;
|
||||
they do not create real tasks or run agents.
|
||||
|
||||
Use **Components → Agent setup prompt** to review the shared setup handoff:
|
||||
hover/focus logo motion, one-click copying with a prompt preview, animated
|
||||
confirmation, and manual-copy recovery. Opening the preview copies immediately;
|
||||
@@ -140,6 +146,47 @@ choices, item verdicts, suggested tasks, tool reviews, runtime questions, and ph
|
||||
the bottom navigation. The normal message composer remains usable below the
|
||||
pending card.
|
||||
|
||||
Use **Chat & Comments → Task Chat Unanswered Questions → Test Drive** to
|
||||
try a regular task question: dismiss it, reload, reopen its compact feed entry,
|
||||
and submit the preserved draft. The composer has no question pending badge.
|
||||
The demo uses real thread/form components with fixture response callbacks.
|
||||
The Agent Chat stories share the same component fixture.
|
||||
|
||||
Use **Composer → New task** to review task creation through the production
|
||||
`TaskChatComposer`. The editor, file menu, work modes, assignee/model picker,
|
||||
and send control are shared with task chat. An inset bar above the composer uses
|
||||
the queued-message container and holds Project on the left. Its searchable picker
|
||||
shows project colors. Worktrees sits beside Project when the selected project has
|
||||
isolation enabled. Choose a new worktree, reuse an eligible worktree from that
|
||||
project, or keep the shared project workspace. Reuse carries the worktree's source
|
||||
checkout, including a non-primary checkout. Changing projects clears the
|
||||
previous worktree choice. Switching from reuse to a new worktree or the project
|
||||
workspace restores the project default checkout. Task creation uses the selected company and has
|
||||
no separate heading or settings control.
|
||||
Fresh tasks start with an empty request and the last task assignee chosen in that
|
||||
company, including a human. If that assignee is unavailable, the CEO is the
|
||||
default, or the first eligible agent when the company has no CEO. Explicit launch
|
||||
assignees and saved drafts keep their selection. Selecting an assignee never
|
||||
inserts a mention. Clicking the assignee opens its searchable list directly;
|
||||
model and effort have a separate trigger and appear only for agents whose
|
||||
harness supports those settings.
|
||||
Fresh tasks also keep the company's last project (including No project); launch
|
||||
context and saved drafts take precedence. New projects receive a random palette
|
||||
color and become the remembered project. The shared composer remembers the last
|
||||
effort within the company and applies it only when the selected model supports
|
||||
that level. Explicit task overrides and drafts take precedence. Default model
|
||||
labels show the configured or known adapter model, otherwise simply Default.
|
||||
Claude defaults supplied by the server's host environment remain Default unless
|
||||
the agent explicitly configures the model. Project edits on an existing task
|
||||
become the remembered project only after the task update succeeds.
|
||||
Clicking or tapping outside either selector dismisses it and preserves the task
|
||||
draft and selections. Mobile sheets return focus to the trigger without reopening.
|
||||
Stories cover empty and prefilled drafts, remembered, human, and unassigned
|
||||
selections, the direct assignee picker, sub-tasks, planning, files, saving,
|
||||
retryable failures, creation, light theme, and mobile, plus worktree reuse,
|
||||
loading, empty, error, and isolation-disabled states. Story submissions use local
|
||||
fixtures and never start an agent.
|
||||
|
||||
Use **Composer → Model and effort picker** to review harness-specific model
|
||||
choices. Codex uses the curated adapter catalog unless the instance declares
|
||||
`PAPERCLIP_ADAPTER_MODELS`; general OpenAI API models are not Codex choices.
|
||||
@@ -1216,6 +1263,29 @@ the updated sandbox image with the matching runner qualification changes.
|
||||
|
||||
### Native runner restart recovery
|
||||
|
||||
Project discovery through `list_projects` returns up to 50 compact summaries.
|
||||
It uses `GET /api/companies/:companyId/projects?view=summary&limit=50&cursor=...`;
|
||||
the cursor is optional. The database reads bounded summary projections. Agent
|
||||
and run trust boundaries narrow database candidates before per-project access
|
||||
checks. Projects with their own authorization policy remain candidates because
|
||||
that policy can contribute scope; every result still passes the full access check.
|
||||
Only visible projects determine page boundaries and continuations.
|
||||
The default project-list API response remains unchanged.
|
||||
Use its `nextCursor` as the next call's `cursor` until it is null; `limit` accepts
|
||||
1–50. Descriptions include at most 1,000 characters and an explicit truncation
|
||||
flag. Full project records remain available through the authorized project API.
|
||||
Workspace configuration is excluded from discovery responses so large projects
|
||||
cannot overflow the runner's durable tool-result command limit.
|
||||
|
||||
When an accepted result survives a shutdown failure, workspace repair uses a
|
||||
new assessment for the repaired workspace state. Its status decision and
|
||||
assessment reference commit together. Recovery completes from the saved result
|
||||
without another provider turn, clears stale successful-run errors, and retains
|
||||
the original error in `recoveredExecutionFailure`. Exact-state session cleanup
|
||||
recognizes both the legacy `adapter_failed` and current `provider_transport_failed`
|
||||
close-failure labels. Process ownership, pending tool outcomes, and checkpoint
|
||||
verification still control whether that session can be reused.
|
||||
|
||||
Paperclip Runner keeps its heartbeat run, native session, logical runner, and
|
||||
provider session identities across server restarts. A coordinated hot restart
|
||||
registers a correlated recovery request before it signals the dev supervisor.
|
||||
|
||||
+21
-5
@@ -209,10 +209,26 @@ telemetry. See [Announcements](ANNOUNCEMENTS.md).
|
||||
|
||||
### Agent chat discovery
|
||||
|
||||
With Agent Chat enabled, the Chats sidebar always includes the company's
|
||||
earliest-created agent, plus personal starred agents and up to four other recent
|
||||
conversations. First use has the same compact rows as returning use. The compose
|
||||
icon shares a column with stars and appears on hover or keyboard focus (always on
|
||||
touch). It opens a company-wide name/role search, independent of sidebar membership.
|
||||
With Agent Chat enabled, Chat is the first row of the Work section and opens a
|
||||
secondary sidebar beside the primary nav. It lists every agent you can chat
|
||||
with: the open conversation first, then your other conversations by recent
|
||||
activity, then the rest of the roster alphabetically. Terminated agents and
|
||||
agents you have left are omitted unless you have history with them. Search
|
||||
filters by name, title, or role; **+** starts or reopens a conversation.
|
||||
Selecting an agent opens their persistent conversation; it does not reset history
|
||||
or create a task until the existing first-write flow requires one.
|
||||
|
||||
Beside a conversation, the side panel opens on the agent's tasks: one card per
|
||||
task the agent created, was assigned, commented on, or acted on, newest first,
|
||||
with the task list's filters and a sort control. The agent's artifacts are a
|
||||
second card stack available from the panel's **+** menu. Both open in a new tab
|
||||
so the conversation stays open.
|
||||
|
||||
### Combined Inbox + Task List
|
||||
|
||||
An opt-in experimental setting (`enableCombinedInboxTasks`, off by default)
|
||||
folds Inbox into Tasks. The Inbox nav row goes away and its unread badge moves
|
||||
to Tasks. A Views menu on the task list covers the inbox views (Mine, Unread,
|
||||
Blocked, Recent, Everything) and the task-status views (All, Active, Backlog,
|
||||
Done). Bare `/issues` opens the last-used view, defaulting to Mine; links that
|
||||
carry a task filter open All. Old `/inbox` links redirect to the matching view.
|
||||
@@ -1502,6 +1502,13 @@ Required UX behaviors:
|
||||
- conflict toasts on atomic checkout failure
|
||||
- no silent background failures; every failed run visible in UI
|
||||
|
||||
Regular task chats and Agent Chat keep unanswered questions as compact,
|
||||
reopenable entries at their original position in the feed. Dismissing a question
|
||||
or sending a newer message clears its form from the composer without resolving
|
||||
the saved question. Questions do not contribute to composer pending counts.
|
||||
Dismissal persists locally for the person and task across reloads; reopening
|
||||
restores the original form and draft. Approval and permission gates are unchanged.
|
||||
|
||||
## 15. Operational Requirements
|
||||
|
||||
## 15.1 Environment
|
||||
@@ -1702,12 +1709,18 @@ Confirmed project creation appears as a durable card in the shared task transcri
|
||||
|
||||
### User continuation after execution recovery stops
|
||||
|
||||
An authenticated user message or an exact failed-run Retry can start a fresh
|
||||
An authenticated user message, a validated undelivered native message queue, or an exact failed-run Retry can start a fresh
|
||||
native or legacy conversation turn once the prior execution is confirmed stopped. Retain the source history and uncertain
|
||||
action outcomes; do not replay tool calls or reset the failed incident's automatic
|
||||
retry budget. Existing pause, approval, budget, ownership, and dependency gates
|
||||
remain in effect. See `doc/execution-semantics.md` for admission and stop-proof
|
||||
requirements.
|
||||
The task recovery notice offers Retry for eligible failures and verified native
|
||||
startup cancellations, with failed attempts explained inline. Preparing native
|
||||
turns keep the Steer label. Steer and Interrupt immediately move the submitted
|
||||
messages from the composer queue into the conversation while delivery proceeds.
|
||||
Provider acknowledgement remains authoritative; failed delivery restores the
|
||||
latest queue with an inline error. Neither action produces a toast.
|
||||
|
||||
### Managed AI authentication
|
||||
|
||||
@@ -1893,3 +1906,23 @@ unavailable. Preserve current ownership and newer-work fences. See
|
||||
endpoints delegate to sources while retaining response shapes.
|
||||
- GitHub.com, manual refresh only. No upstream editing, polling, webhook sync, commits,
|
||||
or pull-request creation in this milestone.
|
||||
|
||||
### Experimental AI connection routing
|
||||
|
||||
Opt-in plugin routers may represent a pool as an AI runtime binding. Core keeps
|
||||
company and credential authorization, atomically records task/agent affinity and
|
||||
a pool cursor, and persists concrete native recovery evidence. The full contract
|
||||
is in [AI-CONNECTION-ROUTERS.md](connections/AI-CONNECTION-ROUTERS.md). Disabled
|
||||
routing cannot allocate new tasks; already admitted native runs remain recoverable.
|
||||
|
||||
### Connection instructions
|
||||
|
||||
Connections can store optional, versioned agent instructions independently of
|
||||
provider and transport. Catalog templates control editor visibility; saved
|
||||
settings and runtime delivery also support connections without a template.
|
||||
The server includes instructions only when the connection and at least one
|
||||
action are available to the run's agent and responsible identity. An immutable
|
||||
per-turn snapshot participates in session compatibility, so subsequent turns
|
||||
remove stale instructions after edits or access revocation. See
|
||||
[Connection instructions](connections/CONNECTION-INSTRUCTIONS.md) for contracts,
|
||||
UI conventions, custom adapter integration, and initial memory templates.
|
||||
+8
-1
@@ -299,7 +299,7 @@ should name the rejected action and the specific restriction.
|
||||
|
||||
There is no separate messaging or chat system. Tasks are the communication channel. This keeps all context attached to the work it relates to and creates a natural audit trail.
|
||||
|
||||
Experimental Agent Chat presents one persistent task per person and agent as a simplified conversation. Chat has a searchable secondary sidebar with agent avatars; adding an agent starts or reopens their single conversation. It retains the task composer, transcript, tools, attachments, documents, and existing Subtasks panel, with ordinary company visibility. New execution tasks are ordinary project tasks, not children of the conversation. Idle conversations wait for a message without entering execution-task work queues. Agents clarify goals here and create assigned tasks for substantial execution. `/new` resets provider context at an ordered session boundary within the same task while preserving visible history. `enableAgentChat` is disabled by default; the V1 lifecycle and rollout contract is specified in `SPEC-implementation.md`.
|
||||
Experimental Agent Chat presents one persistent task per person and agent as a simplified conversation. Chat has a searchable secondary sidebar with agent avatars that lists every eligible agent, conversations first; adding an agent starts or reopens their single conversation. It retains the task composer, transcript, tools, attachments, documents, and existing Subtasks panel, with ordinary company visibility. New execution tasks are ordinary project tasks, not children of the conversation. Idle conversations wait for a message without entering execution-task work queues. Agents clarify goals here and create assigned tasks for substantial execution. `/new` resets provider context at an ordered session boundary within the same task while preserving visible history. `enableAgentChat` is disabled by default; the V1 lifecycle and rollout contract is specified in `SPEC-implementation.md`.
|
||||
|
||||
### Question recipients
|
||||
|
||||
@@ -686,3 +686,10 @@ requirements, and advisory warnings for missing or external references. New
|
||||
upstream skills require reviewed selection; removed or deselected skills remain
|
||||
installed. Editing starts with an independent copy. Write-back and PR publication
|
||||
are a later milestone; exact path and commit provenance provide their base.
|
||||
|
||||
### Experimental connection routing
|
||||
|
||||
A virtual AI connection can rotate new task/agent allocations through an
|
||||
authorized pool while preserving session affinity. Admission, credentials and
|
||||
durable recovery remain host responsibilities; policy can be supplied by an
|
||||
opt-in plugin. See [the experimental contract](connections/AI-CONNECTION-ROUTERS.md).
|
||||
@@ -0,0 +1,93 @@
|
||||
# Adapter model and harness audit: October 2, 2026
|
||||
|
||||
This audit follows the [September 22, 2026 audit](adapter-model-audit-2026-09-22.md).
|
||||
It covers model selection in the coding-agent adapters and the shared harness
|
||||
pins in the runner, the provider pack, and the evaluation image. Catalog
|
||||
entries identify models; provider accounts and installed CLIs determine access.
|
||||
No agent defaults or saved model selections are migrated.
|
||||
|
||||
## Model changes
|
||||
|
||||
| Adapter | Changes from the audit |
|
||||
| --- | --- |
|
||||
| Codex and the Codex runner catalog | Add GPT-6.1 Sol (`gpt-6.1-sol`). Expose efforts through Ultra and Fast mode, as documented. Keep `gpt-5.6-sol` as the default. |
|
||||
| Claude on Bedrock | Add Sonnet 5.5 (`us.anthropic.claude-sonnet-5-5`), using the catalog's existing US inference-profile convention. Sonnet 5.5 IDs (direct or Bedrock-qualified) get the documented `xhigh` and `max` efforts and require Claude Code 2.1.284 or later on the CLI lane. |
|
||||
| OpenCode | Add `openai/gpt-6.1-sol` and `anthropic/claude-sonnet-5-5` to the static fallback used by remote environments. Both IDs are present in the OpenCode model registry. |
|
||||
| Claude Code (direct) | No change in this audit. [#14993](https://github.com/paperclipai/paperclip/pull/14993) (merged October 5, 2026, superseding #14816) adds Sonnet 5.5 (`claude-sonnet-5-5`, Claude Code 2.1.284 or later) and refreshes the Claude runtime. |
|
||||
| Grok Build, Gemini CLI, Kimi Code, Cursor | No new verified model IDs. Grok 4.7, Gemini 3.8 Flash, and Kimi K3 remain the newest documented models. |
|
||||
|
||||
## Harness changes
|
||||
|
||||
| Harness | Before | After |
|
||||
| --- | --- | --- |
|
||||
| Codex CLI (shared provider pack) | 0.156.0 | 0.160.0 |
|
||||
| OpenCode (shared provider pack) | 1.18.32 | 1.18.34 |
|
||||
| Grok CLI (`@xai-official/grok`) | 1.0.41 | 1.0.46 |
|
||||
| Gemini CLI | 0.60.0 | 0.62.0 |
|
||||
| Kimi Code CLI | 2.0.2 | 2.1.1 |
|
||||
| Cursor CLI | 2026.09.18-9a7762b | 2026.10.01-e373342 |
|
||||
| GitHub CLI | 2.101.0 | 2.102.0 |
|
||||
| Claude Agent SDK / Claude Code | 0.3.280 / 2.1.280 | 0.3.286 / 2.1.286 via #14993 (merged); this branch keeps that pin |
|
||||
| Hermes | 0.19.0 | 0.19.0 (current) |
|
||||
| ACPX, `claude-agent-acp`, `codex-acp` bridges | 0.13.1 / 0.73.0 / 1.6.2 | Unchanged; separately qualified |
|
||||
| Pi (`@earendil-works/pi-coding-agent`) | 0.87.1 (fleet image) | Unchanged; Pi 1.0 is qualified upstream in the Pi runner stack |
|
||||
|
||||
The remote Codex compatibility window moves to `>=0.149.0 <0.161.0`. The
|
||||
minimum stays fixed. See
|
||||
[paperclip-runner-compatibility.md](architecture/paperclip-runner-compatibility.md).
|
||||
|
||||
## Sources and verification
|
||||
|
||||
- [OpenAI's Codex model guide](https://learn.chatgpt.com/docs/models) lists
|
||||
GPT-6.1 Sol (`gpt-6.1-sol`) with reasoning efforts from Light to Ultra and
|
||||
Standard and Fast modes at launch. The bundled Codex 0.160.0 model metadata
|
||||
also contains `gpt-6.1-sol`. The same page records that `gpt-5.4` and
|
||||
`gpt-5.4-mini` retired from Codex with ChatGPT sign-in on August 31, 2026,
|
||||
and that `gpt-5.5` retires from Codex with ChatGPT sign-in on October 14,
|
||||
2026. Neither retirement applies to the OpenAI API, so the picker keeps
|
||||
those IDs for API-key users; see "Deferred items".
|
||||
- [Codex CLI releases](https://github.com/openai/codex/releases) 0.157.0
|
||||
through 0.160.0 add GPT-6 Sol and Luna metadata, app-server pagination, and
|
||||
authoritative provider catalogs. The Linux x64 executable digest comes from
|
||||
the integrity-verified `@openai/codex@0.160.0-linux-x64` archive.
|
||||
- [Claude Sonnet 5.5](https://platform.claude.com/docs/en/models/sonnet-5-5/overview)
|
||||
documents the Bedrock ID `anthropic.claude-sonnet-5-5` and the September 28,
|
||||
2026 release. The [Claude Code changelog](https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md)
|
||||
entry for 2.1.284 adds `claude-sonnet-5-5`, so that version is the CLI minimum. [Models overview](https://platform.claude.com/docs/en/about-claude/models/overview)
|
||||
lists `xhigh` and `max` effort support for the 5.5 generation.
|
||||
- [OpenCode releases](https://github.com/anomalyco/opencode/releases)
|
||||
1.18.33 and 1.18.34 contain fixes only. The OpenCode model registry at
|
||||
models.dev lists both added provider-qualified IDs.
|
||||
- Grok CLI 1.0.46 is the `latest` tag on npm. [xAI's model list](https://docs.x.ai/docs/models)
|
||||
still ends at Grok 4.7. Gemini CLI 0.62.0 and Kimi Code 2.1.1 are the
|
||||
`latest` tags on npm; [Google's model catalog](https://ai.google.dev/gemini-api/docs/models)
|
||||
and [Kimi's model page](https://www.kimi.com/code/docs/en/kimi-code/models.html)
|
||||
list no newer coding models.
|
||||
- Cursor CLI 2026.10.01-e373342 is the version the official installer
|
||||
resolves; the pinned archive digest comes from the versioned tarball, and
|
||||
the extracted binary reports that version. [Cursor's catalog](https://cursor.com/docs/models-and-pricing)
|
||||
shows new Grok 4.7 500k and Kimi K3 rows without exact CLI IDs, so no
|
||||
fallback IDs were added.
|
||||
- [GitHub CLI 2.102.0](https://github.com/cli/cli/releases/tag/v2.102.0)
|
||||
contains security fixes for download commands. The pinned digest matches the
|
||||
release `checksums.txt`.
|
||||
- [Hermes on PyPI](https://pypi.org/project/hermes-agent/) remains at 0.19.0.
|
||||
|
||||
## Deferred items
|
||||
|
||||
- `gpt-5.4` and `gpt-5.4-mini` stay in the Codex picker because the OpenAI API
|
||||
still serves them. A later audit can remove them once API retirement is
|
||||
published or operators confirm no API-key agents depend on them. `gpt-5.5`
|
||||
needs the same review after October 14, 2026.
|
||||
- The ACP bridges (ACPX 0.19.4, `claude-agent-acp` 0.85.1, `codex-acp` 2.1.1)
|
||||
are newer upstream. They carry reviewed patches and qualified executable
|
||||
digests, so they need a separate qualification campaign rather than a pin
|
||||
bump. ACPX 0.15.1 and 0.17.0 also change message limits and session
|
||||
journal records.
|
||||
- Pi 1.0.0 is published, but the runner's Pi profile and the fleet image stay
|
||||
on the qualified 0.84.2 / 0.87.1 pins until the Pi 1.0 runner stack lands.
|
||||
- The native Grok runtime (`native:grok` 1.0.13) is bound by a qualified
|
||||
profile digest and is not refreshed by this audit.
|
||||
- Claude Mythos 5.1 remains invitation-only with no public selectable ID.
|
||||
- Cursor's exact IDs for Grok 4.7 500k, Kimi K3, and GLM 5.3 are not shown
|
||||
in the documentation and the unauthenticated CLI returns no list.
|
||||
@@ -46,15 +46,15 @@ would hide the runtime that executed the task.
|
||||
|
||||
## Remote Codex version window
|
||||
|
||||
Remote native Codex runs accept stable CLI versions **`>=0.149.0 <0.157.0`**.
|
||||
The install pin remains `0.156.0`. A sandbox image can therefore use an older
|
||||
Remote native Codex runs accept stable CLI versions **`>=0.149.0 <0.161.0`**.
|
||||
The install pin is `0.160.0`. A sandbox image can therefore use an older
|
||||
compatible Codex without failing startup or installing another copy.
|
||||
|
||||
The minimum is fixed at **`0.149.0`** until maintainers deliberately change it.
|
||||
It is not a rolling one-month support window. The official
|
||||
[`@openai/codex` publication history](https://registry.npmjs.org/@openai/codex)
|
||||
records `0.149.0` on 2026-08-20, `0.153.4` on 2026-09-04, and `0.156.0` on
|
||||
2026-09-22. See also the [Codex changelog](https://learn.chatgpt.com/docs/changelog).
|
||||
records `0.149.0` on 2026-08-20, `0.153.4` on 2026-09-04, `0.156.0` on
|
||||
2026-09-22, and `0.160.0` on 2026-10-01. See also the [Codex changelog](https://learn.chatgpt.com/docs/changelog).
|
||||
This is an explicit compatibility window, not a claim that every Codex minor
|
||||
release follows a backward-compatible protocol.
|
||||
|
||||
@@ -65,7 +65,7 @@ release follows a backward-compatible protocol.
|
||||
- The same check applies to discovered, explicitly staged, and npm-installed
|
||||
remote Codex executables, including the final executable after linking.
|
||||
- For an incompatible image, the existing
|
||||
`PAPERCLIP_RUNNER_REMOTE_CODEX_NPM_SPEC=@openai/codex@0.156.0` configuration
|
||||
`PAPERCLIP_RUNNER_REMOTE_CODEX_NPM_SPEC=@openai/codex@0.160.0` configuration
|
||||
allows installation of the pinned runtime. Without that configuration,
|
||||
startup reports the supported range and the remediation.
|
||||
- Runner binary contracts, required runner capabilities, artifact digests,
|
||||
@@ -85,6 +85,12 @@ app-server binaries with a local deterministic Responses API fixture. Both
|
||||
passed initialization, thread creation with a dynamic tool, a tool-call/result
|
||||
round trip, turn completion, and thread resume. This verifies the tested
|
||||
protocol path; it is not a live model, Linux sandbox, or exhaustive feature test.
|
||||
The 2026-10-02 refresh moved the install pin and the upper bound to `0.160.0`
|
||||
after verifying the integrity-checked Linux x64 `0.160.0` executable digest and
|
||||
the installed ACP package contract. Codex `0.157.0` through `0.160.0` add the
|
||||
GPT-6.1 Sol catalog entry and app-server pagination without a documented
|
||||
app-server protocol break; the Linux CI qualification workflow covers the real
|
||||
startup and session-resume probes for the new pin.
|
||||
Version-boundary and remote artifact-preparation regressions are covered by
|
||||
`codex-runtime-compatibility.test.ts` and `native-session-executor.test.ts`.
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
# Experimental AI connection routers
|
||||
|
||||
AI routers are virtual, company-scoped connections owned by a capability-gated
|
||||
plugin (`ai.connections.route`). The instance flag `enableAiConnectionRouters`
|
||||
defaults to false. Pools also default to disabled. The host must implement this
|
||||
contract; the plugin's minimum version alone does not establish compatibility.
|
||||
Routing has no visible enable control in Experimental settings on either
|
||||
self-hosted or Cloud instances, even after an operator enables it. Self-hosted
|
||||
operators can set `enableAiConnectionRouters` through the instance-admin
|
||||
`PATCH /api/instance/settings/experimental` API. Cloud operators use the existing
|
||||
fleet or per-stack managed feature configuration. Installing a plugin does not
|
||||
enable routing; enabling routing does not enable individual pools.
|
||||
|
||||
The host authorizes members using the ordinary company, user, sharing, install,
|
||||
health and harness checks. Saving a binding or changing its harness requires
|
||||
at least one usable member for that agent. New-agent creation installs only
|
||||
members authorized by the ordinary personal-owner or shared-install policy,
|
||||
in the same transaction as the agent. Pool membership cannot install a
|
||||
restricted shared connection. It sends authorized metadata and sanitized usage
|
||||
observations to `onRouteAiConnection`. The plugin proposes an opaque member ID;
|
||||
it cannot receive credentials or expand authorization. Pure round robin does
|
||||
not probe usage. Usage-aware selection has a shared 15-second probe budget,
|
||||
60-second freshness cache and ordinary grant/secret freshness invalidation.
|
||||
Concurrent starts share an in-flight probe for the same grant and credential
|
||||
freshness. Each caller retains its selection deadline.
|
||||
|
||||
One cursor spans all agents in a pool. An allocation transaction locks the
|
||||
cursor, checks config and cursor revisions, rechecks authorization, and writes
|
||||
the task pin and cursor advance together. Pins use company, pool, agent and the
|
||||
existing task key (including `__heartbeat__`). Wakes without a task key retain
|
||||
the original run ID as an affinity key in retry context. Conflicts retry at most
|
||||
20 times; contention beyond that returns an actionable conflict.
|
||||
|
||||
Pins snapshot the concrete binding and member profile. Removing or editing a
|
||||
member affects future allocations. Composer changes can change a supported
|
||||
model or effort, with a note on fallback, but never the account or harness.
|
||||
Session reset and compaction retain the pin. Revocation requires operator repair.
|
||||
Authentication repair uses the failed run’s durable concrete allocation and
|
||||
current member permissions; reconnect-and-continue preserves the agent’s pool
|
||||
binding. Changing the agent’s pool invalidates the old repair card.
|
||||
A pre-existing managed session can adopt its saved account when it is an
|
||||
eligible member; otherwise the operator must explicitly reset the session.
|
||||
|
||||
Credential `ai_session_epoch` changes on reconnect or manual rotation. Only
|
||||
verified runtime refresh write-back preserves it. Session fingerprints use the
|
||||
secret ID and epoch while authentication failure attribution still uses the
|
||||
token generation. A reconnect that replaces an indexed legacy secret changes
|
||||
the session identity even when both secret epochs are zero.
|
||||
Adopting a valid account preserves a session only when the complete effective
|
||||
configuration matches a prior fingerprint. Core can bridge binding-only agent
|
||||
revisions (up to 20) and unchanged legacy token identities at epoch zero. Changes
|
||||
to other settings, explicit resets, and credential replacement retain their
|
||||
existing reset behavior; no fingerprint category is exempted.
|
||||
Native recovery retains concrete routing evidence and can finish after the
|
||||
router flag or plugin is disabled or uninstalled; it revalidates underlying
|
||||
account access and never makes a new allocation.
|
||||
|
||||
The private Cloud plugin owns round-robin and quota policy. Its manifest declares
|
||||
`aiConnectionRouter: { name, description }` alongside `ai.connections.route`.
|
||||
Core adds that connector to the regular catalog and hosts its setup and account
|
||||
management using authenticated, company-scoped pool APIs. No separate plugin
|
||||
page or sidebar entry is needed. New allocations over
|
||||
the chosen threshold wait; known exhaustion defers pinned turns. The deferred
|
||||
run schedules `ai_connection_pool_wait` without spending the failure retry
|
||||
budget, and the UI labels that retry as **Pool exhausted**.
|
||||
|
||||
Operators choose **Connectors → AI connection pool → Add connection pool**,
|
||||
select existing accounts, arrange their order, and create a paused connection.
|
||||
The normal account page supports rename, member changes, enabling, and removal.
|
||||
**Used by** lists the company’s non-terminated agents configured to use the pool,
|
||||
with their avatars and links to their profiles. Paused agents remain listed.
|
||||
Optional usage and runtime defaults live under **Advanced**. Creating another
|
||||
account opens the normal catalog in a new tab so the pool draft stays intact.
|
||||
The catalog marks experimental-disabled or unavailable plugins as unavailable.
|
||||
|
||||
Deletion uses
|
||||
`DELETE /api/companies/:companyId/ai-connection-pools/:poolId` with the current
|
||||
`expectedRevision`. Core disables and archives the virtual connection, retaining
|
||||
its task pins, cursor and run records. It rejects stale edits and new allocations;
|
||||
already admitted runs keep their concrete recovery evidence. Cleanup remains
|
||||
available when experimental routing or the plugin is disabled. The catalog captures the reviewed pool revision before showing its removal
|
||||
confirmation and uses the pool endpoint. Ordinary connection update and removal
|
||||
endpoints reject pools so they cannot bypass configuration revisions. Non-managers
|
||||
see the permission requirement without making a denied management request.
|
||||
|
||||
Configuration and committed selection are recorded in activity records. Runs
|
||||
record the selected member/profile and override notes in the local run log and
|
||||
recovery context. These records stay in the instance database.
|
||||
|
||||
Tests: `cd server && pnpm exec vitest run src/__tests__/ai-connection-router.test.ts`
|
||||
plus the existing AI connection, retry accounting, run-dispatch and UI suites.
|
||||
|
||||
## UI review in Storybook
|
||||
|
||||
Run `pnpm --filter @paperclipai/ui storybook` from Core, then open
|
||||
**AI Connections / Connection pools**. The 16 stories use production components
|
||||
for pool selection, legacy-session adoption, unavailable/read-only selections,
|
||||
mixed-provider composer models and effort, mobile composer settings, usage waits,
|
||||
run selections and override notes, and scheduled retries.
|
||||
`AllCoreSurfaces` provides an overview; individual stories expose the expanded
|
||||
menus and adoption dialog. Run `pnpm --filter @paperclipai/ui build-storybook`
|
||||
to build the preview.
|
||||
|
||||
Pool configuration stories belong to the private Cloud plugin's own Storybook
|
||||
in `extensions/plugin-connection-pool/storybook/`. Both previews use fictional
|
||||
accounts; they do not call live providers or mutate a Paperclip instance.
|
||||
Core also owns **Connectors / Pool host** stories for the generic native
|
||||
extension. The overview links to Cloud's **In Connectors** preview on port 6010;
|
||||
its stories mount these same production routes, header, sidebar and tokens.
|
||||
**Full Setup And Management** exercises the complete catalog-to-pool journey.
|
||||
Both previews include **Operator Setup Required** for a manually disabled host.
|
||||
|
||||
## Full-app browser acceptance
|
||||
|
||||
Install the compatible plugin into an isolated, loopback `local_trusted` instance,
|
||||
enable the experimental flag, and use a company named for an E2E or test drive
|
||||
with two saved, authorized AI accounts. Run:
|
||||
|
||||
```sh
|
||||
PAPERCLIP_CONNECTION_POOL_E2E=1 \
|
||||
AI_CONNECTIONS_TEST_URL=http://127.0.0.1:3100 \
|
||||
AI_CONNECTIONS_TEST_COMPANY_ID=<test-company-id> \
|
||||
pnpm exec playwright test --config tests/ai-connections-app/playwright.config.ts connection-pools.spec.ts
|
||||
```
|
||||
|
||||
These opt-in tests exercise the shipped app, installed plugin and database without
|
||||
mocking browser routes or pool APIs. They cover catalog navigation, setup, ordering,
|
||||
paused defaults, rename, member changes, refresh persistence, stale edits and
|
||||
removal. They verify that an operator-enabled host still offers no Experimental
|
||||
control for routing. With a test agent already bound to a saved pool, they also verify
|
||||
the **Used by** list, avatars and profile links. Cleanup archives only the test's own pool and verifies that existing
|
||||
accounts and pools remain intact. This suite does not execute agents or probe
|
||||
live usage; provider and restart acceptance remain separate gated test drives.
|
||||
@@ -0,0 +1,120 @@
|
||||
# Connection instructions
|
||||
|
||||
Connection instructions are an optional, provider-independent capability. A saved
|
||||
connection can contribute a short paragraph to an authorized agent’s execution
|
||||
prompt. Memory providers are the first catalog templates; custom and non-memory
|
||||
connections use exactly the same settings, authorization, and delivery path.
|
||||
|
||||
## Catalog and saved settings
|
||||
|
||||
A connector can declare `agentInstructions: { id, version, text }` in its
|
||||
`AppDefinition`. Author reviewed defaults directly in that app's
|
||||
`packages/shared/src/app-definitions/<slug>.json`, alongside its configuration
|
||||
and supporting provider `docsUrl`. This field is the source of truth; there is
|
||||
no separate template registry. Catalog ingestion validates and preserves it,
|
||||
including edits or removal, when regenerating the other definition fields. Run
|
||||
`node scripts/ingest-app-definitions.mjs --definitions-only` to verify. Template
|
||||
text is limited to 2,000 characters. Remote MCP initialization prose is never
|
||||
adopted as trusted instructions automatically.
|
||||
|
||||
Every `ToolConnection` has nullable `agentInstructions`:
|
||||
|
||||
```json
|
||||
{
|
||||
"enabled": true,
|
||||
"text": "Use the release handbook before changing a deployment. Cite the checklist.",
|
||||
"template": { "id": "handbook.usage", "version": 1 }
|
||||
}
|
||||
```
|
||||
|
||||
The template provenance is optional. Authorized custom instructions can be saved
|
||||
without a catalog template. `null` means no instructions. Disabling retains text
|
||||
and tool access. New catalog connections persist the current default enabled;
|
||||
the initial migration backfills existing connections for the five participating
|
||||
providers. A reconnect, OAuth return, or catalog refresh retains saved settings.
|
||||
Catalog version changes do not replace custom text or opt-outs. **Reset to
|
||||
default** explicitly adopts the current template and preserves the toggle.
|
||||
|
||||
Existing connect, finish, and connection PATCH requests accept the same nullable
|
||||
setting. Mutations use connection configuration permissions and company checks,
|
||||
and write activity records. Required provider configuration lives in
|
||||
`config.methodConfig`, independently of instruction text and enabled state.
|
||||
|
||||
## UI convention
|
||||
|
||||
The editor appears only when explicit template metadata exists. Setup (including
|
||||
inline setup and OAuth) uses the current flow, without another wizard step. The
|
||||
Permissions page places **Agent instructions** after agent access and before
|
||||
Actions. It shows **Tell agents to use {provider}**, visible text, editing,
|
||||
**Reset to default** after changes, and an amber notice when off. Failed saves
|
||||
retain the draft. Agent Instructions includes a read-only **From connections**
|
||||
section with source links and an explanation of task-dependent availability.
|
||||
Disabled instructions remain inspectable there with an amber notice.
|
||||
|
||||
## Runtime delivery and compatibility
|
||||
|
||||
The gateway resolves instructions using the stored run’s company, agent, task,
|
||||
project, active responsible identity, connection grants, and effective action
|
||||
policy. At least one tool must be available (including tools requiring approval).
|
||||
The resolver uses persisted settings and the cached catalog; it does not contact
|
||||
providers. It does not gate delivery on a provider name, memory category, or the
|
||||
presence of a catalog template.
|
||||
Prompt assembly checks access to candidate connections’ cached tools directly,
|
||||
using the same task restrictions and policy decisions as discovery. It does not
|
||||
enter the discovery queue, so queue saturation neither drops guidance nor replaces
|
||||
an otherwise compatible session. Revocation is still checked on every turn.
|
||||
|
||||
Eligible blocks are sorted by connection ID. Each block identifies its
|
||||
connection, selected grant, optional template provenance, and declared public
|
||||
configuration. Secret/password fields are excluded. Missing required public
|
||||
configuration withholds standing instructions. The snapshot is `{ text, digest }`,
|
||||
where `digest` is SHA-256 of the complete UTF-8 text. Declared public configuration and
|
||||
source changes therefore change the digest as well as text edits.
|
||||
|
||||
The server replaces caller-supplied runtime instruction fields before executing.
|
||||
Native Runner receives the snapshot in `runtimeContext.connectionInstructions`
|
||||
and composes it alongside agent instructions. Legacy adapters receive it through
|
||||
shared wake-prompt composition on fresh and resumed turns. Session compatibility
|
||||
includes the snapshot: changing or removing it replaces an incompatible session,
|
||||
including when live tool refresh would otherwise retain that session. Active
|
||||
turns retain the snapshot with which they started. Older native snapshots without
|
||||
this field remain valid. No AGENTS.md or shared harness home is modified.
|
||||
|
||||
Custom integration contracts:
|
||||
|
||||
- **HTTP adapter:** the JSON request contains top-level `connectionInstructions`
|
||||
(`{ text, digest }` or `null`), also available on invocation `context`.
|
||||
This server-owned field overrides `payloadTemplate`. Append its text alongside
|
||||
agent instructions before invoking a model; do not reuse a session with a
|
||||
different digest.
|
||||
- **Process adapter:** `PAPERCLIP_CONNECTION_INSTRUCTIONS_FILE` points to a private
|
||||
JSON file containing the same snapshot. Read it during the invocation and
|
||||
append `.text` to your model prompt. It is empty when no instructions apply.
|
||||
Configured or inherited paths cannot override it. The temporary file is removed
|
||||
when the process exits, including failure paths.
|
||||
|
||||
Instruction guidance grants no new permissions. Approval requirements remain
|
||||
attached to tool calls, and remembered content does not override the current task.
|
||||
Provider memory scope follows the provider’s real configuration; Paperclip does
|
||||
not claim isolation from invented agent IDs or tags.
|
||||
|
||||
## Honcho configuration
|
||||
|
||||
Honcho requires `config.methodConfig.workspaceId` for new setup. The field is
|
||||
expanded above instructions, with validation beside it. For tools declaring a
|
||||
`workspace_id` argument, the existing managed-argument mechanism projects the
|
||||
configured workspace after caller arguments and removes the field from the
|
||||
agent-visible schema. Pending approvals compare against the current projected
|
||||
arguments, so changing the workspace invalidates the old approved invocation.
|
||||
Existing connections missing a workspace retain their tools and manual workspace
|
||||
arguments, but receive no standing guidance until configured.
|
||||
|
||||
## Verification
|
||||
|
||||
`server/src/__tests__/connection-instructions.test.ts` persists a non-memory
|
||||
fixture, resolves effective access, captures a real process invocation, and checks
|
||||
shared/native prompt composition. Route tests cover permissions and OAuth draft
|
||||
preservation. Session tests cover edits/removal and incompatible resumes.
|
||||
Storybook uses production components backed by stateful mock APIs, including
|
||||
Notion with and without explicit templates. Model behavior evaluations and new
|
||||
agent/company memory scope controls are deferred.
|
||||
@@ -122,6 +122,7 @@ read/write proof separately and never describe metadata discovery as live proof.
|
||||
- [Current access defaults](#current-default-access-policy)
|
||||
- [Golden-path agent tutorial](#golden-path-agent-tutorial)
|
||||
- [Connection UX and user journeys](#connection-ux-and-user-journeys)
|
||||
- [Optional agent instructions](#optional-agent-instructions)
|
||||
- [Chat and email connector UX](./CHAT-CONNECTOR-UX.md)
|
||||
- [Production validation evidence](#step-9-align-with-production-validation)
|
||||
- [AppDefinition field reference](#appdefinition-field-reference)
|
||||
@@ -147,6 +148,59 @@ A complete connector proposal produces:
|
||||
denied/quarantined call when the method declares one, revoke, and audit
|
||||
evidence.
|
||||
|
||||
## Optional agent instructions
|
||||
|
||||
Some connections need standing guidance: tools tell an agent what it *can* do,
|
||||
while a short paragraph explains when it should use them. Memory is the first
|
||||
use case. See [Connection instructions](CONNECTION-INSTRUCTIONS.md) for the
|
||||
generic catalog metadata, saved settings, API, runtime, and integration contracts.
|
||||
**Design explorations → Connections → Agent instructions** in Storybook uses
|
||||
production components with mocked APIs.
|
||||
|
||||
When adding a template:
|
||||
|
||||
- Show the section only when the connector explicitly provides a nonempty
|
||||
instruction template. Use **Agent instructions** and the checkbox
|
||||
**Tell agents to use {provider}**, with the exact paragraph visible. No template
|
||||
means no instructions UI, including no blank editor.
|
||||
Keep setup in the existing flow and its single footer; add no required test or
|
||||
extra wizard step. Use the current shared access defaults/disclosure.
|
||||
- Use a reviewed, versioned provider-specific suggestion. Explain when to recall,
|
||||
when to save, the allowed context, and what to do if a call fails. Prefer one
|
||||
short paragraph over a full skill or a pasted tool catalog. Avoid unsupported
|
||||
promises about hooks, automatic transcript capture, or private memory.
|
||||
- Default provided instructions on for every participating connector. Let users
|
||||
edit, **Reset to default** after changes, or disable guidance while retaining
|
||||
tools. Show a compact amber notice when off. Reconnect and catalog refresh
|
||||
preserve explicit opt-outs and custom text. Omit suggestion attribution badges
|
||||
and routine lifecycle/storage helper copy.
|
||||
- Edit in an optional **Agent instructions** section of the existing **Permissions**
|
||||
page, between agent access and Actions. Use the actual connection page shell,
|
||||
credential form, and footers in review stories; include a baseline story for
|
||||
comparison rather than constructing parallel page chrome. Show recipients
|
||||
from current assignments and a read-only, source-linked copy on the agent.
|
||||
Keep action controls and tests in the existing **Permissions** screen.
|
||||
- Resolve runtime guidance with the same company, agent, task, responsible-user,
|
||||
and grant checks used for tools. Compose a revisioned block alongside agent
|
||||
instructions; never permanently edit AGENTS.md or shared harness home files.
|
||||
Verify fresh and resumed turns, removal, revoked access, and unsupported adapters.
|
||||
- Keep required provider settings, such as Honcho workspace, expanded near the
|
||||
top of setup and configuration, above instructions. Put validation beside the
|
||||
field and block completion while required values are missing. Memory stays an
|
||||
ordinary connection; do not add a default-memory chooser or agent preference.
|
||||
Keep provider binding identifiers separate from editable prose. Agent IDs,
|
||||
tags, and query filters alone are not access-control boundaries. Any promised
|
||||
isolation needs server enforcement across all relevant tool paths.
|
||||
- Treat MCP initialization instructions as external provider content. Review
|
||||
them as template source; do not silently inject arbitrary remote text or let
|
||||
a provider update overwrite an operator's instructions. Guidance never grants
|
||||
additional tool permissions.
|
||||
- Prove both delivery and behavior: deterministic assignment/resume checks, then
|
||||
a bounded save → **fresh session** recall with synthetic data through the real
|
||||
run gateway. Record exact model/template/catalog versions, tool receipts,
|
||||
costs, ingestion timeout, and cleanup. Discovery and a model's “saved” reply
|
||||
do not prove memory works. See the linked plan for the minimal matrix.
|
||||
|
||||
## Use This Document As The Checklist
|
||||
|
||||
An agent implementing a connection should be able to begin with only a provider
|
||||
@@ -158,7 +212,7 @@ The shortest valid implementation usually changes these files:
|
||||
|
||||
```text
|
||||
scripts/ingest-app-definitions.mjs # human-authored definition source
|
||||
packages/shared/src/app-definitions/<slug>.json # generated definition
|
||||
packages/shared/src/app-definitions/<slug>.json # definition with app-owned instruction template
|
||||
packages/shared/src/app-definitions.generated.ts # generated registry
|
||||
ui/public/brands/apps/<slug>.svg # official, sanitized mark
|
||||
ui/public/brands/apps/manifest.json # runtime branding paths
|
||||
@@ -648,7 +702,7 @@ controls and precise labels over explanatory paragraphs. Remove repeated
|
||||
headings, redundant access summaries, implementation details, and reassurance
|
||||
that does not help the user decide or act. Keep necessary warnings, meaningful
|
||||
consequences, and actionable errors. Put optional expert settings under a
|
||||
collapsed Advanced disclosure. Link to provider-owned administration, such as
|
||||
collapsed disclosure through **Change**. Link to provider-owned administration, such as
|
||||
AgentMail allowlists, rather than rebuilding it in Paperclip.
|
||||
|
||||
**Keep ongoing interactions in Paperclip tasks.** Connections are where users
|
||||
@@ -708,7 +762,13 @@ definitions and visible manifest entries to match exactly.
|
||||
|
||||
### Phase 5: Author the definition at the durable source
|
||||
|
||||
The checked-in provider JSON files are generated. Do not edit one and stop.
|
||||
Transport/auth fields in the checked-in provider JSON files are generated. Do
|
||||
not edit those fields and stop. Optional `agentInstructions: { id, version, text }`
|
||||
templates are authored directly in each app's JSON definition, alongside its
|
||||
supporting `docsUrl`. Ingestion validates and preserves that field; edit or
|
||||
remove it there, and increment its version when changing the default. There is
|
||||
no central instruction-template registry. See
|
||||
[Connection instructions](./CONNECTION-INSTRUCTIONS.md) for the runtime contract.
|
||||
|
||||
1. Add or update the provider in `scripts/ingest-app-definitions.mjs`.
|
||||
2. Update `packages/shared/src/self-serve-mcp-research.json` when it belongs to
|
||||
@@ -1511,11 +1571,16 @@ The operator should see Apps, Connections, and Review language. Keep protocol la
|
||||
Request only the documented scope set the reviewed connection needs; never
|
||||
adopt every scope returned by discovery. Operators should not have to predict
|
||||
every future tool during setup. Keep the default view to the minimum inputs
|
||||
needed for a working connection, fold optional expert controls under one
|
||||
collapsed **Advanced** disclosure, and enforce execution afterward through
|
||||
needed for a working connection, fold optional expert controls under the
|
||||
**Change** link beside the access summary, and enforce execution afterward through
|
||||
Paperclip's resource boundaries, risk classification, tier defaults, optional
|
||||
quarantine, and audit.
|
||||
|
||||
When a provider supplies its MCP endpoint, keep that URL out of the initial
|
||||
form. Composio exposes it through **Reuse an existing session** for operators
|
||||
with a custom session URL; resumed custom endpoints and URL validation errors
|
||||
keep the field visible. Preserve the endpoint when its disclosure is closed.
|
||||
|
||||
### Step 8: Apply Governance Defaults
|
||||
|
||||
Governance is automatic because every catalog entry becomes a normal tool-access object:
|
||||
@@ -2326,3 +2391,147 @@ in-memory provider responses. The `provider-native`, `provider-decline`, and
|
||||
`provider-second` Product E2E cases exercise native preference, persisted choice,
|
||||
restart recovery, and an independently observed gateway read. They do not prove
|
||||
compatibility with the real external providers.
|
||||
|
||||
### Public aggregator apps in Connectors
|
||||
|
||||
The Connectors catalog combines native definitions with the public Composio and
|
||||
Arcade app snapshots in `packages/shared/src/aggregator-app-catalog.ts`. Native
|
||||
definitions always take precedence, including definitions temporarily hidden or
|
||||
unavailable on the instance. Each remaining app has one card with its provider
|
||||
logos; Connect opens a provider picker when multiple providers support the app.
|
||||
An app with one provider goes directly to setup or its saved gateway flow.
|
||||
Search covers names, aliases, providers,
|
||||
and saved accounts. The uninstalled catalog uses pages of 50 entries; matching
|
||||
installed connectors remain above every page. Listing an upstream app never
|
||||
creates a Paperclip connection or marks that app authorized.
|
||||
Provider filters include native cards only when they contain accounts managed
|
||||
by that provider. A matching public catalog entry alone does not include a
|
||||
native card; its native onboarding remains in Paperclip and All.
|
||||
|
||||
Composio app setup offers a dropdown of connected Composio accounts and an option
|
||||
to connect a new one. The board calls `COMPOSIO_MANAGE_CONNECTIONS` directly to
|
||||
check the requested toolkit and generate its hosted authorization link. This
|
||||
creates no task and starts no agent run. An already active app skips new link
|
||||
creation. After the human signs in, completion checks the toolkit's ACTIVE
|
||||
account status again. New gateways default to all humans and all agents; app
|
||||
setup preserves the selected gateway's saved access and tool policies without
|
||||
adding agent-specific grants. Access remains gateway-wide, not app-level
|
||||
isolation. Saved credentials are resolved for the acting human and authorization
|
||||
URLs stay outside audit/result storage. A failed or uncertain add is never
|
||||
retried automatically; the user can explicitly request a new link.
|
||||
|
||||
Composio is authoritative for app accounts and authorization. Paperclip stores
|
||||
only account observations in `tool_connection_app_snapshots`: IDs, aliases,
|
||||
statuses, default flags, check times, and failure times. Observations are scoped
|
||||
to company, saved gateway, viewing human, and credential identity, including
|
||||
secret versions. Credential replacement or rotation hides the old inventory.
|
||||
Discovery and app setup never create agent grants or change saved policies.
|
||||
|
||||
Apps starts a background check of the entire supported public Composio catalog,
|
||||
independent of search and pagination. `POST /tool-connections/:id/composio/apps/sync`
|
||||
returns cached observations and sync progress; `{force:true}` requests a refresh.
|
||||
`tool_connection_app_syncs` holds a per-credential lease and progress. Repeated
|
||||
visits and concurrent tabs share the lease. Checks use explicit `action:list`,
|
||||
batches of 32, a four-minute deadline, and five-minute freshness. Known accounts
|
||||
are checked first. Polling updates account rows while browsing stays usable.
|
||||
Manual **Refresh Composio** belongs in the saved Composio account's kebab menu;
|
||||
it checks that account only. Do not add a catalog-wide refresh button.
|
||||
Composio gateway setup finishes on its Permissions page. That page lists detected
|
||||
apps in a bounded, scrollable list and starts a refresh on first load. Its
|
||||
**Refresh Composio** button shows catalog-check progress and refreshes only that
|
||||
gateway. The same connected-app list and refresh controls serve Arcade and
|
||||
Executor gateways when discovery is available. Executor remains available as a
|
||||
connection, but its catalog filter chip is temporarily hidden.
|
||||
A complete empty list removes an observed account; EXPIRED accounts show Needs
|
||||
sign-in. Failed or incomplete evidence retains the last accounts, visibly
|
||||
unverified, and offers retry. Interrupted jobs can be restarted after the lease
|
||||
expires. A late response cannot replace a newer check or a changed credential.
|
||||
|
||||
The saved Connect MCP OAuth credential supports listing accounts by toolkit;
|
||||
it is not a consumer user API key or a developer project API key. This discovery
|
||||
covers the supported public catalog, including toolkit variants. It does not
|
||||
claim complete enumeration of custom apps outside that catalog. A future
|
||||
identity-bound inventory endpoint should replace this scan when available.
|
||||
Never forward an MCP OAuth token to an unrelated REST inventory endpoint.
|
||||
|
||||
Imported accounts appear above the paginated catalog with their actual source:
|
||||
**Via “saved connection name”**, rather than attributing upstream authorization
|
||||
to the gateway creator. Already connected Composio accounts remain visible even
|
||||
when Paperclip has a native connector; native Connect offers still take
|
||||
precedence. Imported account menus offer only **Open in Composio**. Access
|
||||
controls remain on the saved gateway and apply to all apps on that connection.
|
||||
The Manage dialog displays provider observations
|
||||
and links to Composio; it does not offer local rename or upstream removal.
|
||||
Use `https://dashboard.composio.dev/~/org/connect/apps` for Connect app management.
|
||||
The organization placeholder resolves to the signed-in user's For You area.
|
||||
The bare dashboard opens Platform developer projects, which have a separate
|
||||
account inventory and are the wrong destination for saved Connect MCP accounts.
|
||||
Remove connection remains available on the saved gateway itself. Its confirmation
|
||||
states that removing it from Paperclip does not delete accounts in Composio.
|
||||
|
||||
Targeted Composio app setup URLs reuse the saved-account chooser when an active
|
||||
gateway exists. Explicit `new=1`, resume, and reconnect keep their own gateway
|
||||
setup flow. Existing ACTIVE app accounts are checked without creating another
|
||||
hosted link or a task. New app authorization remains a direct hosted sign-in
|
||||
handoff, followed by a fresh account check.
|
||||
|
||||
Arcade still offers an agent task draft to verify the requested app and obtain
|
||||
any provider authorization link. It requires the app's tools in the selected
|
||||
gateway, a catalog refresh, and any tool authorization. New gateway setup retains
|
||||
the app name through `targetToolkit`; it creates only the provider gateway.
|
||||
Composio then returns to direct app setup; Arcade opens the task draft.
|
||||
Setup entry points preselect an assignable agent: prefer an agent named
|
||||
**Default agent**, then organizational rank and leadership roles, then creation
|
||||
date. If there is no named default, use the same rank and age ordering on the
|
||||
remaining agents. The user can change the selection before continuing.
|
||||
The board's Create Task action creates a new task even when an open task has the
|
||||
same title. A request key protects retries of the same submitted draft, and the
|
||||
success confirmation links to the created task.
|
||||
Connection setup drafts opt into the dialog's `navigateOnCreate` option, taking
|
||||
the operator to the created task after submission. Other callers stay on their
|
||||
current page by default; the option does not add a visible control.
|
||||
The task asks the assigned agent to perform setup directly without hiring or
|
||||
delegating, and to provide a browser link and wait when human authorization is
|
||||
needed. Before provider calls, it checks access for the task's current assignee.
|
||||
Missing access uses `connection_request` with the saved `connectionId` and exact
|
||||
indexed `toolNames`, producing an embedded card with the requesting agent's
|
||||
avatar and a **Grant access** action. The addressed human connection manager
|
||||
approves the frozen tool set; acceptance adds agent-scoped access and resumes
|
||||
the task. Composio setup asks for Search Tools as Allowed and Manage Connections
|
||||
as Ask first. Writes and unknown-risk tools retain per-call approval. Changed or
|
||||
quarantined tools require a new request. Existing policies, identity selection,
|
||||
and other agents' access are preserved. The task never grants itself permissions
|
||||
or requests access for every agent.
|
||||
The connection's Permissions page lists additional agent access separately
|
||||
and lets the connection manager remove it without changing the default action
|
||||
permissions. Removing that grant also disables its Ask-first tools.
|
||||
A successful agent run or a local gateway fixture does not prove that
|
||||
the underlying app is authorized; verify against the real provider gateway.
|
||||
OAuth recovery exposes a validated native sign-in link on both page and dialog
|
||||
hosts. A blocked window or navigation retains the existing connection and OAuth
|
||||
session so the link can continue sign-in without creating another gateway.
|
||||
|
||||
Composio reuses `composio-search-catalog.json`. Arcade's snapshot records its
|
||||
official logo and evidence URLs, excluding hidden and coming-soon entries. To
|
||||
review and refresh only Arcade's claims, run:
|
||||
|
||||
```sh
|
||||
node scripts/update-arcade-app-catalog.mjs --verified-at YYYY-MM-DD
|
||||
```
|
||||
|
||||
Use the date of the public catalog review. The script preserves the prior file
|
||||
if the source structure or entries fail validation. `--input <saved-html>` allows
|
||||
reproducing the snapshot from the same official catalog response. Neither public
|
||||
snapshot proves a user's gateway configuration or app authorization.
|
||||
|
||||
### Managed aggregator account inventory
|
||||
|
||||
Composio, Arcade, and Executor observations share the existing app snapshot/sync tables and the manager-only `/api/tool-connections/:connectionId/aggregator/apps` list, `/sync`, and `/refresh` endpoints. Legacy Composio endpoints remain supported. These rows are observations, never executable connections or authorization grants. Company, gateway, viewing human, and effective credential version define the cache; fully successful enumeration reconciles removal, while partial failure retains stale observations.
|
||||
|
||||
Arcade discovery paginates its [admin account list](https://github.com/ArcadeAI/arcade-go/blob/main/adminuserconnection.go) and [tool requirements](https://github.com/ArcadeAI/arcade-go/blob/main/tool.go), filters to the configured user and exposed gateway tools, and stores only allowlisted account metadata. An existing project key plus `Arcade-User-ID` can be reused. Otherwise optional manager-only sync setup stores a separate user-owned vault key for discovery; it is excluded from gateway invocation credentials and tool access grants. OAuth gateway tokens are not substituted for project keys.
|
||||
|
||||
Executor reads its [MCP integration inventory](https://github.com/UsefulSoftwareCo/executor/blob/main/packages/hosts/mcp/src/tool-server.ts) or uses fixed, read-only `connections.list` and `integrations.list` calls in code mode. Its pure `connections.createHandoff` URL builder can supply the upstream console destination. No arbitrary generated code or provider REST endpoint is used. Unknown integrations remain separate, without guessed branding. Last-health `healthy` is connected, `expired` needs sign-in, and unverified or degraded health never earns a green check. Missing inventory support is visible as discovery unavailable. Managers can set a trusted HTTPS console URL for gateways that do not expose a destination, preserving workspace and self-hosted paths.
|
||||
|
||||
Apps groups native and imported accounts under canonical app cards, preserving all upstream account identities. Native onboarding wins over aggregator connect offers. Imported account menus only open their upstream provider; deletion and per-app authorization stay upstream. Parent gateway menus own refresh and optional discovery setup. Paperclip policies continue to govern invocation through the gateway.
|
||||
|
||||
Acceptance fixtures: `server/src/__tests__/aggregator-app-discovery.test.ts`, `server/src/__tests__/aggregator-app-sync.test.ts`, `ui/src/pages/apps/Browse.test.tsx`, and the production-page/full-stack test drive at `tests/aggregator-accounts/test-drive.ts`. The expected stories and evidence are recorded in `doc/plans/2026-10-05-managed-aggregator-account-user-stories.md`. Fixture runs do not replace live provider proof when working credentials are available.
|
||||
@@ -8,7 +8,9 @@ rotate credentials without re-enabling the toggle.
|
||||
Generic custom MCP connections retain their existing behavior.
|
||||
|
||||
These are ordinary company-scoped tool connections. There is no separate memory
|
||||
page, automatic conversation upload, prompt injection, or background memory sync.
|
||||
page, automatic conversation upload, or background memory sync. Reviewed defaults
|
||||
use the generic [connection instructions](CONNECTION-INSTRUCTIONS.md) capability
|
||||
to explain when to recall and save; operators can edit or disable them.
|
||||
Agents explicitly invoke the provider's tools through the existing gateway, and
|
||||
operators manage access and actions on the regular Permissions screen.
|
||||
|
||||
@@ -22,7 +24,7 @@ Official documentation and public endpoint discovery checked September 24, 2026.
|
||||
| [Zep](https://help.getzep.com/memory-mcp-server) | Remote MCP, `https://api.getzep.com/mcp`, OAuth | Configure Memory MCP and the identity provider in Zep first. A project administrator assigns MCP seats and shared graphs. Sign in with the assigned work identity. An ordinary Zep API key is not the credential for this endpoint. |
|
||||
| [Supermemory](https://supermemory.ai/docs/supermemory-mcp/mcp) | Remote MCP, `https://mcp.supermemory.ai/mcp`, OAuth | Sign in and select the workspace, read/write access, and optional container tags offered by Supermemory. Developer API keys are separate from hosted MCP sign-in. |
|
||||
| [Cognee](https://docs.cognee.ai/cognee-cloud/connections/cloud-mcp) | Bundled Cloud API bridge, Cloud API key | Copy the tenant API Base URL and key from Cognee Cloud → API Keys. Requires an active Cloud workspace. The bundled bridge works in public deployments without a local MCP runtime host. |
|
||||
| [Honcho](https://honcho.dev/docs/v3/guides/integrations/mcp) | Remote MCP, `https://mcp.honcho.dev`, Bearer API key | Create an organization and API key in the Honcho dashboard. Workspace, peer, and session selectors remain explicit provider tool arguments. |
|
||||
| [Honcho](https://honcho.dev/docs/v3/guides/integrations/mcp) | Remote MCP, `https://mcp.honcho.dev`, Bearer API key | Create an organization and API key in the Honcho dashboard. Configure the required workspace in Paperclip; it is projected into workspace-scoped tool arguments. Peer and session selectors follow provider/task context. |
|
||||
|
||||
Zep and Supermemory use user grants, the existing PKCE OAuth broker, and automatic
|
||||
client registration/discovery. Zep advertises its authorization server at
|
||||
|
||||
@@ -1105,8 +1105,7 @@
|
||||
"https://honcho.dev/docs/v3/guides/integrations/mcp"
|
||||
],
|
||||
"reviewedAt": "2026-09-30",
|
||||
"liveProof": "not-run",
|
||||
"keyPermissions": "Create peers/sessions and save memory under the API key project. Create a key with read and write permissions for these actions; Paperclip cannot increase an existing key\u2019s permissions."
|
||||
"liveProof": "not-run"
|
||||
},
|
||||
{
|
||||
"app": "hugging-face",
|
||||
|
||||
+21
-1
@@ -3,6 +3,14 @@
|
||||
Paperclip has two live eval families with different questions, owners, and
|
||||
evidence. Choose the family before selecting a model, profile, or case.
|
||||
|
||||
The explicit-only [native instruction consolidation comparison](plans/2026-10-03-native-completion-consolidation.md)
|
||||
uses six Product E2E cells per source variant. It measures the completion
|
||||
constraint reduction separately from the earlier native tool-description
|
||||
trial. Provider-free start/resume payload capture is a byte measurement;
|
||||
behavioral qualification requires the original paired live outcomes and
|
||||
retained content. Neither source admission nor a scripted pass proves model
|
||||
behavior.
|
||||
|
||||
- **Runner Evals:** real Runner/provider behavior against a seeded mock control
|
||||
plane. Definitions live in `paperclip-evals/evals/paperclip-runner`; see the
|
||||
[direct live protocol evals](../packages/paperclip-runner/docs/runner-protocol-live-evals.md).
|
||||
@@ -81,7 +89,9 @@ The explicit-only [production hiring templates suite](../tests/runner-e2e/README
|
||||
adds two local native Codex/Claude cells. It exercises API-created production
|
||||
CEO defaults, an explicitly requested hiring skill/reference read, a permanent
|
||||
coder hire, independently computed saved JSON fixtures and worker reuse.
|
||||
Each cell expects five turns. Source/read coverage and workflow outcome are
|
||||
Each cell requires five work turns and admits at most two strictly attributed
|
||||
server task-completion turns. Every actual run remains counted; unknown or
|
||||
extra-work turns fail. Source/read coverage and workflow outcome are
|
||||
separate: missing read provenance leaves the candidate/baseline pair
|
||||
uncomparable even if work succeeds. Baseline bundles and coder examples derive
|
||||
from their own source revision, without requiring candidate wording or length.
|
||||
@@ -93,6 +103,14 @@ execution ID because `--all` excludes explicit-only suites. Each cell applies a
|
||||
1,000-cent company and agent budget hard stop before task creation and records
|
||||
both limits in its evidence.
|
||||
|
||||
The explicit-only [stock-harness suite](../tests/runner-e2e/STOCK-HARNESS.md)
|
||||
reuses skill, ordered-continuation, and chat-restart journeys across eight local
|
||||
legacy/native profiles with production-default hires. It closes the custom QA
|
||||
manual coverage gap. Its required credential-free prerequisite maps vendor
|
||||
instruction layering, the tiny hire bundle, and shared startup/resume reductions
|
||||
to executable checks. The 24 live cells are configured; no live qualification is
|
||||
claimed from their setup or unit calibration.
|
||||
|
||||
The explicit-only `agent-chat-stories` suite covers the experimental settings
|
||||
lifecycle for a configured native agent and follow-ups during active work. Its
|
||||
fixture-driven file wait and persisted-plan oracle are documented in the
|
||||
@@ -386,3 +404,5 @@ The explicit-only Product E2E `confirmation-replies` suite tests conversational
|
||||
approval and rejection, persisted message provenance, approval before execution,
|
||||
ambiguous proposals, and the existing card-click path with native Claude/Codex.
|
||||
See the [suite contract](../tests/runner-e2e/README.md#conversational-confirmation-replies-explicit-only).
|
||||
|
||||
Hiring notification accounting now also requires exact completed action attribution. Missing native/provider ID mapping is uncomparable evidence; it must not be reported as a model task regression or waived through name/order matching. The fixture waits for both known completion callbacks and settled bracketed observations, including the gap before pending outbox work becomes a wake. Strict action replay and original machine verdicts are retained separately.
|
||||
@@ -452,6 +452,23 @@ Workspace incoherence feeds into the same non-terminal liveness and stranded ass
|
||||
|
||||
For runtime-created `git_worktree` execution workspaces, branch coherence is part of workspace coherence. The persisted execution workspace branch is the recorded branch for future dispatch. Reusing that workspace must verify that the worktree is still registered and that `HEAD` is on the recorded branch. Successful run finalization must perform the same check before recording `workspace_finalize=succeeded`. If the run switched to a publishing/PR branch without updating the execution workspace record, finalization may auto-restore the recorded branch only when the worktree is clean, still registered, and the recorded branch points at the current `HEAD`; the repair is recorded as a workspace operation before the successful finalize row. If that safe repair cannot be proven, finalization records a failed workspace finalize and the run fails with bounded evidence for the expected and actual branch. A branch change is sanctioned when a control-plane path updates the execution workspace record before finalization, when publishing work happens in a separate worktree and the managed issue worktree remains on its recorded branch, or when the finalizer performs this clean same-commit restoration.
|
||||
|
||||
Sandbox Git restore uses the host branch and commit captured before staging.
|
||||
If that identity is unchanged, a rebased or amended sandbox history with shared
|
||||
ancestry replaces the starting tip instead of being merged with it. The ref
|
||||
update checks the expected old commit; a concurrent change retries through the
|
||||
normal history integration path. Git holds the HEAD and applicable branch locks
|
||||
while restore verifies the attached/detached branch identity and commits the ref
|
||||
transaction. A checkout during integration cannot redirect that write.
|
||||
The directory merge still preserves host-only
|
||||
file changes under its existing rules. A changed host branch requires recovery.
|
||||
An intentional reset to an ancestor exports a full Git bundle so restore keeps
|
||||
the actual sandbox tip; an empty delta is reserved for an unchanged tip.
|
||||
Unrelated sandbox history keeps the existing history-preserving graft only when
|
||||
the recorded host has not advanced; it must not replace concurrent host work.
|
||||
Warm sandbox reuse must match the current host Git tip and branch as well as the
|
||||
file snapshot and saved stamp, including managed nested repositories. A history
|
||||
or branch mismatch restages the host before the next run begins.
|
||||
|
||||
### Workspace scan failures before provider startup
|
||||
|
||||
Repository discovery distinguishes an ordinary folder from a failed Git read.
|
||||
@@ -484,9 +501,15 @@ An explicit recovery action is a typed liveness repair path for a source issue.
|
||||
|
||||
A terminal native failure can retain a result accepted before checkpoint or cleanup failed. That result is historical evidence, not a live controller. New user input may start a fresh turn after the controller and execution environment have stopped and ordinary admission checks pass. Preserve the failed run, its result, and its recovery budget. Do not commit the old result, infer action outcomes, or replay the failed turn. A message saved while cleanup is pending must be reconsidered after verified cleanup and delivered once. Admission must consume its deferred receipt in the same transaction that creates the new run, including in agent chat, where later messages keep their separate turns. Completing that run must not promote the consumed receipt again. Workspace-export repair retains its separate saved-result recovery path.
|
||||
|
||||
When a legacy turn admitted by an explicit user message fails or times out, a bounded transient retry needs its own durable continuation authorization. Initial admission binds the message's exact body digest and revision. Scheduling requires that original binding and revalidates the human author, source task, assignee, and execution ownership, then records the successor's receipt in the same transaction as its run. Historical receipts without a message binding cannot authorize automatic retries and are not backfilled. The parent receipt remains intact. Dispatch checks the exact successor, retry parent, and unchanged message binding again. Deleted or edited input, changed actors or ownership, superseded work, and duplicate successors cannot renew authority. One-run Retry and Interrupt intents retain their separate admission contracts; they are not renewable user-message authority. Other retry policies cannot borrow this receipt: their scheduling fails closed. Successful explicit turns do not create a prose-only missing-comment follow-up with spent authority.
|
||||
|
||||
Explicit retry admission checks the local parent adapter controller and verified environment cleanup. The existing queue-first release policy may retain only its own terminal execution claim while that cleanup is pending; finalization and the periodic stale-lock sweep reconsider the same policy after cleanup. Generic terminal-lock cleanup and stale checkout adoption leave these claims for that settlement. New user input, reassignment, and a successor's claim take precedence. Exhausted retries and revoked or missing authorization release the terminal claim without requesting another retry. Pending or failed environment cleanup cannot mint a successor receipt.
|
||||
|
||||
A new user message can continue a terminal native run whose process fields were cleared before local stop receipts existed. Admission must verify the exact run, runner, workspace, and provider session in the retained suspended state, with no active provider turn, pending tool call, or undelivered output. Missing or mismatched state keeps the hold. A later recorded process launch also keeps the hold until its stop is verified. Normal assignment, decision, controller, environment cleanup, and active-run gates still apply. The message starts one fresh conversation turn; it does not replay the failed run, reset its recovery budget, or certify unknown action outcomes.
|
||||
|
||||
The task thread exposes the existing guarded Retry action for failed or timed-out legacy conversation runs. Where the server supports an explicit new attempt after a stopped legacy conversation, the thread must not hide that action solely because the old run still has a recovery-needed projection. Native and process recovery holds, pending decisions, active execution, and other retry gates remain in force. When a gate hides Retry, the thread says the message is preserved instead of promising an unavailable action. This presentation change does not rewrite historical outcomes or certify prior actions.
|
||||
The task thread exposes the guarded Retry action for failed or timed-out conversation runs and native preparation cancelled before provider startup when the never-started proof is verified. The server projects this eligibility on the recovery notice and rechecks it on Retry. Cleanup quarantine retains its inspection path, and non-conversation reconciliation gates remain enforced. Pending decisions, active execution, pause, budget, dependency, and ownership gates remain in force. A refused Retry reports its reason inline and remains available for another attempt.
|
||||
|
||||
A continuation cancelled by the queued-run gate while waiting for review did not start a provider. Its matching dispatch-gate receipt and unclaimed execution fields keep this deliberate wait out of unknown-action reconciliation. Pending review and decision gates still apply. If an older server already created a recovery hold for this exact state, Retry or a new user message can request one fresh turn after the server verifies there is no provider, coordinator, or unfinished cleanup. Saved user input is reconsidered through the same admission path; the cancelled turn is not replayed.
|
||||
|
||||
A valid recovery action must name:
|
||||
|
||||
@@ -666,6 +689,8 @@ The same bounded rule applies when the previous heartbeat reported waiting on a
|
||||
|
||||
A continuation that the staleness gate cancelled with `issue_continuation_waiting_on_review` is a *deliberate park*, not a disappeared execution path. The latest run reported that the issue is waiting for review/approval (for example, an umbrella issue whose work was just decomposed into sub-tasks). Treating that park as a stranded run would retry it, then escalate it to `blocked` with a recovery action and an operator-facing failure notice — even though nothing failed and there is nothing for a human to do.
|
||||
|
||||
Execution admission reads a narrow server-owned cancellation-evidence projection. Ordinary run presentation can redact `resultJson` for database encoding or output size; that presentation projection must not decide Retry eligibility or saved-input recovery. The admission projection excludes provider diagnostics and preserves whether the stored result is absent.
|
||||
|
||||
Recovery rule for a parked-for-review continuation:
|
||||
|
||||
- if the issue has a real waiting target — open (non-terminal) sub-tasks or existing unresolved blockers — Paperclip converts the deliberate wait into a first-class dependency wait: it sets the issue `blocked` by those issues, keeps the original assignee, and posts a plain-language comment explaining that the task will resume automatically when its dependencies finish. The issue then self-resumes through the normal `issue_blockers_resolved` path; no recovery action or escalation owner is involved
|
||||
@@ -1003,7 +1028,7 @@ For native conversations, an authenticated user message sent after the previous
|
||||
|
||||
Local recovery records a server-authored stop receipt before it clears a verified absent process identity. A new execution request invalidates that receipt before any process can spawn; recording a new process identity also invalidates it. Missing process IDs without a receipt still block admission. Remote execution continues to require termination receipts for every lease.
|
||||
|
||||
If cleanup or another execution gate is still pending, the message stays in its existing queue receipt. Startup and periodic scheduling reconsider up to 50 due receipts per pass, at most once per 30 seconds per receipt, without calling a model or resetting recovery attempts. Cleanup callbacks use the same admission path. The issue lock prevents concurrent workers from delivering an adopted or discarded receipt again. The queued-message area shows the current wait reason. Pauses, approvals, budgets, ownership, and external chat authorization remain enforced. A message sent before the run finished does not grant new post-stop authority.
|
||||
If cleanup or another execution gate is still pending, the message stays in its existing queue receipt. Startup and periodic scheduling reconsider up to 50 due receipts per pass, at most once per 30 seconds per receipt, without calling a model or resetting recovery attempts. Cleanup callbacks use the same admission path. The issue lock prevents concurrent workers from delivering an adopted or discarded receipt again. The queued-message area shows the current wait reason. Pauses, approvals, budgets, ownership, and external chat authorization remain enforced. For a failed native conversation, saved authenticated messages that have never been delivered can request a fresh turn after verified stop even when submitted before the failure finished. Admission validates and adopts their exact queue receipt, order, and original authors. This does not replay the failed request or undo an operator cancellation.
|
||||
|
||||
Historical legacy interruption holds for conversational adapters no longer block new messages or Resume. Automatic classification uses the server-owned adapter identity saved atomically at run claim, the saved adapter invocation, or the continuation policy, never the agent’s current adapter settings. Missing historical adapter evidence retains the automatic hold; an explicit user continuation can retire it after proving the predecessor stopped. A terminal row with a live predecessor process, an unreleased environment lease, or failed/pending cleanup still blocks actual admission and Resume; a release timestamp alone does not prove cleanup succeeded. Retry scheduling can happen before cleanup, but grants no execution authority. Recovery folds their obsolete no-replay bookkeeping without changing task ownership, status, or automatically waking old work. The audit trail remains readable. Native integrity and ownership holds, and non-conversational adapter holds, remain enforced.
|
||||
|
||||
@@ -1045,7 +1070,7 @@ unknown, and they do not gain automatic replay eligibility.
|
||||
Admission validates the persisted comment's author, task, and time against every
|
||||
held predecessor. Retry validates the selected failed run's company, task, and
|
||||
agent and preserves that run's identity through admission and history loading.
|
||||
Duplicate Retry requests adopt the same successor. An agent-authored comment, an old queued request, or a generic
|
||||
Duplicate Retry requests adopt the same successor. Only a validated, unconsumed human queue receipt can authorize saved-message continuation after a native failure; arbitrary historical input, an agent-authored comment, or a generic
|
||||
system wake cannot release a hold. The source task keeps its assignee. Process
|
||||
ownership, active controllers, cleanup leases, pause, approval, budget, and normal
|
||||
execution gates still apply. Dependency-blocked interaction mode remains limited
|
||||
@@ -1156,6 +1181,15 @@ controller, lease, or result). It also checks for contradictory launch/process
|
||||
evidence and verifies local cleanup or exact remote termination receipts. The
|
||||
preparer must have finished or its startup lease must have expired. A missing
|
||||
PID alone does not establish this proof.
|
||||
For older interrupted preparation rows without a cancellation receipt, or with
|
||||
a retained startup fence but no preparation-settled marker, the
|
||||
immutable Paperclip Runner adapter claim, unresolved runtime, and preparing stage
|
||||
must agree. The old controller must belong to another server boot and its lease
|
||||
must have expired. Only absent results or the retained before-selection
|
||||
cancellation fence qualify. No native identity, coordinator, adapter invocation,
|
||||
provider event, or process-launch evidence may exist. Environment cleanup still
|
||||
requires the same receipts. This historical proof permits explicit Retry or a
|
||||
newer saved user message; it does not replay the cancelled input.
|
||||
|
||||
The existing bounded saved-message worker rechecks this proof after restart.
|
||||
Admission atomically settles an unclaimed coordinator and admits one fresh turn,
|
||||
@@ -1322,8 +1356,22 @@ execution recovery holds still apply. Unconfirmed cleanup does not start work.
|
||||
|
||||
The active session advertises steering only when its driver supports it. A
|
||||
transport method that rejects steering does not grant that capability. The
|
||||
queued-message control remains mounted until the server accepts a steer request,
|
||||
so a rejected last-row action keeps its message and visible error.
|
||||
queued-message controller remains mounted across an empty queue, preserving
|
||||
pending delivery and inline errors. Steer immediately moves the selected message
|
||||
into the conversation; Interrupt immediately moves all submitted queued messages.
|
||||
Stale queue polls do not restore those rows while delivery is pending or accepted.
|
||||
Rejected delivery restores the latest queue and shows an inline error without a
|
||||
toast. The provider acknowledgement remains the authoritative delivery receipt;
|
||||
optimistic display never grants permission or records successful delivery. A preparing
|
||||
Paperclip Runner retains the Steer label while steering is unavailable; the
|
||||
initial legacy database default and an empty queue snapshot cannot select
|
||||
Interrupt. Model and effort changes apply to the next turn and do not change
|
||||
the active turn's steering target. While awaiting steering acknowledgement, the
|
||||
queue route holds the task and queue locks but leaves the run row available for
|
||||
PRP event persistence. It locks and rereads the run after acknowledgement before
|
||||
merging the receipt, preserving concurrent provider updates.
|
||||
If the run stops during that wait, the request fails with a stale-target error
|
||||
and retains the queued input for continuation.
|
||||
|
||||
### Preserve work across handoff and deliver requested files
|
||||
|
||||
|
||||
@@ -504,6 +504,33 @@ or `restore_failed`. Unknown values are omitted. Workspace paths and arbitrary
|
||||
pre-restore result data are not included. A later successful run does not, by
|
||||
itself, establish that an earlier failed restore recovered the workspace files.
|
||||
|
||||
When available, the saved `workspaceRestoreDiagnostic` adds the bounded fields
|
||||
`workspaceRestorePhase`, `workspaceRestoreStep`, `workspaceRestoreErrorCode`,
|
||||
`workspaceRestoreHttpStatus`, and `workspaceRestoreExitCode` to `run_execution`.
|
||||
The phase is `workspace` or `asset`. The failing step is one of `git_export`,
|
||||
`git_import`, `workspace_transfer`, `workspace_extract`, `directory_merge`,
|
||||
`git_integration`, `index_reset`, `git_ref_cleanup`, or `asset_restore`.
|
||||
Nested steps retain the most specific failing operation. Error codes come from
|
||||
the restore diagnostic allowlist, with unrecognized codes reported as `unknown`;
|
||||
HTTP statuses are integers from 400 through 599 and process exit codes are
|
||||
integers from 1 through 255. These fields accompany a known restore failure code
|
||||
only. They omit error messages, raw command lines, paths, process output, and arbitrary
|
||||
cause data. Git error wrappers preserve only these safe codes and numbers for
|
||||
diagnostics, without adding the original error as a cause.
|
||||
For `git_integration`, optional `workspaceRestoreGitCommand` identifies the fixed
|
||||
command family: `rev_parse`, `symbolic_ref`, `merge_base`, `merge_tree`,
|
||||
`commit_tree`, `update_ref`, or `log`. `workspaceRestoreGitFailureKind` is
|
||||
`merge_conflict`, `invalid_object`, `ref_conflict`, `permission_denied`, or
|
||||
`unknown`. A merge conflict requires an uninterrupted `merge-tree --write-tree`
|
||||
exit of 1 with a completed tree ID in stdout; exit 1 alone is ambiguous.
|
||||
Object and ref classifications require recognized Git diagnostics;
|
||||
permission denial requires an OS `EACCES` or `EPERM` code. Unrecognized or
|
||||
localized messages remain `unknown`. Up to 16 KiB of stderr is inspected only
|
||||
in memory; no arguments, stderr, paths, repository URLs, filenames, or ref names
|
||||
enter these fields. Handled probes and successful retries emit no diagnostic.
|
||||
This does not change
|
||||
restore behavior, retries, timeouts, or recovery policy.
|
||||
|
||||
A caught directory-merge lock timeout also records `restoreLockOwnerState`
|
||||
(`alive`, `dead`, `unknown`, `missing`, or `invalid`), `restoreLockKnownLocalHolder`,
|
||||
and, when available, `restoreLockOwnerSameProcess`, `restoreLockOwnerPredatesProcess`,
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
# Composio connections and permissions in Paperclip
|
||||
|
||||
October 2, 2026 · Current behavior and proposed product model
|
||||
|
||||
**Composio should be the source of truth for its app accounts. Paperclip should show those accounts and control who can use the saved Composio connection.** Connecting Circleback in Composio should not require connecting it again in Paperclip when the saved gateway can already reach that account.
|
||||
|
||||
The current integration supports direct app authorization and checking known accounts, but discovery is incomplete. Its Circleback cards also look like independent Paperclip connections even though their permissions belong to the entire Composio gateway. This document explains that distinction and proposes a clearer experience. Recommendations below are proposals; this document does not change product behavior.
|
||||
|
||||
## What the saved connection actually represents
|
||||
|
||||
The default integration uses Composio Connect at `https://connect.composio.dev/mcp`. Composio describes this as one MCP connection that discovers and executes tools across apps through orchestration tools. It is different from an application creating its own Composio SDK session. [Composio Connect documentation](https://docs.composio.dev/docs/composio-connect)
|
||||
|
||||
There are three objects to distinguish:
|
||||
|
||||
| Object | Meaning |
|
||||
| --- | --- |
|
||||
| Saved Composio connection in Paperclip | An endpoint, credentials, access rules, and tool catalog. We have called this a gateway. Multiple saved Composio connections are possible. |
|
||||
| Connected app account in Composio | A particular authorized Circleback, Spotify, or other app account. |
|
||||
| App row in Paperclip | A cached observation of an account available through a saved gateway, plus a convenient setup or management entry point. |
|
||||
|
||||
**An observed Circleback row does not create another credential or independent connection in Paperclip.** The public app catalog is another separate thing: it says Composio supports Circleback, without proving that this gateway has an authorized Circleback account.
|
||||
|
||||
Composio accounts are scoped. Projects separate resources, and connected accounts belong to an upstream user identity. Dashboard-created accounts use a Composio dashboard user identity, which is different from an application's `user_id`. Matching a person's email or using the same Composio brand is insufficient to establish that two integrations reach the same accounts. [Project isolation](https://docs.composio.dev/reference/api-reference/projects), [Connected account identities](https://docs.composio.dev/reference/api-reference/connected-accounts)
|
||||
|
||||
For our existing Connect gateway, the practical question is: **Does listing Circleback through this exact saved credential return the account?** That check is stronger evidence than seeing Circleback somewhere in the Composio dashboard. The current integration has not established a general mapping from Connect OAuth identity to every dashboard project.
|
||||
|
||||
## What happens to tools
|
||||
|
||||
Paperclip does expose the gateway's tools to permitted agents. It reads the upstream MCP `tools/list`, stores the returned definitions, presents eligible tools through its own runtime, checks policy, and forwards execution to the saved endpoint using server-resolved credentials.
|
||||
|
||||
That is a proxy of the upstream tool catalog. It does **not** turn every app in our public catalog into its own set of executable Paperclip tools.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Agent --> Policy[Paperclip gateway access and tool policy]
|
||||
Policy --> Tools[Composio MCP tools]
|
||||
Tools --> Accounts[Composio authorized app accounts]
|
||||
Accounts --> Circleback[Circleback]
|
||||
Apps[Paperclip Apps page] --> Check[Read account status through saved gateway]
|
||||
Check --> Accounts
|
||||
Check --> Cache[Safe account observations]
|
||||
Cache --> Apps
|
||||
```
|
||||
|
||||
The live test gateway currently has 11 active Composio catalog entries, including search, schemas, connection management, batch execution, and workbench tools. It does not list a separate Circleback tool set. Circleback execution happens behind those Composio tools. A custom session endpoint could expose a different catalog; the actual upstream definitions determine what Paperclip presents.
|
||||
|
||||
Consequently, an account can be available for execution even before the Apps page discovers it. The account snapshot is a display cache, not an execution allowlist.
|
||||
|
||||
## What currently works and what is incomplete
|
||||
|
||||
These findings come from the current worktree and a read-only check of the local test gateway.
|
||||
|
||||
| Capability | Current behavior |
|
||||
| --- | --- |
|
||||
| Reuse an already connected app | Setup lists the requested toolkit first. An active account skips creating another authorization link. No agent task is necessary. |
|
||||
| Authorize an app from Paperclip | The backend requests a hosted Composio authorization link. Completion checks account status again. |
|
||||
| Recognize a known account | Safe account IDs, aliases, statuses, default flags, and check times are cached. A fresh check confirmed Circleback is active. |
|
||||
| Discover an app connected independently | The Apps page checks its visible catalog toolkits and previously observed or configured toolkits. A previously unseen app can remain undiscovered until its page or search is checked. |
|
||||
| Notice external disconnection | Rechecking a known toolkit replaces the observation with the provider's returned account list. Until then, the card can be stale. |
|
||||
| Rename or remove an app account | Paperclip calls Composio, then lists accounts again to confirm the change. This changes upstream state. |
|
||||
| Give Circleback its own agent permissions | The row's Permissions action opens the saved Composio gateway's permissions. It is not an independent Circleback permission boundary. |
|
||||
|
||||
Refresh happens on page entry, changes to the visible catalog query, and browser focus. The one-minute query freshness setting is **not** a background polling schedule. We have no full inventory reconciliation job or lifecycle webhook integration here.
|
||||
|
||||
If a provider check fails, we preserve previous observations and show an error. That avoids interpreting a failed request as a disconnection, but the old green check can still look current. The UI should distinguish **last confirmed connected** from **verified now**.
|
||||
|
||||
Two additional presentation gaps matter:
|
||||
|
||||
- Native connector precedence currently excludes overlapping aggregator cards altogether. A native Notion offer can therefore hide the fact that Notion is already available through Composio. We should suppress duplicate connection offers while still showing real upstream accounts and their source.
|
||||
- The aggregator row's “Connected by” identity comes from the saved gateway owner metadata. It does not establish who authorized the app independently in Composio. Preserve the account-row visual style, but use truthful attribution such as **Via “Work Composio”** unless the upstream author is known.
|
||||
|
||||
## Who owns which permissions
|
||||
|
||||
| Question | Authority |
|
||||
| --- | --- |
|
||||
| Is this app account authorized and active? | Composio, backed by the app's authorization. |
|
||||
| What access did the app's OAuth consent grant? | The app and Composio's authorization configuration. |
|
||||
| Can this Paperclip human or agent use the saved gateway credential? | Paperclip's grants and access controls. |
|
||||
| Is this exposed MCP tool allowed, denied, or subject to approval? | Paperclip's tool policy. |
|
||||
| Can this agent use only Circleback through a broadly enabled Composio executor? | Not established by the Circleback app row or its current Permissions link. |
|
||||
|
||||
New gateways now default to all humans and all agents. That makes the gateway available to those actors; it does not override credential requirements, tool policies, or Composio's own account restrictions. App setup preserves the saved gateway's existing rules.
|
||||
|
||||
The policy system evaluates the catalog entry being invoked. For example, it sees `COMPOSIO_MULTI_EXECUTE_TOOL`; it does not automatically expand every nested app action into a separate Circleback permission decision. The current integration has no Composio-specific layer that grants or denies downstream toolkit/account operations independently.
|
||||
|
||||
An approval for a broad execution tool is therefore an approval for that call and its arguments. It should not be presented as a verified per-app read/write policy. Generic argument rules can constrain particular shapes, but do not by themselves provide comprehensive app isolation across batch execution and workbench paths.
|
||||
|
||||
Likewise, an app card's absence, a native app's precedence, or removing a cached observation cannot stop a permitted gateway from reaching an upstream account. A visual catalog choice is not a runtime restriction.
|
||||
|
||||
## What connecting or disconnecting elsewhere should mean
|
||||
|
||||
The following is the proposed behavior, assuming the account belongs to the identity and scope reachable by the saved gateway.
|
||||
|
||||
| Event | Expected Paperclip behavior |
|
||||
| --- | --- |
|
||||
| Connect Circleback in Composio | Import it into connected apps automatically. No second OAuth flow or local activation step. |
|
||||
| Click Connect when Circleback is already active | Verify and show the existing account. Do not create another account. |
|
||||
| Disconnect Circleback in Composio | Update the account row after reconciliation. Provider execution follows upstream state even while our card is stale. |
|
||||
| App authorization expires | Show Needs sign-in and offer the provider's reauthorization flow. |
|
||||
| Composio cannot be reached | Keep the last observation, mark it stale, and show Check again. Do not claim a confirmed disconnection. |
|
||||
| Add a second account for the same app | Show both accounts. Do not imply that clicking one pins all later agent calls to it unless execution actually enforces that choice. |
|
||||
| Remove Composio from Paperclip | Remove local access to that saved gateway. Keep upstream app accounts in Composio. |
|
||||
|
||||
Composio documents automatic OAuth token refresh and an expired-account event. That gives us one potential sync signal, but does not prove that our Connect credential can subscribe to every creation, deletion, or status event. [Authentication lifecycle](https://docs.composio.dev/docs/authentication)
|
||||
|
||||
## The experience I recommend
|
||||
|
||||
**Keep one saved connection per Composio account or configured endpoint, show its apps as imported accounts, and manage app authorization in Composio.** This matches the underlying system and keeps our interface small.
|
||||
|
||||
1. **Connecting Composio imports its available accounts.** Put them above the catalog using the same divider, check, logo, and account-row layout as other connections, with a Composio badge and quoted gateway name. Label this inventory as connected apps, rather than apps separately installed into Paperclip.
|
||||
2. **Connect asks which saved Composio account to use, or offers a new one.** Check existing authorization first. If needed, open a provider-generated sign-in link directly and verify the result. No agent picker or task draft.
|
||||
3. **An app's Manage action shows its accounts and opens Composio management.** Prefer a toolkit/account-specific URL where supported and verified; otherwise link clearly to Composio. Do not promise a deep link that the provider does not expose.
|
||||
4. **Permissions live on the Composio gateway.** From an imported app row, call the action **Composio permissions** and explain once that changes apply to apps accessible through that gateway. Keep native connector permissions independent.
|
||||
5. **Manage upstream app removal in Composio for the initial model.** Reserve **Remove connection** in Paperclip for removing the saved gateway locally. If we retain direct upstream removal, name it **Disconnect from Composio** and explicitly say it can affect other clients using that account.
|
||||
|
||||
This is a UX recommendation, not a technical inability to remove accounts. Composio Connect supports account creation, listing, renaming, and removal, and our backend already implements those operations. [Connection management capabilities](https://docs.composio.dev/docs/composio-connect)
|
||||
|
||||
Removing an account from Composio should also not be described as guaranteed revocation of the target app's OAuth grant. Composio distinguishes removing an account from revoking its authorization at the provider. [Connected account lifecycle](https://docs.composio.dev/reference/api-reference/connected-accounts)
|
||||
|
||||
For native overlaps, keep the native Connect offer as requested, but include actual Composio accounts in the connected inventory with their source visible. A Composio account should never silently become a native account.
|
||||
|
||||
## How to make discovery complete
|
||||
|
||||
The current per-toolkit checks are useful, but cannot support the promise “we automatically import everything you already connected.” Scanning thousands of catalog toolkits is an expensive substitute and can still miss apps absent from our public catalog.
|
||||
|
||||
Composio provides a paginated connected-account API with project-key authentication and user/toolkit/status filters. Its SDK also offers paginated session toolkit discovery filtered to connected apps. These are better building blocks for inventory than probing every public catalog entry. [Connected-account API](https://docs.composio.dev/reference/api-reference/connected-accounts/getConnectedAccounts), [Session inventory](https://docs.composio.dev/docs/configuring-sessions)
|
||||
|
||||
**The unresolved integration question is whether we can enumerate that same inventory using the identity behind our current Connect OAuth gateway.** We must not assume that credential is a project API key or that a dashboard project's users match it. Listing every account in a project without the correct identity filter would not answer the user's question.
|
||||
|
||||
Proposed implementation order:
|
||||
|
||||
1. Establish a supported inventory API for the saved credential, including its upstream subject, project/session scope, and shared-account semantics. If Connect cannot provide this, evaluate an explicit project/session integration as a separate setup mode.
|
||||
2. Reconcile every inventory page on connection completion and refresh; add bounded background reconciliation and supported event notifications. Events trigger another authoritative read rather than becoming a second source of truth.
|
||||
3. Store only safe metadata, scoped to company, gateway, and resolved credential identity. Preserve last-success time and sync errors. Change credentials or upstream identity without carrying over another identity's connected status.
|
||||
4. Reconcile removals only after a complete successful listing. Partial pages and failed calls must not delete unseen accounts. Surface unknown custom apps using provider metadata where available.
|
||||
|
||||
Until the identity-bound enumeration route is proven, describe the current display as **apps checked through this connection** rather than complete synchronization. The current cache uses a credential-reference fingerprint; that is not proof of the upstream user's identity.
|
||||
|
||||
## If we later want independent app permissions
|
||||
|
||||
We should decide this separately from inventory. The simpler gateway model is valid, provided the interface is honest about its scope.
|
||||
|
||||
Composio SDK sessions can restrict toolkits, tools, and selected connected accounts. A session MCP endpoint can use those restrictions and a direct-tools preset to expose an explicit tool set. That offers a possible route to enforced per-agent scope, but it requires a different integration contract from merely observing accounts behind Connect. [Session configuration](https://docs.composio.dev/docs/configuring-sessions), [Session MCP endpoints](https://docs.composio.dev/docs/sessions-via-mcp)
|
||||
|
||||
The other route is a Paperclip executor that validates every downstream tool and account and prevents broader execution paths from bypassing those rules. This is substantial runtime work. Adding a per-app toggle while leaving unrestricted gateway execution available would not establish the promised boundary.
|
||||
|
||||
For apps already supported natively, a native connection remains the clearest way to apply that connector's individual access controls.
|
||||
|
||||
## Evidence and checks still needed
|
||||
|
||||
The local gateway returned 11 active Composio tools, and a fresh read through our app refresh endpoint confirmed an ACTIVE Circleback account. This proves account-status checking for that saved credential. It does not prove all-app enumeration, a successful Circleback business operation, external lifecycle synchronization, or independent Circleback permissions. No real account was disconnected or permission changed for this document.
|
||||
|
||||
Before claiming complete sync, demonstrate an app connected independently in the matching Composio scope appearing without searching for it; then externally disconnect a disposable test account and verify both the UI and execution outcome. Also check account isolation, multiple-account selection, provider failures, native overlaps, and gateway removal without upstream account deletion.
|
||||
|
||||
Implementation evidence in this worktree:
|
||||
|
||||
- `server/src/services/tool-access.ts`: `openComposioGateway`, `setupComposioApp`, `refreshComposioApps`, and `manageComposioAppAccount` implement checks, safe observations, and upstream mutations.
|
||||
- `server/src/services/tool-gateway.ts`: `policyInputForAgentTool` and remote MCP execution show the tool-policy boundary and forwarding of catalog tool names and arguments.
|
||||
- `server/src/services/tool-access-policy.ts`: catalog entries and saved connections determine the policy context; no automatic downstream Composio app expansion is implemented.
|
||||
- `ui/src/pages/apps/Browse.tsx`: native precedence, page-based discovery, connected-account rows, permission links, and owner attribution.
|
||||
- `packages/db/src/schema/tool_connection_app_snapshots.ts`: cached account observations, separate from connection grants.
|
||||
- `doc/connections/CONNECTOR-PLAYBOOK.md`: the current aggregator catalog and setup behavior.
|
||||
|
||||
The recommendation is to adopt the provider-owned account model now, then prove complete discovery before promising it. Independent app permissions can follow if we choose to build and enforce them.
|
||||
@@ -0,0 +1,740 @@
|
||||
{
|
||||
"schema": "paperclip.hiring-executable-accounting-replay.v1",
|
||||
"assessmentType": "Provider-free replay through both new executable guards; original model measurements and verdicts unchanged",
|
||||
"evaluatedCodeRevision": "eef64009dc144b91b245a731ea9a1c3c07406a19",
|
||||
"evaluatedCodeHashes": {
|
||||
"tests/runner-e2e/hiring-template-cases.ts": "06915e33cc9a5241cd28a7184fa33d098c30aed4114accea4f93838aaba3738f",
|
||||
"tests/runner-e2e/hiring-template-scoring.ts": "a9e1efa7e11bad3aa8dc873a28325b3153799251740ae87c4623a08f7d45a5cb",
|
||||
"tests/runner-e2e/hiring-template-flow.ts": "f3c6d29dc5019955dda4372ead7a9fdfbde447b7b92ef103527b9393ec60bd30",
|
||||
"tests/runner-e2e/hiring-template-turn-accounting.ts": "8f2ad8b09e419796b372929b59dc7f86d41dcbf3a3144c45e329a1aa988b383b",
|
||||
"tests/runner-e2e/chat-flow.ts": "09dd87ef7aef299ddbd1fad65b72817c042595f9e4a4e699c430497ac053a2d4"
|
||||
},
|
||||
"graderVersion": "paperclip.hiring-templates.v2",
|
||||
"definitionDigest": "4d527906a967ca71c4c0e62cfbdc3405419931bcd6741b0630c4223bed2d4b6a",
|
||||
"providerCalls": 0,
|
||||
"newProviderRuns": 0,
|
||||
"automaticRetries": 0,
|
||||
"summary": {
|
||||
"originalPairs": "Codex Fail→Fail; Claude Fail→Fail",
|
||||
"correctedWorkflowOutcomePairs": "Codex Pass→Pass; Claude Pass→Pass",
|
||||
"fullAttemptComparison": "Uncomparable in both profiles and variants; source-read coverage unchanged",
|
||||
"originalMachineFailuresPreserved": 4,
|
||||
"correctedScorerGuardsPassed": 4,
|
||||
"correctedFinalChatGuardsPassed": 4,
|
||||
"unchangedCoverageAssessments": 4,
|
||||
"unchangedOtherOutcomeAssessments": 4,
|
||||
"actualMeasuredRuns": 28,
|
||||
"actualModelChargesKnown": false,
|
||||
"broadEquivalenceEstablished": false
|
||||
},
|
||||
"assessments": [
|
||||
{
|
||||
"variant": "candidate",
|
||||
"profile": "runner-codex",
|
||||
"original": {
|
||||
"sourceRevision": "9f5404ad3aacbe76777952759414d34fd381e674",
|
||||
"graderVersion": "paperclip.hiring-templates.v1",
|
||||
"definitionDigest": "4d7f18420889325af89aa48eaaf021e8178aa6d26d504b92041635a15741f004",
|
||||
"machineStatus": "failed",
|
||||
"outcomePassed": false,
|
||||
"countCheckPassed": false,
|
||||
"comparisonStatus": "uncomparable"
|
||||
},
|
||||
"corrected": {
|
||||
"graderVersion": "paperclip.hiring-templates.v2",
|
||||
"definitionDigest": "4d527906a967ca71c4c0e62cfbdc3405419931bcd6741b0630c4223bed2d4b6a",
|
||||
"outcomePassed": true,
|
||||
"comparisonStatus": "uncomparable",
|
||||
"scorerGuardPassed": true,
|
||||
"finalChatGuardPassed": true,
|
||||
"turnAccounting": {
|
||||
"version": "paperclip.hiring-template-turn-accounting.v1",
|
||||
"counts": {
|
||||
"requiredWorkTurns": 5,
|
||||
"maximumCompletionTurns": 2,
|
||||
"maximumTotalTurns": 7,
|
||||
"requestedLeadTurns": 3,
|
||||
"coderTurns": 2,
|
||||
"completionTurns": 2,
|
||||
"unclassifiedTurns": 0,
|
||||
"snapshotRunCount": 7,
|
||||
"actualRunCount": 7,
|
||||
"costAccountingRunCount": 7
|
||||
},
|
||||
"predicates": [
|
||||
{
|
||||
"id": "known-fixture-context",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "complete-public-run-ledger",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "exact-five-required-work-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "successful-native-without-retries",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "resolved-company-account-and-identity",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "three-distinct-requested-chat-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "one-coder-execution-per-known-task",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "task-origins-are-first-two-requested-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "bounded-server-completion-receipts",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "completion-runs-have-attributed-chat-replies",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "no-notification-created-extra-tasks",
|
||||
"passed": true
|
||||
}
|
||||
],
|
||||
"passed": true
|
||||
},
|
||||
"checks": [
|
||||
{
|
||||
"id": "one-coder-hire",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Exactly one permanent coding teammate reports to the lead."
|
||||
},
|
||||
{
|
||||
"id": "execution-account",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Hire keeps the lead model and managed account binding."
|
||||
},
|
||||
{
|
||||
"id": "two-worker-tasks",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Both independent tasks are completed by the same coder in the chosen project."
|
||||
},
|
||||
{
|
||||
"id": "bounded-work-and-completion-turns",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Exactly three requested lead turns and two coder executions, plus at most two strictly attributed completion notifications; every actual run remains counted."
|
||||
},
|
||||
{
|
||||
"id": "initial-json-artifact",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Independent computation checks each original input/value and worker authorship."
|
||||
},
|
||||
{
|
||||
"id": "reused-json-artifact",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "The reused coder applies the changed separator to every input."
|
||||
},
|
||||
{
|
||||
"id": "original-preserved",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Reuse preserves the original document, revision and task identity."
|
||||
},
|
||||
{
|
||||
"id": "source-fingerprints",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Served instruction and hiring source bytes match the evaluated revision."
|
||||
},
|
||||
{
|
||||
"id": "production-ceo-bundle",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The API-created lead receives this revision's default CEO bundle, without a fixture override."
|
||||
},
|
||||
{
|
||||
"id": "assigned-hiring-skill",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Production CEO defaults assign the hiring skill."
|
||||
},
|
||||
{
|
||||
"id": "production-source-reads",
|
||||
"dimension": "coverage",
|
||||
"passed": false,
|
||||
"detail": "Completed lead read receipts before the hire prove the explicitly requested skill, guide, checklist and coder example paths. Unrecognized or missing reads leave coverage uncomparable."
|
||||
},
|
||||
{
|
||||
"id": "supplied-coder-instructions",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Saved hired instructions use the source revision's coder example with its company/name placeholders filled."
|
||||
},
|
||||
{
|
||||
"id": "hired-instructions-durable",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The same saved instruction bundle survives the reused worker execution."
|
||||
},
|
||||
{
|
||||
"id": "hired-skills-durable",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The saved skill selections survive the reused worker execution."
|
||||
}
|
||||
]
|
||||
},
|
||||
"coverageUnchanged": true,
|
||||
"otherOutcomesUnchanged": true,
|
||||
"originalBytesUnchanged": true,
|
||||
"inputSha256": {
|
||||
"result": "44cbf64cfabd0257f31fb4d6909018d346f5667b0a7b3d7e855a2ece7e29c292",
|
||||
"hiringTemplate": "9609ddcb81c6bee3c5db97312138b4ea3e294da13ea540e41f363c826a514067",
|
||||
"apiState": "ef02af29bcf61ff3eace0549ca164a28c7950d1a3e14fc58c89bf9cb0f944b4d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"variant": "candidate",
|
||||
"profile": "runner-acpx-claude",
|
||||
"original": {
|
||||
"sourceRevision": "9f5404ad3aacbe76777952759414d34fd381e674",
|
||||
"graderVersion": "paperclip.hiring-templates.v1",
|
||||
"definitionDigest": "4d7f18420889325af89aa48eaaf021e8178aa6d26d504b92041635a15741f004",
|
||||
"machineStatus": "failed",
|
||||
"outcomePassed": false,
|
||||
"countCheckPassed": false,
|
||||
"comparisonStatus": "uncomparable"
|
||||
},
|
||||
"corrected": {
|
||||
"graderVersion": "paperclip.hiring-templates.v2",
|
||||
"definitionDigest": "4d527906a967ca71c4c0e62cfbdc3405419931bcd6741b0630c4223bed2d4b6a",
|
||||
"outcomePassed": true,
|
||||
"comparisonStatus": "uncomparable",
|
||||
"scorerGuardPassed": true,
|
||||
"finalChatGuardPassed": true,
|
||||
"turnAccounting": {
|
||||
"version": "paperclip.hiring-template-turn-accounting.v1",
|
||||
"counts": {
|
||||
"requiredWorkTurns": 5,
|
||||
"maximumCompletionTurns": 2,
|
||||
"maximumTotalTurns": 7,
|
||||
"requestedLeadTurns": 3,
|
||||
"coderTurns": 2,
|
||||
"completionTurns": 2,
|
||||
"unclassifiedTurns": 0,
|
||||
"snapshotRunCount": 7,
|
||||
"actualRunCount": 7,
|
||||
"costAccountingRunCount": 7
|
||||
},
|
||||
"predicates": [
|
||||
{
|
||||
"id": "known-fixture-context",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "complete-public-run-ledger",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "exact-five-required-work-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "successful-native-without-retries",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "resolved-company-account-and-identity",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "three-distinct-requested-chat-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "one-coder-execution-per-known-task",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "task-origins-are-first-two-requested-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "bounded-server-completion-receipts",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "completion-runs-have-attributed-chat-replies",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "no-notification-created-extra-tasks",
|
||||
"passed": true
|
||||
}
|
||||
],
|
||||
"passed": true
|
||||
},
|
||||
"checks": [
|
||||
{
|
||||
"id": "one-coder-hire",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Exactly one permanent coding teammate reports to the lead."
|
||||
},
|
||||
{
|
||||
"id": "execution-account",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Hire keeps the lead model and managed account binding."
|
||||
},
|
||||
{
|
||||
"id": "two-worker-tasks",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Both independent tasks are completed by the same coder in the chosen project."
|
||||
},
|
||||
{
|
||||
"id": "bounded-work-and-completion-turns",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Exactly three requested lead turns and two coder executions, plus at most two strictly attributed completion notifications; every actual run remains counted."
|
||||
},
|
||||
{
|
||||
"id": "initial-json-artifact",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Independent computation checks each original input/value and worker authorship."
|
||||
},
|
||||
{
|
||||
"id": "reused-json-artifact",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "The reused coder applies the changed separator to every input."
|
||||
},
|
||||
{
|
||||
"id": "original-preserved",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Reuse preserves the original document, revision and task identity."
|
||||
},
|
||||
{
|
||||
"id": "source-fingerprints",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Served instruction and hiring source bytes match the evaluated revision."
|
||||
},
|
||||
{
|
||||
"id": "production-ceo-bundle",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The API-created lead receives this revision's default CEO bundle, without a fixture override."
|
||||
},
|
||||
{
|
||||
"id": "assigned-hiring-skill",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Production CEO defaults assign the hiring skill."
|
||||
},
|
||||
{
|
||||
"id": "production-source-reads",
|
||||
"dimension": "coverage",
|
||||
"passed": false,
|
||||
"detail": "Completed lead read receipts before the hire prove the explicitly requested skill, guide, checklist and coder example paths. Unrecognized or missing reads leave coverage uncomparable."
|
||||
},
|
||||
{
|
||||
"id": "supplied-coder-instructions",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Saved hired instructions use the source revision's coder example with its company/name placeholders filled."
|
||||
},
|
||||
{
|
||||
"id": "hired-instructions-durable",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The same saved instruction bundle survives the reused worker execution."
|
||||
},
|
||||
{
|
||||
"id": "hired-skills-durable",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The saved skill selections survive the reused worker execution."
|
||||
}
|
||||
]
|
||||
},
|
||||
"coverageUnchanged": true,
|
||||
"otherOutcomesUnchanged": true,
|
||||
"originalBytesUnchanged": true,
|
||||
"inputSha256": {
|
||||
"result": "a83aecc26ef0af2e231cb364299cf0cb2f2ccd83b6998913dcd33be72d4c0824",
|
||||
"hiringTemplate": "4f83f523b955120c5baac18572dce752b278dbc9ff765b940c0e17c37ab28ae0",
|
||||
"apiState": "a208a3e3568fe9aacbec8d683a502a0d304a47571a1b97c229d2936e5d1bb087"
|
||||
}
|
||||
},
|
||||
{
|
||||
"variant": "baseline",
|
||||
"profile": "runner-codex",
|
||||
"original": {
|
||||
"sourceRevision": "296a4df85e8bcc97a160fc78c291b17adb828196",
|
||||
"graderVersion": "paperclip.hiring-templates.v1",
|
||||
"definitionDigest": "4d7f18420889325af89aa48eaaf021e8178aa6d26d504b92041635a15741f004",
|
||||
"machineStatus": "failed",
|
||||
"outcomePassed": false,
|
||||
"countCheckPassed": false,
|
||||
"comparisonStatus": "uncomparable"
|
||||
},
|
||||
"corrected": {
|
||||
"graderVersion": "paperclip.hiring-templates.v2",
|
||||
"definitionDigest": "4d527906a967ca71c4c0e62cfbdc3405419931bcd6741b0630c4223bed2d4b6a",
|
||||
"outcomePassed": true,
|
||||
"comparisonStatus": "uncomparable",
|
||||
"scorerGuardPassed": true,
|
||||
"finalChatGuardPassed": true,
|
||||
"turnAccounting": {
|
||||
"version": "paperclip.hiring-template-turn-accounting.v1",
|
||||
"counts": {
|
||||
"requiredWorkTurns": 5,
|
||||
"maximumCompletionTurns": 2,
|
||||
"maximumTotalTurns": 7,
|
||||
"requestedLeadTurns": 3,
|
||||
"coderTurns": 2,
|
||||
"completionTurns": 2,
|
||||
"unclassifiedTurns": 0,
|
||||
"snapshotRunCount": 7,
|
||||
"actualRunCount": 7,
|
||||
"costAccountingRunCount": 7
|
||||
},
|
||||
"predicates": [
|
||||
{
|
||||
"id": "known-fixture-context",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "complete-public-run-ledger",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "exact-five-required-work-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "successful-native-without-retries",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "resolved-company-account-and-identity",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "three-distinct-requested-chat-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "one-coder-execution-per-known-task",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "task-origins-are-first-two-requested-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "bounded-server-completion-receipts",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "completion-runs-have-attributed-chat-replies",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "no-notification-created-extra-tasks",
|
||||
"passed": true
|
||||
}
|
||||
],
|
||||
"passed": true
|
||||
},
|
||||
"checks": [
|
||||
{
|
||||
"id": "one-coder-hire",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Exactly one permanent coding teammate reports to the lead."
|
||||
},
|
||||
{
|
||||
"id": "execution-account",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Hire keeps the lead model and managed account binding."
|
||||
},
|
||||
{
|
||||
"id": "two-worker-tasks",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Both independent tasks are completed by the same coder in the chosen project."
|
||||
},
|
||||
{
|
||||
"id": "bounded-work-and-completion-turns",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Exactly three requested lead turns and two coder executions, plus at most two strictly attributed completion notifications; every actual run remains counted."
|
||||
},
|
||||
{
|
||||
"id": "initial-json-artifact",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Independent computation checks each original input/value and worker authorship."
|
||||
},
|
||||
{
|
||||
"id": "reused-json-artifact",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "The reused coder applies the changed separator to every input."
|
||||
},
|
||||
{
|
||||
"id": "original-preserved",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Reuse preserves the original document, revision and task identity."
|
||||
},
|
||||
{
|
||||
"id": "source-fingerprints",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Served instruction and hiring source bytes match the evaluated revision."
|
||||
},
|
||||
{
|
||||
"id": "production-ceo-bundle",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The API-created lead receives this revision's default CEO bundle, without a fixture override."
|
||||
},
|
||||
{
|
||||
"id": "assigned-hiring-skill",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Production CEO defaults assign the hiring skill."
|
||||
},
|
||||
{
|
||||
"id": "production-source-reads",
|
||||
"dimension": "coverage",
|
||||
"passed": false,
|
||||
"detail": "Completed lead read receipts before the hire prove the explicitly requested skill, guide, checklist and coder example paths. Unrecognized or missing reads leave coverage uncomparable."
|
||||
},
|
||||
{
|
||||
"id": "supplied-coder-instructions",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Saved hired instructions use the source revision's coder example with its company/name placeholders filled."
|
||||
},
|
||||
{
|
||||
"id": "hired-instructions-durable",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The same saved instruction bundle survives the reused worker execution."
|
||||
},
|
||||
{
|
||||
"id": "hired-skills-durable",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The saved skill selections survive the reused worker execution."
|
||||
}
|
||||
]
|
||||
},
|
||||
"coverageUnchanged": true,
|
||||
"otherOutcomesUnchanged": true,
|
||||
"originalBytesUnchanged": true,
|
||||
"inputSha256": {
|
||||
"result": "d912d8400bfff4f7f922aa6b264f9ce5fff1ba2d893abf03790c4cfea352261f",
|
||||
"hiringTemplate": "88b23e4d2cc8831a5d88b8479cc69c0d1c2e17a6a5243707b4bd19716a244aca",
|
||||
"apiState": "dff6bb13bb54d45024966d9cfbeb6dbe6f2241e674b0b1350c1384863202a97f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"variant": "baseline",
|
||||
"profile": "runner-acpx-claude",
|
||||
"original": {
|
||||
"sourceRevision": "296a4df85e8bcc97a160fc78c291b17adb828196",
|
||||
"graderVersion": "paperclip.hiring-templates.v1",
|
||||
"definitionDigest": "4d7f18420889325af89aa48eaaf021e8178aa6d26d504b92041635a15741f004",
|
||||
"machineStatus": "failed",
|
||||
"outcomePassed": false,
|
||||
"countCheckPassed": false,
|
||||
"comparisonStatus": "uncomparable"
|
||||
},
|
||||
"corrected": {
|
||||
"graderVersion": "paperclip.hiring-templates.v2",
|
||||
"definitionDigest": "4d527906a967ca71c4c0e62cfbdc3405419931bcd6741b0630c4223bed2d4b6a",
|
||||
"outcomePassed": true,
|
||||
"comparisonStatus": "uncomparable",
|
||||
"scorerGuardPassed": true,
|
||||
"finalChatGuardPassed": true,
|
||||
"turnAccounting": {
|
||||
"version": "paperclip.hiring-template-turn-accounting.v1",
|
||||
"counts": {
|
||||
"requiredWorkTurns": 5,
|
||||
"maximumCompletionTurns": 2,
|
||||
"maximumTotalTurns": 7,
|
||||
"requestedLeadTurns": 3,
|
||||
"coderTurns": 2,
|
||||
"completionTurns": 2,
|
||||
"unclassifiedTurns": 0,
|
||||
"snapshotRunCount": 7,
|
||||
"actualRunCount": 7,
|
||||
"costAccountingRunCount": 7
|
||||
},
|
||||
"predicates": [
|
||||
{
|
||||
"id": "known-fixture-context",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "complete-public-run-ledger",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "exact-five-required-work-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "successful-native-without-retries",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "resolved-company-account-and-identity",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "three-distinct-requested-chat-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "one-coder-execution-per-known-task",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "task-origins-are-first-two-requested-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "bounded-server-completion-receipts",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "completion-runs-have-attributed-chat-replies",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "no-notification-created-extra-tasks",
|
||||
"passed": true
|
||||
}
|
||||
],
|
||||
"passed": true
|
||||
},
|
||||
"checks": [
|
||||
{
|
||||
"id": "one-coder-hire",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Exactly one permanent coding teammate reports to the lead."
|
||||
},
|
||||
{
|
||||
"id": "execution-account",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Hire keeps the lead model and managed account binding."
|
||||
},
|
||||
{
|
||||
"id": "two-worker-tasks",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Both independent tasks are completed by the same coder in the chosen project."
|
||||
},
|
||||
{
|
||||
"id": "bounded-work-and-completion-turns",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Exactly three requested lead turns and two coder executions, plus at most two strictly attributed completion notifications; every actual run remains counted."
|
||||
},
|
||||
{
|
||||
"id": "initial-json-artifact",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Independent computation checks each original input/value and worker authorship."
|
||||
},
|
||||
{
|
||||
"id": "reused-json-artifact",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "The reused coder applies the changed separator to every input."
|
||||
},
|
||||
{
|
||||
"id": "original-preserved",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Reuse preserves the original document, revision and task identity."
|
||||
},
|
||||
{
|
||||
"id": "source-fingerprints",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Served instruction and hiring source bytes match the evaluated revision."
|
||||
},
|
||||
{
|
||||
"id": "production-ceo-bundle",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The API-created lead receives this revision's default CEO bundle, without a fixture override."
|
||||
},
|
||||
{
|
||||
"id": "assigned-hiring-skill",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Production CEO defaults assign the hiring skill."
|
||||
},
|
||||
{
|
||||
"id": "production-source-reads",
|
||||
"dimension": "coverage",
|
||||
"passed": false,
|
||||
"detail": "Completed lead read receipts before the hire prove the explicitly requested skill, guide, checklist and coder example paths. Unrecognized or missing reads leave coverage uncomparable."
|
||||
},
|
||||
{
|
||||
"id": "supplied-coder-instructions",
|
||||
"dimension": "coverage",
|
||||
"passed": false,
|
||||
"detail": "Saved hired instructions use the source revision's coder example with its company/name placeholders filled."
|
||||
},
|
||||
{
|
||||
"id": "hired-instructions-durable",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The same saved instruction bundle survives the reused worker execution."
|
||||
},
|
||||
{
|
||||
"id": "hired-skills-durable",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The saved skill selections survive the reused worker execution."
|
||||
}
|
||||
]
|
||||
},
|
||||
"coverageUnchanged": true,
|
||||
"otherOutcomesUnchanged": true,
|
||||
"originalBytesUnchanged": true,
|
||||
"inputSha256": {
|
||||
"result": "844763ddc83461b2bea620f153f4bfe0bfee20a27bf33a60017a45c774ce2f6a",
|
||||
"hiringTemplate": "27b153798472e206970c46358febdda363cb75f892734757e76994117f0d2c9b",
|
||||
"apiState": "441cf7272ea28c5e1182adf48dba09a401e88c6cad3a99e72c8cf3f46b1c5b41"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
# Executable hiring lifecycle accounting repair
|
||||
|
||||
**Current TL;DR:** No new model calls were made. The original Codex and Claude pairs remain Fail → Fail. The stricter v3 executable replay verifies Codex accounting in both variants; Claude notification action attribution remains unresolved/uncomparable because provider and native request IDs cannot be exactly joined. No notification writes are observed in the retained native API receipts. Source-read coverage remains uncomparable for both profiles, independently of action attribution. Earlier sidecar-v1 and initial executable-v2 passes remain below with their limited task-only proof identified.
|
||||
|
||||
## Current stricter executable replay
|
||||
|
||||
Code revision: `e4077ade1818d98b9862ae79ee1d49a007dcf9c1`. Hiring grader: `paperclip.hiring-templates.v3`; turn accounting: `paperclip.hiring-template-turn-accounting.v2`. The separately versioned [v2 JSON receipt](2026-10-02-hiring-executable-accounting-replay.v2.json) pins exact source hashes and unchanged original inputs.
|
||||
|
||||
| Profile | Original machine grades | Limited sidecar v1 / initial executable v2 | Stricter executable accounting | Source-read coverage |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Codex | Fail → Fail | Pass → Pass | Verified / both new guards pass in each variant | Uncomparable both |
|
||||
| ACPX Claude | Fail → Fail | Pass → Pass | Unresolved/uncomparable action attribution in each variant; guards fail closed | Uncomparable both; historical six-backtick exact-template mismatch remains |
|
||||
|
||||
The limited sidecar and initial executable check rejected notification-created tasks but could miss an unrelated document write during a notification. Their original results and hashes are preserved; they do not prove every notification was harmless. The stricter helper adds a twelfth predicate and an independent action-coverage check. It requires complete contiguous event streams, an accepted control-plane result and succeeded/completed terminal, exact canonical/native action IDs, successful known GET API receipts or verified reads/discovery, and attributed native chat finish. Document/task/agent mutations, failed mutation attempts, incomplete streams and unknown or unmatched actions cannot pass. A readonly hint alone cannot qualify a generic API call.
|
||||
|
||||
Codex verifies 10 candidate and 11 baseline notification tool executions, including five and seven exactly joined successful GET calls, with zero unresolved actions. ACPX's native request IDs and provider execution IDs use separate namespaces; names, ordering and counts cannot safely join them. All observed ACPX native API calls are successful GETs, but the stronger cross-ledger proof is missing (five candidate/four baseline unresolved observations). This is an evidence limitation, not a newly observed task failure or a prompt regression. No production carriers or source-read grading are changed.
|
||||
|
||||
The observation repair retries entire bracketed snapshots, waits both known Done task callbacks and attributed chat replies (including batching), checks untruncated pending-wake diagnostics, and requires two identical complete observations. It covers the quiet gap before pending outbox work enqueues a wake. Accounted coalesced wakes are admitted only with a linked terminal run and known callbacks. The final guard performs that same complete refresh rather than mixing a stale evidence snapshot with new runs. The generic five-work-turn helper still admits zero notifications when none are owed; this production delegated fixture owes two task completions.
|
||||
|
||||
All six non-count outcome checks and every original source/template coverage check remain byte-identical in all four replay projections. All 28 actual model runs remain counted; original input hashes and failed verdicts are unchanged. The stricter action check is separately classified as coverage when attribution is missing. No broad equivalence is established.
|
||||
|
||||
- 977 credential-free support tests pass across 64 files, including 144 focused helper/scorer/settlement integrations.
|
||||
- E2E typecheck, exact two-cell discovery, canonical capability contract/inventory checks and diff checks pass.
|
||||
- Two current-head review findings are fixed: racing observations and unchecked completion-turn writes. Fresh review/CI is required on this source.
|
||||
- Initial-head CI retained two unrelated browser failures: exact agent-run denial feedback and touch-picker scroll position. Neither browser path imports the changed runner-e2e harness. They are preserved without a blind rerun; the necessary review-fix head runs normal CI.
|
||||
- No providers, old campaign retries or extra paid scope are launched. Actual prior model charges remain unknown.
|
||||
|
||||
## Initial executable v2 replay (limited notification proof)
|
||||
|
||||
TL;DR: This repairs a grading defect; it does not change model instructions or rerun models. The original Codex and Claude pairs remain Fail → Fail. Provider-free replay through both corrected executable paths passes all four retained cells; source-read coverage remains uncomparable. This is a grading correction, not a new model-performance result.
|
||||
|
||||
The original fixture required exactly five total runs. Production adds legitimate server completion turns after delegated work. The corrected v2 contract requires exactly three distinct user-requested CEO turns and two coder executions. At most two additional completion turns must pass strict public identity/account/task/delivery/timing/reply attribution. One turn may batch both completed tasks. Unknown, duplicate, failed, retried runs, notification-created tasks and missing public observations fail. This initial version did not inspect unrelated document writes during notifications; see the stricter current assessment above.
|
||||
|
||||
Both executable count guards use the shared helper: the hiring scorer and the hiring-only final chat guard. The catalog declares five required / seven maximum total runs so timeout/cost planning includes notifications. Non-hiring count guards remain unchanged. Source-read and exact coder-body grading remain unchanged, including the historical Claude six-backtick mismatch. The grader version and full helper/chat/source digest change.
|
||||
|
||||
## Preserved measurement
|
||||
|
||||
The immutable [original and bounded sidecar report](https://github.com/paperclipai/paperclip/blob/8eb517ca1497687237163bdef4dfc4d3332ea916/doc/plans/2026-10-02-hiring-template-live-comparison.md) retains candidate `9f5404ad3aacbe76777952759414d34fd381e674` and historical `296a4df85e8bcc97a160fc78c291b17adb828196`, the identical original fixture digest, 28 actual successful runs, eight automatic completion turns, four successful cleanups and unknown actual charges. It calibrates sidecar v1 with 69 passing tests and records exact original input/grader hashes. The executable repair is a new source/grader revision; it cannot rewrite those measurements or prove performance equivalence.
|
||||
|
||||
## Provider-free verification
|
||||
|
||||
The executable code revision is `eef64009dc144b91b245a731ea9a1c3c07406a19`; report-only commits do not change its grader bytes. The [JSON receipt](2026-10-02-hiring-executable-accounting-replay.json) pins all five grader/flow/helper source hashes, v2 definition digest and original result/hiring/API input hashes.
|
||||
|
||||
| Profile | Original executable result | Corrected retained workflow outcome | Both new count guards | Source coverage |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Codex | Fail → Fail | Pass → Pass | Pass in both variants | Uncomparable both |
|
||||
| ACPX Claude | Fail → Fail | Pass → Pass | Pass in both variants | Uncomparable both; historical exact coder body still fails |
|
||||
|
||||
All six other outcome checks and every coverage check are byte-identical in the replay's check projection. Original input files and failed grades remain unchanged. All 28 actual model runs remain counted. The new helper has 11 lifecycle predicates, factoring the sidecar's lifecycle rule without requiring an already-computed original result.
|
||||
|
||||
- All 946 credential-free E2E support tests pass across 64 files, including 99 helper calibrations and the four added scorer/final-guard integrations.
|
||||
- Fifteen focused hiring/run-count tests pass. The integrations admit five core turns, six with batching and seven with distinct notifications; reject an arbitrary wake despite the same total count; require public observations; retain source/template coverage failures; and preserve non-hiring count guards.
|
||||
- E2E typecheck, normal plugin SDK/Runner TypeScript dependency builds, canonical capability contract/inventory checks and existing two-cell discovery pass.
|
||||
- Retained replay executes the actual new scorer and final chat guard in each of the four original cells, using the unchanged saved API observations. Both guards pass 4/4; coverage and the six non-count outcomes stay unchanged 4/4.
|
||||
- Full repository CI and fresh review are pending on the separate draft PR. Local general repository typecheck/test/build were not repeated; exact-head CI must supply those gates before handoff.
|
||||
|
||||
Initial development calibration caught mismatched synthetic account data and a deliberately changed template not preserved across reuse. The fixture data was corrected; assertions were kept. Initial cold typecheck lacked built Runner declarations; normal dependency builds resolved it. A temporary replay script's CommonJS extension rejected top-level await; the same script executed as ESM. These provider-free setup/development attempts remain private and are not live model results. No provider runs, old campaign retries or paid scope expansion are authorized for this repair. Public replay receipts will include only grader/input hashes, counts, predicate/check results and original verdicts; credentials, provider session IDs and hidden reasoning remain private.
|
||||
|
||||
The branch was safely replayed on master `59c07ede7` before PR creation. The intervening master changes touch only two agent-provider UI files; all eval source bytes are unchanged. The four-cell provider-free replay was repeated against the final reachable code revision, with the same input hashes, checks and zero providers.
|
||||
@@ -0,0 +1,815 @@
|
||||
{
|
||||
"schema": "paperclip.hiring-executable-accounting-replay.v2",
|
||||
"assessmentType": "Provider-free replay through both new executable guards; original model measurements and verdicts unchanged",
|
||||
"evaluatedCodeRevision": "e4077ade1818d98b9862ae79ee1d49a007dcf9c1",
|
||||
"evaluatedCodeHashes": {
|
||||
"tests/runner-e2e/hiring-template-cases.ts": "a440ebf5a932525d388f507f8b47490a535d377c9f9c9beba6144f1fc2214b7c",
|
||||
"tests/runner-e2e/hiring-template-scoring.ts": "5f600b8329c981f1fc94ecc1b70a94b78d4a32a5b8859600208cbe1f91be2dac",
|
||||
"tests/runner-e2e/hiring-template-flow.ts": "10b2415ad6ee293286db0a8cc8cb7a6a363d993a70560ab0a82f668018b5e3f5",
|
||||
"tests/runner-e2e/hiring-template-turn-accounting.ts": "13792c69669b99748087d6a78af8ab35c6293d1ad3e3396efcbe39620169ad35",
|
||||
"tests/runner-e2e/chat-flow.ts": "85d8c3da832871f38cc4ed940046d28ed7b3edf17486ce43b0f3313c06d17d7a"
|
||||
},
|
||||
"graderVersion": "paperclip.hiring-templates.v3",
|
||||
"definitionDigest": "cddbcb6b2f3050e065599011ace2faee048047fae6ea9bc9183db8c5744e2f38",
|
||||
"providerCalls": 0,
|
||||
"newProviderRuns": 0,
|
||||
"automaticRetries": 0,
|
||||
"summary": {
|
||||
"originalPairs": "Codex Fail→Fail; Claude Fail→Fail",
|
||||
"correctedWorkflowOutcomePairs": "Codex Pass→Pass; Claude action attribution unresolved/uncomparable in both variants (not a measured task failure)",
|
||||
"fullAttemptComparison": "Uncomparable in both profiles and variants; source-read coverage unchanged",
|
||||
"originalMachineFailuresPreserved": 4,
|
||||
"correctedScorerGuardsPassed": 2,
|
||||
"correctedFinalChatGuardsPassed": 2,
|
||||
"strictActionVerifiedAssessments": 2,
|
||||
"strictActionUncomparableAssessments": 2,
|
||||
"unchangedOriginalCoverageAssessments": 4,
|
||||
"unchangedOtherOutcomeAssessments": 4,
|
||||
"actualMeasuredRuns": 28,
|
||||
"actualModelChargesKnown": false,
|
||||
"broadEquivalenceEstablished": false,
|
||||
"priorSidecarLimitation": "Sidecar v1 and initial executable v2 checked notification-created tasks, not unrelated document writes. Their original passes remain preserved."
|
||||
},
|
||||
"assessments": [
|
||||
{
|
||||
"variant": "candidate",
|
||||
"profile": "runner-codex",
|
||||
"original": {
|
||||
"sourceRevision": "9f5404ad3aacbe76777952759414d34fd381e674",
|
||||
"graderVersion": "paperclip.hiring-templates.v1",
|
||||
"definitionDigest": "4d7f18420889325af89aa48eaaf021e8178aa6d26d504b92041635a15741f004",
|
||||
"machineStatus": "failed",
|
||||
"outcomePassed": false,
|
||||
"countCheckPassed": false,
|
||||
"comparisonStatus": "uncomparable"
|
||||
},
|
||||
"corrected": {
|
||||
"graderVersion": "paperclip.hiring-templates.v3",
|
||||
"definitionDigest": "cddbcb6b2f3050e065599011ace2faee048047fae6ea9bc9183db8c5744e2f38",
|
||||
"outcomePassed": true,
|
||||
"comparisonStatus": "uncomparable",
|
||||
"scorerGuardPassed": true,
|
||||
"finalChatGuardPassed": true,
|
||||
"assessmentStatus": "verified",
|
||||
"turnAccounting": {
|
||||
"version": "paperclip.hiring-template-turn-accounting.v2",
|
||||
"counts": {
|
||||
"requiredWorkTurns": 5,
|
||||
"maximumCompletionTurns": 2,
|
||||
"maximumTotalTurns": 7,
|
||||
"requestedLeadTurns": 3,
|
||||
"coderTurns": 2,
|
||||
"completionTurns": 2,
|
||||
"unclassifiedTurns": 0,
|
||||
"snapshotRunCount": 7,
|
||||
"actualRunCount": 7,
|
||||
"costAccountingRunCount": 7
|
||||
},
|
||||
"predicates": [
|
||||
{
|
||||
"id": "known-fixture-context",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "complete-public-run-ledger",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "exact-five-required-work-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "successful-native-without-retries",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "resolved-company-account-and-identity",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "three-distinct-requested-chat-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "one-coder-execution-per-known-task",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "task-origins-are-first-two-requested-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "bounded-server-completion-receipts",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "completion-runs-have-attributed-chat-replies",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "no-notification-created-extra-tasks",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "completion-turns-only-report-actions",
|
||||
"passed": true
|
||||
}
|
||||
],
|
||||
"actionEvidence": {
|
||||
"status": "verified",
|
||||
"notificationRuns": 2,
|
||||
"canonicalExecutions": 10,
|
||||
"matchedNativeApiCalls": 5,
|
||||
"unknownExecutions": 0
|
||||
},
|
||||
"passed": true
|
||||
},
|
||||
"checks": [
|
||||
{
|
||||
"id": "one-coder-hire",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Exactly one permanent coding teammate reports to the lead."
|
||||
},
|
||||
{
|
||||
"id": "execution-account",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Hire keeps the lead model and managed account binding."
|
||||
},
|
||||
{
|
||||
"id": "two-worker-tasks",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Both independent tasks are completed by the same coder in the chosen project."
|
||||
},
|
||||
{
|
||||
"id": "bounded-work-and-completion-turns",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Exactly three requested lead turns and two coder executions, plus at most two strictly attributed completion notifications; every actual run remains counted."
|
||||
},
|
||||
{
|
||||
"id": "initial-json-artifact",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Independent computation checks each original input/value and worker authorship."
|
||||
},
|
||||
{
|
||||
"id": "reused-json-artifact",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "The reused coder applies the changed separator to every input."
|
||||
},
|
||||
{
|
||||
"id": "original-preserved",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Reuse preserves the original document, revision and task identity."
|
||||
},
|
||||
{
|
||||
"id": "source-fingerprints",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Served instruction and hiring source bytes match the evaluated revision."
|
||||
},
|
||||
{
|
||||
"id": "production-ceo-bundle",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The API-created lead receives this revision's default CEO bundle, without a fixture override."
|
||||
},
|
||||
{
|
||||
"id": "assigned-hiring-skill",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Production CEO defaults assign the hiring skill."
|
||||
},
|
||||
{
|
||||
"id": "production-source-reads",
|
||||
"dimension": "coverage",
|
||||
"passed": false,
|
||||
"detail": "Completed lead read receipts before the hire prove the explicitly requested skill, guide, checklist and coder example paths. Unrecognized or missing reads leave coverage uncomparable."
|
||||
},
|
||||
{
|
||||
"id": "supplied-coder-instructions",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Saved hired instructions use the source revision's coder example with its company/name placeholders filled."
|
||||
},
|
||||
{
|
||||
"id": "hired-instructions-durable",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The same saved instruction bundle survives the reused worker execution."
|
||||
},
|
||||
{
|
||||
"id": "hired-skills-durable",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The saved skill selections survive the reused worker execution."
|
||||
},
|
||||
{
|
||||
"id": "completion-action-attribution",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Notification actions require exact canonical/native identities; missing cross-namespace mapping is uncomparable, not proof of extra work."
|
||||
}
|
||||
]
|
||||
},
|
||||
"coverageUnchanged": true,
|
||||
"otherOutcomesUnchanged": true,
|
||||
"originalBytesUnchanged": true,
|
||||
"inputSha256": {
|
||||
"result": "44cbf64cfabd0257f31fb4d6909018d346f5667b0a7b3d7e855a2ece7e29c292",
|
||||
"hiringTemplate": "9609ddcb81c6bee3c5db97312138b4ea3e294da13ea540e41f363c826a514067",
|
||||
"apiState": "ef02af29bcf61ff3eace0549ca164a28c7950d1a3e14fc58c89bf9cb0f944b4d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"variant": "candidate",
|
||||
"profile": "runner-acpx-claude",
|
||||
"original": {
|
||||
"sourceRevision": "9f5404ad3aacbe76777952759414d34fd381e674",
|
||||
"graderVersion": "paperclip.hiring-templates.v1",
|
||||
"definitionDigest": "4d7f18420889325af89aa48eaaf021e8178aa6d26d504b92041635a15741f004",
|
||||
"machineStatus": "failed",
|
||||
"outcomePassed": false,
|
||||
"countCheckPassed": false,
|
||||
"comparisonStatus": "uncomparable"
|
||||
},
|
||||
"corrected": {
|
||||
"graderVersion": "paperclip.hiring-templates.v3",
|
||||
"definitionDigest": "cddbcb6b2f3050e065599011ace2faee048047fae6ea9bc9183db8c5744e2f38",
|
||||
"outcomePassed": false,
|
||||
"comparisonStatus": "uncomparable",
|
||||
"scorerGuardPassed": false,
|
||||
"finalChatGuardPassed": false,
|
||||
"assessmentStatus": "uncomparable",
|
||||
"turnAccounting": {
|
||||
"version": "paperclip.hiring-template-turn-accounting.v2",
|
||||
"counts": {
|
||||
"requiredWorkTurns": 5,
|
||||
"maximumCompletionTurns": 2,
|
||||
"maximumTotalTurns": 7,
|
||||
"requestedLeadTurns": 3,
|
||||
"coderTurns": 2,
|
||||
"completionTurns": 2,
|
||||
"unclassifiedTurns": 0,
|
||||
"snapshotRunCount": 7,
|
||||
"actualRunCount": 7,
|
||||
"costAccountingRunCount": 7
|
||||
},
|
||||
"predicates": [
|
||||
{
|
||||
"id": "known-fixture-context",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "complete-public-run-ledger",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "exact-five-required-work-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "successful-native-without-retries",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "resolved-company-account-and-identity",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "three-distinct-requested-chat-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "one-coder-execution-per-known-task",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "task-origins-are-first-two-requested-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "bounded-server-completion-receipts",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "completion-runs-have-attributed-chat-replies",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "no-notification-created-extra-tasks",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "completion-turns-only-report-actions",
|
||||
"passed": false
|
||||
}
|
||||
],
|
||||
"actionEvidence": {
|
||||
"status": "uncomparable",
|
||||
"notificationRuns": 2,
|
||||
"canonicalExecutions": 3,
|
||||
"matchedNativeApiCalls": 0,
|
||||
"unknownExecutions": 5
|
||||
},
|
||||
"passed": false
|
||||
},
|
||||
"checks": [
|
||||
{
|
||||
"id": "one-coder-hire",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Exactly one permanent coding teammate reports to the lead."
|
||||
},
|
||||
{
|
||||
"id": "execution-account",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Hire keeps the lead model and managed account binding."
|
||||
},
|
||||
{
|
||||
"id": "two-worker-tasks",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Both independent tasks are completed by the same coder in the chosen project."
|
||||
},
|
||||
{
|
||||
"id": "bounded-work-and-completion-turns",
|
||||
"dimension": "outcome",
|
||||
"passed": false,
|
||||
"detail": "Exactly three requested lead turns and two coder executions, plus at most two strictly attributed completion notifications; every actual run remains counted."
|
||||
},
|
||||
{
|
||||
"id": "initial-json-artifact",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Independent computation checks each original input/value and worker authorship."
|
||||
},
|
||||
{
|
||||
"id": "reused-json-artifact",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "The reused coder applies the changed separator to every input."
|
||||
},
|
||||
{
|
||||
"id": "original-preserved",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Reuse preserves the original document, revision and task identity."
|
||||
},
|
||||
{
|
||||
"id": "source-fingerprints",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Served instruction and hiring source bytes match the evaluated revision."
|
||||
},
|
||||
{
|
||||
"id": "production-ceo-bundle",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The API-created lead receives this revision's default CEO bundle, without a fixture override."
|
||||
},
|
||||
{
|
||||
"id": "assigned-hiring-skill",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Production CEO defaults assign the hiring skill."
|
||||
},
|
||||
{
|
||||
"id": "production-source-reads",
|
||||
"dimension": "coverage",
|
||||
"passed": false,
|
||||
"detail": "Completed lead read receipts before the hire prove the explicitly requested skill, guide, checklist and coder example paths. Unrecognized or missing reads leave coverage uncomparable."
|
||||
},
|
||||
{
|
||||
"id": "supplied-coder-instructions",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Saved hired instructions use the source revision's coder example with its company/name placeholders filled."
|
||||
},
|
||||
{
|
||||
"id": "hired-instructions-durable",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The same saved instruction bundle survives the reused worker execution."
|
||||
},
|
||||
{
|
||||
"id": "hired-skills-durable",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The saved skill selections survive the reused worker execution."
|
||||
},
|
||||
{
|
||||
"id": "completion-action-attribution",
|
||||
"dimension": "coverage",
|
||||
"passed": false,
|
||||
"detail": "Notification actions require exact canonical/native identities; missing cross-namespace mapping is uncomparable, not proof of extra work."
|
||||
}
|
||||
]
|
||||
},
|
||||
"coverageUnchanged": true,
|
||||
"otherOutcomesUnchanged": true,
|
||||
"originalBytesUnchanged": true,
|
||||
"inputSha256": {
|
||||
"result": "a83aecc26ef0af2e231cb364299cf0cb2f2ccd83b6998913dcd33be72d4c0824",
|
||||
"hiringTemplate": "4f83f523b955120c5baac18572dce752b278dbc9ff765b940c0e17c37ab28ae0",
|
||||
"apiState": "a208a3e3568fe9aacbec8d683a502a0d304a47571a1b97c229d2936e5d1bb087"
|
||||
}
|
||||
},
|
||||
{
|
||||
"variant": "baseline",
|
||||
"profile": "runner-codex",
|
||||
"original": {
|
||||
"sourceRevision": "296a4df85e8bcc97a160fc78c291b17adb828196",
|
||||
"graderVersion": "paperclip.hiring-templates.v1",
|
||||
"definitionDigest": "4d7f18420889325af89aa48eaaf021e8178aa6d26d504b92041635a15741f004",
|
||||
"machineStatus": "failed",
|
||||
"outcomePassed": false,
|
||||
"countCheckPassed": false,
|
||||
"comparisonStatus": "uncomparable"
|
||||
},
|
||||
"corrected": {
|
||||
"graderVersion": "paperclip.hiring-templates.v3",
|
||||
"definitionDigest": "cddbcb6b2f3050e065599011ace2faee048047fae6ea9bc9183db8c5744e2f38",
|
||||
"outcomePassed": true,
|
||||
"comparisonStatus": "uncomparable",
|
||||
"scorerGuardPassed": true,
|
||||
"finalChatGuardPassed": true,
|
||||
"assessmentStatus": "verified",
|
||||
"turnAccounting": {
|
||||
"version": "paperclip.hiring-template-turn-accounting.v2",
|
||||
"counts": {
|
||||
"requiredWorkTurns": 5,
|
||||
"maximumCompletionTurns": 2,
|
||||
"maximumTotalTurns": 7,
|
||||
"requestedLeadTurns": 3,
|
||||
"coderTurns": 2,
|
||||
"completionTurns": 2,
|
||||
"unclassifiedTurns": 0,
|
||||
"snapshotRunCount": 7,
|
||||
"actualRunCount": 7,
|
||||
"costAccountingRunCount": 7
|
||||
},
|
||||
"predicates": [
|
||||
{
|
||||
"id": "known-fixture-context",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "complete-public-run-ledger",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "exact-five-required-work-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "successful-native-without-retries",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "resolved-company-account-and-identity",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "three-distinct-requested-chat-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "one-coder-execution-per-known-task",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "task-origins-are-first-two-requested-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "bounded-server-completion-receipts",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "completion-runs-have-attributed-chat-replies",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "no-notification-created-extra-tasks",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "completion-turns-only-report-actions",
|
||||
"passed": true
|
||||
}
|
||||
],
|
||||
"actionEvidence": {
|
||||
"status": "verified",
|
||||
"notificationRuns": 2,
|
||||
"canonicalExecutions": 11,
|
||||
"matchedNativeApiCalls": 7,
|
||||
"unknownExecutions": 0
|
||||
},
|
||||
"passed": true
|
||||
},
|
||||
"checks": [
|
||||
{
|
||||
"id": "one-coder-hire",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Exactly one permanent coding teammate reports to the lead."
|
||||
},
|
||||
{
|
||||
"id": "execution-account",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Hire keeps the lead model and managed account binding."
|
||||
},
|
||||
{
|
||||
"id": "two-worker-tasks",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Both independent tasks are completed by the same coder in the chosen project."
|
||||
},
|
||||
{
|
||||
"id": "bounded-work-and-completion-turns",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Exactly three requested lead turns and two coder executions, plus at most two strictly attributed completion notifications; every actual run remains counted."
|
||||
},
|
||||
{
|
||||
"id": "initial-json-artifact",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Independent computation checks each original input/value and worker authorship."
|
||||
},
|
||||
{
|
||||
"id": "reused-json-artifact",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "The reused coder applies the changed separator to every input."
|
||||
},
|
||||
{
|
||||
"id": "original-preserved",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Reuse preserves the original document, revision and task identity."
|
||||
},
|
||||
{
|
||||
"id": "source-fingerprints",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Served instruction and hiring source bytes match the evaluated revision."
|
||||
},
|
||||
{
|
||||
"id": "production-ceo-bundle",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The API-created lead receives this revision's default CEO bundle, without a fixture override."
|
||||
},
|
||||
{
|
||||
"id": "assigned-hiring-skill",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Production CEO defaults assign the hiring skill."
|
||||
},
|
||||
{
|
||||
"id": "production-source-reads",
|
||||
"dimension": "coverage",
|
||||
"passed": false,
|
||||
"detail": "Completed lead read receipts before the hire prove the explicitly requested skill, guide, checklist and coder example paths. Unrecognized or missing reads leave coverage uncomparable."
|
||||
},
|
||||
{
|
||||
"id": "supplied-coder-instructions",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Saved hired instructions use the source revision's coder example with its company/name placeholders filled."
|
||||
},
|
||||
{
|
||||
"id": "hired-instructions-durable",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The same saved instruction bundle survives the reused worker execution."
|
||||
},
|
||||
{
|
||||
"id": "hired-skills-durable",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The saved skill selections survive the reused worker execution."
|
||||
},
|
||||
{
|
||||
"id": "completion-action-attribution",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Notification actions require exact canonical/native identities; missing cross-namespace mapping is uncomparable, not proof of extra work."
|
||||
}
|
||||
]
|
||||
},
|
||||
"coverageUnchanged": true,
|
||||
"otherOutcomesUnchanged": true,
|
||||
"originalBytesUnchanged": true,
|
||||
"inputSha256": {
|
||||
"result": "d912d8400bfff4f7f922aa6b264f9ce5fff1ba2d893abf03790c4cfea352261f",
|
||||
"hiringTemplate": "88b23e4d2cc8831a5d88b8479cc69c0d1c2e17a6a5243707b4bd19716a244aca",
|
||||
"apiState": "dff6bb13bb54d45024966d9cfbeb6dbe6f2241e674b0b1350c1384863202a97f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"variant": "baseline",
|
||||
"profile": "runner-acpx-claude",
|
||||
"original": {
|
||||
"sourceRevision": "296a4df85e8bcc97a160fc78c291b17adb828196",
|
||||
"graderVersion": "paperclip.hiring-templates.v1",
|
||||
"definitionDigest": "4d7f18420889325af89aa48eaaf021e8178aa6d26d504b92041635a15741f004",
|
||||
"machineStatus": "failed",
|
||||
"outcomePassed": false,
|
||||
"countCheckPassed": false,
|
||||
"comparisonStatus": "uncomparable"
|
||||
},
|
||||
"corrected": {
|
||||
"graderVersion": "paperclip.hiring-templates.v3",
|
||||
"definitionDigest": "cddbcb6b2f3050e065599011ace2faee048047fae6ea9bc9183db8c5744e2f38",
|
||||
"outcomePassed": false,
|
||||
"comparisonStatus": "uncomparable",
|
||||
"scorerGuardPassed": false,
|
||||
"finalChatGuardPassed": false,
|
||||
"assessmentStatus": "uncomparable",
|
||||
"turnAccounting": {
|
||||
"version": "paperclip.hiring-template-turn-accounting.v2",
|
||||
"counts": {
|
||||
"requiredWorkTurns": 5,
|
||||
"maximumCompletionTurns": 2,
|
||||
"maximumTotalTurns": 7,
|
||||
"requestedLeadTurns": 3,
|
||||
"coderTurns": 2,
|
||||
"completionTurns": 2,
|
||||
"unclassifiedTurns": 0,
|
||||
"snapshotRunCount": 7,
|
||||
"actualRunCount": 7,
|
||||
"costAccountingRunCount": 7
|
||||
},
|
||||
"predicates": [
|
||||
{
|
||||
"id": "known-fixture-context",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "complete-public-run-ledger",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "exact-five-required-work-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "successful-native-without-retries",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "resolved-company-account-and-identity",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "three-distinct-requested-chat-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "one-coder-execution-per-known-task",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "task-origins-are-first-two-requested-turns",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "bounded-server-completion-receipts",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "completion-runs-have-attributed-chat-replies",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "no-notification-created-extra-tasks",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "completion-turns-only-report-actions",
|
||||
"passed": false
|
||||
}
|
||||
],
|
||||
"actionEvidence": {
|
||||
"status": "uncomparable",
|
||||
"notificationRuns": 2,
|
||||
"canonicalExecutions": 3,
|
||||
"matchedNativeApiCalls": 0,
|
||||
"unknownExecutions": 4
|
||||
},
|
||||
"passed": false
|
||||
},
|
||||
"checks": [
|
||||
{
|
||||
"id": "one-coder-hire",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Exactly one permanent coding teammate reports to the lead."
|
||||
},
|
||||
{
|
||||
"id": "execution-account",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Hire keeps the lead model and managed account binding."
|
||||
},
|
||||
{
|
||||
"id": "two-worker-tasks",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Both independent tasks are completed by the same coder in the chosen project."
|
||||
},
|
||||
{
|
||||
"id": "bounded-work-and-completion-turns",
|
||||
"dimension": "outcome",
|
||||
"passed": false,
|
||||
"detail": "Exactly three requested lead turns and two coder executions, plus at most two strictly attributed completion notifications; every actual run remains counted."
|
||||
},
|
||||
{
|
||||
"id": "initial-json-artifact",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Independent computation checks each original input/value and worker authorship."
|
||||
},
|
||||
{
|
||||
"id": "reused-json-artifact",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "The reused coder applies the changed separator to every input."
|
||||
},
|
||||
{
|
||||
"id": "original-preserved",
|
||||
"dimension": "outcome",
|
||||
"passed": true,
|
||||
"detail": "Reuse preserves the original document, revision and task identity."
|
||||
},
|
||||
{
|
||||
"id": "source-fingerprints",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Served instruction and hiring source bytes match the evaluated revision."
|
||||
},
|
||||
{
|
||||
"id": "production-ceo-bundle",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The API-created lead receives this revision's default CEO bundle, without a fixture override."
|
||||
},
|
||||
{
|
||||
"id": "assigned-hiring-skill",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "Production CEO defaults assign the hiring skill."
|
||||
},
|
||||
{
|
||||
"id": "production-source-reads",
|
||||
"dimension": "coverage",
|
||||
"passed": false,
|
||||
"detail": "Completed lead read receipts before the hire prove the explicitly requested skill, guide, checklist and coder example paths. Unrecognized or missing reads leave coverage uncomparable."
|
||||
},
|
||||
{
|
||||
"id": "supplied-coder-instructions",
|
||||
"dimension": "coverage",
|
||||
"passed": false,
|
||||
"detail": "Saved hired instructions use the source revision's coder example with its company/name placeholders filled."
|
||||
},
|
||||
{
|
||||
"id": "hired-instructions-durable",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The same saved instruction bundle survives the reused worker execution."
|
||||
},
|
||||
{
|
||||
"id": "hired-skills-durable",
|
||||
"dimension": "coverage",
|
||||
"passed": true,
|
||||
"detail": "The saved skill selections survive the reused worker execution."
|
||||
},
|
||||
{
|
||||
"id": "completion-action-attribution",
|
||||
"dimension": "coverage",
|
||||
"passed": false,
|
||||
"detail": "Notification actions require exact canonical/native identities; missing cross-namespace mapping is uncomparable, not proof of extra work."
|
||||
}
|
||||
]
|
||||
},
|
||||
"coverageUnchanged": true,
|
||||
"otherOutcomesUnchanged": true,
|
||||
"originalBytesUnchanged": true,
|
||||
"inputSha256": {
|
||||
"result": "844763ddc83461b2bea620f153f4bfe0bfee20a27bf33a60017a45c774ce2f6a",
|
||||
"hiringTemplate": "27b153798472e206970c46358febdda363cb75f892734757e76994117f0d2c9b",
|
||||
"apiState": "441cf7272ea28c5e1182adf48dba09a401e88c6cad3a99e72c8cf3f46b1c5b41"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,51 @@
|
||||
# Legacy document skill repair qualification — 2026-10-02
|
||||
|
||||
**TL;DR: one newly passing case (Claude original document delivery), one unchanged pass (Claude explicit storage), two unchanged OpenCode failures, and no pending pairs or new overall machine failures. OpenCode's explicit-case clickable handoff nevertheless got worse:** baseline supplied a clickable API document URL, while candidate supplied only a code-formatted path. Both fail the original UI-only link oracle, whose intended route was narrower than the request's "usable link" wording. This result does not establish general non-regression.
|
||||
|
||||
This focused repair keeps the eight-word manual, reduced shared prompts and merged Codex base fix #14920 fixed. It varies only the early operational Paperclip skill recipe and the presence of the new `issue-documents.md` reference. The earlier combined prompt-removal comparison retains its two new classic Claude/OpenCode storage failures; this trial repairs Claude's original case but leaves OpenCode's original regression unresolved.
|
||||
|
||||
| Classic profile | Case | Pre-fix skill → repaired skill | Retained evidence |
|
||||
| --- | --- | --- | --- |
|
||||
| Claude | Original assigned skill | Fail → Pass | Baseline lacks a durable task document; candidate saves one containing the skill marker. |
|
||||
| Claude | Explicit Paperclip document | Pass → Pass | Both save the document/revision and provide a canonical UI link. |
|
||||
| OpenCode | Original assigned skill | Fail → Fail | Both write a workspace file rather than a public task document. Candidate requires automatic disposition recovery. |
|
||||
| OpenCode | Explicit Paperclip document | Fail → Fail | Both save the document/revision. Baseline provides a clickable API URL; candidate gives a code-formatted UI path without an anchor. Original UI-link checks fail in both. |
|
||||
|
||||
Original machine verdicts and evidence hashes are retained in the [safe evidence projection](2026-10-02-legacy-document-skill-repair.json). No model was rerun to improve these results.
|
||||
|
||||
## Frozen sources and public reports
|
||||
|
||||
- [Repaired skill campaign](https://github.com/paperclipai/paperclip/actions/runs/37060885547), source `abd0b628ca642c09a54a4edc56a5227402f6686e`, branch `codex/legacy-document-repaired-skill`. [Published candidate report](https://d1p6rlowie26tp.cloudfront.net/runner-e2e/campaigns/gha-37060885547-1/index.html).
|
||||
- [Pre-fix skill campaign](https://github.com/paperclipai/paperclip/actions/runs/37060888047), source `bc83fe030234439ac51279502a28803958963e2e`, branch `codex/legacy-document-pre-fix-skill`. [Published baseline report](https://d1p6rlowie26tp.cloudfront.net/runner-e2e/campaigns/gha-37060888047-1/index.html).
|
||||
- Both trusted default-branch workflows ran concurrently on separate concurrency targets. All eight cells pass their exact-source prerequisite gate before providers; all eight clean up successfully. Failed campaigns are published successfully and remain failed.
|
||||
- The four model/effort/auth/tool/permission/environment configurations match. Models are `claude-sonnet-4-6` and `openrouter/deepseek/deepseek-v4-flash-0731`. The comparison records 203 byte-identical fixture/behavior sources and explicit present/absent skill-source fingerprints. The new reference is absent from the baseline, rather than silently introduced into it.
|
||||
- Local admission preparation passed 909 E2E support tests, E2E typecheck and four-cell discovery; the candidate exact-source gate passed 571 checks, including one Rust check. Initial sandbox and obsolete catalog-count attempts remain retained. The original request and behavioral oracle were not weakened.
|
||||
|
||||
## What OpenCode actually received
|
||||
|
||||
The repaired original assignment invokes only its assigned Context integrity output skill, then writes `task-document.md`. There is no operational Paperclip skill load or issue-document reference read before that local write. A separate automatic `issue_disposition_repair` run then loads Paperclip. Its returned content contains the new early saved-document paragraph and reference pointer even though the overall skill result is marked truncated; it never reads the reference or saves a document, and only finalizes the issue's status.
|
||||
|
||||
This is evidence of operational-skill selection after work, not evidence that the assignment saw and ignored the new recipe. The pre-fix original run also writes locally before loading the old Paperclip skill. Whether the adapter should activate operational Paperclip guidance before work is a production-delivery question for review; expanding a skill the first assignment never opened would not address the observed sequence.
|
||||
|
||||
The explicit candidate loads the assigned skill, then Paperclip, receives the new early paragraph, reads `issue-documents.md`, and calls the document API successfully. Its saved document has a persisted revision. The remaining defect is its comment's non-clickable path. Baseline supplied an actual Markdown link to the issue-document API GET endpoint. Read-only source review confirms that this endpoint returns the document in board context, and the unprefixed UI issue route redirects through the selected company's prefix while preserving the document hash. Actual authenticated browser navigation against these cleaned-up measured instances was not replayed, so neither URL is classified as a proven broken route. Candidate's missing clickable anchor is directly observable.
|
||||
|
||||
A future explicit fixture should name a clickable Paperclip UI document link if that is the intended contract. This clarification would not retroactively pass or fail either original result. A short canonical Markdown UI-link example can be considered separately from operational-skill delivery. No further production instruction change or paid rerun was made for this diagnosis.
|
||||
|
||||
## Runs, timing and cost
|
||||
|
||||
Eight provider turns were expected; nine actual runs are retained. Candidate OpenCode's original assignment succeeds but leaves the task `in_progress`, triggering automatic disposition recovery. The recovery succeeds and marks it done, without creating the missing document. Both runs, their time and cost are counted rather than selecting a better attempt.
|
||||
|
||||
| Profile | Case | Baseline provider seconds | Candidate provider seconds | Baseline reported USD | Candidate reported USD |
|
||||
| --- | --- | ---: | ---: | ---: | ---: |
|
||||
| legacy-claude | Original | 23.032 | 27.702 | 0.1668394500 | 0.2267947500 |
|
||||
| legacy-claude | Explicit storage | 34.999 | 40.513 | 0.1997571000 | 0.2561425500 |
|
||||
| legacy-opencode | Original | 36.770 | 68.032 | 0.0033927116 | 0.0047148437 |
|
||||
| legacy-opencode | Explicit storage | 206.191 | 46.300 | 0.0102644593 | 0.0042468724 |
|
||||
|
||||
Reported LLM totals are $0.3802537209 baseline and $0.4918990161 candidate. All four baseline and five candidate ledger rows contain token usage and reported cost. Local runtime is unmetered and actual external billing is not established. Single trials, the additional recovery and baseline OpenCode's long explicit-case execution prevent a general timing or spending conclusion.
|
||||
|
||||
## PR maintenance and remaining qualifications
|
||||
|
||||
The skill insertion shifted generated capability heading anchors. General PR CI detected stale `capabilities.yaml`; the paid workflow's narrower build and every exact-source admission still passed before provider access. Both canonical capability metadata inventories are regenerated after measurement, include the new reference where declared, and adds a credential-free admission test that rejects stale/missing manifests. These maintenance changes do not modify the frozen measured branches or the skill bytes used in this comparison.
|
||||
|
||||
Draft [PR #14948](https://github.com/paperclipai/paperclip/pull/14948) keeps this repair and the tiny manual; draft [PR #14961](https://github.com/paperclipai/paperclip/pull/14961) measures native completion documentation separately. OpenCode original delivery remains unresolved, and existing Claude chat-memory and legacy ACP credential/receipt findings remain separate. No broad matrix rerun was launched, and merging remains user-controlled.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,75 @@
|
||||
# OpenCode skill routing and clickable delivery — 2026-10-02
|
||||
|
||||
**TL;DR: 0 newly failing cells, 0 newly passing cells, 2 unchanged passes, and 0 pending pairs in this matched trial. Both document cases pass in both variants, so this does not establish that the metadata/link change caused the earlier failures to recover. Handoff remains imperfect in the original case:** candidate uses a clickable link with the wrong `PAP` company prefix; baseline supplies only a bare prefix-less slug path. Neither defect is scored by the preserved original document-storage oracle. The explicit clickable-link cases deliver the correct `RUN` link in both variants.
|
||||
|
||||
The narrow correction expands the operational skill's stock discovery description to include Paperclip task/heartbeat work and document/file delivery. The early API recipe asks for a clickable Markdown link, and the reference shows a canonical UI-link example. The full skill stays on disk; the eight-word manual and shared prompts remain reduced. No full-body injection, adapter policy override or native tool guidance is added to legacy runs.
|
||||
|
||||
| Classic OpenCode case | Pre-correction skill | Corrected skill | Independent evidence and limitation |
|
||||
| --- | --- | --- | --- |
|
||||
| Original assigned skill, verbatim request | Pass | Pass | Exactly one saved document with skill-only marker and persisted revision in each. Both handoff paths are deficient and outside this case's link-free oracle. |
|
||||
| Explicit Paperclip storage with clarified clickable UI link | Pass | Pass | Saved revision/content and exact clickable `/RUN/issues/RUN-1#document-context-integrity-output` in both. |
|
||||
|
||||
Original machine results, source/configuration proof, tool-read sequence, costs and evidence hashes are preserved in the [safe evidence projection](2026-10-02-opencode-skill-routing-link-qualification.json). All four cells clean up successfully; none uses automatic disposition recovery or an extra attempt. No provider was rerun to improve these results.
|
||||
|
||||
## Sources, admission and inspectable reports
|
||||
|
||||
- Candidate `fe9dc1e3c518825242ed889ab9c8352986f8c2ed`, frozen branch `codex/opencode-stock-routing-qualified`: [workflow](https://github.com/paperclipai/paperclip/actions/runs/37069547401), [published dashboard and screenshots](https://d1p6rlowie26tp.cloudfront.net/runner-e2e/campaigns/gha-37069547401-1/index.html).
|
||||
- Baseline `0d7ecfa96d72fba79b7f0a25052b42c0686c0488`, frozen branch `codex/opencode-pre-routing-skill`: [workflow](https://github.com/paperclipai/paperclip/actions/runs/37069552374), [published dashboard and screenshots](https://d1p6rlowie26tp.cloudfront.net/runner-e2e/campaigns/gha-37069552374-1/index.html).
|
||||
- Trusted default-branch workflows ran concurrently on separate targets. Each of four measured cells passed its own exact-source, credential-free 587-assertion prerequisite before provider credentials. Candidate source fingerprint is `aeb792549146cc29eda779e83ae22d336276e31162f738a1dde85a82e7833902`; baseline is `0315602ac6788cf0d65561a73cc85d790768ef90c6815257077a2381f961b96e`.
|
||||
- Git tree comparison verifies 8,242 identical tracked files. Only the two operational skill sources and an explicit baseline provenance receipt differ. Models, effort, tools, auth, permissions, budgets, manual/shared prompts and behavioral fixtures/graders are fixed; both fixture configuration hashes match. Suite/source digest differences correctly include the changed skill bytes.
|
||||
- Local source preparation passed 918 E2E support tests, eight canonical inventory calibrations, skill validation, E2E typecheck, two-cell discovery and both exact-source prerequisites. Both capability inventories use the same declared source list. Initial setup mistakes remain retained and did not reach providers.
|
||||
|
||||
The explicit fixture now asks for a clickable company-prefixed Paperclip UI document link, matching its intended oracle. Bare paths and code-formatted paths fail calibration; correct relative/same-app absolute links pass. The original assigned-skill request and procedure remain verbatim. Earlier API-link grades are not retroactively changed, and these explicit results are not a direct repeat of the older “usable link” cohort.
|
||||
|
||||
## Which guidance was read before delivery
|
||||
|
||||
In the candidate original assignment, OpenCode loads the assigned Context integrity output skill, then operational Paperclip before any document write. The returned Paperclip skill is marked truncated, but the early saved-document recipe and reference pointer are visible. It reads `issue-documents.md`, then writes the public task document through the API. This differs from the previously failing candidate, which first loaded Paperclip only in a separate disposition-recovery run after its local-only output.
|
||||
|
||||
The current baseline original assignment first loads the assigned output skill and writes a workspace Markdown file. It later loads Paperclip and reads the issue-document reference, then saves a public document before completing the **same** assignment. Thus its successful delivery did not require improved metadata; stock selection timing varies between trials. Both explicit variants load Paperclip and read the reference before the document API call. Four rendered final-state screenshots were inspected, and public document revisions/content and completion comments agree with the retained results.
|
||||
|
||||
The candidate original comment has an actual Markdown href `/PAP/issues/RUN-1#document-context-integrity-output`, despite the measured company prefix being `RUN`. Baseline's original comment has a bare `/issues/runner-e2e-…#document-task-document` path without a Markdown anchor. The candidate's literal prefix matches the reference's example, making example copying a plausible cause, but this trial does not isolate that cause. Authenticated click navigation was not replayed after cleanup; neither original link is presented as a verified usable handoff. A minimal follow-up can show an issue-derived prefix instead of a literal company example. These original machine passes must not be presented as fully correct links.
|
||||
|
||||
## Timing, usage and cost
|
||||
|
||||
Four provider turns were expected and four assignment runs occurred. All four ledger receipts contain token usage and reported LLM cost. Local runtime remains unmetered; actual external billing is not established.
|
||||
|
||||
| Case | Baseline provider seconds | Candidate provider seconds | Baseline cell seconds | Candidate cell seconds | Baseline reported USD | Candidate reported USD |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| Original | 25.870 | 67.897 | 46.285 | 89.455 | 0.0058472545 | 0.0041383700 |
|
||||
| Explicit clickable storage | 73.984 | 82.751 | 94.804 | 102.901 | 0.0049436470 | 0.0066440790 |
|
||||
|
||||
Reported LLM totals are **$0.0107909015 baseline** and **$0.0107824490 candidate**. The candidate original case takes longer in this trial; these single observations, different cache/token receipts and unmetered runtime do not establish general speed, cost or quality equivalence.
|
||||
|
||||
## Preserved failures and scope
|
||||
|
||||
The [original combined prompt-removal report](2026-10-02-stock-harness-live-comparison.md) still records two new classic Claude/OpenCode document-delivery failures, two OpenCode ordered-case improvements, and separate unchanged credential/chat failures. The [first recipe repair report](2026-10-02-legacy-document-skill-repair.md) still records Claude's improvement, OpenCode's unresolved local-only output, and the candidate's worse non-clickable explicit handoff. New successes do not erase those earlier observations or establish robustness.
|
||||
|
||||
This comparison qualifies only classic OpenCode's two selected journeys. Existing classic Codex/Claude/OpenCode and ACP Codex skill mounts were audited; ACP Claude's names/root-only metadata and unsupported custom ACP delivery remain separate. Hermes/Pi/OpenClaw are not live-qualified here. Native completion descriptions and hiring templates have separate changes and measurements. Both PRs remain draft; no merge or further paid breadth is part of this report.
|
||||
|
||||
## Provider-free follow-up after measurement
|
||||
|
||||
The approved reference-only follow-up replaces the literal `PAP` link with a
|
||||
JavaScript construction using the current `issue.identifier` and the successful
|
||||
write receipt's `saved.key`. It is a later source change, **not live-qualified**
|
||||
by the frozen candidate above. The independent storage grader now calibrates a
|
||||
wrong-company link as failure, and executes the shipped recipe against two
|
||||
other company prefixes plus a locked-write redirected document key. All 25
|
||||
focused document/source-digest tests, eight inventory calibrations, canonical
|
||||
generated metadata checks and E2E typecheck pass. An initial sandboxed support attempt denied localhost/tsx
|
||||
pipe creation; the failed attempt is retained, with its affected files rerun
|
||||
unchanged under permitted local execution: 906 assertions passed in the original
|
||||
921-assertion attempt, and all 82 selected assertions passed in the permitted
|
||||
retry, including every originally failed assertion. No new model calls or old verdict
|
||||
changes are part of this follow-up.
|
||||
|
||||
Fresh native-PR review caught a supported edge case in that later recipe:
|
||||
`issue.identifier` may be null. The recipe now uses the current issue ID in the
|
||||
unprefixed UI route when no identifier exists. Provider-free tests cover both
|
||||
null and absent identifiers, preserving the returned document key. Source review
|
||||
confirms that the board resolves the loaded issue's actual company and preserves
|
||||
the document hash; no agent-side company fetch is needed. This route logic also
|
||||
corrects wrong prefixes, so the frozen candidate's `PAP` href is noncanonical,
|
||||
not a proven broken link. No authenticated click replay or further model run was
|
||||
made. The previous 4/5 review and local setup/timing failures remain retained;
|
||||
current focused document/source/manifest/admission checks pass 56 assertions and
|
||||
E2E typecheck passes. This correction still has no live qualification.
|
||||
@@ -0,0 +1,357 @@
|
||||
{
|
||||
"schema": "paperclip.stock-harness.safe-comparison/v1",
|
||||
"candidateSha": "f02d8d0df327abb43b20c7e7beb86798239abbf5",
|
||||
"baselineSha": "12c5433c67dc2e62916b879349c7ba2b6e0431f0",
|
||||
"nativeCodexFixHeldConstant": "408f70e69f9c5e49cb4377f4886ac2001bfa67a2",
|
||||
"comparisonManifest": {
|
||||
"schema": "paperclip.stock-harness-comparison.v1",
|
||||
"variant": "previous-instructions",
|
||||
"candidateSha": "f02d8d0df327abb43b20c7e7beb86798239abbf5",
|
||||
"restoredInstructionsFrom": "e00d10d5d5594f6e2d1e8cf4e0ec81c074b0bd88",
|
||||
"nativeCodex14920": "held-constant; no before/after performance claim",
|
||||
"productionDifferences": [
|
||||
"server/src/onboarding-assets/default/AGENTS.md",
|
||||
"packages/adapter-utils/src/server-utils.ts"
|
||||
],
|
||||
"structuralOracle": "Historical manual exact content and observed generic procedures; independent behavioral graders unchanged",
|
||||
"expectedMatrixCells": 24,
|
||||
"expectedProviderTurns": 48,
|
||||
"maxInitialCampaignsPerVariant": 1,
|
||||
"behavioralSources": {
|
||||
"context-integrity-cases.ts": "201d05f8257c1121a94dd9d8a22f08b9db80555e388013ffeabb65e301499fd2",
|
||||
"context-integrity-scoring.ts": "50d370044e45386ba87fb23ca63c228b96bb8373d67bb7e82e8b2a89eb23f252",
|
||||
"context-integrity-flow.ts": "63bcb7bdba4c80b15ff5872f16383844456eab0c5a10627da5d3487a26821c6a",
|
||||
"chat-cases.ts": "754b251bdd42605e147da9081b663329234260d2b13df0df3d44562e362b14ca",
|
||||
"chat-flow.ts": "11fc506559da61315ce04a877f6b38a055e71e532fecfcf62c749a1d3bf985e7",
|
||||
"live-fixtures.ts": "1ef80209cc53cc514a8e58af4c2225e38ec15b20acf17ca7564506ca6617ac3e",
|
||||
"runner.spec.ts": "14d57e0f9131b3f1981ac593464738d01362008eefe85ea836075c794681fa85",
|
||||
"launch.ts": "5ada6ca5484542f028b29fc603dc7992213d4c06fa9046834bd7d23c4511b847",
|
||||
"stock-harness-admission.ts": "c9775d77d48f8d59ba1197a7e5769f14db0c7d33f5e2a2968a04466e083d63ff",
|
||||
"types.ts": "c45dc3b2366457449ec9e7d413f930605dec88ea93a14ce5c5a5f445a27d148c"
|
||||
},
|
||||
"instructionSha256": {
|
||||
"manual": "e4d2375d722602cd744403292d99f6e6c9b7e8014d9cdfa6f9811ace03428e5f",
|
||||
"shared": "819026782b92f37064fca354ddbca685b3c05f1869ec560e40d8a757b0a8aee9"
|
||||
}
|
||||
},
|
||||
"modelsEffortToolsPermissionsCredentials": "same fixture/source configuration; effort inherits provider default when unspecified",
|
||||
"expectedCellsPerVariant": 24,
|
||||
"expectedProviderTurnsPerVariant": 48,
|
||||
"baseline": [
|
||||
{"executionId":"stock-harness.legacy-acp-claude.local.assigned-skill-explicit-invocation","profile":"legacy-acp-claude","case":"assigned-skill-explicit-invocation","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042864888","cellAttempt":1,"status":"failed","failureClass":"secret_leak","causeCodes":["persisted_credential_guard"],"failedMatcherPaths":[],"model":"claude-sonnet-4-6","runtimeMode":"legacy","durationMs":59075,"providerDurationMs":42037,"cleanup":"passed","billing":{"llm":{"runCount":1,"runsWithTokenUsage":1,"runsWithReportedCost":1,"inputTokens":15184,"outputTokens":2261,"cachedInputTokens":218341,"totalTokens":235786,"reportedCostUsd":0.16114005000000003,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":42037,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.16114005000000003,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.16114005000000003,"complete":true},"billingTypes":["metered_api"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"historical-generic-procedures-observed":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":14137,"reportedPromptChars":13607,"freshPromptChars":4688,"retrievalClipped":false}],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"78f6add6f11a45f58bfc6fee81600871f888750986c27497563a79eba39a101c","evidence-manifest.json":"14f67d036a3663f606cf269890f5a36a18221e5c7dcb466f513f761d882fb6eb","snapshots/context-integrity.json":"93bba435c845af31707d4e4b84b2330cd8ff14045a76c2d1267b958c8133950b","snapshots/stock-harness-hire.json":"e8b85aa909fc6af6a72b180c79572d70a26dd6ee6307c9b985f80b928eb96170","snapshots/stock-harness.json":"fa0eb10eb3939b91d83fed8a8c9f9915bb407ab03a5a9bd7e5df99aa593ffb99","snapshots/stock-harness-preflight.json":"2ac069369e2d26b0e4c213966770962c0282f9a7665fa1297fe3c70df2c73960"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":7,"failed":0}},
|
||||
{"executionId":"stock-harness.legacy-acp-claude.local.continuity-restart","profile":"legacy-acp-claude","case":"continuity-restart","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042864888","cellAttempt":1,"status":"failed","failureClass":"secret_leak","causeCodes":["persisted_credential_guard","structural_receipt_incomplete"],"failedMatcherPaths":["stockHarness.fresh-default-delivered"],"model":"claude-sonnet-4-6","runtimeMode":"legacy","durationMs":36961,"providerDurationMs":19492,"cleanup":"passed","billing":{"llm":{"runCount":2,"runsWithTokenUsage":2,"runsWithReportedCost":2,"inputTokens":19069,"outputTokens":329,"cachedInputTokens":55518,"totalTokens":74916,"reportedCostUsd":0.1039979,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":19492,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.1039979,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.1039979,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"historical-generic-procedures-observed":true,"fresh-default-delivered":false},"invocationMetrics":[{"capturedPromptChars":16395,"reportedPromptChars":17606,"freshPromptChars":1462,"retrievalClipped":true},{"capturedPromptChars":16395,"reportedPromptChars":17638,"freshPromptChars":1462,"retrievalClipped":true}],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"b00296e015e0d35a213e031aede08732f900cf8aaa0f67dd6adc30310871a9f4","evidence-manifest.json":"101cb2a051bb64d3fc12e3a5389c2f39be4acb85e120b9bca1557163643f32e8","snapshots/context-integrity.json":null,"snapshots/stock-harness-hire.json":"df3b7ce86f99b1f31ce3061e637e936d40a68b63d49aa307cf4a9dcfa75b3983","snapshots/stock-harness.json":"cffaf9caec42246841497abf2059ee2d11c23a67971ad14112fa96beb23d302a","snapshots/stock-harness-preflight.json":"7da7c7be4d0c33c6d1c3fadeb500eebe3fceba797edd0f24a9db187091896b6b"}},
|
||||
{"executionId":"stock-harness.legacy-acp-claude.local.ordered-comment-continuation","profile":"legacy-acp-claude","case":"ordered-comment-continuation","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042864888","cellAttempt":1,"status":"failed","failureClass":"secret_leak","causeCodes":["persisted_credential_guard","structural_receipt_incomplete"],"failedMatcherPaths":["stockHarness.fresh-default-delivered"],"model":"claude-sonnet-4-6","runtimeMode":"legacy","durationMs":169818,"providerDurationMs":150645,"cleanup":"passed","billing":{"llm":{"runCount":2,"runsWithTokenUsage":2,"runsWithReportedCost":2,"inputTokens":49679,"outputTokens":7557,"cachedInputTokens":465247,"totalTokens":522483,"reportedCostUsd":0.45116585000000003,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":150645,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.45116585000000003,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.45116585000000003,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"historical-generic-procedures-observed":true,"fresh-default-delivered":false},"invocationMetrics":[{"capturedPromptChars":16433,"reportedPromptChars":21108,"freshPromptChars":4688,"retrievalClipped":true},{"capturedPromptChars":14499,"reportedPromptChars":14021,"freshPromptChars":4688,"retrievalClipped":false}],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"9994318ef5b3cac9143215d580f7551600777864f3d54a0cb2c48941a0799523","evidence-manifest.json":"79ba61a1c2ac3adde8d1905f09a051d0b711be7c618285b728d13f3650fe9d64","snapshots/context-integrity.json":"0566e89e716c742ee4452f5d9bd719354dba968459809e3e362794f55135b705","snapshots/stock-harness-hire.json":"05919c3be635604a40c0ea9bb76ab1ce1a1ce6545417e66e02c5a26881f082ee","snapshots/stock-harness.json":"6fdbee98a58b20e131a46a5bf9220c1d9e6c4cfc02d64baeee700627869b137c","snapshots/stock-harness-preflight.json":"cb4abccc23ebbe0cd4884ca1d5238eef14b82e15987a2d9e7083ccb608504189"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":12,"failed":0}},
|
||||
{"executionId":"stock-harness.legacy-acp-codex.local.assigned-skill-explicit-invocation","profile":"legacy-acp-codex","case":"assigned-skill-explicit-invocation","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042864888","cellAttempt":1,"status":"failed","failureClass":"secret_leak","causeCodes":["persisted_credential_guard"],"failedMatcherPaths":[],"model":"gpt-5.6-sol","runtimeMode":"legacy","durationMs":59328,"providerDurationMs":35548,"cleanup":"passed","billing":{"llm":{"runCount":1,"runsWithTokenUsage":1,"runsWithReportedCost":0,"inputTokens":511,"outputTokens":30,"cachedInputTokens":27646,"totalTokens":28187,"reportedCostUsd":0,"costStatus":"unpriced"},"runtime":{"provider":"local","agentRunDurationMs":35548,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":false},"billingTypes":["metered_api"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"historical-generic-procedures-observed":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":13618,"reportedPromptChars":13642,"freshPromptChars":4687,"retrievalClipped":false}],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"743b4b283ee73f34db4434facefb26012145fa528a1912856725c4f13e5b9247","evidence-manifest.json":"fe7f46cac1d8722c6137ec813638ecd0c5944f1ea25cc24092ff8d22236c807b","snapshots/context-integrity.json":"d247901367c17626a5738743dc300566c758db50fa7c0b60e5f74be5b7083245","snapshots/stock-harness-hire.json":"15866eaa21a98137bd2c4088dd53256360cdf65b27c8475d49d98cd49788fcf6","snapshots/stock-harness.json":"639cd4b7fec29383a18016e5b8be2227f68d8a5c37035318089ace252408a231","snapshots/stock-harness-preflight.json":"f52edc51ee627e9f5f02d3049a83a9b99fc073fc504ca9f238cf95bc691a1ce8"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":7,"failed":0}},
|
||||
{"executionId":"stock-harness.legacy-acp-codex.local.continuity-restart","profile":"legacy-acp-codex","case":"continuity-restart","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042864888","cellAttempt":1,"status":"failed","failureClass":"secret_leak","causeCodes":["persisted_credential_guard","structural_receipt_incomplete"],"failedMatcherPaths":["stockHarness.invocation-evidence-complete","stockHarness.fresh-default-delivered"],"model":"gpt-5.6-sol","runtimeMode":"legacy","durationMs":44754,"providerDurationMs":22118,"cleanup":"passed","billing":{"llm":{"runCount":3,"runsWithTokenUsage":1,"runsWithReportedCost":0,"inputTokens":662,"outputTokens":29,"cachedInputTokens":29955,"totalTokens":30646,"reportedCostUsd":0,"costStatus":"unpriced"},"runtime":{"provider":"local","agentRunDurationMs":22118,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":false},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":false,"historical-generic-procedures-observed":true,"fresh-default-delivered":false},"invocationMetrics":[{"capturedPromptChars":16383,"reportedPromptChars":17671,"freshPromptChars":1461,"retrievalClipped":true}],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"3ba472d29268b3e2d2170cd9947d7c89fbe685d902fedacf5c74a1952a97ce3a","evidence-manifest.json":"a5c263cc9afe3836a3c9f46cda537bb07a374c30fd7ad6f136088680b3ade01c","snapshots/context-integrity.json":null,"snapshots/stock-harness-hire.json":"8dd9d388cc265387d960fd435c19874601a5f7ca209b328943f8bacb145c5d89","snapshots/stock-harness.json":"f2b69dda7e26a76c952f0b36f10cfd7e9fb364ae3930585961d84093d17c2979","snapshots/stock-harness-preflight.json":"5fc3c8235143766fc6be8c7e12b31b92222594698c966462f944f13fb5929113"}},
|
||||
{"executionId":"stock-harness.legacy-acp-codex.local.ordered-comment-continuation","profile":"legacy-acp-codex","case":"ordered-comment-continuation","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042864888","cellAttempt":1,"status":"failed","failureClass":"secret_leak","causeCodes":["persisted_credential_guard","structural_receipt_incomplete"],"failedMatcherPaths":["contextIntegrity.ordered-comments","contextIntegrity.changed-scope-preserved","contextIntegrity.packing-report-order","contextIntegrity.final-scope-applied","contextIntegrity.continuation-run-count","contextIntegrity.continuation-wake-comment-ids","contextIntegrity.single-durable-output","contextIntegrity.completed-task","contextIntegrity.successful-runs","stockHarness.invocation-evidence-complete"],"model":"gpt-5.6-sol","runtimeMode":"legacy","durationMs":50896,"providerDurationMs":39443,"cleanup":"passed","billing":{"llm":{"runCount":4,"runsWithTokenUsage":1,"runsWithReportedCost":0,"inputTokens":992,"outputTokens":46,"cachedInputTokens":24349,"totalTokens":25387,"reportedCostUsd":0,"costStatus":"unpriced"},"runtime":{"provider":"local","agentRunDurationMs":39443,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":false},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":false,"historical-generic-procedures-observed":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":14032,"reportedPromptChars":14056,"freshPromptChars":4687,"retrievalClipped":false}],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"33ac59f0a690ed76618f695e658575751e718f53f9d828d8b52c690df9c15afb","evidence-manifest.json":"92c048e01012a77b94003e38f3f73888d725f6942327f7fbf3e6d2a42d12184d","snapshots/context-integrity.json":"2f9a803657068a67bcbf3b190370395187ef13b7a1487705d0305e121339eac7","snapshots/stock-harness-hire.json":"f47bb35d606ae00e6057a2a77769e2091e8ccb12eca08db3a17e1ed297a8ccf4","snapshots/stock-harness.json":"05582c37956b55f6a2bd7cad0c55bc0fa1dbff875d7dbed12b83352f6bd4e04c","snapshots/stock-harness-preflight.json":"badf01a591f5327f0dc2e48865711f8f4d7327888a7688668728173a29b273a8"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":3,"failed":9}},
|
||||
{"executionId":"stock-harness.legacy-claude.local.assigned-skill-explicit-invocation","profile":"legacy-claude","case":"assigned-skill-explicit-invocation","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042864888","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"claude-sonnet-4-6","runtimeMode":"legacy","durationMs":49054,"providerDurationMs":29420,"cleanup":"passed","billing":{"llm":{"runCount":1,"runsWithTokenUsage":1,"runsWithReportedCost":1,"inputTokens":29883,"outputTokens":1348,"cachedInputTokens":225730,"totalTokens":256961,"reportedCostUsd":0.19999125,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":29420,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.19999125,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.19999125,"complete":true},"billingTypes":["metered_api"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"historical-generic-procedures-observed":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":7512,"reportedPromptChars":7512,"freshPromptChars":4684,"retrievalClipped":false}],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"4fc73f7b1226b9a41b209a2f6dfa2a58805ddc1a7e7b699ac2b42b2e74a189a7","evidence-manifest.json":"440ee14cca3f8558fb47ecf27b17693c0f9dbecdcf5c8532d7e2ccbefe31700b","snapshots/context-integrity.json":"4545847b9c4ab2a4fa3053a384b5e990133705fc9da0d1e9cd98f1275ec05f44","snapshots/stock-harness-hire.json":"a6abfc40d1c788e4b897f2375506d59b2caba9fab0d4df019e7137fe9f66177f","snapshots/stock-harness.json":"755cc9d5f656dc068a08c8c141203eaa5d3d60b53edd9d516887aae108119f45","snapshots/stock-harness-preflight.json":"f52730b4555ab31de952c8c1e38db4067fb40756b6befd36ed4b686e1a273d73"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":7,"failed":0}},
|
||||
{"executionId":"stock-harness.legacy-claude.local.continuity-restart","profile":"legacy-claude","case":"continuity-restart","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042864888","cellAttempt":1,"status":"failed","failureClass":"candidate_failure","causeCodes":["chat_memory_assertion"],"failedMatcherPaths":[],"model":"claude-sonnet-4-6","runtimeMode":"legacy","durationMs":40202,"providerDurationMs":23072,"cleanup":"passed","billing":{"llm":{"runCount":2,"runsWithTokenUsage":2,"runsWithReportedCost":2,"inputTokens":39036,"outputTokens":829,"cachedInputTokens":116171,"totalTokens":156036,"reportedCostUsd":0.1936638,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":23072,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.1936638,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.1936638,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"historical-generic-procedures-observed":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":11451,"reportedPromptChars":11451,"freshPromptChars":1458,"retrievalClipped":false},{"capturedPromptChars":11508,"reportedPromptChars":11508,"freshPromptChars":1458,"retrievalClipped":false}],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"f8e377f617204eabdb6b4e0c24ab3280c51664b429051f7ea21a4746790820a6","evidence-manifest.json":"45818efcb8ed6d663f7ffab6d53605d5191fb983a68e422dadebdae875ed5b4c","snapshots/context-integrity.json":null,"snapshots/stock-harness-hire.json":"e3a8735ad4d5f89dc3899668c36791579562157fa25aa32a6e59aa098d0c079a","snapshots/stock-harness.json":"f3d24452ade2f232b0f671768d46e4d65a6a0c2a0740bb1daa17a4871a09aa7b","snapshots/stock-harness-preflight.json":"b85c96ada9801ce6c6569338b68bc810494a0711f01af39a63932e288f7a81a3"}},
|
||||
{"executionId":"stock-harness.legacy-claude.local.ordered-comment-continuation","profile":"legacy-claude","case":"ordered-comment-continuation","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042864888","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"claude-sonnet-4-6","runtimeMode":"legacy","durationMs":142017,"providerDurationMs":119179,"cleanup":"passed","billing":{"llm":{"runCount":2,"runsWithTokenUsage":2,"runsWithReportedCost":2,"inputTokens":66601,"outputTokens":6610,"cachedInputTokens":783643,"totalTokens":856854,"reportedCostUsd":0.58397715,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":119179,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.58397715,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.58397715,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"historical-generic-procedures-observed":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":14473,"reportedPromptChars":14447,"freshPromptChars":4684,"retrievalClipped":false},{"capturedPromptChars":7926,"reportedPromptChars":7926,"freshPromptChars":4684,"retrievalClipped":false}],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"9161c05c288e8620e5a748a28c901e1582efc240ca640203e1d8f7527e22a651","evidence-manifest.json":"296246a83eb0870a53e70f359f1859719f30b655e6ed20908abbfe4213605280","snapshots/context-integrity.json":"17779f4ebc38ed08071e40ac4b939149a18dfceb76c0f1f4f6ed21bf5d0153fa","snapshots/stock-harness-hire.json":"1f5a97e38555f57f47ee1900440d88ee521de75e7589c163f7688c852b661dc8","snapshots/stock-harness.json":"499a0b49c042b7c44f992c56983d207677cf619306ed6f6db9975ef1b84d455b","snapshots/stock-harness-preflight.json":"8f92c4b703533a645196bf03d1353be7196271d25638be7cb3fae6ec92c69c52"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":12,"failed":0}},
|
||||
{"executionId":"stock-harness.legacy-codex.local.assigned-skill-explicit-invocation","profile":"legacy-codex","case":"assigned-skill-explicit-invocation","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042864888","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"gpt-5.6-sol","runtimeMode":"legacy","durationMs":50078,"providerDurationMs":27658,"cleanup":"passed","billing":{"llm":{"runCount":1,"runsWithTokenUsage":1,"runsWithReportedCost":0,"inputTokens":128946,"outputTokens":1727,"cachedInputTokens":97854,"totalTokens":228527,"reportedCostUsd":0,"costStatus":"unpriced"},"runtime":{"provider":"local","agentRunDurationMs":27658,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":false},"billingTypes":["metered_api"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"historical-generic-procedures-observed":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":12332,"reportedPromptChars":12332,"freshPromptChars":4683,"retrievalClipped":false}],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"ea06fc8b1ec166c9932de8634d08e528e90f7b734388ccad6d47b4b96df9ff6d","evidence-manifest.json":"45e71226e99eaee9bea0b0fae2cca35de573c2ab9d5add13159f321c8480ac0b","snapshots/context-integrity.json":"4cf10f812f258a77b472abd74f6691224dfcba4b9a2dec9c0f2c35c5ae741d7a","snapshots/stock-harness-hire.json":"19db78dbf51a67ff4bc307ef378da9cc024a0cae1c977951b49d089c1ec47fe4","snapshots/stock-harness.json":"a3cb7e2bf4e775cf9058b6be33d8df5321e312df2aed39736719454647610a8f","snapshots/stock-harness-preflight.json":"e9a116f8605a2630ff46075598ac6bae30817892d76efcd9a25891649cac0856"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":7,"failed":0}},
|
||||
{"executionId":"stock-harness.legacy-codex.local.continuity-restart","profile":"legacy-codex","case":"continuity-restart","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042864888","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"gpt-5.6-sol","runtimeMode":"legacy","durationMs":85550,"providerDurationMs":29332,"cleanup":"passed","billing":{"llm":{"runCount":3,"runsWithTokenUsage":3,"runsWithReportedCost":0,"inputTokens":468597,"outputTokens":2919,"cachedInputTokens":373648,"totalTokens":845164,"reportedCostUsd":0,"costStatus":"unpriced"},"runtime":{"provider":"local","agentRunDurationMs":29332,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":false},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"historical-generic-procedures-observed":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":2787,"reportedPromptChars":2787,"freshPromptChars":0,"retrievalClipped":false},{"capturedPromptChars":2730,"reportedPromptChars":2730,"freshPromptChars":0,"retrievalClipped":false},{"capturedPromptChars":16326,"reportedPromptChars":16326,"freshPromptChars":1457,"retrievalClipped":false}],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"db7be8127de96e697a9d274b7a18db40695daa234684ec4d8e28afd8665c1f3e","evidence-manifest.json":"2e9151536b8ade2bfbb3c465438e8ace21417258c4037b6923920ee0784b0e47","snapshots/context-integrity.json":null,"snapshots/stock-harness-hire.json":"af8b2fe11402a8b87a056818df29a62829ec9425f8a2d955e95692980e15e043","snapshots/stock-harness.json":"739e642582faf89b74c522774c64ea26c6af623539b7a8f99ce332066c965600","snapshots/stock-harness-preflight.json":"8fe9f32d7629a1f6bd632d58e22e39e47baff2b8bc3b74e3593e6411ab3c9a2b"}},
|
||||
{"executionId":"stock-harness.legacy-codex.local.ordered-comment-continuation","profile":"legacy-codex","case":"ordered-comment-continuation","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042864888","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"gpt-5.6-sol","runtimeMode":"legacy","durationMs":74114,"providerDurationMs":57150,"cleanup":"passed","billing":{"llm":{"runCount":2,"runsWithTokenUsage":2,"runsWithReportedCost":0,"inputTokens":628626,"outputTokens":7098,"cachedInputTokens":563211,"totalTokens":1198935,"reportedCostUsd":0,"costStatus":"unpriced"},"runtime":{"provider":"local","agentRunDurationMs":57150,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":false},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"historical-generic-procedures-observed":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":9811,"reportedPromptChars":9785,"freshPromptChars":0,"retrievalClipped":false},{"capturedPromptChars":12746,"reportedPromptChars":12746,"freshPromptChars":4683,"retrievalClipped":false}],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"56a457fcc7a36809eb4bfcedeff2fa079f5272754ff52fc453e8bf8d813e52f3","evidence-manifest.json":"bf17fb7d2a05f7e99e0723907bdc57f0a88bf6838d68f31f4b84dc6ebc62d3c7","snapshots/context-integrity.json":"a9156b810e32fa1d9d07826276a00c62ebe4f25ac3dc284ada157f2b2d72d800","snapshots/stock-harness-hire.json":"5fc8abd51d401c96d701b0862ac2dc674eb36244fdc25c49b67ba3947ba5a2fb","snapshots/stock-harness.json":"c72b8c53e1e50a82ab6ff815b363942936440cfff75f8f8ca5f54185c723889d","snapshots/stock-harness-preflight.json":"b14d304196cdc3146436c66199243c6da04784c13e45c9bd31e0ef83e97a29bc"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":12,"failed":0}},
|
||||
{"executionId":"stock-harness.legacy-opencode.local.assigned-skill-explicit-invocation","profile":"legacy-opencode","case":"assigned-skill-explicit-invocation","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042864888","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"openrouter/deepseek/deepseek-v4-flash-0731","runtimeMode":"legacy","durationMs":86384,"providerDurationMs":68956,"cleanup":"passed","billing":{"llm":{"runCount":1,"runsWithTokenUsage":1,"runsWithReportedCost":1,"inputTokens":57002,"outputTokens":2365,"cachedInputTokens":223232,"totalTokens":282599,"reportedCostUsd":0.0044563934000000005,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":68956,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.0044563934000000005,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.0044563934000000005,"complete":true},"billingTypes":["unknown"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"historical-generic-procedures-observed":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":12335,"reportedPromptChars":12335,"freshPromptChars":4686,"retrievalClipped":false}],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"20dbb34bb975f73c3ac2695c047b6cbc8f6382757939ae6210b08a0f13ba9e9d","evidence-manifest.json":"67c3e042be8e0a5a0cd7cca82d1b0887e1392a2806c3b4db67baa3e99365b6be","snapshots/context-integrity.json":"decf2d93769acce2e2990de27c81ee32fd805ca53d3c2be68c56f22a013a7ac0","snapshots/stock-harness-hire.json":"187598ed72837719be8d6f0b77c5f8d35cfc871f8cf41d4fdc5b259f88171025","snapshots/stock-harness.json":"98424edd880cd05e0cbf8c32973fe9fbfc055037a74e76c20f893c3ee263f6a0","snapshots/stock-harness-preflight.json":"86d9f4080a2045ae3fa68468d0ffaf68baa26f27f0528274f13762f093b4c113"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":7,"failed":0}},
|
||||
{"executionId":"stock-harness.legacy-opencode.local.continuity-restart","profile":"legacy-opencode","case":"continuity-restart","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042864888","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"openrouter/deepseek/deepseek-v4-flash-0731","runtimeMode":"legacy","durationMs":126589,"providerDurationMs":78910,"cleanup":"passed","billing":{"llm":{"runCount":3,"runsWithTokenUsage":3,"runsWithReportedCost":3,"inputTokens":64286,"outputTokens":1046,"cachedInputTokens":28672,"totalTokens":94004,"reportedCostUsd":0.0022020907,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":78910,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.0022020907,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.0022020907,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"historical-generic-procedures-observed":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":7611,"reportedPromptChars":7611,"freshPromptChars":0,"retrievalClipped":false},{"capturedPromptChars":7554,"reportedPromptChars":7554,"freshPromptChars":0,"retrievalClipped":false},{"capturedPromptChars":16335,"reportedPromptChars":16335,"freshPromptChars":1460,"retrievalClipped":false}],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"29dce6418aa35bb8070b07433046bbf39c9a8367a198cdb0aa28a0bea480032c","evidence-manifest.json":"439dc46130b607b7488e9df7736a711b267efa53689fe9fdad2f822525677215","snapshots/context-integrity.json":null,"snapshots/stock-harness-hire.json":"7f7ad64827bbe430e06a7e01b1377443653793584264c0fa00ac88a742bd0f49","snapshots/stock-harness.json":"4dd536951a950e7507710a9eb44cdbcd354907397f1e95b8297b28a98fd75dbc","snapshots/stock-harness-preflight.json":"8fa2ec342ad583fb20fed4db88890fc31eab3ef5898a952a85371e53dd433aaf"}},
|
||||
{"executionId":"stock-harness.legacy-opencode.local.ordered-comment-continuation","profile":"legacy-opencode","case":"ordered-comment-continuation","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37048838402","cellAttempt":1,"status":"failed","failureClass":"candidate_failure","causeCodes":["deadline_or_run_start_timeout"],"failedMatcherPaths":["contextIntegrity.comments-queued-as-batch","contextIntegrity.initial-run-started","contextIntegrity.ordered-comments","contextIntegrity.changed-scope-preserved","contextIntegrity.packing-report-order","contextIntegrity.final-scope-applied","contextIntegrity.continuation-run-count","contextIntegrity.continuation-wake-comment-ids","contextIntegrity.single-durable-output","contextIntegrity.completed-task","contextIntegrity.successful-runs"],"model":"openrouter/deepseek/deepseek-v4-flash-0731","runtimeMode":"legacy","durationMs":720616,"providerDurationMs":442902,"cleanup":"passed","billing":{"llm":{"runCount":3,"runsWithTokenUsage":2,"runsWithReportedCost":2,"inputTokens":51446,"outputTokens":8911,"cachedInputTokens":967936,"totalTokens":1028293,"reportedCostUsd":0.030657645200000007,"costStatus":"partial"},"runtime":{"provider":"local","agentRunDurationMs":442902,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.030657645200000007,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.030657645200000007,"complete":false},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"historical-generic-procedures-observed":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":16392,"reportedPromptChars":16461,"freshPromptChars":0,"retrievalClipped":true},{"capturedPromptChars":16406,"reportedPromptChars":16879,"freshPromptChars":4686,"retrievalClipped":true},{"capturedPromptChars":12749,"reportedPromptChars":12749,"freshPromptChars":4686,"retrievalClipped":false}],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"6d0e5d54b1742b02eb37346747510c66cb5cd0b80262a6c9590fd2a28861dcb2","evidence-manifest.json":"758931a4d30213d1b320f58cd2aa338e8a6a8aed579b878e14c5e52848bd5f13","snapshots/context-integrity.json":"bcaa4ee278361a90347625044aa49086b27093356abc0f3700b0363d28c8653c","snapshots/stock-harness-hire.json":"21431fa5e7cb6486037c670fc1e4aa64fb24716ad8ab325601ff294ada40263f","snapshots/stock-harness.json":"29c55e52f4adbbc9259cb9e221c08bfbbd7ad4c5f8247c24b3f029362e9000de","snapshots/stock-harness-preflight.json":"f341723074c996c79d8a6c30b9f12270634b20b40da452b13e4ab9ce91b47cac"},"taskDocumentCount":null,"behaviorCheckCounts":{"passed":1,"failed":11}},
|
||||
{"executionId":"stock-harness.runner-acpx-claude.local.assigned-skill-explicit-invocation","profile":"runner-acpx-claude","case":"assigned-skill-explicit-invocation","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042864888","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"claude-sonnet-5","runtimeMode":"native","durationMs":46264,"providerDurationMs":25372,"cleanup":"passed","billing":{"llm":{"runCount":1,"runsWithTokenUsage":1,"runsWithReportedCost":1,"inputTokens":10,"outputTokens":1042,"cachedInputTokens":141254,"totalTokens":142306,"reportedCostUsd":0,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":25372,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":true},"billingTypes":["unknown"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true},"invocationMetrics":[],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"073085f804a58d191bd44972edfb08d914fe622a86f236d0c4dc1b2bf8a03322","evidence-manifest.json":"4fad428c1df72abbfee24a4f7338e25284fc874cff1220e6b631fa86eb2d0bed","snapshots/context-integrity.json":"77e27b8ea01a83e0b65f3d5410e0787fbf84a7f6ea02f4401bd77488999dd4a2","snapshots/stock-harness-hire.json":"8c6c5d56fd068c15f1e19ce9a0b62204443c894d204311bd949d5e22c624fd3d","snapshots/stock-harness.json":"2a6860d7c29ad0d03240bda0a779b83d0455b8ce9fcfb1cf9e6ad559c4dec112","snapshots/stock-harness-preflight.json":"79b11e544d2821d1025476f5fd0d737e822ae5ab731ab32047d1fbf6b76015e4"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":7,"failed":0}},
|
||||
{"executionId":"stock-harness.runner-acpx-claude.local.continuity-restart","profile":"runner-acpx-claude","case":"continuity-restart","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042864888","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"claude-sonnet-5","runtimeMode":"native","durationMs":77424,"providerDurationMs":36820,"cleanup":"passed","billing":{"llm":{"runCount":3,"runsWithTokenUsage":3,"runsWithReportedCost":3,"inputTokens":6,"outputTokens":94,"cachedInputTokens":94997,"totalTokens":95097,"reportedCostUsd":0,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":36820,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true},"invocationMetrics":[],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"be582fe8af5f19543c16c8590697c5aa9dc688fdb653b0550aba0b55174becd1","evidence-manifest.json":"cfbd1cb13917a96b08dd46aae1fcc63e2b27af5d1d22f4952974d3415c74ffb8","snapshots/context-integrity.json":null,"snapshots/stock-harness-hire.json":"254f18a8e8e1d6a5a2e7731728756671c1374cd4d1210469172bed9c2cd6e4c2","snapshots/stock-harness.json":"8efa84d6907de220dd7b9fc2c384c61aae0606e7f5311f0e926e26ae1617d869","snapshots/stock-harness-preflight.json":"c7e3f2342ef9fc824b28e3f22ffbbb326d2ba1ff84e36c4deedc56a7abefbe07"}},
|
||||
{"executionId":"stock-harness.runner-acpx-claude.local.ordered-comment-continuation","profile":"runner-acpx-claude","case":"ordered-comment-continuation","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042864888","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"claude-sonnet-5","runtimeMode":"native","durationMs":132587,"providerDurationMs":108895,"cleanup":"passed","billing":{"llm":{"runCount":2,"runsWithTokenUsage":2,"runsWithReportedCost":2,"inputTokens":26,"outputTokens":7996,"cachedInputTokens":565030,"totalTokens":573052,"reportedCostUsd":0,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":108895,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true},"invocationMetrics":[],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"62c6c55047813b0b5aa5334e29dbe3f90225c83ac436eab14afd97d78ed9ec65","evidence-manifest.json":"7d53ad7f80b65a1c7af3fad4fa3d0c652ae25cdaeae3d41cbee1bb888b853fb5","snapshots/context-integrity.json":"2081459b761a84469399bec377d7380c172b64fddae48735c5329771656e28a7","snapshots/stock-harness-hire.json":"f11c90fdae215949458d61a6e1ca6565fe277ea30d52fd922c05cffdf4867e5c","snapshots/stock-harness.json":"f303a3fb5c5e55cefb696d46204aafe6f1cefc5b0a6b07a728f2bf0dbf1dfdcf","snapshots/stock-harness-preflight.json":"79768aa58996d28ee80c8a9e8d5c4ba1a42eba58a408163cd8a6037ac6005b26"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":12,"failed":0}},
|
||||
{"executionId":"stock-harness.runner-codex.local.assigned-skill-explicit-invocation","profile":"runner-codex","case":"assigned-skill-explicit-invocation","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042864888","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"gpt-5.6-sol","runtimeMode":"native","durationMs":53751,"providerDurationMs":29850,"cleanup":"passed","billing":{"llm":{"runCount":1,"runsWithTokenUsage":1,"runsWithReportedCost":1,"inputTokens":162041,"outputTokens":1296,"cachedInputTokens":137822,"totalTokens":301159,"reportedCostUsd":0,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":29850,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":true},"billingTypes":["unknown"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true},"invocationMetrics":[],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"ffcc5203624c19b0acda50dfd1e283c595ae3ed6072e4aaf9781fef4bf08fa45","evidence-manifest.json":"1460fae9f00a241a3ddbe7f623a4c1c6cf0f6434e73e47d0de7f98dc00dec459","snapshots/context-integrity.json":"63d16c6558efcf03097fd2ef8caf1f01c39a38127aee2904eba0cf0edfb711e6","snapshots/stock-harness-hire.json":"61630a0e0521f8ccca30753bde385b731f5c8c15cf6280b000947e80acf3decf","snapshots/stock-harness.json":"bc95a45aab085ce3eee988a9b235281b847cd8cb73c4d294ce576b97e73d879f","snapshots/stock-harness-preflight.json":"4daf4cd83a8b1539ab8a8db2fd5cf204746884d8cde04f6507acfff5be75ef11"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":7,"failed":0}},
|
||||
{"executionId":"stock-harness.runner-codex.local.continuity-restart","profile":"runner-codex","case":"continuity-restart","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042864888","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"gpt-5.6-sol","runtimeMode":"native","durationMs":84362,"providerDurationMs":35132,"cleanup":"passed","billing":{"llm":{"runCount":3,"runsWithTokenUsage":3,"runsWithReportedCost":3,"inputTokens":315100,"outputTokens":2339,"cachedInputTokens":231408,"totalTokens":548847,"reportedCostUsd":0,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":35132,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true},"invocationMetrics":[],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"dbf2e14fcda52d2a33808e5a4c226d45f864752274c823c26beb7c2ea5409f03","evidence-manifest.json":"86a34f1a25854354bee0d9f0ea88cab9ba9b51476cf274d12f5b491a9910188a","snapshots/context-integrity.json":null,"snapshots/stock-harness-hire.json":"a38cab643c78fde9d61d15074b82297b94bf46ae5c3296aa06e6958f67188eda","snapshots/stock-harness.json":"2d148d3e55c57619bde56558f7e87e85cbb1305e46eba83bedb21c3ee4b43b54","snapshots/stock-harness-preflight.json":"9517ef436cc83da9078dd75587f01be8a28685131c37773dc092cf89c137ca0c"}},
|
||||
{"executionId":"stock-harness.runner-codex.local.ordered-comment-continuation","profile":"runner-codex","case":"ordered-comment-continuation","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042864888","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"gpt-5.6-sol","runtimeMode":"native","durationMs":92663,"providerDurationMs":70832,"cleanup":"passed","billing":{"llm":{"runCount":2,"runsWithTokenUsage":2,"runsWithReportedCost":2,"inputTokens":428674,"outputTokens":3591,"cachedInputTokens":372482,"totalTokens":804747,"reportedCostUsd":0,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":70832,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true},"invocationMetrics":[],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"8c7a81172e0d519ffc8aca9a3025c88166d93d12a1744e560813c66063d6d130","evidence-manifest.json":"b495ad141c12f2458e3b66123cb673dec68511bcb5c2d59af78400dc67c417eb","snapshots/context-integrity.json":"166eb5a26fa389529287aa940c4e3caa85665ea67fcce019bc69a8bb8d9f7b41","snapshots/stock-harness-hire.json":"2fe4b0d207648628658312e97b491336929197f826c00dce012b6afc43537564","snapshots/stock-harness.json":"79b83fed61c69589d38b48552b04dce41bf41dca27f6e7f5dd15229024b6338f","snapshots/stock-harness-preflight.json":"689ce3141b2c6970eac5468885e5111daa730bcda25a1459e7bac4177650045e"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":12,"failed":0}},
|
||||
{"executionId":"stock-harness.runner-opencode.local.assigned-skill-explicit-invocation","profile":"runner-opencode","case":"assigned-skill-explicit-invocation","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042864888","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"openrouter/deepseek/deepseek-v4-flash-0731","runtimeMode":"native","durationMs":90297,"providerDurationMs":68028,"cleanup":"passed","billing":{"llm":{"runCount":1,"runsWithTokenUsage":1,"runsWithReportedCost":1,"inputTokens":44259,"outputTokens":1165,"cachedInputTokens":62976,"totalTokens":108400,"reportedCostUsd":0.0020380985,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":68028,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.0020380985,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.0020380985,"complete":true},"billingTypes":["unknown"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true},"invocationMetrics":[],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"3ffa3bff50fbade8801da7c26ffed426eee9bda9a7e3640793f03a220f74ea82","evidence-manifest.json":"6df0c3b49ef54154721d4d0ba434f893fcba372ba8588fef2bd1a5947284b1d3","snapshots/context-integrity.json":"41b60c88adb0c529148981a55ab3b18964035b47786da50942183b58e8884fed","snapshots/stock-harness-hire.json":"c9a2fe249e217fcd5c205f1dc768446333932de74d87dc6f4a1e7855c5e4d815","snapshots/stock-harness.json":"474b0d6aecd8e8bce23f78c260ad4dc83c286d1d39917ea0920ad4b402d9404b","snapshots/stock-harness-preflight.json":"baeb38686ea4d3233906d83a889fdf3d805846b27f25c6c04da68842822a496d"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":7,"failed":0}},
|
||||
{"executionId":"stock-harness.runner-opencode.local.continuity-restart","profile":"runner-opencode","case":"continuity-restart","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042864888","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"openrouter/deepseek/deepseek-v4-flash-0731","runtimeMode":"native","durationMs":155833,"providerDurationMs":101201,"cleanup":"passed","billing":{"llm":{"runCount":3,"runsWithTokenUsage":3,"runsWithReportedCost":3,"inputTokens":48028,"outputTokens":96,"cachedInputTokens":23808,"totalTokens":71932,"reportedCostUsd":0.0004892436,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":101201,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.0004892436,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.0004892436,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true},"invocationMetrics":[],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"57fe32615f16ff3706044419bb81362a67ec35aed7a5f74a5b8207b5831a5cba","evidence-manifest.json":"07e3be863cc20a1352a094f38e6133004dbfd673f4fd59599431a022ce061ff1","snapshots/context-integrity.json":null,"snapshots/stock-harness-hire.json":"d127504476aee2130312fdf092a523060188066abe9f969ae9b178b57374cced","snapshots/stock-harness.json":"0df941763ed917e2c553a6c6a4dfb0c87bdb7181d42a28f614c765906b74f1e9","snapshots/stock-harness-preflight.json":"f57655c970472d2a9b9bebe100d8c31788ed76e06da4363046a5a6d3f09269bb"}},
|
||||
{"executionId":"stock-harness.runner-opencode.local.ordered-comment-continuation","profile":"runner-opencode","case":"ordered-comment-continuation","sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceRef":"refs/heads/codex/stock-harness-previous-instructions","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042864888","cellAttempt":1,"status":"failed","failureClass":"candidate_failure","causeCodes":["deadline_or_run_start_timeout"],"failedMatcherPaths":["contextIntegrity.comments-queued-as-batch","contextIntegrity.initial-run-started","contextIntegrity.ordered-comments","contextIntegrity.changed-scope-preserved","contextIntegrity.packing-report-order","contextIntegrity.final-scope-applied","contextIntegrity.continuation-run-count","contextIntegrity.continuation-wake-comment-ids","contextIntegrity.single-durable-output","contextIntegrity.completed-task","contextIntegrity.successful-runs"],"model":"openrouter/deepseek/deepseek-v4-flash-0731","runtimeMode":"native","durationMs":721578,"providerDurationMs":446042,"cleanup":"passed","billing":{"llm":{"runCount":11,"runsWithTokenUsage":11,"runsWithReportedCost":11,"inputTokens":159917,"outputTokens":7291,"cachedInputTokens":1077987,"totalTokens":1245195,"reportedCostUsd":0.015645790399999998,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":446042,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.015645790399999998,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.015645790399999998,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true},"invocationMetrics":[],"prerequisite":{"sourceSha":"12c5433c67dc2e62916b879349c7ba2b6e0431f0","sourceFingerprint":"2e5f22848450179ac2d573662c718300f86a935d09d15204c9d97e98f65b53f4","passed":true,"providerCallsBeforeAdmission":0,"passedTests":536},"evidenceHashes":{"result.json":"f727dd5f76162c4b995e649e1a7bf55a52381efa550e3bda46ec0a51a899d178","evidence-manifest.json":"6a550c598b67f60fcea70d56907c4a2c84239bc0d01e58737e99a0014144bb70","snapshots/context-integrity.json":"14212826847fbd9d6d63c7b70afb102fdb5d0100f7d8f1368d46ff6f5e32db07","snapshots/stock-harness-hire.json":"ab6dd2377fc251e44ed8f3680d40d10a315b635f3003e45501c7496037fb21ca","snapshots/stock-harness.json":"38759bca194a8038f0d2be4598e50a04d61215ecabdfec95cd7f6cfa4eeec61c","snapshots/stock-harness-preflight.json":"312611057436fd8cbdcc5179ffa5e62144800a6a9244e646bb6e0b3a9a0cb551"},"taskDocumentCount":null,"behaviorCheckCounts":{"passed":1,"failed":11}}
|
||||
],
|
||||
"candidate": [
|
||||
{"executionId":"stock-harness.legacy-acp-claude.local.assigned-skill-explicit-invocation","profile":"legacy-acp-claude","case":"assigned-skill-explicit-invocation","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/reduce-default-agent-manual","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042856368","cellAttempt":1,"status":"failed","failureClass":"secret_leak","causeCodes":["persisted_credential_guard","no_single_paperclip_document"],"failedMatcherPaths":["contextIntegrity.single-durable-output"],"model":"claude-sonnet-4-6","runtimeMode":"legacy","durationMs":123175,"providerDurationMs":111928,"cleanup":"passed","billing":{"llm":{"runCount":2,"runsWithTokenUsage":2,"runsWithReportedCost":2,"inputTokens":25333,"outputTokens":5771,"cachedInputTokens":316215,"totalTokens":347319,"reportedCostUsd":0.2838505,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":111928,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.2838505,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.2838505,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"generic-procedures-absent":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":10966,"reportedPromptChars":10449,"freshPromptChars":787,"retrievalClipped":false},{"capturedPromptChars":6028,"reportedPromptChars":5498,"freshPromptChars":787,"retrievalClipped":false}],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"2a5fed84ee0f95a67e2c2b0d4247ffae33e16a7c2aa11dc57b11228e0db90965","evidence-manifest.json":"7e31c707e866e15bf44a276ac252c423a47a63364e76ad85de9a8d5b6a4e1dfd","snapshots/context-integrity.json":"3c36c18d5e257e788514c02b50d63c7c69885201df0a07750129d55c0f583676","snapshots/stock-harness-hire.json":"2d128826b4e5068ad6ed819512bcb17160346becd9344b8ab8e70c984c3241b7","snapshots/stock-harness.json":"49b8da640ab0b9dd2b209ce0cf9f3bec6deceb801bfe98c0166d7702a9732a80","snapshots/stock-harness-preflight.json":"a12fdf9b8b7d761ff223e30dcaeb835ad9bf32ef7f1c8b8983a638d175c2ab5f"},"taskDocumentCount":0,"behaviorCheckCounts":{"passed":6,"failed":1}},
|
||||
{"executionId":"stock-harness.legacy-acp-claude.local.continuity-restart","profile":"legacy-acp-claude","case":"continuity-restart","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/reduce-default-agent-manual","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042856368","cellAttempt":1,"status":"failed","failureClass":"secret_leak","causeCodes":["persisted_credential_guard"],"failedMatcherPaths":[],"model":"claude-sonnet-4-6","runtimeMode":"legacy","durationMs":34813,"providerDurationMs":18798,"cleanup":"passed","billing":{"llm":{"runCount":2,"runsWithTokenUsage":2,"runsWithReportedCost":2,"inputTokens":16797,"outputTokens":329,"cachedInputTokens":54375,"totalTokens":71501,"reportedCostUsd":0.092943,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":18798,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.092943,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.092943,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"generic-procedures-absent":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":13201,"reportedPromptChars":12723,"freshPromptChars":787,"retrievalClipped":false},{"capturedPromptChars":13233,"reportedPromptChars":12755,"freshPromptChars":787,"retrievalClipped":false}],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"ecab7c9f83668fd4712702bb906adf38e188e34f7f07bd7d746da685339a2ecd","evidence-manifest.json":"544a726ddf3ac212671f7c3d73dafcf94cd5b3abb9a3ad5197d6c793e9d7aaa0","snapshots/context-integrity.json":null,"snapshots/stock-harness-hire.json":"78f5c467789921db4d2194ba80459524b04644f2d8d8a3b8678fdf7686004e8f","snapshots/stock-harness.json":"61641e5e25351e748afb2e4533d70b1cc34a8b78e1fbc56a27d96b2017bd0f45","snapshots/stock-harness-preflight.json":"4e522117d8489a16263c3d66ca513207217a7861733bfd11ef2f7149aef680e9"}},
|
||||
{"executionId":"stock-harness.legacy-acp-claude.local.ordered-comment-continuation","profile":"legacy-acp-claude","case":"ordered-comment-continuation","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/reduce-default-agent-manual","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042856368","cellAttempt":1,"status":"failed","failureClass":"secret_leak","causeCodes":["persisted_credential_guard"],"failedMatcherPaths":[],"model":"claude-sonnet-4-6","runtimeMode":"legacy","durationMs":154765,"providerDurationMs":139409,"cleanup":"passed","billing":{"llm":{"runCount":2,"runsWithTokenUsage":2,"runsWithReportedCost":2,"inputTokens":61445,"outputTokens":6979,"cachedInputTokens":562903,"totalTokens":631327,"reportedCostUsd":0.5120724,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":139409,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.5120724,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.5120724,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"generic-procedures-absent":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":13419,"reportedPromptChars":12915,"freshPromptChars":787,"retrievalClipped":false},{"capturedPromptChars":6390,"reportedPromptChars":5912,"freshPromptChars":787,"retrievalClipped":false}],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"9c23fec76e718c1fa5f775ae8cdcee1e5433039ff74d7ffbf306b5be6693700d","evidence-manifest.json":"5284aba88b923322b1f162045d20e36236fb731689527a1a1d06d5305efd4a64","snapshots/context-integrity.json":"8fef43dac791a84db62bfea8977f586afe3e5dc5c36225eaab79faaa15b4dfca","snapshots/stock-harness-hire.json":"4961a6103fcb6b58d5d65981c68afa178ea9e1eb6038ad3b3049d47e4bc737ab","snapshots/stock-harness.json":"4e11dd1b37581fed8628b7ebd953ebf3b6fe389d357ffd2efcd9e6b976e4d64a","snapshots/stock-harness-preflight.json":"7d962ba46f4f02f111ed4e423705009f38ee99096d180b693f4c41ed4c805126"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":12,"failed":0}},
|
||||
{"executionId":"stock-harness.legacy-acp-codex.local.assigned-skill-explicit-invocation","profile":"legacy-acp-codex","case":"assigned-skill-explicit-invocation","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/reduce-default-agent-manual","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042856368","cellAttempt":1,"status":"failed","failureClass":"secret_leak","causeCodes":["persisted_credential_guard"],"failedMatcherPaths":[],"model":"gpt-5.6-sol","runtimeMode":"legacy","durationMs":65745,"providerDurationMs":49358,"cleanup":"passed","billing":{"llm":{"runCount":1,"runsWithTokenUsage":1,"runsWithReportedCost":0,"inputTokens":1379,"outputTokens":34,"cachedInputTokens":41987,"totalTokens":43400,"reportedCostUsd":0,"costStatus":"unpriced"},"runtime":{"provider":"local","agentRunDurationMs":49358,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":false},"billingTypes":["metered_api"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"generic-procedures-absent":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":5509,"reportedPromptChars":5533,"freshPromptChars":786,"retrievalClipped":false}],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"81c5c35a942c78a2c9d7d77064264504311a191c6dd421e656d4f0751670b6ff","evidence-manifest.json":"bff59a88d627186d18282fe39822dd5a57d3a2396ae9aca5fa178a3b4ac3cf60","snapshots/context-integrity.json":"3809130fd34f0b6035ed5b17bd9e354d19aac02fc2f64aea01811803151c7f82","snapshots/stock-harness-hire.json":"4e0468ee05d56f08faa9ffbe3ed6cf28c07490a8b27a08e80875264dc66daac5","snapshots/stock-harness.json":"c5a82192ff53c6eec7a5497602a9969af3c21ea7fde8fdff4309025f5ccf80d2","snapshots/stock-harness-preflight.json":"12944d93aac9f8688d2832369f7eadc10a9546a6fb6bc2f2d8b3caef38b0cc2f"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":7,"failed":0}},
|
||||
{"executionId":"stock-harness.legacy-acp-codex.local.continuity-restart","profile":"legacy-acp-codex","case":"continuity-restart","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/reduce-default-agent-manual","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042856368","cellAttempt":1,"status":"failed","failureClass":"secret_leak","causeCodes":["persisted_credential_guard","structural_receipt_incomplete"],"failedMatcherPaths":["stockHarness.invocation-evidence-complete"],"model":"gpt-5.6-sol","runtimeMode":"legacy","durationMs":35779,"providerDurationMs":20059,"cleanup":"passed","billing":{"llm":{"runCount":3,"runsWithTokenUsage":1,"runsWithReportedCost":0,"inputTokens":711,"outputTokens":30,"cachedInputTokens":31118,"totalTokens":31859,"reportedCostUsd":0,"costStatus":"unpriced"},"runtime":{"provider":"local","agentRunDurationMs":20059,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":false},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":false,"generic-procedures-absent":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":12764,"reportedPromptChars":12788,"freshPromptChars":786,"retrievalClipped":false}],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"1c59fb6b3845ef6bc4f9cc549826e45334b3220ce5832f2fe4b9d23fd45136b6","evidence-manifest.json":"4cdd7cc5bb0a025e7626e947dd72558c13203aa9dc9765f5afbfed5acc907de1","snapshots/context-integrity.json":null,"snapshots/stock-harness-hire.json":"dcbe621edcd9437c47361c74676db38a240fdcaff2401d41213a185e7cf9b785","snapshots/stock-harness.json":"8908438e0c7565ebca6d4753e619f87bd0c015210214fbb8c39876d7e13cebaa","snapshots/stock-harness-preflight.json":"3b2de5b161384bebe03f7cad3b3268f98cca291cd8fbf4b8a0c830536daecf87"}},
|
||||
{"executionId":"stock-harness.legacy-acp-codex.local.ordered-comment-continuation","profile":"legacy-acp-codex","case":"ordered-comment-continuation","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/stock-harness-candidate-cancelled-cells","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37045368302","cellAttempt":1,"status":"failed","failureClass":"secret_leak","causeCodes":["persisted_credential_guard"],"failedMatcherPaths":["contextIntegrity.comments-queued-as-batch","contextIntegrity.initial-run-started","contextIntegrity.ordered-comments","contextIntegrity.changed-scope-preserved","contextIntegrity.packing-report-order","contextIntegrity.final-scope-applied","contextIntegrity.continuation-run-count","contextIntegrity.continuation-wake-comment-ids","contextIntegrity.single-durable-output","contextIntegrity.completed-task","contextIntegrity.successful-runs"],"model":"gpt-5.6-sol","runtimeMode":"legacy","durationMs":720307,"providerDurationMs":46665,"cleanup":"passed","billing":{"llm":{"runCount":1,"runsWithTokenUsage":1,"runsWithReportedCost":0,"inputTokens":540,"outputTokens":28,"cachedInputTokens":32395,"totalTokens":32963,"reportedCostUsd":0,"costStatus":"unpriced"},"runtime":{"provider":"local","agentRunDurationMs":46665,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":false},"billingTypes":["metered_api"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"generic-procedures-absent":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":5923,"reportedPromptChars":5947,"freshPromptChars":786,"retrievalClipped":false}],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"a1285f2fecccdcdac85360784e4be82188b564d3135f7adf6606fa1451307376","evidence-manifest.json":"39864272e9331c88c1266f2b5f05b755aacdc46c2c7015c4d4df8539c0a22718","snapshots/context-integrity.json":"5e663656093c672fb294719edb8636dd15a5ed1441912f32a32c78254663b797","snapshots/stock-harness-hire.json":"35d859103bf2acb7c7f6d212a8ce277e42726da388f7c52ee0b05d600dc09c78","snapshots/stock-harness.json":"483b4efedcc34570ad0ad364d92ba0212186cbc36a704639106e181402470468","snapshots/stock-harness-preflight.json":"5bb04e0c93a3f4ac5d9f41a5775fba85e0687a8161e255257545d54151421c62"},"taskDocumentCount":null,"behaviorCheckCounts":{"passed":1,"failed":11}},
|
||||
{"executionId":"stock-harness.legacy-claude.local.assigned-skill-explicit-invocation","profile":"legacy-claude","case":"assigned-skill-explicit-invocation","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/reduce-default-agent-manual","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042856368","cellAttempt":1,"status":"failed","failureClass":"candidate_failure","causeCodes":["no_single_paperclip_document"],"failedMatcherPaths":["contextIntegrity.single-durable-output"],"model":"claude-sonnet-4-6","runtimeMode":"legacy","durationMs":37720,"providerDurationMs":25763,"cleanup":"passed","billing":{"llm":{"runCount":1,"runsWithTokenUsage":1,"runsWithReportedCost":1,"inputTokens":27376,"outputTokens":1281,"cachedInputTokens":184039,"totalTokens":212696,"reportedCostUsd":0.17707545,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":25763,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.17707545,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.17707545,"complete":true},"billingTypes":["metered_api"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"generic-procedures-absent":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":3611,"reportedPromptChars":3611,"freshPromptChars":783,"retrievalClipped":false}],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"8636a50a54d39fd66b1439f13ce5ac125b79f16b2826f76fc297427a1a2ab701","evidence-manifest.json":"d1d7f47272981406d104f5ae6455d5b0a4f6113f3c3d02fc8bc787d7c8fc70fc","snapshots/context-integrity.json":"eac7d7b889e5cc3861b937ffa87c7b92d25ade5b0f2bb73bdd29b5764c2a085a","snapshots/stock-harness-hire.json":"545030ff52d9196a604e099b81b52a3071f582f2467284c695eb62d96589c67c","snapshots/stock-harness.json":"c9ca463a792c7dccd00c0795c1e405495d9e1dff30b2cbae2b5e7bac7114fdd2","snapshots/stock-harness-preflight.json":"c1b5a843459d80e074759600c830c92bb00a0d2430255cda7bed5c22cabc7680"},"taskDocumentCount":0,"behaviorCheckCounts":{"passed":6,"failed":1}},
|
||||
{"executionId":"stock-harness.legacy-claude.local.continuity-restart","profile":"legacy-claude","case":"continuity-restart","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/reduce-default-agent-manual","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042856368","cellAttempt":1,"status":"failed","failureClass":"candidate_failure","causeCodes":["chat_memory_assertion"],"failedMatcherPaths":[],"model":"claude-sonnet-4-6","runtimeMode":"legacy","durationMs":28251,"providerDurationMs":11843,"cleanup":"passed","billing":{"llm":{"runCount":2,"runsWithTokenUsage":2,"runsWithReportedCost":2,"inputTokens":19490,"outputTokens":298,"cachedInputTokens":50061,"totalTokens":69849,"reportedCostUsd":0.09257055,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":11843,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.09257055,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.09257055,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"generic-procedures-absent":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":10776,"reportedPromptChars":10776,"freshPromptChars":783,"retrievalClipped":false},{"capturedPromptChars":10833,"reportedPromptChars":10833,"freshPromptChars":783,"retrievalClipped":false}],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"86af4c61a239c718181ab58fd815cf7a200e7473b2fe9390b58bc15ff9d6ed4c","evidence-manifest.json":"e1c69d7c6044b0e8ba65fa1b961de84989facb3fd65f6bba42632b22d2a1bab9","snapshots/context-integrity.json":null,"snapshots/stock-harness-hire.json":"9a320857e3d1fe14a41b0154b8c1d2cfc81f869ed69790bd729fe7c86e9c54fb","snapshots/stock-harness.json":"9a2d546c992bf9d59711e37e81d841b7b1c73e6e1d62a6f27b5302f1d8ea3980","snapshots/stock-harness-preflight.json":"13318522befc6c0ffcc0d6ca7a0dc12112902427ddc1a7ac36f3ac43e7f83d8d"}},
|
||||
{"executionId":"stock-harness.legacy-claude.local.ordered-comment-continuation","profile":"legacy-claude","case":"ordered-comment-continuation","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/reduce-default-agent-manual","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042856368","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"claude-sonnet-4-6","runtimeMode":"legacy","durationMs":158440,"providerDurationMs":139386,"cleanup":"passed","billing":{"llm":{"runCount":2,"runsWithTokenUsage":2,"runsWithReportedCost":2,"inputTokens":64318,"outputTokens":7515,"cachedInputTokens":669889,"totalTokens":741722,"reportedCostUsd":0.5548662,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":139386,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.5548662,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.5548662,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"generic-procedures-absent":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":11079,"reportedPromptChars":11053,"freshPromptChars":783,"retrievalClipped":false},{"capturedPromptChars":4025,"reportedPromptChars":4025,"freshPromptChars":783,"retrievalClipped":false}],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"59885705b15fc33517ab7f7a82d9ada2e411c918b10fa5b4ce928498a801e739","evidence-manifest.json":"c2a5be637da0d7ff9436b732d8db72ab1fdce788caf6a40c5973c14a27bcc08a","snapshots/context-integrity.json":"fbee7c1a37b964bcc327ebbf37602bc227bccb7fafdc4296614e178e66016599","snapshots/stock-harness-hire.json":"cf2f8ff4b1eb13c5e5f854ecac27c25826e7a4dbafbc277e8702b8beee6d913f","snapshots/stock-harness.json":"3e8e7c06f0918a896341a38e4613cc1f4caaad4b5ab12c03a8c3e41c4707a82a","snapshots/stock-harness-preflight.json":"6d6716a08fc2ea7d27ee08840fd3290a04a8e85dfed5950b690f4c2267d20a6e"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":12,"failed":0}},
|
||||
{"executionId":"stock-harness.legacy-codex.local.assigned-skill-explicit-invocation","profile":"legacy-codex","case":"assigned-skill-explicit-invocation","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/reduce-default-agent-manual","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042856368","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"gpt-5.6-sol","runtimeMode":"legacy","durationMs":44075,"providerDurationMs":27042,"cleanup":"passed","billing":{"llm":{"runCount":1,"runsWithTokenUsage":1,"runsWithReportedCost":0,"inputTokens":241944,"outputTokens":2132,"cachedInputTokens":215273,"totalTokens":459349,"reportedCostUsd":0,"costStatus":"unpriced"},"runtime":{"provider":"local","agentRunDurationMs":27042,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":false},"billingTypes":["metered_api"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"generic-procedures-absent":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":4223,"reportedPromptChars":4223,"freshPromptChars":782,"retrievalClipped":false}],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"b0b26f37d2f0c0342a9a275b22716be190891d1b55e79fa8d33e3f495649a26b","evidence-manifest.json":"446abc91b7366507c4100741d71ace903de0b78149b33e45fa90d39183f71b75","snapshots/context-integrity.json":"85197eb92d34334cc2de658d09d7a7e54d44ab3d2fd81ad87732c88c6610b6b2","snapshots/stock-harness-hire.json":"568453725988fc61d459f96efda07d19ec5c00935d4c4a9ff9557cd0062aef29","snapshots/stock-harness.json":"0c8626740b520687d08b0b1f0aab8a9ead2ebd58290252f05d890f524636f461","snapshots/stock-harness-preflight.json":"9a15351a65076deeb77ccd58216def768a658fc743fd57dc89c3234ff6b36542"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":7,"failed":0}},
|
||||
{"executionId":"stock-harness.legacy-codex.local.continuity-restart","profile":"legacy-codex","case":"continuity-restart","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/reduce-default-agent-manual","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042856368","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"gpt-5.6-sol","runtimeMode":"legacy","durationMs":62742,"providerDurationMs":10028,"cleanup":"passed","billing":{"llm":{"runCount":3,"runsWithTokenUsage":3,"runsWithReportedCost":0,"inputTokens":91929,"outputTokens":475,"cachedInputTokens":45089,"totalTokens":137493,"reportedCostUsd":0,"costStatus":"unpriced"},"runtime":{"provider":"local","agentRunDurationMs":10028,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":false},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"generic-procedures-absent":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":2787,"reportedPromptChars":2787,"freshPromptChars":0,"retrievalClipped":false},{"capturedPromptChars":2730,"reportedPromptChars":2730,"freshPromptChars":0,"retrievalClipped":false},{"capturedPromptChars":11443,"reportedPromptChars":11443,"freshPromptChars":782,"retrievalClipped":false}],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"1ec35f3857350d86d3888c4becf8ce898a59641f3eb9825a0d800970ec7090ad","evidence-manifest.json":"908761dcb779dcd0b95ef78b5e9addb77b211acbc502a1c8fdf793202d309472","snapshots/context-integrity.json":null,"snapshots/stock-harness-hire.json":"b7b6f4db7cdc84a795ccfa24df58dd8131fe8a44b3ef1cdaf36dfc6685556da7","snapshots/stock-harness.json":"9a0045bbf4240ddbf08ec0444e08ef70e3ef76399db09075b5316b1fc51ac5bf","snapshots/stock-harness-preflight.json":"cfbae9842e2a045b815a9a7f1fa471f8f9494ec999c91e9808933cbc013c84a4"}},
|
||||
{"executionId":"stock-harness.legacy-codex.local.ordered-comment-continuation","profile":"legacy-codex","case":"ordered-comment-continuation","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/reduce-default-agent-manual","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37042856368","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"gpt-5.6-sol","runtimeMode":"legacy","durationMs":66403,"providerDurationMs":44175,"cleanup":"passed","billing":{"llm":{"runCount":2,"runsWithTokenUsage":2,"runsWithReportedCost":0,"inputTokens":416426,"outputTokens":4483,"cachedInputTokens":353673,"totalTokens":774582,"reportedCostUsd":0,"costStatus":"unpriced"},"runtime":{"provider":"local","agentRunDurationMs":44175,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":false},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"generic-procedures-absent":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":8456,"reportedPromptChars":8430,"freshPromptChars":0,"retrievalClipped":false},{"capturedPromptChars":4637,"reportedPromptChars":4637,"freshPromptChars":782,"retrievalClipped":false}],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"71f99ec1b45a57ae40acbd42c55e835f63d9d2329752f6bffcabd4488ecb2a96","evidence-manifest.json":"0c3347aaec6d7c3498e62c19938762c35a6069d31385e5d617174695ab60462e","snapshots/context-integrity.json":"5c08b2af1b27e9255505e917958e7b97856cfbf48cce3bef3b8e792275823b6e","snapshots/stock-harness-hire.json":"ddc6b182c4d53c7cd7f63c906d2a64e6286c54c8b6e9991e0c78da578586d865","snapshots/stock-harness.json":"11a737f54107eb9a9e55234890bce90003f84b258f6d5c8512d65f080edfc52f","snapshots/stock-harness-preflight.json":"d72a98df5a4e00bfa766e85ef28b866d32b89fe2aa9a67e3000ad50ddea47599"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":12,"failed":0}},
|
||||
{"executionId":"stock-harness.legacy-opencode.local.assigned-skill-explicit-invocation","profile":"legacy-opencode","case":"assigned-skill-explicit-invocation","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/stock-harness-candidate-cancelled-cells","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37045368302","cellAttempt":1,"status":"failed","failureClass":"candidate_failure","causeCodes":["no_single_paperclip_document"],"failedMatcherPaths":["contextIntegrity.single-durable-output"],"model":"openrouter/deepseek/deepseek-v4-flash-0731","runtimeMode":"legacy","durationMs":212558,"providerDurationMs":199337,"cleanup":"passed","billing":{"llm":{"runCount":2,"runsWithTokenUsage":2,"runsWithReportedCost":2,"inputTokens":96260,"outputTokens":6708,"cachedInputTokens":172288,"totalTokens":275256,"reportedCostUsd":0.010608548800000001,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":199337,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.010608548800000001,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.010608548800000001,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"generic-procedures-absent":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":8426,"reportedPromptChars":8439,"freshPromptChars":0,"retrievalClipped":false},{"capturedPromptChars":4226,"reportedPromptChars":4226,"freshPromptChars":785,"retrievalClipped":false}],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"435f708b59438d1d344130bc9e23b8ddbc93adf74d4e5ea8f049e0f1d06efa72","evidence-manifest.json":"8591f98df496159f99b98659ffdcbdf2e86718dccb4545d461f592bab0626b05","snapshots/context-integrity.json":"e4271e26bb891c3c15f2d54dd5cfba6605ebb4431456d70b4f146bb8eb4e1788","snapshots/stock-harness-hire.json":"6bb95cb92fbb14976b5f6796f530823f6aacdcb1448f89cb8b2ec093125266e2","snapshots/stock-harness.json":"b8a5a04730324a038869e28f79d55dd06ebf12abc2e6366bbfe9218995a6748b","snapshots/stock-harness-preflight.json":"c17087412694993475276a69162a171859dc54afae5b08ab89573cf0637d6e19"},"taskDocumentCount":0,"behaviorCheckCounts":{"passed":6,"failed":1}},
|
||||
{"executionId":"stock-harness.legacy-opencode.local.continuity-restart","profile":"legacy-opencode","case":"continuity-restart","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/stock-harness-candidate-cancelled-cells","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37045368302","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"openrouter/deepseek/deepseek-v4-flash-0731","runtimeMode":"legacy","durationMs":69761,"providerDurationMs":19747,"cleanup":"passed","billing":{"llm":{"runCount":3,"runsWithTokenUsage":3,"runsWithReportedCost":3,"inputTokens":12219,"outputTokens":116,"cachedInputTokens":0,"totalTokens":12335,"reportedCostUsd":0.0003185752,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":19747,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.0003185752,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.0003185752,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"generic-procedures-absent":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":3403,"reportedPromptChars":3403,"freshPromptChars":0,"retrievalClipped":false},{"capturedPromptChars":3346,"reportedPromptChars":3346,"freshPromptChars":0,"retrievalClipped":false},{"capturedPromptChars":11452,"reportedPromptChars":11452,"freshPromptChars":785,"retrievalClipped":false}],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"0e56bb0c46a05efeeffa6feb09f54fe13a33c2c96ab4e7a7ec548a0d80a1a235","evidence-manifest.json":"0e601f9bde1bdb7f44f000704078343a1478046c9eb488f2c8a785cd1d4cebec","snapshots/context-integrity.json":null,"snapshots/stock-harness-hire.json":"ab7d6af088306e1d43d4901a370dffa46b16dd4c17c2838a8e9e48a2b8e7a62f","snapshots/stock-harness.json":"2a6eabd1657c89568f890de12840747b544c7385c4363b3016879b07028961fa","snapshots/stock-harness-preflight.json":"539f106844e9421c284944c0b1526d74b0cc8e0905c5960be14cfa77ee17a023"}},
|
||||
{"executionId":"stock-harness.legacy-opencode.local.ordered-comment-continuation","profile":"legacy-opencode","case":"ordered-comment-continuation","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/stock-harness-candidate-cancelled-cells","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37045368302","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"openrouter/deepseek/deepseek-v4-flash-0731","runtimeMode":"legacy","durationMs":212184,"providerDurationMs":188508,"cleanup":"passed","billing":{"llm":{"runCount":2,"runsWithTokenUsage":2,"runsWithReportedCost":2,"inputTokens":177025,"outputTokens":7495,"cachedInputTokens":256512,"totalTokens":441032,"reportedCostUsd":0.011804638700000002,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":188508,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.011804638700000002,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.011804638700000002,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true,"invocation-evidence-complete":true,"generic-procedures-absent":true,"fresh-default-delivered":true},"invocationMetrics":[{"capturedPromptChars":10775,"reportedPromptChars":10736,"freshPromptChars":0,"retrievalClipped":false},{"capturedPromptChars":4640,"reportedPromptChars":4640,"freshPromptChars":785,"retrievalClipped":false}],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"8ca5a9a446dda03100d8ee66c5b9e02fd19c335dfc8cc34f83662aa1181240b9","evidence-manifest.json":"496a392c39d3a75f0287c19a6fc25e7577dee6bfef0e3c7f16801f61574cfa99","snapshots/context-integrity.json":"da023ad1b2e22b1659573e1829cfb273a4d3e329d1b324506dbc73104901ecf7","snapshots/stock-harness-hire.json":"ec6d9274b80d291e8949c71a9bfb1f6fdc88cd78a2094dcad1c0a599ce9b78cd","snapshots/stock-harness.json":"680d3af8a66f473a95905920123f0760aff53eb4ce09cd01393a78b6292178cd","snapshots/stock-harness-preflight.json":"5b79fae54bd6036dab2ad8bf8e8bacb9087e702b363dbbe4af2c58115eea8ead"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":12,"failed":0}},
|
||||
{"executionId":"stock-harness.runner-acpx-claude.local.assigned-skill-explicit-invocation","profile":"runner-acpx-claude","case":"assigned-skill-explicit-invocation","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/stock-harness-candidate-cancelled-cells","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37045368302","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"claude-sonnet-5","runtimeMode":"native","durationMs":50059,"providerDurationMs":28899,"cleanup":"passed","billing":{"llm":{"runCount":1,"runsWithTokenUsage":1,"runsWithReportedCost":1,"inputTokens":10,"outputTokens":1332,"cachedInputTokens":136292,"totalTokens":137634,"reportedCostUsd":0,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":28899,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":true},"billingTypes":["unknown"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true},"invocationMetrics":[],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"526b8881ac8dd2b51a788ca6ae535f7cd856e38066fadbd70737071c4f898abf","evidence-manifest.json":"40dc320ecab36c004d5e33bba8ebe1fb4d8444fd23d7ef0f055b188b474fffca","snapshots/context-integrity.json":"0b0b2b42376e1c5cf58b6ae1e9e0960b6b2bc82218e20589e04ca44639207ecb","snapshots/stock-harness-hire.json":"7f47075f11209d29c92091925bb61b2e2e22b06e1ce49b7dba69060242d62fc5","snapshots/stock-harness.json":"06398532782d5e4c9eb76ef55cdc87ed8d49c6f31dc06f09ce28715aa09f76fa","snapshots/stock-harness-preflight.json":"cb1980515ce11e2b55fcea5799277f511d0c6d01bf26b78e93be3ae17fa9b08d"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":7,"failed":0}},
|
||||
{"executionId":"stock-harness.runner-acpx-claude.local.continuity-restart","profile":"runner-acpx-claude","case":"continuity-restart","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/stock-harness-candidate-cancelled-cells","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37045368302","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"claude-sonnet-5","runtimeMode":"native","durationMs":93902,"providerDurationMs":43211,"cleanup":"passed","billing":{"llm":{"runCount":3,"runsWithTokenUsage":3,"runsWithReportedCost":3,"inputTokens":6,"outputTokens":101,"cachedInputTokens":92222,"totalTokens":92329,"reportedCostUsd":0,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":43211,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true},"invocationMetrics":[],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"332ba05ee7cda6971777af3454e8589652f742f45cd6350da4396a420e8c549f","evidence-manifest.json":"eaff7dd508a80dffef9d638a39e13b21d2ef427086ae478c10520c2b336fe881","snapshots/context-integrity.json":null,"snapshots/stock-harness-hire.json":"e8a854b3a0d886b01cb910e5b05baa5f1c791ef57b9a04168a0ce65cc60df7ce","snapshots/stock-harness.json":"1e53449e403bc079887cd183e2b556fbecd238a20130ef07c0c5decf499ff4ae","snapshots/stock-harness-preflight.json":"02283c369f44ab85a6400e0659da00e48bee84727fcebc35a8a691c4529c7cc0"}},
|
||||
{"executionId":"stock-harness.runner-acpx-claude.local.ordered-comment-continuation","profile":"runner-acpx-claude","case":"ordered-comment-continuation","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/stock-harness-candidate-cancelled-cells","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37045368302","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"claude-sonnet-5","runtimeMode":"native","durationMs":93050,"providerDurationMs":70393,"cleanup":"passed","billing":{"llm":{"runCount":2,"runsWithTokenUsage":2,"runsWithReportedCost":2,"inputTokens":18,"outputTokens":3794,"cachedInputTokens":332880,"totalTokens":336692,"reportedCostUsd":0,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":70393,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true},"invocationMetrics":[],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"3aae7a7ca62c39c74ac6d12e71b6b75223c76112ea018b8d08db1da52ea72e6a","evidence-manifest.json":"f08a3eee09a8fd4b279865dbe8f9ab2a66cc1a79addb216fe484561fcb9e2467","snapshots/context-integrity.json":"400df6ee5a9294998d4306f6eab6816e86155d8ecf0f39a6fc672381d47d423f","snapshots/stock-harness-hire.json":"604c41ac65944ef25cb7bbbf19941401139ed422342d7d6ebf9a07b79ecf7a75","snapshots/stock-harness.json":"f17d4f47657a0151dbfc585de64e338b7f79deae052f454794c2b934a7d398a2","snapshots/stock-harness-preflight.json":"fa502f0e3a3467dab6b04f810e6001ca635ce2d4bb5bae42a50e7a85f520a356"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":12,"failed":0}},
|
||||
{"executionId":"stock-harness.runner-codex.local.assigned-skill-explicit-invocation","profile":"runner-codex","case":"assigned-skill-explicit-invocation","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/stock-harness-candidate-cancelled-cells","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37045368302","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"gpt-5.6-sol","runtimeMode":"native","durationMs":48064,"providerDurationMs":26079,"cleanup":"passed","billing":{"llm":{"runCount":1,"runsWithTokenUsage":1,"runsWithReportedCost":1,"inputTokens":133429,"outputTokens":1102,"cachedInputTokens":110284,"totalTokens":244815,"reportedCostUsd":0,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":26079,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":true},"billingTypes":["unknown"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true},"invocationMetrics":[],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"690736dbf71179913631255763fd7b8aecd825236532154e54a3e1e53240d6b2","evidence-manifest.json":"75135aa0e7020617f54b5f2f593a5d48669ac7ecc864064d1eefc91af86d431c","snapshots/context-integrity.json":"46e65857a4d82f6ad900bc7e89166c35f2543faa368e48130f83dcea526ca65e","snapshots/stock-harness-hire.json":"2bf564606e974bb38baf50e363edd6d11aa5009fd429602a44fecb337bf9036c","snapshots/stock-harness.json":"2bf943a7cba0fc3886a04fa96f5646677f427e516232b35f6538384f4e6ed39f","snapshots/stock-harness-preflight.json":"635be5d6d578d7ad95b0e87697b939ed10f3cf2d3338caedc94350a1ed67ccc9"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":7,"failed":0}},
|
||||
{"executionId":"stock-harness.runner-codex.local.continuity-restart","profile":"runner-codex","case":"continuity-restart","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/stock-harness-candidate-cancelled-cells","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37045368302","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"gpt-5.6-sol","runtimeMode":"native","durationMs":89776,"providerDurationMs":43862,"cleanup":"passed","billing":{"llm":{"runCount":3,"runsWithTokenUsage":3,"runsWithReportedCost":3,"inputTokens":460440,"outputTokens":2336,"cachedInputTokens":378421,"totalTokens":841197,"reportedCostUsd":0,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":43862,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true},"invocationMetrics":[],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"b32c6c2c10d1eaabd0e2408db313bf9f30a7a60413733ef8ddfd8114ff6f2fdb","evidence-manifest.json":"34a60f60f021a731a98fab708ac31e344d8ecfa5a1d7dc72daa8c957028cbcea","snapshots/context-integrity.json":null,"snapshots/stock-harness-hire.json":"855403ffdcaa2f1542a5ebbc0dbd7590c609c170afc657884eeb9a1e79a51310","snapshots/stock-harness.json":"d4997c371e7db1a9e1c13536279480035eee7873e92a3ab3300969d508339481","snapshots/stock-harness-preflight.json":"642913765f2dfb9bbae2f46401448e4c3b6d3f9134cba2a073178d530c395efc"}},
|
||||
{"executionId":"stock-harness.runner-codex.local.ordered-comment-continuation","profile":"runner-codex","case":"ordered-comment-continuation","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/stock-harness-candidate-cancelled-cells","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37045368302","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"gpt-5.6-sol","runtimeMode":"native","durationMs":85296,"providerDurationMs":64569,"cleanup":"passed","billing":{"llm":{"runCount":2,"runsWithTokenUsage":2,"runsWithReportedCost":2,"inputTokens":359345,"outputTokens":2627,"cachedInputTokens":307813,"totalTokens":669785,"reportedCostUsd":0,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":64569,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true},"invocationMetrics":[],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"1a1a5f9b53dfc8cd2d96bafb8b34f6b1ac006514da70963ea3d8328f1c594309","evidence-manifest.json":"5220dd721a29958a2dbc6139a33bed2c94765801181c795abca6118722204f7b","snapshots/context-integrity.json":"100d34aa2c461b540164a00442100d5f6f438f706d14fb7316d80bf07d680cd8","snapshots/stock-harness-hire.json":"103296d40bc0b4500fa9b45ffb1e8192b770a1d4f87f8d0dd4f9e309384d407c","snapshots/stock-harness.json":"8d1eea8270fd2c29e284db9af321bebe85e635f6ec77d56674466d1480012ba8","snapshots/stock-harness-preflight.json":"be022954d2b5f5ef0bdae4cd892f06163f9d0863d0201aa92a5bab12f56a4dac"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":12,"failed":0}},
|
||||
{"executionId":"stock-harness.runner-opencode.local.assigned-skill-explicit-invocation","profile":"runner-opencode","case":"assigned-skill-explicit-invocation","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/stock-harness-candidate-cancelled-cells","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37045368302","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"openrouter/deepseek/deepseek-v4-flash-0731","runtimeMode":"native","durationMs":69432,"providerDurationMs":45144,"cleanup":"passed","billing":{"llm":{"runCount":1,"runsWithTokenUsage":1,"runsWithReportedCost":1,"inputTokens":22870,"outputTokens":1171,"cachedInputTokens":105472,"totalTokens":129513,"reportedCostUsd":0.0021534242,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":45144,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.0021534242,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.0021534242,"complete":true},"billingTypes":["unknown"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true},"invocationMetrics":[],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"69ac8808e939a796c40ac3665d56b152b43747fa91b7bbc84fd0a0f2fc68f5d9","evidence-manifest.json":"a0af2ef7ba4653d7ec825ac12de2a9e5f8b7fd7042bcfbe215e8af1b75ad38f8","snapshots/context-integrity.json":"e82ea3b31d4c6c73a95715c1d0e31338eb31a50f760bb6ea99b1d21032c6da32","snapshots/stock-harness-hire.json":"87ec7c1456344be2ed5cf68d4813920e8204fc483c6a34b1b62116bd721d9073","snapshots/stock-harness.json":"11cb4a18967ee59a7d5c202ee590a8dcfa7a9c145ab5ac87cde8da6a69f25497","snapshots/stock-harness-preflight.json":"c044805c4bb0db4c3cac0a4bbf54b937da7d1d376a4ac319f4d2baf3c636f51c"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":7,"failed":0}},
|
||||
{"executionId":"stock-harness.runner-opencode.local.continuity-restart","profile":"runner-opencode","case":"continuity-restart","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/stock-harness-candidate-cancelled-cells","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37045368302","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"openrouter/deepseek/deepseek-v4-flash-0731","runtimeMode":"native","durationMs":152945,"providerDurationMs":93715,"cleanup":"passed","billing":{"llm":{"runCount":3,"runsWithTokenUsage":3,"runsWithReportedCost":3,"inputTokens":26520,"outputTokens":106,"cachedInputTokens":45312,"totalTokens":71938,"reportedCostUsd":0.0005020232,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":93715,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.0005020232,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.0005020232,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true},"invocationMetrics":[],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"ae12b2eee65f45b8e2f530c037002e91db351134b6848da89e80bf7c15eb25a3","evidence-manifest.json":"dce1906b35fbbe6ac471ef922a2f0b1bae9da9438d56e78894e3651cba402aaa","snapshots/context-integrity.json":null,"snapshots/stock-harness-hire.json":"a57da515e6454ee8b82b9bf633a8e07097217f30555606538e135c7979de71c0","snapshots/stock-harness.json":"fa0501dbd07065bbbbd9a1f4c6bd82ba27f0fa45238790d531db4587c000309d","snapshots/stock-harness-preflight.json":"a20db2a5478fb7bd568bf6fcfbe7702dbe2a7a0390d9b36ec0fe2a6d07954d1b"}},
|
||||
{"executionId":"stock-harness.runner-opencode.local.ordered-comment-continuation","profile":"runner-opencode","case":"ordered-comment-continuation","sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceRef":"refs/heads/codex/stock-harness-candidate-cancelled-cells","workflowRunUrl":"https://github.com/paperclipai/paperclip/actions/runs/37045368302","cellAttempt":1,"status":"passed","failureClass":null,"causeCodes":[],"failedMatcherPaths":[],"model":"openrouter/deepseek/deepseek-v4-flash-0731","runtimeMode":"native","durationMs":95671,"providerDurationMs":72942,"cleanup":"passed","billing":{"llm":{"runCount":2,"runsWithTokenUsage":2,"runsWithReportedCost":2,"inputTokens":24355,"outputTokens":2690,"cachedInputTokens":160512,"totalTokens":187557,"reportedCostUsd":0.0043860217,"costStatus":"reported"},"runtime":{"provider":"local","agentRunDurationMs":72942,"leaseDurationMs":null,"leaseCount":0,"costStatus":"not_metered","costSource":"local_not_metered"},"reportedCostUsd":0.0043860217,"estimatedRuntimeCostUsd":0,"observedAndEstimatedCostUsd":0.0043860217,"complete":true},"billingTypes":["unavailable"],"stockChecks":{"budget-hard-stops":true,"default-hire-bundle":true,"provider-runs-present":true},"invocationMetrics":[],"prerequisite":{"sourceSha":"f02d8d0df327abb43b20c7e7beb86798239abbf5","sourceFingerprint":"ea488f1e5f67865c0e08e3628eb3081b86c6b3a8e40631702697ffac08359211","passed":true,"providerCallsBeforeAdmission":0,"passedTests":537},"evidenceHashes":{"result.json":"54f3029b581b82be87bd30b7670029bd5722915917e4bcab2e8ef0682bc3c0be","evidence-manifest.json":"b793fa707ccc2125b192816d59e922a0d43348c11eb516b8dff7e5d445b5545b","snapshots/context-integrity.json":"105ff5eb813ac3779e3ba04a8115ce1495c185011de4735782d3c4adba99787e","snapshots/stock-harness-hire.json":"4dde5055f48fff1b4cf466b4307739c967e2595e3c2e93d4e07230545741fa16","snapshots/stock-harness.json":"b0ce3eb0edce09ca4ef61af6f622cddd7c9bb1a3286c9ea7756dc228d00c10fa","snapshots/stock-harness-preflight.json":"78f2298b3bb4357b2a3ce6d75a922fc34d6449ef43378bb0dcc40476469daa01"},"taskDocumentCount":1,"behaviorCheckCounts":{"passed":12,"failed":0}}
|
||||
],
|
||||
"totals": {
|
||||
"baseline": {
|
||||
"results": 24,
|
||||
"passed": 15,
|
||||
"failed": 9,
|
||||
"missing": 0,
|
||||
"cleanupPassed": 24,
|
||||
"durationMs": 3250195,
|
||||
"providerDurationMs": 2108034,
|
||||
"llm": {
|
||||
"runCount": 58,
|
||||
"runsWithTokenUsage": 52,
|
||||
"runsWithReportedCost": 43,
|
||||
"inputTokens": 2778581,
|
||||
"outputTokens": 68015,
|
||||
"cachedInputTokens": 6908917,
|
||||
"reportedCostUsd": 1.7494252618000001
|
||||
}
|
||||
},
|
||||
"candidate": {
|
||||
"results": 24,
|
||||
"passed": 15,
|
||||
"failed": 9,
|
||||
"missing": 0,
|
||||
"cleanupPassed": 24,
|
||||
"durationMs": 2804913,
|
||||
"providerDurationMs": 1540860,
|
||||
"llm": {
|
||||
"runCount": 47,
|
||||
"runsWithTokenUsage": 45,
|
||||
"runsWithReportedCost": 36,
|
||||
"inputTokens": 2280185,
|
||||
"outputTokens": 58933,
|
||||
"cachedInputTokens": 4655025,
|
||||
"reportedCostUsd": 1.7431513318
|
||||
}
|
||||
}
|
||||
},
|
||||
"matchedPassingTiming": {
|
||||
"baseline": {
|
||||
"cells": 13,
|
||||
"medianCellDurationMs": 85550,
|
||||
"medianProviderDurationMs": 57150
|
||||
},
|
||||
"candidate": {
|
||||
"cells": 13,
|
||||
"medianCellDurationMs": 69761,
|
||||
"medianProviderDurationMs": 43862
|
||||
}
|
||||
},
|
||||
"missingBaseline": [],
|
||||
"cancellationRecovery": {
|
||||
"sourceSha": "f02d8d0df327abb43b20c7e7beb86798239abbf5",
|
||||
"originalRun": 37042856368,
|
||||
"reason": "superseding pilot cancelled incomplete cells; completed behavior failures not rerun",
|
||||
"executionIds": [
|
||||
"stock-harness.legacy-acp-codex.local.ordered-comment-continuation",
|
||||
"stock-harness.legacy-opencode.local.ordered-comment-continuation",
|
||||
"stock-harness.legacy-opencode.local.continuity-restart",
|
||||
"stock-harness.runner-acpx-claude.local.continuity-restart",
|
||||
"stock-harness.legacy-opencode.local.assigned-skill-explicit-invocation",
|
||||
"stock-harness.runner-acpx-claude.local.assigned-skill-explicit-invocation",
|
||||
"stock-harness.runner-acpx-claude.local.ordered-comment-continuation",
|
||||
"stock-harness.runner-codex.local.continuity-restart",
|
||||
"stock-harness.runner-codex.local.ordered-comment-continuation",
|
||||
"stock-harness.runner-codex.local.assigned-skill-explicit-invocation",
|
||||
"stock-harness.runner-opencode.local.assigned-skill-explicit-invocation",
|
||||
"stock-harness.runner-opencode.local.continuity-restart",
|
||||
"stock-harness.runner-opencode.local.ordered-comment-continuation"
|
||||
]
|
||||
},
|
||||
"baselineInfrastructureRecovery": {
|
||||
"schema": "paperclip.stock-harness.infra-recovery/v1",
|
||||
"sourceSha": "12c5433c67dc2e62916b879349c7ba2b6e0431f0",
|
||||
"originalRun": 37042864888,
|
||||
"originalJob": 110958906481,
|
||||
"reason": "AWS runner shutdown, no result artifact; no completed model failure rerun",
|
||||
"executionIds": [
|
||||
"stock-harness.legacy-opencode.local.ordered-comment-continuation"
|
||||
]
|
||||
},
|
||||
"layoutRecovery": {
|
||||
"resultEdits": 0,
|
||||
"oracleEdits": 0,
|
||||
"originalEvidenceRetained": true,
|
||||
"manifests": [
|
||||
{
|
||||
"campaign": "candidate-original",
|
||||
"sha256": "9135457cdc339d2fcaeea52cd06622ecaea6a74561df73d4d7a0baa0d8c0e7cc"
|
||||
},
|
||||
{
|
||||
"campaign": "baseline-final",
|
||||
"sha256": "85223428a0d473ba83e324621e80d711725ebe069c4f055665b0bf987d1f0c4c"
|
||||
},
|
||||
{
|
||||
"campaign": "candidate-recovery",
|
||||
"sha256": "7788f879d0cf1d140d452cfa78fe085b34bc23d298d7c31cac9d630b12b2572f"
|
||||
},
|
||||
{
|
||||
"campaign": "baseline-recovery-final",
|
||||
"sha256": "e39d62dd60f092e76f8776e844041f2458fca1989854d57ab13932a36c937ceb"
|
||||
}
|
||||
]
|
||||
},
|
||||
"limitations": [
|
||||
"Single trial per cell; not general coding quality or statistical proof.",
|
||||
"Skill task document storage wording is ambiguous; original graders unchanged.",
|
||||
"Persisted credential guard failures are not equivalent to poor task behavior.",
|
||||
"Report costs are incomplete; zero reported native cost has unknown billing type.",
|
||||
"Local runtime and interrupted partial attempts are not metered.",
|
||||
"Historical public prompt retrieval is clipped in three ACP cells; missing suffix does not prove provider omission.",
|
||||
"Reconstructed reports use separate copies with directory-layout-only repair; original public reports retain infrastructure failures."
|
||||
],
|
||||
"fixtureMatchProof": {
|
||||
"schema": "paperclip.stock-harness.fixture-match-proof/v1",
|
||||
"cells": [
|
||||
{
|
||||
"executionId": "stock-harness.legacy-codex.local.ordered-comment-continuation",
|
||||
"fixtureConfigSha256": "f727e68de06dec501c49efc41ab941bd6d63826db82e615d5e60f2d20133b488",
|
||||
"model": "gpt-5.6-sol"
|
||||
},
|
||||
{
|
||||
"executionId": "stock-harness.legacy-codex.local.assigned-skill-explicit-invocation",
|
||||
"fixtureConfigSha256": "ca51e8b29aab8e6efa32f2433c0f3960e43f9fc2f5edcd0660e753810d71f30d",
|
||||
"model": "gpt-5.6-sol"
|
||||
},
|
||||
{
|
||||
"executionId": "stock-harness.legacy-codex.local.continuity-restart",
|
||||
"fixtureConfigSha256": "8b1669039f3285f89a7f93c57e1c0dc12f071fbb2bbc8dcef6bf70bc14a4949a",
|
||||
"model": "gpt-5.6-sol"
|
||||
},
|
||||
{
|
||||
"executionId": "stock-harness.legacy-claude.local.ordered-comment-continuation",
|
||||
"fixtureConfigSha256": "94db17927ceddb54fe7766a362adcf78783726c68e1e136e407572aef6fed319",
|
||||
"model": "claude-sonnet-4-6"
|
||||
},
|
||||
{
|
||||
"executionId": "stock-harness.legacy-claude.local.assigned-skill-explicit-invocation",
|
||||
"fixtureConfigSha256": "6c21be9bb3fcec6274372fc586d88e42d82bc0d438e26a69ef614f8e6fcb8519",
|
||||
"model": "claude-sonnet-4-6"
|
||||
},
|
||||
{
|
||||
"executionId": "stock-harness.legacy-claude.local.continuity-restart",
|
||||
"fixtureConfigSha256": "695071e5453c90288659f54f40fb307486f64d5fb8a401b767d2e9974faab15f",
|
||||
"model": "claude-sonnet-4-6"
|
||||
},
|
||||
{
|
||||
"executionId": "stock-harness.runner-codex.local.ordered-comment-continuation",
|
||||
"fixtureConfigSha256": "9e19859684aad0246fd009061101eec59c41179e51e92a9467f506af22558126",
|
||||
"model": "gpt-5.6-sol"
|
||||
},
|
||||
{
|
||||
"executionId": "stock-harness.runner-codex.local.assigned-skill-explicit-invocation",
|
||||
"fixtureConfigSha256": "8fc61211aefb339a310469695d716c3faea15c2a547c543d70c599857a7e071d",
|
||||
"model": "gpt-5.6-sol"
|
||||
},
|
||||
{
|
||||
"executionId": "stock-harness.runner-codex.local.continuity-restart",
|
||||
"fixtureConfigSha256": "024e90354d760f90a40e2b3923468b72d36a9fabdb4fc58c10a7939a09e5cbc3",
|
||||
"model": "gpt-5.6-sol"
|
||||
},
|
||||
{
|
||||
"executionId": "stock-harness.runner-opencode.local.ordered-comment-continuation",
|
||||
"fixtureConfigSha256": "9dd6fef8fea77b07a67681acc1d7866fac9b4f16e23aca69eda32a4656cc260b",
|
||||
"model": "openrouter/deepseek/deepseek-v4-flash-0731"
|
||||
},
|
||||
{
|
||||
"executionId": "stock-harness.runner-opencode.local.assigned-skill-explicit-invocation",
|
||||
"fixtureConfigSha256": "891eb6dd154aa5926a3de2eae79780661b436618b6eac7733998750c6c341ba3",
|
||||
"model": "openrouter/deepseek/deepseek-v4-flash-0731"
|
||||
},
|
||||
{
|
||||
"executionId": "stock-harness.runner-opencode.local.continuity-restart",
|
||||
"fixtureConfigSha256": "441c2fa8bdd26cf14ac2440311b99a71b7dd6202698cafc3d01ed7a91dc4b004",
|
||||
"model": "openrouter/deepseek/deepseek-v4-flash-0731"
|
||||
},
|
||||
{
|
||||
"executionId": "stock-harness.runner-acpx-claude.local.ordered-comment-continuation",
|
||||
"fixtureConfigSha256": "77cc32714b23852d3ef67c687202d89f7513275500b3c522a0a1de3d745c9c4e",
|
||||
"model": "claude-sonnet-5"
|
||||
},
|
||||
{
|
||||
"executionId": "stock-harness.runner-acpx-claude.local.assigned-skill-explicit-invocation",
|
||||
"fixtureConfigSha256": "c190a1e731ef450ea6ae0c24b5a8ae0f21db725b47350645cdbb9273415c5acd",
|
||||
"model": "claude-sonnet-5"
|
||||
},
|
||||
{
|
||||
"executionId": "stock-harness.runner-acpx-claude.local.continuity-restart",
|
||||
"fixtureConfigSha256": "40876df6f88f34ba8232ccba8816bf7e1173be0042a90a174f79cddcca346c4c",
|
||||
"model": "claude-sonnet-5"
|
||||
},
|
||||
{
|
||||
"executionId": "stock-harness.legacy-acp-codex.local.ordered-comment-continuation",
|
||||
"fixtureConfigSha256": "22500c39874cda62a2512ac3d8a43309ce5879f55f8515e31dd8ac632edb6c93",
|
||||
"model": "gpt-5.6-sol"
|
||||
},
|
||||
{
|
||||
"executionId": "stock-harness.legacy-acp-codex.local.assigned-skill-explicit-invocation",
|
||||
"fixtureConfigSha256": "896d8602d2990bf7517a4392d66909c6649e0f00f79a5ea0bb8f0e370b7b597a",
|
||||
"model": "gpt-5.6-sol"
|
||||
},
|
||||
{
|
||||
"executionId": "stock-harness.legacy-acp-codex.local.continuity-restart",
|
||||
"fixtureConfigSha256": "7b66845e231674cc6e78f1d86e863dc03005998c2674316af50db60ec53b7e4a",
|
||||
"model": "gpt-5.6-sol"
|
||||
},
|
||||
{
|
||||
"executionId": "stock-harness.legacy-acp-claude.local.ordered-comment-continuation",
|
||||
"fixtureConfigSha256": "6c1b9d7debb306af405640eb381e1709ae7185e7925bc2bd3ef31acf98fbff8f",
|
||||
"model": "claude-sonnet-4-6"
|
||||
},
|
||||
{
|
||||
"executionId": "stock-harness.legacy-acp-claude.local.assigned-skill-explicit-invocation",
|
||||
"fixtureConfigSha256": "53f308d6fbdc30adc60370279ab38679b5d87075899be4db6c0730593e1632c4",
|
||||
"model": "claude-sonnet-4-6"
|
||||
},
|
||||
{
|
||||
"executionId": "stock-harness.legacy-acp-claude.local.continuity-restart",
|
||||
"fixtureConfigSha256": "80ec22c5080ef1a71fc97547c3e78f4bc5f54f121f46f982506f2f9f773b27d0",
|
||||
"model": "claude-sonnet-4-6"
|
||||
},
|
||||
{
|
||||
"executionId": "stock-harness.legacy-opencode.local.ordered-comment-continuation",
|
||||
"fixtureConfigSha256": "e015ded0177d8d4c893acc658e25699b28937d97cb9d58da023ddff7ba7eac0b",
|
||||
"model": "openrouter/deepseek/deepseek-v4-flash-0731"
|
||||
},
|
||||
{
|
||||
"executionId": "stock-harness.legacy-opencode.local.assigned-skill-explicit-invocation",
|
||||
"fixtureConfigSha256": "dd673f469cab583d26bc2e4b484f18b27884ec14a59542177026389181e2da2c",
|
||||
"model": "openrouter/deepseek/deepseek-v4-flash-0731"
|
||||
},
|
||||
{
|
||||
"executionId": "stock-harness.legacy-opencode.local.continuity-restart",
|
||||
"fixtureConfigSha256": "3dadc83dff00ef2919cd24c6328ea9507f971e7d294e9ece699ff2ee6fa98230",
|
||||
"model": "openrouter/deepseek/deepseek-v4-flash-0731"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workflowProvenance": [
|
||||
{
|
||||
"run": 37042856368,
|
||||
"workflowSha": "7d59de6113caf5219d34d65e1fb5f0c1a7fbdc2a",
|
||||
"workflowUrl": "https://github.com/paperclipai/paperclip/actions/runs/37042856368"
|
||||
},
|
||||
{
|
||||
"run": 37042864888,
|
||||
"workflowSha": "7d59de6113caf5219d34d65e1fb5f0c1a7fbdc2a",
|
||||
"workflowUrl": "https://github.com/paperclipai/paperclip/actions/runs/37042864888"
|
||||
},
|
||||
{
|
||||
"run": 37045368302,
|
||||
"workflowSha": "7d59de6113caf5219d34d65e1fb5f0c1a7fbdc2a",
|
||||
"workflowUrl": "https://github.com/paperclipai/paperclip/actions/runs/37045368302"
|
||||
},
|
||||
{
|
||||
"run": 37044967981,
|
||||
"workflowSha": "7d59de6113caf5219d34d65e1fb5f0c1a7fbdc2a",
|
||||
"workflowUrl": "https://github.com/paperclipai/paperclip/actions/runs/37044967981"
|
||||
},
|
||||
{
|
||||
"run": 37048838402,
|
||||
"workflowSha": "7d59de6113caf5219d34d65e1fb5f0c1a7fbdc2a",
|
||||
"workflowUrl": "https://github.com/paperclipai/paperclip/actions/runs/37048838402"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
# Stock-harness live comparison — 2026-10-02
|
||||
|
||||
**TL;DR:** Two newly failing paired cases: classic Claude/OpenCode document delivery. Two newly passing cases: classic and native OpenCode ordered continuation. Seven unchanged failures and 13 unchanged passes. The extra ACP Claude storage symptom occurs beneath an existing credential-guard failure. All 48 results are available; no general performance equivalence is established.
|
||||
|
||||
The reduced instructions are **not yet qualified for merge**. Classic Claude and classic OpenCode pass the historical skill-output case but fail with the reduced instructions: they finish without saving a Paperclip task document. Native Codex and native Claude pass all three journeys in both variants. Single trials and ambiguous fixture storage wording limit causal attribution; the failed oracle remains unchanged.
|
||||
|
||||
The reductions and evaluation setup are in draft [PR #14948](https://github.com/paperclipai/paperclip/pull/14948). This report and its [safe evidence projection](2026-10-02-stock-harness-live-comparison.json) record the measured revisions rather than claiming the final documentation head was run through the full matrix.
|
||||
|
||||
## Revisions and matched inputs
|
||||
|
||||
- Candidate: `f02d8d0df327abb43b20c7e7beb86798239abbf5`, eight-word hire manual plus reduced shared startup/resume instructions.
|
||||
- Historical baseline: `12c5433c67dc2e62916b879349c7ba2b6e0431f0`, the same evaluation harness with only the prior default manual and shared prompt source restored from `e00d10d5d5594f6e2d1e8cf4e0ec81c074b0bd88`. Its manifest records ten identical behavioral/fixture source hashes.
|
||||
- Native Codex fix [#14920](https://github.com/paperclipai/paperclip/pull/14920) is held constant. This comparison does not measure its before/after performance.
|
||||
- Same eight profiles, three journeys, provider models, effort configuration, skills, tools, permissions, credentials, local runtime, and independent graders. Effort inherits provider defaults where the existing profile does not set it.
|
||||
- Each variant selects 24 cells and initially budgets 48 turns, with existing bounded retries and 1,000-cent company/agent hard stops. Recorded run-ledger counts can differ after early failures, retries and cleanup.
|
||||
|
||||
## Per-profile and case outcomes
|
||||
|
||||
These are recorded overall qualifications, including security and receipt failures. A credential-guard failure does not imply every behavioral matcher failed.
|
||||
|
||||
| Profile | Model | Journey | Historical | Reduced |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `legacy-codex` | `gpt-5.6-sol` | `assigned-skill-explicit-invocation` | Pass | Pass |
|
||||
| `legacy-codex` | `gpt-5.6-sol` | `ordered-comment-continuation` | Pass | Pass |
|
||||
| `legacy-codex` | `gpt-5.6-sol` | `continuity-restart` | Pass | Pass |
|
||||
| `legacy-claude` | `claude-sonnet-4-6` | `assigned-skill-explicit-invocation` | Pass | Fail: no Paperclip document |
|
||||
| `legacy-claude` | `claude-sonnet-4-6` | `ordered-comment-continuation` | Pass | Pass |
|
||||
| `legacy-claude` | `claude-sonnet-4-6` | `continuity-restart` | Fail: chat memory | Fail: chat memory |
|
||||
| `legacy-opencode` | `openrouter/deepseek/deepseek-v4-flash-0731` | `assigned-skill-explicit-invocation` | Pass | Fail: no Paperclip document |
|
||||
| `legacy-opencode` | `openrouter/deepseek/deepseek-v4-flash-0731` | `ordered-comment-continuation` | Fail: deadline/run-start timeout | Pass |
|
||||
| `legacy-opencode` | `openrouter/deepseek/deepseek-v4-flash-0731` | `continuity-restart` | Pass | Pass |
|
||||
| `legacy-acp-codex` | `gpt-5.6-sol` | `assigned-skill-explicit-invocation` | Fail: credential guard | Fail: credential guard |
|
||||
| `legacy-acp-codex` | `gpt-5.6-sol` | `ordered-comment-continuation` | Fail: credential guard, receipt incomplete | Fail: credential guard |
|
||||
| `legacy-acp-codex` | `gpt-5.6-sol` | `continuity-restart` | Fail: credential guard, receipt incomplete | Fail: credential guard, receipt incomplete |
|
||||
| `legacy-acp-claude` | `claude-sonnet-4-6` | `assigned-skill-explicit-invocation` | Fail: credential guard | Fail: credential guard, no Paperclip document |
|
||||
| `legacy-acp-claude` | `claude-sonnet-4-6` | `ordered-comment-continuation` | Fail: credential guard, receipt incomplete | Fail: credential guard |
|
||||
| `legacy-acp-claude` | `claude-sonnet-4-6` | `continuity-restart` | Fail: credential guard, receipt incomplete | Fail: credential guard |
|
||||
| `runner-codex` | `gpt-5.6-sol` | `assigned-skill-explicit-invocation` | Pass | Pass |
|
||||
| `runner-codex` | `gpt-5.6-sol` | `ordered-comment-continuation` | Pass | Pass |
|
||||
| `runner-codex` | `gpt-5.6-sol` | `continuity-restart` | Pass | Pass |
|
||||
| `runner-acpx-claude` | `claude-sonnet-5` | `assigned-skill-explicit-invocation` | Pass | Pass |
|
||||
| `runner-acpx-claude` | `claude-sonnet-5` | `ordered-comment-continuation` | Pass | Pass |
|
||||
| `runner-acpx-claude` | `claude-sonnet-5` | `continuity-restart` | Pass | Pass |
|
||||
| `runner-opencode` | `openrouter/deepseek/deepseek-v4-flash-0731` | `assigned-skill-explicit-invocation` | Pass | Pass |
|
||||
| `runner-opencode` | `openrouter/deepseek/deepseek-v4-flash-0731` | `ordered-comment-continuation` | Fail: deadline/run-start timeout | Pass |
|
||||
| `runner-opencode` | `openrouter/deepseek/deepseek-v4-flash-0731` | `continuity-restart` | Pass | Pass |
|
||||
|
||||
## Findings and next step
|
||||
|
||||
The classic Claude and classic OpenCode skill runs read the pinned skill marker and complete the task, but save zero Paperclip documents; both historical runs save exactly one. The legacy ACP Claude skill run has the same document difference under its credential-guard failure. The earlier reduced-instruction Claude pilot also failed this document oracle. The pinned skill asks for a “task document” without explicitly naming Paperclip storage, so this may reveal both reliance on the former manual and a fixture ambiguity. It remains a failed delivery result, not a regraded pass.
|
||||
|
||||
Keep the agreed tiny manual. Next, verify that legacy agents discover and follow the existing **Paperclip skill/API path for durable task documents and artifacts**. The skill already prohibits file-only handoff, lists issue-document routes and gives a plan-document example; audit that delivery before adding a concise generic-document recipe if needed. Run the same preserved case plus a clearly storage-specific case on both variants. Legacy agents do not receive native `paperclip_finish`/`paperclip_block` tools. Native tool descriptions are a separate item 2.3 change; guidance must follow the actual runtime capability. No production or skill instructions were changed to make these runs pass.
|
||||
|
||||
Classic Claude chat fails the memory assertion in both variants. Six legacy ACP cells per variant fail the persisted-credential guard: the scanner finds a scoped provider credential in persisted ACP session state. Raw session files and credential values are not published. These are existing qualification failures requiring their own investigation; they cannot be waived or interpreted as instruction-reduction quality evidence.
|
||||
|
||||
Historical native OpenCode ordered comments hit the case deadline without the required continuation outcome. The initial historical classic OpenCode ordered cell lost its AWS runner before result upload; its one unchanged-source recovery also hits the 12-minute deadline. Both OpenCode ordered cases pass in the candidate. Completed failures are never rerun to select a better result. Those candidate passes against historical timeouts are observed differences, not proof of a general improvement.
|
||||
|
||||
Historical public prompt retrieval is clipped in three legacy ACP cells (four invocations), including identity/connection suffix text. Those structural receipt failures cannot establish that the provider omitted instructions. The candidate ACP Codex chat run also lacks a complete per-run invocation receipt. Instruction presence/absence claims are bounded by the retained public receipts.
|
||||
|
||||
## Timing, usage and cost
|
||||
|
||||
| Variant | Retained cells | Pass / fail / missing | Cell duration sum | Provider duration sum | Recorded runs with tokens / with reported cost / total | Reported LLM subtotal |
|
||||
| --- | ---: | --- | ---: | ---: | --- | ---: |
|
||||
| baseline | 24 | 15 / 9 / 0 | 3250.195s | 2108.034s | 52 / 43 / 58 | $1.749425 |
|
||||
| candidate | 24 | 15 / 9 / 0 | 2804.913s | 1540.860s | 45 / 36 / 47 | $1.743151 |
|
||||
|
||||
For the 13 cells that pass both variants, median cell time is 85.550s historical versus 69.761s reduced; median provider time is 57.150s versus 43.862s. This excludes failures and is descriptive, not a reliable speedup estimate.
|
||||
|
||||
Provider billing is incomplete. Legacy Codex/ACP usage is unpriced in several runs; native Codex/Claude often report zero with billing type `unknown`. Reported subtotals do not prove zero native spending or invoice totals. Local runner costs and interrupted partial runs are not metered. Diagnostic/pilot costs and repeated interrupted work are outside the matched-cell subtotal and remain retained. No aggregate cost-saving claim is justified.
|
||||
|
||||
## Campaign and evidence history
|
||||
|
||||
| Campaign | Source | Outcome / evidence |
|
||||
| --- | --- | --- |
|
||||
| [Initial diagnostic Codex](https://github.com/paperclipai/paperclip/actions/runs/37034213743) | `a63437069` | Skill passes; predates mandatory admission. |
|
||||
| [Cold full attempt](https://github.com/paperclipai/paperclip/actions/runs/37037105491) | `36e987246` | Missing SDK build; cancelled before provider admission. |
|
||||
| [SDK pilot](https://github.com/paperclipai/paperclip/actions/runs/37039240025) | `4163dbfd0` | Missing daemon; stops before providers. |
|
||||
| [Daemon pilot](https://github.com/paperclipai/paperclip/actions/runs/37040493183) | `ac6ddefb5` | Missing fake Codex fixture; stops before providers. |
|
||||
| [Complete-build Claude pilot](https://github.com/paperclipai/paperclip/actions/runs/37041741124) | `f02d8d0df` | 537 prerequisites pass; skill document oracle fails; reported $0.175308. |
|
||||
| [Initial candidate matrix](https://github.com/paperclipai/paperclip/actions/runs/37042856368) | `f02d8d0df` | 4 pass, 7 fail, 13 cancelled when a same-target-branch pilot superseded it. Original attempts retained. |
|
||||
| [Candidate cancelled-cell recovery](https://github.com/paperclipai/paperclip/actions/runs/37045368302) | `f02d8d0df` | Only the 13 cancelled cells: 11 pass, 2 fail. Combined candidate has 24 results, 15 pass / 9 fail. |
|
||||
| [Historical matrix](https://github.com/paperclipai/paperclip/actions/runs/37042864888) | `12c5433c6` | 15 pass, 8 recorded failures, one AWS-runner shutdown without result upload. |
|
||||
| [Historical interrupted-cell recovery](https://github.com/paperclipai/paperclip/actions/runs/37048838402) | `12c5433c6` | Only legacy OpenCode ordered comments: 720.616s deadline failure; evidence and cleanup valid. Historical cohort now has 24 results, 15 pass / 9 fail. |
|
||||
| [Current packaging pilot](https://github.com/paperclipai/paperclip/actions/runs/37044967981) | `1eb5ba420` | Attempt 1 stopped before cell/provider execution; attempt 2 passes, evidence valid and cleanup pass. |
|
||||
|
||||
The same-target-branch workflow concurrency rule caused the candidate interruption; that orchestration mistake was acknowledged and corrected with a separate unchanged-source recovery branch. No completed model failure was retried. The historical missing cell is recovered once for a runner shutdown. Partial/failed attempts remain part of the history and unknown-spend accounting.
|
||||
|
||||
The measured matrix packages contain a prerequisite folder beside the exact campaign root. The trusted selector correctly rejects this layout, so their original published reports synthesize missing-result infrastructure failures. A separate local reconstruction nests that prerequisite folder under the campaign root and verifies byte identity for every copied file; result, scorer, usage, screenshot and prerequisite content are unchanged. The unchanged trusted selector then selects 11/24 original candidate, 13/13 recovery 23/24 original historical packages and 1/1 interrupted-cell recovery; recovered normalization validates every retained result’s evidence. Original missing packages stay missing in their original campaign; the separate recovery result has its own provenance. This is declared directory-layout recovery, not a canonical successful publication or a change to the oracle. The safe JSON records result/receipt/scoring hashes and recovery-manifest hashes.
|
||||
|
||||
The fix at `1eb5ba420` writes prerequisites beneath the exact campaign root. Its single selected Codex skill cell passes through the protected report and publication pipeline: [interactive pilot report](https://d1p6rlowie26tp.cloudfront.net/runner-e2e/campaigns/gha-37044967981-2/index.html), [GitHub evidence](https://github.com/paperclipai/paperclip/actions/runs/37044967981#artifacts). It has `evidenceValid=true`, cleanup pass, 27.149s provider / 49.523s cell, and a reported zero subtotal with unknown billing type. It does not replace the full matched cohort or qualify untested providers.
|
||||
|
||||
Original historical/recovery dashboards retain the publication failure: [historical](https://d1p6rlowie26tp.cloudfront.net/runner-e2e/campaigns/gha-37042864888-1/index.html), [candidate recovery](https://d1p6rlowie26tp.cloudfront.net/runner-e2e/campaigns/gha-37045368302-1/index.html). Inspect their access-controlled raw artifacts for measured cell evidence; do not read synthetic missing-result rows as task performance.
|
||||
|
||||
## Verification and remaining qualification
|
||||
|
||||
At `1eb5ba420`, all 557 credential-free prerequisites pass (556 TypeScript plus one Rust), along with 895 E2E support tests, E2E typecheck and 24-cell discovery. The cold builder prepares SDK/shared outputs and both daemon/fake protocol binaries, records hashes, and fails closed before credentials. The 313 filtered native tests are not counted as passing coverage. This head has 52 passing CI gates and two skipped gates; Greptile reviewed that exact head at 5/5 with zero open threads. Later documentation heads require fresh checks/review; measured sources remain explicit above.
|
||||
|
||||
Repository typecheck and build pass. The complete local Vitest run has 14,870 passed, 83 skipped and three unrelated timing failures; the three affected files pass unchanged narrow reruns (25 auth, 16 reviewed-chat binding, four webhook tests). Original failures are retained rather than calling the first full run green.
|
||||
|
||||
PR #14948 stays draft while document delivery is unresolved. Unrepresented harnesses, legacy ACP credential safety, public-receipt clipping, the independent ACP base-replacement issue, existing Claude chat memory behavior and saved-session migration are not qualified away by these results. This small matrix measures skill/context/chat behavior, not broad coding quality or statistical equivalence.
|
||||
|
||||
## Retained delivery diagnosis and approved repair
|
||||
|
||||
Both failed classic agents loaded the operational Paperclip skill. This is not an observed skill-discovery failure. In the reduced Claude run, the agent wrote a workspace Markdown file before loading Paperclip and then completed the issue without a document API write. The reduced OpenCode run loaded Paperclip, accessed the assigned skill through the public skills API, and wrote a workspace task-document file; it made no document, attachment, or work-product delivery write. Both corresponding historical runs saved a Paperclip issue document.
|
||||
|
||||
Claude received the full operational skill, including its existing no-local-only delivery guidance. OpenCode's skill result was explicitly truncated in both variants: the early artifact rule survived, but the later generic document endpoint and plan-only write example were omitted. That truncation is existing behavior, not a newly measured regression. Removing always-on delivery reminders may interact with it, but the combined manual/shared reduction and ambiguous assigned-skill wording prevent a causal attribution to one layer.
|
||||
|
||||
Dotta approved a small early API-runtime skill recipe and a generic issue-document reference. The tiny manual remains eight words. The recipe checks the successful write's returned saved revision/content and links the returned key; it does not require another GET after a clear valid receipt. It preserves explicit destinations, downloadable-file delivery, and native document-tool boundaries.
|
||||
|
||||
The focused repair comparison will hold the tiny manual/shared prompts fixed and vary only `skills/paperclip/SKILL.md` plus the new `references/issue-documents.md`. Classic Claude and OpenCode each run the preserved original case and an added explicitly Paperclip-storage case: four cells per variant, eight expected provider turns in total. This measures the skill repair separately from the original reduction. The explicit case independently checks public saved body/revision and an agent comment linking the exact same-app document; local-only, missing-revision, wrong-document, local-path, and other-origin outcomes cannot pass. Current state: fixture preparation and source freeze, no repair provider run yet.
|
||||
@@ -1,7 +1,17 @@
|
||||
# Stock harness, with Paperclip: working checklist
|
||||
|
||||
Created: 2026-10-02. Status: item 1 implemented for native Codex app-server;
|
||||
PR validation in progress. Other implementation items remain open.
|
||||
Created: 2026-10-02. Status: item 1 merged for native Codex app-server in
|
||||
[PR #14920](https://github.com/paperclipai/paperclip/pull/14920). Item 2's default
|
||||
hire manual is reduced to identity only, and common legacy startup/resume
|
||||
instructions are in [PR #14948](https://github.com/paperclipai/paperclip/pull/14948).
|
||||
GitHub live qualification retains earlier document-delivery failures; the latest
|
||||
focused OpenCode comparison passes both cases in both variants but still exposes
|
||||
deficient original-case handoff links. PR #14948 remains draft. The original
|
||||
[live report](2026-10-02-stock-harness-live-comparison.md), completed
|
||||
[skill repair](2026-10-02-legacy-document-skill-repair.md) and latest
|
||||
[stock-selection/link comparison](2026-10-02-opencode-skill-routing-link-qualification.md)
|
||||
retain their separate sources and limitations. Additional carriers and fixed
|
||||
native instructions remain open; hiring templates are being reduced separately.
|
||||
|
||||
Goal: keep the agent's stock harness behavior and add only what it needs to work
|
||||
with Paperclip. Apply this across legacy adapters, the new Runner, and their
|
||||
@@ -12,7 +22,11 @@ record the proposed behavior and Dotta's direction, make a bounded change, and
|
||||
verify the affected paths before moving on. New findings get stable IDs in the
|
||||
ledger below so they do not disappear into conversation history.
|
||||
|
||||
**Current item: 1 — validate and review the native Codex instruction fix.**
|
||||
For every change, record its executable test/eval coverage before continuing.
|
||||
Distinguish coverage setup, deterministic results, and measured live results;
|
||||
configured cells alone do not qualify behavior.
|
||||
|
||||
**Current item: 2 — reduce the default operating manual and shared prompt layers.**
|
||||
|
||||
## Agreed direction and boundaries
|
||||
|
||||
@@ -43,7 +57,7 @@ ledger below so they do not disappear into conversation history.
|
||||
- [x] Remove default replacement of stock base instructions across those paths.
|
||||
- [x] Verify the actual app-server request and retained session instructions,
|
||||
including resume; checking only a prompt builder is insufficient.
|
||||
- [ ] Verify Paperclip task context, tools, auth, assigned skills, and completion
|
||||
- [x] Verify Paperclip task context, tools, auth, assigned skills, and completion
|
||||
still work. Record applicable regressions and eval results.
|
||||
|
||||
Starting points: [Codex backend](../../packages/paperclip-runner/src/backends/codex-native-backend.ts),
|
||||
@@ -61,14 +75,20 @@ and need a provider session reset. Do not reset active sessions automatically.
|
||||
The separate Codex-through-ACP dependency patch remains a coverage follow-up
|
||||
under item 6; this change covers the native app-server path.
|
||||
|
||||
Verification: 412 focused TypeScript/Rust tests, repository typecheck/build,
|
||||
55 passing PR checks, and Greptile 5/5. The original full local test command
|
||||
was stopped after setup failures; affected suites passed on rerun. No paid
|
||||
live campaign was run, and no task-quality improvement is claimed.
|
||||
|
||||
## 2. Reduce the default operating manual and shared prompt layers
|
||||
|
||||
- [ ] Inventory what an agent actually receives: hire instructions, shared
|
||||
prompt template, wake context, runtime prompt, bootstrap, and loaded skills.
|
||||
Separate always-present text from content loaded on demand.
|
||||
- [ ] Agree on the tiny common contract and the coordination details that each
|
||||
runtime still requires. Legacy API coordination and native semantic tools
|
||||
need appropriate instructions for their respective interfaces.
|
||||
- [x] Agree on the tiny default: identity only, with no skill or native-tool
|
||||
pointers. The harness supplies coordination instructions.
|
||||
- [x] Reduce the generic default hire `AGENTS.md` to the agreed sentence and
|
||||
update the existing creation test to expect the minimal bundle.
|
||||
- [ ] Remove repeated workflow rules and stock coding/style/autonomy guidance.
|
||||
- [ ] Move detailed planning, hiring, artifacts, and exceptional procedures to
|
||||
discoverable references or tools where feasible.
|
||||
@@ -80,7 +100,130 @@ Starting points: [default hire instructions](../../server/src/onboarding-assets/
|
||||
[native runtime contract](../../packages/paperclip-runner/src/contracts/runtime-context.ts),
|
||||
[Paperclip operational skill](../../skills/paperclip/SKILL.md).
|
||||
|
||||
Decision: exact retained paragraph and on-demand boundaries pending.
|
||||
Decision: Dotta confirmed the harness already handles skill and tool delivery;
|
||||
the default hire manual is now only "You are an agent in a Paperclip company."
|
||||
This replaces 602 words with eight. The shared loader supplies this default
|
||||
across instruction-bundle-capable adapters for non-CEO hires without explicit
|
||||
instructions. Existing saved bundles retain their content; CEO, first-agent,
|
||||
and role/team templates remain separate work under item 3. The common
|
||||
prompt/wake reduction is complete locally below; additional carriers, native
|
||||
instructions, and skill/reference corrections remain open.
|
||||
|
||||
Verification: the existing agent-skills route suite passed all 54 tests,
|
||||
including default creation, custom bundles, and CEO/first-agent paths. The
|
||||
onboarding asset suite passed all 10 tests. This initially had only deterministic
|
||||
coverage. Dotta subsequently requested behavioral eval coverage for every change;
|
||||
the dedicated production-default-hire suite below closes the custom QA manual
|
||||
gap. Full repository typecheck/build/test were not rerun for this narrow asset
|
||||
and existing-test update.
|
||||
|
||||
### Shared prompt follow-ups
|
||||
|
||||
- [x] **2.1 Reduce common legacy startup/resume instructions.** Keep
|
||||
identity and connection guidance in the shared task/chat defaults; remove the
|
||||
generic resumed-wake execution contract. Preserve current task/event data,
|
||||
specialized wake contracts, custom templates, skills, and auth.
|
||||
- [ ] **2.2 Review additional legacy carriers.** Reduce Hermes local/gateway
|
||||
wrappers, review Pi system delivery, and check OpenClaw fresh-wake framing.
|
||||
Preserve transport facts and user configuration. Dotta deferred this item on
|
||||
2026-10-02 for a later revisit; it remains open.
|
||||
- [ ] **2.3 Reduce native Runner instructions and constraints.** Improve
|
||||
discoverable tool documentation first, then shorten fixed guidance and
|
||||
consolidate completion rules. Verify prompt revisions, digests, and session
|
||||
compatibility across native Codex, ACPX, and OpenCode. Dotta approved the
|
||||
native tool-documentation slice on 2026-10-02 in a separate worktree. Native
|
||||
`paperclip_finish`/`paperclip_block` guidance must not leak into legacy
|
||||
completion paths, which use the operational skill and API.
|
||||
|
||||
### Follow-up 2.1 implementation and verification
|
||||
|
||||
The common task and conversation defaults now share the same identity sentence
|
||||
and unchanged connection guidance: 113 words each, down from 661 and 205.
|
||||
Connection guidance accounts for 108 of those words. The ordinary resumed-wake
|
||||
execution contract is removed (172 words), including the old opt-in used by
|
||||
OpenClaw on fresh turns. `includeExecutionContract` remains accepted as a
|
||||
deprecated no-op for adapter/plugin source compatibility.
|
||||
|
||||
Current task facts, ordered comments, work modes, approval/review state,
|
||||
checkout, holds/blockers, recovery/watchdog roles, and external-chat contracts
|
||||
remain in their existing owners. Skill delivery, runtime authentication,
|
||||
custom `promptTemplate`, and `bootstrapPromptTemplate` mechanics are unchanged.
|
||||
Hermes's own wrappers and Pi's system carrier remain follow-up 2.2; native
|
||||
fixed instructions and full-turn constraints remain follow-up 2.3.
|
||||
|
||||
The later read-only 2.2 audit found another OpenClaw-owned HTTP identity,
|
||||
checkout/status, delegation, plan-approval and task-discovery wrapper in
|
||||
`buildWakeText`. Its short conversation branch is selected when optional task
|
||||
Markdown is present, including on ordinary task dispatch. Existing dispatch
|
||||
coverage does not assert the absence of conversation waiting language. This is
|
||||
a framing concern to verify, not a measured failure. Pi already uses additive
|
||||
`--append-system-prompt` delivery and suppresses the duplicate user-prompt copy;
|
||||
its next step is a delivery audit with minimal changes. These findings are
|
||||
deferred with 2.2 and are not implemented in PR #14948.
|
||||
|
||||
The new defaults apply when prompts are assembled after deployment. Existing
|
||||
provider sessions can retain earlier startup instructions in their history
|
||||
until reset; no active session or saved custom template is rewritten here.
|
||||
|
||||
Verification: 563 distinct focused tests passed across 18 suites:
|
||||
|
||||
- Shared prompt selection/rendering, operational-skill selection, and retained
|
||||
specialized wake contracts (130 tests).
|
||||
- ACPX, Pi, OpenCode, Cursor Cloud, and Codex local execution, including
|
||||
task/chat fresh/resumed/reset/fallback delivery, custom prompts, skill
|
||||
mounting, and authentication (259 tests).
|
||||
- Hermes local prompt rendering and gateway execution (41 tests).
|
||||
- Claude, Gemini, Cursor local, Grok, Kimi, OpenClaw, and Claude/Codex ACP
|
||||
fallback execution (113 tests).
|
||||
- Native execution input, preserving shared wake compatibility (20 tests).
|
||||
|
||||
`@paperclipai/adapter-utils` typecheck and build passed; `git diff --check`
|
||||
passed. Full repository typecheck/build/test and live behavioral evals were not
|
||||
run for this local bounded change. These checks prove prompt and runtime
|
||||
mechanics, not an improvement in coding-task quality.
|
||||
|
||||
### Remaining shared layers inspected (2026-10-02)
|
||||
|
||||
The table records the initial inspection before follow-up 2.1. Word counts
|
||||
cover fixed source text, not complete assembled prompts or token counts. They
|
||||
exclude task data, assigned agent instructions, and skills. This inspection
|
||||
establishes instruction mechanics, not a performance result.
|
||||
|
||||
| Layer | Delivery at initial inspection | Disposition |
|
||||
| --- | --- | --- |
|
||||
| Shared legacy task/chat templates | `DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE` was 661 words and the conversation template was 205, including connection guidance. Used by Claude, Codex, Cursor local/cloud, Gemini, Grok, Kimi, OpenCode, Pi, and Hermes local. | Completed in 2.1: both defaults now 113 words. Repeated procedures removed, connection guidance retained, no replacement skill pointer. |
|
||||
| Generic resumed wake contract | `renderPaperclipWakePrompt` added a 172-word execution contract on ordinary resumed turns. OpenClaw requested it on fresh turns too because it has no shared template. | Completed in 2.1: generic contract removed, including legacy opt-ins. Task/event data and conditional runtime contracts retained. |
|
||||
| Additional legacy carriers | Hermes local adds a 171-word identity/API/curl wrapper before the shared task template; Hermes gateway supplies its own four-rule execution contract. Pi carries shared defaults in its system extension and suppresses the wake copy when that extension owns policy. | Separate follow-up 2.2; changing the shared constants alone does not remove every wrapper. Preserve transport facts and custom configuration. |
|
||||
| Native fixed prompt | The 262-word `paperclip-execution.v5` prompt carries hiring, delegation/dependencies, connection setup, and finalization guidance across native Codex, ACPX, and OpenCode backends. | Keep one short native completion/dependency contract. Relocate uncommon procedures to the relevant tool documentation, improving that documentation where needed before removing instructions. Any change needs prompt revision/digest/session-compatibility verification. |
|
||||
| Native full-turn constraints | `nativeTaskConstraints` adds assigned-skill limits, current agent-file paths, document/file delivery, answered-question handling, and accepted completion/final-response sequencing. Codex/OpenCode add another completion reminder. Prepared constraints reach the model envelope through `native-session-runtime`; compact continuations avoid replaying prior task constraints. | Consolidate duplicate completion instructions. Keep current paths, answer scope, and the required result protocol. Move detailed file/document procedure to discoverable tool descriptions where adequately covered. |
|
||||
| Task/event-specific framing | Server task Markdown and wake rendering supply work mode, ordered comments, approved revisions, checkout/holds/blockers, recovery/review/watchdog roles, external-chat bookkeeping/delivery, and conversation handoff. | Retain the facts and mode boundaries in the first reduction. Use one authoritative owner for shared mode directives; review specialized wording separately. |
|
||||
|
||||
Custom `promptTemplate` and fresh-only `bootstrapPromptTemplate` configuration
|
||||
are distinct from shipped defaults and should not be silently rewritten.
|
||||
The built-in process adapter invokes a configured command; HTTP passes context
|
||||
as JSON. They do not use the shared text template. External adapter plugins and
|
||||
hosted configuration variants still require the broader item 6 audit.
|
||||
|
||||
An accepted-plan discrepancy needs resolution: task Markdown permits cohesive
|
||||
implementation on the current issue, while the wake renderer's planning-mode
|
||||
accepted-confirmation branch requires children and prohibits implementation on
|
||||
the source issue. Existing tests assert both versions. Production reachability
|
||||
after the server changes work mode has not yet been established; do not claim
|
||||
this proves a live plan-continuation failure.
|
||||
|
||||
Dotta selected follow-up 2.1 first: reduce the common legacy startup/resume
|
||||
copies, preserving conditional context. Additional carriers and native fixed
|
||||
instructions remain distinct follow-ups 2.2 and 2.3. No shared runtime text was
|
||||
changed by the initial inspection.
|
||||
|
||||
Coverage identified during inspection includes shared prompt selection/rendering,
|
||||
adapter execution tests, native backend/runtime context tests, and server
|
||||
task-context/native-input tests. Follow-up 2.1 extended the shared and adapter
|
||||
checks; its results are above. Follow-ups 2.2 and 2.3 need their corresponding
|
||||
carrier/protocol assertions. Product E2E context-integrity, completion-updates,
|
||||
and direct blocker guidance are candidate lifecycle checks; their current scope
|
||||
does not establish coding-task quality. The initial inspection itself ran no
|
||||
tests or live evals.
|
||||
|
||||
## 3. Review every hiring and role template
|
||||
|
||||
@@ -107,7 +250,61 @@ Starting points: [onboarding assets](../../server/src/onboarding-assets/),
|
||||
[hiring skill and references](../../skills/paperclip-create-agent/),
|
||||
[teams catalog](../../packages/teams-catalog/catalog/).
|
||||
|
||||
Decision: per-template disposition and migration policy pending.
|
||||
Dotta approved and merged [PR #14985](https://github.com/paperclipai/paperclip/pull/14985)
|
||||
at `862a5758ba0e88a33232c1f1fa645e85c38a3113`, after corrected source
|
||||
`57dcee147ed0b2d2e3cc657cd9e50fb16bf9ec25` passed full CI and fresh Greptile 5/5.
|
||||
The hiring skill/references, CEO/CoS assets and team catalog are now on master.
|
||||
Their live qualification remains separate from unit/CI verification.
|
||||
[Prompt diffs and preserved scope](https://github.com/paperclipai/paperclip/blob/57dcee147ed0b2d2e3cc657cd9e50fb16bf9ec25/doc/plans/2026-10-02-hiring-template-prompt-diff.md).
|
||||
|
||||
The read-only audit found four onboarding CEO files (1,897 words), a 164-word
|
||||
CoS manual, four role examples (coder 652, QA 619, UX 1,325, security 1,724 words),
|
||||
and eight team-catalog manuals. Hiring step 6, the 60–150-line role guide and
|
||||
review checklist would regenerate the removed operating policies.
|
||||
|
||||
- [x] **3.1** Reduce role drafting rules, coder/adapted hire examples and matching
|
||||
catalog coder, preserving metadata, auth, permissions and skill selections.
|
||||
- [x] **3.2** Reduce the CEO bundle/copy, including forced delegation, hiring,
|
||||
memory and repeated API recipes.
|
||||
- [x] **3.3** Review remaining roles/catalog and CoS copy.
|
||||
- [ ] **3.4** Review specialized built-in/plugin contracts separately; retain
|
||||
product-required behavior rather than assuming every rule is redundant.
|
||||
|
||||
Custom and existing bundles and governance remain deliberate rollout boundaries.
|
||||
Specialized Summarizer, Reflection Coach and Wiki Maintainer prompts remain
|
||||
unchanged for separate product-contract review; optional Content Lead was already
|
||||
tiny. The generic non-CEO fallback belongs to PR #14948. PR #14985 does not reduce
|
||||
every shipped specialized agent.
|
||||
|
||||
Its explicit `hiring-templates` suite selects native local Codex and ACPX Claude
|
||||
`hire-coder-template-reuse` cells: a real default CEO, explicitly requested
|
||||
production hiring skill/coder reference, independently scored saved JSON
|
||||
artifacts and session reuse. Outcome and source/read coverage are separate;
|
||||
missing or redacted receipts do not prove no regression. The existing first-task
|
||||
suite covers actual wizard CoS snapshot selection. The matched union holds the
|
||||
reduced manual/shared/operational skill and native completion guidance constant,
|
||||
restoring only 21 historical hiring production/derived sources in baseline.
|
||||
Frozen candidate `9f5404ad3aacbe76777952759414d34fd381e674` and baseline
|
||||
`296a4df85e8bcc97a160fc78c291b17adb828196` passed 705 exact-source credential-free
|
||||
prerequisites each (704 TypeScript + 1 Rust; zero providers), with 8,242 identical
|
||||
other tracked files and matching fixture/model/auth/permissions configuration.
|
||||
Only four candidate-specific single-file CEO selection assertions are filtered
|
||||
symmetrically; each variant's bundle/source hashes and canonical catalog are
|
||||
independently admitted. Two cells per variant, five expected turns each:
|
||||
four cells / 20 turns, 15 minutes each, no automatic retries or broader selection.
|
||||
The [candidate](https://github.com/paperclipai/paperclip/actions/runs/37075466208)
|
||||
and [baseline](https://github.com/paperclipai/paperclip/actions/runs/37075469463)
|
||||
protected campaigns are dispatched. No graded live pairs yet; pending evidence
|
||||
cannot establish non-regression.
|
||||
[Inspect the partial report](https://github.com/paperclipai/paperclip/blob/e3720f369df070036526dd295a7a68e49488c51a/doc/plans/2026-10-02-hiring-template-live-comparison.md).
|
||||
Report-only updates preserve the measured refs and merged hiring PR.
|
||||
|
||||
The default-manual PR is replayed on the merged hiring base, preserving custom
|
||||
CEO bundle checks, the minimal generic boundary and both eval suites. Its prior
|
||||
browser failure remains retained: a deterministic process fixture replayed its
|
||||
last plan command on an asynchronous `chat_task_completed` wake and wrote a
|
||||
second, text-identical source plan revision. It was not paid/provider execution;
|
||||
fresh rebased CI remains required before readiness.
|
||||
|
||||
## 4. Fix repository context while retaining Paperclip configuration
|
||||
|
||||
@@ -154,7 +351,7 @@ does not apply.
|
||||
| Legacy Codex / Claude | Local adapters, managed auth and isolated configuration | Initial inspection only |
|
||||
| Other legacy local adapters | ACPX, OpenCode, Pi, Cursor, Gemini, Grok, Kimi, Hermes | Pending |
|
||||
| Other adapter transports | Cursor Cloud, Hermes/OpenClaw gateways, process, HTTP, external adapter plugins | Pending |
|
||||
| Runner Codex | App-server driver, runnerd bridge, Rust provider, direct/fallback paths | Additive instruction fix implemented; PR validation pending |
|
||||
| Runner Codex | App-server driver, runnerd bridge, Rust provider, direct/fallback paths | Additive instruction fix merged in PR #14920 |
|
||||
| Runner ACPX | Enabled profiles, especially Claude/Grok; declared or pending profiles tracked separately | Claude initial inspection; remaining audit pending |
|
||||
| Runner OpenCode | Native provider and configuration paths | Pending |
|
||||
| Hosted/remote providers | Claude Managed and AWS AgentCore; identify their own baseline rather than assuming CLI semantics | Pending |
|
||||
@@ -172,15 +369,18 @@ does not apply.
|
||||
|
||||
## Verification and evals — apply to each item
|
||||
|
||||
- [ ] Map existing coverage before adding cases; use [doc/evals.md](../evals.md).
|
||||
- [x] Map existing coverage for all implemented changes before adding cases;
|
||||
see the SH-1–SH-3 map below and [doc/evals.md](../evals.md).
|
||||
Keep Runner protocol evals and Product E2E evals distinct.
|
||||
- [ ] Start with narrow deterministic checks for instruction layering, effective
|
||||
- [x] Set up narrow deterministic checks for the implemented instruction layering,
|
||||
hire, and shared-prompt changes. Future changes still need their own map for effective
|
||||
configuration, skill/auth delivery, and session behavior where appropriate.
|
||||
- [ ] Use Runner evals for provider/session/tool protocol changes; use Product
|
||||
E2E for real hiring, repository context, task lifecycle, and artifact delivery.
|
||||
- [ ] Review existing context-integrity, hiring, completion-updates, and blocker
|
||||
suites for reusable coverage; record gaps rather than claiming coverage from
|
||||
a similarly named case.
|
||||
- [x] Reuse existing protocol tests for native Codex request layering and add Product
|
||||
E2E for production-default hiring, skill delivery, task lifecycle, and chat
|
||||
continuity. Repository context and additional artifact cases remain future work.
|
||||
- [x] Review existing suites for reusable coverage. Their custom QA manuals did
|
||||
not exercise the tiny default hire; the new suite deliberately omits those
|
||||
bundles and reuses independently graded skill, continuation, and chat journeys.
|
||||
- [ ] Compare task quality as well as Paperclip protocol compliance when claiming
|
||||
that fewer instructions improve agent performance. Keep model, effort,
|
||||
permissions, tools, and fixture comparable.
|
||||
@@ -189,19 +389,71 @@ does not apply.
|
||||
- [ ] Run the relevant checks for each change and the repository's required full
|
||||
verification before a PR-ready handoff. Record unrun checks and their reasons.
|
||||
|
||||
### Coverage for changes implemented so far
|
||||
|
||||
The [stock-harness runbook](../../tests/runner-e2e/STOCK-HARNESS.md) defines a
|
||||
credential-free prerequisite plus 24 explicit local Product E2E cells across
|
||||
eight legacy/native profiles. Each profile runs assigned-skill invocation,
|
||||
ordered comment continuation, and chat continuity across restart. Public receipts
|
||||
verify the identity-only managed bundle before provider execution; final receipts
|
||||
check actual legacy prompts and both budget hard stops. The existing lifecycle
|
||||
oracles still own task/chat success. Models, auth, skills, permissions, and
|
||||
secret-reference plumbing are inherited from existing profiles.
|
||||
|
||||
| Coverage ID | Implemented change | Executable coverage | Live status |
|
||||
| --- | --- | --- | --- |
|
||||
| SH-1 | Native Codex additive developer instructions, including start/resume/recovery | TypeScript driver, runnerd transport, Runner Lab/live-session, and Rust provider tests in `pnpm test:e2e:runner:stock-harness`; native Codex cells exercise real hires. | Native Codex passes all three matched journeys in both variants; #14920 held constant, so task success is not before/after vendor-base proof. |
|
||||
| SH-2 | Eight-word default hire `AGENTS.md` | Public creation/onboarding tests; exact independent public bundle oracle before and after provider execution for every stock-harness cell. | Candidate public tiny-bundle and budget receipts pass in all 24 retained cells. Behavioral delivery is not fully qualified; see F14–F17. |
|
||||
| SH-3 | Reduced shared task/chat defaults and removed generic resume contract | Shared renderer and ACPX/Codex/OpenCode/Pi/Hermes/Cursor Cloud regressions; actual legacy invocation prompts checked in the new suite. | Actual legacy prompts measured. Document-delivery regressions and clipped/missing receipts retained; additional carriers remain deferred item 2.2. |
|
||||
|
||||
The suite is explicit-only and excluded from `--all`; it does not add paid work
|
||||
to ordinary campaigns. Negative calibration covers manual regrowth, missing or
|
||||
malformed prompt receipts, old startup/resume procedures, missing connection
|
||||
guidance, wrong budgets, and skipped prerequisite assertions. Source revisions,
|
||||
attempts, cost, partial failures, cleanup, and sanitized evidence use the existing
|
||||
report pipeline. No paid providers were launched during the initial setup.
|
||||
Subsequent GitHub measurements are recorded below; no broad coding-quality
|
||||
improvement or full live qualification is claimed. Unrepresented harnesses remain
|
||||
unqualified; Codex-through-ACP vendor-base preservation is still F7.
|
||||
|
||||
Local setup verification: 478 prerequisite tests passed (477 TypeScript plus one
|
||||
Rust), 860 Product E2E support tests passed, E2E typecheck passed, and all 24 cells
|
||||
were discovered. The 313 unrelated native tests filtered by the gate are not
|
||||
passing coverage. Final prerequisite evidence is retained at
|
||||
`tests/runner-e2e/results/stock-harness-preflight-2026-10-02T16-15-39.064Z/preflight.json`;
|
||||
earlier interrupted, missing-Hermes-discovery, and setup/test-timeout attempts are
|
||||
retained as failures. Hermes is checked through its package config because the
|
||||
root Vitest project list omits it. Later exact-head prerequisites expanded to
|
||||
557 passed checks (556 TypeScript and one Rust), and E2E support to 895 tests.
|
||||
Repository typecheck/build passed. The complete local Vitest run retained three
|
||||
unrelated timing failures; all three files passed unchanged reruns. At
|
||||
`1eb5ba420`, 52 CI gates pass and two skip; Greptile is 5/5 with zero open
|
||||
threads. Later documentation heads require fresh checks. See the live report
|
||||
for exact source hashes, campaigns, failures and cost limitations.
|
||||
|
||||
## Findings ledger
|
||||
|
||||
Confirmed mechanics below do not by themselves establish an effect on task quality.
|
||||
|
||||
| ID | Finding | Work item / disposition |
|
||||
| --- | --- | --- |
|
||||
| F1 | Native Codex sent Paperclip text as `baseInstructions`. Probes on codex-cli 0.153.4 showed replacement; additive `developerInstructions` retained the stock base on start and cold resume. | 1; app-server fix implemented, PR validation pending |
|
||||
| F1 | Native Codex sent Paperclip text as `baseInstructions`. Probes on codex-cli 0.153.4 showed replacement; additive `developerInstructions` retained the stock base on start and cold resume. | 1; app-server fix merged in PR #14920 |
|
||||
| F2 | Default hires and role templates prescribe substantial operating procedures; common prompt and wake layers add further coordination text. | 2–3; mechanics confirmed, performance effect unmeasured |
|
||||
| F3 | Hiring references require legacy Paperclip skill/comment procedures, while native Runner intentionally omits that operational skill and uses semantic tools. | 2–3, 5; reconcile runtime contracts |
|
||||
| F4 | Local Claude appends instructions; Runner Claude preserves the Claude Code preset. Runner isolation excludes project/local settings, which can also exclude repository instruction discovery. | 4; selective context fix to design |
|
||||
| F5 | Some Codex capability settings differ between the direct driver and daemon path; an intermediate configuration does not prove the final provider behavior. | 6; effective-path audit pending |
|
||||
| F6 | Omitting or nulling `baseInstructions` on an old Codex thread's resume preserves its saved replacement; an empty string produces an empty base. | 1; document the required provider session reset; no automatic migration in this PR |
|
||||
| F7 | The isolated Codex-through-ACP dependency patch also sets `baseInstructions` on start/resume. It is a separate path from the native app-server backend. | 6; follow-up patch/profile audit pending |
|
||||
| F8 | The operational skill says target-bound confirmations default `supersedeOnUserComment` to true; the default manual and server normalizer say false. The server uses false. | 2; correct stale skill/reference guidance in a follow-up |
|
||||
| F9 | The default manual required a comment on every task, while the operational skill's verified external-chat shortcut delegates comments and lifecycle bookkeeping to the harness. | 2; unconditional manual rule removed; shared layers still need review |
|
||||
| F10 | Removing the default manual left the 661-word legacy task template and its 172-word resumed-wake execution contract. Hermes and Pi have additional policy carriers. | 2.1 complete locally: task/chat defaults 113 words, no generic resume contract; 2.2 wrappers pending |
|
||||
| F11 | Task Markdown and the wake renderer prescribe different accepted-plan behavior for planning-mode accepted-confirmation payloads; tests currently expect both. | 2; unify the directive owner; production reachability still to trace |
|
||||
| F12 | Native fixed instructions and full-turn constraints repeat completion and uncommon procedures, while reserved finish/block tool descriptions are only one sentence each. | 2; improve tool documentation before removing needed native protocol guidance |
|
||||
| F13 | Existing context-integrity/chat fixtures injected a QA manual, so their green results did not qualify the production tiny hire default. | Dedicated stock-harness suite measured real default hires; retained failures prevent blanket qualification. |
|
||||
| F14 | Historical classic Claude/OpenCode skill runs save one Paperclip document; reduced runs save none while completing the task. Legacy ACP Claude has the same behavior under a separate guard failure. Pinned skill storage wording is ambiguous. | Measured delivery failures; keep original oracle. Improve legacy skill/API delivery guidance separately, then compare both variants with preserved and storage-specific cases. |
|
||||
| F15 | Classic Claude restart chat fails its memory assertion in both variants. | Existing behavior, not attributable to this reduction from these trials. |
|
||||
| F16 | All six legacy ACP cells per variant fail the persisted-provider-credential guard. Three historical ACP cells also have clipped public prompt retrieval, and candidate ACP Codex chat lacks a complete invocation receipt. | Security/receipt qualification follow-ups; do not waive guard, expose raw sessions, or infer missing provider instructions from clipped receipts. |
|
||||
| F17 | Prerequisites beside the campaign root violate trusted artifact selection. A same-target pilot superseded 13 candidate cells; one historical AWS runner shut down without upload. | Packaging fixed at `1eb5ba420` and pilot passes. Directory-layout-only copies preserve every byte; missing cells alone recovered at unchanged source, completed failures never rerun. |
|
||||
|
||||
Append new findings with evidence, affected paths, and the numbered item that
|
||||
will address them. Record intentional behavior explicitly rather than as a bug.
|
||||
@@ -214,6 +466,26 @@ will address them. Record intentional behavior explicitly rather than as a bug.
|
||||
| 2026-10-02 | Paperclip-owned MCP isolation and configuration changes/session resets are acceptable. | Preserve Paperclip auth and assigned skills while fixing repository context. |
|
||||
| 2026-10-02 | Dotta requested implementation and a PR for item 1. Native app-server paths now use additive developer instructions. | 139 targeted TypeScript tests and 91 Rust provider tests passed; repository typecheck/build passed. Remaining test/review results to record. |
|
||||
| 2026-10-02 | Verified actual Codex instruction layering using a localhost Responses stub, without paid inference. | codex-cli 0.153.4 sent identical 14,732-character stock base instructions on start and cold resume, with the Paperclip marker retained in developer input. This is protocol evidence, not a task-quality eval. |
|
||||
| 2026-10-02 | Dotta requested and confirmed the merge of item 1. | PR #14920 merged at `408f70e69f9c5e49cb4377f4886ac2001bfa67a2`, with all 55 checks passing and Greptile 5/5. |
|
||||
| 2026-10-02 | Dotta directed an identity-only default manual with no skill or runtime pointers; the harness already handles coordination. | Reduced the default to eight words. Existing hire and onboarding suites passed all 64 tests. Shared prompts and role templates remain pending. |
|
||||
| 2026-10-02 | Dotta requested three explicit shared-prompt follow-ups and selected common legacy startup/resume reduction first. | Follow-ups 2.1–2.3 recorded. 2.1 complete locally: both defaults 113 words; generic resume contract removed. 563 focused tests and shared utility typecheck/build passed. Extra carriers and native instruction reduction remain pending. |
|
||||
| 2026-10-02 | Dotta requested executable eval coverage for everything implemented so far and all subsequent changes before continuing. | Added SH-1–SH-3 coverage map, credential-free prerequisite, and 24 production-default-hire cells. 478 prerequisite and 860 support tests passed; E2E typecheck/discovery passed. Live provider results remain `not_run`; item 2.2/2.3 unchanged. |
|
||||
| 2026-10-02 | Dotta requested PRs and GitHub-runner before/after qualification while discussing subsequent work separately. | Draft [PR #14948](https://github.com/paperclipai/paperclip/pull/14948); native Codex diagnostic [passed](https://github.com/paperclipai/paperclip/actions/runs/37034213743), 34.745 s provider / 56.660 s cell. Comparison restores only the prior manual/shared prompts and holds #14920 constant; no general coding-quality claim. |
|
||||
| 2026-10-02 | Full candidate cold setup failed before provider admission; stopped and retained the attempt. | [Run 37037105491](https://github.com/paperclipai/paperclip/actions/runs/37037105491), target `36e987246`: prerequisite imports lacked the plugin SDK build. Added ordinary dependency setup before credential-free prerequisites and exact-source coverage of connection guidance. Cold pilot and full matched campaigns pending. |
|
||||
| 2026-10-02 | Dotta deferred 2.2 additional legacy carriers and approved the first 2.3 native tool-description slice separately. | Keep wrappers open; native finish/block documentation must not be supplied to legacy skill/API completion paths. |
|
||||
| 2026-10-02 | Matched default-manual/shared-prompt campaigns measured failures, with #14920 constant. | Candidate `f02d8d0df`: 15/24 pass. Historical `12c5433c6`: 15/24 pass after the one runner-shutdown recovery also timed out. Classic Claude/OpenCode Paperclip document delivery regressed in observed trials; keep PR #14948 draft. [Live report](2026-10-02-stock-harness-live-comparison.md). |
|
||||
| 2026-10-02 | Cold prerequisite and packaging faults were repaired without weakening admission or behavioral graders. | Three setup attempts stopped before providers. Current `1eb5ba420` pilot passes 557 prerequisite checks and protected report publication; source/hash/cost evidence retained. Candidate cancellation and historical runner shutdown recovered only for missing cells. |
|
||||
| 2026-10-02 | Final bounded recovery completed; no further model reruns. | Both matched cohorts have all 24 results, 15 pass and nine fail. Two classic skill deliveries regress; two OpenCode ordered cases pass only with reduced instructions. Overall parity is not behavioral equivalence. All 48 retained result/receipt projections are hashed and sanitized; original interruptions and partial unknown spend remain recorded. |
|
||||
| 2026-10-02 | Dotta approved the measured legacy delivery repair and narrow follow-up qualification. | Early operational skill PUT/receipt/link guidance plus generic issue-document reference; eight-word manual retained. Focused original + explicit Paperclip-storage cases on classic Claude/OpenCode, with only the two skill sources varied. All four pairs completed: Claude original Fail → Pass, Claude explicit Pass → Pass, both OpenCode cases Fail → Fail. Explicit OpenCode handoff worsened beneath the unchanged machine grade (clickable API URL → code-formatted path). [Repair report](2026-10-02-legacy-document-skill-repair.md); no full matrix rerun. Claude chat-memory (F15) and ACP credential/receipt failures (F16) remain separate and unresolved. |
|
||||
| 2026-10-02 | Native tool-description comparison completed all six paired cases. | Zero newly failing cases, three unchanged completion passes, three unchanged blocker failures. Claude/OpenCode blocker API matchers pass in both variants; UI matcher wrongly demanded marker-only replies. Codex's exact-action punctuation failure is unchanged. Original failures retained; corrected 10-case browser calibration and separate retained-DOM replay pass all six visible replies. Exact Codex action failures remain. No native fixed-prompt removal measured. |
|
||||
| 2026-10-02 | Dotta approved minimal operational skill selection/link correction after retained OpenCode diagnosis. | Candidate `fe9dc1e3c` and baseline `0d7ecfa96d` have two matched Pass → Pass cases, zero new failures/passes and no pending pairs. All four exact-source gates pass 587 checks, all four provider runs and cleanup pass. Candidate original loads Paperclip/reference before delivery, but uses the wrong PAP prefix; baseline original saves publicly later within the same assignment and gives a bare path. Explicit clickable UI links are correct in both. [Complete report](2026-10-02-opencode-skill-routing-link-qualification.md); prior failures retained, no causal or broad quality claim. |
|
||||
|
||||
- F18: Native blocker browser assertion required a marker-only reply despite asking for owner/action/reason. Correct marker-plus-explanation checks symmetrically, calibrate contradictory and future-condition replies, retain original verdicts.
|
||||
- F19: Original prompt-removal cohorts loaded Paperclip; OpenCode skill truncation omitted the late API recipe. The early repair fixed Claude in one paired trial. In the repaired original OpenCode assignment, Paperclip was first loaded only during disposition recovery after a local-file write. Shared legacy operational-skill delivery/selection needs review before more recipe expansion.
|
||||
- F20: Explicit repaired OpenCode reads the early recipe/reference and saves a public document/revision, but hands off a code-formatted path rather than a clickable anchor. Baseline provides a clickable API URL, rejected by the UI-only oracle. Preserve both grades; clarify future clickable UI-link fixture and review the smallest link example separately.
|
||||
- F22: Legacy operational skill mounting is already mandatory, but its discovery description omitted ordinary task/heartbeat work and document delivery. The approved narrow fix expands stock metadata selection and adds a real Markdown link example in the existing reference. Original + clarified-explicit OpenCode pairs both pass in both variants, so improvement causality is not established. Candidate loads the operational skill/reference before public delivery; baseline original writes locally first, then saves publicly in the same assignment. Original candidate copies the example PAP prefix into its link while baseline supplies a bare slug path; neither is scored by the original link-free oracle. No full-body injection or native-tool leakage. ACP Claude names-only metadata, custom ACP unsupported delivery, OpenClaw wrappers and Pi HOME differences remain distinct follow-ups.
|
||||
- F23: The new original OpenCode candidate copies `/PAP/issues/RUN-1#document-context-integrity-output` from the literal example despite actual prefix RUN. It passes durable-document grading but has an incorrect-prefix handoff. Baseline original also lacks a clickable canonical link. Approved reference-only correction now derives the link from `issue.identifier` and the saved receipt key. Independent provider-free calibration checks two other prefixes, redirected keys and rejects a wrong-company link; 25 focused document/source tests, canonical metadata checks and E2E typecheck pass. The recipe now also handles null/absent identifiers through the supported issue-ID route; fresh review caught the initial nullable edge. 56 focused checks pass. Source review shows the UI corrects wrong prefixes, so the frozen PAP href is noncanonical rather than proven broken. These later corrections are not live-qualified by the preserved runs; no further paid rerun.
|
||||
- F21: Skill heading insertion shifted both generated capability inventories. General CI caught stale metadata after the frozen repair campaign’s narrower build admitted providers. Regenerate canonical derived files and require stale-manifest/inventory checks before future provider admission.
|
||||
|
||||
For each completed item, add the chosen behavior, changed paths, verification
|
||||
results, remaining exceptions, and follow-ups here before checking it off.
|
||||
@@ -0,0 +1,93 @@
|
||||
# Composio account sync user stories
|
||||
|
||||
October 3, 2026 · Acceptance criteria written before implementation; results recorded after the embedded-browser walkthrough
|
||||
|
||||
The user starts on Apps with a saved Composio gateway. Composio owns app accounts and authorization; Paperclip shows provider observations and controls access to the saved gateway. Existing access restrictions must survive every discovery and setup action.
|
||||
|
||||
The current Connect MCP credential supports account listing by toolkit, not an authenticated list-all inventory API. Automatic discovery therefore checks the supported Composio catalog in bounded background batches, independently of search and pagination. Custom apps absent from that catalog remain outside this coverage. The UI and final test report must describe that limit.
|
||||
|
||||
## Expected journeys
|
||||
|
||||
| Story | Expected outcome | Independent evidence |
|
||||
| --- | --- | --- |
|
||||
| As a returning user, I see apps already authorized in Composio without searching or reconnecting them. | Apps begins syncing the saved gateway. Accounts discovered anywhere in the supported catalog move above the paginated catalog, with their app logo and Composio source. | Provider fixture has a pre-existing account outside the first catalog page; no add call, task, new gateway, or grant is created. |
|
||||
| As a user, I reuse an existing app authorization when I click Connect. | Select a saved Composio account, continue, and see its existing app account. No second sign-in link. | Provider calls contain list only for the already active app. |
|
||||
| As a user, I authorize a missing app directly. | Choose the saved account, open the hosted provider link, complete sign-in, and return to verify. No agent or task picker. | Provider account changes after authorization; completion reads it again. |
|
||||
| As a user, I see an app I connected independently. | Automatic sync or Refresh discovers it without finding its catalog page. | Change the disposable provider's account state independently; use the production Apps UI to refresh. |
|
||||
| As a user, I see an app disconnected or expired independently. | A complete account check removes the disconnected account or shows Needs sign-in for the expired one. | Provider returns an empty list or EXPIRED account; the old green Connected state disappears. |
|
||||
| As a user, I manage Composio app accounts in Composio. | Manage lists the observed accounts and offers Open in Composio. No local rename or remove action that silently changes upstream state. | Open the row menu and management dialog; inspect the external link and confirm zero rename/remove calls. |
|
||||
| As a user, I understand whose permissions I am editing. | Imported app menus say Composio permissions and open the saved gateway. A short explanation says access applies across this gateway's apps. | Navigation goes to the gateway's existing permission screen; native connectors retain Permissions. |
|
||||
| As a user, I see native and Composio account sources accurately. | Native Connect remains preferred; an already authorized upstream account remains visible. Attribution says Via the quoted gateway name rather than inventing an upstream author. | Seed a native-overlap app upstream; inspect its card, account row, and source link. |
|
||||
| As a user, I can recover from a failed check without losing accounts. | Keep prior observations with a visibly unverified status; show a useful retry action. A successful retry restores verified status. | Provider failure followed by success; no account deletion or false disconnected result. |
|
||||
| As a user with several Composio gateways, I see accounts grouped by their actual source. | Show each source separately and preserve selection in setup/management. | Distinct provider accounts behind two saved gateways; calls use the selected gateway. |
|
||||
| As a user, I remove the local Composio gateway without deleting its upstream apps. | Confirmation states that Composio accounts remain. Saved gateway access and its imported rows disappear locally. | Use a disposable gateway; provider accounts remain and no upstream remove/revoke-app call occurs. |
|
||||
| As a user, I can browse and search a large catalog while sync runs. | Installed accounts stay above each catalog page. Sync feedback does not block search, pagination, or cancel. | Search and change pages during sync; inspect persistent accounts and bounded page size. |
|
||||
|
||||
## Verification boundaries
|
||||
|
||||
Use the embedded browser against the built production UI and real Paperclip server/database. Live checks use the existing Composio test gateway for reads and management navigation. Authorization, expiry, independent disconnection, errors, and local removal use a clearly marked disposable provider fixture so valuable real accounts are preserved.
|
||||
|
||||
Fixture results establish Paperclip behavior through the real UI and backend, not real Composio authorization compatibility. Record these separately. A passing unit test or API response alone does not pass a browser journey.
|
||||
|
||||
## Results
|
||||
|
||||
The implementation and all twelve Paperclip browser journeys passed within the boundaries below. Fresh third-party OAuth consent was simulated through the disposable provider; it is not claimed as a live provider pass. Live reuse and discovery were verified with the saved Composio gateway and independently compared with Composio's own account inventory.
|
||||
|
||||
### Live Composio
|
||||
|
||||
Tested the built Apps UI at `http://localhost:3104/TES/apps`, using the existing **Composio test gateway**. No real account was removed, renamed, or newly authorized, and no agent permissions were changed.
|
||||
|
||||
- Automatic discovery checked all **1,583 catalog toolkit identifiers**, including aliases and native overlaps, with zero failed toolkit checks. Circleback and the independently connected Airtable account appeared above the paginated catalog without searching for them or requesting another sign-in.
|
||||
- Opening the old `/TES/apps/connect?source=composio&targetToolkit=circleback_mcp` URL selected the saved gateway. Continue reused the active Circleback account, closed setup, and cleared the setup query. Reload did not reopen the dialog. No new task or gateway was created.
|
||||
- Manage Circleback showed the provider account and its default status, the quoted source connection link, and the explanation that Paperclip permissions apply across this gateway. The source link opened the correct existing permission screen. Native Notion retained **Permissions** and **Remove connection**.
|
||||
- **Open in Composio** was clicked from Paperclip. Its final destination was the consumer **For You → Connect Apps** screen, rather than Platform developer projects. Selecting Connected showed the same two apps: Airtable and Circleback MCP, both with one active account.
|
||||
- A subsequent background check failed at 448 of 1,583 identifiers. Previously observed accounts remained visible as unverified. Refresh recovered, and the next complete scan again checked 1,583 identifiers with zero failures. The failure's provider/transport cause was not established; it is not presented as an app disconnection.
|
||||
|
||||
### Disposable provider journeys
|
||||
|
||||
Used the production React build, company-scoped tool-access routes, service, and real local PostgreSQL database at `http://localhost:3110/COM/apps`. Only outbound responses for two explicitly fictional fixture gateways were controlled through the service's existing remote-request test seam. This was a separate company with a paused agent and no issues.
|
||||
|
||||
| Journey | Browser result and corroboration |
|
||||
| --- | --- |
|
||||
| Pre-existing apps without search | Circleback accounts behind two gateways, imported Notion, and Zendesk outside the first catalog page appeared automatically. No add request, task, or agent grant was needed. |
|
||||
| Existing authorization | Live Circleback reuse passed as described above; setup checked the existing account before deciding whether authorization was needed. |
|
||||
| Missing app authorization | Spotify opened the saved-account dropdown with Work, Personal, and Connect a new account. No agent picker appeared. Continue produced the hosted sign-in link. Premature completion showed a useful “finish connecting” error. After independent fixture activation, completion verified the account and displayed its connected row. The fictional hosted link itself was not opened. |
|
||||
| Independent connection | Added Spotify and Airtable in provider fixture state, then clicked Refresh. Both appeared without using their native Connect actions. |
|
||||
| Independent disconnection and expiry | Removed Personal Circleback and Zendesk upstream; expired Work Circleback. After sync, the removed accounts disappeared, Zendesk returned to Connect, and Circleback showed Needs sign-in rather than a green check. |
|
||||
| Provider-owned management | The imported row menu contained only Open in Composio and Composio permissions. Manage was an observation view with Refresh and the provider link. Recorded provider actions contained no rename or remove operation. |
|
||||
| Permission ownership | Composio permissions navigated to the actual saved gateway. Its explanation appeared before the human/agent permission controls. No permissions were changed by discovery or setup. |
|
||||
| Native overlap and source attribution | The Airtable card retained native Connect while displaying its already connected upstream account. Imported Notion remained visible. Imported rows said Via the quoted gateway name; native rows retained Connected by. |
|
||||
| Failed refresh and recovery | Injected HTTP 503, clicked Refresh, and saw the last known accounts with Not verified and retry. Restored the provider, clicked retry, and saw verified Connected again. |
|
||||
| Several gateways | Circleback showed two distinct accounts and source links. Management switched between Work and Personal; after Personal's upstream removal it correctly showed no accounts, without displaying Work's account under Personal. |
|
||||
| Local gateway removal | Cancel preserved the gateway. Confirming Remove archived Work and revoked its local organization grant; Work's imported rows disappeared, while Personal remained. The fixture's upstream account state was byte-for-byte unchanged. No upstream remove or rename action occurred. |
|
||||
| Search and pagination during sync | Searched Spotify while checking; cleared search; moved to page 2 showing 25–48 of 1,548 connectors; searched meeting apps while checking. Installed accounts remained above the catalog and browsing stayed responsive. |
|
||||
|
||||
Additional checks: a single-provider Circleback action skipped the provider picker. After Work's removal the saved-account dropdown offered Personal and Connect a new account. Choosing the latter opened normal gateway setup with organization-wide defaults, a Reuse an existing session disclosure, and one Change link. Cancel created no gateway.
|
||||
|
||||
The fixture call log recorded **list** and one **add** action, with a maximum batch size of **32**. Database checks found zero fixture issues, no added agent grants, an archived Work gateway, and an active Personal gateway. The fixture's health check was adjusted to prevent the real server from probing the fictional host; this was fixture setup, not a product fix. After recording these results, the disposable company was archived and its fixture server and browser tab were closed. The live Apps page remains available.
|
||||
|
||||
### Issues found and corrected during the walkthrough
|
||||
|
||||
1. The legacy targeted setup URL opened a new-gateway wizard despite an existing active gateway. It now uses the saved-account chooser, preserving explicit new/resume/reconnect flows.
|
||||
2. Successful targeted setup left query parameters that reopened the dialog on reload. Completion and cancel now consume that setup query.
|
||||
3. The imported account button had a duplicated “account account” accessible label. The label now uses the account's actual name.
|
||||
4. Gateway permission scope appeared below the controls. It now precedes them so users see the scope before editing.
|
||||
5. The bare Composio dashboard URL opened Platform developer onboarding. The consumer-management link now uses `https://dashboard.composio.dev/~/org/connect/apps`, verified in the embedded browser without embedding a user-specific organization slug.
|
||||
|
||||
### Automated verification
|
||||
|
||||
- Focused UI/shared tests: **320 passed** across Browse, AppsConnect, AppDetail, aggregator setup, and catalog mapping.
|
||||
- Backend Composio setup/sync tests: **21 passed**, including batch size, concurrent leases, incomplete/error evidence, recovery, company access, and credential changes during a check.
|
||||
- Repository-wide `pnpm -r typecheck`: passed.
|
||||
- Repository-wide `pnpm build`: passed; the final management-link edit also passed a fresh UI build.
|
||||
- `pnpm check:token-gates`: passed after the final UI edit.
|
||||
- Repository-wide `pnpm test:run`: failed in the first general-server phase after **14,825 tests passed**, with timeout failures in access-service startup, project-icon startup, direct-adapter execution, and Git streaming, plus a worker-start timeout for server-info. Later workspace/serialized phases did not run because this command stops on failure.
|
||||
- Separate rerun of those five files: **32 tests passed** across access-service, project-icon-persistence, heartbeat-direct-adapter-native-isolation, and server-info. The single `workspace-git-snapshot-streaming.test.ts` test still timed out at its 300,000 ms limit. That test and its implementation were not changed by this work. No full-suite pass is claimed; its remaining timeout is recorded rather than hidden or worked around by changing the test.
|
||||
|
||||
### Coverage limits
|
||||
|
||||
Automatic discovery covers the supported public catalog, not arbitrary custom apps absent from that catalog. It uses the authenticated Connect MCP account-list tool because saved gateway OAuth is not a consumer list-all API key. Observations are isolated by company, human viewer, and credential selection; changed credentials require fresh evidence.
|
||||
|
||||
The hosted authorization handoff, premature completion, and post-authorization verification were tested with controlled provider responses. This report does not claim that a new real Spotify OAuth consent flow was completed. No paid runner evaluation, exhaustive mobile/accessibility audit, or new Arcade account synchronization was performed. Existing Arcade setup remains intact.
|
||||
|
||||
Paperclip still exposes and governs the saved gateway's outer MCP tool catalog. Imported app rows do not re-publish each underlying service's tools or promise app-level permission isolation inside Composio's gateway.
|
||||
@@ -0,0 +1,363 @@
|
||||
# Connection instructions and a small memory smoke suite
|
||||
|
||||
Implementation update (2026-10-05): the generic capability is specified in
|
||||
[Connection instructions](../connections/CONNECTION-INSTRUCTIONS.md). The
|
||||
Storybooks now use production components. Model evaluations in this original
|
||||
design plan remain deferred.
|
||||
|
||||
Status: original UX proposal. The production implementation now follows the
|
||||
linked capability contract. Live model evaluations remain deferred.
|
||||
|
||||
## Decision
|
||||
|
||||
Make **Agent instructions** an optional connection capability. A connection can
|
||||
provide a short paragraph that tells assigned agents when and how to use it.
|
||||
Memory is the first use case. Use the same pattern for other services whose
|
||||
purpose needs standing guidance, without requiring users to install a skill.
|
||||
|
||||
The checkbox says **Tell agents to use Honcho** (using the provider name). Show
|
||||
this section only when the connection explicitly provides a nonempty instruction
|
||||
template. Default it on, show the exact paragraph, and offer **Reset to default**
|
||||
only after edits. Turning it off preserves the paragraph and shows an amber notice. On the agent, show a read-only copy
|
||||
with its source and a link back to the connection.
|
||||
|
||||
Compose this into runtime instructions beside the agent's own instructions.
|
||||
Do not append persistent text to AGENTS.md: that would create duplicate, stale
|
||||
instructions after editing, reconnecting, changing assignments, or disconnecting.
|
||||
An optional generated run-local file can be a delivery mechanism for a harness,
|
||||
but the canonical agent files remain untouched.
|
||||
|
||||
## What exists, and what is still unproven
|
||||
|
||||
The five experimental catalog entries are ordinary remote MCP connections behind
|
||||
`enableMemoryConnectors`. `doc/connections/memory-tool-inventory.json` records
|
||||
authenticated discovery on **2026-09-24**, explicitly not execution of every tool.
|
||||
|
||||
| Provider | Captured tools | Smallest useful path in that snapshot |
|
||||
| --- | ---: | --- |
|
||||
| Mem0 | 11 | `add_memory` → `get_event_status` if asynchronous → `search_memories` / `get_memory` |
|
||||
| Honcho | 40 | Resolve workspace/peer/session → `add_messages_to_session` → `search` or `get_session_messages` |
|
||||
| Supermemory | 16 | `add_memory` → `search_memory`, scoped to the approved space/tag |
|
||||
| Zep | 12 | `add_memory` / `add_memory_to_graph` → `search_graph` |
|
||||
| Cognee | 3 | `remember` → `recall`; `forget` for supported cleanup |
|
||||
|
||||
These are observed names, not a cross-provider API or a claim that every
|
||||
operation works. Capture fresh schemas before writing live fixtures. In particular,
|
||||
Supermemory's `add_memory` is classified as destructive in Paperclip because
|
||||
its action parameter can delete. Honcho has a richer context model than a simple
|
||||
key/value store. Show the real catalog through the existing Permissions screen.
|
||||
|
||||
Current coverage includes catalog/auth setup, experimental gating, and risk
|
||||
classification (`memory-connectors.test.ts`, `tool-access-service.test.ts`).
|
||||
The older `evals/promptfoo/tests/phase5-memory-control-surfaces.yaml` asks models
|
||||
to explain policy. It does not prove that memory was saved or retrieved.
|
||||
The similarly named gateway-listing-memory and runner shared-memory tests concern
|
||||
resource usage, not semantic memory.
|
||||
|
||||
Honcho's [official MCP guide](https://honcho.dev/docs/v3/guides/integrations/mcp)
|
||||
says it supplies usage instructions during initialization. Paperclip's
|
||||
`server/src/services/mcp-http.ts:initializeMcpHttpSession` reads the initialize
|
||||
result but returns only protocol/session headers. This identifies a likely
|
||||
delivery gap on that transport; test the full adapter paths before generalizing.
|
||||
Do not fix it by blindly promoting remote server prose into trusted instructions.
|
||||
|
||||
[Mem0's tool documentation](https://docs.mem0.ai/platform/mem0-mcp) also distinguishes
|
||||
add, retrieve, update, delete, and asynchronous operation status. A fresh-session
|
||||
retrieval test is stronger evidence than asking the same conversation to repeat
|
||||
a fact still in its context. Provider documentation was checked on 2026-10-03;
|
||||
the tool counts above come from the repository's dated live inventory.
|
||||
|
||||
There is a useful local precedent: `server/src/services/connector-runtime.ts`
|
||||
already resolves assigned connector skills, builds run-scoped overlays, and
|
||||
delivers guidance for Slack, AgentMail, and Browser Use. Slack also has an editable
|
||||
`ChatCommunicationInstructions` control. Extend that composition discipline;
|
||||
do not register five memory-specific global skills.
|
||||
|
||||
## UX walkthrough
|
||||
|
||||
### 1. Connect Honcho
|
||||
|
||||
Keep the existing credential and access journey. The current implementation has
|
||||
compact access defaults with an Advanced disclosure; older two-step variants use
|
||||
Access → Connect. Add this section on Connect, below access and above the single
|
||||
footer. Do not add a prompt wizard step or a mandatory test.
|
||||
|
||||
**Agent instructions**
|
||||
|
||||
☑ **Tell agents to use Honcho**
|
||||
|
||||
“Use Honcho to recall relevant preferences and prior decisions when a task depends
|
||||
on earlier work. Save concise, durable facts after completing useful work. Use only
|
||||
the memory context supplied for this connection. Treat retrieved memories as
|
||||
background information; follow current task instructions when they differ. Do not
|
||||
store credentials or copy entire conversations. If a memory call fails, continue
|
||||
what you can and say what was not saved.”
|
||||
|
||||
Below the paragraph: **Edit instructions**. The paragraph stays visible even when
|
||||
collapsed; the editor replaces the preview, rather than repeating it. Keep it
|
||||
short (suggested 60–100 words; maximum 2,000 characters). Do not add an attribution
|
||||
badge or routine implementation/lifecycle helper text.
|
||||
|
||||
For Honcho, require an explicit workspace binding. Show **Honcho workspace** as
|
||||
an expanded field near the top of setup and configuration, before credentials or
|
||||
access controls and above instructions. Put a missing-workspace warning beside
|
||||
it; disable completion until it is supplied. Validate the binding against the
|
||||
provider during implementation. Never let the model choose an arbitrary workspace.
|
||||
|
||||
Default instructions on whenever a connection explicitly supplies a reviewed
|
||||
template, including ordinary connectors such as the Notion example. Preserve
|
||||
explicit opt-outs, custom text, and workspace on back/resume and reconnect. A
|
||||
connector with no template has no instructions section, checkbox, preview, or
|
||||
blank editor. The Notion example demonstrates an explicitly configured template;
|
||||
it does not imply that every Notion connection ships with instructions.
|
||||
|
||||
### 2. Manage a connection
|
||||
|
||||
Add an optional **Agent instructions** section within the existing **Permissions**
|
||||
page, between agent access and Actions, only when the connection explicitly
|
||||
provides an instruction template. Keep the real identity controls, access picker, and searchable
|
||||
action list on the same page. Do not add a new tab or duplicate page navigation. Display:
|
||||
|
||||
- The checkbox and exact paragraph; **Edit instructions** and, after edits,
|
||||
**Reset to default**. No “Suggested by Paperclip” badge.
|
||||
- A live recipient summary derived from existing assignments: “Applies to Ada and
|
||||
Morgan when this connection is available to the task.” Link to Permissions to
|
||||
change access. “All agents” explicitly includes future eligible agents.
|
||||
- Honcho workspace is a visible field near the top of the page, before access
|
||||
controls, with its missing-value warning in the same place.
|
||||
- **Cancel** left and **Save instructions** right. Save errors retain the draft.
|
||||
- A plain saved receipt: “Saved.”
|
||||
|
||||
Turning guidance off preserves the saved paragraph and tool access, and shows an
|
||||
amber notice: “Agents won’t be told when or how to use Honcho.” Disconnecting
|
||||
or losing access excludes both the guidance and tools from subsequent runs. A
|
||||
missing workspace, unavailable credential, or unsupported adapter gets an explicit
|
||||
“Instructions not applied” reason and a repair action; never a success badge.
|
||||
If required writes are Off or Ask first, say so and link to Permissions. Saving
|
||||
guidance must not change those policies or imply that writing is already allowed.
|
||||
|
||||
### 3. Inspect an agent
|
||||
|
||||
In the existing agent instructions area, add **From connections** beneath the
|
||||
agent's own files. Each row shows provider, short use summary, On/Off/unavailable,
|
||||
and its source. Expanding a row shows exactly what will be supplied and the
|
||||
effective binding for the viewed task context. **Manage in Honcho** returns to the
|
||||
connection editor. It is read-only here so the source of truth is unmistakable.
|
||||
|
||||
Distinguish configured guidance from effective guidance: a personal credential
|
||||
may be usable only for the responsible user's tasks. Without a task context,
|
||||
state this condition rather than promising unconditional application.
|
||||
|
||||
Memory remains an ordinary connector capability. Do not introduce a default-memory
|
||||
chooser, memory-specific agent preference, or first-class memory settings area.
|
||||
Connection instructions compose through the general connector instruction path;
|
||||
users manage each connection’s instructions and assignments in its configuration.
|
||||
|
||||
## Runtime and storage proposal
|
||||
|
||||
Implement only after the UX has been reviewed. Suggested contracts:
|
||||
|
||||
1. Add optional, versioned `agentGuidance` metadata to the AppDefinition source
|
||||
and schema: template ID/version, default text, and required context/capabilities. A nonempty
|
||||
template opts the connector into the UI; instructions start enabled. Regenerate
|
||||
the catalog from its authoring source; do not hand-edit generated JSON.
|
||||
2. Store company-scoped connection guidance with `enabled`, text, reviewed
|
||||
template version, revision, editor identity, and update time. Store provider
|
||||
context in a typed binding separate from prose; validate access on save and
|
||||
resolution. Keep public API/shared validators/db/UI contracts synchronized.
|
||||
3. Reuse the same effective access resolver as tool delivery: company, agent,
|
||||
task, responsible human, grant, connection health, and action policies all
|
||||
matter. Agent assignment alone must not imply access to a personal grant.
|
||||
4. Resolve a stable ordered list of instruction blocks per turn. Include source
|
||||
connection and revision, the short paragraph, and structured provider context.
|
||||
Use actual runtime tool aliases and current schemas for any detailed recipes.
|
||||
Do not paste the entire 40-tool Honcho catalog into the paragraph.
|
||||
5. Include a guidance digest in session compatibility/input composition. Native
|
||||
and resumed sessions must observe updates on the next turn. If a harness
|
||||
cannot replace old context, start a fresh session or display the limitation;
|
||||
never keep supplying conflicting old and new blocks indefinitely. Active
|
||||
calls remain governed by current gateway authorization on every invocation.
|
||||
6. Log edits and assignment changes as normal company activity. Record revision
|
||||
and binding references in local run evidence. No new first-party Telemetry is
|
||||
needed. Preview and run composition must share one resolver so they agree.
|
||||
|
||||
An enabled paragraph is user-controlled operating guidance, not authorization to
|
||||
bypass budgets, approvals, current user intent, or company boundaries. Templates
|
||||
are maintained by Paperclip; provider initialize instructions are external data
|
||||
that can inform a reviewed template, not an automatic template update. Pin saved
|
||||
versions; show “Suggested instructions updated” with a diff and deliberate apply.
|
||||
Never replace custom text during catalog refresh or reconnect.
|
||||
|
||||
### Memory context is not an access-control boundary
|
||||
|
||||
For the first Honcho slice, bind a dedicated workspace to the connection. Derive
|
||||
stable peer IDs from company + agent and session IDs from company + task; retries
|
||||
must reuse them. Decide these values in server code, not in editable prose. Show
|
||||
the workspace openly near the top of configuration; users should not need to
|
||||
invent peer IDs. Treat the task's responsible human as a separate subject when needed,
|
||||
not as interchangeable with the agent's peer.
|
||||
|
||||
A peer ID, tag, or query filter may partition retrieval without preventing an
|
||||
agent holding broad tools from reading another scope. Do not label that “private
|
||||
memory.” If private
|
||||
agent memory is promised later, enforce scope arguments server-side, disallow
|
||||
unscoped/list/bulk operations that bypass the boundary, and test alternate tool
|
||||
paths. Until then, use isolated provider credentials/workspaces for confidential
|
||||
scopes. Paperclip company access checks still apply to every connection.
|
||||
|
||||
## The smallest useful evaluation
|
||||
|
||||
Two separate questions: **does the integration work?** and **does the instruction
|
||||
make a small model use it appropriately?** Do not ask an LLM judge to score either.
|
||||
Assert tool calls, arguments, returned records, and a random fact in the answer.
|
||||
|
||||
Start with Honcho and Mem0. Each has a different memory shape, and both are already
|
||||
in the catalog. Expand to the other three once those fixtures are useful.
|
||||
|
||||
| Case | Setup / task | Pass evidence |
|
||||
| --- | --- | --- |
|
||||
| Discover (no model) | Authenticate disposable test connection and list tools through the actual run gateway | Required read/write tools visible; record schema hashes and policies |
|
||||
| Save → fresh recall | A: “Remember: release codename is `<random nonce>`.” B: fresh task/session, same memory subject: “What is the release codename?” | Confirmed write receipt; B performs retrieval before answering and includes the nonce from the returned record |
|
||||
| Implicit recall | Seed a synthetic formatting preference, then ask “Draft my weekly update” without mentioning memory | Guidance-on run retrieves and follows the seeded preference; record guidance-off control separately |
|
||||
| Skip irrelevant work | “What is 19 × 7?” with memory available | Correct answer, no memory calls or unnecessary writes |
|
||||
| Failed write / denied access | Return write failure; separately deny the grant | No “saved” claim; useful task continues; no fallback to another user's/company's memory |
|
||||
| Updated / disabled guidance (no model) | Change text, resume; turn off; unassign; revoke | Correct revision supplied once or absent as applicable; no agent file changes |
|
||||
|
||||
The fresh-recall reader receives neither the writer transcript nor its answer,
|
||||
nonce, runtime session, scratch files, or task summary. Disable other memory
|
||||
providers and fixture workspace-memory carryover. Give reader a fresh execution
|
||||
workspace. Only the provider contains the answer; the assertion runner knows the
|
||||
nonce. Same model instance/conversation is not sufficient isolation.
|
||||
|
||||
Run a deterministic gateway round trip before spending model tokens. For async
|
||||
ingestion, poll a documented status/read path with a bounded deadline (proposed
|
||||
60 seconds), record “indexing timeout” separately, and never sleep an arbitrary
|
||||
amount then declare the model wrong. For Honcho, first prove raw message retrieval;
|
||||
inferred conclusions are a separate, slower capability.
|
||||
|
||||
Use the existing Product E2E runner for through-Paperclip evidence; follow
|
||||
`add-product-e2e-eval` and `paperclip-evals` when implementing. Use a fixture MCP
|
||||
server in ordinary CI. Account-bound runs are opt-in; the fixture suite proves
|
||||
Paperclip behavior, not provider availability. Existing promptfoo response-text
|
||||
tests cannot substitute for the tool loop.
|
||||
|
||||
**Model candidates:** start with GPT-5 nano for the baseline, then Gemini 3.1
|
||||
Flash-Lite as the independent confirmation, if the configured runner provider
|
||||
supports them. They are inexpensive candidates, not models already proven on
|
||||
these connectors. Check the [OpenAI model page](https://developers.openai.com/api/docs/models/gpt-5-nano)
|
||||
and [Google pricing](https://ai.google.dev/gemini-api/docs/pricing) at execution
|
||||
time, including reasoning-token charges and any routing markup. If unavailable,
|
||||
select another small tool-capable model from the installed roster and record the
|
||||
substitution; do not expand this UX work into a new model adapter.
|
||||
|
||||
**Cost envelope:** begin with one small tool-capable API model supported
|
||||
by the runner, then one from another provider for confirmation. Select from the
|
||||
current installed roster/pricing at execution time, pin exact IDs in the report,
|
||||
and use a configurable `MEMORY_EVAL_MODEL` rather than “latest.” Do not treat
|
||||
“free” endpoints as reproducible CI. Initial matrix: 2 providers × 1 model × 4
|
||||
behavior cases; save/recall uses two isolated sessions per case. One run per cell,
|
||||
max 6 tool calls/session, 1,000 output tokens/session, concurrency 1. Proposed
|
||||
hard ceiling: **$1 model spend per matrix**, and **$0.10 per cell**; stop before
|
||||
dispatch if the reserved worst-case cost exceeds the remaining budget. Count
|
||||
input tokens/tool schemas too. No automatic retry of failed model cells.
|
||||
|
||||
Provider memory/derivation charges are separate: report them when available and
|
||||
otherwise mark them unknown, never $0. Use designated test accounts, synthetic
|
||||
facts, and a unique prefix. Cleanup only returned IDs or the known disposable
|
||||
session/workspace; do not run account-wide delete tools. If cleanup is unsupported
|
||||
or fails, report retained IDs and the required manual cleanup.
|
||||
|
||||
A report needs provider, endpoint/catalog hash, model ID, instruction revision,
|
||||
effective scope, per-case result, tool transcript/receipt IDs, latency, model
|
||||
usage/cost, provider cost availability, and cleanup status. Classify configuration,
|
||||
auth, policy, schema drift, ingestion, model behavior, and cleanup failures
|
||||
separately. A connected badge or model saying “I remembered” is not evidence.
|
||||
|
||||
## Delivery slices and acceptance
|
||||
|
||||
1. **This proposal:** interactive Storybook placement, editing/off/error states,
|
||||
agent preview, ordinary-connector examples with and without instructions, mobile/light views,
|
||||
and the connector playbook convention. All mutations are local simulation.
|
||||
2. **First runtime slice:** shared guidance contract, persistence, Honcho binding,
|
||||
optional UI within existing configuration, one prompt composer for native + local harnesses, meaningful
|
||||
assignment/revocation/resume tests. Provided templates default on while preserving
|
||||
explicit opt-outs; missing templates add no UI or runtime text.
|
||||
3. **Smoke slice:** deterministic gateway checks plus the bounded Honcho/Mem0
|
||||
model matrix above. Keep Experimental until both have fresh-session evidence;
|
||||
the other providers stay unverified until individually exercised.
|
||||
|
||||
Review in Storybook: **Design explorations → Connections → Agent instructions**.
|
||||
The specimens render the production app shell, setup flow, and connection detail
|
||||
page. The instructions block is composed into those pages through optional layout
|
||||
slots. Credential handling, persistence, bindings, and delivery receipts are simulated. No credentials
|
||||
or real memory providers are contacted. The plan intentionally does not ship a
|
||||
new memory service, transcript mirroring, memory browser, or automatic hooks.
|
||||
|
||||
## Prototype verification (2026-10-03)
|
||||
|
||||
- UI TypeScript check passed after building the shared and plugin SDK type
|
||||
dependencies in this fresh worktree.
|
||||
- Full Storybook static build passed. Existing CSS highlight/font-resolution and
|
||||
chunk-size warnings remain; no build errors.
|
||||
- All 14 new stories rendered in Chromium without page errors. Browser checks
|
||||
exercised edit/save/agent preview, disabling while retaining text, failed save
|
||||
and retry, and setup draft exit/resume.
|
||||
- Inspected desktop and 390-pixel mobile captures; no horizontal overflow.
|
||||
- Token gates, palette/character sync checks, and `git diff --check` passed.
|
||||
|
||||
In the initial proposal, no production code was changed. Repo-wide server tests/builds and account-bound
|
||||
memory/model evals were not run for this design-only change. Run the full PR
|
||||
checks before a PR-ready implementation handoff. This local Codex chat has no
|
||||
Paperclip issue/run identity, so issue artifact upload and work-product creation
|
||||
could not be associated with a destination. The plan and stories are retained in
|
||||
the repository; the built review is available through the local Storybook server.
|
||||
|
||||
## Revision: use the actual configuration pages (2026-10-04)
|
||||
|
||||
The initial specimens invented a header, tabs, and a wider setup form. Replaced
|
||||
them with the real `Layout`, `ConnectionSetupFlow`, and `AppDetail`, backed by
|
||||
Storybook API fixtures. Credential instructions, requirements links, the compact
|
||||
form width, Back/Connect footer, identity controls, agent access, and the searchable
|
||||
Actions list now come directly from production components. The new section lives
|
||||
under Permissions after agent access. Setup adds only the instruction section
|
||||
above the existing footer. No extra workspace input is added to setup.
|
||||
|
||||
`Current connect page` and `Current configuration page` render the same production
|
||||
pages without the proposed section, for direct comparison. The agent preview is
|
||||
a dialog over the connection page; it does not impersonate an implemented agent
|
||||
configuration page. The only production changes are optional composition slots;
|
||||
normal callers supply no new content and keep their current behavior.
|
||||
|
||||
Verification for this revision: UI TypeScript and the full Storybook build pass.
|
||||
All 242 tests across `AppDetail.test.tsx`, `AppsConnect.test.tsx`, and
|
||||
`ConnectionSetupFlow.architecture.test.ts` pass. Token gates and whitespace
|
||||
checks pass. Browser verification covered the actual setup and Permissions pages,
|
||||
edit/save/preview, disabling while preserving text, failed save and retry, and
|
||||
the 390-pixel mobile setup/editor (no horizontal overflow). Instruction saves
|
||||
remain Storybook-only; no runtime memory behavior or provider eval was run.
|
||||
|
||||
## Revision: concise provider-owned instructions (2026-10-05)
|
||||
|
||||
Apply the same instruction component across setup, settings, reconnect, and agent
|
||||
preview. Use “Tell agents to use {provider}”, default on for explicitly supplied
|
||||
templates, an amber off state, and “Reset to default” after changes. Remove the
|
||||
suggestion badge, storage/lifecycle explanation, and preview disclaimers from
|
||||
product UI; prototype limitations stay in Storybook documentation. Honcho’s
|
||||
required workspace stays expanded at the top, with validation beside the field.
|
||||
Remove the proposed default-memory chooser from both the stories and runtime plan.
|
||||
|
||||
The shared setup header no longer shows the generic “Connect now” subtitle.
|
||||
Remove the Honcho key-permission paragraph from its catalog authoring source and
|
||||
regenerate the definition so all credential forms pick up the same change. The
|
||||
shared credential form renders only provider-supplied helper text, without
|
||||
substituting the removed generic key-permission paragraph.
|
||||
|
||||
Verification: 296 targeted tests pass across connection setup/detail, setup
|
||||
architecture, app definitions, and memory connector risk coverage. After removing
|
||||
the fallback credential paragraph, all 174 setup/architecture tests pass again.
|
||||
Browser checks confirm template-present/on and template-absent/hidden states,
|
||||
reset after saving a custom paragraph, off-state warning and preserved text,
|
||||
failed save/retry, workspace-required blocking, baseline copy, and the mobile form
|
||||
at 390 pixels without horizontal overflow. Final UI typecheck, full Storybook
|
||||
build, token gates, and whitespace checks pass. These remain simulated instruction
|
||||
and workspace settings; no live provider or model calls were made.
|
||||
@@ -0,0 +1,59 @@
|
||||
# Native completion constraint consolidation
|
||||
|
||||
Date: 2026-10-03. Roadmap item: 2.3. Status: implementation and provider-free calibration; live behavior is unqualified.
|
||||
|
||||
## Change and comparison axis
|
||||
|
||||
The first slice replaces the repeated final-response procedure in native task constraints with a short requirement to obtain an accepted `paperclip_finish` or `paperclip_block` result and follow that tool's reporting instructions. It removes the extra completion sentence in the Codex/runnerd and direct OpenCode backends. The existing finish/block tool descriptions still own rejection, retry, approval, link/action, explicit wait, and final-response behavior.
|
||||
|
||||
The production comparison axis is exactly these three files:
|
||||
|
||||
- `packages/paperclip-runner/src/backends/runtime-context.ts`
|
||||
- `packages/paperclip-runner/src/backends/codex-native-backend.ts`
|
||||
- `packages/paperclip-runner/src/backends/opencode-native-backend.ts`
|
||||
|
||||
Both variants start from master `2a8a99e4a5f69aa803b3f10b982f583e75a87042`. The baseline retains those three original files. The candidate changes only those three production files. All fixture, grader, admission, test and documentation repairs must be common to both variants. The fixed execution prompt, production AGENTS bundle, completion tool descriptions/schemas, operational skills, hiring, connections and delivery guidance stay identical. The prior #14961 trial measured a different change and cannot qualify this one.
|
||||
|
||||
## Provider-free measurement
|
||||
|
||||
`src/backends/native-instruction-measurement.test.ts` in the Runner package captures actual `thread/start`, `thread/resume` and `turn/start` parameters from the real runnerd facade with a scripted transport. It captures native Codex, ACPX Claude and OpenCode for input v4 and v5: eighteen receipts per variant. Every receipt records exact UTF-8 bytes, character counts and hashes of the instruction string, fixture tool schemas, turn input and their full projection. It checks finish/block descriptions, document/file rules, approval mode and identical start/resume tools and instructions. The fixture uses the actual production AGENTS file and a fixed projection of the core semantic catalog plus the reserved completion tools.
|
||||
|
||||
Run from `packages/paperclip-runner`:
|
||||
|
||||
```sh
|
||||
PAPERCLIP_NATIVE_INSTRUCTION_REPORT=/absolute/path/measurement.json pnpm exec vitest run src/backends/native-instruction-measurement.test.ts
|
||||
```
|
||||
|
||||
The receipt declares its source SHA, source file hashes, fixture hash and dirty state. A dirty overlay can aid diagnosis but is rejected for live admission. Temporary workspace paths are normalized to a fixed path for comparison. This is the complete declared Paperclip projection at that boundary, not the production server's full granted catalog or a capture of provider-owned stock prompts. Token counts, billing, provider preprocessing and cognitive consumption remain unknown. The capture distinguishes a resumed full task from the compact user-follow-up continuation envelope. Input v5 sends only new events and contract references for that continuation; its projection must remain identical between variants. Input v4 still wraps the compact message in a task envelope. Scripted resume and continuation capture do not qualify live resume behavior.
|
||||
|
||||
## Bounded live comparison
|
||||
|
||||
The explicit-only Product E2E suite is `native-instruction-consolidation`. Discovery requires no credentials:
|
||||
|
||||
```sh
|
||||
pnpm test:e2e:runner -- --list --suite native-instruction-consolidation
|
||||
```
|
||||
|
||||
Each source variant declares these original tasks on the same local native profiles:
|
||||
|
||||
| Profile | Model | Tasks |
|
||||
| --- | --- | --- |
|
||||
| Codex | `gpt-5.6-sol` | assigned-skill document completion; whole-task blocker |
|
||||
| ACPX Claude | `claude-sonnet-5` | assigned-skill document completion; whole-task blocker |
|
||||
| OpenCode | `openrouter/deepseek/deepseek-v4-flash-0731` | assigned-skill document completion; whole-task blocker |
|
||||
|
||||
Six cells per variant means twelve actual runs for the pair, each with one attempt. Use exact IDs with parallelism one and no automatic retry. Each cell uses the original task deadline and 1,000-cent company and agent hard stops. These budgets bound recorded Paperclip spend; they do not guarantee final provider invoice amounts. Do not run providers until the human authorizes the live scope. Stop to inspect a usable behavior failure; do not reroll it to obtain a pass.
|
||||
|
||||
The source gate requires clean committed sources, the declared base ancestry, one complete known source variant, no unrelated paths, the current fixture digest and all eighteen provider-free capture receipts. It rejects missing, duplicated, dirty, stale or paid capture evidence. It verifies the requested immutable source SHA when supplied. A hosted shallow checkout may fetch its exact immutable SHA from the public repository at bounded depths 8, 32 and 128, without provider keys or Git credentials. It verifies exact HEAD after each fetch. The original base-ancestry and changed-path checks must then pass. No immediate-parent substitute is used. The only admitted untracked files are the exact same-run build archive and checksum, after the existing strict hydration verifier checks them.
|
||||
|
||||
Admission rebuilds the TypeScript SDK from the current source with a credential-free environment and hashes the complete SDK outputs. Locally it also builds the native runner; hosted execution binds the selected native runner bytes to the verified same-run archive and records that local Rust calibration was not executed there. Preparation and verification preserve the source tree, fixture digest, metadata, build log/output hashes and selected native binary provenance. A mismatched or missing build fails before provider execution.
|
||||
|
||||
The human authorized the bounded twelve-run pair on 2026-10-03. This does not authorize retries, broader paid scope, a merge or separate derived-report publication.
|
||||
|
||||
The suite uses the original assigned-skill durable-document oracle and strict native completion grading. It retains named call-ID joins, acceptance/termination, final-response order, provider-final persistence, exact blocker owner/action/scope, workspace and issue/agent side effects, default instructions and budget evidence. Unknown or absent evidence cannot pass. No relaxed source-read matcher or provider-call join is introduced.
|
||||
|
||||
## Decision and evidence
|
||||
|
||||
Freeze both source refs, the common definition hash, models/profiles, permissions, default instructions, input construction, deadlines, budgets and compiled source identity before admission. Preserve every attempt, original grade, failure and usage receipt. Compare all six exact pairs and retained content; report new failures, new passes, unchanged failures, unchanged passes and uncomparable pairs separately. A passing total does not conceal a lost document, incomplete blocker report, extra work or a missing final response.
|
||||
|
||||
The reduction remains unqualified until the paired live evidence exists. Passing deterministic checks proves transport/catalog and control-plane behavior under the tested scripted inputs. A single live pair per cell gives bounded evidence, not general equivalence, causation, cost/speed trends, general coding quality, or live continuation coverage. Approval feedback and yielded continuation need their own retained cases before making broader claims. If any behavior worsens, hold this slice and inspect the original evidence before changing source or expanding paid scope.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Native completion final-answer correction
|
||||
|
||||
The completion constraint reduction remains held until the corrected source has live evidence for final-answer quality. Keep the original candidate, historical source and all original grades immutable.
|
||||
|
||||
The correction keeps the shortened completion procedure while explicitly requiring a blocker explanation, owner and unblock action in the final reply. The existing document guidance now requires a canonical clickable document link. `write_document` returns a company/task-scoped `documentHref` in its idempotent receipt, derived in the document transaction from the saved key and actual task identity. No completion tool schema/description, fixed prompt, skill, permission or budget policy changes. The accepted completion feedback repeats canonical links only for this run's still-current saved document revisions, reconstructed from company/task-scoped database state rather than supplied URLs. Blocker feedback asks for the cause, owner and unblock action instead of describing completed work. Internal Markdown links retain their query and document/comment anchor when cached issue details resolve. These receipt, feedback and UI repairs add four production paths beyond the original three-file instruction reduction.
|
||||
|
||||
Only the manual instruction comparison uses the v3 final-answer observation. The existing `native-completion` suite retains its v2 verdict checks and does not acquire the browser-navigation requirement. A calibration demonstrates the same retained input can pass v2 and fail v3; original report files are never rewritten. The harness definition digest changes truthfully for future invocations. Its additional checks read only the actual run-attributed persisted provider final. A blocker label and action do not substitute for an explicit missing-access explanation. Document links must identify the exact task and one revisioned document at the instance origin; wrong origin, task, document, query, credentials or absent evidence fail. Exact call-ID joins, acceptance/termination, ordering, side-effect and budget checks remain enforced.
|
||||
|
||||
After saving the strict native snapshot, each completion cell clicks the rendered final-reply link in the browser. It must open the canonical saved document and show the original content marker in exactly one visible classic document card or document-specific side-panel tab. The navigation check follows the actual configured UI instead of requiring a classic-only element. Retain the navigation receipt and screenshot independently; a valid-looking Markdown URL alone does not qualify navigation.
|
||||
|
||||
Calibrate with correct paraphrases and plausible wrong answers, including the action-only omission. Replay the retained observations only as labeled additional diagnostics; never overwrite or regrade the original results. Provider-free measurement v2 captures eighteen shared runnerd RPC projections plus six direct OpenCode HTTP projections across v4/v5 start, resume and continuation. The latter exercises the concrete OpenCode backend using a local fake server. Both boundaries measure Paperclip-supplied payloads, not provider stock prompts or model cognition. No synthetic receipt qualifies live model behavior.
|
||||
|
||||
The human's paid-run authorization and subsequent request to fix the omission support one bounded corrective confirmation: the six exact `native-instruction-consolidation` candidate cells, one attempt each, no automatic retries, native Codex `gpt-5.6-sol`, ACPX Claude `claude-sonnet-5`, and OpenCode `openrouter/deepseek/deepseek-v4-flash-0731`, original local deadlines, and 1,000-cent company/agent hard stops. Each production correction must have a new immutable source/definition admission before another bounded confirmation. Preserve stopped campaigns, failed cells and canceled-before-provider cells separately. Never reroll an unchanged frozen failure. No new baseline provider run, broader campaign or merge is part of this correction. Stop and inspect a usable behavior failure.
|
||||
|
||||
Report exact source, definition/grader hashes, six outcomes, retained visible replies and document targets, actual run count, cleanup and billing coverage. Passing a single trial does not prove general equivalence, cause, live resume behavior, coding quality, speed or cost trends.
|
||||
|
||||
The receipt-only correction at `be604fcbf81ff238ee38774ff6e644bf0d84eb2d` failed its first Claude document cell; five remaining jobs were canceled before paid execution. The next correction at `3a7349ddc60142283c394121882288f2db07b215` passed both Claude and both Codex cases, but OpenCode again omitted the document link; its blocker job was canceled with the paid step skipped. Preserve both genuine failures and all canceled jobs. Browser navigation was not reached in either missing-link failure.
|
||||
|
||||
OpenCode's inner driver admitted the finishing report locally without invoking the controller feedback handler. Its proxy also discarded the controller response envelope, including the success flag. The correction forwards the exact terminal call ID, thread, turn, tool and structured result through the existing controller request channel. It waits for the controller before committing the inner result, preserves accepted response text and concrete rejection reasons, and rejects malformed acceptance or a turn that ended during feedback. Dynamic-tool unwrapping stays unchanged. A provider-free test crosses the real runnerd, proxy and fake OpenCode boundary: the first completion is rejected, the provider corrects it in the same turn, the second receives the exact final-response feedback, and only one semantic result is proposed.
|
||||
|
||||
This correction changes four additional OpenCode-only production paths. The shared instruction, server receipt/feedback and UI bytes remain identical to 3a7349d. Admit the new source and measure all scripted boundaries again. Confirm only the two affected OpenCode live cases on the new immutable source, one attempt each with the original model, deadlines and budget stops. Keep the four passing Claude/Codex observations explicitly attributed to 3a7349d, backed by the exact provider-path delta and final-head CI. Do not relabel them as live runs on the new head. No new baseline or unchanged-source paid retry is included.
|
||||
|
||||
The e171282 confirmation campaign 37257534042 was stopped during build after fresh review identified a controller/inner-session acceptance race. No paid step started and no provider was executed. Provider-free regression reproduces accepted feedback losing its result when idle/error arrives in flight. Completion settlement now holds terminal mapping, explicit interruption and close until the bound decision and tool result finish, and refuses a concurrent terminal submission before invoking the controller. Raw SSE frames remain retained at receipt; normalized terminal settlement follows the finishing decision. Confirm the same two OpenCode cases only on the newly admitted immutable source; preserve e171282's completed failing review and zero-provider campaign.
|
||||
|
||||
Fresh c3e0cb303 review identified a real serial-input dependency: an interrupt waits for completion while its controller response queues behind the interrupt. No paid campaign was admitted on this source. The proxy now handles only bound response frames outside the serial command queue; commands retain their order and bootstrap failure gates. EOF rejects existing controller waiters and prevents new unanswered requests, then drains the command queue and closes. A real production-bundled proxy, verified local native launcher and fake OpenCode MCP/SSE server reproduce the deadlock before the fix and verify accepted feedback reaches the provider, interruption completes, exactly one result is proposed and EOF exits. A second EOF-without-feedback case proposes no result. This is zero-provider proxy coverage; the real runnerd boundary remains separately covered. Fixture setup and macOS launcher failures are retained, not relabeled as behavioral failures.
|
||||
|
||||
The exact 267aeb3 source passed fresh 5/5 review, but confirmation campaign 37259746983 stopped before provider execution. Its document job entered the harness step, then failed source ancestry hydration before result creation: the fixed eight-commit fetch did not contain the required 2a8a99 base. The remaining blocker cell was canceled. The source was not behaviorally evaluated, and no graded result or cell artifact exists; retain the original failed job log and separate zero-provider receipt.
|
||||
|
||||
Hosted hydration now fetches only the immutable public source at bounded depths 8, 32 and 128. It verifies exact HEAD after every fetch and still requires real base ancestry. A missing or unrelated ancestor, changed HEAD, local checkout or non-shallow checkout does not receive a substitute proof. A real twelve-commit shallow-clone regression fails before the change and passes afterward. Production, fixture prompts, model/default/budget policy and behavioral grading bytes stay unchanged from 267aeb3. Admit the new setup source and confirm the same two OpenCode cases once; no provider run or original failure is retried.
|
||||
@@ -0,0 +1,86 @@
|
||||
# Managed aggregator accounts — acceptance stories
|
||||
|
||||
Date: 2026-10-05
|
||||
|
||||
## Expected behavior
|
||||
|
||||
1. **Discover existing accounts.** After saving an Arcade or Executor gateway, opening Apps discovers the accounts visible through that gateway. No individual app reconnection, task, extra executable connection, or access grant is required.
|
||||
2. **Group accounts.** Notion appears once with its native account, a Composio account, two Arcade accounts, and an Executor account. Identical labels remain distinct by provider, gateway, and upstream account identity. Native Connect/Add account remains the onboarding action.
|
||||
3. **Reconcile upstream changes.** Add or disconnect an upstream account, then use Refresh in only that gateway’s menu. A successful scan updates its accounts; other gateways’ observations remain intact.
|
||||
4. **Manage upstream.** Imported account menus contain only Open in the provider. Links are HTTPS and contain no credentials. Executor workspace/self-hosted paths remain intact. Upstream deletion and per-app authorization stay upstream; Paperclip policies apply to the executable parent gateway.
|
||||
5. **Optional Arcade sync.** A gateway using a project key and Arcade user ID reuses those credentials. Otherwise Set up account sync asks for an optional project key and user ID. Its vault credential is used for discovery only; gateway tool invocation still uses its existing credentials.
|
||||
6. **Honest discovery states.** Unsupported inventory, expired sign-in, partial pages, and stale observations never become successful empty inventories or green checks. Failed scans retain previous accounts and invite refresh/setup.
|
||||
7. **Isolation and rotation.** Account observations are scoped to company, saved gateway, viewing human, and credential version. Rotating a credential or changing the session endpoint hides prior observations until a successful scan. Account discovery cannot grant access.
|
||||
8. **Navigate five chips.** Paperclip defaults to native discovery with all installed apps above it. Composio/Arcade scope discovery while retaining all connected account lines on grouped cards. Native cards appear under a provider only if that provider has observed accounts for the app, not merely because its catalog supports the app. Installed shows only installed apps; All includes every source. Executor's chip is temporarily hidden; its gateway and observed accounts remain available. A Paperclip search expands to All and clearing returns to Paperclip; explicit filters stay scoped. Pagination affects discovery only.
|
||||
9. **Unknown integrations and small screens.** Unknown Executor integrations receive a separate card without guessed branding. Empty results remain actionable. Chips wrap and account rows remain readable at mobile widths.
|
||||
10. **Inspect a saved gateway.** Composio setup opens its Permissions page. Opening that page starts one refresh, displays progress, and shows detected apps with their logos in a bounded, keyboard-scrollable list. Refresh Composio checks only this saved gateway. Partial failures retain observations as unverified. Account discovery does not change human/agent permissions. The redundant OAuth scope guidance and “Reconnect to update permissions” button are removed; actual authorization-repair controls remain available.
|
||||
|
||||
## Reproduction
|
||||
|
||||
Run this checkout’s Storybook on port 6200 and the disposable acceptance server:
|
||||
|
||||
```sh
|
||||
node cli/node_modules/tsx/dist/cli.mjs tests/aggregator-accounts/test-drive.ts
|
||||
```
|
||||
|
||||
Open `http://localhost:6200/iframe.html?id=apps-managed-accounts--full-stack-test-drive&viewMode=story`.
|
||||
|
||||
This mounts the production Apps page against production tool-access routes and a disposable PostgreSQL database. Only external provider responses are simulated. The upstream controls change fixture inventory; use the gateway menu to refresh and observe the result. Existing app data and other local services are untouched.
|
||||
|
||||
The catalog-only stories remain at `Apps/Catalog source selection` and demonstrate the five current chips, pagination, native onboarding precedence, and grouped accounts.
|
||||
|
||||
## Evidence and limitations
|
||||
|
||||
Verified in the embedded browser on 2026-10-05 against the production Apps component, production routes, and disposable PostgreSQL. External Composio, Arcade, and Executor responses were simulated.
|
||||
|
||||
| Story | Result and evidence |
|
||||
| --- | --- |
|
||||
| Existing accounts | Passed: saved gateways imported existing accounts without app reconnection. Gateway onboarding tests confirm Arcade returns to Apps without an agent task. |
|
||||
| Grouping | Passed: one Notion card showed a native account, a Composio account, two accounts from each of two Arcade gateways, and an Executor account. Repeated “Work” labels remained separate. |
|
||||
| Upstream changes | Passed: adding and disconnecting simulated Arcade accounts followed by its gateway’s Refresh updated only that gateway’s account rows. Other providers and the native account remained. |
|
||||
| Upstream management | Passed: imported kebabs contained only Open in the provider. Executor’s link retained its workspace path. Browser verification inspected destinations; it did not complete external provider sign-in. |
|
||||
| Optional Arcade sync | Passed: the browser saved a synthetic discovery key through the real manager-only endpoint and imported the second gateway’s accounts. Database tests also prove existing API-key/user-header reuse and discovery-only vault use. |
|
||||
| Failure states | Passed: expired authorization and an incomplete page retained last-known accounts without green checks. Unsupported Executor inventory showed unavailable, retained stale accounts, and recovered after refresh. |
|
||||
| Isolation and rotation | Passed: service tests cover viewing-user/gateway/company isolation, member/agent configuration denial, endpoint changes, and an actual vault credential rotation followed by discovery using the new value. UI/API-client tests prove user switches do not reuse another user’s account cache or pending HTTP response. Manager account responses also use private, no-store HTTP caching. |
|
||||
| Navigation | Passed: all six chips, Paperclip-to-All search expansion, explicit source scope, clear search, installed pinning, pagination, and empty states. The catalog Storybook interaction also completed without console errors. |
|
||||
| Unknown apps and mobile | Passed: an unknown Executor integration retained a separate card and unverified status. At 390px the production page’s content width was 382px, with wrapped chips and readable account rows. The viewport override was reset afterwards. |
|
||||
|
||||
The initial fixture run ended on the full-stack story. Its upstream controls are explicitly labeled simulated. That run did not exercise native OAuth or live provider dashboards; working Arcade/Executor credentials were unavailable.
|
||||
|
||||
### Permissions-page follow-up: real Paperclip test drive
|
||||
|
||||
The actual CLI `test-drive` server is running from this checkout at
|
||||
`http://localhost:3105`, with an isolated PostgreSQL database and the agent key
|
||||
from `~/.secrets`. The user subsequently connected a real Composio gateway.
|
||||
The embedded browser verified its Permissions page at
|
||||
`/AGG/apps/f878eeb0-b1b9-4081-b779-ffecf3b41e7a/permissions`:
|
||||
|
||||
- Detected Airtable and Circleback from the live saved Composio session.
|
||||
- Manual Refresh Composio displayed “Refreshing…” and catalog-check progress,
|
||||
then completed with both apps visible.
|
||||
- Navigating away and returning automatically started a refresh without a click.
|
||||
- The redundant sign-in guidance and update-permissions button are absent.
|
||||
- The catalog exposes Paperclip, Composio, Arcade, Installed, and All.
|
||||
- 317 focused AppDetail, AppsConnect, and Browse tests pass, including setup
|
||||
navigation, first-load refresh, progress/completion, upstream reconciliation,
|
||||
retained observations after failure, unsupported discovery, and manager-only
|
||||
visibility. UI typecheck, UI production build, and token gates pass.
|
||||
|
||||
This walkthrough did not repeat OAuth sign-in or change upstream accounts. The
|
||||
post-setup redirect is covered by connection-flow tests. The real gateway had two
|
||||
observed apps, so overflow with a long real account inventory was not exercised.
|
||||
The list has a bounded height and keyboard-focusable vertical scroll container.
|
||||
Refresh and settled-page screenshots are saved as `composio-apps-refreshing.png`
|
||||
and `composio-permissions-connected-apps.png` in this task's visualization folder.
|
||||
|
||||
### Automated checks
|
||||
|
||||
- 261 focused UI/provider/API-client tests passed: Browse, AppsConnect, inventory adapters, and HTTP request isolation.
|
||||
- 8 database/service/authorization tests passed, with no skips in the final run. These reused the disposable browser-test database because the host had exhausted its 32 PostgreSQL shared-memory slots. The optional `PAPERCLIP_AGGREGATOR_TEST_DATABASE_URL` test override accepts only a local, disposable database.
|
||||
- 9 shared catalog tests passed.
|
||||
- Final `pnpm -r typecheck`, `pnpm build`, token gates, and `git diff --check` passed.
|
||||
- `pnpm test:run` was attempted but did not complete successfully. It encountered a timeout in `workspace-git-snapshot-streaming.test.ts`, failures in `heartbeat-run-event-sequencing.test.ts`, and numerous PostgreSQL startup skips; the run ended with exit 130. A separate remote-MCP compatibility run also could not start PostgreSQL. These repository-wide checks are not reported as green.
|
||||
|
||||
Local verification logs: `/tmp/paperclip-aggregator-focused-final.log`, `/tmp/paperclip-aggregator-db-final.log`, `/tmp/paperclip-full-typecheck-final.log`, `/tmp/paperclip-full-build-final.log`, `/tmp/paperclip-token-gates-final.log`, and `/tmp/paperclip-full-test.log`.
|
||||
|
||||
Desktop and mobile screenshots are saved in the task’s visualization workspace as `managed-aggregator-accounts.png` and `managed-aggregator-accounts-mobile.png`. This is fixture-backed acceptance evidence, not live provider certification or a production release sign-off.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,382 @@
|
||||
# Native hiring and dependency guidance
|
||||
|
||||
Status: **ship measurement and eval coverage only. Do not ship the procedure
|
||||
relocation or the unqualified runtime repairs.**
|
||||
|
||||
The final PR keeps production agent behavior byte-identical to master
|
||||
`a65ca0950834a85bb93bcc4b4042ecacdebfef53`. Its changes are limited to the
|
||||
measurement test, explicit eval selection, correct hiring credential selection,
|
||||
company/lead budget stops, test-runner configuration, and retained reports.
|
||||
No production prompt, tool description, tool schema, session catalog, dependency
|
||||
mutation or status-transition code changes are included in the final diff.
|
||||
|
||||
The experiment did not pass the no-regression gate. The first corrected
|
||||
comparison had baseline 5 PASS / 1 FAIL and candidate 3 PASS / 3 FAIL: two new
|
||||
failures, no new passes, three unchanged passes and one unchanged failure.
|
||||
The attempted repair also had two new failures among five comparable pairs.
|
||||
Moving text out of the fixed prompt did not justify shipping worse outcomes.
|
||||
Single trials do not establish that the changed instructions caused a failure.
|
||||
|
||||
The original candidate reduced the complete standing projection by 460 bytes,
|
||||
about 0.9%. Restoring important instructions reduced the saving to 125 bytes,
|
||||
about 0.25%. A later, unqualified dependency guard made the projection 127 bytes
|
||||
larger than baseline. None of those changes is included in the final production
|
||||
tree. The final measurement records the unchanged full catalog. Bytes are not
|
||||
tokens, billing, or proof of upstream loading or truncation.
|
||||
|
||||
The [original sanitized comparison](2026-10-05-native-procedure-comparison.json)
|
||||
and [readiness comparison v4](2026-10-05-native-procedure-readiness-v4.json)
|
||||
retain exact sources, original grades and evidence hashes. Their old source
|
||||
revisions remain historical experiments; they do not describe production changes
|
||||
in this measurement-only PR. No completed behavioral failure was rerolled to
|
||||
obtain a pass. Earlier infrastructure recovery and missing evidence remain
|
||||
explicit below. This follows the merged completion slice in #15151.
|
||||
|
||||
## Final scope and verification
|
||||
|
||||
The server measurement test instantiates the actual standard-mode tool authority
|
||||
with all advertised tools and input schemas. It captures scripted native start,
|
||||
resume and continuation payloads across Codex, ACPX Claude and OpenCode and the
|
||||
OpenCode MCP declaration list. These are provider-free measurements.
|
||||
|
||||
The two original Everyday Workflows stories remain explicit-only and keep their
|
||||
independent artifact and ordering oracles. OpenCode is added only to the local
|
||||
hiring/reuse and delegation/feedback cells. These stories use one attempt per
|
||||
cell and actual company and lead-agent budget stops. Managed hiring selects
|
||||
the credential provider from the chosen profile, including OpenRouter. Existing
|
||||
Node test files run under Node's test runner instead of being misclassified as
|
||||
empty Vitest suites.
|
||||
|
||||
The final production identity check compares `packages/` and production
|
||||
`server/` files against the fixed master context. A fresh CI and review must
|
||||
qualify the final tree. The full local database suite is unavailable while this
|
||||
host's PostgreSQL shared-memory slots are exhausted; hosted CI provides those
|
||||
database checks. No new paid campaign is needed to compare unchanged production
|
||||
bytes. This does not claim that the existing product is free of the failures
|
||||
observed in the retained trials.
|
||||
|
||||
## Unqualified runtime repairs retained for follow-up
|
||||
|
||||
The local branch `codex/native-procedure-controller-repairs-unqualified` at
|
||||
`7e270a3d3` preserves the attempted controller repairs. It is not part of this
|
||||
PR's final diff and has not passed live qualification. It scopes completion
|
||||
wakes to each committed transition and rejects silent removal of unfinished
|
||||
dependencies, with explicit handling for obsolete dependencies. Its regression
|
||||
tests still require database execution. Source typecheck, catalog generation,
|
||||
and the non-database measurement/session checks passed locally.
|
||||
|
||||
The remaining issues need separate bounded investigation:
|
||||
|
||||
- Reopened child completion can reuse a previously consumed parent wake key.
|
||||
- A parent can discard a still-running required dependency and finish early.
|
||||
- OpenCode's v4 hiring candidate failed the exact-credential persistence guard
|
||||
in its local SQLite database. Cleanup removed that database. The credential
|
||||
identity and exact persistence mechanism are not retained. No cause or fix
|
||||
is claimed, and no credential check is weakened.
|
||||
|
||||
## Readiness comparison v4 — historical failed experiment
|
||||
|
||||
Candidate `aba7ec219b588de02cf323b4ebca559a105e6d01` and baseline
|
||||
`ce31f5efd0d416205c3994c55dd02b645f8b8423` use common master
|
||||
`a65ca0950834a85bb93bcc4b4042ecacdebfef53`. Candidate
|
||||
[campaign 37348723829](https://github.com/paperclipai/paperclip/actions/runs/37348723829)
|
||||
and baseline [campaign 37348764875](https://github.com/paperclipai/paperclip/actions/runs/37348764875)
|
||||
use trusted workflow `59015846ae02f935411afc620e0867ce812378fb`, distinct
|
||||
from both measured trees. Eleven result/API/ledger sets and 99 evidence hashes
|
||||
are retained. Their final ledgers contain 56 started runs, in addition to the
|
||||
earlier cohorts. All eleven cleanup results passed.
|
||||
|
||||
Candidate: 3 PASS / 3 FAIL. Baseline: 3 PASS / 2 FAIL / 1 setup cell without a
|
||||
behavioral grade. Comparable pairs: two new failures, two new passes and one
|
||||
unchanged pass. The remaining Codex delegation pair is uncomparable because
|
||||
baseline Docker setup timed out before its paid step; no behavioral grade or
|
||||
final result artifact exists for that cell.
|
||||
|
||||
| Story and harness | Baseline | Candidate | Observed missing behavior |
|
||||
|---|---|---|---|
|
||||
| Codex hiring/reuse | PASS | PASS | None observed by the original oracle |
|
||||
| Codex delegation/feedback | No behavioral grade | FAIL | Parent remains blocked after the revised child finishes |
|
||||
| Claude hiring/reuse | FAIL | PASS | Baseline did not deliver the revised ZIP |
|
||||
| Claude delegation/feedback | PASS | FAIL | Candidate finishes the parent before the child revision |
|
||||
| OpenCode hiring/reuse | PASS | FAIL | Candidate credential-persistence guard fails |
|
||||
| OpenCode delegation/feedback | FAIL | PASS | Baseline finishes the parent before the child revision |
|
||||
|
||||
The two new passes do not offset the new failures. The `aba7ec2` CI passed on
|
||||
attempt 2 after five infrastructure-interrupted jobs were recovered and its
|
||||
fresh review was 5/5; source checks did not clear these behavioral failures.
|
||||
|
||||
## Historical experiment scope
|
||||
|
||||
The original paid comparison used master
|
||||
`a386a599983519eb1d399f8b770bfccdb2a74762` as common context. It moved
|
||||
hiring/reuse and assigned-worker guidance to `hire_agent` and `create_task`,
|
||||
kept dependency discovery in the fixed prompt, and returned waiting instructions
|
||||
from `set_dependencies`. It advanced prompt and session-catalog revisions.
|
||||
Those production changes have been removed from the final PR. Connection
|
||||
procedures and legacy skill/API instructions remain unchanged.
|
||||
|
||||
## Measurement boundary
|
||||
|
||||
Capture the actual standard-mode server tool authority with API tools enabled,
|
||||
a local workspace, and no assigned external apps. Measure fixed instructions,
|
||||
all advertised tool descriptions and input schemas, and the serialized task
|
||||
input together. Scripted native start/resume/continuation checks cover Codex,
|
||||
ACPX Claude and OpenCode; authenticated MCP tools/list checks the OpenCode
|
||||
bridge separately. Export source identity, file hashes, bytes and tool counts.
|
||||
|
||||
These receipts have zero provider calls. Bytes are not model tokens or invoice
|
||||
costs. This boundary cannot establish the upstream SDK's private instructions,
|
||||
lazy loading, truncation or cognitive use of returned guidance. The previous
|
||||
completion-only capture selected a partial catalog and is not a full-tool
|
||||
baseline. Preserve its historical evidence unchanged.
|
||||
|
||||
## Historical matched live comparison plan
|
||||
|
||||
Use the existing explicit-only `everyday-workflows` suite with original
|
||||
`hire-reuse` and `delegate-feedback` requests and independent artifact oracles.
|
||||
Select only native Codex `gpt-5.6-sol`, ACPX Claude `claude-sonnet-5`, and OpenCode
|
||||
`openrouter/deepseek/deepseek-v4-flash-0731`, in local environments.
|
||||
|
||||
Freeze common measurement, harness and test-runner repairs in both branches
|
||||
before the candidate production changes. The historical branch keeps the
|
||||
current production prompt and tool contracts. Record exact measured source
|
||||
SHAs separately from the trusted master workflow definition and selected
|
||||
runtime/artifact hashes. Never label a later source revision as the measured
|
||||
revision without showing its delta.
|
||||
|
||||
The plan has six cells per variant, one attempt per cell, a 12-minute cell
|
||||
deadline, at most 12 story run records per cell, and a 1,000-cent company and
|
||||
lead-agent hard stop per cell. The original campaign applied the company cap
|
||||
but mistakenly left the lead budget at 0; the corrected common setup verifies
|
||||
both actual API payloads. All worker runs count toward the company budget;
|
||||
individual worker caps are not asserted.
|
||||
Count every actual run, including notifications and failed attempts. No broad
|
||||
matrix, automatic retries or baseline rerolls are part of this comparison.
|
||||
|
||||
Inspect original grades and retained content for persistent teammate identity,
|
||||
reuse on revision, worker ownership, independently tested delivered artifacts,
|
||||
parent review, dependency release, and final ordering. Check per-pair changes;
|
||||
equal totals are insufficient. Preserve every original failure and missing
|
||||
evidence. Model-authored tests cannot replace the existing independent oracle.
|
||||
|
||||
These two stories do not qualify arbitrary live resume, every existing-blocker
|
||||
combination, general coding quality, or cost/speed trends. Explicitly report
|
||||
missing evidence. Do not loosen the original oracle to create a pass.
|
||||
|
||||
## Historical complete-catalog measurement
|
||||
|
||||
The corrected source `ffe847fabaebbf24bc93924a4b6c8922d46a1fb6` is compared with
|
||||
baseline `aecfa3b6a5cab110a9b926e93705311bbcf4db47`. Both include the same
|
||||
measurement fixture and common eval repairs. Exactly 12 source/generated/unit
|
||||
paths differ; 256 common fixture files have identical bytes.
|
||||
|
||||
| Normalized extracted component | Baseline bytes | Corrected candidate bytes | Change |
|
||||
|---|---:|---:|---:|
|
||||
| Fixed instructions, start/resume | 1,809 | 1,256 | −553 |
|
||||
| All 39 tool declarations including schemas | 44,944 | 45,037 | +93 |
|
||||
| Task input, start/resume | 1,993 | 1,993 | 0 |
|
||||
| Combined standing projection, start/resume | 48,781 | 48,321 | −460 |
|
||||
| OpenCode MCP tools/list declarations | 44,249 | 44,342 | +93 |
|
||||
|
||||
Scripted Codex, ACPX Claude and OpenCode deliveries give the same comparative
|
||||
change. The serialization of the extracted components is measured separately
|
||||
from its path-normalized comparison. The fixture's real temporary paths add
|
||||
168 bytes to the combined start/resume projection in each variant. Neither
|
||||
projection is the entire transport request. Returned dependency guidance adds
|
||||
input when used, so this is not an overall token, latency or cost saving claim.
|
||||
The measurement produces zero provider calls and does not prove upstream
|
||||
loading or truncation behavior.
|
||||
|
||||
## Original comparison — failures retained
|
||||
|
||||
Original sources were candidate `eb01d073c35134bf0d373947b55a79f9e443f8ee` and
|
||||
baseline `64cd2194a1d058356679b653c181f16404aefc80`, using trusted master workflow
|
||||
`a386a599983519eb1d399f8b770bfccdb2a74762`. Source identity remains distinct from
|
||||
workflow identity and the later measurement-only correction.
|
||||
|
||||
- [Original candidate campaign](https://github.com/paperclipai/paperclip/actions/runs/37325777217)
|
||||
- [Original baseline campaign](https://github.com/paperclipai/paperclip/actions/runs/37325870695)
|
||||
|
||||
| Profile | Hire/reuse baseline → candidate | Delegate/feedback baseline → candidate |
|
||||
|---|---|---|
|
||||
| Codex | PASS → PASS | PASS → FAIL |
|
||||
| ACPX Claude | PASS → FAIL | PASS → FAIL |
|
||||
| OpenCode | FAIL → FAIL | FAIL → PASS |
|
||||
|
||||
There are three new overall failures, one new pass, one unchanged pass and one
|
||||
unchanged failure. The candidate did not qualify. Final ledgers match the
|
||||
result run-ID sets: 25 started baseline runs and 15 candidate runs, zero retry
|
||||
links. All twelve cell cleanup receipts passed. Missing/zero billing amounts
|
||||
and unmetered host runtime prevent any claim about actual cost savings.
|
||||
|
||||
The candidate Claude hire created Morgan and a child task, then ended with a
|
||||
waiting message without recording dependencies or using a terminal tool. The
|
||||
lead failed with `native_finalization_missing`. The corrected v7 prompt keeps
|
||||
explicit `set_dependencies` and `paperclip_block` discovery. This is a concrete
|
||||
missing behavior; a single trial does not establish causation.
|
||||
|
||||
The candidate Codex delegate failed in Riley's worker turn with the provider
|
||||
error `Selected model is at capacity` / `serverOverloaded`. Its parent had
|
||||
received the dependency guidance. Candidate Claude delegation failed opening
|
||||
an ACPX session (`session_ensure_failed`); its underlying cause is unknown.
|
||||
Both original grades and classifiers remain unchanged.
|
||||
|
||||
Both OpenCode hire cells failed common setup requesting an OpenAI credential
|
||||
although OpenRouter was selected, before any agent run. Shared setup now uses
|
||||
the profile's actual credential. A separate nonce/cell-ID bug left original
|
||||
lead budgets at 0; six nonce-based setup tests now verify company and lead caps
|
||||
using only the selected provider credential. These common control changes are
|
||||
why the corrected campaign uses a newly matched baseline.
|
||||
|
||||
The baseline OpenCode delegation delivered an artifact passing the independent
|
||||
oracle but completed the parent before the child's final revision. Candidate
|
||||
passed both the artifact and ordering checks. Correct output alone did not
|
||||
qualify that baseline outcome.
|
||||
|
||||
## Corrected comparison
|
||||
|
||||
- [Candidate ffe847f](https://github.com/paperclipai/paperclip/actions/runs/37331580879)
|
||||
- [Baseline aecfa3b](https://github.com/paperclipai/paperclip/actions/runs/37331642168)
|
||||
- [Candidate recovery for two interrupted cells, same ffe847f source](https://github.com/paperclipai/paperclip/actions/runs/37334061104)
|
||||
|
||||
These use the same corrected fixtures, original requests and artifact graders,
|
||||
with one attempt per campaign cell. The original campaign is preserved and is
|
||||
not relabeled as this revision. The common suite hash is
|
||||
`acd17b4acbd7051070531f7d7587131139900f54245b992eb7a62faacda8e334`.
|
||||
|
||||
| Profile | Hire/reuse baseline → candidate | Delegate/feedback baseline → candidate |
|
||||
|---|---|---|
|
||||
| Codex | PASS → PASS | PASS → FAIL (recovery) |
|
||||
| ACPX Claude | PASS → PASS | PASS → PASS (recovery) |
|
||||
| OpenCode | PASS → FAIL | FAIL → FAIL |
|
||||
|
||||
There are **two new failures, zero new passes, three unchanged passes, one
|
||||
unchanged failure and zero pending pairs**. Workflow completion is not the
|
||||
behavioral grade. These are the original result grades; no regrading was done.
|
||||
|
||||
### Missing and recovered behavior
|
||||
|
||||
**Codex delegation regressed in the observed pair.** The first parent turn
|
||||
recorded its dependency and blocked correctly. After feedback, the parent
|
||||
reviewed a stale ZIP and requested a replacement. An API comment operation was
|
||||
rejected with `API comments cannot change runner execution state`. The child
|
||||
finished the replacement at 15:45:48.284 UTC. The parent then recorded the
|
||||
dependency at 15:45:51.801–15:45:59.295 UTC; its receipt scheduled no wake. The
|
||||
parent blocked and finalized at 15:46:19.909 UTC. The child was Done, but the
|
||||
parent remained Blocked with no active continuation. All four native runs had
|
||||
successful run status; that does not make the task outcome successful. This
|
||||
sequence does not by itself establish a prompt, race or platform root cause.
|
||||
|
||||
**OpenCode hiring regressed in the observed pair.** It hired the expected
|
||||
teammate and produced an initial artifact that passed the independent checks.
|
||||
It reused the same teammate for the separator revision, then requested further
|
||||
CLI argument-handling changes. After nine started runs, the story reached the
|
||||
12-minute deadline with parent and revision work still in progress. Cleanup
|
||||
cancelled the remaining work. The final revised-ZIP and settled-outcome checks
|
||||
were not completed; they are not assumed to pass. The baseline passed after
|
||||
four runs. This single trial does not establish a speed or cost trend.
|
||||
|
||||
**OpenCode delegation failed in both variants.** The artifacts passed the
|
||||
independent output checks, but the parents finished before the final child
|
||||
revision. Candidate parent/child completion times were 15:33:43.628 /
|
||||
15:35:03.222 UTC; baseline times were 15:35:21.429 / 15:36:03.043 UTC. Both
|
||||
parents lacked review of that final revision. Equal FAIL totals conceal neither
|
||||
this missing behavior nor the passing artifact checks.
|
||||
|
||||
**Claude hiring now passes all 45 original checks.** Retained semantic receipts
|
||||
show dependency recording, returned guidance and an accepted current-track
|
||||
block with the child agent as owner. The parent later resumed and finished.
|
||||
This repairs the original missing dependency/finalization observation in this
|
||||
trial. It does not prove cognitive use of the guidance or every hiring tool
|
||||
path; API hiring remained available.
|
||||
|
||||
### Interruptions and accounting
|
||||
|
||||
Candidate Codex and Claude delegation jobs received runner shutdown signals at
|
||||
15:27:38 UTC and were cancelled at 15:27:50 UTC. Jobs `111838521694` and
|
||||
`111838521502` retained no result, final ledger or cleanup receipt. Their
|
||||
behavioral grades and total actual runs/charges are unknown. Eight distinct
|
||||
run-record IDs were visible in partial logs; this is not a complete started-run
|
||||
ledger. Preserve the workflow failures and missing evidence.
|
||||
|
||||
Only those two missing candidate cells received one bounded recovery each, on
|
||||
the identical measured source. No completed baseline or other candidate cell
|
||||
was repeated. The original workflow revision was `a386a599`; recovery used
|
||||
`a65ca0950834a85bb93bcc4b4042ecacdebfef53` after master advanced. Workflow file
|
||||
bytes were identical, SHA-256
|
||||
`eb570267e66043c1196338bb386b07ab8e8e8306ba04b6223f13b73b04fdc8f4`.
|
||||
The measured source stayed `ffe847f`, distinct from both workflow revisions.
|
||||
|
||||
All twelve completed/recovered cells have matching result/final-ledger run
|
||||
sets and passing cleanup receipts. They contain 27 baseline and 33 candidate
|
||||
started runs. The original campaign contains 40 more: **100 retained started
|
||||
runs, plus incompletely known activity from the two interrupted jobs**. Zero
|
||||
retry links within the completed ledgers do not erase the two infrastructure
|
||||
recoveries. The audits retain 100 original and 108 corrected JSON evidence
|
||||
hashes. Actual charges remain unknown because usage and interruption records
|
||||
are incomplete and host runtime is unmetered. No free-call or savings claim is
|
||||
made.
|
||||
|
||||
ACPX provider execution IDs are not proven mapped to host call IDs. No strict
|
||||
no-extra-work claim or feedback-consumption inference is made from matching
|
||||
names, ordering or counts. These evidence limits remain even for a passing
|
||||
original story grade.
|
||||
|
||||
## Historical integration and repository checks
|
||||
|
||||
After the paid sources were frozen, the PR merged master
|
||||
`a65ca0950834a85bb93bcc4b4042ecacdebfef53` as
|
||||
`ee096aa23fc88a001e7666215bcd338b5863f377`. Three generated contract conflicts
|
||||
were resolved by normal generation from the combined sources. The paired
|
||||
provider-free baseline is `ce31f5efd0d416205c3994c55dd02b645f8b8423`.
|
||||
Their twelve intended differing paths are identical to the paid source delta;
|
||||
all 256 common fixture files remain byte-identical. **No live campaign measured
|
||||
the integrated head.** Later documentation-only commits do not change that.
|
||||
|
||||
The integrated full-catalog projection is 49,200 → 48,740 normalized bytes at
|
||||
start/resume and 47,422 → 46,962 during continuation. The 460-byte reduction is
|
||||
unchanged. Master's project-discovery schema adds 419 bytes to both tool sets.
|
||||
The actual extracted start/resume projection is 49,368 → 48,908 bytes. All 39
|
||||
declarations remain present; this still does not measure private vendor prompts,
|
||||
model tokens, lazy loading or upstream truncation limits.
|
||||
|
||||
Before integration, 74 focused tool/session/measurement checks, 1,252 eval-support
|
||||
Vitest checks, 128 Node checks and the eval TypeScript check passed. Repository
|
||||
typecheck and build passed before the prompt-only v7 correction. The corrected
|
||||
source CI then passed typecheck, build, tests and all eight browser shards. An
|
||||
interrupted canary job was retried once; that is separate from paid eval recovery.
|
||||
|
||||
At integrated `ee096aa23`, local measurement/session checks passed 48 tests;
|
||||
23 authority tests could not start because PostgreSQL initialization failed,
|
||||
and 21 project-tool tests were skipped by the unsupported database helper.
|
||||
The integrated baseline passed six measurement tests. The older full local
|
||||
suite was stopped after source revisions changed during its run. Its failures
|
||||
remain retained; it is not reported as green.
|
||||
|
||||
[Integrated CI server shard 5](https://github.com/paperclipai/paperclip/actions/runs/37335450901/job/111849219339)
|
||||
failed `workspace-git-snapshot-streaming.test.ts`: the four Git filename lanes
|
||||
over 32 MiB exceeded the 180-second timeout (707 tests passed, two skipped,
|
||||
one failed in that shard). The aggregate verify job therefore failed. The cause
|
||||
has not been established; this is not reclassified as an infrastructure pass.
|
||||
The [review summary](https://github.com/paperclipai/paperclip/pull/15218#issuecomment-5996723170)
|
||||
gave integrated `ee096aa23` 5/5 but noted project-discovery authorization work
|
||||
for actors with sparse visibility. Both inline measurement findings are
|
||||
resolved; the summary's separate concern is not erased by that count.
|
||||
|
||||
At this historical checkpoint the failed behavior comparison required keeping
|
||||
the candidate draft. The final scope separates measurement from the rejected
|
||||
reduction. Future behavior changes need their own diagnosis and qualification;
|
||||
completed paid failures must not be rerolled simply to obtain passing grades.
|
||||
|
||||
## Historical behavior-change gates (not met; change removed)
|
||||
|
||||
- [x] Freeze common fixtures and both source revisions.
|
||||
- [x] Export comparable full-catalog delivery projections.
|
||||
- [x] Pass 74 focused checks, 1,252 eval-support Vitest checks and 128 Node checks.
|
||||
- [ ] Complete repository typecheck, tests and build.
|
||||
- [x] Run and inspect the bounded matched campaign, including original failures.
|
||||
- [ ] Meet the no-regression gate: **failed, two newly failing corrected pairs**.
|
||||
- [ ] Verify current-head CI, review and conflicts before readiness.
|
||||
- [x] Preserve original and corrected grades, hashes and incomplete accounting.
|
||||
|
||||
No merge is authorized.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -52,6 +52,13 @@ and incomplete result-delivery command IDs and statuses. If execution and
|
||||
cleanup both fail, execution retains its original error identity and cleanup is
|
||||
attached as `cleanupError`.
|
||||
|
||||
Semantic settlement also includes up to 20 content-free failure records, with
|
||||
the call ID, operation ID, stage (`dispatch` or `persist_result`), and cause.
|
||||
Causes distinguish an oversized command, a full command journal, known storage
|
||||
errors, dispatcher rejection, and other persistence failures. Exception messages
|
||||
and tool results are excluded. These diagnostics do not authorize replay of an
|
||||
operation whose outcome is unknown.
|
||||
|
||||
Instruction writes also commit an `agent.instruction_write_attempted` activity
|
||||
row and a run-scoped `instructionToolAttempts` entry before permitting the
|
||||
filesystem effect. They retain the call ID, operation ID, and input digest, not
|
||||
@@ -320,6 +327,17 @@ the enclosing workspace task. The original error and restore safety policy are
|
||||
unchanged. These lines stay in the instance run log and its configured durable
|
||||
storage, and are not new first-party telemetry events.
|
||||
|
||||
The optional `step` identifies the failed restore operation. For
|
||||
`phase=workspace` and `step=git_integration`, `gitCommand` identifies one fixed
|
||||
command family (`rev_parse`, `symbolic_ref`, `merge_base`, `merge_tree`,
|
||||
`commit_tree`, `update_ref`, or `log`). `gitFailureKind` is `merge_conflict`,
|
||||
`invalid_object`, `ref_conflict`, `permission_denied`, or `unknown`; it is a
|
||||
bounded diagnostic clue, not a new recovery or retry decision. Only supported
|
||||
exit/OS codes and recognized Git messages produce a specific classification.
|
||||
No command arguments, stderr, filenames, repository URLs, or ref names are saved.
|
||||
The same closed fields persist in `workspaceRestoreDiagnostic` and are
|
||||
revalidated before projection into an enabled Sentry failure report.
|
||||
|
||||
## Codex resume usage snapshot
|
||||
|
||||
The native runner retains a bounded local `harness.diagnostic` event with code
|
||||
|
||||
@@ -4,6 +4,27 @@ The native Runner exposes `create_skill` in Auto and skill-test work. An agent
|
||||
can save a reusable, single-file skill directly in the company's skill library.
|
||||
The tool is unavailable in Ask and pre-acceptance Plan modes.
|
||||
|
||||
The native Runner also exposes `update_skill` in the same modes. Read the
|
||||
existing skill and its `currentVersionId` through the company skill API before
|
||||
editing. Supply the **complete** primary file, not a fragment:
|
||||
|
||||
```json
|
||||
{
|
||||
"skillId": "<existing skill UUID>",
|
||||
"expectedVersionId": "<current version UUID>",
|
||||
"markdown": "---\nname: release-review\ndescription: Review release notes.\n---\n\n# Review\nCheck each release note and its tests.\n",
|
||||
"idempotencyKey": "review-update-1"
|
||||
}
|
||||
```
|
||||
|
||||
The tool uses the existing Skill Studio file endpoint and company `skills.edit`
|
||||
policy. It returns a skill ID, path, resulting version ID, and Skill Studio
|
||||
path. Retry a lost response with **exactly** the same key and inputs; that
|
||||
returns the original receipt without another version or activity record.
|
||||
Reusing the key with different inputs, or updating after the version changes,
|
||||
returns a conflict. After a version conflict, reread the skill and choose a
|
||||
new key for the revised edit. Policy is checked again even on exact retries.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "release-review",
|
||||
|
||||
@@ -28,7 +28,7 @@ COPY cli/package.json ./cli/package.json
|
||||
# The complete resolved lock (including transitive integrity hashes) is reviewed.
|
||||
# Reject registry-time drift BEFORE installing packages or running lifecycle code.
|
||||
# Refresh this digest together with source/provider dependency changes.
|
||||
ARG PAPERCLIP_RUNNER_LOCK_SHA256=2c46a68811ba1d504a41b6f4d3f642bc93f4a1c615097754c9c6486881dba8e6
|
||||
ARG PAPERCLIP_RUNNER_LOCK_SHA256=e1856797158f7d0bdb728b9d6c6dda3d65ad5f7896e68216883174de508431a7
|
||||
RUN printf '%s pnpm-lock.yaml\n' "${PAPERCLIP_RUNNER_LOCK_SHA256}" > /tmp/provider-lock.sha256 \
|
||||
&& sha256sum -c /tmp/provider-lock.sha256 \
|
||||
&& pnpm install --frozen-lockfile --filter '@paperclipai/paperclip-runner...'
|
||||
@@ -56,9 +56,9 @@ ENV PATH=${PAPERCLIP_RUNNER_PROVIDER_PACK_ROOT}/node_modules/.bin:/usr/local/sha
|
||||
# Never add a second CLI version to work around a runner compatibility pin.
|
||||
RUN npm uninstall -g @anthropic-ai/claude-code @openai/codex opencode-ai \
|
||||
&& npm install -g \
|
||||
@xai-official/grok@1.0.41 \
|
||||
@google/gemini-cli@0.60.0 \
|
||||
@moonshot-ai/kimi-code@2.0.2 \
|
||||
@xai-official/grok@1.0.46 \
|
||||
@google/gemini-cli@0.62.0 \
|
||||
@moonshot-ai/kimi-code@2.1.1 \
|
||||
&& npm cache clean --force
|
||||
|
||||
# Hermes requires Python >=3.11,<3.14; the image's default Python is newer.
|
||||
@@ -69,8 +69,8 @@ RUN /usr/bin/python3 -m venv /opt/hermes \
|
||||
&& /opt/hermes/bin/pip install --no-cache-dir hermes-agent==0.19.0 \
|
||||
&& ln -sf /opt/hermes/bin/hermes /usr/local/bin/hermes
|
||||
|
||||
ARG CURSOR_VERSION=2026.09.18-9a7762b
|
||||
ARG CURSOR_SHA256_AMD64=b1308f5a2fc05458b9d8966752986bb23a971bbcc67c842c1df94c4b8132bad9
|
||||
ARG CURSOR_VERSION=2026.10.01-e373342
|
||||
ARG CURSOR_SHA256_AMD64=a79726c6e644520e993970be4c45775a6889802b67abe461a677a53219ae28e8
|
||||
RUN set -eu; \
|
||||
arch="$(dpkg --print-architecture)"; \
|
||||
[ "$arch" = "amd64" ] || { echo "FATAL: cursor pin only covers amd64, not $arch" >&2; exit 1; }; \
|
||||
@@ -84,8 +84,8 @@ RUN set -eu; \
|
||||
ln -sf /usr/local/bin/cursor-agent /usr/local/bin/agent; \
|
||||
chmod -R a+rX /opt/cursor
|
||||
|
||||
ARG GH_VERSION=2.101.0
|
||||
ARG GH_SHA256_AMD64=9bca2d1c16825f109907a23307628a2f0698fbf99662b73a5cf0b020293072b8
|
||||
ARG GH_VERSION=2.102.0
|
||||
ARG GH_SHA256_AMD64=bb766f710eef8ede859c18578c72c327597cd4c8a85b06001b1f3843c6019386
|
||||
RUN set -eu; \
|
||||
arch="$(dpkg --print-architecture)"; \
|
||||
[ "$arch" = "amd64" ] || { echo "FATAL: gh pin only covers amd64, not $arch" >&2; exit 1; }; \
|
||||
@@ -139,9 +139,9 @@ RUN /bin/sh -lc 'set -eu; \
|
||||
for command_name in acpx claude-agent-acp codex-acp; do \
|
||||
command -v "$command_name" >/dev/null || { echo "FATAL: $command_name not on PATH for daytona user" >&2; exit 1; }; \
|
||||
done; \
|
||||
test "$(codex --version)" = "codex-cli 0.156.0"; \
|
||||
test "$(claude --version)" = "2.1.280 (Claude Code)"; \
|
||||
test "$(opencode --version)" = "1.18.32"; \
|
||||
test "$(codex --version)" = "codex-cli 0.160.0"; \
|
||||
test "$(claude --version)" = "2.1.286 (Claude Code)"; \
|
||||
test "$(opencode --version)" = "1.18.34"; \
|
||||
test "$(acpx --version)" = "0.13.1"; \
|
||||
test "$(claude-agent-acp --version)" = "0.73.0"; \
|
||||
test "$(codex-acp --version)" = "@agentclientprotocol/codex-acp 1.6.2"'
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
This image is the Paperclip Cloud fleet sandbox image plus a source-built
|
||||
`paperclip-runnerd` and immutable provider pack. The pack contains Node 24.11,
|
||||
OpenCode 1.18.32, the compiled OpenCode proxy, ACPX 0.13.1 sidecar, qualified ACP
|
||||
OpenCode 1.18.34, the compiled OpenCode proxy, ACPX 0.13.1 sidecar, qualified ACP
|
||||
agents, and the production lockfile. Its manifest digests each executable bridge
|
||||
and binds the pack to the runner source revision, avoiding artifact upload and
|
||||
npm installation on every fresh lease.
|
||||
@@ -14,14 +14,18 @@ image that this Dockerfile can extend directly.
|
||||
|
||||
## Harness versions
|
||||
|
||||
The September 22, 2026 refresh pins Codex 0.156.0, Claude Agent SDK
|
||||
0.3.280 (Claude Code 2.1.280), and OpenCode 1.18.32 in the shared provider
|
||||
The October 2026 refreshes pin Codex 0.160.0, Claude Agent SDK
|
||||
0.3.286 (Claude Code 2.1.286), and OpenCode 1.18.34 in the shared provider
|
||||
pack. Claude Code 2.1.280 is the minimum for
|
||||
[Opus 5.5](https://code.claude.com/docs/en/model-config); it also supports
|
||||
Fable 5.1. Codex uses the current
|
||||
[GPT-6 Sol and Luna model IDs](https://learn.chatgpt.com/docs/models).
|
||||
Grok CLI 1.0.41 supports the current
|
||||
[Grok 4.7](https://docs.x.ai/developers/grok-4-7) model family.
|
||||
[Opus 5.5](https://code.claude.com/docs/en/model-config) and 2.1.284 is the
|
||||
minimum for Sonnet 5.5; the pinned runtime also supports Fable 5.1. Codex
|
||||
0.160.0 ships bundled metadata for the current
|
||||
[GPT-6.1 Sol, GPT-6 Sol, and GPT-6 Luna model IDs](https://learn.chatgpt.com/docs/models).
|
||||
The same refresh pins Grok CLI 1.0.46, Gemini CLI 0.62.0, Kimi Code 2.1.1,
|
||||
Cursor CLI 2026.10.01-e373342, and GitHub CLI 2.102.0 in the sandbox layer.
|
||||
Grok CLI 1.0.46 supports the current
|
||||
[Grok 4.7](https://docs.x.ai/developers/grok-4-7) model family. Hermes stays
|
||||
at 0.19.0, the newest release on PyPI.
|
||||
|
||||
Keep the patched ACP bridge versions separate from their CLI runtime pins.
|
||||
Their executable digests do not change when only the runtime dependency
|
||||
|
||||
@@ -24,7 +24,7 @@ All environment variables that Paperclip uses for server configuration.
|
||||
| `PAPERCLIP_RUNNER_CA_BUNDLE_PATH` | (unset) | Optional PEM CA bundle for direct runner WSS. Platform roots remain enabled. There is no insecure TLS bypass. |
|
||||
| `PAPERCLIP_RUNNER_REMOTE_BINARY_PATH` | (host build) | Host-local path to a `paperclip-runnerd` artifact built for the remote target OS and architecture. Required when Paperclip and the remote sandbox do not share a compatible platform; build metadata and the required transport mode are verified before launch. |
|
||||
| `PAPERCLIP_RUNNER_REMOTE_CODEX_PATH` | (unset) | Optional host-local path to a Codex executable built for the remote target OS and architecture. For remote Codex-backed runners, Paperclip stages and verifies this executable beside `paperclip-runnerd`. |
|
||||
| `PAPERCLIP_RUNNER_REMOTE_CODEX_NPM_SPEC` | (unset) | Optional pinned npm package spec (for example, `@openai/codex@0.156.0`) installed inside each fresh remote lease when its Codex harness is not baked into the sandbox image. Mutually exclusive with `PAPERCLIP_RUNNER_REMOTE_CODEX_PATH`; Paperclip verifies the installed executable before starting `runnerd`. |
|
||||
| `PAPERCLIP_RUNNER_REMOTE_CODEX_NPM_SPEC` | (unset) | Optional pinned npm package spec (for example, `@openai/codex@0.160.0`) installed inside each fresh remote lease when its Codex harness is not baked into the sandbox image. Mutually exclusive with `PAPERCLIP_RUNNER_REMOTE_CODEX_PATH`; Paperclip verifies the installed executable before starting `runnerd`. |
|
||||
| `PAPERCLIP_RUNNER_REMOTE_PROVIDER_PACK_PATH` | `/opt/paperclip-runner/provider-pack` in Docker; otherwise unset | Host-local path to the immutable provider pack built by `pnpm --filter @paperclipai/paperclip-runner build:provider-pack`. Stamped standard Docker images include the pack; downstream compositions and the `cloud` target inherit it. Unstamped local Docker builds skip pack generation. The pack includes its target-built Node 24.11+ runtime, locked production dependencies, OpenCode proxy/executable, and ACPX sidecar. Remote OpenCode and ACPX fail closed without it. A preinstalled pack is accepted only when its complete digested manifest matches this build-owned pack; otherwise Paperclip stages this pack into the sandbox. |
|
||||
| `PAPERCLIP_HIDDEN_SETTINGS` | (unset) | Comma-separated settings surfaces to hide from the UI and floor at the API, for operators hosting Paperclip for others (managed cloud, internal shared server). See [Hiding settings surfaces](#hiding-settings-surfaces). |
|
||||
| `PAPERCLIP_SETTING_DEFAULTS` | (unset) | JSON object replacing the schema default of selected instance settings, for hosting operators. See [Operator setting defaults](#operator-setting-defaults). |
|
||||
|
||||
+4
-3
@@ -73,7 +73,8 @@
|
||||
"test:runner-recovery": "vitest run server/src/services/native-runtime/native-replacement-evidence.test.ts server/src/services/native-runtime/stopped-codex-turn.test.ts server/src/services/native-runtime/native-safe-replacement.test.ts",
|
||||
"test:e2e:browser-context": "playwright test --config tests/e2e/playwright-company-context.config.ts",
|
||||
"test:e2e:runner:browser-support": "playwright test --config tests/runner-e2e/playwright-support.config.ts",
|
||||
"test:e2e:runner:unit": "vitest run --config tests/runner-e2e/vitest.config.ts",
|
||||
"test:e2e:runner:unit": "vitest run --config tests/runner-e2e/vitest.config.ts && node --test tests/runner-e2e/native-completion-checks.test.mjs tests/runner-e2e/native-completion-git-source.test.mjs tests/runner-e2e/native-completion-source-contract.test.mjs",
|
||||
"test:e2e:runner:stock-harness": "node tests/runner-e2e/stock-harness-checks.mjs",
|
||||
"test:e2e:runner:typecheck": "tsc -p tests/runner-e2e/tsconfig.json",
|
||||
"test:e2e:runner:report": "node cli/node_modules/tsx/dist/cli.mjs tests/runner-e2e/report.ts",
|
||||
"test:runner-workflow-evals": "pnpm --filter @paperclipai/paperclip-eval-kernel build && pnpm --filter @paperclipai/paperclip-runner test:runner-workflow-evals",
|
||||
@@ -120,8 +121,8 @@
|
||||
"postgres@3.4.9": "patches/postgres@3.4.9.patch"
|
||||
},
|
||||
"overrides": {
|
||||
"@agentclientprotocol/codex-acp@1.6.2>@openai/codex": "0.156.0",
|
||||
"@agentclientprotocol/claude-agent-acp@0.73.0>@anthropic-ai/claude-agent-sdk": "0.3.280",
|
||||
"@agentclientprotocol/codex-acp@1.6.2>@openai/codex": "0.160.0",
|
||||
"@agentclientprotocol/claude-agent-acp@0.73.0>@anthropic-ai/claude-agent-sdk": "0.3.286",
|
||||
"rollup": ">=4.59.0",
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8",
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { createRuntimeStore, type AcpSessionRecord, type AcpSessionStore } from "acpx/runtime";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { createEphemeralSessionEnvironmentStore } from "./ephemeral-session-environment.js";
|
||||
|
||||
const currentEnvironment = Object.freeze({
|
||||
PAPERCLIP_API_KEY: "current-credential-fixture",
|
||||
PAPERCLIP_RUN_ID: "current-run-fixture",
|
||||
PATH: "/fixture/current-bin",
|
||||
});
|
||||
const oldEnvironment = { PAPERCLIP_API_KEY: "old-credential-fixture", OLD_RUN_PATH: "/fixture/old-run" };
|
||||
|
||||
function record(): AcpSessionRecord {
|
||||
return {
|
||||
schema: "acpx.session.v1",
|
||||
acpxRecordId: "fixture-record",
|
||||
acpSessionId: "fixture-session",
|
||||
agentSessionId: "fixture-agent-session",
|
||||
agentCommand: "fixture-provider",
|
||||
cwd: "/fixture/project",
|
||||
name: "fixture-name",
|
||||
createdAt: "2026-10-03T00:00:00.000Z",
|
||||
lastUsedAt: "2026-10-03T00:00:01.000Z",
|
||||
lastSeq: 3,
|
||||
eventLog: { active_path: "/fixture/events.jsonl", segment_count: 1, max_segment_bytes: 1024, max_segments: 2 },
|
||||
messages: [{ User: { id: "message-1", content: [{ Text: "Retained conversation" }] } }],
|
||||
updated_at: "2026-10-03T00:00:01.000Z",
|
||||
cumulative_token_usage: { input_tokens: 11, output_tokens: 7 },
|
||||
request_token_usage: { request_1: { input_tokens: 11 } },
|
||||
importedFrom: { recordId: "origin-record", cwdOriginal: "/fixture/origin", exportedBy: "fixture-caller", exportedAt: "2026-10-03T00:00:00.000Z" },
|
||||
acpx: {
|
||||
current_mode_id: "code",
|
||||
desired_config_options: { effort: "high" },
|
||||
session_options: { model: "fixture-model", allowed_tools: ["Read", "Write"], max_turns: 4, system_prompt: { append: "Retained caller option" }, env: { ...currentEnvironment } },
|
||||
},
|
||||
};
|
||||
}
|
||||
function freeze<T extends object>(value: T): T {
|
||||
for (const child of Object.values(value)) if (typeof child === "object" && child !== null) freeze(child);
|
||||
return Object.freeze(value);
|
||||
}
|
||||
function stubStore(loaded: AcpSessionRecord | undefined) {
|
||||
const load = vi.fn<AcpSessionStore["load"]>().mockResolvedValue(loaded);
|
||||
const save = vi.fn<AcpSessionStore["save"]>().mockResolvedValue(undefined);
|
||||
return { load, save };
|
||||
}
|
||||
const directories: string[] = [];
|
||||
afterEach(async () => { await Promise.all(directories.splice(0).map(directory => rm(directory, { recursive: true, force: true }))); });
|
||||
async function diskStore() {
|
||||
const stateDir = await mkdtemp(join(tmpdir(), "paperclip-session-env-")); directories.push(stateDir);
|
||||
return { store: createRuntimeStore({ stateDir }), file: join(stateDir, "sessions", "fixture-record.json") };
|
||||
}
|
||||
|
||||
describe("ephemeral ACPX session environment", () => {
|
||||
it("saves a fresh session to the actual file store without persisting its live environment", async () => {
|
||||
const disk = await diskStore(), input = freeze(record());
|
||||
const wrapper = createEphemeralSessionEnvironmentStore(disk.store, currentEnvironment);
|
||||
await wrapper.save(input);
|
||||
const text = await readFile(disk.file, "utf8"), saved = JSON.parse(text);
|
||||
expect(saved.acpx.session_options).not.toHaveProperty("env");
|
||||
for (const value of Object.values(currentEnvironment)) expect(text).not.toContain(value);
|
||||
expect(input.acpx?.session_options?.env).toEqual(currentEnvironment);
|
||||
expect(saved.acpx.current_mode_id).toBe("code");
|
||||
expect(saved.acpx.desired_config_options).toEqual(input.acpx?.desired_config_options);
|
||||
expect(saved.acpx.session_options).toEqual({ model: "fixture-model", allowed_tools: ["Read", "Write"], max_turns: 4, system_prompt: { append: "Retained caller option" } });
|
||||
expect(saved.messages).toEqual(input.messages);
|
||||
expect(saved.imported_from.record_id).toBe("origin-record");
|
||||
const loaded = await wrapper.load(input.acpxRecordId);
|
||||
expect(loaded?.acpx?.session_options?.env).toEqual(currentEnvironment);
|
||||
expect(loaded?.messages).toEqual(input.messages);
|
||||
});
|
||||
|
||||
it("loads an old env-bearing file with rotated credentials without writing, then saves neither environment", async () => {
|
||||
const disk = await diskStore(), legacy = record(); legacy.acpx!.session_options!.env = { ...oldEnvironment };
|
||||
await disk.store.save(legacy);
|
||||
const before = await readFile(disk.file, "utf8");
|
||||
const wrapper = createEphemeralSessionEnvironmentStore(disk.store, currentEnvironment);
|
||||
const loaded = await wrapper.load(legacy.acpxRecordId);
|
||||
expect(loaded?.acpx?.session_options?.env).toEqual(currentEnvironment);
|
||||
expect(loaded?.acpx?.session_options?.env).not.toHaveProperty("OLD_RUN_PATH");
|
||||
expect(await readFile(disk.file, "utf8")).toBe(before);
|
||||
expect(legacy.acpx?.session_options?.env).toEqual(oldEnvironment);
|
||||
await wrapper.save(loaded!);
|
||||
const after = await readFile(disk.file, "utf8");
|
||||
for (const value of [...Object.values(oldEnvironment), ...Object.values(currentEnvironment)]) expect(after).not.toContain(value);
|
||||
expect(JSON.parse(after).acpx.session_options).not.toHaveProperty("env");
|
||||
expect(loaded?.acpx?.session_options?.env).toEqual(currentEnvironment);
|
||||
});
|
||||
|
||||
it("loads an env-free persisted session into the current environment without writing", async () => {
|
||||
const disk = await diskStore(), input = record(); delete input.acpx!.session_options!.env;
|
||||
await disk.store.save(input);
|
||||
const before = await readFile(disk.file, "utf8");
|
||||
const loaded = await createEphemeralSessionEnvironmentStore(disk.store, currentEnvironment).load(input.acpxRecordId);
|
||||
expect(loaded?.acpx?.session_options?.env).toEqual(currentEnvironment);
|
||||
expect(loaded?.acpx?.session_options?.model).toBe("fixture-model");
|
||||
expect(await readFile(disk.file, "utf8")).toBe(before);
|
||||
});
|
||||
|
||||
it("copies loaded records, options and current env without mutating stored or caller-owned objects", async () => {
|
||||
const stored = record(); stored.acpx!.session_options!.env = { ...oldEnvironment }; freeze(stored);
|
||||
const backing = stubStore(stored), wrapper = createEphemeralSessionEnvironmentStore(backing, currentEnvironment);
|
||||
const loaded = await wrapper.load("fixture-record");
|
||||
expect(backing.load).toHaveBeenCalledWith("fixture-record"); expect(backing.save).not.toHaveBeenCalled();
|
||||
expect(loaded).not.toBe(stored); expect(loaded?.acpx).not.toBe(stored.acpx);
|
||||
expect(loaded?.acpx?.session_options).not.toBe(stored.acpx?.session_options);
|
||||
expect(loaded?.acpx?.session_options?.env).not.toBe(currentEnvironment);
|
||||
expect(stored.acpx?.session_options?.env).toEqual(oldEnvironment);
|
||||
loaded!.acpx!.session_options!.env!.PAPERCLIP_API_KEY = "isolated-loaded-fixture";
|
||||
expect(currentEnvironment.PAPERCLIP_API_KEY).toBe("current-credential-fixture");
|
||||
});
|
||||
|
||||
it("saves only a copied env-free projection, preserving unrelated and caller metadata", async () => {
|
||||
const input = freeze(Object.assign(record(), { caller_metadata: { keep: "caller-state", env: { preserve: "unrelated caller metadata" } } }));
|
||||
const backing = stubStore(undefined), wrapper = createEphemeralSessionEnvironmentStore(backing, currentEnvironment);
|
||||
await wrapper.save(input);
|
||||
const saved = backing.save.mock.calls[0]![0];
|
||||
const { env: _environment, ...expectedOptions } = input.acpx!.session_options!;
|
||||
expect(saved).toEqual({ ...input, acpx: { ...input.acpx, session_options: expectedOptions } });
|
||||
expect(saved).not.toBe(input); expect(saved.acpx).not.toBe(input.acpx);
|
||||
expect(saved.acpx?.session_options).not.toBe(input.acpx?.session_options);
|
||||
expect(saved.acpx?.session_options).not.toHaveProperty("env");
|
||||
expect(input.acpx?.session_options?.env).toEqual(currentEnvironment);
|
||||
});
|
||||
|
||||
for (const [name, acpx] of [
|
||||
["absent acpx", undefined],
|
||||
["explicit undefined acpx", undefined],
|
||||
["absent session options", { current_mode_id: "code" }],
|
||||
["undefined session options", { session_options: undefined }],
|
||||
["empty session options", { session_options: {} }],
|
||||
["undefined env", { session_options: { env: undefined, model: "fixture-model" } }],
|
||||
] as const) it(`preserves ${name} when saving and retains existing load injection semantics`, async () => {
|
||||
const input = record();
|
||||
if (name === "absent acpx") delete input.acpx; else input.acpx = acpx;
|
||||
freeze(input);
|
||||
const backing = stubStore(input), wrapper = createEphemeralSessionEnvironmentStore(backing, currentEnvironment);
|
||||
await wrapper.save(input);
|
||||
const saved = backing.save.mock.calls[0]![0];
|
||||
if (name === "undefined env") expect(saved.acpx?.session_options).toEqual({ model: "fixture-model" });
|
||||
else expect(saved).toEqual(input);
|
||||
expect(Object.hasOwn(saved, "acpx")).toBe(Object.hasOwn(input, "acpx"));
|
||||
if (saved.acpx && input.acpx) expect(Object.hasOwn(saved.acpx, "session_options")).toBe(Object.hasOwn(input.acpx, "session_options"));
|
||||
const loaded = await wrapper.load(input.acpxRecordId);
|
||||
expect(loaded?.acpx?.session_options?.env).toEqual(currentEnvironment);
|
||||
expect(backing.save).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("returns a missing record without saving or creating conversation state", async () => {
|
||||
const backing = stubStore(undefined);
|
||||
expect(await createEphemeralSessionEnvironmentStore(backing, currentEnvironment).load("missing")).toBeUndefined();
|
||||
expect(backing.save).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("propagates load and save errors unchanged", async () => {
|
||||
const failure = new Error("fixture store failure"), backing = stubStore(undefined);
|
||||
backing.load.mockRejectedValue(failure); backing.save.mockRejectedValue(failure);
|
||||
const wrapper = createEphemeralSessionEnvironmentStore(backing, currentEnvironment);
|
||||
await expect(wrapper.load("fixture-record")).rejects.toBe(failure);
|
||||
const input = freeze(record()); await expect(wrapper.save(input)).rejects.toBe(failure);
|
||||
expect(input.acpx?.session_options?.env).toEqual(currentEnvironment);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { AcpSessionStore } from "acpx/runtime";
|
||||
|
||||
/** Keep the current run's environment in memory, outside persisted conversation state. */
|
||||
export function createEphemeralSessionEnvironmentStore(
|
||||
persistedStore: AcpSessionStore,
|
||||
currentEnvironment: Readonly<Record<string, string>>,
|
||||
): AcpSessionStore {
|
||||
return {
|
||||
async load(id) {
|
||||
const record = await persistedStore.load(id);
|
||||
if (!record) return undefined;
|
||||
return {
|
||||
...record,
|
||||
acpx: {
|
||||
...record.acpx,
|
||||
session_options: {
|
||||
...record.acpx?.session_options,
|
||||
env: { ...currentEnvironment },
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
save(record) {
|
||||
const persisted = { ...record };
|
||||
if (record.acpx?.session_options !== undefined) {
|
||||
const { env: _environment, ...sessionOptions } = record.acpx.session_options;
|
||||
persisted.acpx = { ...record.acpx, session_options: sessionOptions };
|
||||
}
|
||||
return persistedStore.save(persisted);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { AcpRuntimeOptions } from "acpx/runtime";
|
||||
import { createRuntimeStore, type AcpRuntimeOptions, type AcpSessionRecord, type AcpSessionStore } from "acpx/runtime";
|
||||
import type { AdapterExecutionContext, AdapterRuntimeMcpAccess } from "@paperclipai/adapter-utils";
|
||||
import {
|
||||
DEFAULT_REMOTE_SANDBOX_ADAPTER_TIMEOUT_SEC,
|
||||
@@ -41,6 +41,8 @@ import { sessionCodec } from "./session-codec.js";
|
||||
import { runChildProcess } from "../server-utils.js";
|
||||
import { createPromptContextFixture } from "../test-fixtures/prompt-context.js";
|
||||
import { setExpensiveWorkspaceGitExecutor } from "../git-workspace-sync.js";
|
||||
import { createWorkspaceRestoreTeardown } from "../workspace-restore-teardown.js";
|
||||
import { withWorkspaceRestoreDiagnostics, withWorkspaceRestoreStep } from "../workspace-restore-diagnostics.js";
|
||||
import { resolveReferencedSourceIgnore } from "../sandbox-managed-runtime.js";
|
||||
import {
|
||||
getActiveStepContext,
|
||||
@@ -377,6 +379,42 @@ const ALLOWED_TURN_SPAN_ATTRIBUTE_KEYS = new Set<string>([
|
||||
]);
|
||||
|
||||
describe("shared ACPX engine runtime behavior", () => {
|
||||
it("integrates the env-free store while delivering current credentials and options to the provider", async () => {
|
||||
const root = await makeTempRoot();
|
||||
const stateDir = path.join(root, "state");
|
||||
const first = await runExecutor({ agent: "claude", agentCommand: "node ./fake-acp.js", cwd: root, stateDir,
|
||||
env: { ANTHROPIC_API_KEY: "first-fixture-credential" } });
|
||||
const liveEnvironment = (first.sessionInputs[0]!.sessionOptions as { env: Record<string, string> }).env;
|
||||
expect(liveEnvironment.ANTHROPIC_API_KEY).toBe("first-fixture-credential");
|
||||
const store = first.runtimeOptions[0]!.sessionStore as AcpSessionStore;
|
||||
const record: AcpSessionRecord = {
|
||||
schema: "acpx.session.v1", acpxRecordId: "integration-record", acpSessionId: "integration-session",
|
||||
agentCommand: "fixture-provider", cwd: root, name: "fixture", createdAt: "2026-10-03T00:00:00.000Z",
|
||||
lastUsedAt: "2026-10-03T00:00:01.000Z", lastSeq: 0,
|
||||
eventLog: { active_path: path.join(stateDir, "fixture.jsonl"), segment_count: 1, max_segment_bytes: 1024, max_segments: 2 },
|
||||
messages: [], updated_at: "2026-10-03T00:00:01.000Z", cumulative_token_usage: {}, request_token_usage: {},
|
||||
acpx: { session_options: { env: { ...liveEnvironment }, model: "retained-model", max_turns: 4 } },
|
||||
};
|
||||
await store.save(record);
|
||||
const file = path.join(stateDir, "sessions", "integration-record.json");
|
||||
const saved = await fs.readFile(file, "utf8");
|
||||
expect(saved).not.toContain("first-fixture-credential");
|
||||
expect(JSON.parse(saved).acpx.session_options).toEqual({ model: "retained-model", max_turns: 4 });
|
||||
expect(record.acpx?.session_options?.env).toEqual(liveEnvironment);
|
||||
// A file written by the prior engine is loaded with the current run's env,
|
||||
// without altering the provider launch or deleting unrelated options.
|
||||
record.acpx!.session_options!.env = { ANTHROPIC_API_KEY: "legacy-fixture-credential" };
|
||||
await createRuntimeStore({ stateDir }).save(record);
|
||||
const second = await runExecutor({ agent: "claude", agentCommand: "node ./fake-acp.js", cwd: root, stateDir,
|
||||
env: { ANTHROPIC_API_KEY: "rotated-fixture-credential" } }, { runtime: { sessionParams: first.result.sessionParams } });
|
||||
expect((second.sessionInputs[0]!.sessionOptions as { env: Record<string, string> }).env.ANTHROPIC_API_KEY).toBe("rotated-fixture-credential");
|
||||
const rotatedStore = second.runtimeOptions[0]!.sessionStore as AcpSessionStore;
|
||||
const loaded = await rotatedStore.load(record.acpxRecordId);
|
||||
expect(loaded?.acpx?.session_options).toMatchObject({ env: { ANTHROPIC_API_KEY: "rotated-fixture-credential" }, model: "retained-model", max_turns: 4 });
|
||||
await rotatedStore.save(loaded!);
|
||||
const resaved = await fs.readFile(file, "utf8");
|
||||
for (const value of ["first-fixture-credential", "legacy-fixture-credential", "rotated-fixture-credential"]) expect(resaved).not.toContain(value);
|
||||
});
|
||||
it.each(["claude", "codex", "gemini", "kimi", "custom"])("defaults the legacy %s engine to full auto on fresh and resumed runs", async (agent) => {
|
||||
const root = await makeTempRoot();
|
||||
const config = {
|
||||
@@ -597,6 +635,9 @@ describe("shared ACPX engine runtime behavior", () => {
|
||||
const fresh = await runExecutor(config, { context });
|
||||
expect(fresh.turnInputs).toHaveLength(1);
|
||||
const prompt = String(fresh.turnInputs[0]?.text ?? "");
|
||||
expect(prompt).toContain("You are agent agent-1");
|
||||
expect(prompt).toContain("Connection tools:");
|
||||
expect(prompt).not.toContain("Execution contract:");
|
||||
expect(prompt).toContain(context.paperclipTaskMarkdownAssignment);
|
||||
expect(prompt).toContain(context.paperclipTaskCommunicationGuidance);
|
||||
expect(prompt).not.toContain('"objective":');
|
||||
@@ -610,10 +651,18 @@ describe("shared ACPX engine runtime behavior", () => {
|
||||
const resumed = await runExecutor(config, { context, runtime: { sessionParams: fresh.result.sessionParams } });
|
||||
expect(resumed.sessionInputs[0]?.resumeSessionId).toBe(fresh.result.sessionId);
|
||||
const resumedPrompt = String(resumed.turnInputs[0]?.text ?? "");
|
||||
expect(resumedPrompt).not.toContain("Execution contract:");
|
||||
expect(resumedPrompt).toContain(context.paperclipTaskMarkdownAssignmentCompact);
|
||||
expect(resumedPrompt).not.toContain(context.paperclipTaskCommunicationGuidance);
|
||||
expect(resumedPrompt).not.toContain('"id":"comment-first"');
|
||||
expect(resumedPrompt).toContain('"id":"comment-second"');
|
||||
const reset = await runExecutor(config, { context });
|
||||
expect(reset.sessionInputs[0]?.resumeSessionId).toBeUndefined();
|
||||
const resetPrompt = String(reset.turnInputs[0]?.text ?? "");
|
||||
expect(resetPrompt).toContain("You are agent agent-1");
|
||||
expect(resetPrompt).toContain("Connection tools:");
|
||||
expect(resetPrompt).toContain(context.paperclipTaskMarkdownAssignment);
|
||||
expect(resetPrompt).not.toContain("Execution contract:");
|
||||
});
|
||||
|
||||
it("includes Paperclip env and API access notes in the ACPX prompt without leaking the token", async () => {
|
||||
@@ -747,10 +796,10 @@ describe("shared ACPX engine runtime behavior", () => {
|
||||
expect(prompt).not.toContain("Create child issues");
|
||||
expect(prompt).not.toContain("Use child issues");
|
||||
}
|
||||
expect(String(fresh.meta[0]?.prompt)).toContain(custom ? "Custom agent instructions." : "Continue your Paperclip conversation");
|
||||
expect(String(reset.meta[0]?.prompt)).toContain(custom ? "Custom agent instructions." : "Continue your Paperclip conversation");
|
||||
expect(String(fresh.meta[0]?.prompt)).toContain(custom ? "Custom agent instructions." : "You are agent agent-1");
|
||||
expect(String(reset.meta[0]?.prompt)).toContain(custom ? "Custom agent instructions." : "You are agent agent-1");
|
||||
const ordinary = await runExecutor({ ...config, promptTemplate: "" }, { context: { ...context, conversationMode: false } });
|
||||
expect(String(ordinary.meta[0]?.prompt)).toContain("Execution contract:");
|
||||
expect(String(ordinary.meta[0]?.prompt)).not.toContain("Execution contract:");
|
||||
expect(String(ordinary.meta[0]?.prompt)).toContain("Create child issues from the approved plan");
|
||||
});
|
||||
|
||||
@@ -3656,6 +3705,33 @@ describe("ACPX engine remote managed-home seam (PR 2: per-adapter home seed)", (
|
||||
expect(path.isAbsolute(remoteAssetDir)).toBe(true);
|
||||
});
|
||||
|
||||
it("retains bounded restore diagnostics through real settlement and result reproduction", async () => {
|
||||
const { stateDir, localCwd, executionTarget } = await setupRemoteSandbox();
|
||||
const failure = Object.assign(new Error("private-restore-path and command"), { code: 1, stderr: "private-restore-stderr" });
|
||||
const { result } = await runExecutor(
|
||||
{ agent: "custom", agentCommand: "node ./fake-acp.js", stateDir, cwd: localCwd },
|
||||
{
|
||||
authToken: "real-run-jwt", executionTarget,
|
||||
prepareRemoteManagedHome: async (input) => {
|
||||
const stagedRuntime = await input.stage([]);
|
||||
return {
|
||||
stagedRuntime,
|
||||
teardown: createWorkspaceRestoreTeardown({
|
||||
stagedRuntime: { restoreWorkspace: () => withWorkspaceRestoreDiagnostics("workspace", () =>
|
||||
withWorkspaceRestoreStep("git_integration", async () => { throw failure; })) },
|
||||
onLog: async () => {}, startMessage: "Restoring workspace", failurePrefix: "Restore failed",
|
||||
}),
|
||||
};
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(result.resultJson).toMatchObject({
|
||||
workspaceRestoreFailure: "restore_failed",
|
||||
workspaceRestoreDiagnostic: { phase: "workspace", step: "git_integration", errorCode: "unknown", exitCode: 1 },
|
||||
});
|
||||
expect(JSON.stringify(result)).not.toContain("private-restore-");
|
||||
});
|
||||
|
||||
it("test_remote_seam_teardown_fires_once_on_exit", async () => {
|
||||
const { stateDir, localCwd, executionTarget } = await setupRemoteSandbox();
|
||||
let teardownCalls = 0;
|
||||
@@ -3773,9 +3849,54 @@ describe("ACPX engine Claude skill bundle staging (remote ACP lane)", () => {
|
||||
};
|
||||
}
|
||||
|
||||
it("advertises folded routing metadata and exact staged paths without injecting skill bodies", async () => {
|
||||
const root = await makeTempRoot();
|
||||
const cwd = path.join(root, "worktree");
|
||||
const stateDir = path.join(root, "state");
|
||||
await fs.mkdir(cwd, { recursive: true });
|
||||
const skill = await createSkill(path.join(root, "sources"), "paperclip--versioned", [
|
||||
"---", "description: >-", " Use for Paperclip-managed tasks", " and durable task documents.",
|
||||
"---", "PRIVATE_SKILL_BODY_RECIPE", "",
|
||||
].join("\n"));
|
||||
const { meta, result } = await runExecutor({
|
||||
agent: "claude", agentCommand: "node ./fake-acp.js", stateDir, cwd,
|
||||
paperclipRuntimeSkills: [skill], paperclipSkillSync: { desiredSkills: [skill.key] },
|
||||
});
|
||||
const bundle = await onlyChildDir(path.join(stateDir, "runtime-skills", "claude"));
|
||||
const stagedFile = path.join(bundle, ".claude", "skills", skill.runtimeName, "SKILL.md");
|
||||
const prompt = String(meta[0]?.prompt ?? "");
|
||||
expect(prompt).toContain(`- ${skill.runtimeName}: Use for Paperclip-managed tasks and durable task documents. (file: ${stagedFile})`);
|
||||
expect(prompt).not.toContain("PRIVATE_SKILL_BODY_RECIPE");
|
||||
expect(prompt).not.toContain(skill.source);
|
||||
expect(result.sessionParams?.skills).toMatchObject({ selectedSkills: [skill.runtimeName] });
|
||||
await expect(fs.readFile(stagedFile, "utf8")).resolves.toContain("PRIVATE_SKILL_BODY_RECIPE");
|
||||
});
|
||||
|
||||
it("bounds routing descriptions and keeps files discoverable when metadata is absent or malformed", async () => {
|
||||
const root = await makeTempRoot();
|
||||
const cwd = path.join(root, "worktree");
|
||||
const stateDir = path.join(root, "state");
|
||||
await fs.mkdir(cwd, { recursive: true });
|
||||
const sources = path.join(root, "sources");
|
||||
const bounded = await createSkill(sources, "bounded", `---\ndescription: ${"x".repeat(600)}\n---\nBOUNDED_BODY\n`);
|
||||
const absent = await createSkill(sources, "absent", "# ABSENT_BODY\n");
|
||||
const malformed = await createSkill(sources, "malformed", "---\ndescription: incomplete header\nMALFORMED_BODY\n");
|
||||
const skills = [bounded, absent, malformed];
|
||||
const { meta } = await runExecutor({
|
||||
agent: "claude", agentCommand: "node ./fake-acp.js", stateDir, cwd,
|
||||
paperclipRuntimeSkills: skills, paperclipSkillSync: { desiredSkills: skills.map((skill) => skill.key) },
|
||||
});
|
||||
const prompt = String(meta[0]?.prompt ?? "");
|
||||
expect(prompt).toContain(`- bounded: ${"x".repeat(512)} (file: `);
|
||||
expect(prompt).not.toContain("x".repeat(513));
|
||||
expect(prompt).toMatch(/- absent \(file: .*\/absent\/SKILL\.md\)/);
|
||||
expect(prompt).toMatch(/- malformed \(file: .*\/malformed\/SKILL\.md\)/);
|
||||
for (const body of ["BOUNDED_BODY", "ABSENT_BODY", "MALFORMED_BODY"]) expect(prompt).not.toContain(body);
|
||||
});
|
||||
|
||||
it("rewrites the prompt and skill identity onto the in-sandbox skill root once the bundle is staged", async () => {
|
||||
const { stateDir, localCwd, executionTarget } = await setupRemoteSandbox();
|
||||
const skill = await createSkill(path.join(localCwd, "skills"), "review");
|
||||
const skill = await createSkill(path.join(localCwd, "skills"), "review", "---\ndescription: Review requested task documents.\n---\n# review\nREMOTE_BODY_ONLY\n");
|
||||
|
||||
const { meta, result } = await runExecutor(
|
||||
{
|
||||
@@ -3798,6 +3919,8 @@ describe("ACPX engine Claude skill bundle staging (remote ACP lane)", () => {
|
||||
|
||||
const inSandboxSkillsRoot = prompt.match(/Skill root: (\S+)/)![1]!;
|
||||
expect(inSandboxSkillsRoot).not.toBe(hostSkillsHome);
|
||||
expect(prompt).toContain(`- review: Review requested task documents. (file: ${path.join(inSandboxSkillsRoot, "review", "SKILL.md")})`);
|
||||
expect(prompt).not.toContain("REMOTE_BODY_ONLY");
|
||||
await expect(
|
||||
fs.readFile(path.join(inSandboxSkillsRoot, skill.runtimeName, "SKILL.md"), "utf8"),
|
||||
).resolves.toContain("# review");
|
||||
|
||||
@@ -7,6 +7,7 @@ import path from "node:path";
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import { createHash, randomUUID } from "node:crypto";
|
||||
import { parseFrontmatterMarkdown } from "@paperclipai/shared";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import type {
|
||||
AdapterBillingType,
|
||||
@@ -49,6 +50,7 @@ import {
|
||||
} from "./terminal-session-failure.js";
|
||||
export type { AcpxTerminalSessionFailure } from "./terminal-session-failure.js";
|
||||
import type { WorkspaceRestoreFailureCode, WorkspaceRestoreOutcome } from "../workspace-restore-merge.js";
|
||||
import type { WorkspaceRestoreDiagnostic } from "../workspace-restore-diagnostics.js";
|
||||
import {
|
||||
classifyWorkspaceRestoreFailure,
|
||||
describeWorkspaceRestoreFailure,
|
||||
@@ -85,6 +87,7 @@ import {
|
||||
type PaperclipSkillEntry,
|
||||
} from "@paperclipai/adapter-utils/server-utils";
|
||||
import { shellQuote } from "@paperclipai/adapter-utils/ssh";
|
||||
import { createEphemeralSessionEnvironmentStore } from "./ephemeral-session-environment.js";
|
||||
import {
|
||||
createAcpRuntime,
|
||||
createAgentRegistry,
|
||||
@@ -100,7 +103,6 @@ import {
|
||||
type AcpRuntimeTurnResult,
|
||||
type AcpRuntimeUsageBreakdown,
|
||||
type AcpRuntimeUsageCost,
|
||||
type AcpSessionStore,
|
||||
} from "acpx/runtime";
|
||||
import {
|
||||
ACPX_DUPLEX_LOSS_CANCEL_DEADLINE_MS,
|
||||
@@ -1183,11 +1185,24 @@ async function prepareClaudeSkillRuntime(input: {
|
||||
}
|
||||
|
||||
const selectedNames = materializedNames.sort();
|
||||
const skillDescriptions = await Promise.all(selectedNames.map(async (name) => {
|
||||
const file = path.join(skillsHome, name, "SKILL.md");
|
||||
let description = "";
|
||||
try {
|
||||
const parsed = parseFrontmatterMarkdown(await fs.readFile(file, "utf8"));
|
||||
description = asString(parsed.frontmatter.description, "").replace(/\s+/g, " ").trim().slice(0, 512);
|
||||
} catch {
|
||||
// A readable skill without valid routing metadata remains available.
|
||||
// Do not substitute its body for a missing description.
|
||||
}
|
||||
return `- ${name}${description ? `: ${description}` : ""} (file: ${file})`;
|
||||
}));
|
||||
const promptInstructions = selectedNames.length > 0
|
||||
? [
|
||||
"Paperclip has materialized selected runtime skills for this ACPX Claude session.",
|
||||
`Skill root: ${skillsHome}`,
|
||||
`Selected skills: ${selectedNames.join(", ")}`,
|
||||
...skillDescriptions,
|
||||
"When a task calls for one of these skills, read its SKILL.md from that root and follow it.",
|
||||
].join("\n")
|
||||
: "";
|
||||
@@ -4063,7 +4078,7 @@ export function createAcpxEngineExecutor(deps: AcpxEngineExecutorOptions = {}) {
|
||||
// status stay exactly what the turn produced — this is a signal, not an
|
||||
// outcome change.
|
||||
let workspaceRestoreFailureField:
|
||||
| { workspaceRestoreFailure: WorkspaceRestoreFailureCode }
|
||||
| { workspaceRestoreFailure: WorkspaceRestoreFailureCode; workspaceRestoreDiagnostic?: WorkspaceRestoreDiagnostic }
|
||||
| Record<string, never> = {};
|
||||
// The one settlement step name whose error can be the same workspace-
|
||||
// restore failure the adapter teardown closure already classifies (a
|
||||
@@ -4249,26 +4264,9 @@ export function createAcpxEngineExecutor(deps: AcpxEngineExecutorOptions = {}) {
|
||||
flushChildStderr(childStderrState);
|
||||
childStderrState.logPath = prepared.childStderrLogPath;
|
||||
const persistedRuntimeStore = createRuntimeStore({ stateDir: prepared.stateDir });
|
||||
const runtimeStore: AcpSessionStore = {
|
||||
async load(id) {
|
||||
const record = await persistedRuntimeStore.load(id);
|
||||
if (!record) return undefined;
|
||||
// ACPX resumes from the stored session options rather than the
|
||||
// options passed to ensureSession. Keep conversation state, but
|
||||
// launch the provider with this run's credentials and scratch paths.
|
||||
return {
|
||||
...record,
|
||||
acpx: {
|
||||
...record.acpx,
|
||||
session_options: {
|
||||
...record.acpx?.session_options,
|
||||
env: { ...prepared.env },
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
save: (record) => persistedRuntimeStore.save(record),
|
||||
};
|
||||
// Resume with this run's launch environment; keep it out of the saved
|
||||
// conversation record without mutating the live runtime's options.
|
||||
const runtimeStore = createEphemeralSessionEnvironmentStore(persistedRuntimeStore, prepared.env);
|
||||
const runtimeOptions: PaperclipAcpRuntimeOptions = {
|
||||
cwd: prepared.cwd,
|
||||
// Host-only spawn cwd for the relay proxy on the remote process-session
|
||||
@@ -5413,7 +5411,10 @@ export function createAcpxEngineExecutor(deps: AcpxEngineExecutorOptions = {}) {
|
||||
await withAdapterExecutionPhase(ctx, "workspace_restore", () => runRuntimeSpan("sandbox.syncBack", async () => {
|
||||
const restoreOutcome = await syncBackManagedHome(prepared);
|
||||
if (!restoreOutcome.ok) {
|
||||
workspaceRestoreFailureField = { workspaceRestoreFailure: restoreOutcome.code };
|
||||
workspaceRestoreFailureField = {
|
||||
workspaceRestoreFailure: restoreOutcome.code,
|
||||
...(restoreOutcome.diagnostic ? { workspaceRestoreDiagnostic: restoreOutcome.diagnostic } : {}),
|
||||
};
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { promisify } from "node:util";
|
||||
import { workspacePaths } from "./workspace-manifest.js";
|
||||
import { runWorkspaceGitProcess } from "./workspace-git-stream.js";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { getWorkspaceRestoreDiagnostic, withWorkspaceRestoreDiagnostics, withWorkspaceRestoreStep, withWorkspaceRestoreGitCommand } from "./workspace-restore-diagnostics.js";
|
||||
|
||||
import {
|
||||
buildRemoteGitDeltaBundleScript,
|
||||
@@ -24,6 +25,7 @@ import {
|
||||
REFERENCED_SOURCE_IGNORE_MAX_ENTRY_COUNT,
|
||||
REFERENCED_SOURCE_IGNORE_MAX_TOTAL_BYTES,
|
||||
runLocalGit,
|
||||
resetLocalGitIndexToHead,
|
||||
sanitizeGitRemoteUrl,
|
||||
setExpensiveWorkspaceGitExecutor,
|
||||
withShallowGitWorkspaceClone,
|
||||
@@ -651,7 +653,9 @@ describe("git workspace sync", () => {
|
||||
const savedEnv = new Map(identityEnvKeys.map((key) => [key, process.env[key]]));
|
||||
for (const key of identityEnvKeys) delete process.env[key];
|
||||
try {
|
||||
await integrateImportedGitHead({ localDir: repo, importedHead });
|
||||
await integrateImportedGitHead({
|
||||
localDir: repo, importedHead, baseline: { headCommit: baseHead, branchName: "main" },
|
||||
});
|
||||
} finally {
|
||||
for (const [key, value] of savedEnv) {
|
||||
if (value === undefined) delete process.env[key];
|
||||
@@ -670,7 +674,161 @@ describe("git workspace sync", () => {
|
||||
expect(mergedTree).toContain("imported.txt");
|
||||
});
|
||||
|
||||
it("grafts an imported head onto the current head when histories share no ancestor", async () => {
|
||||
describe("sandbox history rewrites", () => {
|
||||
async function rewrittenHistory() {
|
||||
const repo = await mkdtemp(path.join(os.tmpdir(), "paperclip-git-rewrite-"));
|
||||
cleanupDirs.push(repo);
|
||||
await git(repo, ["init"]);
|
||||
await git(repo, ["checkout", "-b", "host"]);
|
||||
await git(repo, ["config", "user.name", "Paperclip Test"]);
|
||||
await git(repo, ["config", "user.email", "test@paperclip.dev"]);
|
||||
await writeFile(path.join(repo, "tracked.txt"), "base\n");
|
||||
await git(repo, ["add", "."]);
|
||||
await git(repo, ["commit", "-m", "base"]);
|
||||
const ancestor = await git(repo, ["rev-parse", "HEAD"]);
|
||||
await writeFile(path.join(repo, "tracked.txt"), "original change\n");
|
||||
await git(repo, ["commit", "-am", "original change"]);
|
||||
const baseline = { headCommit: await git(repo, ["rev-parse", "HEAD"]), branchName: "host" };
|
||||
await git(repo, ["checkout", "-b", "sandbox"]);
|
||||
await writeFile(path.join(repo, "tracked.txt"), "rewritten change\n");
|
||||
await git(repo, ["commit", "-am", "rewritten change", "--amend"]);
|
||||
const importedHead = await git(repo, ["rev-parse", "HEAD"]);
|
||||
await git(repo, ["checkout", "host"]);
|
||||
return { repo, ancestor, baseline, importedHead };
|
||||
}
|
||||
|
||||
it("accepts rewritten history when the host still matches the starting snapshot", async () => {
|
||||
const { repo, baseline, importedHead } = await rewrittenHistory();
|
||||
// The old integration path attempts this conflicting merge even though
|
||||
// the host has not changed since the run started.
|
||||
await expect(git(repo, ["merge-tree", "--write-tree", baseline.headCommit, importedHead]))
|
||||
.rejects.toMatchObject({ code: 1 });
|
||||
await writeFile(path.join(repo, "local.txt"), "local work\n");
|
||||
await git(repo, ["add", "local.txt"]);
|
||||
const indexBefore = await git(repo, ["write-tree"]);
|
||||
|
||||
await integrateImportedGitHead({ localDir: repo, importedHead, baseline });
|
||||
|
||||
expect(await git(repo, ["rev-parse", "HEAD"])).toBe(importedHead);
|
||||
expect(await git(repo, ["symbolic-ref", "--short", "HEAD"])).toBe("host");
|
||||
expect(await git(repo, ["write-tree"])).toBe(indexBefore);
|
||||
expect(await readFile(path.join(repo, "local.txt"), "utf8")).toBe("local work\n");
|
||||
});
|
||||
|
||||
it("restores an intentional reset to an ancestor when the host has not changed", async () => {
|
||||
const { repo, ancestor, baseline } = await rewrittenHistory();
|
||||
await integrateImportedGitHead({ localDir: repo, importedHead: ancestor, baseline });
|
||||
expect(await git(repo, ["rev-parse", "HEAD"])).toBe(ancestor);
|
||||
});
|
||||
|
||||
it("does not replace host commits made after the starting snapshot", async () => {
|
||||
const { repo, baseline, importedHead } = await rewrittenHistory();
|
||||
await writeFile(path.join(repo, "local.txt"), "concurrent work\n");
|
||||
await git(repo, ["add", "local.txt"]);
|
||||
await git(repo, ["commit", "-m", "host advanced"]);
|
||||
const hostHead = await git(repo, ["rev-parse", "HEAD"]);
|
||||
|
||||
await expect(integrateImportedGitHead({ localDir: repo, importedHead, baseline }))
|
||||
.rejects.toThrow("Failed to merge concurrent remote git histories");
|
||||
|
||||
expect(await git(repo, ["rev-parse", "HEAD"])).toBe(hostHead);
|
||||
expect(await readFile(path.join(repo, "local.txt"), "utf8")).toBe("concurrent work\n");
|
||||
});
|
||||
|
||||
it("does not replace a different host branch at the same starting commit", async () => {
|
||||
const { repo, baseline, importedHead } = await rewrittenHistory();
|
||||
const tree = await git(repo, ["rev-parse", `${importedHead}^{tree}`]);
|
||||
const fastForwardHead = await git(repo, ["commit-tree", tree, "-p", baseline.headCommit, "-m", "sandbox advance"]);
|
||||
await git(repo, ["checkout", "-b", "other"]);
|
||||
await expect(integrateImportedGitHead({ localDir: repo, importedHead: fastForwardHead, baseline }))
|
||||
.rejects.toThrow("branch changed");
|
||||
expect(await git(repo, ["rev-parse", "HEAD"])).toBe(baseline.headCommit);
|
||||
expect(await git(repo, ["rev-parse", "host"])).toBe(baseline.headCommit);
|
||||
});
|
||||
|
||||
it("keeps the conservative merge behavior without a starting snapshot", async () => {
|
||||
const { repo, baseline, importedHead } = await rewrittenHistory();
|
||||
await expect(integrateImportedGitHead({ localDir: repo, importedHead }))
|
||||
.rejects.toThrow("Failed to merge concurrent remote git histories");
|
||||
expect(await git(repo, ["rev-parse", "HEAD"])).toBe(baseline.headCommit);
|
||||
});
|
||||
|
||||
it("refuses unrelated rewritten history after a concurrent host commit", async () => {
|
||||
const { repo, baseline, importedHead } = await rewrittenHistory();
|
||||
const tree = await git(repo, ["rev-parse", `${importedHead}^{tree}`]);
|
||||
const unrelated = await git(repo, ["commit-tree", tree, "-m", "shallow rewrite"]);
|
||||
await writeFile(path.join(repo, "local.txt"), "concurrent work\n");
|
||||
await git(repo, ["add", "local.txt"]);
|
||||
await git(repo, ["commit", "-m", "host advanced"]);
|
||||
const hostHead = await git(repo, ["rev-parse", "HEAD"]);
|
||||
await expect(integrateImportedGitHead({ localDir: repo, importedHead: unrelated, baseline }))
|
||||
.rejects.toThrow("Cannot restore unrelated remote history after the host advanced");
|
||||
expect(await git(repo, ["rev-parse", "HEAD"])).toBe(hostHead);
|
||||
});
|
||||
|
||||
it("accepts a rewrite of an unchanged detached host without attaching a branch", async () => {
|
||||
const { repo, baseline, importedHead } = await rewrittenHistory();
|
||||
await git(repo, ["checkout", "--detach"]);
|
||||
await integrateImportedGitHead({ localDir: repo, importedHead, baseline: { ...baseline, branchName: null } });
|
||||
expect(await git(repo, ["rev-parse", "HEAD"])).toBe(importedHead);
|
||||
await expect(git(repo, ["symbolic-ref", "--quiet", "HEAD"])).rejects.toMatchObject({ code: 1 });
|
||||
});
|
||||
|
||||
it.each([false, true])("rejects a checkout during integration (initially detached: %s)", async (detached) => {
|
||||
const { repo, baseline, importedHead } = await rewrittenHistory();
|
||||
if (detached) await git(repo, ["checkout", "--detach"]);
|
||||
const realGit = (await execFile("sh", ["-c", "command -v git"])).stdout.trim();
|
||||
const bin = path.join(repo, "test-bin");
|
||||
await mkdir(bin);
|
||||
const quote = (value: string) => `'${value.replaceAll("'", "'\\''")}'`;
|
||||
// Switch branches after the initial identity read, at the merge-base
|
||||
// subprocess boundary. Both refs still point to the expected old OID.
|
||||
await writeFile(path.join(bin, "git"), `#!/bin/sh
|
||||
if [ "$3" = merge-base ]; then
|
||||
${quote(realGit)} -C "$2" checkout -B other ${baseline.headCommit} >/dev/null 2>&1 || exit 1
|
||||
fi
|
||||
exec ${quote(realGit)} "$@"
|
||||
`, { mode: 0o755 });
|
||||
const priorPath = process.env.PATH;
|
||||
process.env.PATH = `${bin}${path.delimiter}${priorPath ?? ""}`;
|
||||
try {
|
||||
await expect(integrateImportedGitHead({
|
||||
localDir: repo, importedHead, baseline: { ...baseline, branchName: detached ? null : "host" },
|
||||
})).rejects.toThrow("branch changed");
|
||||
} finally {
|
||||
if (priorPath === undefined) delete process.env.PATH;
|
||||
else process.env.PATH = priorPath;
|
||||
}
|
||||
expect(await git(repo, ["symbolic-ref", "--short", "HEAD"])).toBe("other");
|
||||
expect(await git(repo, ["rev-parse", "host"])).toBe(baseline.headCommit);
|
||||
expect(await git(repo, ["rev-parse", "other"])).toBe(baseline.headCommit);
|
||||
expect(await stat(path.join(repo, ".git", "HEAD.lock")).catch(() => null)).toBeNull();
|
||||
});
|
||||
|
||||
it.each([false, true])("holds the HEAD lock through commit (initially detached: %s)", async (detached) => {
|
||||
const { repo, baseline, importedHead } = await rewrittenHistory();
|
||||
await git(repo, ["branch", "other"]);
|
||||
if (detached) await git(repo, ["checkout", "--detach"]);
|
||||
await writeFile(path.join(repo, ".git", "hooks", "reference-transaction"), `#!/bin/sh
|
||||
if [ "$1" = prepared ]; then
|
||||
if git symbolic-ref HEAD refs/heads/other 2>/dev/null; then exit 1; fi
|
||||
printf blocked > checkout-attempt.txt
|
||||
fi
|
||||
exit 0
|
||||
`, { mode: 0o755 });
|
||||
await integrateImportedGitHead({
|
||||
localDir: repo, importedHead, baseline: { ...baseline, branchName: detached ? null : "host" },
|
||||
});
|
||||
expect(await readFile(path.join(repo, "checkout-attempt.txt"), "utf8")).toBe("blocked");
|
||||
expect(await git(repo, ["rev-parse", "HEAD"])).toBe(importedHead);
|
||||
expect(await git(repo, ["rev-parse", "other"])).toBe(baseline.headCommit);
|
||||
if (detached) await expect(git(repo, ["symbolic-ref", "--quiet", "HEAD"])).rejects.toMatchObject({ code: 1 });
|
||||
else expect(await git(repo, ["symbolic-ref", "--short", "HEAD"])).toBe("host");
|
||||
expect(await stat(path.join(repo, ".git", "HEAD.lock")).catch(() => null)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it.each([false, true])("grafts an unrelated imported head with an unchanged host (baseline supplied: %s)", async (withBaseline) => {
|
||||
const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-git-graft-"));
|
||||
cleanupDirs.push(rootDir);
|
||||
const setupIdentity = ["-c", "user.name=Setup", "-c", "user.email=setup@paperclip.dev"];
|
||||
@@ -693,7 +851,10 @@ describe("git workspace sync", () => {
|
||||
const importedTree = await git(repo, ["rev-parse", `${baseHead}^{tree}`]);
|
||||
const importedHead = await git(repo, [...setupIdentity, "commit-tree", importedTree, "-m", "sandbox rewrite"]);
|
||||
|
||||
await integrateImportedGitHead({ localDir: repo, importedHead });
|
||||
await integrateImportedGitHead({
|
||||
localDir: repo, importedHead,
|
||||
baseline: withBaseline ? { headCommit: currentHead, branchName: "main" } : undefined,
|
||||
});
|
||||
|
||||
const parents = (await git(repo, ["rev-list", "--parents", "-1", "HEAD"])).split(" ");
|
||||
expect(parents.slice(1)).toEqual([currentHead]);
|
||||
@@ -705,7 +866,7 @@ describe("git workspace sync", () => {
|
||||
expect(body).toContain("shares no ancestor");
|
||||
});
|
||||
|
||||
it("does not graft when merge-base fails for a reason other than missing ancestry", async () => {
|
||||
it.each([false, true])("does not graft on a merge-base error other than missing ancestry (baseline supplied: %s)", async (withBaseline) => {
|
||||
const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-git-no-graft-"));
|
||||
cleanupDirs.push(rootDir);
|
||||
const setupIdentity = ["-c", "user.name=Setup", "-c", "user.email=setup@paperclip.dev"];
|
||||
@@ -722,11 +883,119 @@ describe("git workspace sync", () => {
|
||||
// object error (exit 128), not the no-ancestor signal (exit 1). The graft
|
||||
// must not fire, and the integration keeps its loud failure.
|
||||
const missingHead = "0123456789abcdef0123456789abcdef01234567";
|
||||
await expect(integrateImportedGitHead({ localDir: repo, importedHead: missingHead }))
|
||||
.rejects.toThrow(/Failed to merge concurrent remote git histories/);
|
||||
const expectedExit = await runLocalGit(repo, ["merge-tree", "--write-tree", currentHead, missingHead])
|
||||
.catch((error: { code: number }) => error.code);
|
||||
const error = await withWorkspaceRestoreDiagnostics("workspace", () => withWorkspaceRestoreStep("git_integration", () =>
|
||||
integrateImportedGitHead({
|
||||
localDir: repo, importedHead: missingHead,
|
||||
baseline: withBaseline ? { headCommit: currentHead, branchName: "main" } : undefined,
|
||||
}))).catch((error: unknown) => error);
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect((error as Error).message).toMatch(/Failed to merge concurrent remote git histories/);
|
||||
expect(error).not.toHaveProperty("cause");
|
||||
expect(getWorkspaceRestoreDiagnostic(error)).toEqual({ phase: "workspace", step: "git_integration", errorCode: "unknown", exitCode: expectedExit,
|
||||
gitCommand: "merge_tree", gitFailureKind: "invalid_object" });
|
||||
expect(await git(repo, ["rev-parse", "HEAD"])).toBe(currentHead);
|
||||
});
|
||||
|
||||
it("identifies a real merge conflict without changing the host tip or copying Git output", async () => {
|
||||
const repo = await mkdtemp(path.join(os.tmpdir(), "paperclip-git-conflict-diagnostic-"));
|
||||
cleanupDirs.push(repo);
|
||||
await git(repo, ["init", "-b", "host"]);
|
||||
await git(repo, ["config", "user.name", "Test"]);
|
||||
await git(repo, ["config", "user.email", "test@paperclip.dev"]);
|
||||
await writeFile(path.join(repo, "private-filename.txt"), "base\n");
|
||||
await git(repo, ["add", "."]);
|
||||
await git(repo, ["commit", "-m", "base"]);
|
||||
const base = await git(repo, ["rev-parse", "HEAD"]);
|
||||
await writeFile(path.join(repo, "private-filename.txt"), "host change\n");
|
||||
await git(repo, ["commit", "-am", "host"]);
|
||||
const currentHead = await git(repo, ["rev-parse", "HEAD"]);
|
||||
await git(repo, ["checkout", "-b", "imported", base]);
|
||||
await writeFile(path.join(repo, "private-filename.txt"), "remote change\n");
|
||||
await git(repo, ["commit", "-am", "remote"]);
|
||||
const importedHead = await git(repo, ["rev-parse", "HEAD"]);
|
||||
await git(repo, ["checkout", "host"]);
|
||||
const logs: string[] = [];
|
||||
const error = await withWorkspaceRestoreDiagnostics("workspace", () => withWorkspaceRestoreStep("directory_merge", () =>
|
||||
withWorkspaceRestoreStep("git_integration", () => integrateImportedGitHead({ localDir: repo, importedHead }))),
|
||||
async (line) => { logs.push(line); }).catch(error => error);
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect(error.message).toContain("Failed to merge concurrent remote git histories");
|
||||
expect(error).not.toHaveProperty("cause");
|
||||
expect(getWorkspaceRestoreDiagnostic(error)).toEqual({ phase: "workspace", step: "git_integration", errorCode: "unknown",
|
||||
exitCode: 1, gitCommand: "merge_tree", gitFailureKind: "merge_conflict" });
|
||||
expect(await git(repo, ["rev-parse", "HEAD"])).toBe(currentHead);
|
||||
expect(await readFile(path.join(repo, "private-filename.txt"), "utf8")).toBe("host change\n");
|
||||
expect(logs).toHaveLength(1);
|
||||
expect(logs[0]).not.toMatch(/private-filename|host change|remote change/);
|
||||
for (const privateValue of [repo, currentHead, importedHead]) expect(logs[0]).not.toContain(privateValue);
|
||||
});
|
||||
|
||||
it("labels a failed locked ref transaction without changing its branch or tip", async () => {
|
||||
const repo = await mkdtemp(path.join(os.tmpdir(), "paperclip-git-transaction-diagnostic-"));
|
||||
cleanupDirs.push(repo);
|
||||
await git(repo, ["init", "-b", "private-branch"]);
|
||||
await git(repo, ["config", "user.name", "Test"]);
|
||||
await git(repo, ["config", "user.email", "test@paperclip.dev"]);
|
||||
await git(repo, ["commit", "--allow-empty", "-m", "base"]);
|
||||
const base = await git(repo, ["rev-parse", "HEAD"]);
|
||||
await git(repo, ["commit", "--allow-empty", "-m", "advance"]);
|
||||
const importedHead = await git(repo, ["rev-parse", "HEAD"]);
|
||||
await git(repo, ["reset", "--hard", base]);
|
||||
// Git owns this lock. A failed restore must leave it alone and keep its
|
||||
// existing failure behavior; diagnostic collection grants no cleanup rights.
|
||||
await writeFile(path.join(repo, ".git", "HEAD.lock"), "private-lock");
|
||||
const error = await withWorkspaceRestoreDiagnostics("workspace", () => withWorkspaceRestoreStep("git_integration", () =>
|
||||
integrateImportedGitHead({ localDir: repo, importedHead, baseline: { headCommit: base, branchName: "private-branch" } })))
|
||||
.catch(error => error);
|
||||
expect(error).toMatchObject({ code: 128 });
|
||||
const diagnostic = getWorkspaceRestoreDiagnostic(error);
|
||||
expect(diagnostic).toEqual({ phase: "workspace", step: "git_integration", errorCode: "unknown", exitCode: 128,
|
||||
gitCommand: "update_ref", gitFailureKind: "unknown" });
|
||||
expect(await git(repo, ["rev-parse", "HEAD"])).toBe(base);
|
||||
expect(await git(repo, ["symbolic-ref", "--short", "HEAD"])).toBe("private-branch");
|
||||
expect(await readFile(path.join(repo, ".git", "HEAD.lock"), "utf8")).toBe("private-lock");
|
||||
for (const privateValue of [repo, base, importedHead, "private-branch"]) expect(JSON.stringify(diagnostic)).not.toContain(privateValue);
|
||||
});
|
||||
|
||||
it("recognizes a real expected-old ref mismatch without copying the ref or commit IDs", async () => {
|
||||
const repo = await mkdtemp(path.join(os.tmpdir(), "paperclip-git-ref-diagnostic-"));
|
||||
cleanupDirs.push(repo);
|
||||
await git(repo, ["init", "-b", "private-branch"]);
|
||||
await git(repo, ["config", "user.name", "Test"]);
|
||||
await git(repo, ["config", "user.email", "test@paperclip.dev"]);
|
||||
await git(repo, ["commit", "--allow-empty", "-m", "base"]);
|
||||
const base = await git(repo, ["rev-parse", "HEAD"]);
|
||||
await git(repo, ["commit", "--allow-empty", "-m", "advance"]);
|
||||
const current = await git(repo, ["rev-parse", "HEAD"]);
|
||||
const error = await withWorkspaceRestoreDiagnostics("workspace", () => withWorkspaceRestoreStep("git_integration", () =>
|
||||
withWorkspaceRestoreGitCommand("update_ref", () => runLocalGit(repo, ["update-ref", "refs/heads/private-branch", base, base]))))
|
||||
.catch(error => error);
|
||||
expect(error).toMatchObject({ code: 128 });
|
||||
const diagnostic = getWorkspaceRestoreDiagnostic(error);
|
||||
expect(diagnostic).toEqual({ phase: "workspace", step: "git_integration", errorCode: "unknown", exitCode: 128,
|
||||
gitCommand: "update_ref", gitFailureKind: "ref_conflict" });
|
||||
expect(await git(repo, ["rev-parse", "HEAD"])).toBe(current);
|
||||
for (const privateValue of [repo, base, current, "private-branch"]) expect(JSON.stringify(diagnostic)).not.toContain(privateValue);
|
||||
});
|
||||
|
||||
it("preserves the real Git index reset exit code without attaching its raw error", async () => {
|
||||
const repo = await mkdtemp(path.join(os.tmpdir(), "paperclip-git-reset-diagnostic-"));
|
||||
cleanupDirs.push(repo);
|
||||
await git(repo, ["init"]);
|
||||
await writeFile(path.join(repo, "tracked.txt"), "base\n", "utf8");
|
||||
await git(repo, ["add", "tracked.txt"]);
|
||||
await git(repo, ["-c", "user.name=Setup", "-c", "user.email=setup@paperclip.dev", "commit", "-m", "base"]);
|
||||
await writeFile(path.join(repo, ".git", "index.lock"), "fixture lock\n", "utf8");
|
||||
const error = await withWorkspaceRestoreDiagnostics("workspace", () => withWorkspaceRestoreStep("index_reset", () =>
|
||||
resetLocalGitIndexToHead({ localDir: repo }))).catch((error: unknown) => error);
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect((error as Error).message).toContain("Failed to reset local git index");
|
||||
expect(error).not.toHaveProperty("cause");
|
||||
expect(getWorkspaceRestoreDiagnostic(error)).toEqual({ phase: "workspace", step: "index_reset", errorCode: "unknown", exitCode: 128 });
|
||||
});
|
||||
|
||||
describe("readReferencedSourceGitIgnoredPaths", () => {
|
||||
it("returns null for a directory that is not a Git work tree", async () => {
|
||||
const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-referenced-nogit-"));
|
||||
|
||||
@@ -5,6 +5,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { createWorkspaceManifest, workspacePaths, WorkspaceNulParser, type WorkspacePaths } from "./workspace-manifest.js";
|
||||
import { runWorkspaceGitProcess } from "./workspace-git-stream.js";
|
||||
import { preserveWorkspaceRestoreErrorDiagnostic, withWorkspaceRestoreGitCommand, type WorkspaceRestoreGitCommand } from "./workspace-restore-diagnostics.js";
|
||||
|
||||
export interface GitCommandResult {
|
||||
stdout: string;
|
||||
@@ -773,6 +774,12 @@ export function buildRemoteGitDeltaBundleScript(input: {
|
||||
` bundle_base=""`,
|
||||
"fi",
|
||||
]),
|
||||
// An empty bundle means "still at baseSha" to the importer. A reset to an
|
||||
// older ancestor has no delta commits either, but must carry its new tip.
|
||||
// Use the full-bundle path so Git advertises that tip instead of losing it.
|
||||
`if [ -n "$bundle_base" ] && [ "$bundle_base" != ${baseSha} ] && [ "$bundle_base" = "$(git -C ${remoteDir} rev-parse HEAD)" ]; then`,
|
||||
` bundle_base=""`,
|
||||
"fi",
|
||||
`if [ -n "$bundle_base" ]; then`,
|
||||
` commit_count=$(git -C ${remoteDir} rev-list --count HEAD --not "$bundle_base")`,
|
||||
"else",
|
||||
@@ -803,6 +810,14 @@ export function buildRemoteGitDeltaBundleScript(input: {
|
||||
].filter(Boolean).join("\n");
|
||||
}
|
||||
|
||||
// Labels are fixed at the integration call sites, never derived from Git arguments.
|
||||
function runIntegrationGit(
|
||||
command: WorkspaceRestoreGitCommand,
|
||||
...args: Parameters<typeof runLocalGit>
|
||||
): Promise<GitCommandResult> {
|
||||
return withWorkspaceRestoreGitCommand(command, () => runLocalGit(...args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Preserve imported work whose history does not connect to the local one.
|
||||
*
|
||||
@@ -824,11 +839,11 @@ export async function createUnrelatedHistoryGraftCommit(input: {
|
||||
importedHead: string;
|
||||
syncLabel: string;
|
||||
}): Promise<string> {
|
||||
const importedTree = (await runLocalGit(input.localDir, ["rev-parse", `${input.importedHead}^{tree}`], {
|
||||
const importedTree = (await runIntegrationGit("rev_parse", input.localDir, ["rev-parse", `${input.importedHead}^{tree}`], {
|
||||
timeout: 10_000,
|
||||
maxBuffer: 16 * 1024,
|
||||
})).stdout.trim();
|
||||
const importedMessage = (await runLocalGit(input.localDir, ["log", "-1", "--format=%B", input.importedHead], {
|
||||
const importedMessage = (await runIntegrationGit("log", input.localDir, ["log", "-1", "--format=%B", input.importedHead], {
|
||||
timeout: 10_000,
|
||||
maxBuffer: 256 * 1024,
|
||||
})).stdout;
|
||||
@@ -837,8 +852,8 @@ export async function createUnrelatedHistoryGraftCommit(input: {
|
||||
"",
|
||||
`(${input.syncLabel} graft ${input.importedHead.slice(0, 12)}: imported history shares no ancestor with ${input.currentHead.slice(0, 12)})`,
|
||||
].join("\n");
|
||||
const graftCommit = await runLocalGit(
|
||||
input.localDir,
|
||||
const graftCommit = await runIntegrationGit(
|
||||
"commit_tree", input.localDir,
|
||||
[...GIT_SYNC_COMMIT_IDENTITY_ARGS, "commit-tree", importedTree, "-p", input.currentHead, "-m", message],
|
||||
{
|
||||
timeout: 60_000,
|
||||
@@ -848,9 +863,68 @@ export async function createUnrelatedHistoryGraftCommit(input: {
|
||||
return graftCommit.stdout.trim();
|
||||
}
|
||||
|
||||
async function updateLocalGitHead(input: {
|
||||
localDir: string;
|
||||
newHead: string;
|
||||
oldHead: string;
|
||||
branchName: string | null;
|
||||
}): Promise<void> {
|
||||
// Prepare the ref transaction before checking the symbolic HEAD identity.
|
||||
// Git holds HEAD.lock (and the branch lock when attached) until commit/abort,
|
||||
// so a checkout cannot redirect the write after this check. --no-deref also
|
||||
// prevents a detached write from following a newly attached branch.
|
||||
await withWorkspaceRestoreGitCommand("update_ref", () => new Promise<void>((resolve, reject) => {
|
||||
let identityError: unknown;
|
||||
let prepared = false;
|
||||
let output = "";
|
||||
const child = execFile("git", ["-C", input.localDir, "update-ref", "--stdin"], {
|
||||
timeout: 15_000,
|
||||
maxBuffer: 64 * 1024,
|
||||
}, (error, stdout, stderr) => {
|
||||
if (identityError) reject(identityError);
|
||||
else if (error) reject(Object.assign(error, { stdout, stderr }));
|
||||
else if (!stdout.includes("commit: ok\n")) reject(new Error("Git HEAD transaction did not commit."));
|
||||
else resolve();
|
||||
});
|
||||
// Early Git errors close stdin; the process callback reports the error.
|
||||
child.stdin!.on("error", () => {});
|
||||
child.stdout!.on("data", (chunk: string | Buffer) => {
|
||||
output += chunk.toString();
|
||||
if (prepared || !output.includes("prepare: ok\n")) return;
|
||||
prepared = true;
|
||||
void (async () => {
|
||||
try {
|
||||
const branchName = (await runIntegrationGit("symbolic_ref", input.localDir, ["symbolic-ref", "--quiet", "--short", "HEAD"], {
|
||||
timeout: 10_000,
|
||||
}).catch((error) => {
|
||||
if (error.code === 1) return { stdout: "" };
|
||||
throw error;
|
||||
})).stdout.trim() || null;
|
||||
if (branchName !== input.branchName) {
|
||||
throw new Error("Workspace branch changed while remote work was running.");
|
||||
}
|
||||
child.stdin!.end("commit\n");
|
||||
} catch (error) {
|
||||
identityError = error;
|
||||
child.stdin!.end("abort\n");
|
||||
}
|
||||
})();
|
||||
});
|
||||
child.stdin!.write([
|
||||
"start",
|
||||
...(input.branchName === null ? ["option no-deref"] : []),
|
||||
`update HEAD ${input.newHead} ${input.oldHead}`,
|
||||
"prepare",
|
||||
"",
|
||||
].join("\n"));
|
||||
}));
|
||||
}
|
||||
|
||||
export async function integrateImportedGitHead(input: {
|
||||
localDir: string;
|
||||
importedHead: string;
|
||||
/** The host Git identity captured before staging this run. */
|
||||
baseline?: Pick<GitWorkspaceSnapshot, "headCommit" | "branchName">;
|
||||
}): Promise<void> {
|
||||
const isConcurrentRefUpdateError = (error: unknown) => {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
@@ -859,23 +933,25 @@ export async function integrateImportedGitHead(input: {
|
||||
|
||||
for (let attempt = 0; attempt < 5; attempt += 1) {
|
||||
const snapshot = {
|
||||
headCommit: (await runLocalGit(input.localDir, ["rev-parse", "HEAD"])).stdout.trim(),
|
||||
branchName: (await runLocalGit(input.localDir, ["symbolic-ref", "--quiet", "--short", "HEAD"]).catch((error) => {
|
||||
headCommit: (await runIntegrationGit("rev_parse", input.localDir, ["rev-parse", "HEAD"])).stdout.trim(),
|
||||
branchName: (await runIntegrationGit("symbolic_ref", input.localDir, ["symbolic-ref", "--quiet", "--short", "HEAD"]).catch((error) => {
|
||||
if (error.code === 1) return { stdout: "" };
|
||||
throw error;
|
||||
})).stdout.trim() || null,
|
||||
};
|
||||
|
||||
if (input.baseline && snapshot.branchName !== input.baseline.branchName) {
|
||||
throw new Error("Workspace branch changed while remote work was running.");
|
||||
}
|
||||
const currentHead = snapshot.headCommit;
|
||||
if (!currentHead || currentHead === input.importedHead) return;
|
||||
|
||||
const headRef = snapshot.branchName ? `refs/heads/${snapshot.branchName}` : "HEAD";
|
||||
// `git merge-base` exits 1 when the commits share no ancestor — the only
|
||||
// outcome that authorizes the graft fallback below. Every other failure
|
||||
// (timeout, missing object, repository error) must keep failing the
|
||||
// integration instead of silently rewriting the tip.
|
||||
let noCommonAncestor = false;
|
||||
const mergeBase = await runLocalGit(input.localDir, ["merge-base", currentHead, input.importedHead], {
|
||||
const mergeBase = await runIntegrationGit("merge_base", input.localDir, ["merge-base", currentHead, input.importedHead], {
|
||||
timeout: 10_000,
|
||||
maxBuffer: 16 * 1024,
|
||||
}).catch((error: unknown) => {
|
||||
@@ -884,15 +960,15 @@ export async function integrateImportedGitHead(input: {
|
||||
});
|
||||
const mergeBaseHead = mergeBase?.stdout.trim() ?? "";
|
||||
|
||||
if (mergeBaseHead === input.importedHead) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mergeBaseHead === currentHead) {
|
||||
// A rebase/amend rewrites the sandbox tip without a concurrent host edit.
|
||||
// Adopt that history when the host still matches the run's starting tip;
|
||||
// merging the old and rewritten commits can reintroduce resolved conflicts.
|
||||
// Keep the expected-old-value check: if the host advances during this write,
|
||||
// retry against its new tip and use the normal concurrent-history path.
|
||||
if (mergeBaseHead === currentHead || (mergeBaseHead && currentHead === input.baseline?.headCommit)) {
|
||||
try {
|
||||
await runLocalGit(input.localDir, ["update-ref", headRef, input.importedHead, currentHead], {
|
||||
timeout: 10_000,
|
||||
maxBuffer: 16 * 1024,
|
||||
await updateLocalGitHead({
|
||||
localDir: input.localDir, newHead: input.importedHead, oldHead: currentHead, branchName: snapshot.branchName,
|
||||
});
|
||||
return;
|
||||
} catch (error) {
|
||||
@@ -901,7 +977,14 @@ export async function integrateImportedGitHead(input: {
|
||||
}
|
||||
}
|
||||
|
||||
if (mergeBaseHead === input.importedHead) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (noCommonAncestor) {
|
||||
if (input.baseline && currentHead !== input.baseline.headCommit) {
|
||||
throw new Error("Cannot restore unrelated remote history after the host advanced.");
|
||||
}
|
||||
// No common ancestor — merging is impossible and failing here would
|
||||
// discard the imported work. Graft it onto the current head instead;
|
||||
// see createUnrelatedHistoryGraftCommit.
|
||||
@@ -912,9 +995,8 @@ export async function integrateImportedGitHead(input: {
|
||||
syncLabel: "Paperclip remote git sync",
|
||||
});
|
||||
try {
|
||||
await runLocalGit(input.localDir, ["update-ref", headRef, graftCommit, currentHead], {
|
||||
timeout: 10_000,
|
||||
maxBuffer: 16 * 1024,
|
||||
await updateLocalGitHead({
|
||||
localDir: input.localDir, newHead: graftCommit, oldHead: currentHead, branchName: snapshot.branchName,
|
||||
});
|
||||
return;
|
||||
} catch (error) {
|
||||
@@ -925,23 +1007,23 @@ export async function integrateImportedGitHead(input: {
|
||||
|
||||
let mergedTree;
|
||||
try {
|
||||
mergedTree = await runLocalGit(input.localDir, ["merge-tree", "--write-tree", currentHead, input.importedHead], {
|
||||
mergedTree = await runIntegrationGit("merge_tree", input.localDir, ["merge-tree", "--write-tree", currentHead, input.importedHead], {
|
||||
timeout: 60_000,
|
||||
maxBuffer: 256 * 1024,
|
||||
});
|
||||
} catch (error) {
|
||||
const reason = error instanceof Error ? error.message : String(error);
|
||||
throw new Error(
|
||||
throw preserveWorkspaceRestoreErrorDiagnostic(new Error(
|
||||
`Failed to merge concurrent remote git histories for ${currentHead.slice(0, 12)} and ${input.importedHead.slice(0, 12)}: ${reason}`,
|
||||
);
|
||||
), error);
|
||||
}
|
||||
const mergedTreeId = mergedTree.stdout.trim().split("\n")[0]?.trim() ?? "";
|
||||
if (!mergedTreeId) {
|
||||
throw new Error("Failed to compute a merged git tree for workspace restore.");
|
||||
}
|
||||
|
||||
const mergeCommit = await runLocalGit(
|
||||
input.localDir,
|
||||
const mergeCommit = await runIntegrationGit(
|
||||
"commit_tree", input.localDir,
|
||||
[
|
||||
...GIT_SYNC_COMMIT_IDENTITY_ARGS,
|
||||
"commit-tree",
|
||||
@@ -959,9 +1041,8 @@ export async function integrateImportedGitHead(input: {
|
||||
},
|
||||
);
|
||||
try {
|
||||
await runLocalGit(input.localDir, ["update-ref", headRef, mergeCommit.stdout.trim(), currentHead], {
|
||||
timeout: 10_000,
|
||||
maxBuffer: 16 * 1024,
|
||||
await updateLocalGitHead({
|
||||
localDir: input.localDir, newHead: mergeCommit.stdout.trim(), oldHead: currentHead, branchName: snapshot.branchName,
|
||||
});
|
||||
return;
|
||||
} catch (error) {
|
||||
@@ -990,7 +1071,9 @@ export async function resetLocalGitIndexToHead(input: {
|
||||
(error as { stdout?: unknown }).stdout,
|
||||
].filter((value): value is string => typeof value === "string" && value.trim().length > 0).join("\n")
|
||||
: String(error);
|
||||
throw new Error(`Failed to reset local git index to HEAD after workspace restore: ${detail}`);
|
||||
throw preserveWorkspaceRestoreErrorDiagnostic(
|
||||
new Error(`Failed to reset local git index to HEAD after workspace restore: ${detail}`), error,
|
||||
);
|
||||
}
|
||||
|
||||
const hasDiff = async (args: string[]) => {
|
||||
|
||||
@@ -3,13 +3,14 @@ import { selectPaperclipPromptSections as selectSections } from "./server-utils.
|
||||
import { createPromptContextFixture } from "./test-fixtures/prompt-context.js";
|
||||
|
||||
describe("task and event section ownership", () => {
|
||||
it("lets a separate instruction carrier own the execution contract on a resumed turn", () => {
|
||||
it("preserves resumed wake data without reintroducing generic procedures", () => {
|
||||
const context = createPromptContextFixture();
|
||||
const sections = selectSections(context, { resumedSession: true, includeExecutionContract: false });
|
||||
expect(sections.taskContextNote).toBe(context.paperclipTaskMarkdownAssignmentCompact);
|
||||
expect(sections.wakePrompt).toContain('"id":"comment-second"');
|
||||
expect(sections.wakePrompt).not.toContain("Execution contract:");
|
||||
expect(selectSections(context, { resumedSession: true }).wakePrompt).toContain("Execution contract:");
|
||||
for (const includeExecutionContract of [undefined, false, true]) {
|
||||
const sections = selectSections(context, { resumedSession: true, includeExecutionContract });
|
||||
expect(sections.taskContextNote).toBe(context.paperclipTaskMarkdownAssignmentCompact);
|
||||
expect(sections.wakePrompt).toContain('"id":"comment-second"');
|
||||
expect(sections.wakePrompt).not.toContain("Execution contract:");
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves user repetition and distinct same-body comments under their source owners", () => {
|
||||
|
||||
@@ -6,6 +6,9 @@ import path from "node:path";
|
||||
import { execFile as execFileCallback, spawn } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import * as gitSync from "./git-workspace-sync.js";
|
||||
import * as restoreMerge from "./workspace-restore-merge.js";
|
||||
import { getWorkspaceRestoreDiagnostic } from "./workspace-restore-diagnostics.js";
|
||||
import {
|
||||
resetLocalGitIndexToHead,
|
||||
runLocalGit,
|
||||
@@ -835,6 +838,93 @@ describe("sandbox managed runtime", () => {
|
||||
},
|
||||
);
|
||||
|
||||
it("restores a sandbox rebase without merging it with unchanged host history", async () => {
|
||||
const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-sandbox-rebase-"));
|
||||
cleanupDirs.push(rootDir);
|
||||
const host = path.join(rootDir, "host");
|
||||
const remote = path.join(rootDir, "remote");
|
||||
await mkdir(host);
|
||||
await git(host, ["init"]);
|
||||
await git(host, ["checkout", "-b", "work"]);
|
||||
await git(host, ["config", "user.name", "Paperclip Test"]);
|
||||
await git(host, ["config", "user.email", "test@paperclip.dev"]);
|
||||
await writeFile(path.join(host, "pins.txt"), "base\n");
|
||||
await writeFile(path.join(host, "notes.txt"), "original notes\n");
|
||||
await git(host, ["add", "."]);
|
||||
await git(host, ["commit", "-m", "base"]);
|
||||
const base = await git(host, ["rev-parse", "HEAD"]);
|
||||
await writeFile(path.join(host, "pins.txt"), "feature\n");
|
||||
await git(host, ["commit", "-am", "feature"]);
|
||||
const startingHead = await git(host, ["rev-parse", "HEAD"]);
|
||||
await git(host, ["checkout", "-b", "upstream", base]);
|
||||
await writeFile(path.join(host, "pins.txt"), "upstream\n");
|
||||
await git(host, ["commit", "-am", "upstream"]);
|
||||
await git(host, ["checkout", "work"]);
|
||||
|
||||
const prepared = await prepareSandboxManagedRuntime({
|
||||
spec: { transport: "sandbox", provider: "test", sandboxId: "rebase", remoteCwd: remote, timeoutMs: 30_000, apiKey: null },
|
||||
adapterKey: "test-adapter",
|
||||
client: makeFilesystemClient(),
|
||||
workspaceLocalDir: host,
|
||||
});
|
||||
await git(remote, ["config", "user.name", "Paperclip Test"]);
|
||||
await git(remote, ["config", "user.email", "test@paperclip.dev"]);
|
||||
await git(remote, ["fetch", "--unshallow", host, "+refs/heads/*:refs/remotes/source/*"]);
|
||||
await expect(git(remote, ["rebase", "refs/remotes/source/upstream"])).rejects.toMatchObject({ code: 1 });
|
||||
await writeFile(path.join(remote, "pins.txt"), "resolved pins\n");
|
||||
await git(remote, ["add", "pins.txt"]);
|
||||
await git(remote, ["-c", "core.editor=true", "rebase", "--continue"]);
|
||||
const rebasedHead = await git(remote, ["rev-parse", "HEAD"]);
|
||||
expect(rebasedHead).not.toBe(startingHead);
|
||||
await writeFile(path.join(host, "notes.txt"), "host edits during the run\n");
|
||||
await writeFile(path.join(host, "local-only.txt"), "local file\n");
|
||||
await writeFile(path.join(remote, "output.txt"), "sandbox output\n");
|
||||
|
||||
await prepared.restoreWorkspace();
|
||||
|
||||
expect(await git(host, ["rev-parse", "HEAD"])).toBe(rebasedHead);
|
||||
expect(await git(host, ["symbolic-ref", "--short", "HEAD"])).toBe("work");
|
||||
expect(await readFile(path.join(host, "pins.txt"), "utf8")).toBe("resolved pins\n");
|
||||
expect(await readFile(path.join(host, "notes.txt"), "utf8")).toBe("host edits during the run\n");
|
||||
expect(await readFile(path.join(host, "local-only.txt"), "utf8")).toBe("local file\n");
|
||||
expect(await readFile(path.join(host, "output.txt"), "utf8")).toBe("sandbox output\n");
|
||||
expect(await git(host, ["diff", "--cached", "--name-only"])).toBe("");
|
||||
});
|
||||
|
||||
it("restores a sandbox reset to an ancestor with the matching clean working tree", async () => {
|
||||
const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-sandbox-reset-"));
|
||||
cleanupDirs.push(rootDir);
|
||||
const host = path.join(rootDir, "host");
|
||||
const remote = path.join(rootDir, "remote");
|
||||
await mkdir(host);
|
||||
await git(host, ["init"]);
|
||||
await git(host, ["checkout", "-b", "work"]);
|
||||
await git(host, ["config", "user.name", "Paperclip Test"]);
|
||||
await git(host, ["config", "user.email", "test@paperclip.dev"]);
|
||||
await writeFile(path.join(host, "tracked.txt"), "original\n");
|
||||
await git(host, ["add", "."]);
|
||||
await git(host, ["commit", "-m", "original"]);
|
||||
const ancestor = await git(host, ["rev-parse", "HEAD"]);
|
||||
await writeFile(path.join(host, "tracked.txt"), "change to discard\n");
|
||||
await git(host, ["commit", "-am", "later change"]);
|
||||
|
||||
const prepared = await prepareSandboxManagedRuntime({
|
||||
spec: { transport: "sandbox", provider: "test", sandboxId: "reset", remoteCwd: remote, timeoutMs: 30_000, apiKey: null },
|
||||
adapterKey: "test-adapter",
|
||||
client: makeFilesystemClient(),
|
||||
workspaceLocalDir: host,
|
||||
});
|
||||
await git(remote, ["fetch", "--unshallow", host, "work"]);
|
||||
await git(remote, ["reset", "--hard", ancestor]);
|
||||
|
||||
await prepared.restoreWorkspace();
|
||||
|
||||
expect(await git(host, ["rev-parse", "HEAD"])).toBe(ancestor);
|
||||
expect(await git(host, ["symbolic-ref", "--short", "HEAD"])).toBe("work");
|
||||
expect(await readFile(path.join(host, "tracked.txt"), "utf8")).toBe("original\n");
|
||||
expect(await git(host, ["status", "--porcelain"])).toBe("");
|
||||
});
|
||||
|
||||
it("syncs git-backed workspaces through a shallow standalone clone and keeps .git out of archives", async () => {
|
||||
const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-sandbox-git-"));
|
||||
cleanupDirs.push(rootDir);
|
||||
@@ -3975,14 +4065,14 @@ describe("sandbox managed runtime outbound coordinator", () => {
|
||||
]);
|
||||
}
|
||||
|
||||
it("records each failed restore phase without changing stable failure ordering", async () => {
|
||||
it.each([false, true])("records each failed restore phase in stable order (shared error: %s)", async (sharedError) => {
|
||||
const { workspaceDir, remoteWorkspaceDir, dirOf } = await makeOutboundDirs(["home", "private-asset"]);
|
||||
const { control, gate } = makeOutboundControl();
|
||||
const client = makeGatedOutboundClient(true, gate);
|
||||
const workspaceError = Object.assign(new Error("private workspace path"), { code: "EACCES" });
|
||||
control.failWith("workspace", workspaceError);
|
||||
control.failWith("home", Object.assign(new Error("private credential"), { status: 404 }));
|
||||
control.failWith("private-asset", new Error("private asset error"));
|
||||
control.failWith("home", sharedError ? workspaceError : Object.assign(new Error("private credential"), { status: 404 }));
|
||||
control.failWith("private-asset", sharedError ? workspaceError : new Error("private asset error"));
|
||||
const prepared = await prepareSandboxManagedRuntime({
|
||||
spec: makeSpec(remoteWorkspaceDir), adapterKey: "codex", client, workspaceLocalDir: workspaceDir,
|
||||
assets: [
|
||||
@@ -3995,11 +4085,14 @@ describe("sandbox managed runtime outbound coordinator", () => {
|
||||
const diagnostics = lines.filter((line) => line.includes("Workspace restore diagnostic:"));
|
||||
expect(diagnostics).toHaveLength(3);
|
||||
expect(diagnostics).toEqual(expect.arrayContaining([
|
||||
'[paperclip] Workspace restore diagnostic: {"phase":"workspace","errorCode":"EACCES"}\n',
|
||||
'[paperclip] Workspace restore diagnostic: {"phase":"asset","errorCode":"unknown","httpStatus":404}\n',
|
||||
'[paperclip] Workspace restore diagnostic: {"phase":"asset","errorCode":"unknown"}\n',
|
||||
'[paperclip] Workspace restore diagnostic: {"phase":"workspace","step":"workspace_transfer","errorCode":"EACCES"}\n',
|
||||
sharedError ? '[paperclip] Workspace restore diagnostic: {"phase":"asset","step":"asset_restore","errorCode":"EACCES"}\n'
|
||||
: '[paperclip] Workspace restore diagnostic: {"phase":"asset","step":"asset_restore","errorCode":"unknown","httpStatus":404}\n',
|
||||
sharedError ? '[paperclip] Workspace restore diagnostic: {"phase":"asset","step":"asset_restore","errorCode":"EACCES"}\n'
|
||||
: '[paperclip] Workspace restore diagnostic: {"phase":"asset","step":"asset_restore","errorCode":"unknown"}\n',
|
||||
]));
|
||||
expect(diagnostics.join("")).not.toContain("private");
|
||||
expect(getWorkspaceRestoreDiagnostic(workspaceError)).toEqual({ phase: "workspace", step: "workspace_transfer", errorCode: "EACCES" });
|
||||
expect(control.settled).toEqual(expect.arrayContaining(["workspace", "home", "private-asset"]));
|
||||
});
|
||||
|
||||
@@ -4355,6 +4448,48 @@ describe("sandbox git-bundle export transport", () => {
|
||||
.filter((mapping) => path.posix.basename(mapping.sourcePath) === "git-delta.bundle");
|
||||
}
|
||||
|
||||
it.each([
|
||||
"git_export", "git_import", "workspace_transfer", "workspace_extract",
|
||||
"directory_merge", "git_integration", "index_reset", "git_ref_cleanup",
|
||||
] as const)("identifies the actual %s failure during workspace restore", async (step) => {
|
||||
const { localWorkspaceDir, remoteWorkspaceDir } = await setupGitBackedWorkspace("paperclip-restore-diagnostic-");
|
||||
const client = makeTransportClient(step !== "workspace_extract", { syncOutOperations: [], readFilePaths: [] });
|
||||
const prepared = await prepareSandboxManagedRuntime({
|
||||
spec: gitSpec(remoteWorkspaceDir), adapterKey: "test-adapter", client, workspaceLocalDir: localWorkspaceDir,
|
||||
});
|
||||
await commitInSandbox(remoteWorkspaceDir);
|
||||
const failure = Object.assign(new Error("private-restore-command /private-restore-path"), { code: 7, stderr: "private-restore-stderr" });
|
||||
if (step === "git_export") client.run = async () => { throw failure; };
|
||||
if (step === "git_import") vi.spyOn(gitSync, "fetchGitBundleIntoLocalRef").mockRejectedValueOnce(failure);
|
||||
if (step === "directory_merge") vi.spyOn(restoreMerge, "mergeDirectoryWithBaseline").mockRejectedValueOnce(failure);
|
||||
if (step === "git_integration") vi.spyOn(gitSync, "integrateImportedGitHead").mockRejectedValueOnce(failure);
|
||||
if (step === "index_reset") vi.spyOn(gitSync, "resetLocalGitIndexToHead").mockRejectedValueOnce(failure);
|
||||
if (step === "git_ref_cleanup") vi.spyOn(gitSync, "deleteLocalGitRef").mockRejectedValueOnce(failure);
|
||||
if (step === "workspace_transfer") {
|
||||
const syncOut = client.syncOut!;
|
||||
client.syncOut = async (operations) => {
|
||||
if (operations.some((operation) => operation.files.some((file) => file.kind === "directory"))) throw failure;
|
||||
return syncOut(operations);
|
||||
};
|
||||
}
|
||||
if (step === "workspace_extract") {
|
||||
const readRemote = client.readFile;
|
||||
client.readFile = async (remotePath, options) => remotePath.endsWith("workspace-download.tar")
|
||||
? Buffer.from("invalid archive") : readRemote(remotePath, options);
|
||||
}
|
||||
const lines: string[] = [];
|
||||
try {
|
||||
const thrown = await prepared.restoreWorkspace((line) => { lines.push(line); }).then(() => undefined, (error: unknown) => error);
|
||||
expect(thrown).toBeInstanceOf(Error);
|
||||
if (step !== "workspace_extract") expect(thrown).toBe(failure);
|
||||
expect(getWorkspaceRestoreDiagnostic(thrown)).toMatchObject({ phase: "workspace", step, errorCode: "unknown" });
|
||||
if (step !== "workspace_extract") expect(getWorkspaceRestoreDiagnostic(thrown)?.exitCode).toBe(7);
|
||||
const diagnostics = lines.filter((line) => line.includes("Workspace restore diagnostic:"));
|
||||
expect(diagnostics).toHaveLength(1);
|
||||
expect(diagnostics[0]).not.toContain("private-restore-");
|
||||
} finally { vi.restoreAllMocks(); }
|
||||
});
|
||||
|
||||
it("moves the bundle through one native syncOut file mapping, never through readFile", async () => {
|
||||
const capture: TransportCapture = { syncOutOperations: [], readFilePaths: [] };
|
||||
const { localWorkspaceDir, remoteWorkspaceDir } = await setupGitBackedWorkspace("paperclip-bundle-native-");
|
||||
|
||||
@@ -49,7 +49,10 @@ import {
|
||||
type SyncOperationTask,
|
||||
} from "./sync-operation-schedule.js";
|
||||
import type { RuntimeSpanRunner } from "./acpx-engine/startup-timing.js";
|
||||
import { withWorkspaceRestoreDiagnostics } from "./workspace-restore-diagnostics.js";
|
||||
import {
|
||||
recordWorkspaceRestoreDiagnostic, withWorkspaceRestoreDiagnostics, withWorkspaceRestoreStep,
|
||||
type WorkspaceRestoreDiagnostic,
|
||||
} from "./workspace-restore-diagnostics.js";
|
||||
|
||||
const execFile = promisify(execFileCallback);
|
||||
const SANDBOX_WORKSPACE_HEAVY_DIR_NAMES = [
|
||||
@@ -1717,6 +1720,7 @@ export async function prepareSandboxManagedRuntime(input: {
|
||||
// started task before it returns, so lease teardown never starts while an
|
||||
// outbound restore task still writes host data.
|
||||
const outboundTasks: Array<SyncOperationTask<void>> = [];
|
||||
const outboundDiagnostics: Array<WorkspaceRestoreDiagnostic | undefined> = [];
|
||||
|
||||
// The workspace restore task runs only when the run syncs the workspace.
|
||||
// The task exports the sandbox git history (git-backed workspace), reads
|
||||
@@ -1780,7 +1784,7 @@ export async function prepareSandboxManagedRuntime(input: {
|
||||
// the import fails on a missing prerequisite. In that case re-export
|
||||
// a full, self-contained bundle from the still-live sandbox rather
|
||||
// than discard the completed run.
|
||||
const exportAndImport = async (forceFullBundle: boolean): Promise<string> => {
|
||||
const exportAndImport = async (forceFullBundle: boolean): Promise<string> => withWorkspaceRestoreStep("git_export", async () => {
|
||||
await input.client.run(
|
||||
`sh -c ${shellQuote(buildRemoteGitDeltaBundleScript({
|
||||
remoteDir: workspaceRemoteDir,
|
||||
@@ -1838,14 +1842,14 @@ export async function prepareSandboxManagedRuntime(input: {
|
||||
remoteWorkspaceStatus = remoteWorkspaceStatus === "clean" ? "clean" : "dirty";
|
||||
await input.client.remove(remoteWorkspaceStatusPath).catch(() => undefined);
|
||||
}
|
||||
return fetchGitBundleIntoLocalRef({
|
||||
return withWorkspaceRestoreStep("git_import", () => fetchGitBundleIntoLocalRef({
|
||||
localDir: input.workspaceLocalDir,
|
||||
bundlePath: localBundlePath,
|
||||
exportRef,
|
||||
importedRef: importedRef!,
|
||||
baseSha: gitSnapshot.headCommit,
|
||||
});
|
||||
};
|
||||
}));
|
||||
});
|
||||
|
||||
try {
|
||||
importedHead = await exportAndImport(false);
|
||||
@@ -1885,19 +1889,19 @@ export async function prepareSandboxManagedRuntime(input: {
|
||||
"workspace",
|
||||
{ sink: input.onRuntimeProgress, phase: "restore" },
|
||||
);
|
||||
const syncResult = await input.client.syncOut!(operations);
|
||||
const syncResult = await withWorkspaceRestoreStep("workspace_transfer", () => input.client.syncOut!(operations));
|
||||
const transferredBytes = sumSyncResultBytes(syncResult);
|
||||
await workspaceRestore.finish(transferredBytes, transferredBytes);
|
||||
} else {
|
||||
const remoteWorkspaceTar = path.posix.join(runtimeRootDir, "workspace-download.tar");
|
||||
await input.client.run(
|
||||
await withWorkspaceRestoreStep("workspace_transfer", () => input.client.run(
|
||||
`sh -c ${shellQuote(createRemoteTarballFromDirectoryCommand({
|
||||
remoteDir: workspaceRemoteDir,
|
||||
archivePath: remoteWorkspaceTar,
|
||||
exclude: workspaceRestoreExclude,
|
||||
}))}`,
|
||||
{ timeoutMs: input.spec.timeoutMs },
|
||||
);
|
||||
));
|
||||
const workspaceRestore = makeTransferProgress(
|
||||
restoreSink,
|
||||
"Restoring",
|
||||
@@ -1905,52 +1909,57 @@ export async function prepareSandboxManagedRuntime(input: {
|
||||
"workspace",
|
||||
{ sink: input.onRuntimeProgress, phase: "restore" },
|
||||
);
|
||||
const archiveBytes = await input.client.readFile(remoteWorkspaceTar, workspaceRestore.options);
|
||||
const archiveBytes = await withWorkspaceRestoreStep("workspace_transfer", () => input.client.readFile(remoteWorkspaceTar, workspaceRestore.options));
|
||||
const archiveBuffer = toBuffer(archiveBytes);
|
||||
await workspaceRestore.finish(archiveBuffer.byteLength, archiveBuffer.byteLength);
|
||||
await input.client.remove(remoteWorkspaceTar).catch(() => undefined);
|
||||
const localArchivePath = path.join(tempDir, "workspace.tar");
|
||||
await fs.writeFile(localArchivePath, archiveBuffer);
|
||||
await extractTarballToDirectory({
|
||||
archivePath: localArchivePath,
|
||||
localDir: extractedDir,
|
||||
await withWorkspaceRestoreStep("workspace_extract", async () => {
|
||||
await fs.writeFile(localArchivePath, archiveBuffer);
|
||||
await extractTarballToDirectory({
|
||||
archivePath: localArchivePath,
|
||||
localDir: extractedDir,
|
||||
});
|
||||
});
|
||||
}
|
||||
const gitHeadToIntegrate = importedHead;
|
||||
const mergeBaseline = repositories.length === 0 ? baselineSnapshot! : await selectDirectorySnapshot(baselineSnapshot!, {
|
||||
omit: repositories.map((repo) => repo.path), exclude: workspaceRestoreExclude, ignoredPaths: gitSnapshot?.ignoredPaths,
|
||||
await withWorkspaceRestoreStep("directory_merge", async () => {
|
||||
const mergeBaseline = repositories.length === 0 ? baselineSnapshot! : await selectDirectorySnapshot(baselineSnapshot!, {
|
||||
omit: repositories.map((repo) => repo.path), exclude: workspaceRestoreExclude, ignoredPaths: gitSnapshot?.ignoredPaths,
|
||||
});
|
||||
try {
|
||||
await mergeDirectoryWithBaseline({
|
||||
baseline: mergeBaseline,
|
||||
sourceDir: extractedDir,
|
||||
targetDir: input.workspaceLocalDir,
|
||||
beforeApply: gitHeadToIntegrate
|
||||
? async () => {
|
||||
await withWorkspaceRestoreStep("git_integration", () => integrateImportedGitHead({
|
||||
localDir: input.workspaceLocalDir,
|
||||
importedHead: gitHeadToIntegrate,
|
||||
baseline: gitSnapshot ?? undefined,
|
||||
}));
|
||||
}
|
||||
: undefined,
|
||||
afterApply: gitSnapshot
|
||||
? async () => {
|
||||
await withWorkspaceRestoreStep("index_reset", () => resetLocalGitIndexToHead({
|
||||
localDir: input.workspaceLocalDir,
|
||||
checkWorkingTreeClean: remoteWorkspaceStatus === "clean",
|
||||
}));
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
} finally { if (mergeBaseline !== baselineSnapshot) await disposeDirectorySnapshot(mergeBaseline); }
|
||||
});
|
||||
try {
|
||||
await mergeDirectoryWithBaseline({
|
||||
baseline: mergeBaseline,
|
||||
sourceDir: extractedDir,
|
||||
targetDir: input.workspaceLocalDir,
|
||||
beforeApply: gitHeadToIntegrate
|
||||
? async () => {
|
||||
await integrateImportedGitHead({
|
||||
localDir: input.workspaceLocalDir,
|
||||
importedHead: gitHeadToIntegrate,
|
||||
});
|
||||
}
|
||||
: undefined,
|
||||
afterApply: gitSnapshot
|
||||
? async () => {
|
||||
await resetLocalGitIndexToHead({
|
||||
localDir: input.workspaceLocalDir,
|
||||
checkWorkingTreeClean: remoteWorkspaceStatus === "clean",
|
||||
});
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
} finally { if (mergeBaseline !== baselineSnapshot) await disposeDirectorySnapshot(mergeBaseline); }
|
||||
} finally {
|
||||
await emitRuntimeStatus(input.onRuntimeProgress, "finalize", "Finalizing workspace");
|
||||
if (importedRef) {
|
||||
await deleteLocalGitRef({ localDir: input.workspaceLocalDir, ref: importedRef });
|
||||
await withWorkspaceRestoreStep("git_ref_cleanup", () => deleteLocalGitRef({ localDir: input.workspaceLocalDir, ref: importedRef! }));
|
||||
}
|
||||
}
|
||||
});
|
||||
}), restoreSink),
|
||||
}), restoreSink, (diagnostic) => { outboundDiagnostics[0] = diagnostic; }),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1961,18 +1970,19 @@ export async function prepareSandboxManagedRuntime(input: {
|
||||
if (!asset.restore) continue;
|
||||
const assetRestore = asset.restore;
|
||||
const assetKey = asset.key;
|
||||
const taskIndex = outboundTasks.length;
|
||||
outboundTasks.push(() =>
|
||||
withWorkspaceRestoreDiagnostics(
|
||||
"asset",
|
||||
() => runStepSpan(`restore.asset.${assetKey}`, async () => {
|
||||
await withTempDir("paperclip-sandbox-restore-", async (tempDir) => {
|
||||
await assetRestore({
|
||||
await withWorkspaceRestoreStep("asset_restore", () => assetRestore({
|
||||
assetDir: path.posix.join(runtimeRootDir, assetKey),
|
||||
readFile: async (remotePath) => toBuffer(await input.client.readFile(remotePath)),
|
||||
tempDir,
|
||||
});
|
||||
}));
|
||||
});
|
||||
}), restoreSink),
|
||||
}), restoreSink, (diagnostic) => { outboundDiagnostics[taskIndex] = diagnostic; }),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1989,8 +1999,9 @@ export async function prepareSandboxManagedRuntime(input: {
|
||||
// Every outbound restore is a required operation: a rejection is fatal.
|
||||
// The workspace comes first, then each asset in order. Raise the first
|
||||
// rejection in stable task order, so two failures raise the earlier one.
|
||||
for (const result of outboundResults) {
|
||||
for (const [index, result] of outboundResults.entries()) {
|
||||
if (result.status === "rejected") {
|
||||
recordWorkspaceRestoreDiagnostic(result.reason, outboundDiagnostics[index]);
|
||||
throw result.reason;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -925,7 +925,7 @@ describe("renderPaperclipWakePrompt", () => {
|
||||
fallbackFetchNeeded: false,
|
||||
};
|
||||
const ordinary = renderPaperclipWakePrompt(payload, { resumedSession: true });
|
||||
expect(ordinary).toContain("Execution contract:");
|
||||
expect(ordinary).not.toContain("Execution contract:");
|
||||
expect(ordinary).toContain("Create child issues from the approved plan");
|
||||
for (const resumedSession of [false, true]) {
|
||||
const chat = renderPaperclipWakePrompt(payload, {
|
||||
@@ -1171,7 +1171,7 @@ describe("renderPaperclipWakePrompt", () => {
|
||||
expect(incompleteResumePrompt).toContain(
|
||||
"[continuation summary truncated]",
|
||||
);
|
||||
expect(incompleteResumePrompt).toContain(
|
||||
expect(incompleteResumePrompt).not.toContain(
|
||||
"a successful process exit or final response is not sufficient",
|
||||
);
|
||||
});
|
||||
@@ -1410,76 +1410,18 @@ describe("renderPaperclipWakePrompt", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps the default local-agent prompt action-oriented", () => {
|
||||
expect(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE).toContain(
|
||||
"Start actionable work in this heartbeat",
|
||||
);
|
||||
expect(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE).toContain(
|
||||
"do not stop at a plan",
|
||||
);
|
||||
expect(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE).toContain(
|
||||
"clear final disposition",
|
||||
);
|
||||
expect(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE).toContain(
|
||||
"evidence, not valid liveness paths by themselves",
|
||||
);
|
||||
expect(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE).toContain(
|
||||
"keep `in_progress` only when a live continuation path exists",
|
||||
);
|
||||
expect(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE).toContain(
|
||||
"Prefer the smallest verification that proves the change",
|
||||
);
|
||||
expect(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE).toContain(
|
||||
"After 2 consecutive failures of the same control-plane write",
|
||||
);
|
||||
expect(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE).toContain(
|
||||
"adapter/runtime status channel as the sanctioned fallback",
|
||||
);
|
||||
expect(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE).toContain(
|
||||
"Use child issues",
|
||||
);
|
||||
expect(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE).toContain(
|
||||
"instead of polling agents, sessions, or processes",
|
||||
);
|
||||
expect(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE).toContain(
|
||||
"Create child issues directly when you know what needs to be done",
|
||||
);
|
||||
expect(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE).toContain(
|
||||
"POST /api/issues/$PAPERCLIP_TASK_ID/interactions",
|
||||
);
|
||||
// URL paths in prompt text carry real ids or env vars, never brace
|
||||
// placeholders: agents paste these lines verbatim, and a literal {issueId}
|
||||
// reaches the server as /api/issues/%7BissueId%7D and 404s.
|
||||
expect(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE).not.toContain(
|
||||
"/api/issues/{issueId}",
|
||||
);
|
||||
expect(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE).not.toContain(
|
||||
"/api/issues/{id}",
|
||||
);
|
||||
expect(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE).toContain(
|
||||
"kind suggest_tasks, ask_user_questions, or request_confirmation",
|
||||
);
|
||||
expect(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE).toContain(
|
||||
"Use continuationPolicy wake_assignee when you need to resume after a response (it wakes on acceptance and rejection alike; only expiry does not wake); use wake_assignee_on_accept when you want to resume only after acceptance",
|
||||
);
|
||||
expect(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE).not.toContain(
|
||||
"for request_confirmation this resumes only after acceptance",
|
||||
);
|
||||
expect(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE).toContain(
|
||||
"Never create probe or throwaway issue-thread interactions to discover the interactions API shape or your permissions",
|
||||
);
|
||||
expect(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE).toContain(
|
||||
"confirmation:{issueId}:plan:{revisionId}",
|
||||
);
|
||||
expect(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE).toContain(
|
||||
"Wait for acceptance before creating implementation subtasks",
|
||||
);
|
||||
expect(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE).toContain(
|
||||
"Respect budget, pause/cancel, approval gates, and company boundaries",
|
||||
);
|
||||
it("keeps task and chat defaults to identity and connection guidance", () => {
|
||||
for (const template of [
|
||||
DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE,
|
||||
DEFAULT_PAPERCLIP_CONVERSATION_PROMPT_TEMPLATE,
|
||||
]) {
|
||||
expect(template).toBe(
|
||||
`You are agent {{agent.id}} ({{agent.name}}).\n\n${CONNECTION_INTENT_AGENT_GUIDANCE}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("leaves the execution contract to the heartbeat template on fresh scoped wake prompts", () => {
|
||||
it("keeps current task data without generic procedures on fresh scoped wake prompts", () => {
|
||||
const prompt = renderPaperclipWakePrompt({
|
||||
reason: "issue_assigned",
|
||||
issue: {
|
||||
@@ -1499,12 +1441,12 @@ describe("renderPaperclipWakePrompt", () => {
|
||||
|
||||
expect(prompt).toContain("## Paperclip Wake Payload");
|
||||
expect(prompt).not.toContain("Execution contract:");
|
||||
expect(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE).toContain(
|
||||
expect(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE).not.toContain(
|
||||
"Execution contract:",
|
||||
);
|
||||
});
|
||||
|
||||
it("adds the execution contract to resume delta prompts and opted-in fresh prompts", () => {
|
||||
it("does not restore generic procedures on resume or with the legacy opt-in", () => {
|
||||
const payload = {
|
||||
reason: "issue_assigned",
|
||||
issue: {
|
||||
@@ -1522,36 +1464,19 @@ describe("renderPaperclipWakePrompt", () => {
|
||||
fallbackFetchNeeded: false,
|
||||
};
|
||||
|
||||
for (const prompt of [
|
||||
renderPaperclipWakePrompt(payload, { resumedSession: true }),
|
||||
renderPaperclipWakePrompt(payload, { includeExecutionContract: true }),
|
||||
]) {
|
||||
expect(prompt).toContain(
|
||||
"Execution contract: take concrete action in this heartbeat",
|
||||
);
|
||||
expect(prompt).toContain("clear final disposition");
|
||||
expect(prompt).toContain(
|
||||
"Immediately before returning, verify that Paperclip records one of those dispositions",
|
||||
);
|
||||
expect(prompt).toContain(
|
||||
"a successful process exit or final response is not sufficient",
|
||||
);
|
||||
expect(prompt).toContain(
|
||||
"If no valid disposition is recorded, record it now and do not end the run",
|
||||
);
|
||||
expect(prompt).toContain(
|
||||
"After 2 consecutive failures of the same control-plane write",
|
||||
);
|
||||
expect(prompt).toContain(
|
||||
"adapter/runtime status channel as the sanctioned fallback",
|
||||
);
|
||||
expect(prompt).toContain(
|
||||
"evidence, not valid liveness paths by themselves",
|
||||
);
|
||||
expect(prompt).toContain(
|
||||
"Use child issues for long or parallel delegated work instead of polling",
|
||||
);
|
||||
expect(prompt).toContain("named unblock owner/action");
|
||||
for (const resumedSession of [false, true]) {
|
||||
for (const includeExecutionContract of [undefined, false, true]) {
|
||||
const prompt = renderPaperclipWakePrompt(payload, {
|
||||
resumedSession, includeExecutionContract,
|
||||
});
|
||||
expect(prompt).toContain(resumedSession ? "## Paperclip Resume Delta" : "## Paperclip Wake Payload");
|
||||
expect(prompt).toContain("- reason: issue_assigned");
|
||||
expect(prompt).toContain("- issue: PAP-1580 Update prompts");
|
||||
expect(prompt).toContain("- issue status: in_progress");
|
||||
expect(prompt).not.toContain("Execution contract:");
|
||||
expect(prompt).not.toContain("clear final disposition");
|
||||
expect(prompt).not.toContain("do not end the run");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1947,7 +1872,7 @@ describe("renderPaperclipWakePrompt", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps exactly one execution contract in a composed fresh heartbeat prompt", () => {
|
||||
it("keeps connection guidance without a generic manual in a composed fresh prompt", () => {
|
||||
const wakePrompt = renderPaperclipWakePrompt({
|
||||
reason: "issue_assigned",
|
||||
issue: {
|
||||
@@ -1967,7 +1892,8 @@ describe("renderPaperclipWakePrompt", () => {
|
||||
const composed = [wakePrompt, DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE].join(
|
||||
"\n\n",
|
||||
);
|
||||
expect(composed.match(/Execution contract/g)).toHaveLength(1);
|
||||
expect(composed).toContain(CONNECTION_INTENT_AGENT_GUIDANCE);
|
||||
expect(composed).not.toContain("Execution contract:");
|
||||
});
|
||||
|
||||
it("trims comment-batch boilerplate on fresh wakes with zero pending comments", () => {
|
||||
|
||||
@@ -208,41 +208,15 @@ export function resolvePaperclipInstanceRootForAdapter(
|
||||
}
|
||||
|
||||
export const DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE = [
|
||||
"You are agent {{agent.id}} ({{agent.name}}). Continue your Paperclip work.",
|
||||
"",
|
||||
"Execution contract:",
|
||||
"- Start actionable work in this heartbeat; do not stop at a plan unless the issue asks for planning.",
|
||||
"- Leave durable progress in comments, documents, or work products, then update the issue to a clear final disposition before ending the heartbeat.",
|
||||
"- Comments, documents, screenshots, work products, and `Remaining` bullets are evidence, not valid liveness paths by themselves.",
|
||||
"- Final disposition checklist: mark `done` when complete; use `in_review` only with a real reviewer, approval, interaction, or monitor path; use `blocked` only with first-class blockers or a named unblock owner/action; create delegated follow-up issues with blockers when another agent owns the next step; keep `in_progress` only when a live continuation path exists.",
|
||||
"- Prefer the smallest verification that proves the change; do not default to full workspace typecheck/build/test on every heartbeat unless the task scope warrants it.",
|
||||
"- After 2 consecutive failures of the same control-plane write, stop retrying that write for the rest of the heartbeat. Continue useful work, report the failure in the final response, and rely on the adapter/runtime status channel as the sanctioned fallback.",
|
||||
"- Use child issues for parallel or long delegated work instead of polling agents, sessions, or processes.",
|
||||
"- If woken by a human comment on a dependency-blocked issue, respond or triage the comment without treating the blocked deliverable work as unblocked.",
|
||||
"- Create child issues directly when you know what needs to be done; use issue-thread interactions when the board/user must choose suggested tasks, answer structured questions, or confirm a proposal.",
|
||||
"- Use `PAPERCLIP_SCRATCH_DIR` / `PAPERCLIP_RUN_SCRATCH_DIR` for temporary scratch files instead of ad hoc `/tmp` paths; Paperclip removes that run-owned directory after the run ends.",
|
||||
"- To ask for that input, create an interaction on the current issue with POST /api/issues/$PAPERCLIP_TASK_ID/interactions using kind suggest_tasks, ask_user_questions, or request_confirmation. Use continuationPolicy wake_assignee when you need to resume after a response (it wakes on acceptance and rejection alike; only expiry does not wake); use wake_assignee_on_accept when you want to resume only after acceptance.",
|
||||
"- Never create probe or throwaway issue-thread interactions to discover the interactions API shape or your permissions; schema discovery goes through the OpenAPI spec and explicit validation errors, not placeholder cards. Every ask_user_questions, suggest_tasks, or request_confirmation you post must carry a real, answerable prompt; withdraw one you no longer need instead of leaving it pending.",
|
||||
"- When you intentionally restart follow-up work on a completed assigned issue, include structured `resume: true` with the POST /api/issues/$PAPERCLIP_TASK_ID/comments or PATCH /api/issues/$PAPERCLIP_TASK_ID comment payload (substitute that issue's real id when it is not the current task). Generic agent comments on closed issues are inert by default.",
|
||||
"- For plan approval, update the plan document first, then create request_confirmation targeting the latest plan revision with idempotencyKey confirmation:{issueId}:plan:{revisionId}. Wait for acceptance before creating implementation subtasks, and create a fresh confirmation after superseding board/user comments if approval is still needed.",
|
||||
"- If blocked, mark the issue blocked and name the unblock owner and action.",
|
||||
"- Respect budget, pause/cancel, approval gates, and company boundaries.",
|
||||
"- When the server-authenticated wake payload includes an External chat response contract, that narrower contract replaces the generic Paperclip comment, status, checkout, and final-disposition steps above for that turn. Follow the external-chat contract exactly; it does not relax any permission, approval, execution-policy, containment, budget, pause/cancel, or company boundary.",
|
||||
"You are agent {{agent.id}} ({{agent.name}}).",
|
||||
"",
|
||||
CONNECTION_INTENT_AGENT_GUIDANCE,
|
||||
].join("\n");
|
||||
|
||||
// Chat behavior is supplied centrally by the server's task-context markdown.
|
||||
// Keep the ordinary task's completion/delegation contract out of this template.
|
||||
export const DEFAULT_PAPERCLIP_CONVERSATION_PROMPT_TEMPLATE = [
|
||||
"You are agent {{agent.id}} ({{agent.name}}). Continue your Paperclip conversation using the supplied chat mode directive.",
|
||||
"Use available tools and assigned skills as needed; respect budget, pause/cancel, approval gates, and company boundaries.",
|
||||
"Prefer the smallest verification that proves the action. Use PAPERCLIP_SCRATCH_DIR / PAPERCLIP_RUN_SCRATCH_DIR for temporary scratch files.",
|
||||
"After 2 consecutive failures of the same control-plane write, stop retrying that write for the rest of the turn. Report the failure honestly; never claim an unconfirmed mutation succeeded.",
|
||||
"Never create probe or throwaway issue-thread interactions. Every interaction must carry a real, answerable prompt; withdraw one you no longer need.",
|
||||
"",
|
||||
CONNECTION_INTENT_AGENT_GUIDANCE,
|
||||
].join("\n");
|
||||
// Task/chat modes arrive in server-owned context; operational procedures live
|
||||
// in the harness-delivered Paperclip skill rather than either default template.
|
||||
export const DEFAULT_PAPERCLIP_CONVERSATION_PROMPT_TEMPLATE =
|
||||
DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE;
|
||||
|
||||
export const WATCHDOG_DEFAULT_MANDATE = [
|
||||
"You are running as a task watchdog, not as the original deliverable worker.",
|
||||
@@ -2242,6 +2216,7 @@ export function selectPaperclipPromptSections(
|
||||
options: {
|
||||
resumedSession?: boolean;
|
||||
includeCommunicationGuidance?: boolean;
|
||||
/** @deprecated Generic execution instructions are no longer injected. */
|
||||
includeExecutionContract?: boolean;
|
||||
nativeWakeReaderAvailable?: boolean;
|
||||
} = {},
|
||||
@@ -2270,6 +2245,7 @@ export function renderPaperclipWakePrompt(
|
||||
return joinPromptSections([
|
||||
renderPaperclipWakePromptBody(value, options),
|
||||
instructions ? `## Assigned connector skills\n\n${instructions}` : "",
|
||||
asString(parseObject(parseObject(value).connectionInstructions).text, "").trim(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -2277,6 +2253,7 @@ function renderPaperclipWakePromptBody(
|
||||
value: unknown,
|
||||
options: {
|
||||
resumedSession?: boolean;
|
||||
/** @deprecated Generic execution instructions are no longer injected. */
|
||||
includeExecutionContract?: boolean;
|
||||
// Conversation policy arrives in the server-owned task markdown. Generic
|
||||
// task disposition and child-delegation instructions conflict with it.
|
||||
@@ -2301,12 +2278,6 @@ function renderPaperclipWakePromptBody(
|
||||
externalChatTurn ||
|
||||
externalChatReaderTurn ||
|
||||
externalChatQuestionResponseTurn;
|
||||
// The heartbeat prompt template already carries the execution contract on
|
||||
// fresh sessions; only resume deltas (which replace the template) and
|
||||
// template-less adapters need the wake-payload copy. An explicit false means
|
||||
// another delivery carrier owns the contract, including on resume.
|
||||
const includeExecutionContract = options.conversationMode !== true &&
|
||||
(options.includeExecutionContract ?? resumedSession);
|
||||
const hasWakeCommentBatch =
|
||||
normalized.comments.length > 0 ||
|
||||
normalized.includedCount > 0 ||
|
||||
@@ -2397,7 +2368,7 @@ function renderPaperclipWakePromptBody(
|
||||
}
|
||||
};
|
||||
|
||||
const executionContractLines = externalChatContract
|
||||
const wakeContractLines = externalChatContract
|
||||
? [
|
||||
externalChatQuestionResponseTurn
|
||||
? "## External chat answered-question contract"
|
||||
@@ -2444,12 +2415,7 @@ function renderPaperclipWakePromptBody(
|
||||
`Fallback preference order: (1) send back to ${originalAssigneeLabel} with a retry instruction; (2) fix the runtime/adapter/workspace problem, then send it back; (3) reassign to another agent with the right specialty; (4) convert to an explicit manual-review state for the board.`,
|
||||
"",
|
||||
]
|
||||
: includeExecutionContract
|
||||
? [
|
||||
"Execution contract: take concrete action in this heartbeat when the issue is actionable; do not stop at a plan unless planning was requested. Leave durable progress and then give the issue a clear final disposition before ending the heartbeat: `done`, `in_review` with a real reviewer/approval/interaction path, `blocked` with first-class blockers or a named unblock owner/action, delegated follow-up issues with blockers, or `in_progress` only when a live continuation path exists. Immediately before returning, verify that Paperclip records one of those dispositions; a successful process exit or final response is not sufficient. If no valid disposition is recorded, record it now and do not end the run. After 2 consecutive failures of the same control-plane write, stop retrying it for the rest of the heartbeat, continue useful work, report the failure in the final response, and rely on the adapter/runtime status channel as the sanctioned fallback. Use child issues for long or parallel delegated work instead of polling. Comments, documents, screenshots, work products, and `Remaining` bullets are evidence, not valid liveness paths by themselves.",
|
||||
"",
|
||||
]
|
||||
: [];
|
||||
: [];
|
||||
const wakeSummaryLines = [
|
||||
`- reason: ${normalized.reason ?? "unknown"}`,
|
||||
`- issue: ${normalized.issue?.identifier ?? normalized.issue?.id ?? "unknown"}${normalized.issue?.title ? ` ${normalized.issue.title}` : ""}`,
|
||||
@@ -2507,7 +2473,7 @@ function renderPaperclipWakePromptBody(
|
||||
]),
|
||||
"",
|
||||
...externalInteractionContinuationLines,
|
||||
...executionContractLines,
|
||||
...wakeContractLines,
|
||||
...wakeSummaryLines,
|
||||
]
|
||||
: [
|
||||
@@ -2537,7 +2503,7 @@ function renderPaperclipWakePromptBody(
|
||||
: []),
|
||||
"",
|
||||
...externalInteractionContinuationLines,
|
||||
...executionContractLines,
|
||||
...wakeContractLines,
|
||||
...wakeSummaryLines,
|
||||
];
|
||||
|
||||
|
||||
@@ -1,8 +1,90 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { classifyWorkspaceRestoreFailure } from "./workspace-restore-merge.js";
|
||||
import { withWorkspaceRestoreDiagnostics } from "./workspace-restore-diagnostics.js";
|
||||
import {
|
||||
getWorkspaceRestoreDiagnostic, preserveWorkspaceRestoreErrorDiagnostic, recordWorkspaceRestoreDiagnostic,
|
||||
sanitizeWorkspaceRestoreDiagnostic, withWorkspaceRestoreDiagnostics, withWorkspaceRestoreStep, withWorkspaceRestoreGitCommand,
|
||||
type WorkspaceRestoreDiagnostic,
|
||||
} from "./workspace-restore-diagnostics.js";
|
||||
|
||||
describe("workspace restore diagnostics", () => {
|
||||
it.each([new Error("shared nested failure"), "shared primitive failure"])("selects an unlabelled nested failure without retaining a later task's step (%s)", async (error) => {
|
||||
const sink = vi.fn();
|
||||
const snapshots: Array<WorkspaceRestoreDiagnostic | undefined> = [];
|
||||
await expect(withWorkspaceRestoreDiagnostics("workspace", async () => {
|
||||
await Promise.allSettled([
|
||||
withWorkspaceRestoreDiagnostics("workspace", async () => { throw error; }, sink, (value) => { snapshots[0] = value; }),
|
||||
withWorkspaceRestoreDiagnostics("asset", () => withWorkspaceRestoreStep("asset_restore", async () => { throw error; }), sink,
|
||||
(value) => { snapshots[1] = value; }),
|
||||
]);
|
||||
recordWorkspaceRestoreDiagnostic(error, snapshots[0]);
|
||||
throw error;
|
||||
}, sink)).rejects.toBe(error);
|
||||
expect(snapshots[1]?.step).toBe("asset_restore");
|
||||
expect(getWorkspaceRestoreDiagnostic(error)).toEqual(typeof error === "object" ? { phase: "workspace", errorCode: "unknown" } : undefined);
|
||||
expect(sink).toHaveBeenCalledExactlyOnceWith('[paperclip] Workspace restore diagnostic: {"phase":"workspace","errorCode":"unknown"}\n');
|
||||
});
|
||||
|
||||
it("keeps logging when a diagnostic consumer fails", async () => {
|
||||
const error = new Error("restore failed");
|
||||
const sink = vi.fn();
|
||||
await expect(withWorkspaceRestoreDiagnostics("workspace", async () => { throw error; }, sink,
|
||||
() => { throw new Error("consumer failed"); })).rejects.toBe(error);
|
||||
expect(sink).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("retains a nested step and bounded Git fields across an existing wrapper without a cause", async () => {
|
||||
const source = Object.assign(new Error("private command/path"), { code: 1, stderr: "private stderr" });
|
||||
const wrapper = new Error("existing wrapper");
|
||||
await expect(withWorkspaceRestoreDiagnostics("workspace", () => withWorkspaceRestoreStep("directory_merge", async () => {
|
||||
try {
|
||||
await withWorkspaceRestoreStep("git_integration", async () => { throw source; });
|
||||
} catch (error) { throw preserveWorkspaceRestoreErrorDiagnostic(wrapper, error); }
|
||||
}))).rejects.toBe(wrapper);
|
||||
expect(wrapper).not.toHaveProperty("cause");
|
||||
expect(Object.keys(wrapper)).toEqual([]);
|
||||
expect(getWorkspaceRestoreDiagnostic(wrapper)).toEqual({ phase: "workspace", step: "git_integration", errorCode: "unknown", exitCode: 1 });
|
||||
});
|
||||
|
||||
it("attributes a reused error to the later failed retry step", async () => {
|
||||
const error = new Error("same failure");
|
||||
await expect(withWorkspaceRestoreDiagnostics("workspace", async () => {
|
||||
await withWorkspaceRestoreStep("git_import", async () => { throw error; }).catch(() => {});
|
||||
await withWorkspaceRestoreStep("git_export", async () => { throw error; });
|
||||
})).rejects.toBe(error);
|
||||
expect(getWorkspaceRestoreDiagnostic(error)?.step).toBe("git_export");
|
||||
await expect(withWorkspaceRestoreDiagnostics("asset", async () => { throw error; })).rejects.toBe(error);
|
||||
expect(getWorkspaceRestoreDiagnostic(error)).toEqual({ phase: "asset", errorCode: "unknown" });
|
||||
});
|
||||
|
||||
it("isolates concurrent steps even when their errors share identity", async () => {
|
||||
const error = new Error("shared error");
|
||||
const sink = vi.fn();
|
||||
await Promise.allSettled([
|
||||
withWorkspaceRestoreDiagnostics("workspace", () => withWorkspaceRestoreStep("git_import", async () => { throw error; }), sink),
|
||||
withWorkspaceRestoreDiagnostics("asset", () => withWorkspaceRestoreStep("asset_restore", async () => { throw error; }), sink),
|
||||
]);
|
||||
expect(sink.mock.calls.map(([line]) => JSON.parse(line.split(": ")[1]))).toEqual(expect.arrayContaining([
|
||||
{ phase: "workspace", step: "git_import", errorCode: "unknown" },
|
||||
{ phase: "asset", step: "asset_restore", errorCode: "unknown" },
|
||||
]));
|
||||
});
|
||||
|
||||
it("starts a fresh scope for later work inherited from a completed async context", async () => {
|
||||
const error = new Error("later failure");
|
||||
let release!: () => void;
|
||||
const gate = new Promise<void>((resolve) => { release = resolve; });
|
||||
let later!: Promise<unknown>;
|
||||
const sink = vi.fn();
|
||||
await withWorkspaceRestoreDiagnostics("workspace", async () => {
|
||||
later = gate.then(() => withWorkspaceRestoreDiagnostics("asset", () =>
|
||||
withWorkspaceRestoreStep("asset_restore", async () => { throw error; }), sink)).catch((value) => value);
|
||||
}, sink);
|
||||
release();
|
||||
expect(await later).toBe(error);
|
||||
expect(getWorkspaceRestoreDiagnostic(error)).toEqual({ phase: "asset", step: "asset_restore", errorCode: "unknown" });
|
||||
expect(sink).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("leaves successful restores silent", async () => {
|
||||
const sink = vi.fn();
|
||||
expect(await withWorkspaceRestoreDiagnostics("workspace", async () => 42, sink)).toBe(42);
|
||||
@@ -82,3 +164,136 @@ describe("workspace restore diagnostics", () => {
|
||||
expect(sink).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Git integration diagnostic privacy and attribution", () => {
|
||||
async function capture(command: Parameters<typeof withWorkspaceRestoreGitCommand>[0], error: unknown) {
|
||||
let receipt: WorkspaceRestoreDiagnostic | undefined;
|
||||
const originalProperties = error && typeof error === "object" ? Object.getOwnPropertyDescriptors(error) : undefined;
|
||||
await expect(withWorkspaceRestoreDiagnostics("workspace", () => withWorkspaceRestoreStep("git_integration", () =>
|
||||
withWorkspaceRestoreGitCommand(command, async () => { throw error; })), undefined,
|
||||
(value) => { receipt = value; })).rejects.toBe(error);
|
||||
if (originalProperties) expect(Object.getOwnPropertyDescriptors(error)).toEqual(originalProperties);
|
||||
return receipt;
|
||||
}
|
||||
|
||||
it.each([
|
||||
["merge_tree", { code: 1, stdout: "a".repeat(40) + "\nprivate-file", stderr: "private-conflict-body" }, "merge_conflict"],
|
||||
["merge_tree", { code: 1 }, "unknown"],
|
||||
["merge_tree", { code: 1, stderr: `merge-tree: ${"a".repeat(40)} - not something we can merge\n` }, "invalid_object"],
|
||||
["merge_tree", { code: 1, signal: "SIGTERM" }, "unknown"],
|
||||
["merge_tree", { code: 1, killed: true }, "unknown"],
|
||||
["merge_tree", { code: 128, stderr: "fatal: Not a valid object name private-object" }, "invalid_object"],
|
||||
["merge_tree", { code: 128, stderr: `merge-tree: ${"a".repeat(40)} - not something we can merge\n` }, "invalid_object"],
|
||||
["merge_base", { code: 128, stderr: "fatal: Not a valid commit name private-object" }, "invalid_object"],
|
||||
["rev_parse", { code: 128, stderr: "fatal: bad object private-object" }, "invalid_object"],
|
||||
["update_ref", { code: 128, stderr: `fatal: update_ref failed for ref 'private-ref': cannot lock ref 'private-ref': is at ${"a".repeat(40)} but expected ${"b".repeat(40)}\n` }, "ref_conflict"],
|
||||
["update_ref", { code: 128, stderr: "fatal: Unable to create 'private-path.lock': File exists." }, "unknown"],
|
||||
["update_ref", { code: "EACCES", stderr: "private-path" }, "permission_denied"],
|
||||
["commit_tree", { code: "EPERM" }, "permission_denied"],
|
||||
["commit_tree", { code: 1, stderr: "Permission denied private-path" }, "unknown"],
|
||||
["symbolic_ref", { code: 1 }, "unknown"],
|
||||
["merge_base", { code: 1 }, "unknown"],
|
||||
["log", { code: 128, stderr: "localized or unrecognized private-text" }, "unknown"],
|
||||
["log", { code: 128, stderr: "x".repeat(16 * 1024) + "\nfatal: bad object private-object" }, "unknown"],
|
||||
["merge_tree", "private-string", "unknown"],
|
||||
] as const)("classifies only supported %s evidence (%j)", async (command, error, kind) => {
|
||||
const receipt = await capture(command, error);
|
||||
expect(receipt).toMatchObject({ gitCommand: command, gitFailureKind: kind });
|
||||
expect(JSON.stringify(receipt)).not.toContain("private-");
|
||||
expect(Object.keys(receipt!).sort()).toEqual([
|
||||
"phase", "step", "errorCode", "gitCommand", "gitFailureKind",
|
||||
...(typeof error === "object" && typeof error.code === "number" ? ["exitCode"] : []),
|
||||
].sort());
|
||||
});
|
||||
|
||||
it("treats throwing stderr getters as unknown and never replaces the original error", async () => {
|
||||
const error = Object.defineProperty(new Error("private-original"), "stderr", { get() { throw new Error("private-getter"); } });
|
||||
expect(await capture("commit_tree", error)).toEqual({ phase: "workspace", step: "git_integration",
|
||||
errorCode: "unknown", gitCommand: "commit_tree", gitFailureKind: "unknown" });
|
||||
});
|
||||
|
||||
it("retains a nested command across wrappers and nested task diagnostics", async () => {
|
||||
const source = Object.assign(new Error("private-source"), { code: 1, stdout: "a".repeat(40) + "\n" });
|
||||
const wrapper = new Error("private-wrapper");
|
||||
const sink = vi.fn();
|
||||
await expect(withWorkspaceRestoreDiagnostics("workspace", () => withWorkspaceRestoreStep("directory_merge", () =>
|
||||
withWorkspaceRestoreDiagnostics("workspace", () => withWorkspaceRestoreStep("git_integration", async () => {
|
||||
try { await withWorkspaceRestoreGitCommand("merge_tree", async () => { throw source; }); }
|
||||
catch (error) { throw preserveWorkspaceRestoreErrorDiagnostic(wrapper, error); }
|
||||
}), sink)), sink)).rejects.toBe(wrapper);
|
||||
expect(getWorkspaceRestoreDiagnostic(wrapper)).toEqual({ phase: "workspace", step: "git_integration",
|
||||
errorCode: "unknown", exitCode: 1, gitCommand: "merge_tree", gitFailureKind: "merge_conflict" });
|
||||
expect(wrapper).not.toHaveProperty("cause");
|
||||
expect(sink).toHaveBeenCalledTimes(1);
|
||||
expect(sink.mock.calls[0][0]).not.toContain("private-");
|
||||
});
|
||||
|
||||
it("keeps a nested identity probe failure instead of relabelling it as its ref transaction", async () => {
|
||||
const error = Object.assign(new Error("private-probe"), { code: 128, stderr: "private-probe-output" });
|
||||
await expect(withWorkspaceRestoreDiagnostics("workspace", () => withWorkspaceRestoreStep("git_integration", () =>
|
||||
withWorkspaceRestoreGitCommand("update_ref", () =>
|
||||
withWorkspaceRestoreGitCommand("symbolic_ref", async () => { throw error; }))))).rejects.toBe(error);
|
||||
expect(getWorkspaceRestoreDiagnostic(error)).toEqual({ phase: "workspace", step: "git_integration",
|
||||
errorCode: "unknown", exitCode: 128, gitCommand: "symbolic_ref", gitFailureKind: "unknown" });
|
||||
});
|
||||
|
||||
it("does not retain a handled command's label on a later failed step or retry", async () => {
|
||||
const error = Object.assign(new Error("same error"), { code: 1 });
|
||||
await expect(withWorkspaceRestoreDiagnostics("workspace", async () => {
|
||||
await withWorkspaceRestoreStep("git_integration", () =>
|
||||
withWorkspaceRestoreGitCommand("merge_tree", async () => { throw error; })).catch(() => {});
|
||||
await withWorkspaceRestoreStep("index_reset", async () => { throw error; });
|
||||
})).rejects.toBe(error);
|
||||
expect(getWorkspaceRestoreDiagnostic(error)).toEqual({ phase: "workspace", step: "index_reset", errorCode: "unknown", exitCode: 1 });
|
||||
await expect(withWorkspaceRestoreDiagnostics("workspace", () => withWorkspaceRestoreStep("git_integration", async () => {
|
||||
await withWorkspaceRestoreGitCommand("merge_tree", async () => { throw error; }).catch(() => {});
|
||||
await withWorkspaceRestoreGitCommand("update_ref", async () => { throw error; });
|
||||
}))).rejects.toBe(error);
|
||||
expect(getWorkspaceRestoreDiagnostic(error)).toMatchObject({ gitCommand: "update_ref", gitFailureKind: "unknown" });
|
||||
});
|
||||
|
||||
it("retains the selected parallel task's command when two errors share identity", async () => {
|
||||
const error = Object.assign(new Error("shared"), { code: 1, stdout: "a".repeat(40) + "\n" });
|
||||
const snapshots: WorkspaceRestoreDiagnostic[] = [];
|
||||
await expect(withWorkspaceRestoreDiagnostics("workspace", async () => {
|
||||
await Promise.allSettled((["merge_tree", "update_ref"] as const).map((command, index) =>
|
||||
withWorkspaceRestoreDiagnostics("workspace", () => withWorkspaceRestoreStep("git_integration", () =>
|
||||
withWorkspaceRestoreGitCommand(command, async () => { throw error; })), undefined,
|
||||
(receipt) => { snapshots[index] = receipt; })));
|
||||
recordWorkspaceRestoreDiagnostic(error, snapshots[0]);
|
||||
throw error;
|
||||
})).rejects.toBe(error);
|
||||
expect(snapshots.map(value => value.gitCommand)).toEqual(["merge_tree", "update_ref"]);
|
||||
expect(getWorkspaceRestoreDiagnostic(error)).toMatchObject({ gitCommand: "merge_tree", gitFailureKind: "merge_conflict" });
|
||||
});
|
||||
|
||||
it("leaves successful or handled commands silent and preserves values", async () => {
|
||||
const sink = vi.fn();
|
||||
const value = {};
|
||||
expect(await withWorkspaceRestoreDiagnostics("workspace", () => withWorkspaceRestoreStep("git_integration", async () => {
|
||||
await withWorkspaceRestoreGitCommand("symbolic_ref", async () => { throw { code: 1 }; }).catch(() => {});
|
||||
return withWorkspaceRestoreGitCommand("update_ref", async () => value);
|
||||
}), sink)).toBe(value);
|
||||
expect(sink).not.toHaveBeenCalled();
|
||||
const error = new Error("outside restore");
|
||||
await expect(withWorkspaceRestoreGitCommand("log", async () => { throw error; })).rejects.toBe(error);
|
||||
expect(getWorkspaceRestoreDiagnostic(error)).toBeUndefined();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ phase: "asset", step: "git_integration", gitCommand: "merge_tree" },
|
||||
{ phase: "workspace", step: "index_reset", gitCommand: "merge_tree" },
|
||||
{ phase: "workspace", step: "git_integration", gitCommand: "private-command" },
|
||||
])("does not decode Git metadata outside the closed integration contract (%j)", (value) => {
|
||||
const diagnostic = sanitizeWorkspaceRestoreDiagnostic({ ...value, gitFailureKind: "merge_conflict" });
|
||||
expect(diagnostic).not.toHaveProperty("gitCommand");
|
||||
expect(diagnostic).not.toHaveProperty("gitFailureKind");
|
||||
});
|
||||
|
||||
it("revalidates persisted command and failure labels without copying extra fields", () => {
|
||||
expect(sanitizeWorkspaceRestoreDiagnostic({ phase: "workspace", step: "git_integration", gitCommand: "log",
|
||||
gitFailureKind: "private-reason", stderr: "private-text", args: ["private-args"] })).toEqual({
|
||||
phase: "workspace", step: "git_integration", errorCode: "unknown", gitCommand: "log", gitFailureKind: "unknown",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,12 +2,54 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
||||
import type { RuntimeProgressSink } from "./runtime-progress.js";
|
||||
|
||||
type RestorePhase = "workspace" | "asset";
|
||||
const RESTORE_STEPS = new Set([
|
||||
"git_export", "git_import", "workspace_transfer", "workspace_extract",
|
||||
"directory_merge", "git_integration", "index_reset", "git_ref_cleanup", "asset_restore",
|
||||
] as const);
|
||||
export type WorkspaceRestoreStep = typeof RESTORE_STEPS extends Set<infer T> ? T : never;
|
||||
const GIT_COMMANDS = new Set([
|
||||
"rev_parse", "symbolic_ref", "merge_base", "merge_tree", "commit_tree", "update_ref", "log",
|
||||
] as const);
|
||||
export type WorkspaceRestoreGitCommand = typeof GIT_COMMANDS extends Set<infer T> ? T : never;
|
||||
const GIT_FAILURE_KINDS = new Set([
|
||||
"merge_conflict", "invalid_object", "ref_conflict", "permission_denied", "unknown",
|
||||
] as const);
|
||||
type GitFailureKind = typeof GIT_FAILURE_KINDS extends Set<infer T> ? T : never;
|
||||
export interface WorkspaceRestoreDiagnostic {
|
||||
phase: RestorePhase;
|
||||
step?: WorkspaceRestoreStep;
|
||||
errorCode: string;
|
||||
httpStatus?: number;
|
||||
exitCode?: number;
|
||||
gitCommand?: WorkspaceRestoreGitCommand;
|
||||
gitFailureKind?: GitFailureKind;
|
||||
}
|
||||
const ERROR_CODES = new Set([
|
||||
"ENOENT", "EACCES", "EPERM", "ENOSPC", "EIO", "EXDEV", "ENOTDIR", "EISDIR",
|
||||
"ECONNRESET", "ECONNREFUSED", "ETIMEDOUT", "EPIPE", "ENOTFOUND", "EAI_AGAIN",
|
||||
"ABORT_ERR", "UND_ERR_CONNECT_TIMEOUT", "UND_ERR_SOCKET",
|
||||
]);
|
||||
const activeDiagnostic = new AsyncLocalStorage<boolean>();
|
||||
type ErrorDiagnostic = Pick<WorkspaceRestoreDiagnostic, "errorCode" | "httpStatus" | "exitCode">;
|
||||
interface DiagnosticScope {
|
||||
active: boolean;
|
||||
sequence: number;
|
||||
failures: Map<unknown, {
|
||||
sequence: number;
|
||||
step?: WorkspaceRestoreStep;
|
||||
gitCommand?: WorkspaceRestoreGitCommand;
|
||||
gitFailureKind?: GitFailureKind;
|
||||
}>;
|
||||
}
|
||||
const activeDiagnostic = new AsyncLocalStorage<DiagnosticScope>();
|
||||
// Never attach a raw cause to an error just to retain a numeric Git exit code.
|
||||
const wrappedDiagnostics = new WeakMap<object, ErrorDiagnostic>();
|
||||
const restoreDiagnostics = new WeakMap<object, WorkspaceRestoreDiagnostic>();
|
||||
const diagnosticCapture = new AsyncLocalStorage<WeakMap<object, WorkspaceRestoreDiagnostic>>();
|
||||
|
||||
/** Keep each settlement's diagnostic receipt isolated across asynchronous work. */
|
||||
export function withWorkspaceRestoreDiagnosticCapture<T>(operation: () => Promise<T>): Promise<T> {
|
||||
return diagnosticCapture.run(new WeakMap(), operation);
|
||||
}
|
||||
|
||||
function readField(value: Record<string, unknown>, key: string): unknown {
|
||||
try { return value[key]; } catch { return undefined; }
|
||||
@@ -18,23 +60,66 @@ function boundedInteger(value: unknown, minimum: number, maximum: number): numbe
|
||||
? value : undefined;
|
||||
}
|
||||
|
||||
/** Inspect bounded stderr only in memory; never persist Git text or arguments. */
|
||||
function gitFailureKind(command: WorkspaceRestoreGitCommand, error: unknown): GitFailureKind {
|
||||
if (!error || typeof error !== "object") return "unknown";
|
||||
const value = error as Record<string, unknown>;
|
||||
const code = readField(value, "code");
|
||||
if (code === "EACCES" || code === "EPERM") return "permission_denied";
|
||||
if (readField(value, "killed") || readField(value, "signal")) return "unknown";
|
||||
const stderr = readField(value, "stderr");
|
||||
const text = typeof stderr === "string" ? stderr.slice(0, 16 * 1024) : "";
|
||||
if (command === "update_ref" && /cannot lock ref '[^'\r\n]+': is at [a-f0-9]{40,64} but expected [a-f0-9]{40,64}(?:\s|$)/m.test(text)) {
|
||||
return "ref_conflict";
|
||||
}
|
||||
if (/^fatal: (?:bad object |Not a valid object name |not a valid object name |Not a valid commit name )/m.test(text)
|
||||
|| (command === "merge_tree" && /^merge-tree: [a-f0-9]{40,64} - not something we can merge\s*$/m.test(text))) {
|
||||
return "invalid_object";
|
||||
}
|
||||
// merge-tree --write-tree prints the merged tree before reporting conflicts.
|
||||
// Exit 1 alone is insufficient: some Git versions also use it for bad objects.
|
||||
const stdout = readField(value, "stdout");
|
||||
if (command === "merge_tree" && code === 1 && typeof stdout === "string"
|
||||
&& /^(?:[a-f0-9]{40}|[a-f0-9]{64})\r?\n/.test(stdout.slice(0, 66))) return "merge_conflict";
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
/** Annotate the original thrown error in its restore scope; never change it. */
|
||||
export async function withWorkspaceRestoreGitCommand<T>(command: WorkspaceRestoreGitCommand, operation: () => Promise<T>): Promise<T> {
|
||||
const scope = activeDiagnostic.getStore();
|
||||
if (!scope?.active || !GIT_COMMANDS.has(command)) return await operation();
|
||||
const started = scope.sequence;
|
||||
try {
|
||||
return await operation();
|
||||
} catch (error) {
|
||||
// A ref transaction can fail in its nested branch-identity probe. Retain
|
||||
// that more specific command, while later retries get fresh attribution.
|
||||
if (scope.active && (scope.failures.get(error)?.sequence ?? -1) <= started) scope.failures.set(error, {
|
||||
sequence: ++scope.sequence, gitCommand: command, gitFailureKind: gitFailureKind(command, error),
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/** Only fixed codes and bounded numbers may enter the company-readable run log. */
|
||||
function diagnostic(error: unknown): { errorCode: string; httpStatus?: number; exitCode?: number } {
|
||||
const result: { errorCode: string; httpStatus?: number; exitCode?: number } = { errorCode: "unknown" };
|
||||
function diagnostic(error: unknown): ErrorDiagnostic {
|
||||
const result: ErrorDiagnostic = { errorCode: "unknown" };
|
||||
let current = error;
|
||||
// SDKs wrap transport errors in a cause. Bound traversal, including cycles.
|
||||
for (let depth = 0; depth < 4 && current && typeof current === "object"; depth++) {
|
||||
const value = current as Record<string, unknown>;
|
||||
const code = readField(value, "code");
|
||||
const saved = wrappedDiagnostics.get(value);
|
||||
const code = saved?.errorCode !== "unknown" && saved?.errorCode !== undefined
|
||||
? saved.errorCode : readField(value, "code");
|
||||
if (result.errorCode === "unknown" && typeof code === "string" && ERROR_CODES.has(code)) {
|
||||
result.errorCode = code;
|
||||
}
|
||||
const status = boundedInteger(readField(value, "status"), 400, 599)
|
||||
const status = saved?.httpStatus ?? boundedInteger(readField(value, "status"), 400, 599)
|
||||
?? boundedInteger(readField(value, "statusCode"), 400, 599);
|
||||
if (result.httpStatus === undefined && status !== undefined) {
|
||||
result.httpStatus = status;
|
||||
}
|
||||
const exitCode = boundedInteger(readField(value, "exitCode"), 1, 255) ?? boundedInteger(code, 1, 255);
|
||||
const exitCode = saved?.exitCode ?? boundedInteger(readField(value, "exitCode"), 1, 255) ?? boundedInteger(code, 1, 255);
|
||||
if (result.exitCode === undefined && exitCode !== undefined) {
|
||||
result.exitCode = exitCode;
|
||||
}
|
||||
@@ -43,26 +128,125 @@ function diagnostic(error: unknown): { errorCode: string; httpStatus?: number; e
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Retain bounded fields across an existing error wrapper, without a raw cause. */
|
||||
export function preserveWorkspaceRestoreErrorDiagnostic<T extends object>(wrapper: T, source: unknown): T {
|
||||
wrappedDiagnostics.set(wrapper, diagnostic(source));
|
||||
const scope = activeDiagnostic.getStore();
|
||||
const failure = scope?.active ? scope.failures.get(source) : undefined;
|
||||
if (failure) scope!.failures.set(wrapper, failure);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/** Decode persisted adapter metadata; never copy arbitrary properties or text. */
|
||||
export function sanitizeWorkspaceRestoreDiagnostic(value: unknown): WorkspaceRestoreDiagnostic | undefined {
|
||||
if (!value || typeof value !== "object") return undefined;
|
||||
const record = value as Record<string, unknown>;
|
||||
const phase = readField(record, "phase");
|
||||
if (phase !== "workspace" && phase !== "asset") return undefined;
|
||||
const step = readField(record, "step");
|
||||
const code = readField(record, "errorCode");
|
||||
const httpStatus = boundedInteger(readField(record, "httpStatus"), 400, 599);
|
||||
const exitCode = boundedInteger(readField(record, "exitCode"), 1, 255);
|
||||
const gitCommand = readField(record, "gitCommand");
|
||||
const gitFailureKind = readField(record, "gitFailureKind");
|
||||
const hasGitCommand = phase === "workspace" && step === "git_integration"
|
||||
&& typeof gitCommand === "string" && GIT_COMMANDS.has(gitCommand as WorkspaceRestoreGitCommand);
|
||||
return {
|
||||
phase,
|
||||
...(typeof step === "string" && RESTORE_STEPS.has(step as WorkspaceRestoreStep)
|
||||
? { step: step as WorkspaceRestoreStep } : {}),
|
||||
errorCode: typeof code === "string" && ERROR_CODES.has(code) ? code : "unknown",
|
||||
...(httpStatus !== undefined ? { httpStatus } : {}),
|
||||
...(exitCode !== undefined ? { exitCode } : {}),
|
||||
...(hasGitCommand ? {
|
||||
gitCommand: gitCommand as WorkspaceRestoreGitCommand,
|
||||
gitFailureKind: typeof gitFailureKind === "string" && GIT_FAILURE_KINDS.has(gitFailureKind as GitFailureKind)
|
||||
? gitFailureKind as GitFailureKind : "unknown",
|
||||
} : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export function getWorkspaceRestoreDiagnostic(error: unknown): WorkspaceRestoreDiagnostic | undefined {
|
||||
return error && typeof error === "object"
|
||||
? sanitizeWorkspaceRestoreDiagnostic((diagnosticCapture.getStore() ?? restoreDiagnostics).get(error)) : undefined;
|
||||
}
|
||||
|
||||
/** Preserve the scheduler-selected task's snapshot when errors share identity. */
|
||||
export function recordWorkspaceRestoreDiagnostic(error: unknown, diagnostic: WorkspaceRestoreDiagnostic | undefined): void {
|
||||
const safe = sanitizeWorkspaceRestoreDiagnostic(diagnostic);
|
||||
if (error && typeof error === "object") {
|
||||
const receipts = diagnosticCapture.getStore() ?? restoreDiagnostics;
|
||||
if (safe) receipts.set(error, safe);
|
||||
else receipts.delete(error);
|
||||
}
|
||||
const scope = activeDiagnostic.getStore();
|
||||
if (scope?.active) {
|
||||
if (safe?.step) scope.failures.set(error, {
|
||||
sequence: ++scope.sequence, step: safe.step,
|
||||
...(safe.gitCommand ? { gitCommand: safe.gitCommand, gitFailureKind: safe.gitFailureKind } : {}),
|
||||
});
|
||||
else scope.failures.delete(error);
|
||||
}
|
||||
}
|
||||
|
||||
/** Record only the failing step. Successful cleanup must not replace evidence. */
|
||||
export async function withWorkspaceRestoreStep<T>(step: WorkspaceRestoreStep, operation: () => Promise<T>): Promise<T> {
|
||||
const scope = activeDiagnostic.getStore();
|
||||
if (!scope?.active) return await operation();
|
||||
const started = scope.sequence;
|
||||
try {
|
||||
return await operation();
|
||||
} catch (error) {
|
||||
// A nested step owns its failure. A caught/retried error may be thrown again
|
||||
// by a later step, so identity alone cannot identify the current failure.
|
||||
if (scope.active) {
|
||||
const failure = scope.failures.get(error);
|
||||
if (failure && failure.sequence > started) {
|
||||
failure.step ??= step;
|
||||
} else {
|
||||
scope.failures.set(error, { sequence: ++scope.sequence, step });
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/** Add evidence without changing the thrown error, restore policy, or task ordering. */
|
||||
export async function withWorkspaceRestoreDiagnostics<T>(
|
||||
phase: RestorePhase,
|
||||
operation: () => Promise<T>,
|
||||
onProgress?: RuntimeProgressSink,
|
||||
onDiagnostic?: (diagnostic: WorkspaceRestoreDiagnostic) => void,
|
||||
): Promise<T> {
|
||||
// A nested repository restore propagates to its enclosing workspace task.
|
||||
// That task owns the diagnostic. Independent parallel tasks retain their own
|
||||
// async scopes, so two failed tasks still produce two diagnostic lines.
|
||||
if (activeDiagnostic.getStore()) return await operation();
|
||||
return await activeDiagnostic.run(true, async () => {
|
||||
const parent = activeDiagnostic.getStore();
|
||||
const scope: DiagnosticScope = { active: true, sequence: 0, failures: new Map() };
|
||||
return await activeDiagnostic.run(scope, async () => {
|
||||
try {
|
||||
return await operation();
|
||||
} catch (error) {
|
||||
const failure = scope.failures.get(error);
|
||||
const step = failure?.step;
|
||||
const fields = { phase, ...(step ? { step } : {}), ...diagnostic(error),
|
||||
...(phase === "workspace" && step === "git_integration" && failure?.gitCommand
|
||||
? { gitCommand: failure.gitCommand, gitFailureKind: failure.gitFailureKind } : {}),
|
||||
};
|
||||
recordWorkspaceRestoreDiagnostic(error, fields);
|
||||
if (parent?.active && step) parent.failures.set(error, { ...failure, sequence: ++parent.sequence, step });
|
||||
try {
|
||||
await onProgress?.(`[paperclip] Workspace restore diagnostic: ${JSON.stringify({ phase, ...diagnostic(error) })}\n`);
|
||||
onDiagnostic?.({ ...fields });
|
||||
} catch { /* Diagnostic consumers cannot replace a restore failure. */ }
|
||||
try {
|
||||
if (!parent?.active) await onProgress?.(`[paperclip] Workspace restore diagnostic: ${JSON.stringify(fields)}\n`);
|
||||
} catch {
|
||||
// A broken log sink must not replace a restore failure or relax its safety classification.
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
scope.active = false;
|
||||
scope.failures.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { DatabaseSync } from "node:sqlite";
|
||||
import { createWorkspaceManifest, WorkspaceManifestMap, workspacePathMatcher, type PathManifest, type WorkspacePaths, type WorkspaceManifestWriter } from "./workspace-manifest.js";
|
||||
import { shouldExcludePath } from "./exclude-patterns.js";
|
||||
import { resolvePaperclipInstanceRootForAdapter } from "./server-utils.js";
|
||||
import type { WorkspaceRestoreDiagnostic } from "./workspace-restore-diagnostics.js";
|
||||
|
||||
export type SnapshotEntry =
|
||||
| { kind: "dir" }
|
||||
@@ -292,7 +293,7 @@ export type WorkspaceRestoreFailureCode =
|
||||
*/
|
||||
export type WorkspaceRestoreOutcome =
|
||||
| { readonly ok: true }
|
||||
| { readonly ok: false; readonly code: WorkspaceRestoreFailureCode };
|
||||
| { readonly ok: false; readonly code: WorkspaceRestoreFailureCode; readonly diagnostic?: WorkspaceRestoreDiagnostic };
|
||||
|
||||
/**
|
||||
* Classifies a caught workspace-restore error into one allowlisted code. Maps
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { applyWorkspaceRestoreFailure, withWorkspaceRestore } from "./workspace-restore-result.js";
|
||||
import type { AdapterExecutionResult } from "./types.js";
|
||||
import { withWorkspaceRestoreDiagnostics, withWorkspaceRestoreStep, withWorkspaceRestoreGitCommand } from "./workspace-restore-diagnostics.js";
|
||||
|
||||
const completed: AdapterExecutionResult = {
|
||||
exitCode: 0, signal: null, timedOut: false,
|
||||
@@ -10,6 +11,34 @@ const completed: AdapterExecutionResult = {
|
||||
const unsafe = new Error("Daytona syncOut refusing tarball link whose target escapes the extraction dir: .claude/skills/paperclip -> /tmp/private-clone/secret-key");
|
||||
|
||||
describe("workspace restore settlement", () => {
|
||||
it("retains only the safe failed restore step alongside the original completed turn", async () => {
|
||||
const error = Object.assign(new Error("private-restore-path and command"), { code: 1, stderr: "private-restore-output" });
|
||||
const result = await withWorkspaceRestore(async () => completed, () => withWorkspaceRestoreDiagnostics("workspace", () =>
|
||||
withWorkspaceRestoreStep("index_reset", async () => { throw error; })));
|
||||
expect(result.resultJson).toMatchObject({
|
||||
workspaceRestoreFailure: "restore_failed",
|
||||
workspaceRestoreDiagnostic: { phase: "workspace", step: "index_reset", errorCode: "unknown", exitCode: 1 },
|
||||
executionBeforeRestore: { exitCode: 0, timedOut: false, errorCode: null },
|
||||
});
|
||||
expect(JSON.stringify(result)).not.toContain("private-restore-");
|
||||
});
|
||||
|
||||
it("persists only bounded Git evidence without replacing the completed model result", async () => {
|
||||
const error = Object.assign(new Error("private-git-command /private/workspace"), {
|
||||
code: 1, stdout: "a".repeat(40) + "\nprivate-git-file contents", stderr: "private-git-refs",
|
||||
});
|
||||
const result = await withWorkspaceRestore(async () => completed, () => withWorkspaceRestoreDiagnostics("workspace", () =>
|
||||
withWorkspaceRestoreStep("git_integration", () => withWorkspaceRestoreGitCommand("merge_tree", async () => { throw error; }))));
|
||||
expect(result).toMatchObject({
|
||||
errorCode: "workspace_restore_failed", exitCode: completed.exitCode, summary: completed.summary, usage: completed.usage,
|
||||
resultJson: { requestId: "request", workspaceRestoreFailure: "restore_failed",
|
||||
workspaceRestoreDiagnostic: { phase: "workspace", step: "git_integration", errorCode: "unknown", exitCode: 1,
|
||||
gitCommand: "merge_tree", gitFailureKind: "merge_conflict" },
|
||||
executionBeforeRestore: { exitCode: 0, timedOut: false, errorCode: null } },
|
||||
});
|
||||
expect(JSON.stringify(result)).not.toContain("private-git-");
|
||||
});
|
||||
|
||||
it("retains the completed result and safe member while excluding the unsafe target", async () => {
|
||||
const result = await withWorkspaceRestore(async () => completed, async () => { throw unsafe; });
|
||||
expect(result).toMatchObject({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { hasWorkspaceRestoreFailure, safeWorkspaceRestorePath } from "@paperclipai/shared";
|
||||
import type { AdapterExecutionResult } from "./types.js";
|
||||
import { classifyWorkspaceRestoreFailure } from "./workspace-restore-merge.js";
|
||||
import { getWorkspaceRestoreDiagnostic, withWorkspaceRestoreDiagnosticCapture } from "./workspace-restore-diagnostics.js";
|
||||
|
||||
/** A completed model turn does not imply that required workspace files arrived. */
|
||||
export function applyWorkspaceRestoreFailure(result: AdapterExecutionResult): AdapterExecutionResult {
|
||||
@@ -29,40 +30,44 @@ export async function withWorkspaceRestore(
|
||||
execute: () => Promise<AdapterExecutionResult>,
|
||||
restore: () => Promise<void>,
|
||||
): Promise<AdapterExecutionResult> {
|
||||
let result: AdapterExecutionResult | undefined;
|
||||
let executionError: unknown;
|
||||
let executionThrew = false;
|
||||
try {
|
||||
result = await execute();
|
||||
} catch (error) {
|
||||
executionError = error;
|
||||
executionThrew = true;
|
||||
}
|
||||
try {
|
||||
await restore();
|
||||
} catch (error) {
|
||||
const code = classifyWorkspaceRestoreFailure(error);
|
||||
// Parse the archive member only. Never expose the unsafe link target.
|
||||
const member = error instanceof Error
|
||||
? /Daytona syncOut refusing tarball link whose target escapes the extraction dir: (.+?) -> /.exec(error.message)?.[1]
|
||||
: null;
|
||||
const relativePath = safeWorkspaceRestorePath(member);
|
||||
return applyWorkspaceRestoreFailure({
|
||||
...(result ?? {
|
||||
exitCode: null,
|
||||
signal: null,
|
||||
timedOut: false,
|
||||
errorCode: "adapter_failed",
|
||||
// The server applies its ordinary execution-error redaction to this field.
|
||||
errorMessage: executionError instanceof Error ? executionError.message : "Adapter execution failed.",
|
||||
}),
|
||||
resultJson: {
|
||||
...result?.resultJson,
|
||||
workspaceRestoreFailure: code,
|
||||
...(relativePath ? { workspaceRestorePath: relativePath } : {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
if (executionThrew) throw executionError;
|
||||
return result!;
|
||||
return withWorkspaceRestoreDiagnosticCapture(async () => {
|
||||
let result: AdapterExecutionResult | undefined;
|
||||
let executionError: unknown;
|
||||
let executionThrew = false;
|
||||
try {
|
||||
result = await execute();
|
||||
} catch (error) {
|
||||
executionError = error;
|
||||
executionThrew = true;
|
||||
}
|
||||
try {
|
||||
await restore();
|
||||
} catch (error) {
|
||||
const code = classifyWorkspaceRestoreFailure(error);
|
||||
const diagnostic = getWorkspaceRestoreDiagnostic(error);
|
||||
// Parse the archive member only. Never expose the unsafe link target.
|
||||
const member = error instanceof Error
|
||||
? /Daytona syncOut refusing tarball link whose target escapes the extraction dir: (.+?) -> /.exec(error.message)?.[1]
|
||||
: null;
|
||||
const relativePath = safeWorkspaceRestorePath(member);
|
||||
return applyWorkspaceRestoreFailure({
|
||||
...(result ?? {
|
||||
exitCode: null,
|
||||
signal: null,
|
||||
timedOut: false,
|
||||
errorCode: "adapter_failed",
|
||||
// The server applies its ordinary execution-error redaction to this field.
|
||||
errorMessage: executionError instanceof Error ? executionError.message : "Adapter execution failed.",
|
||||
}),
|
||||
resultJson: {
|
||||
...result?.resultJson,
|
||||
workspaceRestoreFailure: code,
|
||||
...(diagnostic ? { workspaceRestoreDiagnostic: diagnostic } : {}),
|
||||
...(relativePath ? { workspaceRestorePath: relativePath } : {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
if (executionThrew) throw executionError;
|
||||
return result!;
|
||||
});
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { createWorkspaceRestoreTeardown } from "./workspace-restore-teardown.js";
|
||||
import { withWorkspaceRestoreDiagnostics, withWorkspaceRestoreStep } from "./workspace-restore-diagnostics.js";
|
||||
|
||||
// One row per adapter's two message strings, taken verbatim from
|
||||
// claude-local, codex-local, and gemini-local. The factory's contract must
|
||||
@@ -24,6 +25,32 @@ const ADAPTER_MESSAGE_PAIRS = [
|
||||
];
|
||||
|
||||
describe.each(ADAPTER_MESSAGE_PAIRS)("createWorkspaceRestoreTeardown ($adapter)", ({ startMessage, failurePrefix }) => {
|
||||
it("keeps concurrent settlements separate when a provider reuses an error", async () => {
|
||||
const error = Object.assign(new Error("private-restore-path"), { code: 1 });
|
||||
let release!: () => void;
|
||||
let started!: () => void;
|
||||
const gate = new Promise<void>((resolve) => { release = resolve; });
|
||||
const waiting = new Promise<void>((resolve) => { started = resolve; });
|
||||
const first = createWorkspaceRestoreTeardown({
|
||||
stagedRuntime: { restoreWorkspace: () => withWorkspaceRestoreDiagnostics("workspace", () =>
|
||||
withWorkspaceRestoreStep("git_import", async () => { throw error; }), async () => { started(); await gate; }) },
|
||||
onLog: async () => {}, startMessage, failurePrefix,
|
||||
})();
|
||||
await waiting;
|
||||
const second = await createWorkspaceRestoreTeardown({
|
||||
stagedRuntime: { restoreWorkspace: () => withWorkspaceRestoreDiagnostics("asset", () =>
|
||||
withWorkspaceRestoreStep("asset_restore", async () => { throw error; })) },
|
||||
onLog: async () => {}, startMessage, failurePrefix,
|
||||
})();
|
||||
release();
|
||||
expect(await first).toEqual({ ok: false, code: "restore_failed", diagnostic: {
|
||||
phase: "workspace", step: "git_import", errorCode: "unknown", exitCode: 1,
|
||||
} });
|
||||
expect(second).toEqual({ ok: false, code: "restore_failed", diagnostic: {
|
||||
phase: "asset", step: "asset_restore", errorCode: "unknown", exitCode: 1,
|
||||
} });
|
||||
});
|
||||
|
||||
it("logs the start message to stdout and returns ok on a clean restore", async () => {
|
||||
const logLines: Array<{ stream: "stdout" | "stderr"; chunk: string }> = [];
|
||||
const teardown = createWorkspaceRestoreTeardown({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { RuntimeProgressSink } from "./runtime-progress.js";
|
||||
import { getWorkspaceRestoreDiagnostic, withWorkspaceRestoreDiagnosticCapture } from "./workspace-restore-diagnostics.js";
|
||||
import {
|
||||
classifyWorkspaceRestoreFailure,
|
||||
describeWorkspaceRestoreFailure,
|
||||
@@ -33,7 +34,7 @@ export function createWorkspaceRestoreTeardown(input: {
|
||||
failurePrefix: string;
|
||||
}): () => Promise<WorkspaceRestoreOutcome> {
|
||||
const { stagedRuntime, onLog, startMessage, failurePrefix } = input;
|
||||
return async () => {
|
||||
return () => withWorkspaceRestoreDiagnosticCapture(async () => {
|
||||
try {
|
||||
await onLog("stdout", startMessage);
|
||||
await stagedRuntime.restoreWorkspace((line) => onLog("stdout", line));
|
||||
@@ -44,8 +45,9 @@ export function createWorkspaceRestoreTeardown(input: {
|
||||
// filesystem path or a process id. Log only the fixed, allowlisted
|
||||
// diagnostic for the classified code.
|
||||
const code = classifyWorkspaceRestoreFailure(err);
|
||||
const diagnostic = getWorkspaceRestoreDiagnostic(err);
|
||||
await onLog("stderr", `${failurePrefix}: ${describeWorkspaceRestoreFailure(code)}\n`);
|
||||
return { ok: false, code };
|
||||
return { ok: false, code, ...(diagnostic ? { diagnostic } : {}) };
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -8,10 +8,21 @@ describe("Claude model defaults", () => {
|
||||
expect(claudeLocalReasoningEffortsForModel(model)).toEqual(["low", "medium", "high", "xhigh", "max"]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
"claude-sonnet-5-5",
|
||||
"claude-sonnet-5-5[1m]",
|
||||
"us.anthropic.claude-sonnet-5-5",
|
||||
"global.anthropic.claude-sonnet-5-5[1m]",
|
||||
])("requires CLI 2.1.284 and offers all efforts for %s", (model) => {
|
||||
expect(minimumClaudeCliVersionForModel(model)).toBe("2.1.284");
|
||||
expect(claudeLocalReasoningEffortsForModel(model)).toEqual(["low", "medium", "high", "xhigh", "max"]);
|
||||
});
|
||||
|
||||
it("keeps model-specific Claude reasoning limits", () => {
|
||||
expect(claudeLocalReasoningEffortsForModel("claude-fable-5-1")).toContain("xhigh");
|
||||
expect(claudeLocalReasoningEffortsForModel("claude-sonnet-5")).toContain("max");
|
||||
expect(claudeLocalReasoningEffortsForModel("claude-sonnet-4-6")).toEqual(["low", "medium", "high", "max"]);
|
||||
expect(claudeLocalReasoningEffortsForModel("claude-sonnet-4-5")).toEqual(["low", "medium", "high"]);
|
||||
expect(claudeLocalReasoningEffortsForModel("claude-haiku-4-5")).toEqual([]);
|
||||
expect(claudeLocalReasoningEffortsForModel("custom-model")).toEqual(["low", "medium", "high"]);
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ export const type = "claude_local";
|
||||
export function claudeLocalReasoningEffortsForModel(model: string): readonly string[] {
|
||||
const id = model.trim().replace(/\[1m\]$/, "").replace(/^(?:(?:us|eu|apac|global)\.)?anthropic\./, "");
|
||||
if (/^claude-haiku-/.test(id)) return [];
|
||||
if (/^claude-(?:opus-5(?:-5)?|opus-4-[78]|sonnet-5|fable-5(?:-1)?)$/.test(id)) {
|
||||
if (/^claude-(?:opus-5(?:-5)?|opus-4-[78]|sonnet-5(?:-5)?|fable-5(?:-1)?)$/.test(id)) {
|
||||
return ["low", "medium", "high", "xhigh", "max"];
|
||||
}
|
||||
if (/^claude-(?:opus|sonnet)-4-6(?:-v1)?$/.test(id)) return ["low", "medium", "high", "max"];
|
||||
@@ -43,13 +43,14 @@ export const models = [
|
||||
{ id: "claude-fable-5-1", label: "Claude Fable 5.1" },
|
||||
{ id: "claude-mythos-5", label: "Claude Mythos 5" },
|
||||
{ id: "claude-opus-5-5", label: "Claude Opus 5.5" },
|
||||
{ id: "claude-sonnet-5", label: "Claude Sonnet 5" },
|
||||
{ id: "claude-sonnet-5-5", label: "Claude Sonnet 5.5" },
|
||||
{ id: "claude-haiku-4-5", label: "Claude Haiku 4.5" },
|
||||
{ id: "claude-fable-5", label: "Claude Fable 5" },
|
||||
{ id: "claude-opus-5", label: "Claude Opus 5" },
|
||||
{ id: "claude-opus-4-8", label: "Claude Opus 4.8" },
|
||||
{ id: "claude-opus-4-7", label: "Claude Opus 4.7" },
|
||||
{ id: "claude-opus-4-6", label: "Claude Opus 4.6" },
|
||||
{ id: "claude-sonnet-5", label: "Claude Sonnet 5" },
|
||||
{ id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6" },
|
||||
{ id: "claude-sonnet-4-5", label: "Claude Sonnet 4.5" },
|
||||
];
|
||||
@@ -63,7 +64,7 @@ Core fields:
|
||||
- cwd (string, optional): default absolute working directory fallback for the agent process (created if missing when possible)
|
||||
- instructionsFilePath (string, optional): absolute path to a markdown instructions file injected at runtime
|
||||
- model (string, optional): Claude model id. Missing or blank defaults to ${DEFAULT_CLAUDE_LOCAL_MODEL} in both CLI and ACP, including existing agents. Explicit model IDs and ANTHROPIC_MODEL overrides are preserved. Bedrock/Vertex without an explicit model retain their provider default.
|
||||
- effort (string, optional): model-specific reasoning effort passed via --effort (low|medium|high; current Opus, Sonnet 5, and Fable models also support xhigh|max)
|
||||
- effort (string, optional): model-specific reasoning effort passed via --effort (low|medium|high; current Opus, Sonnet 5/5.5, and Fable models also support xhigh|max)
|
||||
- chrome (boolean, optional): pass --chrome when running Claude
|
||||
- promptTemplate (string, optional): run prompt template
|
||||
- maxTurnsPerRun (number, optional): max turns for one run
|
||||
@@ -92,6 +93,7 @@ Operational fields:
|
||||
|
||||
Notes:
|
||||
- Claude Opus 5.5 uses model ID \`claude-opus-5-5\` and requires Claude Code v2.1.280 or later.
|
||||
- Claude Sonnet 5.5 uses model ID \`claude-sonnet-5-5\` and requires Claude Code v2.1.284 or later.
|
||||
- filesystemScope and networkScope are spawn-level confinement and are orthogonal to Claude permission flags. Both require Bubblewrap on the host and explicit engine="cli"; default or explicit ACP is rejected because ACP confinement is not yet supported. networkScope="allowlist" injects HTTP_PROXY/HTTPS_PROXY for the CLI while its private network namespace blocks direct sockets, so every required provider/API hostname must be listed explicitly.
|
||||
- The Claude ACP lane requires Node >=24.11.0 and @agentclientprotocol/claude-agent-acp to be installed with this adapter package. Missing prerequisites fail both default and explicit ACP runs with an actionable setup error; the adapter never switches engines automatically.
|
||||
- For ACP runs, model selection is passed through ANTHROPIC_MODEL at ACP server startup; Paperclip-managed Claude permissions and ephemeral skill materialization are handled by the shared ACP engine.
|
||||
|
||||
@@ -1065,10 +1065,13 @@ describe("claude_local ACP lane", () => {
|
||||
// Only a fixed diagnostic may contain the errno, never the raw error.
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.resultJson?.workspaceRestoreFailure).toBe("restore_permission_denied");
|
||||
expect(result.resultJson?.workspaceRestoreDiagnostic).toEqual({
|
||||
phase: "workspace", step: "directory_merge", errorCode: "EACCES",
|
||||
});
|
||||
const allLogs = loggedLines.join("");
|
||||
expect(allLogs).not.toContain("SENTINEL-HOST-PATH-marker");
|
||||
expect(allLogs).not.toContain(localCwd);
|
||||
const diagnostic = '[paperclip] Workspace restore diagnostic: {"phase":"workspace","errorCode":"EACCES"}\n';
|
||||
const diagnostic = '[paperclip] Workspace restore diagnostic: {"phase":"workspace","step":"directory_merge","errorCode":"EACCES"}\n';
|
||||
expect(loggedLines.filter((line) => line.includes("Workspace restore diagnostic:"))).toEqual([diagnostic]);
|
||||
expect(loggedLines.filter((line) => line !== diagnostic).join("")).not.toContain("EACCES");
|
||||
expect(allLogs).toContain("permission denied");
|
||||
|
||||
@@ -38,6 +38,7 @@ function cacheKeyForTarget(command: string, target: AdapterExecutionTarget | nul
|
||||
export function minimumClaudeCliVersionForModel(model: string): string | null {
|
||||
const modelId = model.trim().replace(/\[1m\]$/, "").replace(/^(?:(?:us|eu|apac|global)\.)?anthropic\./, "");
|
||||
if (modelId === "claude-opus-5-5") return "2.1.280";
|
||||
if (modelId === "claude-sonnet-5-5") return "2.1.284";
|
||||
return modelId === "claude-fable-5-1"
|
||||
? CLAUDE_FABLE_5_1_MIN_CLI_VERSION
|
||||
: null;
|
||||
|
||||
@@ -18,7 +18,7 @@ const {
|
||||
signal: null,
|
||||
timedOut: false,
|
||||
stdout: args.includes("--version")
|
||||
? "2.1.280 (Claude Code)\n"
|
||||
? "2.1.284 (Claude Code)\n"
|
||||
: [
|
||||
JSON.stringify({ type: "system", subtype: "init", session_id: "claude-session-1", model: "claude-sonnet" }),
|
||||
JSON.stringify({ type: "assistant", session_id: "claude-session-1", message: { content: [{ type: "text", text: "hello" }] } }),
|
||||
@@ -467,7 +467,7 @@ describe("claude remote execution", () => {
|
||||
return { args: call?.[2] ?? [], result };
|
||||
}
|
||||
|
||||
it.each(["claude-fable-5-1", "claude-opus-5-5"])("passes %s as --model on the CLI lane", async (model) => {
|
||||
it.each(["claude-fable-5-1", "claude-opus-5-5", "claude-sonnet-5-5"])("passes %s as --model on the CLI lane", async (model) => {
|
||||
const { args } = await executeWithModel("paperclip-claude-model-direct-", {
|
||||
model,
|
||||
});
|
||||
@@ -500,6 +500,7 @@ describe("claude remote execution", () => {
|
||||
it.each([
|
||||
["claude-fable-5-1", "2.1.251", "2.1.247"],
|
||||
["claude-opus-5-5", "2.1.280", "2.1.279"],
|
||||
["claude-sonnet-5-5", "2.1.284", "2.1.283"],
|
||||
])("rejects %s before launch below CLI %s", async (model, minimumVersion, detectedVersion) => {
|
||||
runChildProcess.mockResolvedValueOnce({
|
||||
exitCode: 0,
|
||||
|
||||
@@ -17,13 +17,15 @@ const BEDROCK_MODELS: AdapterModel[] = [
|
||||
// Fable 5.1's documented geo inference ID carries no -v1 suffix, unlike earlier entries.
|
||||
{ id: "us.anthropic.claude-fable-5-1", label: "Bedrock Fable 5.1" },
|
||||
{ id: "us.anthropic.claude-opus-5-5", label: "Bedrock Opus 5.5" },
|
||||
{ id: "us.anthropic.claude-sonnet-5", label: "Bedrock Sonnet 5" },
|
||||
// Sonnet 5.5's documented Bedrock ID is dateless, like Fable 5.1 and Opus 5.5.
|
||||
{ id: "us.anthropic.claude-sonnet-5-5", label: "Bedrock Sonnet 5.5" },
|
||||
{ id: "us.anthropic.claude-haiku-4-5-20251001-v1:0", label: "Bedrock Haiku 4.5" },
|
||||
{ id: "us.anthropic.claude-fable-5", label: "Bedrock Fable 5" },
|
||||
{ id: "us.anthropic.claude-opus-5", label: "Bedrock Opus 5" },
|
||||
{ id: "us.anthropic.claude-opus-4-8", label: "Bedrock Opus 4.8" },
|
||||
{ id: "us.anthropic.claude-opus-4-7", label: "Bedrock Opus 4.7" },
|
||||
{ id: "us.anthropic.claude-opus-4-6-v1", label: "Bedrock Opus 4.6" },
|
||||
{ id: "us.anthropic.claude-sonnet-5", label: "Bedrock Sonnet 5" },
|
||||
{ id: "us.anthropic.claude-sonnet-4-6", label: "Bedrock Sonnet 4.6" },
|
||||
{ id: "us.anthropic.claude-sonnet-4-5-20250929-v2:0", label: "Bedrock Sonnet 4.5" },
|
||||
];
|
||||
|
||||
@@ -642,6 +642,7 @@ describe("claude CLI local hello probe hardening", () => {
|
||||
it.each([
|
||||
["claude-fable-5-1", "2.1.251"],
|
||||
["claude-opus-5-5", "2.1.280"],
|
||||
["claude-sonnet-5-5", "2.1.284"],
|
||||
])("warns without executing %s when runtime PATH selects a different executable", async (model, minimumVersion) => {
|
||||
const runtimeDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-cli-runtime-path-"));
|
||||
const runtimeClaudePath = path.join(runtimeDir, "claude");
|
||||
|
||||
@@ -144,6 +144,7 @@ describe("claude CLI model compatibility check", () => {
|
||||
it.each([
|
||||
["claude-fable-5-1", "2.1.251", "2.1.247"],
|
||||
["claude-opus-5-5", "2.1.280", "2.1.279"],
|
||||
["claude-sonnet-5-5", "2.1.284", "2.1.283"],
|
||||
])("fails before the hello probe for %s below CLI %s", async (model, minimumVersion, detectedVersion) => {
|
||||
probeResult.value = {
|
||||
exitCode: 0,
|
||||
|
||||
@@ -15,7 +15,8 @@ describe("codex local adapter metadata", () => {
|
||||
// alias, so it must not be advertised or used as the default (it triggers a fallback warning).
|
||||
expect(DEFAULT_CODEX_LOCAL_MODEL).toBe("gpt-5.6-sol");
|
||||
// Ordered like the ChatGPT app: newest version first, then by capability within a version (#14878).
|
||||
expect(modelIds.slice(0, 12)).toEqual([
|
||||
expect(modelIds.slice(0, 13)).toEqual([
|
||||
"gpt-6.1-sol",
|
||||
"gpt-6-astra",
|
||||
"gpt-6-sol",
|
||||
"gpt-6-luna",
|
||||
@@ -32,12 +33,13 @@ describe("codex local adapter metadata", () => {
|
||||
expect(modelIds).not.toContain("gpt-5.6");
|
||||
expect(isCodexLocalFastModeSupported(DEFAULT_CODEX_LOCAL_MODEL)).toBe(true);
|
||||
expect(isCodexLocalFastModeSupported("gpt-6-astra")).toBe(true);
|
||||
expect(isCodexLocalFastModeSupported("gpt-6.1-sol")).toBe(true);
|
||||
expect(isCodexLocalFastModeSupported("gpt-6-sol")).toBe(true);
|
||||
expect(modelIds).not.toContain("gpt-5.3-codex");
|
||||
expect(modelIds).not.toContain("gpt-5.3-codex-spark");
|
||||
});
|
||||
|
||||
it.each(["gpt-6-astra", "gpt-6-sol", " gpt-6-sol ", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6"])("uses the reasoning efforts advertised for %s", (model) => {
|
||||
it.each(["gpt-6-astra", "gpt-6.1-sol", "gpt-6-sol", " gpt-6-sol ", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6"])("uses the reasoning efforts advertised for %s", (model) => {
|
||||
expect(codexLocalReasoningEffortsForModel(model)).toEqual([
|
||||
"low",
|
||||
"medium",
|
||||
|
||||
@@ -14,6 +14,7 @@ export const DEFAULT_CODEX_LOCAL_MODEL = PAPERCLIP_RUNNER_DEFAULT_MODELS.codex;
|
||||
export const DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX = true;
|
||||
export const CODEX_LOCAL_FAST_MODE_SUPPORTED_MODELS = [
|
||||
"gpt-6-astra",
|
||||
"gpt-6.1-sol",
|
||||
"gpt-6-sol",
|
||||
"gpt-6-luna",
|
||||
"gpt-5.6-sol",
|
||||
@@ -71,6 +72,7 @@ export function codexLocalReasoningEffortsForModel(
|
||||
const normalizedModel = normalizeCodexModel(model);
|
||||
switch (normalizedModel) {
|
||||
case "gpt-6-astra":
|
||||
case "gpt-6.1-sol":
|
||||
case "gpt-6-sol":
|
||||
case "gpt-5.6-sol":
|
||||
case "gpt-5.6-terra":
|
||||
@@ -110,6 +112,7 @@ export function isCodexLocalFastModeSupported(model: string | null | undefined):
|
||||
// then by decreasing capability inside each version, older models at the end. The server
|
||||
// returns this list as written and the model dropdown keeps its order.
|
||||
export const models = [
|
||||
{ id: "gpt-6.1-sol", label: "gpt-6.1-sol" },
|
||||
{ id: "gpt-6-astra", label: "gpt-6-astra" },
|
||||
{ id: "gpt-6-sol", label: "gpt-6-sol" },
|
||||
{ id: "gpt-6-luna", label: "gpt-6-luna" },
|
||||
@@ -138,10 +141,10 @@ Core fields:
|
||||
- cwd (string, optional): default absolute working directory fallback for the agent process (created if missing when possible)
|
||||
- instructionsFilePath (string, optional): absolute path to a markdown instructions file prepended to stdin prompt at runtime
|
||||
- model (string, optional): Codex model id
|
||||
- modelReasoningEffort (string, optional): reasoning effort override passed via -c model_reasoning_effort=...; GPT-6 Astra/Sol and GPT-5.6 Sol/Terra support low|medium|high|xhigh|max|ultra; GPT-6 Luna and GPT-5.6 Luna support low|medium|high|xhigh|max
|
||||
- modelReasoningEffort (string, optional): reasoning effort override passed via -c model_reasoning_effort=...; GPT-6 Astra, GPT-6.1 Sol, GPT-6 Sol, and GPT-5.6 Sol/Terra support low|medium|high|xhigh|max|ultra; GPT-6 Luna and GPT-5.6 Luna support low|medium|high|xhigh|max
|
||||
- promptTemplate (string, optional): run prompt template
|
||||
- search (boolean, optional): run codex with --search
|
||||
- fastMode (boolean, optional): enable Codex Fast mode; supported on GPT-6 (astra/sol/luna), GPT-5.6 (sol/terra/luna), GPT-5.5, GPT-5.4 and passed through for manual model IDs
|
||||
- fastMode (boolean, optional): enable Codex Fast mode; supported on GPT-6 (astra/sol/luna), GPT-6.1 Sol, GPT-5.6 (sol/terra/luna), GPT-5.5, GPT-5.4 and passed through for manual model IDs
|
||||
- dangerouslyBypassApprovalsAndSandbox (boolean, optional): run with bypass flag
|
||||
- command (string, optional): defaults to "codex"
|
||||
- extraArgs (string[], optional): additional CLI args
|
||||
@@ -172,7 +175,7 @@ Notes:
|
||||
- Paperclip injects desired local skills into the effective CODEX_HOME/skills/ directory at execution time so Codex can discover "$paperclip" and related skills without polluting the project working directory. For new and updated agents, Paperclip assigns an isolated managed home at ~/.paperclip/instances/<id>/companies/<companyId>/agents/<agentId>/codex-home/skills/; when CODEX_HOME is explicitly overridden in adapter config, that override is used instead.
|
||||
- New and updated codex_local agents persist an empty OPENAI_API_KEY override by default so a host-level OPENAI_API_KEY cannot leak into Codex runs through process inheritance. Explicit CODEX_HOME overrides must not point at the shared company codex-home, $CODEX_HOME, or ~/.codex.
|
||||
- Some model/tool combinations reject certain effort levels (for example minimal with web search enabled).
|
||||
- Fast mode is supported on GPT-6 (astra/sol/luna), GPT-5.6 (sol/terra/luna), GPT-5.5, GPT-5.4 and manual model IDs. When enabled for those models, Paperclip applies \`service_tier="fast"\` and \`features.fast_mode=true\`.
|
||||
- Fast mode is supported on GPT-6 (astra/sol/luna), GPT-6.1 Sol, GPT-5.6 (sol/terra/luna), GPT-5.5, GPT-5.4 and manual model IDs. When enabled for those models, Paperclip applies \`service_tier="fast"\` and \`features.fast_mode=true\`.
|
||||
- When Paperclip realizes a workspace/runtime for a run, it injects PAPERCLIP_WORKSPACE_* and PAPERCLIP_RUNTIME_* env vars for agent-side tooling.
|
||||
- The ACP engine keeps its workspace sandbox and enables network access on each turn. Explicit sandbox_workspace_write.network_access overrides in extraArgs (or env.PAPERCLIP_CODEX_ACP_NETWORK_ACCESS="false") disable it; execution-target network denial wins. The bundled ACP patch is needed because upstream mode presets override Codex config.toml on every turn.
|
||||
- The CLI engine defaults to a writable workspace sandbox with network access for unattended work and Paperclip API calls. It does not enable the dangerous bypass flag. Explicit sandbox modes/profiles and network overrides in extraArgs retain their meaning. An execution-target network denial remains enforced.
|
||||
|
||||
@@ -508,7 +508,7 @@ describe("codex_local ACP lane", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.each([["gpt-6-astra", "ultra"], ["gpt-6-sol", "ultra"], ["gpt-6-luna", "max"], ["gpt-5.6-sol", "ultra"], ["gpt-5.6-terra", "ultra"], ["gpt-5.6-luna", "max"]])("forwards %s controls to the ACPX Codex target", (model, effort) => {
|
||||
it.each([["gpt-6-astra", "ultra"], ["gpt-6.1-sol", "ultra"], ["gpt-6-sol", "ultra"], ["gpt-6-luna", "max"], ["gpt-5.6-sol", "ultra"], ["gpt-5.6-terra", "ultra"], ["gpt-5.6-luna", "max"]])("forwards %s controls to the ACPX Codex target", (model, effort) => {
|
||||
expect(buildCodexAcpConfig({
|
||||
engine: "acp",
|
||||
model,
|
||||
|
||||
@@ -9,7 +9,7 @@ describe("buildCodexExecArgs", () => {
|
||||
if (resumeSessionId) expect(args.slice(-3)).toEqual(["resume", resumeSessionId, "-"]);
|
||||
});
|
||||
|
||||
it.each([["gpt-6-astra", "ultra"], ["gpt-6-sol", "ultra"], ["gpt-6-luna", "max"], ["gpt-5.6-sol", "ultra"], ["gpt-5.6-terra", "ultra"], ["gpt-5.6-luna", "max"]])("forwards %s, its supported reasoning effort, and fast mode", (model, effort) => {
|
||||
it.each([["gpt-6-astra", "ultra"], ["gpt-6.1-sol", "ultra"], ["gpt-6-sol", "ultra"], ["gpt-6-luna", "max"], ["gpt-5.6-sol", "ultra"], ["gpt-5.6-terra", "ultra"], ["gpt-5.6-luna", "max"]])("forwards %s, its supported reasoning effort, and fast mode", (model, effort) => {
|
||||
const result = buildCodexExecArgs({
|
||||
model,
|
||||
modelReasoningEffort: effort,
|
||||
@@ -148,7 +148,7 @@ describe("buildCodexExecArgs", () => {
|
||||
expect(result.fastModeRequested).toBe(true);
|
||||
expect(result.fastModeApplied).toBe(false);
|
||||
expect(result.fastModeIgnoredReason).toContain(
|
||||
"currently only supported on gpt-6-astra, gpt-6-sol, gpt-6-luna, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gpt-5.5, gpt-5.4 or manually configured model IDs",
|
||||
"currently only supported on gpt-6-astra, gpt-6.1-sol, gpt-6-sol, gpt-6-luna, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gpt-5.5, gpt-5.4 or manually configured model IDs",
|
||||
);
|
||||
expect(result.args).toEqual([
|
||||
"exec",
|
||||
|
||||
@@ -94,7 +94,7 @@ describe("buildCodexLocalConfig", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.each([["gpt-6-astra", "ultra"], ["gpt-6-sol", "ultra"], ["gpt-6-luna", "max"], ["gpt-5.6-sol", "ultra"], ["gpt-5.6-terra", "ultra"], ["gpt-5.6-luna", "max"]])("persists the exact %s model and supported controls", (model, effort) => {
|
||||
it.each([["gpt-6-astra", "ultra"], ["gpt-6.1-sol", "ultra"], ["gpt-6-sol", "ultra"], ["gpt-6-luna", "max"], ["gpt-5.6-sol", "ultra"], ["gpt-5.6-terra", "ultra"], ["gpt-5.6-luna", "max"]])("persists the exact %s model and supported controls", (model, effort) => {
|
||||
const config = buildCodexLocalConfig(
|
||||
makeValues({
|
||||
model,
|
||||
|
||||
@@ -164,7 +164,7 @@ describe("cursor_cloud execute", () => {
|
||||
expect(result.exitCode).toBe(0);
|
||||
const prompt = String(sdkAgent.send.mock.calls[0]?.[0]);
|
||||
expect(prompt).toContain(directive);
|
||||
expect(prompt).toContain(custom ? "Do the work for" : "Continue your Paperclip conversation");
|
||||
expect(prompt).toContain(custom ? "Do the work for" : "You are agent agent-1");
|
||||
expect(prompt).not.toContain("Execution contract:");
|
||||
expect(prompt).not.toContain("Create child issues");
|
||||
});
|
||||
@@ -178,6 +178,9 @@ describe("cursor_cloud execute", () => {
|
||||
expect(result.exitCode).toBe(0);
|
||||
const prompt = String(sdkAgent.send.mock.calls[0]?.[0]);
|
||||
expect(prompt).toContain("## Owned assignment");
|
||||
expect(prompt).toContain("You are agent agent-1 (Cursor Cloud Agent).");
|
||||
expect(prompt).toContain("Connection tools:");
|
||||
expect(prompt).not.toContain("Execution contract:");
|
||||
expect(prompt.indexOf("Append the same ledger entry.")).toBeLessThan(prompt.indexOf("Change the final scope to the launch checklist."));
|
||||
expect(prompt.split("Append the same ledger entry.")).toHaveLength(3);
|
||||
});
|
||||
|
||||
@@ -82,7 +82,7 @@ test("renders standard assignment wake with task authority and no backlog discov
|
||||
expect(prompt).toContain("Paperclip task context:");
|
||||
expect(prompt).toContain("Add focused unit tests for assignment wake and custom prompt rendering.");
|
||||
expect(prompt).toContain("The harness already checked out this issue for the current run.");
|
||||
expect(prompt).toContain("clear final disposition");
|
||||
expect(prompt).not.toContain("clear final disposition");
|
||||
expect(prompt).not.toContain("check for unassigned issues");
|
||||
expect(prompt).not.toContain("status=backlog");
|
||||
});
|
||||
@@ -130,8 +130,8 @@ test("renders scoped planning wake authority before the Hermes default workflow"
|
||||
expect(prompt).toContain("- checkout: already claimed by the harness for this run");
|
||||
expect(prompt).toContain("The harness already checked out this issue for the current run.");
|
||||
expect(prompt).toContain("Issue description:\n```text\nUse the wake payload as runtime authority.\n```");
|
||||
expect(prompt).toContain("clear final disposition");
|
||||
expect(prompt).toContain("keep `in_progress` only when a live continuation path exists");
|
||||
expect(prompt).not.toContain("clear final disposition");
|
||||
expect(prompt).not.toContain("keep `in_progress` only when a live continuation path exists");
|
||||
expect(prompt).not.toContain("check for unassigned issues");
|
||||
expect(prompt).not.toContain("status=backlog");
|
||||
});
|
||||
|
||||
@@ -54,6 +54,7 @@ export function isValidOpenCodeModelId(value: unknown): value is string {
|
||||
export const models: Array<{ id: string; label: string }> = [
|
||||
{ id: DEFAULT_OPENCODE_LOCAL_MODEL, label: DEFAULT_OPENCODE_LOCAL_MODEL },
|
||||
{ id: "openai/gpt-6-astra", label: "openai/gpt-6-astra" },
|
||||
{ id: "openai/gpt-6.1-sol", label: "openai/gpt-6.1-sol" },
|
||||
{ id: "openai/gpt-6-sol", label: "openai/gpt-6-sol" },
|
||||
{ id: "openai/gpt-6-luna", label: "openai/gpt-6-luna" },
|
||||
{ id: "openai/gpt-5.6-sol", label: "openai/gpt-5.6-sol" },
|
||||
@@ -62,6 +63,7 @@ export const models: Array<{ id: string; label: string }> = [
|
||||
{ id: "anthropic/claude-opus-5-5", label: "anthropic/claude-opus-5-5" },
|
||||
{ id: "anthropic/claude-opus-5", label: "anthropic/claude-opus-5" },
|
||||
{ id: "anthropic/claude-fable-5-1", label: "anthropic/claude-fable-5-1" },
|
||||
{ id: "anthropic/claude-sonnet-5-5", label: "anthropic/claude-sonnet-5-5" },
|
||||
{ id: "anthropic/claude-sonnet-5", label: "anthropic/claude-sonnet-5" },
|
||||
{ id: "google/gemini-3.8-flash", label: "google/gemini-3.8-flash" },
|
||||
{ id: "xai/grok-4.7", label: "xai/grok-4.7" },
|
||||
|
||||
@@ -77,7 +77,7 @@ describe("OpenCode local skill injection", () => {
|
||||
});
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(prompt).toContain(directive);
|
||||
expect(prompt).toContain(custom ? "Custom agent instruction." : "Continue your Paperclip conversation");
|
||||
expect(prompt).toContain(custom ? "Custom agent instruction." : "You are agent agent-1");
|
||||
expect(prompt).not.toContain("Execution contract:");
|
||||
expect(prompt).not.toContain("Create child issues");
|
||||
});
|
||||
@@ -102,6 +102,9 @@ describe("OpenCode local skill injection", () => {
|
||||
expect(prompts).toHaveLength(2);
|
||||
expect(prompts[0]).toContain("## Compact assignment");
|
||||
expect(prompts[1]).toContain("## Owned assignment");
|
||||
for (const prompt of prompts) expect(prompt).not.toContain("Execution contract:");
|
||||
expect(prompts[1]).toContain("You are agent agent-1 (OpenCode).");
|
||||
expect(prompts[1]).toContain("Connection tools:");
|
||||
});
|
||||
|
||||
it("injects runtime skills into the configured child HOME", async () => {
|
||||
|
||||
@@ -696,7 +696,7 @@ describe("pi remote execution", () => {
|
||||
expect(userPrompt).toBe("CUSTOM POLICY run-custom-policy");
|
||||
});
|
||||
|
||||
it("keeps the resumed default execution contract in the system carrier only", async () => {
|
||||
it("keeps resumed default identity and connection guidance in the system carrier", async () => {
|
||||
const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-pi-resumed-policy-"));
|
||||
cleanupDirs.push(rootDir);
|
||||
const sessionPath = path.join(rootDir, "session.jsonl");
|
||||
@@ -726,11 +726,13 @@ describe("pi remote execution", () => {
|
||||
const args = call?.[2] ?? [];
|
||||
const systemPrompt = args[args.indexOf("--append-system-prompt") + 1] ?? "";
|
||||
const userPrompt = args.at(-1) ?? "";
|
||||
expect(systemPrompt).toContain("Execution contract:");
|
||||
expect(systemPrompt).toContain("You are agent agent-1 (Pi Builder).");
|
||||
expect(systemPrompt).toContain("Connection tools:");
|
||||
expect(systemPrompt).not.toContain("Execution contract:");
|
||||
expect(userPrompt).not.toContain("Execution contract:");
|
||||
});
|
||||
|
||||
it("keeps the default contract in system input when custom prompt uses loaded instructions", async () => {
|
||||
it("keeps default identity in system input when custom prompt uses loaded instructions", async () => {
|
||||
const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-pi-instructions-policy-"));
|
||||
cleanupDirs.push(rootDir);
|
||||
const sessionPath = path.join(rootDir, "session.jsonl");
|
||||
@@ -769,7 +771,9 @@ describe("pi remote execution", () => {
|
||||
const systemPrompt = args[args.indexOf("--append-system-prompt") + 1] ?? "";
|
||||
const userPrompt = args.at(-1) ?? "";
|
||||
expect(systemPrompt).toContain("Loaded instructions for this run.");
|
||||
expect(systemPrompt).toContain("Execution contract:");
|
||||
expect(systemPrompt).toContain("You are agent agent-1 (Pi Builder).");
|
||||
expect(systemPrompt).toContain("Connection tools:");
|
||||
expect(systemPrompt).not.toContain("Execution contract:");
|
||||
expect(userPrompt).not.toContain("CUSTOM POLICY run-instructions-policy");
|
||||
expect(userPrompt).not.toContain("Execution contract:");
|
||||
});
|
||||
|
||||
@@ -38,6 +38,11 @@ describeEmbeddedPostgres("connections v3 schema core migration", () => {
|
||||
await sql`DELETE FROM "drizzle"."__drizzle_migrations" WHERE "hash" = ${await migrationHash()}`;
|
||||
// AI defaults arrive in 0273/0277 and depend on the composite grant key
|
||||
// from 0232. Rewind those tables before recreating the 0182 grant schema.
|
||||
// Router pins/cursors depend on pools, whose composite connection FK also
|
||||
// arrives after 0182. Rewind these empty fixture tables in dependency order.
|
||||
await sql`DROP TABLE IF EXISTS "ai_connection_task_pins"`;
|
||||
await sql`DROP TABLE IF EXISTS "ai_connection_router_cursors"`;
|
||||
await sql`DROP TABLE IF EXISTS "ai_connection_pools"`;
|
||||
await sql`DROP TABLE IF EXISTS "ai_provider_defaults"`;
|
||||
await sql`DROP TABLE IF EXISTS "ai_connection_defaults"`;
|
||||
await sql`DROP TABLE IF EXISTS "connection_grant_delegations"`;
|
||||
@@ -47,6 +52,9 @@ describeEmbeddedPostgres("connections v3 schema core migration", () => {
|
||||
await sql`DROP TABLE IF EXISTS "browser_use_runs"`;
|
||||
await sql`DROP TABLE IF EXISTS "browser_use_sessions"`;
|
||||
await sql`DROP TABLE IF EXISTS "browser_use_settings"`;
|
||||
// Managed-account observations are later consumers of the same connection key.
|
||||
await sql`DROP TABLE IF EXISTS "tool_connection_app_syncs"`;
|
||||
await sql`DROP TABLE IF EXISTS "tool_connection_app_snapshots"`;
|
||||
await sql`DROP TABLE IF EXISTS "connection_grants"`;
|
||||
await sql`DROP INDEX IF EXISTS "tool_connections_company_uid_uq"`;
|
||||
await sql`ALTER TABLE "tool_connections" DROP CONSTRAINT IF EXISTS "tool_connections_company_id_uq"`;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
CREATE TABLE IF NOT EXISTS "tool_connection_app_snapshots" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"company_id" uuid NOT NULL,
|
||||
"connection_id" uuid NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"credential_key" text NOT NULL,
|
||||
"toolkit" text NOT NULL,
|
||||
"status" text NOT NULL,
|
||||
"accounts" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
"checked_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "tool_connection_app_snapshots" ADD CONSTRAINT "tool_connection_app_snapshots_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL;
|
||||
END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "tool_connection_app_snapshots" ADD CONSTRAINT "tool_connection_app_snapshots_company_connection_fk" FOREIGN KEY ("company_id","connection_id") REFERENCES "public"."tool_connections"("company_id","id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL;
|
||||
END $$;--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "tool_connection_app_snapshots_owner_toolkit_uq" ON "tool_connection_app_snapshots" USING btree ("company_id","connection_id","user_id","toolkit");
|
||||
@@ -0,0 +1,25 @@
|
||||
CREATE TABLE IF NOT EXISTS "tool_connection_app_syncs" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"company_id" uuid NOT NULL,
|
||||
"connection_id" uuid NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"credential_key" text NOT NULL,
|
||||
"lease_id" uuid NOT NULL,
|
||||
"status" text NOT NULL,
|
||||
"checked" integer DEFAULT 0 NOT NULL,
|
||||
"total" integer DEFAULT 0 NOT NULL,
|
||||
"failed" integer DEFAULT 0 NOT NULL,
|
||||
"last_completed_at" timestamp with time zone,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "tool_connection_app_snapshots" ADD COLUMN IF NOT EXISTS "error_at" timestamp with time zone;--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "tool_connection_app_syncs" ADD CONSTRAINT "tool_connection_app_syncs_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL;
|
||||
END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "tool_connection_app_syncs" ADD CONSTRAINT "tool_connection_app_syncs_company_connection_fk" FOREIGN KEY ("company_id","connection_id") REFERENCES "public"."tool_connections"("company_id","id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL;
|
||||
END $$;--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "tool_connection_app_syncs_owner_key_uq" ON "tool_connection_app_syncs" USING btree ("company_id","connection_id","user_id","credential_key");
|
||||
@@ -0,0 +1,14 @@
|
||||
CREATE TABLE "resource_lifecycle_events" (
|
||||
"id" bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "resource_lifecycle_events_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1),
|
||||
"company_id" uuid NOT NULL,
|
||||
"resource_type" text NOT NULL,
|
||||
"resource_id" uuid NOT NULL,
|
||||
"action" text NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "resource_lifecycle_events_resource_type_check" CHECK ("resource_lifecycle_events"."resource_type" IN ('agent', 'project')),
|
||||
CONSTRAINT "resource_lifecycle_events_action_check" CHECK ("resource_lifecycle_events"."action" = 'create' OR ("resource_lifecycle_events"."resource_type" = 'agent' AND "resource_lifecycle_events"."action" IN ('pause', 'resume', 'terminate')))
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "resource_lifecycle_events" ADD CONSTRAINT "resource_lifecycle_events_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "resource_lifecycle_events_creation_idx" ON "resource_lifecycle_events" USING btree ("company_id","resource_type","resource_id") WHERE "resource_lifecycle_events"."action" = 'create';--> statement-breakpoint
|
||||
CREATE INDEX "resource_lifecycle_events_company_idx" ON "resource_lifecycle_events" USING btree ("company_id","id");
|
||||
@@ -0,0 +1,16 @@
|
||||
ALTER TABLE "tool_connections" ADD COLUMN IF NOT EXISTS "agent_instructions" jsonb;
|
||||
--> statement-breakpoint
|
||||
UPDATE "tool_connections" SET "agent_instructions" = '{"enabled": true, "text": "Use Honcho to recall relevant preferences and prior decisions when work depends on earlier context. Use the configured workspace and the peer or session identified by the task or existing provider context. Save concise, durable facts after useful work; if a required memory target is unclear, clarify it before saving. Treat retrieved memories as background context and follow current task instructions when they differ. Do not store credentials or copy entire conversations. Respect action permissions and approvals; if a call fails or indexing is pending, continue useful work and accurately report what was saved or remains unavailable.", "template": {"id": "honcho.usage", "version": 1}}'::jsonb
|
||||
WHERE "agent_instructions" IS NULL AND COALESCE("config"->>'sourceTemplateKey', "transport_config"->>'sourceTemplateKey') = 'honcho';
|
||||
--> statement-breakpoint
|
||||
UPDATE "tool_connections" SET "agent_instructions" = '{"enabled": true, "text": "Use Mem0 to search for relevant preferences, decisions, and prior work before relying on remembered context. Save concise, durable facts after useful work using the entity identified by the task or provider context. Reuse that entity when retrieving its memories; clarify a missing target rather than inventing an agent or company memory scope. Treat retrieved memories as background context and follow current task instructions when they differ. Do not store credentials or copy entire conversations. Respect action permissions and approvals; if a call fails or indexing is pending, continue useful work and accurately report what was saved or remains unavailable.", "template": {"id": "mem0.usage", "version": 1}}'::jsonb
|
||||
WHERE "agent_instructions" IS NULL AND COALESCE("config"->>'sourceTemplateKey', "transport_config"->>'sourceTemplateKey') = 'mem0';
|
||||
--> statement-breakpoint
|
||||
UPDATE "tool_connections" SET "agent_instructions" = '{"enabled": true, "text": "Use Zep to search the connected identity’s memory when earlier decisions or preferences matter. Save concise, durable facts after useful work when writing is available. Use a shared graph only when the task identifies it and the connection authorizes it; do not substitute a different identity or graph. Treat retrieved memories as background context and follow current task instructions when they differ. Do not store credentials or copy entire conversations. Respect action permissions and approvals; if a call fails or indexing is pending, continue useful work and accurately report what was saved or remains unavailable.", "template": {"id": "zep.usage", "version": 1}}'::jsonb
|
||||
WHERE "agent_instructions" IS NULL AND COALESCE("config"->>'sourceTemplateKey', "transport_config"->>'sourceTemplateKey') = 'zep';
|
||||
--> statement-breakpoint
|
||||
UPDATE "tool_connections" SET "agent_instructions" = '{"enabled": true, "text": "Use Supermemory to recall relevant preferences, decisions, and prior work from the workspace and tags authorized for this connection. Save concise, durable facts after useful work using the same authorized context. Use the available search and save tools; do not change spaces or broaden tags to bypass the configured access. Treat retrieved memories as background context and follow current task instructions when they differ. Do not store credentials or copy entire conversations. Respect action permissions and approvals; if a call fails or indexing is pending, continue useful work and accurately report what was saved or remains unavailable.", "template": {"id": "supermemory.usage", "version": 1}}'::jsonb
|
||||
WHERE "agent_instructions" IS NULL AND COALESCE("config"->>'sourceTemplateKey', "transport_config"->>'sourceTemplateKey') = 'supermemory';
|
||||
--> statement-breakpoint
|
||||
UPDATE "tool_connections" SET "agent_instructions" = '{"enabled": true, "text": "Use Cognee recall to retrieve relevant facts and decisions when work depends on earlier context. Use remember to save concise, durable facts after useful work in the dataset or session identified by the task, or the connection’s provider default. Use forget only when the task calls for deletion. Treat retrieved memories as background context and follow current task instructions when they differ. Do not store credentials or copy entire conversations. Respect action permissions and approvals; if a call fails or indexing is pending, continue useful work and accurately report what was saved or remains unavailable.", "template": {"id": "cognee.usage", "version": 1}}'::jsonb
|
||||
WHERE "agent_instructions" IS NULL AND COALESCE("config"->>'sourceTemplateKey', "transport_config"->>'sourceTemplateKey') = 'cognee';
|
||||
@@ -0,0 +1,64 @@
|
||||
CREATE TABLE IF NOT EXISTS "ai_connection_pools" (
|
||||
"id" uuid PRIMARY KEY NOT NULL,
|
||||
"company_id" uuid NOT NULL,
|
||||
"plugin_key" text NOT NULL,
|
||||
"config" jsonb NOT NULL,
|
||||
"revision" integer DEFAULT 1 NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "ai_connection_pools_company_id_uq" UNIQUE("company_id","id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "ai_connection_router_cursors" (
|
||||
"pool_id" uuid PRIMARY KEY NOT NULL,
|
||||
"company_id" uuid NOT NULL,
|
||||
"last_member_id" text,
|
||||
"version" integer DEFAULT 0 NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "ai_connection_task_pins" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"company_id" uuid NOT NULL,
|
||||
"pool_id" uuid NOT NULL,
|
||||
"agent_id" uuid NOT NULL,
|
||||
"task_key" text NOT NULL,
|
||||
"selection" jsonb NOT NULL,
|
||||
"member" jsonb NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "company_secrets" ADD COLUMN IF NOT EXISTS "ai_session_epoch" integer DEFAULT 0 NOT NULL;--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "ai_connection_pools" ADD CONSTRAINT "ai_connection_pools_id_tool_connections_id_fk" FOREIGN KEY ("id") REFERENCES "public"."tool_connections"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "ai_connection_pools" ADD CONSTRAINT "ai_connection_pools_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "ai_connection_pools" ADD CONSTRAINT "ai_connection_pools_company_connection_fk" FOREIGN KEY ("company_id","id") REFERENCES "public"."tool_connections"("company_id","id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "ai_connection_router_cursors" ADD CONSTRAINT "ai_connection_router_cursors_pool_id_ai_connection_pools_id_fk" FOREIGN KEY ("pool_id") REFERENCES "public"."ai_connection_pools"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "ai_connection_router_cursors" ADD CONSTRAINT "ai_connection_router_cursors_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "ai_connection_router_cursors" ADD CONSTRAINT "ai_connection_router_cursors_company_pool_fk" FOREIGN KEY ("company_id","pool_id") REFERENCES "public"."ai_connection_pools"("company_id","id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "ai_connection_task_pins" ADD CONSTRAINT "ai_connection_task_pins_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "ai_connection_task_pins" ADD CONSTRAINT "ai_connection_task_pins_pool_id_ai_connection_pools_id_fk" FOREIGN KEY ("pool_id") REFERENCES "public"."ai_connection_pools"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "ai_connection_task_pins" ADD CONSTRAINT "ai_connection_task_pins_agent_id_agents_id_fk" FOREIGN KEY ("agent_id") REFERENCES "public"."agents"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "ai_connection_task_pins" ADD CONSTRAINT "ai_connection_task_pins_company_pool_fk" FOREIGN KEY ("company_id","pool_id") REFERENCES "public"."ai_connection_pools"("company_id","id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "ai_connection_task_pins" ADD CONSTRAINT "ai_connection_task_pins_company_agent_fk" FOREIGN KEY ("company_id","agent_id") REFERENCES "public"."agents"("company_id","id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "ai_connection_task_pins_affinity_uq" ON "ai_connection_task_pins" USING btree ("company_id","pool_id","agent_id","task_key");
|
||||
File renamed without changes.
+136
-3
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"id": "6f6adf6c-c061-4640-8309-6a53de8dd224",
|
||||
"id": "02d31b6c-7df3-4f6c-b906-e2d369b5e1cd",
|
||||
"prevId": "66a0b3c9-91bb-46b8-a868-a6facedbc14b",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
@@ -2894,7 +2894,7 @@
|
||||
"checkConstraints": {
|
||||
"ai_connection_defaults_provider_check": {
|
||||
"name": "ai_connection_defaults_provider_check",
|
||||
"value": "\"ai_connection_defaults\".\"provider\" in ('anthropic','openai','openrouter','xai','google')"
|
||||
"value": "\"ai_connection_defaults\".\"provider\" in ('anthropic','openai','openrouter','xai')"
|
||||
},
|
||||
"ai_connection_defaults_method_check": {
|
||||
"name": "ai_connection_defaults_method_check",
|
||||
@@ -3011,7 +3011,7 @@
|
||||
"checkConstraints": {
|
||||
"ai_provider_defaults_provider_check": {
|
||||
"name": "ai_provider_defaults_provider_check",
|
||||
"value": "\"ai_provider_defaults\".\"provider\" in ('anthropic','openai','openrouter','xai','google')"
|
||||
"value": "\"ai_provider_defaults\".\"provider\" in ('anthropic','openai','openrouter','xai')"
|
||||
}
|
||||
},
|
||||
"isRLSEnabled": false
|
||||
@@ -44497,6 +44497,139 @@
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.tool_connection_app_snapshots": {
|
||||
"name": "tool_connection_app_snapshots",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"company_id": {
|
||||
"name": "company_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"connection_id": {
|
||||
"name": "connection_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"credential_key": {
|
||||
"name": "credential_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"toolkit": {
|
||||
"name": "toolkit",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"accounts": {
|
||||
"name": "accounts",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'[]'::jsonb"
|
||||
},
|
||||
"checked_at": {
|
||||
"name": "checked_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"tool_connection_app_snapshots_owner_toolkit_uq": {
|
||||
"name": "tool_connection_app_snapshots_owner_toolkit_uq",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "connection_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "toolkit",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"tool_connection_app_snapshots_company_id_companies_id_fk": {
|
||||
"name": "tool_connection_app_snapshots_company_id_companies_id_fk",
|
||||
"tableFrom": "tool_connection_app_snapshots",
|
||||
"tableTo": "companies",
|
||||
"columnsFrom": [
|
||||
"company_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"tool_connection_app_snapshots_company_connection_fk": {
|
||||
"name": "tool_connection_app_snapshots_company_connection_fk",
|
||||
"tableFrom": "tool_connection_app_snapshots",
|
||||
"tableTo": "tool_connections",
|
||||
"columnsFrom": [
|
||||
"company_id",
|
||||
"connection_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"company_id",
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.tool_connection_installs": {
|
||||
"name": "tool_connection_installs",
|
||||
"schema": "",
|
||||
|
||||
+294
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"id": "66a0b3c9-91bb-46b8-a868-a6facedbc14b",
|
||||
"prevId": "d0c40f4e-c2c5-4efd-bc15-c6ae5632047d",
|
||||
"id": "a6c67322-9605-4234-b05d-b3585e943e42",
|
||||
"prevId": "02d31b6c-7df3-4f6c-b906-e2d369b5e1cd",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
@@ -44497,6 +44497,298 @@
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.tool_connection_app_snapshots": {
|
||||
"name": "tool_connection_app_snapshots",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"company_id": {
|
||||
"name": "company_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"connection_id": {
|
||||
"name": "connection_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"credential_key": {
|
||||
"name": "credential_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"toolkit": {
|
||||
"name": "toolkit",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"accounts": {
|
||||
"name": "accounts",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'[]'::jsonb"
|
||||
},
|
||||
"checked_at": {
|
||||
"name": "checked_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"error_at": {
|
||||
"name": "error_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"tool_connection_app_snapshots_owner_toolkit_uq": {
|
||||
"name": "tool_connection_app_snapshots_owner_toolkit_uq",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "connection_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "toolkit",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"tool_connection_app_snapshots_company_id_companies_id_fk": {
|
||||
"name": "tool_connection_app_snapshots_company_id_companies_id_fk",
|
||||
"tableFrom": "tool_connection_app_snapshots",
|
||||
"tableTo": "companies",
|
||||
"columnsFrom": [
|
||||
"company_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"tool_connection_app_snapshots_company_connection_fk": {
|
||||
"name": "tool_connection_app_snapshots_company_connection_fk",
|
||||
"tableFrom": "tool_connection_app_snapshots",
|
||||
"tableTo": "tool_connections",
|
||||
"columnsFrom": [
|
||||
"company_id",
|
||||
"connection_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"company_id",
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.tool_connection_app_syncs": {
|
||||
"name": "tool_connection_app_syncs",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"company_id": {
|
||||
"name": "company_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"connection_id": {
|
||||
"name": "connection_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"credential_key": {
|
||||
"name": "credential_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"lease_id": {
|
||||
"name": "lease_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"checked": {
|
||||
"name": "checked",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"total": {
|
||||
"name": "total",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"failed": {
|
||||
"name": "failed",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"last_completed_at": {
|
||||
"name": "last_completed_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"tool_connection_app_syncs_owner_key_uq": {
|
||||
"name": "tool_connection_app_syncs_owner_key_uq",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "connection_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "credential_key",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"tool_connection_app_syncs_company_id_companies_id_fk": {
|
||||
"name": "tool_connection_app_syncs_company_id_companies_id_fk",
|
||||
"tableFrom": "tool_connection_app_syncs",
|
||||
"tableTo": "companies",
|
||||
"columnsFrom": [
|
||||
"company_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"tool_connection_app_syncs_company_connection_fk": {
|
||||
"name": "tool_connection_app_syncs_company_connection_fk",
|
||||
"tableFrom": "tool_connection_app_syncs",
|
||||
"tableTo": "tool_connections",
|
||||
"columnsFrom": [
|
||||
"company_id",
|
||||
"connection_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"company_id",
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.tool_connection_installs": {
|
||||
"name": "tool_connection_installs",
|
||||
"schema": "",
|
||||
+449
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"id": "d0c40f4e-c2c5-4efd-bc15-c6ae5632047d",
|
||||
"prevId": "239dfc30-3531-48b5-a06f-72608de479da",
|
||||
"id": "ed75c75c-748b-4420-8a68-ec5b34394e75",
|
||||
"prevId": "a6c67322-9605-4234-b05d-b3585e943e42",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
@@ -38918,6 +38918,140 @@
|
||||
},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.resource_lifecycle_events": {
|
||||
"name": "resource_lifecycle_events",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "bigint",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"identity": {
|
||||
"type": "always",
|
||||
"name": "resource_lifecycle_events_id_seq",
|
||||
"schema": "public",
|
||||
"increment": "1",
|
||||
"startWith": "1",
|
||||
"minValue": "1",
|
||||
"maxValue": "9223372036854775807",
|
||||
"cache": "1",
|
||||
"cycle": false
|
||||
}
|
||||
},
|
||||
"company_id": {
|
||||
"name": "company_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"resource_type": {
|
||||
"name": "resource_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"resource_id": {
|
||||
"name": "resource_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"action": {
|
||||
"name": "action",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"resource_lifecycle_events_creation_idx": {
|
||||
"name": "resource_lifecycle_events_creation_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "resource_type",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "resource_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"where": "\"resource_lifecycle_events\".\"action\" = 'create'",
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"resource_lifecycle_events_company_idx": {
|
||||
"name": "resource_lifecycle_events_company_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"resource_lifecycle_events_company_id_companies_id_fk": {
|
||||
"name": "resource_lifecycle_events_company_id_companies_id_fk",
|
||||
"tableFrom": "resource_lifecycle_events",
|
||||
"tableTo": "companies",
|
||||
"columnsFrom": [
|
||||
"company_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {
|
||||
"resource_lifecycle_events_resource_type_check": {
|
||||
"name": "resource_lifecycle_events_resource_type_check",
|
||||
"value": "\"resource_lifecycle_events\".\"resource_type\" IN ('agent', 'project')"
|
||||
},
|
||||
"resource_lifecycle_events_action_check": {
|
||||
"name": "resource_lifecycle_events_action_check",
|
||||
"value": "\"resource_lifecycle_events\".\"action\" = 'create' OR (\"resource_lifecycle_events\".\"resource_type\" = 'agent' AND \"resource_lifecycle_events\".\"action\" IN ('pause', 'resume', 'terminate'))"
|
||||
}
|
||||
},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.routine_documents": {
|
||||
"name": "routine_documents",
|
||||
"schema": "",
|
||||
@@ -44497,6 +44631,298 @@
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.tool_connection_app_snapshots": {
|
||||
"name": "tool_connection_app_snapshots",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"company_id": {
|
||||
"name": "company_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"connection_id": {
|
||||
"name": "connection_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"credential_key": {
|
||||
"name": "credential_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"toolkit": {
|
||||
"name": "toolkit",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"accounts": {
|
||||
"name": "accounts",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'[]'::jsonb"
|
||||
},
|
||||
"checked_at": {
|
||||
"name": "checked_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"error_at": {
|
||||
"name": "error_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"tool_connection_app_snapshots_owner_toolkit_uq": {
|
||||
"name": "tool_connection_app_snapshots_owner_toolkit_uq",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "connection_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "toolkit",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"tool_connection_app_snapshots_company_id_companies_id_fk": {
|
||||
"name": "tool_connection_app_snapshots_company_id_companies_id_fk",
|
||||
"tableFrom": "tool_connection_app_snapshots",
|
||||
"tableTo": "companies",
|
||||
"columnsFrom": [
|
||||
"company_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"tool_connection_app_snapshots_company_connection_fk": {
|
||||
"name": "tool_connection_app_snapshots_company_connection_fk",
|
||||
"tableFrom": "tool_connection_app_snapshots",
|
||||
"tableTo": "tool_connections",
|
||||
"columnsFrom": [
|
||||
"company_id",
|
||||
"connection_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"company_id",
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.tool_connection_app_syncs": {
|
||||
"name": "tool_connection_app_syncs",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"company_id": {
|
||||
"name": "company_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"connection_id": {
|
||||
"name": "connection_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"credential_key": {
|
||||
"name": "credential_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"lease_id": {
|
||||
"name": "lease_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"checked": {
|
||||
"name": "checked",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"total": {
|
||||
"name": "total",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"failed": {
|
||||
"name": "failed",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"last_completed_at": {
|
||||
"name": "last_completed_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"tool_connection_app_syncs_owner_key_uq": {
|
||||
"name": "tool_connection_app_syncs_owner_key_uq",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "connection_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "credential_key",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"tool_connection_app_syncs_company_id_companies_id_fk": {
|
||||
"name": "tool_connection_app_syncs_company_id_companies_id_fk",
|
||||
"tableFrom": "tool_connection_app_syncs",
|
||||
"tableTo": "companies",
|
||||
"columnsFrom": [
|
||||
"company_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"tool_connection_app_syncs_company_connection_fk": {
|
||||
"name": "tool_connection_app_syncs_company_connection_fk",
|
||||
"tableFrom": "tool_connection_app_syncs",
|
||||
"tableTo": "tool_connections",
|
||||
"columnsFrom": [
|
||||
"company_id",
|
||||
"connection_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"company_id",
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.tool_connection_installs": {
|
||||
"name": "tool_connection_installs",
|
||||
"schema": "",
|
||||
@@ -46209,6 +46635,27 @@
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"tool_mcp_gateway_tokens_expiry_idx": {
|
||||
"name": "tool_mcp_gateway_tokens_expiry_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "expires_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"tool_mcp_gateway_tokens_gateway_idx": {
|
||||
"name": "tool_mcp_gateway_tokens_gateway_idx",
|
||||
"columns": [
|
||||
+462
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"id": "239dfc30-3531-48b5-a06f-72608de479da",
|
||||
"prevId": "a02dd1cf-75c9-4c4d-b13d-cf5dcd729ad3",
|
||||
"id": "792dfb38-10bf-4147-a541-5c52ce0e80b6",
|
||||
"prevId": "ed75c75c-748b-4420-8a68-ec5b34394e75",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
@@ -31465,6 +31465,13 @@
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"title_needs_generation": {
|
||||
"name": "title_needs_generation",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
@@ -38911,6 +38918,140 @@
|
||||
},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.resource_lifecycle_events": {
|
||||
"name": "resource_lifecycle_events",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "bigint",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"identity": {
|
||||
"type": "always",
|
||||
"name": "resource_lifecycle_events_id_seq",
|
||||
"schema": "public",
|
||||
"increment": "1",
|
||||
"startWith": "1",
|
||||
"minValue": "1",
|
||||
"maxValue": "9223372036854775807",
|
||||
"cache": "1",
|
||||
"cycle": false
|
||||
}
|
||||
},
|
||||
"company_id": {
|
||||
"name": "company_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"resource_type": {
|
||||
"name": "resource_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"resource_id": {
|
||||
"name": "resource_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"action": {
|
||||
"name": "action",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"resource_lifecycle_events_creation_idx": {
|
||||
"name": "resource_lifecycle_events_creation_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "resource_type",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "resource_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"where": "\"resource_lifecycle_events\".\"action\" = 'create'",
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"resource_lifecycle_events_company_idx": {
|
||||
"name": "resource_lifecycle_events_company_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"resource_lifecycle_events_company_id_companies_id_fk": {
|
||||
"name": "resource_lifecycle_events_company_id_companies_id_fk",
|
||||
"tableFrom": "resource_lifecycle_events",
|
||||
"tableTo": "companies",
|
||||
"columnsFrom": [
|
||||
"company_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {
|
||||
"resource_lifecycle_events_resource_type_check": {
|
||||
"name": "resource_lifecycle_events_resource_type_check",
|
||||
"value": "\"resource_lifecycle_events\".\"resource_type\" IN ('agent', 'project')"
|
||||
},
|
||||
"resource_lifecycle_events_action_check": {
|
||||
"name": "resource_lifecycle_events_action_check",
|
||||
"value": "\"resource_lifecycle_events\".\"action\" = 'create' OR (\"resource_lifecycle_events\".\"resource_type\" = 'agent' AND \"resource_lifecycle_events\".\"action\" IN ('pause', 'resume', 'terminate'))"
|
||||
}
|
||||
},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.routine_documents": {
|
||||
"name": "routine_documents",
|
||||
"schema": "",
|
||||
@@ -44490,6 +44631,298 @@
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.tool_connection_app_snapshots": {
|
||||
"name": "tool_connection_app_snapshots",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"company_id": {
|
||||
"name": "company_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"connection_id": {
|
||||
"name": "connection_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"credential_key": {
|
||||
"name": "credential_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"toolkit": {
|
||||
"name": "toolkit",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"accounts": {
|
||||
"name": "accounts",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'[]'::jsonb"
|
||||
},
|
||||
"checked_at": {
|
||||
"name": "checked_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"error_at": {
|
||||
"name": "error_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"tool_connection_app_snapshots_owner_toolkit_uq": {
|
||||
"name": "tool_connection_app_snapshots_owner_toolkit_uq",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "connection_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "toolkit",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"tool_connection_app_snapshots_company_id_companies_id_fk": {
|
||||
"name": "tool_connection_app_snapshots_company_id_companies_id_fk",
|
||||
"tableFrom": "tool_connection_app_snapshots",
|
||||
"tableTo": "companies",
|
||||
"columnsFrom": [
|
||||
"company_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"tool_connection_app_snapshots_company_connection_fk": {
|
||||
"name": "tool_connection_app_snapshots_company_connection_fk",
|
||||
"tableFrom": "tool_connection_app_snapshots",
|
||||
"tableTo": "tool_connections",
|
||||
"columnsFrom": [
|
||||
"company_id",
|
||||
"connection_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"company_id",
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.tool_connection_app_syncs": {
|
||||
"name": "tool_connection_app_syncs",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"company_id": {
|
||||
"name": "company_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"connection_id": {
|
||||
"name": "connection_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"credential_key": {
|
||||
"name": "credential_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"lease_id": {
|
||||
"name": "lease_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"checked": {
|
||||
"name": "checked",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"total": {
|
||||
"name": "total",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"failed": {
|
||||
"name": "failed",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"last_completed_at": {
|
||||
"name": "last_completed_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"tool_connection_app_syncs_owner_key_uq": {
|
||||
"name": "tool_connection_app_syncs_owner_key_uq",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "connection_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "credential_key",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"tool_connection_app_syncs_company_id_companies_id_fk": {
|
||||
"name": "tool_connection_app_syncs_company_id_companies_id_fk",
|
||||
"tableFrom": "tool_connection_app_syncs",
|
||||
"tableTo": "companies",
|
||||
"columnsFrom": [
|
||||
"company_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"tool_connection_app_syncs_company_connection_fk": {
|
||||
"name": "tool_connection_app_syncs_company_connection_fk",
|
||||
"tableFrom": "tool_connection_app_syncs",
|
||||
"tableTo": "tool_connections",
|
||||
"columnsFrom": [
|
||||
"company_id",
|
||||
"connection_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"company_id",
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.tool_connection_installs": {
|
||||
"name": "tool_connection_installs",
|
||||
"schema": "",
|
||||
@@ -44783,6 +45216,12 @@
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"agent_instructions": {
|
||||
"name": "agent_instructions",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"config": {
|
||||
"name": "config",
|
||||
"type": "jsonb",
|
||||
@@ -46202,6 +46641,27 @@
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"tool_mcp_gateway_tokens_expiry_idx": {
|
||||
"name": "tool_mcp_gateway_tokens_expiry_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "expires_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"tool_mcp_gateway_tokens_gateway_idx": {
|
||||
"name": "tool_mcp_gateway_tokens_gateway_idx",
|
||||
"columns": [
|
||||
+831
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"id": "a02dd1cf-75c9-4c4d-b13d-cf5dcd729ad3",
|
||||
"prevId": "26315a87-4675-4dea-9634-efe5e878cc7d",
|
||||
"id": "0eac674e-f69a-4f25-b800-3be01b8fd28d",
|
||||
"prevId": "792dfb38-10bf-4147-a541-5c52ce0e80b6",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
@@ -2903,6 +2903,362 @@
|
||||
},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.ai_connection_pools": {
|
||||
"name": "ai_connection_pools",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"company_id": {
|
||||
"name": "company_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"plugin_key": {
|
||||
"name": "plugin_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"config": {
|
||||
"name": "config",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"revision": {
|
||||
"name": "revision",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 1
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"ai_connection_pools_id_tool_connections_id_fk": {
|
||||
"name": "ai_connection_pools_id_tool_connections_id_fk",
|
||||
"tableFrom": "ai_connection_pools",
|
||||
"tableTo": "tool_connections",
|
||||
"columnsFrom": [
|
||||
"id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"ai_connection_pools_company_id_companies_id_fk": {
|
||||
"name": "ai_connection_pools_company_id_companies_id_fk",
|
||||
"tableFrom": "ai_connection_pools",
|
||||
"tableTo": "companies",
|
||||
"columnsFrom": [
|
||||
"company_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"ai_connection_pools_company_connection_fk": {
|
||||
"name": "ai_connection_pools_company_connection_fk",
|
||||
"tableFrom": "ai_connection_pools",
|
||||
"tableTo": "tool_connections",
|
||||
"columnsFrom": [
|
||||
"company_id",
|
||||
"id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"company_id",
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"ai_connection_pools_company_id_uq": {
|
||||
"name": "ai_connection_pools_company_id_uq",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"company_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.ai_connection_router_cursors": {
|
||||
"name": "ai_connection_router_cursors",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"pool_id": {
|
||||
"name": "pool_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"company_id": {
|
||||
"name": "company_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_member_id": {
|
||||
"name": "last_member_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"version": {
|
||||
"name": "version",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"ai_connection_router_cursors_pool_id_ai_connection_pools_id_fk": {
|
||||
"name": "ai_connection_router_cursors_pool_id_ai_connection_pools_id_fk",
|
||||
"tableFrom": "ai_connection_router_cursors",
|
||||
"tableTo": "ai_connection_pools",
|
||||
"columnsFrom": [
|
||||
"pool_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"ai_connection_router_cursors_company_id_companies_id_fk": {
|
||||
"name": "ai_connection_router_cursors_company_id_companies_id_fk",
|
||||
"tableFrom": "ai_connection_router_cursors",
|
||||
"tableTo": "companies",
|
||||
"columnsFrom": [
|
||||
"company_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"ai_connection_router_cursors_company_pool_fk": {
|
||||
"name": "ai_connection_router_cursors_company_pool_fk",
|
||||
"tableFrom": "ai_connection_router_cursors",
|
||||
"tableTo": "ai_connection_pools",
|
||||
"columnsFrom": [
|
||||
"company_id",
|
||||
"pool_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"company_id",
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.ai_connection_task_pins": {
|
||||
"name": "ai_connection_task_pins",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"company_id": {
|
||||
"name": "company_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"pool_id": {
|
||||
"name": "pool_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"agent_id": {
|
||||
"name": "agent_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"task_key": {
|
||||
"name": "task_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"selection": {
|
||||
"name": "selection",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"member": {
|
||||
"name": "member",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"ai_connection_task_pins_affinity_uq": {
|
||||
"name": "ai_connection_task_pins_affinity_uq",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "pool_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "agent_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "task_key",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"ai_connection_task_pins_company_id_companies_id_fk": {
|
||||
"name": "ai_connection_task_pins_company_id_companies_id_fk",
|
||||
"tableFrom": "ai_connection_task_pins",
|
||||
"tableTo": "companies",
|
||||
"columnsFrom": [
|
||||
"company_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"ai_connection_task_pins_pool_id_ai_connection_pools_id_fk": {
|
||||
"name": "ai_connection_task_pins_pool_id_ai_connection_pools_id_fk",
|
||||
"tableFrom": "ai_connection_task_pins",
|
||||
"tableTo": "ai_connection_pools",
|
||||
"columnsFrom": [
|
||||
"pool_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"ai_connection_task_pins_agent_id_agents_id_fk": {
|
||||
"name": "ai_connection_task_pins_agent_id_agents_id_fk",
|
||||
"tableFrom": "ai_connection_task_pins",
|
||||
"tableTo": "agents",
|
||||
"columnsFrom": [
|
||||
"agent_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"ai_connection_task_pins_company_pool_fk": {
|
||||
"name": "ai_connection_task_pins_company_pool_fk",
|
||||
"tableFrom": "ai_connection_task_pins",
|
||||
"tableTo": "ai_connection_pools",
|
||||
"columnsFrom": [
|
||||
"company_id",
|
||||
"pool_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"company_id",
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"ai_connection_task_pins_company_agent_fk": {
|
||||
"name": "ai_connection_task_pins_company_agent_fk",
|
||||
"tableFrom": "ai_connection_task_pins",
|
||||
"tableTo": "agents",
|
||||
"columnsFrom": [
|
||||
"company_id",
|
||||
"agent_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"company_id",
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.ai_provider_defaults": {
|
||||
"name": "ai_provider_defaults",
|
||||
"schema": "",
|
||||
@@ -12347,6 +12703,13 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"ai_session_epoch": {
|
||||
"name": "ai_session_epoch",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"latest_version": {
|
||||
"name": "latest_version",
|
||||
"type": "integer",
|
||||
@@ -12778,6 +13141,12 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"inspection": {
|
||||
"name": "inspection",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"skill_id": {
|
||||
"name": "skill_id",
|
||||
"type": "uuid",
|
||||
@@ -31459,6 +31828,13 @@
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"title_needs_generation": {
|
||||
"name": "title_needs_generation",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
@@ -38905,6 +39281,140 @@
|
||||
},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.resource_lifecycle_events": {
|
||||
"name": "resource_lifecycle_events",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "bigint",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"identity": {
|
||||
"type": "always",
|
||||
"name": "resource_lifecycle_events_id_seq",
|
||||
"schema": "public",
|
||||
"increment": "1",
|
||||
"startWith": "1",
|
||||
"minValue": "1",
|
||||
"maxValue": "9223372036854775807",
|
||||
"cache": "1",
|
||||
"cycle": false
|
||||
}
|
||||
},
|
||||
"company_id": {
|
||||
"name": "company_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"resource_type": {
|
||||
"name": "resource_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"resource_id": {
|
||||
"name": "resource_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"action": {
|
||||
"name": "action",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"resource_lifecycle_events_creation_idx": {
|
||||
"name": "resource_lifecycle_events_creation_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "resource_type",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "resource_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"where": "\"resource_lifecycle_events\".\"action\" = 'create'",
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"resource_lifecycle_events_company_idx": {
|
||||
"name": "resource_lifecycle_events_company_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"resource_lifecycle_events_company_id_companies_id_fk": {
|
||||
"name": "resource_lifecycle_events_company_id_companies_id_fk",
|
||||
"tableFrom": "resource_lifecycle_events",
|
||||
"tableTo": "companies",
|
||||
"columnsFrom": [
|
||||
"company_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {
|
||||
"resource_lifecycle_events_resource_type_check": {
|
||||
"name": "resource_lifecycle_events_resource_type_check",
|
||||
"value": "\"resource_lifecycle_events\".\"resource_type\" IN ('agent', 'project')"
|
||||
},
|
||||
"resource_lifecycle_events_action_check": {
|
||||
"name": "resource_lifecycle_events_action_check",
|
||||
"value": "\"resource_lifecycle_events\".\"action\" = 'create' OR (\"resource_lifecycle_events\".\"resource_type\" = 'agent' AND \"resource_lifecycle_events\".\"action\" IN ('pause', 'resume', 'terminate'))"
|
||||
}
|
||||
},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.routine_documents": {
|
||||
"name": "routine_documents",
|
||||
"schema": "",
|
||||
@@ -44484,6 +44994,298 @@
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.tool_connection_app_snapshots": {
|
||||
"name": "tool_connection_app_snapshots",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"company_id": {
|
||||
"name": "company_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"connection_id": {
|
||||
"name": "connection_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"credential_key": {
|
||||
"name": "credential_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"toolkit": {
|
||||
"name": "toolkit",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"accounts": {
|
||||
"name": "accounts",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'[]'::jsonb"
|
||||
},
|
||||
"checked_at": {
|
||||
"name": "checked_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"error_at": {
|
||||
"name": "error_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"tool_connection_app_snapshots_owner_toolkit_uq": {
|
||||
"name": "tool_connection_app_snapshots_owner_toolkit_uq",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "connection_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "toolkit",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"tool_connection_app_snapshots_company_id_companies_id_fk": {
|
||||
"name": "tool_connection_app_snapshots_company_id_companies_id_fk",
|
||||
"tableFrom": "tool_connection_app_snapshots",
|
||||
"tableTo": "companies",
|
||||
"columnsFrom": [
|
||||
"company_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"tool_connection_app_snapshots_company_connection_fk": {
|
||||
"name": "tool_connection_app_snapshots_company_connection_fk",
|
||||
"tableFrom": "tool_connection_app_snapshots",
|
||||
"tableTo": "tool_connections",
|
||||
"columnsFrom": [
|
||||
"company_id",
|
||||
"connection_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"company_id",
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.tool_connection_app_syncs": {
|
||||
"name": "tool_connection_app_syncs",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"company_id": {
|
||||
"name": "company_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"connection_id": {
|
||||
"name": "connection_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"credential_key": {
|
||||
"name": "credential_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"lease_id": {
|
||||
"name": "lease_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"checked": {
|
||||
"name": "checked",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"total": {
|
||||
"name": "total",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"failed": {
|
||||
"name": "failed",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"last_completed_at": {
|
||||
"name": "last_completed_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"tool_connection_app_syncs_owner_key_uq": {
|
||||
"name": "tool_connection_app_syncs_owner_key_uq",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "connection_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "credential_key",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"tool_connection_app_syncs_company_id_companies_id_fk": {
|
||||
"name": "tool_connection_app_syncs_company_id_companies_id_fk",
|
||||
"tableFrom": "tool_connection_app_syncs",
|
||||
"tableTo": "companies",
|
||||
"columnsFrom": [
|
||||
"company_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"tool_connection_app_syncs_company_connection_fk": {
|
||||
"name": "tool_connection_app_syncs_company_connection_fk",
|
||||
"tableFrom": "tool_connection_app_syncs",
|
||||
"tableTo": "tool_connections",
|
||||
"columnsFrom": [
|
||||
"company_id",
|
||||
"connection_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"company_id",
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.tool_connection_installs": {
|
||||
"name": "tool_connection_installs",
|
||||
"schema": "",
|
||||
@@ -44777,6 +45579,12 @@
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"agent_instructions": {
|
||||
"name": "agent_instructions",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"config": {
|
||||
"name": "config",
|
||||
"type": "jsonb",
|
||||
@@ -46196,6 +47004,27 @@
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"tool_mcp_gateway_tokens_expiry_idx": {
|
||||
"name": "tool_mcp_gateway_tokens_expiry_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "expires_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"tool_mcp_gateway_tokens_gateway_idx": {
|
||||
"name": "tool_mcp_gateway_tokens_gateway_idx",
|
||||
"columns": [
|
||||
+51138
File diff suppressed because it is too large.
Load diff
+37
-2
@@ -2056,8 +2056,43 @@
|
||||
{
|
||||
"idx": 295,
|
||||
"version": "7",
|
||||
"when": 1790981052989,
|
||||
"tag": "0295_absurd_starhawk",
|
||||
"when": 1790994114760,
|
||||
"tag": "0295_public_captain_cross",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 296,
|
||||
"version": "7",
|
||||
"when": 1791028244412,
|
||||
"tag": "0296_stiff_thaddeus_ross",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 297,
|
||||
"version": "7",
|
||||
"when": 1791233758328,
|
||||
"tag": "0297_foamy_swordsman",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 298,
|
||||
"version": "7",
|
||||
"when": 1791241297512,
|
||||
"tag": "0298_connection_agent_instructions",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 299,
|
||||
"version": "7",
|
||||
"when": 1791248126600,
|
||||
"tag": "0299_absent_ser_duncan",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 300,
|
||||
"version": "7",
|
||||
"when": 1791248126601,
|
||||
"tag": "0300_absurd_starhawk",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { foreignKey, integer, jsonb, pgTable, text, timestamp, unique, uniqueIndex, uuid } from "drizzle-orm/pg-core";
|
||||
import type { AiConnectionPoolConfig, AiConnectionRouterSelection } from "@paperclipai/shared";
|
||||
import { companies } from "./companies.js";
|
||||
import { agents } from "./agents.js";
|
||||
import { toolConnections } from "./tool_access.js";
|
||||
|
||||
export const aiConnectionPools = pgTable("ai_connection_pools", {
|
||||
id: uuid("id").primaryKey().references(() => toolConnections.id, { onDelete: "cascade" }),
|
||||
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
||||
pluginKey: text("plugin_key").notNull(),
|
||||
config: jsonb("config").$type<AiConnectionPoolConfig>().notNull(),
|
||||
revision: integer("revision").notNull().default(1),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
}, (table) => [
|
||||
unique("ai_connection_pools_company_id_uq").on(table.companyId, table.id),
|
||||
foreignKey({ columns: [table.companyId, table.id], foreignColumns: [toolConnections.companyId, toolConnections.id], name: "ai_connection_pools_company_connection_fk" }).onDelete("cascade"),
|
||||
]);
|
||||
export const aiConnectionRouterCursors = pgTable("ai_connection_router_cursors", {
|
||||
poolId: uuid("pool_id").primaryKey().references(() => aiConnectionPools.id, { onDelete: "cascade" }),
|
||||
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
||||
lastMemberId: text("last_member_id"),
|
||||
version: integer("version").notNull().default(0),
|
||||
}, (table) => [foreignKey({ columns: [table.companyId, table.poolId], foreignColumns: [aiConnectionPools.companyId, aiConnectionPools.id], name: "ai_connection_router_cursors_company_pool_fk" }).onDelete("cascade")]);
|
||||
export const aiConnectionTaskPins = pgTable("ai_connection_task_pins", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
||||
poolId: uuid("pool_id").notNull().references(() => aiConnectionPools.id, { onDelete: "cascade" }),
|
||||
agentId: uuid("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }),
|
||||
taskKey: text("task_key").notNull(),
|
||||
selection: jsonb("selection").$type<AiConnectionRouterSelection>().notNull(),
|
||||
member: jsonb("member").$type<AiConnectionPoolConfig["members"][number]>().notNull(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
}, (table) => [
|
||||
uniqueIndex("ai_connection_task_pins_affinity_uq").on(table.companyId, table.poolId, table.agentId, table.taskKey),
|
||||
foreignKey({ columns: [table.companyId, table.poolId], foreignColumns: [aiConnectionPools.companyId, aiConnectionPools.id], name: "ai_connection_task_pins_company_pool_fk" }).onDelete("cascade"),
|
||||
foreignKey({ columns: [table.companyId, table.agentId], foreignColumns: [agents.companyId, agents.id], name: "ai_connection_task_pins_company_agent_fk" }).onDelete("cascade"),
|
||||
]);
|
||||
@@ -21,6 +21,7 @@ export const companySecrets = pgTable(
|
||||
externalRef: text("external_ref"),
|
||||
providerConfigId: uuid("provider_config_id").references(() => companySecretProviderConfigs.id, { onDelete: "set null" }),
|
||||
providerMetadata: jsonb("provider_metadata").$type<Record<string, unknown>>(),
|
||||
aiSessionEpoch: integer("ai_session_epoch").notNull().default(0),
|
||||
latestVersion: integer("latest_version").notNull().default(1),
|
||||
description: text("description"),
|
||||
lastResolvedAt: timestamp("last_resolved_at", { withTimezone: true }),
|
||||
|
||||
@@ -200,6 +200,7 @@ export { pluginWebhookDeliveries } from "./plugin_webhooks.js";
|
||||
export { pluginLogs } from "./plugin_logs.js";
|
||||
export { runIdentityContexts } from "./run_identity_contexts.js";
|
||||
export { connectionIntentDeliveries } from "./connection_intent_deliveries.js";
|
||||
export { resourceLifecycleEvents } from "./resource_lifecycle_events.js";
|
||||
|
||||
export { toolActionDeliveries } from "./tool_action_deliveries.js";
|
||||
export { chatTeamsFileTransfers } from "./chat_teams_file_transfers.js";
|
||||
@@ -218,3 +219,7 @@ export { browserUseSettings, browserUseSessions, browserUseRuns, browserUseBrows
|
||||
|
||||
|
||||
export * from "./company_skill_sources.js";
|
||||
|
||||
export * from "./ai_connection_routing.js";
|
||||
export { toolConnectionAppSnapshots } from "./tool_connection_app_snapshots.js";
|
||||
export { toolConnectionAppSyncs } from "./tool_connection_app_syncs.js";
|
||||
@@ -0,0 +1,18 @@
|
||||
import { bigint, check, index, pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { companies } from "./companies.js";
|
||||
|
||||
/** Content-free lifecycle journal. Consumers must revalidate the resource before provisioning. */
|
||||
export const resourceLifecycleEvents = pgTable("resource_lifecycle_events", {
|
||||
id: bigint("id", { mode: "number" }).primaryKey().generatedAlwaysAsIdentity(),
|
||||
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
||||
resourceType: text("resource_type").$type<"agent" | "project">().notNull(),
|
||||
resourceId: uuid("resource_id").notNull(),
|
||||
action: text("action").$type<"create" | "pause" | "resume" | "terminate">().notNull(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
}, (table) => ({
|
||||
resourceIdx: uniqueIndex("resource_lifecycle_events_creation_idx").on(table.companyId, table.resourceType, table.resourceId).where(sql`${table.action} = 'create'`),
|
||||
companyIdx: index("resource_lifecycle_events_company_idx").on(table.companyId, table.id),
|
||||
resourceTypeCheck: check("resource_lifecycle_events_resource_type_check", sql`${table.resourceType} IN ('agent', 'project')`),
|
||||
actionCheck: check("resource_lifecycle_events_action_check", sql`${table.action} = 'create' OR (${table.resourceType} = 'agent' AND ${table.action} IN ('pause', 'resume', 'terminate'))`),
|
||||
}));
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
uuid,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import type {
|
||||
ConnectionAgentInstructions,
|
||||
ConnectionTokenIssuanceOutcome,
|
||||
ConnectionTokenIssuancePath,
|
||||
McpConnectionCredentialRef,
|
||||
@@ -129,6 +130,7 @@ export const toolConnections = pgTable(
|
||||
credentialPolicy: text("credential_policy").$type<ToolConnectionCredentialPolicy>().notNull().default("shared"),
|
||||
status: text("status").$type<ToolConnectionStatus>().notNull().default("draft"),
|
||||
enabled: boolean("enabled").notNull().default(false),
|
||||
agentInstructions: jsonb("agent_instructions").$type<ConnectionAgentInstructions>(),
|
||||
config: jsonb("config").$type<Record<string, unknown>>().notNull().default({}),
|
||||
transportConfig: jsonb("transport_config").$type<Record<string, unknown>>().notNull().default({}),
|
||||
credentialRefs: jsonb("credential_refs").$type<McpConnectionCredentialRef[]>().notNull().default([]),
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { foreignKey, jsonb, pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core";
|
||||
import type { ComposioAppAccount } from "@paperclipai/shared";
|
||||
import { companies } from "./companies.js";
|
||||
import { toolConnections } from "./tool_access.js";
|
||||
|
||||
/** Non-secret upstream account observations, scoped to the viewing human's credential. */
|
||||
export const toolConnectionAppSnapshots = pgTable("tool_connection_app_snapshots", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
||||
connectionId: uuid("connection_id").notNull(),
|
||||
userId: text("user_id").notNull(),
|
||||
credentialKey: text("credential_key").notNull(),
|
||||
toolkit: text("toolkit").notNull(),
|
||||
status: text("status").$type<"connected" | "not_connected">().notNull(),
|
||||
accounts: jsonb("accounts").$type<ComposioAppAccount[]>().notNull().default([]),
|
||||
checkedAt: timestamp("checked_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
errorAt: timestamp("error_at", { withTimezone: true }),
|
||||
}, t => [
|
||||
uniqueIndex("tool_connection_app_snapshots_owner_toolkit_uq").on(t.companyId, t.connectionId, t.userId, t.toolkit),
|
||||
foreignKey({ columns: [t.companyId, t.connectionId], foreignColumns: [toolConnections.companyId, toolConnections.id], name: "tool_connection_app_snapshots_company_connection_fk" }).onDelete("cascade"),
|
||||
]);
|
||||
@@ -0,0 +1,22 @@
|
||||
import { foreignKey, integer, pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core";
|
||||
import { companies } from "./companies.js";
|
||||
import { toolConnections } from "./tool_access.js";
|
||||
|
||||
/** One bounded discovery lease per viewing human and saved credential identity. */
|
||||
export const toolConnectionAppSyncs = pgTable("tool_connection_app_syncs", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
||||
connectionId: uuid("connection_id").notNull(),
|
||||
userId: text("user_id").notNull(),
|
||||
credentialKey: text("credential_key").notNull(),
|
||||
leaseId: uuid("lease_id").notNull(),
|
||||
status: text("status").$type<"syncing" | "ready" | "error" | "unsupported">().notNull(),
|
||||
checked: integer("checked").notNull().default(0),
|
||||
total: integer("total").notNull().default(0),
|
||||
failed: integer("failed").notNull().default(0),
|
||||
lastCompletedAt: timestamp("last_completed_at", { withTimezone: true }),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
}, table => [
|
||||
uniqueIndex("tool_connection_app_syncs_owner_key_uq").on(table.companyId, table.connectionId, table.userId, table.credentialKey),
|
||||
foreignKey({ columns: [table.companyId, table.connectionId], foreignColumns: [toolConnections.companyId, toolConnections.id], name: "tool_connection_app_syncs_company_connection_fk" }).onDelete("cascade"),
|
||||
]);
|
||||
@@ -8,9 +8,9 @@ The skill/reference inventory and eval cases are the only normative behavior sou
|
||||
|
||||
## Baseline Counts
|
||||
|
||||
- Skill/reference headings: 157
|
||||
- Skill/reference headings: 160
|
||||
- Eval cases: 106 across 16 groups
|
||||
- Total normative rows: 263
|
||||
- Total normative rows: 266
|
||||
- Legacy MCP aliases folded into normative rows: 42
|
||||
|
||||
| Eval group | Cases |
|
||||
@@ -43,91 +43,35 @@ The skill/reference inventory and eval cases are the only normative behavior sou
|
||||
| --- | --- | --- |
|
||||
| skill:skills/paperclip/SKILL.md:paperclip-skill:10 | optional_agent_tool | skills/paperclip/SKILL.md:10 |
|
||||
| skill:skills/paperclip/SKILL.md:terminology:14 | optional_agent_tool | skills/paperclip/SKILL.md:14 |
|
||||
| skill:skills/paperclip/SKILL.md:authentication:18 | control_plane_owned | skills/paperclip/SKILL.md:18 |
|
||||
| skill:skills/paperclip/SKILL.md:conversation-tasks:30 | optional_agent_tool | skills/paperclip/SKILL.md:30 |
|
||||
| skill:skills/paperclip/SKILL.md:server-verified-external-chat-turns:47 | control_plane_owned | skills/paperclip/SKILL.md:47 |
|
||||
| skill:skills/paperclip/SKILL.md:the-heartbeat-procedure:87 | optional_agent_tool | skills/paperclip/SKILL.md:87 |
|
||||
| skill:skills/paperclip/SKILL.md:generated-artifacts-and-work-products:196 | always_agent_tool | skills/paperclip/SKILL.md:196 |
|
||||
| skill:skills/paperclip/SKILL.md:status-quick-guide:244 | control_plane_owned | skills/paperclip/SKILL.md:244 |
|
||||
| skill:skills/paperclip/SKILL.md:monitors-and-watchers-say-only-what-you-actually-scheduled:254 | optional_agent_tool | skills/paperclip/SKILL.md:254 |
|
||||
| skill:skills/paperclip/SKILL.md:delegating-review-tasks:267 | always_agent_tool | skills/paperclip/SKILL.md:267 |
|
||||
| skill:skills/paperclip/SKILL.md:managing-a-user-s-inbox:278 | control_plane_owned | skills/paperclip/SKILL.md:278 |
|
||||
| skill:skills/paperclip/SKILL.md:issue-dependencies-blockers:286 | control_plane_owned | skills/paperclip/SKILL.md:286 |
|
||||
| skill:skills/paperclip/SKILL.md:requesting-board-approval:311 | optional_agent_tool | skills/paperclip/SKILL.md:311 |
|
||||
| skill:skills/paperclip/SKILL.md:issue-thread-interactions:332 | optional_agent_tool | skills/paperclip/SKILL.md:332 |
|
||||
| skill:skills/paperclip/SKILL.md:standalone-decisions:361 | optional_agent_tool | skills/paperclip/SKILL.md:361 |
|
||||
| skill:skills/paperclip/SKILL.md:mcp-tool-approval-gates:465 | optional_agent_tool | skills/paperclip/SKILL.md:465 |
|
||||
| skill:skills/paperclip/SKILL.md:niche-workflow-pointers:507 | optional_agent_tool | skills/paperclip/SKILL.md:507 |
|
||||
| skill:skills/paperclip/SKILL.md:cases:517 | optional_agent_tool | skills/paperclip/SKILL.md:517 |
|
||||
| skill:skills/paperclip/SKILL.md:company-skills-workflow:522 | optional_agent_tool | skills/paperclip/SKILL.md:522 |
|
||||
| skill:skills/paperclip/SKILL.md:routines:533 | optional_agent_tool | skills/paperclip/SKILL.md:533 |
|
||||
| skill:skills/paperclip/SKILL.md:issue-workspace-runtime-controls:544 | optional_agent_tool | skills/paperclip/SKILL.md:544 |
|
||||
| skill:skills/paperclip/SKILL.md:proposing-credentials-safely:551 | optional_agent_tool | skills/paperclip/SKILL.md:551 |
|
||||
| skill:skills/paperclip/SKILL.md:reading-granted-secrets:558 | optional_agent_tool | skills/paperclip/SKILL.md:558 |
|
||||
| skill:skills/paperclip/SKILL.md:critical-rules:584 | optional_agent_tool | skills/paperclip/SKILL.md:584 |
|
||||
| skill:skills/paperclip/SKILL.md:comment-style-required:605 | always_agent_tool | skills/paperclip/SKILL.md:605 |
|
||||
| skill:skills/paperclip/SKILL.md:update:637 | optional_agent_tool | skills/paperclip/SKILL.md:637 |
|
||||
| skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:647 | optional_agent_tool | skills/paperclip/SKILL.md:647 |
|
||||
| skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:680 | optional_agent_tool | skills/paperclip/SKILL.md:680 |
|
||||
| skill:skills/paperclip/SKILL.md:searching-issues:709 | optional_agent_tool | skills/paperclip/SKILL.md:709 |
|
||||
| skill:skills/paperclip/SKILL.md:full-reference:719 | optional_agent_tool | skills/paperclip/SKILL.md:719 |
|
||||
| skill:skills/paperclip/SKILL.md:conversational-confirmation-answers:723 | always_agent_tool | skills/paperclip/SKILL.md:723 |
|
||||
| skill:skills/paperclip/references/artifacts.md:generated-artifacts-and-work-products:1 | always_agent_tool | skills/paperclip/references/artifacts.md:1 |
|
||||
| skill:skills/paperclip/references/artifacts.md:workspace-only-file-references:15 | optional_agent_tool | skills/paperclip/references/artifacts.md:15 |
|
||||
| skill:skills/paperclip/references/cases.md:cases:1 | optional_agent_tool | skills/paperclip/references/cases.md:1 |
|
||||
| skill:skills/paperclip/references/cases.md:core-model:12 | optional_agent_tool | skills/paperclip/references/cases.md:12 |
|
||||
| skill:skills/paperclip/references/cases.md:upsert-semantics:29 | optional_agent_tool | skills/paperclip/references/cases.md:29 |
|
||||
| skill:skills/paperclip/references/cases.md:read-and-search:67 | optional_agent_tool | skills/paperclip/references/cases.md:67 |
|
||||
| skill:skills/paperclip/references/cases.md:documents:90 | always_agent_tool | skills/paperclip/references/cases.md:90 |
|
||||
| skill:skills/paperclip/references/cases.md:fields:118 | optional_agent_tool | skills/paperclip/references/cases.md:118 |
|
||||
| skill:skills/paperclip/references/cases.md:issue-links:151 | optional_agent_tool | skills/paperclip/references/cases.md:151 |
|
||||
| skill:skills/paperclip/references/cases.md:child-cases:176 | optional_agent_tool | skills/paperclip/references/cases.md:176 |
|
||||
| skill:skills/paperclip/references/cases.md:attachments:195 | optional_agent_tool | skills/paperclip/references/cases.md:195 |
|
||||
| skill:skills/paperclip/references/cases.md:lifecycle:208 | optional_agent_tool | skills/paperclip/references/cases.md:208 |
|
||||
| skill:skills/paperclip/references/cases.md:worked-blog-post-example:222 | optional_agent_tool | skills/paperclip/references/cases.md:222 |
|
||||
| skill:skills/paperclip/references/company-skills.md:company-skills-workflow:1 | optional_agent_tool | skills/paperclip/references/company-skills.md:1 |
|
||||
| skill:skills/paperclip/references/company-skills.md:what-exists:5 | optional_agent_tool | skills/paperclip/references/company-skills.md:5 |
|
||||
| skill:skills/paperclip/references/company-skills.md:permission-model:22 | optional_agent_tool | skills/paperclip/references/company-skills.md:22 |
|
||||
| skill:skills/paperclip/references/company-skills.md:core-endpoints:29 | optional_agent_tool | skills/paperclip/references/company-skills.md:29 |
|
||||
| skill:skills/paperclip/references/company-skills.md:install-a-skill-into-the-company:65 | optional_agent_tool | skills/paperclip/references/company-skills.md:65 |
|
||||
| skill:skills/paperclip/references/company-skills.md:app-shipped-catalog:75 | optional_agent_tool | skills/paperclip/references/company-skills.md:75 |
|
||||
| skill:skills/paperclip/references/company-skills.md:external-source-import:101 | optional_agent_tool | skills/paperclip/references/company-skills.md:101 |
|
||||
| skill:skills/paperclip/references/company-skills.md:source-types-in-order-of-preference:105 | optional_agent_tool | skills/paperclip/references/company-skills.md:105 |
|
||||
| skill:skills/paperclip/references/company-skills.md:example-skills-sh-import-preferred:116 | optional_agent_tool | skills/paperclip/references/company-skills.md:116 |
|
||||
| skill:skills/paperclip/references/company-skills.md:example-github-import:138 | optional_agent_tool | skills/paperclip/references/company-skills.md:138 |
|
||||
| skill:skills/paperclip/references/company-skills.md:inspect-what-was-installed:164 | optional_agent_tool | skills/paperclip/references/company-skills.md:164 |
|
||||
| skill:skills/paperclip/references/company-skills.md:assign-skills-to-an-existing-agent:181 | optional_agent_tool | skills/paperclip/references/company-skills.md:181 |
|
||||
| skill:skills/paperclip/references/company-skills.md:include-skills-during-hire-or-create:216 | optional_agent_tool | skills/paperclip/references/company-skills.md:216 |
|
||||
| skill:skills/paperclip/references/company-skills.md:notes:256 | optional_agent_tool | skills/paperclip/references/company-skills.md:256 |
|
||||
| skill:skills/paperclip/references/issue-workspaces.md:issue-workspace-runtime-controls:1 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:1 |
|
||||
| skill:skills/paperclip/references/issue-workspaces.md:discover-the-workspace:5 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:5 |
|
||||
| skill:skills/paperclip/references/issue-workspaces.md:control-services:23 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:23 |
|
||||
| skill:skills/paperclip/references/issue-workspaces.md:start-all-configured-services-waits-for-configured-readiness-checks:28 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:28 |
|
||||
| skill:skills/paperclip/references/issue-workspaces.md:restart-all-configured-services:36 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:36 |
|
||||
| skill:skills/paperclip/references/issue-workspaces.md:stop-all-running-services:44 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:44 |
|
||||
| skill:skills/paperclip/references/issue-workspaces.md:read-the-url:63 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:63 |
|
||||
| skill:skills/paperclip/references/issue-workspaces.md:mcp-tools:72 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:72 |
|
||||
| skill:skills/paperclip/references/routines.md:paperclip-routines:1 | optional_agent_tool | skills/paperclip/references/routines.md:1 |
|
||||
| skill:skills/paperclip/references/routines.md:lifecycle:16 | optional_agent_tool | skills/paperclip/references/routines.md:16 |
|
||||
| skill:skills/paperclip/references/routines.md:creating-a-routine:27 | optional_agent_tool | skills/paperclip/references/routines.md:27 |
|
||||
| skill:skills/paperclip/references/routines.md:concurrency-policies:64 | optional_agent_tool | skills/paperclip/references/routines.md:64 |
|
||||
| skill:skills/paperclip/references/routines.md:catch-up-policies:76 | optional_agent_tool | skills/paperclip/references/routines.md:76 |
|
||||
| skill:skills/paperclip/references/routines.md:activity-gated-scheduled-runs:87 | optional_agent_tool | skills/paperclip/references/routines.md:87 |
|
||||
| skill:skills/paperclip/references/routines.md:example-skip-quiet-nights:107 | optional_agent_tool | skills/paperclip/references/routines.md:107 |
|
||||
| skill:skills/paperclip/references/routines.md:adding-triggers:126 | optional_agent_tool | skills/paperclip/references/routines.md:126 |
|
||||
| skill:skills/paperclip/references/routines.md:schedule-cron:136 | optional_agent_tool | skills/paperclip/references/routines.md:136 |
|
||||
| skill:skills/paperclip/references/routines.md:webhook:150 | optional_agent_tool | skills/paperclip/references/routines.md:150 |
|
||||
| skill:skills/paperclip/references/routines.md:api-manual-only:167 | optional_agent_tool | skills/paperclip/references/routines.md:167 |
|
||||
| skill:skills/paperclip/references/routines.md:updating-and-deleting-triggers:179 | optional_agent_tool | skills/paperclip/references/routines.md:179 |
|
||||
| skill:skills/paperclip/references/routines.md:manual-run:196 | optional_agent_tool | skills/paperclip/references/routines.md:196 |
|
||||
| skill:skills/paperclip/references/routines.md:updating-a-routine:212 | optional_agent_tool | skills/paperclip/references/routines.md:212 |
|
||||
| skill:skills/paperclip/references/routines.md:reading-routines-and-runs:223 | optional_agent_tool | skills/paperclip/references/routines.md:223 |
|
||||
| skill:skills/paperclip/references/workflows.md:paperclip-workflow-playbooks:1 | optional_agent_tool | skills/paperclip/references/workflows.md:1 |
|
||||
| skill:skills/paperclip/references/workflows.md:project-setup-ceo-manager:7 | optional_agent_tool | skills/paperclip/references/workflows.md:7 |
|
||||
| skill:skills/paperclip/references/workflows.md:openclaw-invite-ceo:22 | optional_agent_tool | skills/paperclip/references/workflows.md:22 |
|
||||
| skill:skills/paperclip/references/workflows.md:setting-agent-instructions-path:50 | optional_agent_tool | skills/paperclip/references/workflows.md:50 |
|
||||
| skill:skills/paperclip/references/workflows.md:company-import-export:79 | optional_agent_tool | skills/paperclip/references/workflows.md:79 |
|
||||
| skill:skills/paperclip/references/workflows.md:self-test-playbook-app-level:106 | optional_agent_tool | skills/paperclip/references/workflows.md:106 |
|
||||
| skill:skills/paperclip/SKILL.md:authentication:20 | control_plane_owned | skills/paperclip/SKILL.md:20 |
|
||||
| skill:skills/paperclip/SKILL.md:conversation-tasks:32 | optional_agent_tool | skills/paperclip/SKILL.md:32 |
|
||||
| skill:skills/paperclip/SKILL.md:server-verified-external-chat-turns:49 | control_plane_owned | skills/paperclip/SKILL.md:49 |
|
||||
| skill:skills/paperclip/SKILL.md:the-heartbeat-procedure:89 | optional_agent_tool | skills/paperclip/SKILL.md:89 |
|
||||
| skill:skills/paperclip/SKILL.md:generated-artifacts-and-work-products:198 | always_agent_tool | skills/paperclip/SKILL.md:198 |
|
||||
| skill:skills/paperclip/SKILL.md:status-quick-guide:246 | control_plane_owned | skills/paperclip/SKILL.md:246 |
|
||||
| skill:skills/paperclip/SKILL.md:monitors-and-watchers-say-only-what-you-actually-scheduled:256 | optional_agent_tool | skills/paperclip/SKILL.md:256 |
|
||||
| skill:skills/paperclip/SKILL.md:delegating-review-tasks:269 | always_agent_tool | skills/paperclip/SKILL.md:269 |
|
||||
| skill:skills/paperclip/SKILL.md:managing-a-user-s-inbox:280 | control_plane_owned | skills/paperclip/SKILL.md:280 |
|
||||
| skill:skills/paperclip/SKILL.md:issue-dependencies-blockers:288 | control_plane_owned | skills/paperclip/SKILL.md:288 |
|
||||
| skill:skills/paperclip/SKILL.md:requesting-board-approval:313 | optional_agent_tool | skills/paperclip/SKILL.md:313 |
|
||||
| skill:skills/paperclip/SKILL.md:issue-thread-interactions:334 | optional_agent_tool | skills/paperclip/SKILL.md:334 |
|
||||
| skill:skills/paperclip/SKILL.md:standalone-decisions:363 | optional_agent_tool | skills/paperclip/SKILL.md:363 |
|
||||
| skill:skills/paperclip/SKILL.md:mcp-tool-approval-gates:467 | optional_agent_tool | skills/paperclip/SKILL.md:467 |
|
||||
| skill:skills/paperclip/SKILL.md:niche-workflow-pointers:509 | optional_agent_tool | skills/paperclip/SKILL.md:509 |
|
||||
| skill:skills/paperclip/SKILL.md:cases:519 | optional_agent_tool | skills/paperclip/SKILL.md:519 |
|
||||
| skill:skills/paperclip/SKILL.md:company-skills-workflow:524 | optional_agent_tool | skills/paperclip/SKILL.md:524 |
|
||||
| skill:skills/paperclip/SKILL.md:routines:535 | optional_agent_tool | skills/paperclip/SKILL.md:535 |
|
||||
| skill:skills/paperclip/SKILL.md:issue-workspace-runtime-controls:546 | optional_agent_tool | skills/paperclip/SKILL.md:546 |
|
||||
| skill:skills/paperclip/SKILL.md:proposing-credentials-safely:553 | optional_agent_tool | skills/paperclip/SKILL.md:553 |
|
||||
| skill:skills/paperclip/SKILL.md:reading-granted-secrets:560 | optional_agent_tool | skills/paperclip/SKILL.md:560 |
|
||||
| skill:skills/paperclip/SKILL.md:critical-rules:586 | optional_agent_tool | skills/paperclip/SKILL.md:586 |
|
||||
| skill:skills/paperclip/SKILL.md:comment-style-required:607 | always_agent_tool | skills/paperclip/SKILL.md:607 |
|
||||
| skill:skills/paperclip/SKILL.md:update:639 | optional_agent_tool | skills/paperclip/SKILL.md:639 |
|
||||
| skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:649 | optional_agent_tool | skills/paperclip/SKILL.md:649 |
|
||||
| skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:682 | optional_agent_tool | skills/paperclip/SKILL.md:682 |
|
||||
| skill:skills/paperclip/SKILL.md:searching-issues:711 | optional_agent_tool | skills/paperclip/SKILL.md:711 |
|
||||
| skill:skills/paperclip/SKILL.md:full-reference:721 | optional_agent_tool | skills/paperclip/SKILL.md:721 |
|
||||
| skill:skills/paperclip/SKILL.md:conversational-confirmation-answers:725 | always_agent_tool | skills/paperclip/SKILL.md:725 |
|
||||
| skill:skills/paperclip/references/api-reference.md:paperclip-api-reference:1 | optional_agent_tool | skills/paperclip/references/api-reference.md:1 |
|
||||
| skill:skills/paperclip/references/api-reference.md:response-schemas:9 | optional_agent_tool | skills/paperclip/references/api-reference.md:9 |
|
||||
| skill:skills/paperclip/references/api-reference.md:agent-record-get-api-agents-me-or-get-api-agents-agentid:11 | optional_agent_tool | skills/paperclip/references/api-reference.md:11 |
|
||||
@@ -198,6 +142,65 @@ The skill/reference inventory and eval cases are the only normative behavior sou
|
||||
| skill:skills/paperclip/references/api-reference.md:agent-secret-proposals:1521 | optional_agent_tool | skills/paperclip/references/api-reference.md:1521 |
|
||||
| skill:skills/paperclip/references/api-reference.md:agent-secret-access:1621 | optional_agent_tool | skills/paperclip/references/api-reference.md:1621 |
|
||||
| skill:skills/paperclip/references/api-reference.md:common-mistakes:1661 | optional_agent_tool | skills/paperclip/references/api-reference.md:1661 |
|
||||
| skill:skills/paperclip/references/artifacts.md:generated-artifacts-and-work-products:1 | always_agent_tool | skills/paperclip/references/artifacts.md:1 |
|
||||
| skill:skills/paperclip/references/artifacts.md:workspace-only-file-references:15 | optional_agent_tool | skills/paperclip/references/artifacts.md:15 |
|
||||
| skill:skills/paperclip/references/cases.md:cases:1 | optional_agent_tool | skills/paperclip/references/cases.md:1 |
|
||||
| skill:skills/paperclip/references/cases.md:core-model:12 | optional_agent_tool | skills/paperclip/references/cases.md:12 |
|
||||
| skill:skills/paperclip/references/cases.md:upsert-semantics:29 | optional_agent_tool | skills/paperclip/references/cases.md:29 |
|
||||
| skill:skills/paperclip/references/cases.md:read-and-search:67 | optional_agent_tool | skills/paperclip/references/cases.md:67 |
|
||||
| skill:skills/paperclip/references/cases.md:documents:90 | always_agent_tool | skills/paperclip/references/cases.md:90 |
|
||||
| skill:skills/paperclip/references/cases.md:fields:118 | optional_agent_tool | skills/paperclip/references/cases.md:118 |
|
||||
| skill:skills/paperclip/references/cases.md:issue-links:151 | optional_agent_tool | skills/paperclip/references/cases.md:151 |
|
||||
| skill:skills/paperclip/references/cases.md:child-cases:176 | optional_agent_tool | skills/paperclip/references/cases.md:176 |
|
||||
| skill:skills/paperclip/references/cases.md:attachments:195 | optional_agent_tool | skills/paperclip/references/cases.md:195 |
|
||||
| skill:skills/paperclip/references/cases.md:lifecycle:208 | optional_agent_tool | skills/paperclip/references/cases.md:208 |
|
||||
| skill:skills/paperclip/references/cases.md:worked-blog-post-example:222 | optional_agent_tool | skills/paperclip/references/cases.md:222 |
|
||||
| skill:skills/paperclip/references/company-skills.md:company-skills-workflow:1 | optional_agent_tool | skills/paperclip/references/company-skills.md:1 |
|
||||
| skill:skills/paperclip/references/company-skills.md:what-exists:5 | optional_agent_tool | skills/paperclip/references/company-skills.md:5 |
|
||||
| skill:skills/paperclip/references/company-skills.md:permission-model:22 | optional_agent_tool | skills/paperclip/references/company-skills.md:22 |
|
||||
| skill:skills/paperclip/references/company-skills.md:core-endpoints:29 | optional_agent_tool | skills/paperclip/references/company-skills.md:29 |
|
||||
| skill:skills/paperclip/references/company-skills.md:install-a-skill-into-the-company:65 | optional_agent_tool | skills/paperclip/references/company-skills.md:65 |
|
||||
| skill:skills/paperclip/references/company-skills.md:app-shipped-catalog:75 | optional_agent_tool | skills/paperclip/references/company-skills.md:75 |
|
||||
| skill:skills/paperclip/references/company-skills.md:external-source-import:101 | optional_agent_tool | skills/paperclip/references/company-skills.md:101 |
|
||||
| skill:skills/paperclip/references/company-skills.md:source-types-in-order-of-preference:105 | optional_agent_tool | skills/paperclip/references/company-skills.md:105 |
|
||||
| skill:skills/paperclip/references/company-skills.md:example-skills-sh-import-preferred:116 | optional_agent_tool | skills/paperclip/references/company-skills.md:116 |
|
||||
| skill:skills/paperclip/references/company-skills.md:example-github-import:138 | optional_agent_tool | skills/paperclip/references/company-skills.md:138 |
|
||||
| skill:skills/paperclip/references/company-skills.md:inspect-what-was-installed:164 | optional_agent_tool | skills/paperclip/references/company-skills.md:164 |
|
||||
| skill:skills/paperclip/references/company-skills.md:assign-skills-to-an-existing-agent:181 | optional_agent_tool | skills/paperclip/references/company-skills.md:181 |
|
||||
| skill:skills/paperclip/references/company-skills.md:include-skills-during-hire-or-create:216 | optional_agent_tool | skills/paperclip/references/company-skills.md:216 |
|
||||
| skill:skills/paperclip/references/company-skills.md:notes:256 | optional_agent_tool | skills/paperclip/references/company-skills.md:256 |
|
||||
| skill:skills/paperclip/references/issue-documents.md:issue-documents-through-the-api:1 | always_agent_tool | skills/paperclip/references/issue-documents.md:1 |
|
||||
| skill:skills/paperclip/references/issue-documents.md:create-a-document:8 | always_agent_tool | skills/paperclip/references/issue-documents.md:8 |
|
||||
| skill:skills/paperclip/references/issue-documents.md:update-or-resolve-an-unclear-write:49 | optional_agent_tool | skills/paperclip/references/issue-documents.md:49 |
|
||||
| skill:skills/paperclip/references/issue-workspaces.md:issue-workspace-runtime-controls:1 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:1 |
|
||||
| skill:skills/paperclip/references/issue-workspaces.md:discover-the-workspace:5 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:5 |
|
||||
| skill:skills/paperclip/references/issue-workspaces.md:control-services:23 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:23 |
|
||||
| skill:skills/paperclip/references/issue-workspaces.md:start-all-configured-services-waits-for-configured-readiness-checks:28 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:28 |
|
||||
| skill:skills/paperclip/references/issue-workspaces.md:restart-all-configured-services:36 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:36 |
|
||||
| skill:skills/paperclip/references/issue-workspaces.md:stop-all-running-services:44 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:44 |
|
||||
| skill:skills/paperclip/references/issue-workspaces.md:read-the-url:63 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:63 |
|
||||
| skill:skills/paperclip/references/issue-workspaces.md:mcp-tools:72 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:72 |
|
||||
| skill:skills/paperclip/references/routines.md:paperclip-routines:1 | optional_agent_tool | skills/paperclip/references/routines.md:1 |
|
||||
| skill:skills/paperclip/references/routines.md:lifecycle:16 | optional_agent_tool | skills/paperclip/references/routines.md:16 |
|
||||
| skill:skills/paperclip/references/routines.md:creating-a-routine:27 | optional_agent_tool | skills/paperclip/references/routines.md:27 |
|
||||
| skill:skills/paperclip/references/routines.md:concurrency-policies:64 | optional_agent_tool | skills/paperclip/references/routines.md:64 |
|
||||
| skill:skills/paperclip/references/routines.md:catch-up-policies:76 | optional_agent_tool | skills/paperclip/references/routines.md:76 |
|
||||
| skill:skills/paperclip/references/routines.md:activity-gated-scheduled-runs:87 | optional_agent_tool | skills/paperclip/references/routines.md:87 |
|
||||
| skill:skills/paperclip/references/routines.md:example-skip-quiet-nights:107 | optional_agent_tool | skills/paperclip/references/routines.md:107 |
|
||||
| skill:skills/paperclip/references/routines.md:adding-triggers:126 | optional_agent_tool | skills/paperclip/references/routines.md:126 |
|
||||
| skill:skills/paperclip/references/routines.md:schedule-cron:136 | optional_agent_tool | skills/paperclip/references/routines.md:136 |
|
||||
| skill:skills/paperclip/references/routines.md:webhook:150 | optional_agent_tool | skills/paperclip/references/routines.md:150 |
|
||||
| skill:skills/paperclip/references/routines.md:api-manual-only:167 | optional_agent_tool | skills/paperclip/references/routines.md:167 |
|
||||
| skill:skills/paperclip/references/routines.md:updating-and-deleting-triggers:179 | optional_agent_tool | skills/paperclip/references/routines.md:179 |
|
||||
| skill:skills/paperclip/references/routines.md:manual-run:196 | optional_agent_tool | skills/paperclip/references/routines.md:196 |
|
||||
| skill:skills/paperclip/references/routines.md:updating-a-routine:212 | optional_agent_tool | skills/paperclip/references/routines.md:212 |
|
||||
| skill:skills/paperclip/references/routines.md:reading-routines-and-runs:223 | optional_agent_tool | skills/paperclip/references/routines.md:223 |
|
||||
| skill:skills/paperclip/references/workflows.md:paperclip-workflow-playbooks:1 | optional_agent_tool | skills/paperclip/references/workflows.md:1 |
|
||||
| skill:skills/paperclip/references/workflows.md:project-setup-ceo-manager:7 | optional_agent_tool | skills/paperclip/references/workflows.md:7 |
|
||||
| skill:skills/paperclip/references/workflows.md:openclaw-invite-ceo:22 | optional_agent_tool | skills/paperclip/references/workflows.md:22 |
|
||||
| skill:skills/paperclip/references/workflows.md:setting-agent-instructions-path:50 | optional_agent_tool | skills/paperclip/references/workflows.md:50 |
|
||||
| skill:skills/paperclip/references/workflows.md:company-import-export:79 | optional_agent_tool | skills/paperclip/references/workflows.md:79 |
|
||||
| skill:skills/paperclip/references/workflows.md:self-test-playbook-app-level:106 | optional_agent_tool | skills/paperclip/references/workflows.md:106 |
|
||||
|
||||
## Legacy MCP Alias Index
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ 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 **39 tools**: 14 always-agent tools and 25 optional tools
|
||||
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
|
||||
@@ -39,7 +39,7 @@ by src/catalog/catalog-docs.test.ts; update the catalog, not the numbers. -->
|
||||
`finish_task`, `block_task`, `request_review`, `write_document`,
|
||||
`request_human_input`, `register_deliverable`, `inspect_operation_result`.
|
||||
|
||||
### Optional tools (25), by group
|
||||
### Optional tools (26), by group
|
||||
|
||||
| Group | Tools |
|
||||
| --- | --- |
|
||||
@@ -49,7 +49,7 @@ by src/catalog/catalog-docs.test.ts; update the catalog, not the numbers. -->
|
||||
| cases | `list_cases`, `upsert_case` |
|
||||
| workspace_runtime | `get_workspace_runtime`, `control_workspace_service` |
|
||||
| routines | `list_routines`, `manage_routine` |
|
||||
| company_skills | `create_skill`, `list_company_skills`, `sync_company_skills` |
|
||||
| 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` |
|
||||
|
||||
Loaded 100 of 627 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user