paperclip/packages/shared/src/telemetry
Dotta 25cf079ec5
feat(runner): add Codex-native application integration (#12591)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The runner package is useful only when the application can start,
observe, and recover a native Codex run safely.
> - Existing direct adapters must keep their current execution and
finalization paths.
> - The application boundary therefore needs additive persistence,
authorization, coordination, and recovery behind an explicit
experimental adapter.
> - This pull request adds that Codex-only boundary without activating
generalized providers, remote environments, or the later task/SDK
surfaces.

## Linked Issues or Issue Description

**Subsystem affected**

Shared contracts, database persistence, adapter utilities, server
native-runtime services, and the experimental Paperclip Runner adapter.

**Problem or motivation**

The already-landed runner package has a qualified Codex path, but the
application needs durable native-run state, guarded runtime selection,
authenticated coordination, tool security, finalization, and recovery
before the experimental adapter can be exercised safely.

**Proposed solution**

Add a Codex-only `paperclip_runner` application path behind the existing
default-off native-runner setting. Bind native state and coordination to
company/run identity, preserve persisted-run recovery, and leave every
direct adapter on its existing legacy execution path.

**Alternatives considered**

The earlier stack boundary introduced a generalized executor and
remote-environment lifecycle here. That made this PR depend on
implementations in higher PRs and changed reusable sandbox behavior
globally. Those pieces are now deferred together to #12592.

**Roadmap alignment**

ROADMAP.md does not list a conflicting native-runner integration
project. This change adds the application boundary for the existing
Runner architecture.

## What Changed

- Added native run/result/finalization/provider-trace persistence,
shared validators, and idempotent migration/replay coverage.
- Added guarded Codex-only runtime selection, authenticated PRP
coordination, recovery, finalization, and interaction services.
- Added run/company-bound tool-gateway authorization, credential
redaction, SSRF protections, and replay-safe behavior.
- Added the explicit `paperclip_runner` adapter behind the default-off
rollout setting.
- Preserved legacy answered-question wake projection and direct-adapter
execution/finalization paths.
- Hardened cancellation so only owned in-memory child processes are
signaled; persisted recycled PIDs/process groups are never trusted.
- Retained the narrow Claude ACPX isolated-context security follow-up
discovered after #12590.
- Deferred the generalized executor, provider ingress, remote lifecycle,
SDK/lab/eval work, release-process changes, and lockfile.

## Verification

- Changed-file delta against `master`: 133 files.
- GitHub Actions is the authoritative verification environment for this
PR.
- Full CI, security, and Greptile review will run on this lowest
unmerged stack PR.
- Local tests/build/typecheck were not run because this checkout is
resource constrained.
- Static diff/reference checks pass, and `pnpm-lock.yaml` is unchanged.

## Risks

- This touches central heartbeat and agent-route code, so legacy
compatibility is the primary risk.
- Runtime selection remains Codex-only and explicit; direct Codex,
Claude, OpenCode, process, HTTP, and plugin adapters remain on their
existing paths.
- Fresh native starts fail closed while the rollout flag is off;
persisted native records remain readable and recoverable.
- Cancellation, company/run binding, tool calls, status decisions, and
completion writes are guarded or replay-safe.

> For core feature work, check [ROADMAP.md](ROADMAP.md) first and
discuss it in #dev before opening the PR. Feature PRs that overlap with
planned core work may need to be redirected.

## Model Used

OpenAI Codex, GPT-5.6, with repository tools, code execution, and
parallel agent review.

## 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 linked existing issues or described the issue in-PR
following the relevant issue template
- [x] I have not referenced internal or instance-local Paperclip issues
or links
- [x] My branch name describes the change and contains no internal
Paperclip ticket id
- [ ] I have run tests locally and they pass — GitHub Actions is
authoritative for this resource-constrained checkout
- [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 risks above
- [ ] All Paperclip CI and security gates are green
- [ ] Greptile is 5/5 with no open actionable findings
- [x] I will address all Greptile and reviewer comments before merge

## Stack

- Position: 3 of 5 overall; lowest of 3 currently unmerged
- Base: `master`
- Previous:
[#12590](https://github.com/paperclipai/paperclip/pull/12590), qualified
Claude ACPX runtime — merged
- Next: [#12592](https://github.com/paperclipai/paperclip/pull/12592),
generalized Codex executor, task experience, and developer SDKs

---------

Co-authored-by: Dev Agent <dev@paperclip.ing>
2026-08-31 14:38:38 -05:00
..
generated feat(runner): add Codex-native application integration (#12591) 2026-08-31 14:38:38 -05:00
README.md refactor: disambiguate the Telemetry and Observability data paths (#12128) 2026-08-24 16:42:33 -07:00
client-contract.test.ts feat(telemetry): align client with wire contract — chunking, deterministic batchId, batched retry, bounded store (#9946) 2026-07-21 09:25:58 -05:00
client-types.test.ts Implement generated client telemetry types (#8818) 2026-07-01 11:39:39 -07:00
client.test.ts feat(telemetry): align client with wire contract — chunking, deterministic batchId, batched retry, bounded store (#9946) 2026-07-21 09:25:58 -05:00
client.ts feat(telemetry): align client with wire contract — chunking, deterministic batchId, batched retry, bounded store (#9946) 2026-07-21 09:25:58 -05:00
config.ts feat(telemetry): align client with wire contract — chunking, deterministic batchId, batched retry, bounded store (#9946) 2026-07-21 09:25:58 -05:00
events.ts fix(interactions): authorize resolvers consistently (#11376) 2026-08-16 13:46:50 -05:00
index.ts fix(interactions): authorize resolvers consistently (#11376) 2026-08-16 13:46:50 -05:00
readme-contract.test.ts Add telemetry data contract docs (#8886) 2026-07-02 12:38:03 -07:00
retention.test.ts fix(interactions): authorize resolvers consistently (#11376) 2026-08-16 13:46:50 -05:00
retention.ts fix(interactions): authorize resolvers consistently (#11376) 2026-08-16 13:46:50 -05:00
state.ts feat: implement app-side telemetry sender 2026-04-02 10:47:29 -05:00
types.ts feat(telemetry): align client with wire contract — chunking, deterministic batchId, batched retry, bounded store (#9946) 2026-07-21 09:25:58 -05:00

README.md

Telemetry Data Contract

This document explains how contributors should use Paperclip's public telemetry contract. It does not duplicate the full list of individual events or dimensions. It documents extra semantic and privacy rules where the generated shape is not sufficient.

The canonical source for first-party event names, dimensions, optionality, allowed primitive value types, and enum descriptions is packages/shared/src/telemetry/generated/paperclip-telemetry.ts.

Shared enum constants live in packages/shared/src/constants.ts. Use those constants when code needs a reusable domain, but treat the generated telemetry types as the final authority for emitted first-party telemetry shapes.

Public Sources

Use these files when reviewing or changing telemetry code:

Contract item Public source
First-party event names PaperclipEventName in generated/paperclip-telemetry.ts
Per-event dimensions and optionality EventDimensionsMap in generated/paperclip-telemetry.ts
Enum descriptions for telemetry dimensions PAPERCLIP_ENUM_DESCRIPTIONS in generated/paperclip-telemetry.ts
Schema version and event envelope helpers SCHEMA_VERSION, makeEvent(), and makeBatch() in generated/paperclip-telemetry.ts
Runtime-safe event names and dimensions TelemetryEventName and TelemetryEventDimensions in types.ts
Allowed primitive dimension values TelemetryDimensionValue in types.ts
Shared reusable enum domains Named exports in constants.ts
First-party typed emit helpers events.ts
Generic client behavior client.ts
Retention windows and event class assignments RETENTION_DAYS and EVENT_RETENTION_CLASS in retention.ts

Do not copy generated event lists or dimension tables into this README. They will drift as the generated contract changes.

Emission Boundary

Paperclip telemetry uses named events with explicit dimension fields. Treat open-ended string dimensions as public contract values, not as a place for user content or private operational data. Do not send PII, secrets, credentials, private paths, prompts, model output, or other sensitive values through telemetry dimensions.

Telemetry emitters send raw dimension values. They must not pre-normalize enum-like values into a reporting form just to match today's known domain.

The receiving layer owns canonicalization. Keeping canonicalization in one place means emitters can stay simple and accurate: emit what the product observed, use the generated contract for required and optional fields, and let the receiving layer decide how legacy spellings, aliases, unknown names, and future values map to a stable reporting shape.

Do not add client-side lowercasing, alias mapping, or fallback mapping unless the generated telemetry contract specifically requires that emitted value.

If a dimension is privacy-protected before emission, emit only the protected value and its matching public marker as defined by the typed helper or generated contract. Do not emit private source material in telemetry dimensions.

Interaction Resolver Events

interaction.created records the interaction kind and whether the create request used a deprecated resolver-policy alias. It does not record the prompt, title, options, questions, target identifier, creator identifier, or resolver identifier.

interaction.resolved records the low-cardinality interaction outcome defined in the generated contract. Its legacy_inherited_restriction dimension is true only when stored migration provenance preserves a legacy resolver-policy restriction. It is false for canonical new writes. This dimension describes policy provenance. It does not contain user content or an identifier.

Use trackInteractionCreated() and trackInteractionResolved() from events.ts to emit these events. The generated contract remains the authority for their exact dimensions and optionality.

Other Data Paths

This document covers Paperclip Telemetry only. The generated Telemetry contract covers neither the Observability path nor the run-log path. Two other data paths document their own contract in their own file:

  • Observability — the OpenTelemetry trace path, the sandbox startup trace spans, and the sandbox duplex transport instrumentation.
  • Run-Log Events — events written to the local heartbeat_run_events table.

Dimension Values

Telemetry dimension values must be primitives. Use only the value types allowed by TelemetryDimensionValue:

  • string
  • number
  • boolean

Do not emit null, undefined, arrays, or objects as dimension values. Optional dimensions should be omitted when absent.

When a dimension is enum-like, use the shared constant from constants.ts when one exists. If no shared constant exists, use the generated telemetry type as the domain. In all cases, the generated telemetry type remains the source of truth for the emitted value.

Required, Optional, And Sentinel Values

Required and optional dimensions are defined by EventDimensionsMap.

Required dimensions must be present for every event of that name. Optional dimensions should be emitted only when the value is known and useful.

Sentinel values are only for required fields that have no observed raw value at the emitting layer. Do not use a sentinel to hide a concrete value that is new, custom, or not yet represented by a shared constant. Emit the concrete raw value and let the receiving layer canonicalize it.

Adding Or Changing Telemetry

Client code is responsible for emitting approved telemetry events at the right place in the product. Stable event names, dimensions, and enum domains must come from the generated telemetry contract before normal emitters use them.

For product work that needs to propose a new first-party event before schema registration, use the proposal marker workflow in doc/TELEMETRY_WORKFLOW.md. Those proposed calls stay on client.track(), carry an @ts-expect-error marker on the event-name argument, and are swallowed at runtime until the generated schema registers the event name.

For stable event work:

  1. Start from generated/paperclip-telemetry.ts. The generated types are what reviewers use to verify event names, dimensions, optionality, value types, enum descriptions, and schema version.
  2. Choose stable event and dimension names. Do not include user content, local machine details, secrets, credentials, private paths, or values that are not part of the public event contract.
  3. Use only string, number, or boolean dimension values.
  4. Reuse a shared constant from constants.ts for enum-like dimensions when one exists. If the generated telemetry domain has values beyond a shared constant, keep the emitter aligned with the generated telemetry type.
  5. Keep emitters raw. Do not normalize, alias-map, or lowercase enum-like values in the client unless the generated contract explicitly calls for that emitted value.
  6. Add or update a typed helper in events.ts when the event is first-party and should have a stable helper API.
  7. Update tests for helper behavior, including raw pass-through for enum-like values when that is the intended boundary.
  8. Update this README only when the contributor workflow, source-of-truth pointers, or durable invariants change. Do not add an event catalog here.

Before opening a pull request, verify that the emitted code, typed helpers, and generated telemetry contract agree. If they disagree, fix the contract or code rather than documenting around the mismatch in this README.

For new first-party events that are not in the generated contract yet, follow the public proposal and promotion workflow in doc/TELEMETRY_WORKFLOW.md.

Retention

Retention windows are documented in retention.ts. Each event is assigned a retention class; the class determines the window in days. This is a housekeeping and query-cost concern managed by data-infra, not a schema concern — updating a retention window does not require a schema version bump.

Current classes:

Class Window Description
operational_enum_count 90 days Enum/boolean/count/bucket events. No token material, no PII.

When a new event carries only enums, booleans, counts, or coarse buckets and no token material or PII, assign it to operational_enum_count in EVENT_RETENTION_CLASS. If no existing class fits, define a new class in RETENTION_DAYS and document it here.