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
123 changes: 123 additions & 0 deletions .github/downstream/bump-agent-core.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Template — copy into a product repository as
# .github/workflows/bump-agent-core.yml
# together with repin_agent_core.py, as
# .github/workflows/repin_agent_core.py
#
# Opens (or refreshes) a pull request moving this product's AgentCore pin to a
# newly released tag. Triggered by AgentCore's release workflow, and manually
# runnable to recover a missed or failed dispatch.
#
# Required secrets:
# AGENT_CORE_REPO_TOKEN — read access to the private ApodexAI/AgentCore, so
# `uv` can resolve the git dependency.
# BUMP_PR_TOKEN — token used to push the branch and open the PR. This must NOT
# be the default GITHUB_TOKEN: pull requests created with GITHUB_TOKEN do
# not trigger workflow runs, so this product's CI would never run against
# the bump — which is the entire point of the PR. Use a GitHub App
# installation token or a fine-grained PAT with contents:write and
# pull-requests:write on this repository.
# Both may be the same App token if it is scoped to both repositories.

name: Bump AgentCore

on:
repository_dispatch:
types: [agent-core-release]
workflow_dispatch:
inputs:
version:
description: "AgentCore tag to pin, e.g. v0.2.0"
required: true

jobs:
bump:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
token: ${{ secrets.BUMP_PR_TOKEN }}
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
python-version: "3.12"

- name: Resolve target version
run: |
set -euo pipefail
version="${{ github.event.client_payload.version || inputs.version }}"
case "$version" in
v[0-9]*) ;;
*) echo "Refusing to pin '$version': expected a v-prefixed release tag." >&2
exit 1 ;;
esac
echo "VERSION=$version" >> "$GITHUB_ENV"
echo "BRANCH=chore/agent-core-$version" >> "$GITHUB_ENV"

# The dependency is declared as ssh://git@github.com/...; rewrite that
# exact scheme so uv resolves it over HTTPS with a token instead of
# needing an SSH key on the runner.
- name: Authenticate git for the private dependency
env:
AGENT_CORE_REPO_TOKEN: ${{ secrets.AGENT_CORE_REPO_TOKEN }}
run: |
set -euo pipefail
git config --global \
url."https://x-access-token:${AGENT_CORE_REPO_TOKEN}@github.com/".insteadOf \
"ssh://git@github.com/"

# Substitute the rev in place, then relock. Do NOT use `uv add` here: it
# rewrites the standard PEP 508 direct-URL dependency into uv's
# proprietary [tool.uv.sources] table, which pip ignores — silently
# breaking any non-uv install path and adding a structural diff to every
# bump PR.
- name: Repin AgentCore
run: |
set -euo pipefail
python3 .github/workflows/repin_agent_core.py "$VERSION"
uv lock

- name: Stop if already pinned
id: diff
run: |
set -euo pipefail
if git diff --quiet -- pyproject.toml uv.lock; then
echo "Already pinned to ${VERSION}; nothing to open."
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi

- name: Open or refresh the bump pull request
if: steps.diff.outputs.changed == 'true'
env:
GH_TOKEN: ${{ secrets.BUMP_PR_TOKEN }}
run: |
set -euo pipefail
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'

# Force-push a deterministic branch so a re-dispatch of the same
# version refreshes the existing PR instead of failing.
git switch -c "$BRANCH"
git add pyproject.toml uv.lock
git commit -m "chore: bump AgentCore to ${VERSION}"
git push --force origin "$BRANCH"

if gh pr view "$BRANCH" --json number >/dev/null 2>&1; then
echo "Refreshed existing pull request for ${BRANCH}."
exit 0
fi

cat > /tmp/bump-body.md <<EOF
Moves the \`apodex-agent-core\` pin to [\`${VERSION}\`](https://github.com/ApodexAI/AgentCore/releases/tag/${VERSION}).

Release notes and the changelog entry are on that release page.

Opened automatically by AgentCore's release workflow. Merge once this
product's CI is green; do not patch shared implementation code here —
shared fixes belong in AgentCore.
EOF

gh pr create \
--head "$BRANCH" \
--title "chore: bump AgentCore to ${VERSION}" \
--body-file /tmp/bump-body.md
57 changes: 57 additions & 0 deletions .github/downstream/repin_agent_core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""Repoint this product's AgentCore pin at a released tag.

Copied into the product repository alongside the bump workflow.

Deliberately an in-place rev substitution rather than `uv add`: `uv add` rewrites
the standard PEP 508 direct-URL dependency into uv's proprietary
`[tool.uv.sources]` table, which pip and other installers ignore. That would
silently break any non-uv install path (a Dockerfile running `pip install .`, for
one) and put a structural diff in every bump pull request.
"""

from __future__ import annotations

import argparse
import re
import sys
from pathlib import Path

# Anchor on the repository path so the '@' inside 'git@github.com' is never
# mistaken for the rev separator.
PIN = re.compile(r'(AgentCore\.git@)[^"\'\s]+')


def repin(text: str, version: str) -> tuple[str, int]:
return PIN.subn(rf"\g<1>{version}", text)


def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("version", help="Release tag to pin, e.g. v0.2.0")
parser.add_argument("--file", default="pyproject.toml")
args = parser.parse_args(argv)

path = Path(args.file)
original = path.read_text(encoding="utf-8")
updated, count = repin(original, args.version)

if count != 1:
print(
f"Expected exactly one AgentCore pin in {path}, found {count}.\n"
"The dependency declaration changed shape; update this script rather "
"than letting the bump land a half-edited pin.",
file=sys.stderr,
)
return 1

if updated == original:
print(f"Already pinned to {args.version}.")
return 0

path.write_text(updated, encoding="utf-8")
print(f"Repinned to {args.version}.")
return 0


if __name__ == "__main__":
raise SystemExit(main())
20 changes: 19 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,25 @@ jobs:
with:
python-version: "3.12"
- run: uv sync --frozen --extra dev
- run: uv run ruff check agent_core tests
- run: uv run ruff check agent_core tests scripts
- run: uv run pyright agent_core
- run: uv run pytest -q
- run: uv build

# Two products pin an AgentCore revision. If published code changes without a
# version bump, both end up reporting the same version for different code and
# the installed dist-info stops identifying what is running. Enforce the bump
# at the pull request, where it is cheap to fix.
version-bump:
if: >-
github.event_name == 'pull_request' &&
!contains(github.event.pull_request.labels.*.name, 'skip-version-bump')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# The check needs the merge base, which a shallow clone does not have.
fetch-depth: 0
- run: python3 scripts/check_version_bump.py --base "$BASE_SHA"
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
73 changes: 73 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Release

on:
push:
tags: ["v*"]

permissions:
contents: write

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
python-version: "3.12"

# Fail before building anything if the tag names a version the tree does
# not declare, or if the release has no changelog entry to publish.
- name: Verify tag matches declared version
run: python3 scripts/version.py --check-tag "$TAG"
env:
TAG: ${{ github.ref_name }}

- name: Extract release notes
run: |
set -euo pipefail
version="$(python3 scripts/version.py)"
python3 scripts/changelog_section.py "$version" > release-notes.md

# The tagged tree is re-verified rather than trusting main's CI run: a tag
# can point at a commit that never went through a pull request.
- run: uv sync --frozen --extra dev
- run: uv run ruff check agent_core tests scripts
- run: uv run pyright agent_core
- run: uv run pytest -q
- run: uv build

- name: Publish GitHub Release
run: |
set -euo pipefail
gh release create "$TAG" \
--title "AgentCore ${TAG#v}" \
--notes-file release-notes.md \
dist/*.whl dist/*.tar.gz
env:
TAG: ${{ github.ref_name }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# Notify both consumers so a release always produces a visible bump PR
# rather than waiting for someone to remember. This runs after the release
# exists, and a missing token degrades to a warning: the release itself is
# already published and must not be failed by downstream plumbing.
- name: Request downstream bump PRs
env:
TAG: ${{ github.ref_name }}
GH_TOKEN: ${{ secrets.DOWNSTREAM_BUMP_TOKEN }}
run: |
set -euo pipefail
if [ -z "${GH_TOKEN:-}" ]; then
echo "::warning title=No downstream dispatch::DOWNSTREAM_BUMP_TOKEN is not set; \
no bump PRs were requested. Open them manually, or see docs/downstream-bump.md."
exit 0
fi
for repo in ApodexAI/ApodexHarness ApodexAI/FrontierAgentInternal; do
echo "Dispatching agent-core-release ${TAG} to ${repo}"
gh api "repos/${repo}/dispatches" \
--method POST \
-f event_type=agent-core-release \
-f "client_payload[version]=${TAG}" \
|| echo "::warning title=Dispatch failed::Could not notify ${repo}; open its bump PR manually."
done
66 changes: 66 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Changelog

Notable changes to `apodex-agent-core`, newest first. This file is the contract
between AgentCore and its consumers: every release entry must say what a product
has to know before upgrading. The release workflow reads the matching section as
the GitHub Release body, so a release with no entry here fails.

Versioning follows [docs/versioning.md](docs/versioning.md).

## [0.2.0] - 2026-09-03

First versioned release. Functionally this is the runtime that products have
already been consuming by commit SHA; what changes is that the revision now has
a name.

Why 0.2.0 and not 0.1.1: `0.1.0` was the bootstrap constant and was never
released or moved. Every commit from the initial foundation through the
cooldown-fallback observability work declared that same version, so `0.1.0`
identifies no particular code and is retired rather than reused.

### Published surface

The importable surface is `agent_core.*` as scoped in the README, with per-area
contracts in `docs/*-boundary.md`: messages and token estimation, context
management and spill storage, LLM runtime (binding, streaming, retry
classification, runaway recovery), the agent loop and its typed product hooks,
durable run journals, MiniDAG and registries, middleware and observers, agent-bus
coordination, skills, and scheduling.

### Consumer action

Repin from a commit SHA to the tag:

```toml
dependencies = [
"apodex-agent-core @ git+ssh://git@github.com/ApodexAI/AgentCore.git@v0.2.0",
]
```

No source changes are required: the code at `v0.2.0` is `main` as of this
release. Installed metadata now reports `0.2.0`, so `pip list` and the
`dist-info` in a product image finally identify which AgentCore is running.

Note that as of this release neither product declares AgentCore on its `main`
branch — the pin exists only on each product's in-progress migration branch
(`fix/ci-bwrap-soft-probe`, `refactor/agent-core`). The repin above applies
wherever the declaration currently lives.

### Added

- `docs/versioning.md`: version policy, what counts as a breaking change while
the public surface is still wider than `agent_core.__all__`, and the release
procedure.
- `CHANGELOG.md` (this file).
- CI now fails a pull request that changes published code without bumping
`[project].version`, which is what let 80 commits ship as `0.1.0`.
- `Release` workflow: pushing a `v*` tag re-runs the full check suite, verifies
the tag matches `[project].version`, builds the wheel and sdist, and publishes
them on a GitHub Release with these notes attached.
- Automated downstream bump PRs: a release dispatches to ApodexHarness and
FrontierAgentInternal, which repin and open a pull request for their own CI to
validate. Templates live in `.github/downstream/`; setup is
[docs/downstream-bump.md](docs/downstream-bump.md).
- Corrected the documented pin scheme to `git+ssh://git@github.com/`, which is
what both products actually declare. The credential note previously rewrote
`https://github.com/`, a no-op against an `ssh://` dependency URL.
Loading