You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(stlc): restore the custom-code tree a stale seal anchor reverted
A build on 2026-09-21 (567abff "Build SDK") removed 5,224 lines across
105 files from this trunk -- 14 source files and 10 test files deleted
outright, including lib/utils/metadata_filters.py,
lib/core/observability/sgp_obs_setup.py, lib/core/temporal/logging.py and
lib/core/adapters/llm/_genai_metrics.py. 95 of the 96 changed paths were
under the hand-written src/agentex/lib/ and tests/ trees.
An stlc seal replays the content diff between its `base` and `integrated`
anchors, so anything the trunk gained past `integrated` is overwritten
rather than merely skipped. The tracking file in effect named an
`integrated` commit dated six days earlier that lived on a side branch,
not on this trunk -- and the back-sync that had just brought the custom
code here was not an ancestor of it. The replay therefore restored a tree
predating the custom code entirely, and every command reported success.
This restores the affected paths from the production trunk, which kept
all 24 files and is the authoritative copy. Verified: the diff against
production for src/agentex/lib/, tests/, adk/README.md and
adk/pyproject.toml is now empty.
Deliberately NOT restored, because this trunk is correct and production
is stale or the file does not apply:
src/agentex/_client.py production still defaults to localhost; this
trunk carries https://agentex.sgp.scale.com,
which is what stainless.yml configures
.stats.yml the SaaS-only spec/config hashes are gone by
design under self-hosted codegen
.github/** this trunk's own CI work
release-please-config.json
Nothing shipped from the damage: both agentex-sdk-v0.28.1 and
agentex-client-v0.28.1 resolve to the production trunk with the custom
tree intact. It surfaced only because the promote gate refused to carry
the deletion into production.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: adk/README.md
+20Lines changed: 20 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -27,6 +27,26 @@ This automatically pulls in [`agentex-client`](../) (the slim Stainless-generate
27
27
28
28
The two packages contribute disjoint files to the `agentex.*` namespace — `agentex/lib/*` ships only from `agentex-sdk`.
29
29
30
+
## Workflow logging
31
+
32
+
Use the workflow logger in Temporal workflow code:
33
+
34
+
```python
35
+
from agentex.lib.core.temporal.logging import make_workflow_logger
36
+
37
+
logger = make_workflow_logger(__name__)
38
+
```
39
+
40
+
It suppresses logs while Temporal replays recorded history and adds top-level
41
+
`workflow_id` and `run_id` fields during workflow execution. It preserves the
42
+
message, caller fields, and exception details. Outside workflows, including in
43
+
activities, it behaves like the ordinary SDK logger.
44
+
45
+
New Temporal templates use this helper. Existing agents must replace their own
46
+
workflow loggers to get the same behavior. This does not create trace context or
47
+
add trace IDs to workflows that lack it. Temporal's worker diagnostics still report
48
+
replay failures.
49
+
30
50
## Repo layout
31
51
32
52
This package is hand-authored and lives at `adk/` inside [scaleapi/scale-agentex-python](https://github.com/scaleapi/scale-agentex-python). Stainless codegen never touches `adk/**` — it's outside the generated surface. The sibling `agentex-client` package lives at the repo root and IS Stainless-generated.
The mount is `required=false` and guarded by `[ -s ... ]`, so with no secret injected the build is
14
+
byte-identical to one without any of this. That covers every local build, every CI build, and every
15
+
agent that never opts in. An empty secret file is skipped too.
16
+
17
+
## Opting in
18
+
19
+
Add the index to the agent's `pyproject.toml`:
20
+
21
+
```toml
22
+
[[tool.uv.index]]
23
+
name = "scale-pypi"
24
+
url = "<the scale-customer-pypi URL>"
25
+
```
26
+
27
+
**No `default = true`, deliberately.** An earlier revision of this snippet had it, which was
28
+
misleading in both directions. It would not survive the build — the Dockerfiles export
29
+
`UV_INDEX`, which binds the mirror as a *named* index ahead of public PyPI rather than
30
+
replacing it as the default, and a name rebound that way does not carry the project entry's
31
+
default flag. And it is not the behaviour we want anyway: the mirror exists to supply the
32
+
Scale-internal packages that are not on public PyPI, not to become the sole source for every
33
+
dependency.
34
+
35
+
So resolution is **mirror first, public PyPI as fallback**. `sgp-obs` can only come from the
36
+
mirror, because it exists nowhere else. An ordinary dependency the mirror happens not to carry
37
+
still resolves from PyPI instead of failing the build, which is what keeps a scaffolded agent
38
+
building when the mirror is incomplete or unreachable.
39
+
40
+
The name must be exactly `scale-pypi`. uv applies `UV_INDEX_SCALE_PYPI_USERNAME` /
41
+
`UV_INDEX_SCALE_PYPI_PASSWORD` to the index of that name, so renaming it makes the credentials
42
+
silently stop applying. Setting `UV_INDEX_URL` instead does not authenticate a *named* index at
43
+
all, and the resolve fails with a 401.
44
+
45
+
## Three things that are easy to get wrong
46
+
47
+
**The token arrives percent-encoded.** The buildspec URL-encodes it to embed it in the pip config's
48
+
URL userinfo, so a token containing `+`, `/` or `=` arrives as `%2B`, `%2F`, `%3D`. The `uv sync`
49
+
templates decode it before exporting it as a password. Passing it through still-encoded sends a
50
+
different string and the resolve 401s.
51
+
52
+
**The credential must not follow project-controlled configuration.** uv binds credentials by index
53
+
*name*, and the name-to-URL mapping would otherwise come from the agent's own `pyproject.toml` — so a
54
+
project that pointed `scale-pypi` at another host would receive the token. Verified against a local
55
+
server: the rogue host receives `Authorization: Basic aws:<token>` and the real index is never
56
+
contacted. The templates therefore export `UV_INDEX` to re-bind the name to the URL the *broker*
57
+
supplied, which overrides whatever the project declared. With that in place the rogue host is never
58
+
contacted. The pinned URL carries no userinfo; the token still travels only in
59
+
`UV_INDEX_SCALE_PYPI_PASSWORD`.
60
+
61
+
The case this defends is not a malicious agent author — they also write the Dockerfile and could read
62
+
the mounted secret directly. It is a *contributed* change to a project file, where a one-line URL edit
63
+
is far less conspicuous in review than an exfiltration command in a Dockerfile.
64
+
65
+
**The two template variants work differently, deliberately.**
66
+
67
+
| Template | Install step | How the credential is supplied |
68
+
| --- | --- | --- |
69
+
|`Dockerfile-uv.j2`|`uv sync` against the agent's `pyproject.toml`| Named index `scale-pypi`, pinned via `UV_INDEX`, token decoded into `UV_INDEX_SCALE_PYPI_PASSWORD`|
70
+
|`Dockerfile.j2`|`uv pip install -r requirements.txt`| No pyproject is present, so there is no named index to bind to. The credentialed URL is used directly via `UV_DEFAULT_INDEX`|
71
+
72
+
The `requirements.txt` variant does **not** decode the token, and that is the point: it stays inside
73
+
the URL, already encoded for exactly that use. Decoding it there would corrupt it. It is also not
74
+
exposed to the redirection problem above, because the URL comes wholly from the injected secret.
0 commit comments