Files
PaperClipAI/doc/dot-runner-prototype.md
T
DottaandPaperclip fc6304dfe5 feat(runner): add experimental OpenAI Dot provider over MCP Events (#15402)
## Thinking Path

> - Paperclip manages AI agents, tasks, permissions, and execution
budgets.
> - Paperclip Runner gives each provider the same admitted task and tool
authority.
> - OpenAI Dot runs outside the local process tree and needs
asynchronous work delivery.
> - The merged MCP gateway supplies OAuth consent and signed event
delivery.
> - A personal assistant grant cannot safely stand in for an assigned
agent.
> - This pull request adds a separate Dot agent connection and a durable
Rust Runner bridge.
> - The operator can assign work to Dot and inspect its accepted work,
tool receipts, and result.

## Linked Issues or Issue Description

**Agent or provider**

OpenAI Dot, as an experimental provider of the existing Paperclip Runner
adapter.

**Why this adapter is useful**

An operator can assign normal Paperclip tasks to an existing Dot. Dot
can read its mailbox, request work on an assigned task, use admitted
task tools, and submit a result. Paperclip keeps company scope,
checkout, approvals, known budget limits, and activity attribution.

**How the agent is invoked**

A dedicated `/mcp/runner` OAuth resource pairs one Dot grant with one
agent. A signed MCP mailbox event wakes Dot. Dot explicitly accepts the
assignment. The Rust Runner owns the durable turn and operation
receipts. The first release supports self-hosted instances with a local
Runner controller.

**Additional context**

This extends the merged public MCP gateway from #14846 and the assistant
invitation and device-consent work from #14933. This also integrates the
merged assistant tool and configuration expansion in #15380. Dot retains
its dedicated agent resource and cannot receive personal configuration
permission. The public assistant connection remains a personal
connection.

## What Changed

- Add a durable Rust Dot provider and its TypeScript Runner driver.
- Add closed PRP v3 external-provider operations and native execution
input v6.
- Add company-scoped pairing, mailbox, assignment, and operation
records.
- Reuse merged browser/device consent, client metadata verification,
webhook admissions, refresh, secret rotation, and warm-standby gates.
- Keep Dot scopes, issuer, grants, event workers, and tool access
separate from personal assistant access.
- Add Dot configuration, pairing, readiness, and consent UI. Keep agent
grants out of the personal Connections entry.
- Regenerate the Dot-only migration after master. Preserve published
gateway migrations. Make the new migration safe to reapply.
- Document setup, recovery, accounting limits, evidence, and remaining
account qualification.
- Reverify reconnect callbacks and wake outstanding work with a fresh
mailbox reference; preserve the existing assignment and operation
receipts.
- Clean up Dot bindings and waiting runs on OAuth revoke and
refresh-token replay. Old grants cannot revoke replacement bindings.
- Restore the pairing reference when an unsaved agent form is reopened;
document board-only pairing routes in OpenAPI.
- Accept a clean Rust exit after the acknowledged shutdown receipt.
Unexpected exits still require recovery.
- Clear the cached binding after a successful revoke so a failed
connection refresh cannot restore it.
- Add production-component Storybook states and screenshots for pairing
and connection review. All preview account data is synthetic.
- Persist normalized completion, serialize Dot turns and durable work
admission, and poll subscription readiness.
- Serialize mailbox writes and cursor reads; retain paused fence
acknowledgement without task authority.
- Authorize admitted review runs without changing the worker assignee.
Include the fenced assignment ID in production stop notices.

## Verification

- This PR integrates master `4a8178e9c`. Dot migration
`0317_messy_famine.sql` follows the published history and is safe to
reapply. The merge preserves the reserved migration connection,
batch-commit handling, private task checks, task monitors, and native
accounting.
- Local workspace typecheck, full build, and UI token gates pass. The
server typecheck passes after the review fixes. Database and native
executor regressions pass.
- All twelve real Rust/PostgreSQL Dot integration tests and twelve Dot
driver tests pass. The tests cover native document writing and
finalization, durable replay, queue admission, mailbox ordering,
admitted reviews, stale authority, production stop references, and
paused acknowledgements.
- Current head `d0e7e0626` passes all 57 checks: 53 pass and four are
intentionally skipped. This includes full typecheck, build, tests, Rust
Runner verification, browser E2E, release verification, and Canary Dry
Run. Greptile rates this exact head 5/5. All review threads are
resolved.
- The full local root test run is slower than the sharded CI run and has
not completed. The full CI test gates pass on the current commit.
Focused local regressions pass.
- Real-account pairing and event delivery on this base commit remain
unqualified. Live account and setup proof are recorded in the follow-up
#15414.

The following screenshots use synthetic preview data. They show the
production pairing component and do not qualify a real account or the
full agent setup journey.

![Synthetic pairing
preview](https://github.com/paperclipai/paperclip/blob/codex/dot-events-prototype/doc/screenshots/openai-dot-runner/pairing.jpg?raw=true)

![Synthetic connected
preview](https://github.com/paperclipai/paperclip/blob/codex/dot-events-prototype/doc/screenshots/openai-dot-runner/connected.jpg?raw=true)

## Risks

- This base adapter uses `PAPERCLIP_ENABLE_OPENAI_DOT=1` plus Public MCP
and Paperclip Runner. The separate experimental-settings follow-up in
#15414 replaces this environment flag with saved operator settings.
- Dot does not expose provider token usage or cost. The operator must
acknowledge external billing. Known Paperclip budget gates still apply.
- Cancellation fences Paperclip authority. It does not confirm that Dot
stopped all external activity.
- Assigned skill files and third-party MCP bindings are unsupported and
reject admission. There is no mounted workspace, model selector, or
provider thread identifier.
- Hosted agent-broker and remote controller deployments are not
qualified.
- The new migration follows the merged master history. Existing
prototype databases still need the normal master migration history
before this Dot-only migration.

## Model Used

OpenAI Codex, based on GPT-6. The exact deployment ID and context window
size are not exposed in this session. Capabilities used: reasoning,
repository editing, code execution, and test inspection.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #123` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub references)
- [x] My branch name describes the change and contains no internal
Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-07 19:11:50 -05:00

17 KiB

OpenAI Dot runner prototype

The reference prototype is now accompanied by an experimental Rust Runner provider, dedicated /mcp/runner agent connection and durable broker. See OpenAI Dot with Paperclip Runner for setup and the current qualification limits. The transport proof below remains specific to this reference harness.

Built 2026-10-02 on the public MCP foundation from codex/paperclip-mcp-experimental-setting (commit a88448f77), merged into the fresh codex/dot-events-prototype worktree.

This is a working protocol prototype, not a production-selectable Dot agent. The default demo's Dot peer and task authority are synthetic. That demo does not contact OpenAI, spend model credits, or modify an existing company. The optional live lab below is intended to connect an actual Dot. The new provider implements the runner's HarnessDriver contract and executes through HarnessDriverBackend; it does not use the legacy HTTP adapter.

Try it

After installing and building workspace dependencies:

pnpm prototype:dot

The command creates a temporary PostgreSQL database, company, user and task; creates a real OAuth grant through PKCE and consent; starts two loopback HTTP servers; subscribes to a signed event; and drives a runner assignment to completion through authenticated MCP. It prints each successful stage and removes the temporary state. Its synthetic Dot peer deliberately retries a write concurrently to prove there is only one report. No credentials are printed. This is a CLI demo, not a mock UI or a connection to your Dot account.

Fresh-worktree preparation (use the repository's pinned pnpm version):

pnpm install --no-frozen-lockfile
pnpm --filter '@paperclipai/server^...' --filter '!@paperclipai/paperclip-runner' --filter '!@paperclipai/ui' build

The local callback uses an injected transport restricted to one fixed synthetic HTTPS URL and rewrites it to loopback. The production event transport retains its HTTPS, public-IP DNS pinning and no-redirect checks.

Connect a real Dot to the disposable lab

The separate live lab uses the real remote webhook transport, with no synthetic Dot peer or callback override. It still uses synthetic task admission and one projected save_report tool, so it is a transport acceptance test, not production agent scheduling. It requires an existing Dot account with custom plugins.

Expose only port 43127 through an HTTPS tunnel, then run:

cloudflared tunnel --protocol http2 --url http://127.0.0.1:43127 --no-autoupdate
DOT_LAB_ORIGIN=https://YOUR-TUNNEL-HOST pnpm prototype:dot:live

Add https://YOUR-TUNNEL-HOST/mcp/paperclip as an OAuth MCP plugin in the account that owns the Dot. The consent page waits for local operator approval. The lab prints a local control.json path; it contains a temporary credential, must stay local, and is removed when the lab stops. The separate control port (43128 by default) must never be tunneled. Inspect the pending client and return origin before approving its exact request ID:

node server/scripts/dot-runner-live-control.mjs /path/to/control.json status
node server/scripts/dot-runner-live-control.mjs /path/to/control.json approve REQUEST_ID

The consent page then returns to ChatGPT. Give Dot the standing instruction printed by the lab, including the exact company and inbox task IDs. Wait for a verified paperclip.dot.work_available subscription in status, then publish the assignment from Paperclip's side:

node server/scripts/dot-runner-live-control.mjs /path/to/control.json queue
node server/scripts/dot-runner-live-control.mjs /path/to/control.json status

The expected result is a signed event delivery, Dot reading and accepting the assignment, a report saying 17 + 25 = 42, and a structured runner completion. A delivered webhook alone is not a passing test. status exposes the report, runner transcript and delivery outcome only over authenticated loopback. Keep the lab and tunnel running while ChatGPT loads the newly installed tools; the plugin directory can list them before Dot can invoke them. A stopped quick tunnel cannot be reused just by restarting the lab. A new tunnel address needs a plugin configured for that address and fresh OAuth consent. Restarting the lab also discards its OAuth client registrations and grants. Stop the lab with the local control command (... control.json stop), then stop the tunnel after the test. Use this command for reliable cleanup; development process wrappers may terminate on a signal before asynchronous cleanup finishes. The lab expires after two hours, revokes its connection on graceful shutdown, and deletes its temporary database and encryption key. It never opens the user's existing Paperclip database.

Two directions, two explicit identities

sequenceDiagram
  participant Host as Paperclip host
  participant Runner as Dot runner bridge
  participant MCP as OAuth MCP + event outbox
  participant Dot as OpenAI Dot
  Host->>Runner: Admitted task + explicit grant/agent binding
  Runner->>MCP: Persist work_available references
  MCP->>Dot: Signed webhook wakeup
  Dot-->>MCP: Receipt (does not mean work started)
  Dot->>MCP: Read inbox and assignment; accept
  MCP->>Runner: Authenticated, bound commands
  Runner-->>Host: PRP turn.started
  Dot->>MCP: Call projected tools / report progress / propose result
  MCP->>Runner: Check current authority; deduplicate request ID
  Runner-->>Host: PRP progress and structured result
  Note over Host: Paperclip decides final task disposition
  Dot->>MCP: Personal Paperclip reads/writes while idle
  Note over MCP: These retain the consenting person's identity

Dot's always-available personal tools come from the merged public MCP connection. They act as the person who consented. Agent participation is separate: a trusted host binds an already admitted run to one OAuth grant, company, agent, task, session and turn. Connecting or selecting a task never grants impersonation. The registry has no MCP registration tool and no generic API executor.

What is implemented

  • DotHarnessDriver: one turn per admitted run, explicit acceptance, bounded lifetime, projected tools, progress and validated paperclip.run_result.v1 completion. Reports normal PRP events consumed by the existing native backend.
  • createDotRunnerMcpBridge: six tools on the existing authenticated endpoint: paperclip_dot_inbox, paperclip_dot_read, paperclip_dot_accept, paperclip_dot_tool, paperclip_dot_progress, paperclip_dot_finish. Bound tools are visible only to the selected grant with write consent.
  • Optional paperclip.dot.work_available event on the existing durable outbox. Its companyId and taskId identify a designated standing inbox task. Each assignment can concern a different task; the event carries only IDs, and Dot retrieves the current assignment through authenticated tools. Existing authorization, encrypted callback material, signed verification, retry, expiration, rotation and unsubscribe behavior are reused.
  • Matching duplicate commands share one receipt, including simultaneous writes. A changed retry is rejected. Ambiguous writes remain unknown and are not redispatched. Per-run receipts are memory-only in this prototype.
  • Revocation/expiry prevents further accepted commands and ends event waiting. The live host authority callback runs for each new operation and before returning tool output. The host's tool dispatcher must enforce atomic domain permissions, pause, ownership, approvals and budget checks at the actual write.
  • Usage and cost remain unknown. Delivery does not fabricate a started turn. Result acceptance proposes a disposition; it does not directly set issue status.

Host wiring and the production gap

The detailed implementation proposal is in OpenAI Dot as a production Runner provider.

The default app does not construct the bridge or advertise the Dot event. The host must explicitly pass enableDotPrototype: true to createPublicMcpEvents, pass the bridge as the fourth argument to publicMcpIngressRoutes, and register a driver for a trusted admitted run. The lab demonstrates that composition with synthetic admission and a single synthetic projected tool. There is no production scheduling integration yet.

Do not enable this by supplying a fake Codex profile to the native execution factory or by creating a heartbeat row from an MCP request. Production needs:

  1. An explicit governed agent binding/consent and a qualified Dot provider in native admission, with task checkout, budgets, pause and policy checks.
  2. Durable provider mailbox and command receipts, reconnect/recovery and reconciliation of uncertain tool outcomes. The event outbox already persists; this prototype's runner registry does not. After restart it refuses recovery.
  3. Server-owned runtime tool projection/dispatch with ordinary audit receipts. The lab tool is not the production runner tool authority.
  4. A stop policy that can handle remote autonomy honestly. Revoking Paperclip access cannot prove that Dot or its child tasks stopped. Active close reports provider stop is unconfirmed; interruption, steering and resume are not advertised. dot-bridge:<session> identifies this bridge, not an OpenAI task.
  5. A reachable staging deployment, real Dot plugin onboarding, and a live event-triggered assignment before claiming client compatibility or release readiness. Hosted use also needs the companion Cloud broker deployed.

An existing in-flight external write can finish after revocation. Do not retry with a fresh request ID to force a result. Inspect authoritative task state. Unregister completed runs to release the bounded in-process registry.

Intended live onboarding

  1. Connect the existing Paperclip MCP plugin in developer mode and consent to the team and requested writes.
  2. Explicitly bind that connection to the chosen Dot agent after the production admission work above. Show the identity and the standing inbox task.
  3. Give Dot one standing instruction to subscribe to paperclip.dot.work_available for that inbox, inspect current assignments, accept intended work, use the projected tools, and finish with a structured result. Treat duplicate events as wakeups, never as another task. Avoid comment-acknowledgement loops.
  4. Assign work in Paperclip and verify the entire loop with the actual account.

OpenAI now documents MCP Events for dots. Private developer-mode testing does not require a public directory listing; public distribution is a separate review. See MCP Events, developer mode, and app review.

Verification

pnpm --filter @paperclipai/paperclip-runner exec vitest run src/drivers/dot/dot-harness-driver.test.ts
pnpm --filter @paperclipai/server exec vitest run src/__tests__/public-mcp.test.ts
pnpm prototype:dot

Runner tests cover native PRP validation, cross-company/grant/turn rejection, unknown outcomes, concurrent retries, expiry, live authority checks, malformed results, late writes, revocation and unsupported recovery. The MCP suite checks real OAuth, event signatures, the opt-in catalog, personal-tool coexistence and revocation. The CLI demo uses real TCP for both directions.

Recorded validation: eight runner tests passed; all 37 MCP tests passed; the changed Dot scenario passed again after the final uncertainty-handling fix; runner and server TypeScript checks passed; the loopback CLI demo passed. Shared/server dependency builds and the runner TypeScript build also passed. The repository-wide test/build, Rust provider qualification and hosted Cloud acceptance were not run. Actual Dot acceptance is recorded below. This is not a PR-ready production provider handoff.

Live-lab setup on 2026-10-02: the isolated server started, its public OAuth discovery endpoint returned 200, and unauthenticated MCP requests returned 401. The live script passed an explicit TypeScript check, and the 37 MCP regression tests passed again after changing the bridge to a structural driver interface. During that initial setup, no actual Dot connected or subscribed: native desktop automation was unavailable, and the inspected browser account showed Dot creation rather than an existing Dot. The temporary tunnel subsequently expired; it and the lab were stopped. The later attempts below used the correct existing Dot account.

Actual Dot attempt, 2026-10-02

With the existing Dot open in the correct Chrome profile, the user approved installing the private Paperclip Dot Lab plugin and its lab-only OAuth grant. The first real callback exposed a lab bug: OAuth consent request IDs are opaque pcmcp_request_ tokens, not UUIDs. The live script now validates that format. Restarting the disposable database also erased ChatGPT's cached public client registration. The exact public client ID and redirect URI were restored only in the verified one-company lab database; no grant or token was inserted directly. The normal consent and PKCE exchange then completed successfully.

Chrome briefly showed ERR_BLOCKED_BY_CLIENT; after the user handled/retried the page, it showed the ordinary stale-client server error instead. The browser block's originating component was not identified. No browser protection was disabled. It should not be confused with the separately diagnosed OAuth error.

Observed live results:

  • ChatGPT called authenticated server/discover, tools/list, and events/list using MCP 2026-07-28; all returned HTTP 200.
  • The plugin UI showed its connected account, 10 read tools, 6 write tools, and all four event definitions, including the six Dot runner actions.
  • Dot found paperclip.dot.work_available and recognized its argument schema. Initially it reported no Paperclip action tools in its runtime, including the read-only paperclip_connection. An explicit @ mention did not make them available immediately.
  • Dot also reported that its event-subscription route requires a successful read-only connection check, so the reduced event-only test could not proceed.
  • The lab observed zero tools/call or events/subscribe requests, zero subscriptions, and zero deliveries. No assignment was queued and no report was saved. This is live discovery/connection evidence, not an end-to-end task or event-wakeup pass.

The first lab and tunnel were stopped too early, revoking the temporary grant and deleting its database. Dot subsequently reported that the action tools had become available, but its connection check failed against that offline endpoint. This does not establish a Dot runtime incompatibility. A fresh lab and tunnel were connected as Paperclip Dot Lab Retry; the new server observed Dot calling paperclip_connection successfully. The first plugin definition remains installed with its expired endpoint. Production still needs the admission, durable mailbox, recovery and tool-authority work above.

Successful real Dot retry, 2026-10-02

The replacement private plugin completed fresh dynamic client registration, normal OAuth consent and PKCE without manual database repairs. It used the real Dot in the user's Chrome account and the public HTTPS tunnel. The lab retained the ordinary callback verification, DNS/IP pinning and webhook signing checks.

Observed server evidence (UTC):

Time Observation
22:16:30 Dot called paperclip_connection successfully.
22:17:00 events/subscribe succeeded; the verified paperclip.dot.work_available subscription was present.
22:17:24 The operator queued the single assignment. Its webhook was observed as delivered after one attempt.
22:17:48 Dot called paperclip_dot_inbox after the event, without another chat prompt.
22:17:56 Dot read the assignment using paperclip_dot_read.
22:18:10 Dot accepted it; the native Runner emitted turn.started.
22:18:23 Dot called save_report through paperclip_dot_tool; the lab saved exactly one report: Dot received the Paperclip event. 17 + 25 = 42.
22:18:38 Dot submitted a valid paperclip.run_result.v1; the Runner emitted run.result.proposed, turn.completed and run.terminal with state succeeded.
22:18:50 events/unsubscribe succeeded; no subscription remained.

Dot's final chat message confirmed event receipt and completion. It could not confirm complete unsubscription from its client response, but the server independently recorded the successful unsubscribe. Removing the subscription also removed its delivery rows, so delivery was verified before that cleanup. Dot separately disclosed an accidental cloud desktop app inventory call during setup; it reported opening no app content and stopping immediately. This test proves the transport and Runner handshake, not isolation of Dot's other tools.

This is a real event-triggered, two-way transport acceptance pass using the new Runner backend. It still uses synthetic admission, one disposable task and one projected tool. It does not establish production scheduling, recovery, budget enforcement, arbitrary personal API operations, or reliable remote stop. The replacement lab was left running for inspection under its two-hour expiry; the test subscription was removed and no further assignment was queued.