Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -483,11 +483,21 @@ jobs:
print("agentexperience.maf needs the maf extra, as it should")
else:
raise AssertionError("agentexperience.maf imported without agent-framework-core")

# Nor does the core import OpenTelemetry: the adapter needs the otel extra, and says so.
try:
import agentexperience.telemetry
except ImportError:
print("agentexperience.telemetry needs the otel extra, as it should")
else:
raise AssertionError("agentexperience.telemetry imported without opentelemetry-api")
PY
wheels=("$out"/dist/*.whl)
test "${#wheels[@]}" -eq 1 || { echo "::error::expected exactly one wheel, found ${#wheels[@]}"; exit 1; }
uv pip install --python "$out/venv/bin/python" "${wheels[0]}[maf]"
"$out/venv/bin/python" -c "import agentexperience.maf as maf; print('agentexperience.maf imports with the maf extra:', maf.use_agent_experience.__name__)"
uv pip install --python "$out/venv/bin/python" "${wheels[0]}[otel]"
"$out/venv/bin/python" -c "import agentexperience.telemetry as t; print('agentexperience.telemetry imports with the otel extra:', t.enable.__name__)"

- name: PostgreSQL ${{ matrix.pg-major }} conformance tests
if: ${{ matrix.pg-major }}
Expand Down Expand Up @@ -515,20 +525,21 @@ jobs:
fail-fast: false
matrix:
include:
# Five modules, each with its own floor: the core (.) Go 1.24; the PostgreSQL store (postgres) and the
# Six modules, each with its own floor: the core (.) Go 1.24; the PostgreSQL store (postgres) and the
# in-memory store (inmemory) 1.25 (the pgx and golang.org/x/text releases without known vulnerabilities
# need it); the Agent Framework integration (maf) 1.26, the Go SDK's own minimum; and the PostgreSQL test
# need it); the Agent Framework integration (maf) 1.26, the Go SDK's own minimum; the PostgreSQL test
# support with the store's PostgreSQL tests (postgres/pgtest) 1.26, where its container library's
# dependencies have no known vulnerabilities. Each leg runs every module its Go version can build; the
# dependencies have no known vulnerabilities; and the optional OpenTelemetry adapter (telemetry) 1.26,
# which the OpenTelemetry API's own releases need. Each leg runs every module its Go version can build; the
# PostgreSQL legs run the two modules with PostgreSQL tests (the postgres build tag).
- go-version: '1.24.x'
modules: '.'
- go-version: '1.25.x'
modules: '. postgres inmemory'
- go-version: '1.26.x'
modules: '. postgres postgres/pgtest inmemory maf'
modules: '. postgres postgres/pgtest inmemory maf telemetry'
- go-version: stable
modules: '. postgres postgres/pgtest inmemory maf'
modules: '. postgres postgres/pgtest inmemory maf telemetry'
- go-version: stable
modules: 'postgres/pgtest maf'
pg-major: 16
Expand All @@ -555,6 +566,7 @@ jobs:
go/postgres/pgtest/go.sum
go/inmemory/go.sum
go/maf/go.sum
go/telemetry/go.sum

- name: The modules' dependencies are tidy and verified
run: |
Expand Down
13 changes: 7 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ name: Release
# three languages at one version, from one verified commit. The verify job re-runs the release checks on the tagged
# commit and uploads what it verified: the six NuGet packages, and the Python sdist and wheel. After a reviewer
# approves the `nuget-release` environment, the publish job pushes exactly those NuGet files to nuget.org, then tags
# the five Go modules at the tagged commit and creates the GitHub release. After a reviewer approves the
# the six Go modules at the tagged commit and creates the GitHub release. After a reviewer approves the
# `pypi-release` environment, the publish-pypi job uploads exactly the verified Python files to PyPI.
#
# No API key or token is stored anywhere. The publish job exchanges its GitHub OIDC token for a short-lived nuget.org
Expand Down Expand Up @@ -100,11 +100,12 @@ jobs:
go/inmemory | go/postgres) echo "go" ;;
go/postgres/pgtest) echo "go go/postgres" ;;
go/maf) echo "go go/inmemory go/postgres go/postgres/pgtest" ;;
go/telemetry) echo "go go/inmemory go/maf" ;;
*) echo "::error::No sibling set for the Go module in $1" >&2; return 1 ;;
esac
}
mismatched=0
for dir in go go/inmemory go/postgres go/postgres/pgtest go/maf; do
for dir in go go/inmemory go/postgres go/postgres/pgtest go/maf go/telemetry; do
mod="$dir/go.mod"
test -f "$mod" || { echo "::error::$mod is missing"; exit 1; }
requires="$(grep -v '=>' "$mod" | sed -nE 's#^[[:space:]]*(require[[:space:]]+)?(github\.com/fabbrik/AgentExperience\.NET/go[^[:space:]]*)[[:space:]]+([^[:space:]]+).*#\2 \3#p')"
Expand Down Expand Up @@ -318,7 +319,7 @@ jobs:
working-directory: go
run: |
set -euo pipefail
for module in . inmemory postgres postgres/pgtest maf; do
for module in . inmemory postgres postgres/pgtest maf telemetry; do
(
cd "$module"
go mod verify
Expand Down Expand Up @@ -435,7 +436,7 @@ jobs:
# tag-go-modules:begin
set -euo pipefail
error="$(mktemp)"
for module in go go/inmemory go/postgres go/postgres/pgtest go/maf; do
for module in go go/inmemory go/postgres go/postgres/pgtest go/maf go/telemetry; do
tag="$module/$GO_VERSION"
if existing="$(gh api "repos/$GH_REPO/git/ref/tags/$tag" --jq '.object.type + " " + .object.sha' 2> "$error")"; then
read -r type sha <<< "$existing"
Expand All @@ -458,14 +459,14 @@ jobs:
# The Go module proxy must know every module at the new version before the release is announced. Asking for a
# version makes the proxy fetch it; a module path is case-escaped as the proxy protocol requires (each upper-case
# letter becomes ! and the letter in lower case: AgentExperience is !agent!experience). Each module gets ten
# attempts of at most 30 seconds, 30 seconds apart: at worst 9.5 minutes a module, 47.5 for all five, which the
# attempts of at most 30 seconds, 30 seconds apart: at worst 9.5 minutes a module, 57 for all six, which the
# job's 75-minute timeout covers with the push to nuget.org.
- name: Check the Go module proxy has every module
env:
GO_VERSION: ${{ needs.verify.outputs.go-version }}
run: |
set -euo pipefail
for module in go go/inmemory go/postgres go/postgres/pgtest go/maf; do
for module in go go/inmemory go/postgres go/postgres/pgtest go/maf go/telemetry; do
path="github.com/fabbrik/AgentExperience.NET/$module"
escaped="$(sed -E 's/([A-Z])/!\L\1/g' <<< "$path")"
url="https://proxy.golang.org/$escaped/@v/$GO_VERSION.info"
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ AgentExperience.NET is a **preview**. It is not production ready, and public API

## Unreleased

### Port telemetry: Python and Go emit what .NET emits (story 23.5)

- **Python and Go report the same spans and metrics as .NET** for the operations they perform: `inject`, `retrieve`,
`record_injection`, `capture.start_run`, `capture.append_attempt`, `capture.complete_run`, `verify`, `reflect`,
`finalize` and `index`, under the same two sources and meters, span names, three instruments, four dimensions and span
attributes as [the telemetry contract](docs/telemetry.md#the-python-and-go-ports), so one dashboard and one alert serve
a mixed fleet. Neither core imports OpenTelemetry: they call a small observer hook that costs a `None`/`nil` check
when nothing is registered. Python: the optional `otel` extra (`pip install "agenticexperience[otel]"`, the
OpenTelemetry API only), `agentexperience.telemetry.enable()`. Go: the new module `go/telemetry` (the OpenTelemetry API
only), `telemetry.Enable()`; it is released with the other Go modules, now six, and nothing requires it. No
OpenTelemetry SDK or exporter is a dependency, and an observer, exporter or provider that raises is swallowed.
- **A pin for the names.** A .NET test generates [`docs/telemetry-names.json`](docs/telemetry-names.json) from the .NET
definitions (`AGENTEXPERIENCE_TELEMETRY_NAMES_UPDATE=1` rewrites it) and fails on drift; the Python and Go tests load it
and fail when a name or value a port can emit is not in it. No .NET library behaviour or public API changed.
- **Go: `...Context` forms.** `StartRunContext`, `(*RunCapture).AppendAttemptContext`, `CompleteContext` and
`FinalizeContext` take the context the operation's span parents under; the existing forms are unchanged.

### Contract 1.7: the lessons view in Python and Go (story 23.4)

- **Contract 1.7** (add-only: every earlier file is unchanged; [what 1.7 adds](contract/README-1.7.md)) publishes what
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,9 @@ lesson was injected) is published as [a language-neutral contract](contract/READ
[what 1.5 adds](contract/README-1.5.md), [what 1.6 adds](contract/README-1.6.md): retrieval by meaning, with
embeddings, [what 1.7 adds](contract/README-1.7.md): the lessons view): a JSON Schema, the SQL to run, and
fixtures generated from and checked against this implementation on every build. A pure-Python core that passes
contract 1.7 (including retrieval by meaning, with an embedder you register, and a read-only lessons view), with a one-call Python Agent Framework integration, is in [`python/`](python/README.md). A lesson a .NET
contract 1.7 (including retrieval by meaning, with an embedder you register, and a read-only lessons view), with a one-call Python Agent Framework integration and optional [OpenTelemetry telemetry](docs/telemetry.md#the-python-and-go-ports), is in [`python/`](python/README.md). A lesson a .NET
agent learned is injected into a Python agent, and the other way round. A Go core and PostgreSQL store that pass contract 1.7 (including retrieval by meaning, with an embedder you register, and a read-only lessons view), with a one-call Go Agent
Framework integration, are in [`go/`](go/README.md). A lesson a .NET agent learned is injected into a Go agent, and
Framework integration and an optional OpenTelemetry module, are in [`go/`](go/README.md). A lesson a .NET agent learned is injected into a Go agent, and
the other way round. Both are published with each release, at the same version as the NuGet packages (a Python alpha,
`0.1.0aN`, for `0.1.0-preview.N`), and are previews in the same sense:

Expand Down
10 changes: 5 additions & 5 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ The same tag also releases the two ports, at the same version, from the same ver

- **Python:** the distribution `agenticexperience` (`python/`), an sdist and a wheel, on PyPI. The first release that
carries it is described under [First publish to PyPI](#first-publish-to-pypi).
- **Go:** the five modules under `go/`: `github.com/fabbrik/AgentExperience.NET/go`, `.../go/inmemory`, `.../go/postgres`, `.../go/postgres/pgtest` and `.../go/maf`. A Go module is released by a tag
- **Go:** the six modules under `go/`: `github.com/fabbrik/AgentExperience.NET/go`, `.../go/inmemory`, `.../go/postgres`, `.../go/postgres/pgtest`, `.../go/maf` and `.../go/telemetry` (the optional OpenTelemetry adapter; nothing else requires it). A Go module is released by a tag
named after its directory, `go/vX`, `go/inmemory/vX`, and so on, which the Go module proxy serves from then on.

Every release from this repository is a **preview** — `0.1.0-preview.N`, set once in `Directory.Build.props` — and
Expand Down Expand Up @@ -388,11 +388,11 @@ deployments**, select `nuget-release`, and approve. The `publish` job then:
2. exchanges the job's GitHub OIDC token for a short-lived nuget.org key through `NuGet/login`;
3. runs `dotnet nuget push "artifacts/packages/*.nupkg" --source https://api.nuget.org/v3/index.json --api-key "$NUGET_API_KEY" --skip-duplicate`, with the short-lived key from step 2,
which uploads each `.snupkg` alongside its `.nupkg`;
4. tags the five Go modules at the tagged commit, through the GitHub API with the job's own token: `go/vX`,
`go/inmemory/vX`, `go/postgres/vX`, `go/postgres/pgtest/vX` and `go/maf/vX`, where `vX` is the tag. A tag that
4. tags the six Go modules at the tagged commit, through the GitHub API with the job's own token: `go/vX`,
`go/inmemory/vX`, `go/postgres/vX`, `go/postgres/pgtest/vX`, `go/maf/vX` and `go/telemetry/vX`, where `vX` is the tag. A tag that
already points at that commit is kept; one that points anywhere else fails the job here, before the GitHub release.
It then asks `proxy.golang.org` for each module at that version, and fails if the proxy does not know one after
ten attempts, 30 seconds apart (at worst about 48 minutes for all five; the job's timeout is 75 minutes);
ten attempts, 30 seconds apart (at worst about 57 minutes for all six; the job's timeout is 75 minutes);
5. only then creates the GitHub release for the tag: a prerelease when the version has a suffix, with that
version's `CHANGELOG.md` section, the Known limits table (or "None currently.") and the Documented boundaries
table from `docs/known-limits.md` as its notes. A preview's release notes say what it does not promise.
Expand Down Expand Up @@ -551,7 +551,7 @@ partial failure: `--check-url` skips the files PyPI already has, and tags alread
echo "FAILED: python/dist does not hold agenticexperience $python; run the build above first"; false
else
uv publish --check-url https://pypi.org/simple/ "python/dist/agenticexperience-$python.tar.gz" "python/dist/agenticexperience-$python-py3-none-any.whl" \
&& for module in go go/inmemory go/postgres go/postgres/pgtest go/maf; do
&& for module in go go/inmemory go/postgres go/postgres/pgtest go/maf go/telemetry; do
tag="$module/$go"
git rev-parse -q --verify "refs/tags/$tag" > /dev/null || git tag "$tag" "$verified" || exit 1
[ "$(git rev-parse "$tag^{commit}")" = "$verified" ] || { echo "FAILED: $tag exists at another commit"; exit 1; }
Expand Down
Loading
Loading