Skip to content

Implementation history — for Temporal AI Partner technical review - #6

Draft
tyler5673 wants to merge 37 commits into
review/full-implementation-basefrom
main
Draft

Implementation history — for Temporal AI Partner technical review#6
tyler5673 wants to merge 37 commits into
review/full-implementation-basefrom
main

Conversation

@tyler5673

@tyler5673 tyler5673 commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

This PR isn't meant to be merged — it's just here so Temporal's team has something to leave inline comments on for our AI Partner submission (https://temporal.io/partners/ai asks for a PR link so they can review against the Integrator's Guide).

Most of this repo was built via direct commits to main rather than through PRs, so there wasn't an existing one that covered the whole implementation. This one diffs everything built on top of the initial scaffold — config, error mapping, the SDK 3.0.0 retarget, CI, docs, and various fixes.

One thing to flag: the very first commit (385d201, the original scaffold with the initial activities, YouPlugin, and error mapping) can't show up in this diff. GitHub always computes a PR's diff from the merge-base of the two branches, and 385d201 is main's actual root commit — there's nothing before it to diff against. Getting it into this same PR would mean rewriting main's history, which would change the hash of every commit after it, including the one our published v1.0.0 release/PyPI package is tied to. Not worth it.

So if you want eyes on that original commit too, it's viewable (and commentable) on its own here: 385d201 — since it's a root commit with no parent, GitHub shows its whole tree as one diff there.

Between that commit and this PR, that's the full implementation.

Related: the docs listing for this integration is at temporalio/documentation#5084.

tyler5673 and others added 29 commits July 7, 2026 13:37
- export set_config from __init__ for programmatic config override
- remove wrapper functions in test_activities, import activities directly
- remove non-asserting test_contents_activity_default_markdown
- remove duplicate test_you_activities_matches_plugin_activities
- remove redundant @pytest.mark.asyncio (asyncio_mode=auto handles it)

34 tests passing, ruff + mypy clean.
Replace manual os.environ try/finally with monkeypatch fixture for
automatic cleanup. Matches the pattern already used in test_activities.
- README with quickstart (plugin + manual paths), activity table,
  error handling, security notes, and example usage
- CONTRIBUTING with dev setup, checks, commit conventions, and
  guidance for adding new activities
- add readme = README.md to pyproject.toml
- test_global_override_wins_over_env: set_config() programmatic override
  beats YDC_API_KEY env via the production _cfg() read path
- test_typed_internal_server_errors_passthrough: Search/Research/Contents
  InternalServerError fall through to retryable (they extend YouError
  directly, not YouDefaultError)

38 tests passing, ruff + mypy clean.
Previously to_temporal_error was only tested in isolation via
test_errors.py. No test verified that an SDK error raised inside
an activity's try/except block was correctly caught and mapped.

Added 4 tests covering:
- search activity: SDK auth error (401) -> YouAuthError non-retryable
- search activity: SDK quota error (402) -> YouQuotaExhausted non-retryable
- research activity: SDK auth error (401) -> YouAuthError non-retryable
- contents activity: SDK auth error (401) -> YouAuthError non-retryable

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…sts, codeowners

Cross-referenced with temporalio/sdk-python, langchain-youdotcom, and
n8n-nodes-youdotcom to align this repo with established OSS patterns.

Changes:
- pyproject.toml: add [project.urls], classifiers, keywords (matches
  temporalio/sdk-python and langchain-youdotcom)
- CHANGELOG.md: Keep-a-Changelog format (matches temporalio/sdk-python)
- publish.yml: upgrade from tag-triggered to workflow_dispatch with
  version bumping, prerelease support, and GitHub Release creation
  (matches langchain-youdotcom and n8n-nodes-youdotcom)
- ci.yml: add workflow_dispatch integration test job with YDC_API_KEY
  secret and Temporal CLI install (matches langchain-youdotcom)
- .github/CODEOWNERS: auto-assign dx team for PR review (matches dx-toolkit)

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Add call_args assertions proving every SearchInput/ResearchInput/ContentsInput
field reaches the correct SDK method with the right enum conversion, plus
client-construction tests for retry_config/timeout_ms/server_url kwargs.
Move the end-to-end demo into the repo as tests/test_integration.py, gated by
an integration marker and skipped without YDC_API_KEY, so the CI integration
job has real tests to run.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
The integration tests self-skip without YDC_API_KEY and we're not adding
a key to a public repo, so the job was dead weight. The integration test
file stays for local runs (pytest -m integration).

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
https://you.com/platform/api-keys is a sign-in wall; https://you.com/platform
is the public API platform page linked from docs and pricing.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
- Replace sign-in-wall https://you.com/platform/api-keys with public
  https://you.com/platform in README and QUOTA_CTA.
- Soften quota message to "Check your usage and plan options".

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…dpoint, fix stale docs

- examples/run_worker.py: remove unnecessary YouPlugin() from Client.connect()
  (SimplePlugin only provides activities and workflow runner, client doesn't need it)
- _errors.py: map 422 from YouDefaultError to YouValidationError non-retryable
  (previously only the typed UnprocessableEntityError from research was caught;
  search/contents 422 would have passthrough as retryable)
- tests/test_errors.py: add test for 422 via YouDefaultError
- CHANGELOG.md: fix test count (42 after adding the new 422 test)
- CONTRIBUTING.md: clarify integration tests are not run in CI, only locally

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Replace the SDK's default YDCUserAgentOverrideHook with a custom
_TemporalUserAgentHook that sets User-Agent: youdotcom-temporal/0.1.0
on every request. This matches the pattern used by langchain-youdotcom
and n8n-nodes-youdotcom for integration usage tracking.

The SDK's hook unconditionally overrides User-Agent on every request,
so swapping the hook after SDK construction is the reliable way to
set a custom user-agent. Tests cover the hook behavior, header
setting, and hook replacement.
Remove the _TemporalUserAgentHook and _patch_user_agent workaround.
Instead, set client.sdk_configuration.user_agent directly after SDK
construction. This is the proper approach that will work once the
SDK's YDCUserAgentOverrideHook is fixed to respect custom user_agent
values (tracked in sdk-spec-drift-audit memory log).

Also make __version__ dynamic via importlib.metadata instead of
hardcoding 0.1.0. The user-agent string now derives from the installed
package version automatically.
Fix error class names (SearchUnauthorizedError → UnauthorizedResponseError,
etc.), add missing search/contents/research params, add three new activities
(Answer, Finance Research, Background Research), bump youdotcom to >=3.0.0,<4.

Changes:
- _errors.py: replace removed search-specific error classes with generic
  ones (UnauthorizedResponseError, ForbiddenResponseError,
  UnprocessableEntityResponseError, InternalServerErrorResponse), add
  PaymentRequiredResponseError for 402, add finance research and background
  research task error classes to auth/validation/500 tuples
- models.py: add offset/include_domains/exclude_domains/boost_domains/
  crawl_timeout to SearchInput, max_age to ContentsInput, background/
  source_control/output_schema to ResearchInput, new AnswerInput and
  FinanceResearchInput dataclasses
- activities.py: use you.search_async()/you.contents_async() direct methods,
  pass through all new params, add youdotcom_answer (you.answer_async),
  youdotcom_finance_research (you.finance_research_async),
  youdotcom_research_background (research_helpers.research_and_wait_async),
  fix research effort validation to include frontier
- __init__.py: export new activities and input types
- pyproject.toml: bump youdotcom from >=2.3.0,<3 to >=3.0.0,<4
- tests: 69 tests pass (ruff + mypy clean), covers all 6 activities, new
  params, error mapping for each endpoint
- README: document all 6 activities with input field tables

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
- Add 8 integration tests covering all 6 activities against the real
  You.com API + local Temporal server (all pass in 69s)
- Add timeout_s field to ResearchInput (used only by
  youdotcom_research_background): controls how long research_and_wait_async
  waits for SSE streaming before falling back to polling. Defaults to
  120s in the activity; use 14400 for frontier tasks
- Update unit tests: verify timeout_s passthrough + custom timeout test
- Update README: document timeout_s parameter

Integration test coverage:
  test_integration_search — real search, verify web results
  test_integration_search_with_domain_filters — include_domains filter
  test_integration_answer — real answer, verify citations
  test_integration_research — real research (lite), verify output
  test_integration_contents — real contents, verify markdown
  test_integration_finance_research — real finance research (deep)
  test_integration_research_background — background research with polling
  test_plugin_workflow_against_local_server — full Temporal workflow path

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
After [DX-695](https://linear.app/you-inc/issue/DX-695) closed the SDK 3.0.0
retarget gap, audit the diff against the Temporal contrib plugin patterns
and the SDK's own conventions, then fill in the small plug-fixing gaps:

- Ship a py.typed marker (and add it to the hatch wheel target) so
  downstream consumers get type checking on this plugin's public API.
- Add a [dependency-groups] entry mirroring [project.optional-dependencies]
  to unlock 'pip install --group dev -e .' in CI alongside uv's --all-extras.
- Refresh the pyproject description to mention Answer + Finance Research
  (previously only listed search/research/contents).
- Add examples/hello_background_research_workflow.py and a launcher. The
  timeout_s vs start_to_close_timeout relationship is non-obvious and we
  hit the 600s SSE timeout during integration tests; the example makes it
  explicit for the frontier-effort case.
- examples/run_workflow.py now generates a unique workflow id per
  invocation and sets an explicit id_reuse_policy so re-runs of the search
  example don't trip the default ALLOW_DUPLICATE_FAILED_ONLY behaviour.
- Flesh out CHANGELOG.md [Unreleased] with Added/Changed/Fixed subsections
  covering the full SDK 3.0.0 retarget.

Verified: ruff, mypy (7 files), 70 unit tests pass, wheel builds with
'youdotcom_temporal/py.typed' shipped, both example workflows import.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…rker serves both examples

PR review caught that the README example commands reuse the same worker
for run_workflow.py and run_background_research_workflow.py, but
run_worker.py only registered HelloSearch. Running the background example
against the worker would fail with WorkflowNotRegisteredError. Register
both workflows; the launchers stay separate so each example still has its
own command for copy-pasting.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…cy-groups] only

Matches the SDK's own convention: [dependency-groups] is the single source
of truth for dev tooling. [project.optional-dependencies] was a copy that
would drift when one list was updated without the other. Updated README
dev setup from 'uv sync --all-extras' to 'uv sync --group dev'.

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

Retarget Temporal Plugin to SDK 3.0.0
…opping [project.optional-dependencies]

The previous commit removed [project.optional-dependencies] in favor of
[dependency-groups], but left three references to 'uv sync --all-extras'
in ci.yml, publish.yml, and CONTRIBUTING.md. With no extras defined,
--all-extras is a no-op and dev tools (ruff, mypy, pytest) would not be
installed in CI.

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

Staff-engineer review findings:

1. YouConfig.timeout_seconds default increased from 30s to 300s. The
   previous 30s default would cause httpx.ReadTimeout for deep/exhaustive
   inline research calls (60-300s). Search/answer/contents complete in
   <10s so the extra headroom is harmless; Temporal's start_to_close_timeout
   is the real wall-clock ceiling.

2. httpx.TimeoutException now mapped to ApplicationError(type=
   'YouTimeoutError', non_retryable=False) instead of propagating as a
   raw httpx exception. Gives users a clean error type in the Temporal UI
   and lets Temporal retry with backoff.

3. README 'Plugin path' example no longer shows plugins=[YouPlugin()]
   on Client.connect — only the worker needs the plugin. run_worker.py
   already did this correctly; the README was inconsistent.

4. README error handling table now includes the YouTimeoutError row and
   a note about the 300s default.

5. CHANGELOG [Unreleased] updated with all post-review fixes: timeout
   increase, httpx timeout mapping, run_worker fix, dependency-group
   dedup, CI fix, README cleanup.

Tests: 72 unit tests pass (added test_client_default_timeout_is_300s
and test_http_timeout_maps_to_retryable_error). ruff + mypy clean.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…dabot, droid-review, filterwarnings

Mirrors the You.com Python SDK repo's CI and packaging conventions:

- CI: add concurrency cancellation (cancel-in-progress: true) to save CI
  minutes when a new push supersedes an in-progress run.
- CI: add build-check job that builds the wheel and verifies py.typed is
  shipped — catches packaging regressions before merge.
- Dependabot: weekly updates for uv dependencies and GitHub Actions,
  grouped by dev/runtime. Prevents dependency staleness.
- Droid auto-review: automated PR review workflow (same as SDK repo).
  Skips external contributors; manual dispatch available for forks.
- pyproject.toml: PEP 639 license declaration (license = 'MIT' +
  license-files = ['LICENSE']) replacing the old-style table form.
- pytest: filterwarnings for ResourceWarning and
  PytestUnraisableExceptionWarning — catches leaked httpx transports
  during test runs, same enforcement as the SDK test suite.

Verified: ruff, mypy, 72 unit tests pass (filterwarnings active, no
resource leaks), wheel builds with new license format.

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

Post-merge fixes: timeout default, httpx error mapping, CI alignment
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 4 to 7.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](astral-sh/setup-uv@v4...v7)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
- Version bumped from 0.1.0 to 1.0.0
- Classifier: Development Status :: 5 - Production/Stable
- CHANGELOG [Unreleased] → [1.0.0] — 2026-08-07

Verified: 43/43 README + example checks pass against production You.com API
and local Temporal server. Published to Test PyPI, install verified.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
@tyler5673
tyler5673 marked this pull request as draft August 11, 2026 00:19
tyler5673 and others added 8 commits August 11, 2026 10:31
- Add tests/test_replay_safety.py: run a workflow with a mocked SDK,
  record one call, replay the history, assert the SDK is not called
  again (the 'testing plugins for side effects' property from the
  Temporal AI partner review standards).
- Add activity summary to the example workflows for Temporal UI
  debuggability.
…#10)

* fix: resolve package exports lazily so Workflow files can import them

youdotcom_temporal/__init__.py imported the Activity layer eagerly, which pulls
in the You.com SDK and urllib.request. The workflow sandbox rejects
urllib.request, so importing anything from this package inside a Workflow file
failed at Worker construction with RestrictedWorkflowAccessError.

Wrapping the import in workflow.unsafe.imports_passed_through() could not fix
it: Python imports a parent package before the submodule body runs, so the
passthrough block never got the chance to cover the parent's eager import. In
practice a Worker only started if YouPlugin was also passed, because the
plugin's sandbox passthrough list happened to cover the SDK -- an accidental
coupling that nothing documented.

Resolve the public names through a module __getattr__ (PEP 562) instead, so the
SDK loads on first attribute access rather than at import time. Public imports
are unchanged.

Note the boundary: importing an Activity *function* still loads the SDK on any
import scheme, since that is the module the function lives in. A Workflow that
wants to stay SDK-free refers to its Activities by name, which is what the new
test workflow does.

Verified against SandboxedWorkflowRunner.prepare_workflow, the path
Worker.__init__ takes for every registered Workflow. Tests cover the sandbox
registration path, the no-SDK-on-import invariant (in a subprocess, since the
test session imports the SDK elsewhere), and that every public name still
resolves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test: run the Temporal-server tests in CI, and fix the one that never could

CI never installed the temporal CLI, so test_replay_safety was skipped on all
four Python versions on every run. An always-skipped test is indistinguishable
from one that does not exist, and that gap is a large part of why the sandbox
bug in this PR reached v1.0.0.

Installing the CLI surfaced that test_replay_safety could never have passed
anyway. Its Workflow was defined in the test module, and the sandbox re-imports
the module a Workflow is defined in -- which re-executed the module-level
shutil.which("temporal") in the file's own skipif marker, something the sandbox
blocks. The test only ever "passed" by being skipped. Moving the Workflow into
its own module fixes it; it now runs and passes.

Also adds an end-to-end test for this PR's fix: a real dev server, a real
Worker, a Workflow that imports the package at module scope, and no YouPlugin,
so nothing supplies a sandbox passthrough. Confirmed it fails on main with
"Failed validating workflow" and a restricted urllib.request, and passes here.
prepare_workflow exercises the right code path but only a real Worker proves it.

conftest.py puts the tests directory on sys.path so the helper Workflow modules
import by name. Workflows have to live outside the test module for the reason
above, and pytest's rootdir insertion did not reliably cover them.

Suite goes from 76 passed / 1 skipped to 78 passed / 0 skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bump the package version for the 1.0.1 patch release, which ships the lazy
package-export fix from #10.

Also regenerates uv.lock, which had drifted: it still recorded version 0.1.0
and carried the [project.optional-dependencies] dev block that v1.0.0 removed
when dev tooling moved to [dependency-groups].
Bumps [Factory-AI/droid-action](https://github.com/factory-ai/droid-action) from 5 to 6.
- [Release notes](https://github.com/factory-ai/droid-action/releases)
- [Commits](Factory-AI/droid-action@v5...v6)

---
updated-dependencies:
- dependency-name: Factory-AI/droid-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…ith 2 updates (#12)

Bumps the dev-dependencies group with 2 updates in the / directory: [mypy](https://github.com/python/mypy) and [ruff](https://github.com/astral-sh/ruff).


Updates `mypy` from 2.3.0 to 2.3.1
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](python/mypy@v2.3.0...v2.3.1)

Updates `ruff` from 0.16.1 to 0.16.3
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](astral-sh/ruff@0.16.1...0.16.3)

---
updated-dependencies:
- dependency-name: mypy
  dependency-version: 2.3.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: ruff
  dependency-version: 0.16.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…1.2 (#15)

Replace the post-construction `user_agent` mutation with `app_name` /
`app_version` constructor kwargs so the SDK's `X-Client-Info` header
emits `client=youdotcom-temporal/<version>` on every outbound request.
The SDK's own `user-agent` stays as `youdotcom-python-sdk/<v>`.

- Bump `youdotcom>=3.0.0,<4` to `>=3.1.2,<4`
- Drop `_USER_AGENT` constant and `sdk_configuration.user_agent` mutation
- Update `tests/test_client.py` to assert the new constructor kwargs
- Regenerate `uv.lock`

Closes DX-784

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Add `extraction: dict[str, Any] | None = None` to `SearchInput`,
mirroring the SDK's `extraction` object (shipped in youdotcom 3.1.0).
When set, it takes priority over `livecrawl` / `livecrawl_formats`
and is passed to `you.search_async(extraction=...)` instead, avoiding
the SDK's `ValueError` on dual-set. The legacy fields remain accepted
for backward compatibility.

- Add `extraction` field to `SearchInput` in `models.py`
- Route `extraction` or `livecrawl`/`livecrawl_formats` in `activities.py`
- Add tests for extraction path and priority-over-livecrawl
- Update README SearchInput table with `extraction` row and deprecation notes
- Update CHANGELOG Unreleased section

Closes DX-721

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