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

on:
pull_request:
push:
branches: [ main ]
# release.yml runs this whole workflow as its gate.
workflow_call:
# Nightly, so drift between this gem and the framework it extends surfaces
# without anyone having to push.
schedule:
- cron: "0 7 * * *"

jobs:
test:
name: Ruby ${{ matrix.ruby }}
runs-on: ubuntu-latest
env:
BUNDLE_JOBS: 4
BUNDLE_RETRY: 3
CI: true
strategy:
fail-fast: false
matrix:
# The gemspec claims >= 3.0, but Gemfile.lock's `BUNDLED WITH 2.7.2`
# cannot install below 3.2 — so 3.2 is the floor this can actually
# prove. Either raise required_ruby_version to match, or pin an
# older bundler and add the earlier versions back.
ruby: [ "3.2", "3.3", "3.4" ]
steps:
- uses: actions/checkout@v6

- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true

- name: Run tests
run: bundle exec rake test

# This gem's own suite runs against mocks — deliberately, so it stays fast
# and dependency-free — which means it can pass while the concerns no
# longer compose with the framework they extend. activeagent carries a
# dummy Rails app and a cross-repo suite for exactly that; run it here
# against this working tree.
#
# SOLID_AGENT_STRICT=1: a test that would skip because the resolved gem
# lacks an API fails instead. Here the resolved gem *is* this checkout, so
# a skip would mean something was removed.
integration:
name: activeagent (${{ matrix.activeagent_ref }})
runs-on: ubuntu-latest
env:
BUNDLE_JOBS: 4
BUNDLE_RETRY: 3
CI: true
RAILS_ENV: test
SOLID_AGENT_STRICT: "1"
# Placeholders, matching activeagent's own CI: its config/active_agent.yml
# reads these at boot and provider clients raise when constructed without
# one. Nothing here reaches a provider — every generation in the
# cross-repo suite goes through the mock provider — so the values are
# deliberately not secrets, and a real call would fail loudly rather than
# bill anyone.
ANTHROPIC_API_KEY: ANTHROPIC_API_KEY
OPEN_AI_API_KEY: OPEN_AI_API_KEY
OPEN_ROUTER_API_KEY: OPEN_ROUTER_API_KEY
strategy:
fail-fast: false
matrix:
# main catches breakage before the framework releases; the latest
# tag catches breakage against what users have installed.
activeagent_ref: [ main, latest-release ]
steps:
- uses: actions/checkout@v6
with:
path: solid_agent

- name: Check out activeagent
uses: actions/checkout@v6
with:
repository: activeagents/activeagent
path: activeagent
fetch-depth: 0

- name: Select the activeagent revision
working-directory: activeagent
run: |
if [ "${{ matrix.activeagent_ref }}" = "latest-release" ]; then
tag=$(git tag --list 'v*' --sort=-v:refname | head -n1)
if [ -z "$tag" ]; then
echo "No release tag found; staying on main." >> "$GITHUB_STEP_SUMMARY"
else
echo "Testing against activeagent $tag" >> "$GITHUB_STEP_SUMMARY"
git checkout --detach "$tag"
fi
fi

# The harness lives in activeagent, so revisions predating it have
# nothing to run — every release tag until one ships with it, and
# main until activeagents/activeagent#364 merges. That is a skip with
# a reason, not a failure: this job exists to report on the pairing,
# and "the other side is too old" is a legitimate report.
- name: Check the cross-repo harness is present
id: harness
run: |
if [ -f activeagent/gemfiles/solid_agent_main.gemfile ]; then
echo "present=true" >> "$GITHUB_OUTPUT"
else
echo "present=false" >> "$GITHUB_OUTPUT"
echo "activeagent (${{ matrix.activeagent_ref }}) has no gemfiles/solid_agent_main.gemfile — it predates the cross-repo suite, so there is nothing to run against it." >> "$GITHUB_STEP_SUMMARY"
fi

- name: Install system deps
if: steps.harness.outputs.present == 'true'
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends -y build-essential git libyaml-dev pkg-config

- uses: ruby/setup-ruby@v1
if: steps.harness.outputs.present == 'true'
with:
ruby-version: "3.4"
bundler-cache: true
working-directory: activeagent
env:
BUNDLE_GEMFILE: ${{ github.workspace }}/activeagent/gemfiles/solid_agent_main.gemfile
SOLID_AGENT_PATH: ${{ github.workspace }}/solid_agent

- name: Setup database
if: steps.harness.outputs.present == 'true'
working-directory: activeagent/test/dummy
env:
BUNDLE_GEMFILE: ${{ github.workspace }}/activeagent/gemfiles/solid_agent_main.gemfile
SOLID_AGENT_PATH: ${{ github.workspace }}/solid_agent
run: |
bundle exec ruby bin/rails db:create
bundle exec ruby bin/rails db:migrate

# Includes the dashboard engine's execution test: it builds an agent
# class around SolidAgent::HasContext, and is the path a keyword rename
# in this gem breaks first.
- name: Run the cross-repo suite
if: steps.harness.outputs.present == 'true'
working-directory: activeagent
env:
BUNDLE_GEMFILE: ${{ github.workspace }}/activeagent/gemfiles/solid_agent_main.gemfile
SOLID_AGENT_PATH: ${{ github.workspace }}/solid_agent
run: |
bin/test test/integration/solid_agent/*_test.rb \
actionagent/test/agent_execution_service_test.rb

- name: Report the resolved versions
if: always() && steps.harness.outputs.present == 'true'
working-directory: activeagent
env:
BUNDLE_GEMFILE: ${{ github.workspace }}/activeagent/gemfiles/solid_agent_main.gemfile
SOLID_AGENT_PATH: ${{ github.workspace }}/solid_agent
run: |
{
echo '```'
bundle list | grep -E "activeagent|actionagent|solid_agent" || true
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
98 changes: 98 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: Release

# Builds the gem, attaches it to a GitHub release, and publishes to RubyGems
# via trusted publishing (OIDC — no stored API key).
#
# Requires a trusted publisher configured on rubygems.org for solid_agent,
# pointing at this repository and this workflow file (release.yml). Until
# that exists, the push step fails with an authorization error.
#
# Mirrors activeagents/activeagent's release workflow, including the gate:
# this gem depends on activeagent and the dashboard engine depends on this
# gem, so a release that the cross-repo suite has not seen is a release that
# can break either neighbour.

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

jobs:
# The full CI suite, including the cross-repo job that runs this working
# tree against activeagent main and against its latest release.
verify:
uses: ./.github/workflows/ci.yml
secrets: inherit

build:
needs: verify
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v6
- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3"
bundler-cache: true

# A gem that installs and resolves but dies on require is the failure
# this guards against, and it is invisible without looking inside the
# archive.
- name: Build and inspect the gem
run: |
gem build solid_agent.gemspec
mkdir -p pkg && mv solid_agent-*.gem pkg/
contents=$(tar -xOf pkg/solid_agent-*.gem data.tar.gz | tar -tzf -)
for path in lib/solid_agent.rb lib/solid_agent/has_context.rb; do
echo "$contents" | grep -qx "$path" || { echo "gem is missing $path"; exit 1; }
done

- uses: actions/upload-artifact@v4
with:
name: gem
path: pkg/*.gem

- name: Attach the gem to the release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
with:
files: pkg/*.gem
generate_release_notes: true

publish:
# Tag pushes release; workflow_dispatch covers environments where tags
# can't be pushed directly. RubyGems refuses to overwrite an existing
# version, so an accidental re-run cannot double-publish.
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
needs: build
runs-on: ubuntu-latest
permissions:
id-token: write # OIDC exchange with rubygems.org
contents: read
steps:
- uses: actions/checkout@v6
- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3"
bundler-cache: true

# The action publishes no v1 tag; @main is the ref its docs pin.
- uses: rubygems/configure-rubygems-credentials@main

- name: Publish to RubyGems
run: |
gem build solid_agent.gemspec
gem_path=$(ls solid_agent-*.gem)
version=$(basename "$gem_path" .gem | sed -E 's/^solid_agent-//')

# `gem list --all` prints one line: `name (1.2.0, 1.1.0, ...)`, so
# the version is matched between its delimiters — anchoring on
# "($version" alone would only ever see the newest release.
if gem list --remote --exact --all solid_agent | tr -d ' ' |
grep -qE "[(,]${version//./\\.}[,)]"; then
echo "solid_agent $version is already on RubyGems; nothing to do."
exit 0
fi

gem push "$gem_path"
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ PATH
remote: .
specs:
solid_agent (0.2.0)
activeagent (>= 1.1.0)
activeagent (>= 1.0.0)
activemodel (>= 7.0)
activerecord (>= 7.0)
activesupport (>= 7.0)
Expand Down
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2025 Active Agents AI

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Loading