Skip to content

spike: Nexus Service support (workflow-backed Operations) - #9

Draft
tyler5673 wants to merge 3 commits into
mainfrom
feat/nexus-service
Draft

spike: Nexus Service support (workflow-backed Operations)#9
tyler5673 wants to merge 3 commits into
mainfrom
feat/nexus-service

Conversation

@tyler5673

@tyler5673 tyler5673 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Linear: DX-745

A design spike and reference implementation for Temporal Nexus support, exposing the six You.com Activities as cross-Namespace Operations on top of the existing Activity layer.

This is a spike, not a merge candidate. It works and it is tested, but it freezes a public cross-Namespace contract that no caller has yet exercised across a real Namespace boundary, and one open question below will change that contract. Opening it for a routing decision: does Nexus belong in the partner submission, or in a follow-up v2? See Open questions.

Activity-only users are unaffected either way — youdotcom_temporal.nexus is a separate import.

Why every Operation is async (workflow-backed)

Nexus synchronous Operations must finish within the 10-second handler deadline. Several You.com calls routinely exceed it:

  • search with full-page extraction (crawl_timeout up to 60s)
  • contents with multiple URLs and a high per-URL crawl_timeout
  • research and finance_research (minutes)
  • research_background (up to 4 hours for frontier effort)

Temporal's guidance is explicit — use a sync Operation "only when its complete execution path is highly reliable, has predictably low latency, and finishes well within the 10-second handler deadline." A sync handler that blows the deadline is killed as a retryable error, and the circuit breaker "trips after 5 consecutive retryable errors, blocking all Operations from the caller to that Endpoint." That failure is shared-fate across the Endpoint, so every Operation here is asynchronous and backed by a thin Workflow.

The tradeoff worth naming: this makes a fast search cost a full Workflow Execution rather than a couple of Actions. We are buying blast-radius safety with per-call cost.

What's here

File Purpose
src/youdotcom_temporal/nexus.py YouDotComService contract (6 Operations) + YouDotComServiceHandler + you_nexus_service_handler()
src/youdotcom_temporal/workflows.py 6 backing Workflows + you_nexus_workflows(), each with a per-Activity start_to_close_timeout
src/youdotcom_temporal/__init__.py Lazy public exports (PEP 562) — see the bug fix below
tests/test_nexus.py, tests/_caller_workflow.py Contract, handler, sandbox-registration, and timeout/retry tests (no server needed)
examples/run_nexus_worker.py Handler-side Worker example + caller-Workflow sketch
README.md, CHANGELOG.md Nexus section and entries

Bug fix worth landing regardless of the routing decision

youdotcom_temporal/__init__.py imported the Activity layer eagerly, which imports the You.com SDK and urllib.request. Python imports a parent package before a submodule body runs, so a submodule's own workflow.unsafe.imports_passed_through() block never got the chance to cover it. Verified against SandboxedWorkflowRunner.prepare_workflow — the path Worker.__init__ takes for every registered Workflow:

  • The backing Workflows only registered when YouPlugin was also passed, because the plugin's passthrough list was silently doing the block's job.
  • The documented caller Workflow could not start at all: a caller in another Namespace registers no plugin, so it had no passthrough to inherit.

The public names now resolve through a module __getattr__, so the SDK loads on first attribute access — after any passthrough block has been entered. Public imports are unchanged. This is a latent sharp edge in the Activity layer that the Nexus work exposed; it is not specific to Nexus.

Other fixes found while reviewing this

  • research_background's 4-hour ceiling was unreachable. The Activity hardcodes timeout_s=120.0 when unset, overriding the SDK's effort-based default (600s, 14400s for frontier). A default frontier call failed after two minutes and, under the original retry policy, submitted three billable research tasks. The Workflow now fills timeout_s from research_effort.
  • The ceiling had zero headroom over the Activity's own wait. research_and_wait_async issues a final GET after its internal wait expires, so an exact match let Temporal kill the attempt before the Activity could report anything. The ceiling is now derived from the waits plus a margin.
  • The research Operations no longer retry. Each attempt submits a new billable research task, and the previous attempt keeps running because the Activities do not heartbeat, so a retry multiplied cost instead of recovering. The fast Operations still retry.
  • Corrected claims: dropped "cancelable" (see limits below) and "without importing the Activity code" (importing the contract loads the Activity layer); fixed a comment that had the relationship to the client's 300s HTTP timeout backwards for search/answer/contents.

Open questions

  1. Should this ship in the partner submission, or as v2? Recommendation is v2 — see the contract concerns above.
  2. Where does the idempotency key live? Backing Workflow Ids are currently random, so a retried Nexus StartOperation request starts a second Workflow and a second billable You.com call. Temporal's guidance is that "Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID should be passed in the Operation input as part of the Nexus Service contract." Doing that changes the input types — which is the main reason this is not a merge candidate as-is.
  3. Do the partner standards expect heartbeating Activities? Cancelling an Operation cancels the backing Workflow, but the Activities do not heartbeat, so an in-flight You.com call runs to completion and is still billed. Fixing it means adding heartbeats to the Activity long-poll loops.

Known limits

  • Cancellation does not reach You.com (see above).
  • Operation starts are not idempotent (see above).
  • nexusrpc is imported but not declared. It arrives transitively via temporalio, which pins nexus-rpc==1.4.0. Declare it under the distribution name nexus-rpc before release — nexusrpc is the import name and not a valid requirement. A range like >=1.4,<2 would conflict when temporalio bumps its pin.
  • No end-to-end Nexus round-trip test. It needs a local Temporal server with Nexus enabled plus an Endpoint; the contract and both sandbox registration paths are unit-tested.

Verification

  • uv run ruff check — clean
  • uv run mypy src — clean (strict)
  • uv run pytest — 87 passed, 1 skipped (replay-safety, needs the temporal CLI), 8 deselected (integration)

Out of scope

  • No changes to the Activity implementations, YouPlugin, or error mapping.
  • No pyproject.toml dependency changes (deliberately deferred, see limits).

@tyler5673 tyler5673 changed the title feat: Nexus Service support (workflow-backed Operations) spike: Nexus Service support (workflow-backed Operations) Aug 17, 2026
@tyler5673
tyler5673 force-pushed the feat/nexus-service branch 5 times, most recently from 6dd3504 to 503ba91 Compare August 21, 2026 20:54
tyler5673 and others added 2 commits August 21, 2026 14:54
Rebased onto main with PR #15 (SDK 3.1.2 + attribution header) and
PR #16 (extraction parameter) merged. Squashes the nexus spike into
one clean commit.

- YouDotComService exposes all six Activities as async Nexus Operations
- contract.py holds the Nexus contract with SDK response models
- workflows.py ships six thin backing Workflows with per-Activity ceilings
- Idempotency key support for deduplication of Nexus StartOperation retries
- Unit tests covering contract, handler, and sandbox registration
- Integration tests for live Nexus round-trip (gated behind -m integration)

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…orrect timeout_s comment

- contract.py, nexus.py: SDK 3.1.2 shipped lazy imports (PEP 562), so the
  imports_passed_through() wrapper is belt-and-braces rather than load-bearing.
  Updated comments that referenced DX-776 as future work.
- workflows.py: comment claimed the Activity forwards timeout_s untouched
  but activities.py substitutes 120s when timeout_s is None, preventing the
  SDK effort-based deadline derivation. Corrected to describe actual behavior.
- plugin.py: re-added annotated_types to _PASSTHROUGH_MODULES, eliminating
  13 UserWarning messages about late import under the workflow sandbox.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
The Activity now forwards timeout_s as-is (PR #17 merged), so the SDK
derives the effort-based deadline itself. Updated the comment that
described the old 120s substitution behavior.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
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.

1 participant