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
161 changes: 161 additions & 0 deletions .github/workflows/publish-crates.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
name: Publish crates

# Publishes agent-memory-core, then agent-memory-mcp, to crates.io when an
# exact semver tag is pushed (issue #9).
#
# Trigger: the same exact-vX.Y.Z filter as release.yml. GSD milestone tags
# (v1.0-milestone) and pre-release suffixes (v0.1.0-rc.1) do not match, and a
# fork cannot push a tag here, which is what makes `id-token: write` acceptable.
#
# ── WHY A SEPARATE FILE ────────────────────────────────────────────────────────
# release.yml runs on GitHub-hosted runners under the issue #1 exception, which
# holds only while it uses no org secret. This workflow uses none either: it
# authenticates with crates.io Trusted Publishing. The runner's GitHub OIDC token
# is exchanged for a crates.io token that is scoped to this repo and workflow and
# expires after 30 minutes, and crates-io-auth-action revokes it in its post
# step. Keeping it in a separate file keeps release.yml's policy readable and lets
# a crates.io outage fail independently of the GitHub Release.
#
# Runner: GitHub-hosted, deliberately. This repo is public and the org runner
# group enforces allows_public_repositories: false, so a self-hosted label would
# queue until cancelled. Do not change it to a self-hosted label.
#
# ── IDEMPOTENT BY DESIGN ───────────────────────────────────────────────────────
# Trusted Publishing can only be configured for a crate that already exists on
# crates.io, so the FIRST publish of each crate is done by hand from the owner's
# machine before the tag is pushed. This workflow therefore skips any crate whose
# version is already in the crates.io index. On the v0.1.0 tag both crates are
# expected to be skipped; from v0.1.1 on it publishes both. Re-running a failed
# run is safe: whatever already landed is skipped.
#
# Order: core first, then wait until the index serves the new core version,
# because agent-memory-mcp depends on agent-memory-core = "<same version>" and
# cargo resolves that dependency from the index during its verify build.
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'

permissions:
contents: read

# Never run two publishes at once. A queued run waits instead of cancelling the
# one in progress, because cancelling mid-publish can leave core published and
# mcp not.
concurrency:
group: publish-crates
cancel-in-progress: false

env:
CARGO_TERM_COLOR: always

jobs:
publish:
name: Publish to crates.io (Trusted Publishing)
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # GitHub OIDC token for the crates.io Trusted Publishing exchange
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Install Rust stable
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
with:
toolchain: stable

- name: Tag must match both crate versions
id: version
run: |
set -euo pipefail
TAG_VERSION="${GITHUB_REF_NAME#v}"
META="$(cargo metadata --format-version 1 --no-deps --locked)"
for crate in agent-memory-core agent-memory-mcp; do
v="$(jq -r --arg n "$crate" '.packages[] | select(.name == $n) | .version' <<<"$META")"
if [ "$v" != "$TAG_VERSION" ]; then
echo "::error::$crate is at '$v' but the tag is $GITHUB_REF_NAME"
exit 1
fi
echo "$crate $v matches $GITHUB_REF_NAME"
done
echo "version=$TAG_VERSION" >> "$GITHUB_OUTPUT"

# Tests before the token is minted, so the 30-minute token is not spent
# waiting on a test run. release.yml runs the full gate separately.
- name: cargo test
run: cargo test --workspace --locked

- name: Which crates still need publishing
id: plan
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
set -euo pipefail
# The sparse index cargo itself reads. Path for names of 4+ chars:
# first two chars / next two chars / name. A 404 means the crate does
# not exist yet. The file is one JSON object per version (NDJSON):
# slurp it so `-e` judges ONE boolean. A per-line `select` with `-e`
# exits 4 whenever the LAST line does not match (jq 1.6), which
# would report a published version as missing.
# Only a 404 means "crate absent". Any other failure stops the run: a
# transient error must never be read as "not published".
published() {
local name="$1" body="$RUNNER_TEMP/index-$1" code
code="$(curl -sS --retry 3 -o "$body" -w '%{http_code}' \
"https://index.crates.io/${name:0:2}/${name:2:2}/${name}")"
case "$code" in
200) jq -e -s --arg v "$VERSION" 'any(.[]; .vers == $v)' "$body" >/dev/null ;;
404) return 1 ;;
*) echo "::error::index lookup for $name returned HTTP $code"; exit 1 ;;
esac
}
for crate in agent-memory-core agent-memory-mcp; do
key="${crate//-/_}"
if published "$crate"; then
echo "$crate $VERSION is already on crates.io; skipping it"
echo "$key=false" >> "$GITHUB_OUTPUT"
else
echo "$crate $VERSION will be published"
echo "$key=true" >> "$GITHUB_OUTPUT"
fi
done

- name: Authenticate to crates.io (OIDC)
if: steps.plan.outputs.agent_memory_core == 'true' || steps.plan.outputs.agent_memory_mcp == 'true'
id: auth
uses: rust-lang/crates-io-auth-action@3268eb413133e2960fa4ae0e963ca76380c54cd9 # v1.0.6

- name: Publish agent-memory-core
if: steps.plan.outputs.agent_memory_core == 'true'
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
run: cargo publish -p agent-memory-core --locked

- name: Wait for agent-memory-core in the index
if: steps.plan.outputs.agent_memory_core == 'true' && steps.plan.outputs.agent_memory_mcp == 'true'
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
set -euo pipefail
# cargo publish already waits for the index (since cargo 1.66). This
# loop is a guard so the next publish never races the index.
for attempt in $(seq 1 30); do
if curl -fsS "https://index.crates.io/ag/en/agent-memory-core" 2>/dev/null \
| jq -e -s --arg v "$VERSION" 'any(.[]; .vers == $v)' >/dev/null; then
echo "agent-memory-core $VERSION is in the index"
exit 0
fi
echo "attempt $attempt: not in the index yet; waiting 10 s"
sleep 10
done
echo "::error::agent-memory-core $VERSION did not appear in the index within 5 minutes"
exit 1

- name: Publish agent-memory-mcp
if: steps.plan.outputs.agent_memory_mcp == 'true'
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
run: cargo publish -p agent-memory-mcp --locked
17 changes: 15 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ name: Release
#
# 1. It uses NO org secrets — only the built-in GITHUB_TOKEN, which
# softprops/action-gh-release uses implicitly. There is no signing key, no
# Sonatype or npm token, no crates.io token. The Homebrew formula in
# Sonatype or npm token, no crates.io token. crates.io publishing lives in
# the separate publish-crates.yml (Trusted Publishing over OIDC, no
# secret either). The Homebrew formula in
# UnityInFlow/homebrew-tap is updated by hand, outside this workflow. The
# self-hosted policy exists to protect secrets this pipeline does not hold.
# 2. This repo is public and the org runner group enforces
Expand Down Expand Up @@ -179,8 +181,10 @@ jobs:
"$HOME/.zig/${ZIG_DIR}/zig" version
cargo install --locked --version "${CARGO_ZIGBUILD_VERSION}" cargo-zigbuild

# The package is agent-memory-mcp (crates.io name, #9); its [[bin]] is
# still `agent-memory`, so the binary path and asset names are unchanged.
- name: Build (cargo zigbuild, cross-compiled from this Linux host)
run: cargo zigbuild --release --locked --target ${{ matrix.target }} -p agent-memory
run: cargo zigbuild --release --locked --target ${{ matrix.target }} -p agent-memory-mcp

- name: Smoke test (host-arch-aware --version; foreign-arch/darwin = presence-only)
run: |
Expand Down Expand Up @@ -327,6 +331,15 @@ jobs:
brew install unityinflow/tap/agent-memory
```

### Install via cargo

```
cargo install agent-memory-mcp --locked
```

The crate is `agent-memory-mcp`; the executable it installs is
`agent-memory`.

### Manual install

| Platform | Asset |
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/spike-cross-compile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,9 @@ jobs:
run: |
set -euo pipefail
case "${{ matrix.tool }}" in
zigbuild) cargo zigbuild --release --locked --target ${{ matrix.target }} -p agent-memory ;;
xwin) cargo xwin build --release --locked --target ${{ matrix.target }} -p agent-memory ;;
mingw) cargo build --release --locked --target ${{ matrix.target }} -p agent-memory ;;
zigbuild) cargo zigbuild --release --locked --target ${{ matrix.target }} -p agent-memory-mcp ;;
xwin) cargo xwin build --release --locked --target ${{ matrix.target }} -p agent-memory-mcp ;;
mingw) cargo build --release --locked --target ${{ matrix.target }} -p agent-memory-mcp ;;
esac

- name: Smoke test (suffix- and host-arch-aware)
Expand Down
17 changes: 10 additions & 7 deletions .planning/REQUIREMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,16 @@ Each maps to a roadmap phase. IDs continue from the v1.0 numbering (archived in

### Distribution (DIST)

- [ ] **DIST-05**: Linux musl binaries (x86_64/aarch64) build via a target-suffixed CFLAGS typedef shim (`-Du_int*_t=uint*_t`) on the existing zigbuild pipeline — removable when sqlite-vec PR #199 ships in a pinned release
- [ ] **DIST-03**: A Windows x86_64 binary is published — toolchain decided by an early spike (cargo-xwin/msvc preferred, mingw-w64/gnu fallback); artifact-presence smoke only (no Windows runner in the fleet), marked best-effort in release notes
- [ ] **DIST-06**: Product release v0.1.0 published: checksummed tarballs for all supported targets + updated Homebrew formula, with release notes covering the tag-match behavioral change
- [ ] **DIST-06**: Product release v0.1.0 published: checksummed tarballs for all supported targets + updated Homebrew formula, with release notes covering the tag-match behavioral change. *Re-scoped 2026-10-25 (issue #7): "all supported targets" = the six `release.yml` builds (four required + the best-effort musl pair); also covers the crates.io publish of `agent-memory-core` + `agent-memory-mcp` (#9) and CHANGELOG/SECURITY.md (#8).*

*DIST-05 (hard musl) and DIST-03 (Windows) moved to v0.2.0 on 2026-10-25 by the owner decision on issue #7 — see Future Requirements.*

## Future Requirements

Acknowledged but deferred — not in this milestone.

- **DIST-05** *(moved from v1.1 Phase 6 to v0.2.0, 2026-10-25, issue #7)*: Linux musl binaries (x86_64/aarch64) as a **hard** release target via a target-suffixed CFLAGS typedef shim (`-Du_int*_t=uint*_t`) on the existing zigbuild pipeline — removable when sqlite-vec PR #199 ships in a pinned release. Until then the musl legs stay best-effort, as in v0.0.1. Toolchain verdict already recorded (Phase 3 D-11).
- **DIST-03** *(moved from v1.1 Phase 6 to v0.2.0, 2026-10-25, issue #7)*: A Windows x86_64 binary is published — cargo-xwin 0.23.0 / `x86_64-pc-windows-msvc` (Phase 3 D-11 verdict; mingw-w64/gnu proven fallback); artifact-presence smoke only, best-effort label in release notes.
- **SEARCH-06**: Request-level `mode` override (`keyword | semantic | hybrid`) — cheap additive, revisit if agents need deterministic keyword testing
- **MCP-08**: LLM auto-arbitration on write (mem0 AUDN-style) — heavy, non-local
- Bi-temporal invalidation (Zep-style) — complexity not justified at local scale
Expand All @@ -58,7 +60,7 @@ Acknowledged but deferred — not in this milestone.

## Traceability

Mapped by roadmap creation 2026-07-12. Coverage: 12/12 v1.1 requirements mapped to Phases 3-6.
Mapped by roadmap creation 2026-07-12. Coverage: 12/12 v1.1 requirements mapped to Phases 3-6; re-scoped 2026-10-25 (issue #7): 10 stay in v1.1, DIST-05 and DIST-03 moved to v0.2.0.

| Requirement | Phase | Status |
|-------------|-------|--------|
Expand All @@ -71,10 +73,11 @@ Mapped by roadmap creation 2026-07-12. Coverage: 12/12 v1.1 requirements mapped
| SEARCH-05 | Phase 4 | Complete |
| DIST-04 | Phase 5 | Complete |
| INTEROP-02 | Phase 5 | Complete |
| DIST-05 | Phase 6 | Pending |
| DIST-03 | Phase 6 | Pending |
| DIST-06 | Phase 6 | Pending |
| DIST-05 | v0.2.0 (moved from Phase 6, 2026-10-25, #7) | Deferred |
| DIST-03 | v0.2.0 (moved from Phase 6, 2026-10-25, #7) | Deferred |
| DIST-06 | Phase 6 (v0.1.0 release, #7) | Pending |

---
*Requirements defined: 2026-07-12 from approved v2 backlog + 4-dimension research (research/SUMMARY.md)*
*Traceability filled: 2026-07-12 by roadmap creation (Phases 3-6)*
*Traceability updated: 2026-10-25 — Phase 6 re-scoped to the v0.1.0 release (owner decision on issue #7); DIST-05/DIST-03 → v0.2.0*
Loading
Loading