chore(release): prepare 0.27.0b2 with the sgp-obs log-duplication fix - #521
stephen-wang24 wants to merge 5 commits into
Conversation
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
| RUN --mount=type=secret,id=codeartifact-pip-conf,required=false \ | ||
| if [ -s /run/secrets/codeartifact-pip-conf ]; then \ | ||
| export UV_DEFAULT_INDEX="$(sed -n 's#^[[:space:]]*index-url[[:space:]]*=[[:space:]]*##p' /run/secrets/codeartifact-pip-conf | head -1)"; \ | ||
| fi; \ | ||
| uv pip install --system -r requirements.txt |
There was a problem hiding this comment.
requirements.txt can choose a source package whose build code runs inside this same RUN. That code inherits the credentialed UV_DEFAULT_INDEX and can read codeartifact-pip-conf while it is mounted. Keep the secret out of dependency build processes, or install only trusted private artifacts before running project-controlled builds. The same pattern appears in the other requirements-based Dockerfile templates.
How this was verified: Project-controlled requirements run build code while the private-index secret and credentialed environment value are both available.
Knowledge Base Used: Command-line workflows
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agentex/lib/cli/templates/default/Dockerfile.j2
Line: 40-44
Comment:
`requirements.txt` can choose a source package whose build code runs inside this same `RUN`. That code inherits the credentialed `UV_DEFAULT_INDEX` and can read `codeartifact-pip-conf` while it is mounted. Keep the secret out of dependency build processes, or install only trusted private artifacts before running project-controlled builds. The same pattern appears in the other requirements-based Dockerfile templates.
**How this was verified:** Project-controlled requirements run build code while the private-index secret and credentialed environment value are both available.
**Knowledge Base Used:** [Command-line workflows](https://app.greptile.com/scale-ai/-/custom-context/knowledge-base/scaleapi/scale-agentex-python/-/docs/command-line-workflows.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| if shutdown is None: | ||
| logger.debug("sgp-obs has no shutdown(); needs 0.16.0+ to flush on exit") | ||
| return | ||
| await asyncio.to_thread(shutdown) |
There was a problem hiding this comment.
sgp_obs.shutdown does blocking exporter work, but this await has no deadline. If an exporter or collector stalls, ACP and Temporal shutdown can wait past the pod grace period. asyncio.to_thread also leaves a stalled default-executor thread to be joined at process exit. Give the whole flush a hard budget and use an exit path that cannot be held open by the worker thread.
Knowledge Base Used:
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agentex/lib/core/observability/sgp_obs_setup.py
Line: 339
Comment:
`sgp_obs.shutdown` does blocking exporter work, but this await has no deadline. If an exporter or collector stalls, ACP and Temporal shutdown can wait past the pod grace period. `asyncio.to_thread` also leaves a stalled default-executor thread to be joined at process exit. Give the whole flush a hard budget and use an exit path that cannot be held open by the worker thread.
**Knowledge Base Used:**
- [Observability](https://app.greptile.com/scale-ai/-/custom-context/knowledge-base/scaleapi/scale-agentex-python/-/docs/observability.md)
- [Tracing pipeline](https://app.greptile.com/scale-ai/-/custom-context/knowledge-base/scaleapi/scale-agentex-python/-/docs/tracing-pipeline.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| init_sgp_obs() | ||
|
|
||
| await self.start_health_check_server() |
There was a problem hiding this comment.
init_sgp_obs runs before the cleanup try. If health startup, registration, client creation, workflow checks, or Worker construction fails, the new providers never reach shutdown_sgp_obs, so buffered startup telemetry and exporter resources are left behind. Start the try immediately after initialization so every later exit drains what was created.
Knowledge Base Used:
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agentex/lib/core/temporal/workers/worker.py
Line: 237-239
Comment:
`init_sgp_obs` runs before the cleanup `try`. If health startup, registration, client creation, workflow checks, or `Worker` construction fails, the new providers never reach `shutdown_sgp_obs`, so buffered startup telemetry and exporter resources are left behind. Start the `try` immediately after initialization so every later exit drains what was created.
**Knowledge Base Used:**
- [Temporal execution](https://app.greptile.com/scale-ai/-/custom-context/knowledge-base/scaleapi/scale-agentex-python/-/docs/temporal-execution.md)
- [Observability](https://app.greptile.com/scale-ai/-/custom-context/knowledge-base/scaleapi/scale-agentex-python/-/docs/observability.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.|
https://github.com/scaleapi/scale-agentex-python/actions/runs/35035099786 published to public pypi landed https://pypi.org/project/agentex-sdk/#history |
…agentex's The hand-over added in #518 had two halves, and only one of them covered an agent's own modules. The latch in `make_logger` never looked at the logger's name, so anything created after init was fine. The sweep matched the `agentex` prefix, so a logger created BEFORE init under any other name kept its handler and went on printing a second, ungoverned copy of every record. That is not a corner case: agents call `make_logger(__name__)` from their own modules, and `project.acp` -- the module that builds the ACP server, in every scaffold -- logs at import, which is necessarily before `init_sgp_obs` runs. Measured on dbt-assistant running 0.27.0b1: 123 of 3361 log lines were the second copy, each 80 microseconds after its governed twin, carrying `name`/`request_id` but no `trace_id`, `span_id`, `source` or `agent_id`. Since it is emitted before the pipeline's filters, it also escapes the allowlist and the truncation. The SDK cannot know an agent's package name, so `make_logger` now marks each handler it attaches and the sweep takes back exactly those, on a logger of any name. Prefix matching stays for `agentex.*` itself, where every handler is ours by definition. A handler this module did not attach is still left alone -- litellm's three loggers and anything else keep what their owner set up, which is why sgp-obs warns about them rather than stripping them. Renamed `route_agentex_loggers_to_root` to `route_loggers_to_root`, since "agentex loggers" is what the bug was. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
`request_id` had exactly one writer: `CustomJSONFormatter`, on the handler
`make_logger` attaches to each module's own logger. Removing that handler is
what stops the duplicate line -- and it takes the field's only writer with it,
so without this the id would not move to the governed copy, it would disappear.
Measured on dbt-assistant: `request_id` was on 5.2% of lines, which were
exactly the ungoverned copies, against `trace_id` on 90.6%.
The ACP middleware now binds its id into sgp-obs' shared correlation context,
which the logs pipeline enriches every record from. sgp-obs can also fill that
context from its own `RequestIdMiddleware`; binding the SDK's id instead keeps
ONE generator for the value, so the id in the logs is the id
`ctx_var_request_id` gives application code and the id `x-request-id` carried
in.
Deliberately not written onto the record here. The pipeline's enrich stage runs
on a copy of the record at handler time and treats a hand-set value as
authoritative, which cannot collide with a caller's own field. Setting the
attribute up front does collide: with `request_id` already on the record, the
stdlib raises `KeyError: Attempt to overwrite 'request_id' in LogRecord` from
`logger.info(..., extra={"request_id": ...})` -- measured, not theoretical, and
an unacceptable way for telemetry to reach an agent.
Fail-open throughout, and the optional import is resolved once: Python does not
cache a failed import, so attempting one per request would re-walk sys.path for
the majority of agents that never install sgp-obs.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
0.27.0b1 is not just prepared, it is installed: the six obs-test agents pin `agentex-sdk==0.27.0b1` and dbt-assistant is running it, which is how the log duplication was measured. Leaving the two fixes above under the same version would make "0.27.0b1" name two different artifacts. Still does not publish anything. Further fixes on this branch can ride b2 while it is unpublished; once it is published, the next one bumps again. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Base branch
This targets
stephen/agentex-sdk-obs-beta-base(cut frommainata71fa670), notmainand notnext. There is no plan to merge this to the mainline yet: the sgp-obs wiring was merged tonextin #518 and reverted again in #522, and a batch of issues found while measuring it on sgp-dev is still open. This branch is where the wiring survives and gets fixed; the diff below is unchanged by the re-target.What this branch is
0.27.0b2,0.27.0-b2in the release-please manifestFixed in this update
An agent's own loggers printed every record twice
make_loggerattaches a handler to each module's own logger. sgp-obs' logs pipeline replaces the handlers on the root logger and leaves named loggers alone, so both print. #518 handled that with a sweep plus a latch — but only the latch ignored the logger's name. The sweep matched theagentexprefix, so a logger created beforeinit_sgp_obsunder any other name kept its handler.Agents call
make_logger(__name__)from their own modules, andproject.acp(the module that builds the ACP server, in every scaffold) logs at import — always before init. So every record it logged was emitted twice, 80µs apart:nameloggertrace_id/span_idsource/agent_idrequest_idThe leaf copy is emitted before the pipeline's filters, so it also escapes the allowlist and the truncation. Measured on sgp-dev: 123 of 3361 lines on dbt-assistant (3.7%), 1 line on sync-simple, 0 on all six obs-test agents.
The SDK cannot know an agent's package name, so
make_loggernow marks each handler it attaches and the sweep takes back exactly those, on a logger of any name. Theagentex.*prefix rule stays, where every handler is ours by definition. A handler this SDK did not attach is still left alone — litellm's three loggers and anything else keep what their owner set up.route_agentex_loggers_to_rootis nowroute_loggers_to_root, since "agentex loggers" is what the bug was.request_idwould have disappeared with the duplicateThe leaf handler was the only writer of
request_id(viaCustomJSONFormatter). That is why dbt-assistant showedrequest_idon 5.2% of lines againsttrace_idon 90.6% — different copies of the same records. Removing the duplicate removes the field's only writer, so the ACP middleware now binds its id into sgp-obs' shared correlation context, which the pipeline enriches every record from.Binding rather than stamping the record is deliberate, and not a style choice: with
request_idalready set on the record,logger.info(..., extra={"request_id": ...})raisesKeyError: Attempt to overwrite 'request_id' in LogRecordfrom the stdlib. Verified — an earlier draft of this fix used a record factory and broke exactly that call. The enrich stage runs on a copy of the record at handler time instead, and treats a hand-set value as authoritative.sgp-obs can also fill that context from its own
RequestIdMiddleware. Binding the SDK's id keeps one generator for the value, so the id in the logs is the idctx_var_request_idgives application code and the idx-request-idcarried in.Version
0.27.0b1is not merely prepared, it is installed: the six obs-test agents pinagentex-sdk==0.27.0b1and dbt-assistant runs it. Leaving these fixes under that version would make one version name two artifacts, so this is0.27.0b2. Further fixes can ride b2 while it is unpublished.Release scope
Still prepares, does not publish. Do not run
publish-pypi.ymluntil the public-vs-private distribution path is confirmed; if public PyPI is selected, dispatch foragentex-sdkonly. Agents pinning0.27.0b1need their pin moved before they pick this up.Validation
ruff check .— cleanpyright -p .— 0 errorspytest— 1687 passed, 1622 skippedproject.acpprinted 2 lines, now 1; theagentex.*logger printed 1 throughoutrequest_idbind/reset checked against the realsgp_obs.contextmodule, not only the test stub: bound inside the request, matchingctx_var_request_id, cleared afterNew tests: 5 in
test_logging_handover.py(an agent-named logger prints once; only our handler comes off a shared logger; a non-propagating logger of ours is left alone; the latch covers agent loggers) and 8 intests/test_request_id_correlation.py.Not fixed here
From the same sgp-dev measurement, the following are not SDK regressions and are out of scope for this branch:
aws-dev-sgp-api-key-declansecret — a raw urllib probe with no SDK in the path 401s identicallytrace_idgen_ai_client_token_usageabsent on dbt-assistantbase_urlis not official OpenAIGreptile's four findings on the b1 commit are also still open: the Dockerfile template's private-index secret, the unbounded shutdown flush, sync LiteLLM calls skipping metrics, and worker startup failures skipping cleanup.
🤖 Generated with Claude Code
This PR is not safe to merge until the private-index token is isolated and observability shutdown is bounded.
Fix with agent prompt
Summary
Agentex now supports optional
sgp-obsobservability and private package installs while preparing both packages for the0.27.0b1beta release. The observability setup stays inactive unless an agent installs the private package and enables the matching environment settings.Diagram
sequenceDiagram participant Build as Agent image build participant Broker as Package broker participant UV as uv install participant ACP as ACP server participant Worker as Temporal worker participant Obs as sgp-obs participant Backend as Telemetry backend Broker-->>Build: Mount private index secret Build->>UV: Install project dependencies UV->>UV: Run selected package build code Build->>ACP: Start server ACP->>Obs: init_sgp_obs(app) Build->>Worker: Start worker process Worker->>Obs: init_sgp_obs() ACP->>Worker: Dispatch Temporal work Worker->>Obs: Record model and runtime telemetry Obs->>Backend: Export signals par ACP shutdown ACP->>ACP: Drain async and sync spans ACP->>Obs: shutdown_sgp_obs() and Worker shutdown Worker->>Worker: Drain sync spans Worker->>Obs: shutdown_sgp_obs() endReviews (1) · Last reviewed commit: "chore(release): prepare 0.27.0b1"