Skip to content

feat(observability): export workflow and activity-attempt spans over OTLP (closes #61) - #366

Closed
DocGrok1 wants to merge 1 commit into
oll4com:mainfrom
DocGrok1:otlp-worker-spans-61
Closed

DocGrok1 wants to merge 1 commit into
oll4com:mainfrom
DocGrok1:otlp-worker-spans-61

Conversation

@DocGrok1

Copy link
Copy Markdown

Closes #61

Problem and user impact

With SPACE_OTEL_EXPORTER_OTLP_ENDPOINT set, only the API exported spans. Workers emitted none, so a slow or failing turn, Room Agent mission or memory repair could only be followed by joining log lines by hand, and background continuations started a new, unlinked trace (OBS-001 / OBS-007).

Change

One connected trace from the API SERVER span to the workflow span to one span per activity attempt, across worker.ts, memory-maintenance-worker.ts and memory-mutation-worker.ts.

  • Shared exporter in packages/runtime (no copy): bounded queue with counted drops, per-POST timeout, never blocks workflow/activity work. Added optional span links, and fixed a flush on an empty queue that stopped all later exports. API behavior and apps/api/tests/otlp-span-exporter.test.ts unchanged.
  • Workers: worker-tracing.ts registers the workflow interceptor module (workflow-tracing.ts) and activity interceptors in all three entrypoints only when the endpoint is set; with it unset nothing is registered.
  • Determinism: workflow code does no I/O. Spans leave the sandbox only through a sink with callDuringReplay: false. Span ids are derived from Temporal ids, so replay and retried workflow tasks never duplicate spans and the workflow random source is untouched.
  • Span tree: workflow span parented on the propagated traceparent span id (the API now passes its own SERVER span id); one span per activity attempt with attempt, outcome, reasonCode / category; trace context travels in a space-trace Temporal header to activities, child workflows (memoryMaintenanceWorkflow → memoryConsolidationWorkflow) and continue-as-new runs. roomAgentSupervisorWorkflow: one span per mission turn in the trace of the request that queued it, linked to the supervisor run. dotGoalWorkflow and API-started memory workflows get the memo.
  • Span links for continue-as-new runs and background continuations (roomAgentContinuation.sourceTraceparent, optional contract field).
  • Allowlist: ids, workflow/activity type, attempt, status, stable code/category, durations only; identifier-only values. Prompts, model output, file paths, URLs, error messages are never exported (test enforces it).
  • No new tracing dependency. @temporalio/testing is added as a worker devDependency for the integration test (see [$400] Test every Temporal workflow on a real time-skipping test server and gate replay determinism in CI #60).
  • Docs: docs/quality/observability.md and docs/configuration-reference.md updated, including the "Span export covers API requests only" bullet.

Verification

Linux x86_64 (kernel 6.12), Node v22.21.1, npm 10.9.4, commit 519da28 (this PR's head). Every line below was actually run; nothing on macOS or Windows.

Command Result
npm run check exit 0
npm test -w @space/worker 251/251 passed (first run). On a later re-run on a machine without the sqlite3 CLI: 250/251, the one failure is codex-cli-completion.test.ts (spawn sqlite3 ENOENT), which is unchanged by this PR and fails the same way on main there
npm test -w @space/api -- otlp 8/8 passed
@space/api trace-context tests 35/35 passed
npm test -w @space/runtime 260/260 passed
npm test -w @space/contracts 490/490 passed
OTLP integration + replay tests (otlp-workflow-tracing.integration.test.ts, workflow-tracing.test.ts) on the @temporalio/testing time-skipping server with a local fake collector 17/17 passed
Negative control: same replay test with callDuringReplay: true fails as expected (duplicate spans), so the test does detect replay emission
npm run lint 0 errors (warnings only, pre-existing)
npm run hygiene:preflight passed
npm run public:export:verify ok: true

Jaeger (manual check). Real jaegertracing/all-in-one with SPACE_OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318. The run used a local, uncommitted demo test: a Fastify demo route wired with the API's real createHttpObservability and TemporalCodexAppServerTurnStarter, plus the real workers' tracing options, running the real workflows on the time-skipping Temporal server with stub activities (no AI provider). It is not a run of a full installed stack.

Turn trace (API → codexAppServerTurnWorkflow → two attempts of runCodexAppServerTurn, first FAILED with CODEX_APP_SERVER_UNAVAILABLE, second COMPLETED):

turn trace

Memory trace (API → memoryMaintenanceWorkflow → activities → child memoryConsolidationWorkflow → activities, across the maintenance and mutation workers):

memory trace

Span listing: https://raw.githubusercontent.com/DocGrok1/spaceapp/e139be4701fd36c42499b2252364296a10b185b7/jaeger-trace-summary.txt

The demo's stub activity deliberately threw an error containing prompt text, a URL with a token and a file path; none of it appears in the exported attributes.

  • Relevant automated tests pass (see above).
  • Linux coverage listed. macOS and Windows: not run.
  • UI changes: none.
  • Every commit has a Signed-off-by: line (DCO 1.1); MIT License.
  • No credentials, tokens, personal memory, customer data, private paths or runtime state.
  • Documentation updated.
  • Security / resource / rollback: export is off by default; when on, the bounded queue and timeouts cap memory and latency; rollback is unsetting the variable or reverting this commit. No migration.

AI-assisted; the results above are from commands actually executed. The "Verify export provenance" step is expected to fail on this fork PR, per the issue.

…OTLP

With SPACE_OTEL_EXPORTER_OTLP_ENDPOINT set, an API request that starts
Temporal work now produces one connected trace: the API SERVER span, the
workflow span, one span per activity attempt and nested child workflows,
across the worker and both memory workers.

- Workflow interceptor module (workflow-tracing.ts) registered by
  worker-tracing.ts in all three worker entrypoints, only when export is
  on. Spans leave the sandbox through a sink with callDuringReplay: false;
  span ids are derived from Temporal ids, so replay and retried workflow
  tasks never duplicate spans and the workflow random source is untouched.
- Trace context travels in a `space-trace` Temporal header to activities,
  child workflows and continue-as-new runs.
- Activity spans: one per attempt, child of the workflow span, with
  attempt, outcome, reasonCode and category.
- roomAgentSupervisorWorkflow: one span per mission turn in the trace of
  the request that queued it, linked to the supervisor run.
- Span links for continue-as-new runs and background continuations
  (roomAgentContinuation.sourceTraceparent, optional contract field).
- Attribute allowlist with identifier-only values.
- API: workflows get the request's own SERVER span id as parent;
  dotGoalWorkflow and API-started memory workflows get the memo.
- Shared exporter (packages/runtime): optional span links, and a fix for
  a flush on an empty queue that stopped all later exports.
- Integration test on the @temporalio/testing time-skipping server with
  a fake OTLP collector; replay test; docs updated.

Closes oll4com#61

Signed-off-by: Joshua L. Lopez <joshualee.2733@gmail.com>
Copilot AI balanced review requested due to automatic review settings October 30, 2026 06:30
@coderabbitai

coderabbitai Bot commented Oct 30, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 59 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 7f1d3295-3848-4709-be8c-03338797c1a5
Commits

Reviewing files that changed from the base of the PR and between d9059c1 and 519da28.

Files ignored due to path filters: 1
  • package-lock.json is excluded by !**/package-lock.json
Files selected for processing: 26
  • apps/api/src/dot-goals.ts
  • apps/api/src/memory-consolidation-coordinator.ts
  • apps/api/src/memory-mutation-coordinator.ts
  • apps/api/src/trace-context.ts
  • apps/api/tests/trace-context-workflow-parent.test.ts
  • apps/worker/package.json
  • apps/worker/src/activity-observability.ts
  • apps/worker/src/memory-maintenance-worker.ts
  • apps/worker/src/memory-mutation-worker.ts
  • apps/worker/src/room-background-continuation.ts
  • apps/worker/src/trace-carrier.ts
  • apps/worker/src/worker-tracing.ts
  • apps/worker/src/worker.ts
  • apps/worker/src/workflow-tracing.ts
  • apps/worker/src/workflows.ts
  • apps/worker/tests/activity-observability.test.ts
  • apps/worker/tests/otlp-workflow-tracing.integration.test.ts
  • apps/worker/tests/room-background-continuation.test.ts
  • apps/worker/tests/room-continuation-fixture.ts
  • apps/worker/tests/support/fake-otlp-collector.ts
  • apps/worker/tests/workflow-tracing.test.ts
  • docs/configuration-reference.md
  • docs/quality/observability.md
  • packages/contracts/src/schemas.ts
  • packages/runtime/src/otlp-span-exporter.ts
  • packages/runtime/tests/otlp-span-exporter-shared.test.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Tracing can misreport failures and cancellations as completed and break trace continuity for interrupted-turn cleanup and background continuations, while supervisor replay coverage is missing.

5 open issues
What changed in this PR

This PR extends optional OTLP tracing across Temporal workflows and activity attempts, connecting worker execution to API request traces and addressing the coverage gaps in #61.

Changes:

  • Adds workflow and mission spans, trace propagation, and continuation links across all three workers.
  • Extends the shared exporter with span links and fixes exports stopping after an empty flush.
  • Adds tracing, integration, and replay tests, plus configuration and coverage documentation.
File Description
packages/​runtime/​tests/​otlp-span-exporter-shared.test.ts Tests links, idle flush recovery, and queue bounds.
packages/​runtime/​src/​otlp-span-exporter.ts Supports span links and fixes empty-queue flushing.
packages/​contracts/​src/​schemas.ts Adds optional continuation source trace context.
package-lock.json Locks Temporal dependencies and updates workspace metadata.
docs/​quality/​observability.md Documents workflow tracing, links, and limitations.
docs/​configuration-reference.md Documents OTLP configuration across services.
apps/​worker/​tests/​workflow-tracing.test.ts Tests carriers, filtering, registration, and deduplication.
apps/​worker/​tests/​support/​fake-otlp-collector.ts Provides a local test collector.
apps/​worker/​tests/​room-continuation-fixture.ts Supports traced continuation fixtures.
apps/​worker/​tests/​room-background-continuation.test.ts Tests continuation source-context preservation.
apps/​worker/​tests/​otlp-workflow-tracing.integration.test.ts Tests real workflow traces, links, and replay suppression.
apps/​worker/​tests/​activity-observability.test.ts Checks tracing registration across worker entrypoints.
apps/​worker/​src/​workflows.ts Wraps supervisor mission turns in tracing scopes.
apps/​worker/​src/​workflow-tracing.ts Implements workflow interceptors and mission scopes.
apps/​worker/​src/​worker.ts Registers shared tracing options.
apps/​worker/​src/​worker-tracing.ts Configures interceptors and replay-suppressed sinks.
apps/​worker/​src/​trace-carrier.ts Defines trace headers, deterministic IDs, and attribute filtering.
apps/​worker/​src/​room-background-continuation.ts Preserves source context for continuation links.
apps/​worker/​src/​memory-mutation-worker.ts Registers shared tracing options.
apps/​worker/​src/​memory-maintenance-worker.ts Registers shared tracing options.
apps/​worker/​src/​activity-observability.ts Parents activity spans using propagated workflow context.
apps/​worker/​package.json Adds Temporal common and testing dependencies.
apps/​api/​tests/​trace-context-workflow-parent.test.ts Tests request-span parenting and memory workflow memos.
apps/​api/​src/​trace-context.ts Propagates the exported API span as parent.
apps/​api/​src/​memory-mutation-coordinator.ts Adds request trace context to workflow memos.
apps/​api/​src/​memory-consolidation-coordinator.ts Adds request trace context to workflow memos.
apps/​api/​src/​dot-goals.ts Passes request trace context when starting goals.

Review effort: Balanced

Comment on lines +128 to +131
const { traceparent: sourceRequestTraceparent, ...sourceTurn } = source.queueItem.turn;
const sourceTraceparent = sourceRequestTraceparent ?? source.queueItem.turn.roomAgentContinuation?.sourceTraceparent;
const item = roomAgentSupervisorQueueItemSchema.parse({ missionId: input.missionId, turn: {
...source.queueItem.turn,
...sourceTurn,
Comment on lines +102 to +104
const status = (result as { status?: unknown } | null)?.status;
if (status === "FAILED") return "FAILED_RESULT";
return "COMPLETED";
cancellationType: ChildWorkflowCancellationType.WAIT_CANCELLATION_COMPLETED
// One span per mission turn in the trace of the request that queued it
// (a no-op without OTLP export; it does no I/O and issues no commands).
await traceWorkflowScope({ name: "mission", traceparent: item.turn.traceparent, args: [item] }, async () => {
cancellationType: ChildWorkflowCancellationType.WAIT_CANCELLATION_COMPLETED
// One span per mission turn in the trace of the request that queued it
// (a no-op without OTLP export; it does no I/O and issues no commands).
await traceWorkflowScope({ name: "mission", traceparent: item.turn.traceparent, args: [item] }, async () => {
Comment on lines +266 to +270
const cases: Array<[string, string]> = [
[buildCodexAppServerTurnWorkflowId(turn("a")), "workflows.ts"],
["space-memory-maintenance:tracing", "memory-maintenance-workflows.ts"],
["space-memory-consolidation:tracing-1", "memory-maintenance-workflows.ts"]
];
@oll4com

oll4com commented Oct 30, 2026

Copy link
Copy Markdown
Owner

Thank you for the time and care you put into this pull request.
We have decided to end the SpaceApp bounty programme, so pull requests made for the bounty issues will not be reviewed, merged or paid, and we are closing this one.
We are sorry for the work this affects, and thank you again for your interest in SpaceApp.

@oll4com oll4com closed this Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[$600] Export worker and workflow spans over OTLP: one trace from API request to every Temporal activity

3 participants