-
Notifications
You must be signed in to change notification settings - Fork 90
feat(e2e): tier-1 cross-agent matrix harness #122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
kaghni
wants to merge
15
commits into
main
Choose a base branch
from
feat/e2e-agent-matrix
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 1 commit
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
9d0e181
feat(e2e): tier-1 cross-agent matrix harness
kaghni 12b5525
feat(e2e): auto-resolve test workspace from operator's logged-in creds
kaghni a9b3533
fix(e2e): spawn built bundle/cli.js for installers, drop tsx runtime dep
kaghni 69ccab3
docs(e2e): single-command UX + growth/CI-promotion targets
kaghni baec844
feat(e2e): all six agents + checklist-aligned case coverage
kaghni 970b3c3
Merge remote-tracking branch 'origin/main' into feat/e2e-agent-matrix
kaghni 0fcff3a
feat(e2e): case 09 — install side effects must not write broken paths
kaghni 5b0a071
feat(e2e): cases 10-12 — close remaining RELEASE_CHECKLIST gaps
kaghni 295421f
feat(e2e): auto-discover cases — drop file in cases/, runner picks it up
kaghni b69464e
fix(e2e): skipFor points report as skip, not pass
kaghni 1e6840d
feat(e2e): cases 13-18 — full-lifecycle coverage
kaghni dbad55d
fix(e2e): real-Deeplake validation pass — bundle isolation, prechecks…
kaghni e64a8d7
Merge remote-tracking branch 'origin/main' into feat/e2e-agent-matrix
kaghni 748eb6f
fix(e2e): case 06 per-run table isolation + 180s spawn budget
kaghni 5777dab
fix(ensureColumn): trust ALTER's "already exists" verdict; add new-us…
kaghni File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| name: E2E (cross-agent matrix) | ||
|
|
||
| # Manual trigger only. This workflow spawns real agent CLIs against real | ||
| # provider APIs and a dedicated Deeplake test workspace — every run costs | ||
| # real money and takes ~10 minutes. We deliberately do NOT run it on | ||
| # every PR; the source + bundle byte-checks in `npm test` keep gating | ||
| # merges. Use this workflow as a release-readiness gate by triggering it | ||
| # manually from the Actions tab against your feature branch. | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| case_filter: | ||
| description: "Only run this case id (e.g. 01-capture-smoke). Leave blank for all." | ||
| required: false | ||
| type: string | ||
| agent_filter: | ||
| description: "Only run this agent id (e.g. claude-code). Leave blank for all." | ||
| required: false | ||
| type: string | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| e2e: | ||
| name: Tier-1 cross-agent matrix | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 30 | ||
| # Gate the job on creds being present. Forks without the e2e secret | ||
| # see a clean skip in the Actions UI rather than a misleading red. | ||
| if: ${{ github.event.repository.full_name == 'activeloopai/hivemind' }} | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 22 | ||
|
|
||
| - name: Install dependencies | ||
| run: npm install | ||
|
|
||
| - name: Build bundles | ||
| # The harness drives the actual bundles for codex/cursor/hermes/pi | ||
| # (claude-code uses --plugin-dir against the source tree). Without | ||
| # build, `hivemind <agent> install` would copy stale or missing | ||
| # bundle files into the tmp HOME. | ||
| run: npm run build | ||
|
|
||
| - name: Install agent CLIs | ||
| # Each tier-1 agent CLI must be on PATH for its driver to spawn. | ||
| # We install the npm-distributed CLIs here; cursor-agent and | ||
| # hermes are typically installed via the agent vendor's own | ||
| # installer outside the npm ecosystem. If those binaries are | ||
| # not on a CI runner, their driver will fail with a clear | ||
| # "spawn error" and the matrix continues. | ||
| run: | | ||
| npm install -g @anthropic-ai/claude-code @openai/codex | ||
| # Pi ships via npm too. | ||
| npm install -g @piapp/cli || true | ||
| # cursor-agent and hermes — install via curl when available; | ||
| # if not, their points fail loudly rather than silently skip. | ||
| curl -fsSL https://cursor.com/install-cli.sh | bash -s -- --print 2>/dev/null || echo "cursor-agent install skipped" | ||
| # Hermes install would go here; install method varies by vendor. | ||
| which claude codex pi cursor-agent hermes 2>&1 || true | ||
|
|
||
| - name: Run e2e matrix | ||
| env: | ||
| HIVEMIND_E2E_CREDS_JSON: ${{ secrets.HIVEMIND_E2E_CREDS_JSON }} | ||
| ANTHROPIC_API_KEY: ${{ secrets.HIVEMIND_E2E_ANTHROPIC_API_KEY }} | ||
| OPENAI_API_KEY: ${{ secrets.HIVEMIND_E2E_OPENAI_API_KEY }} | ||
| GOOGLE_API_KEY: ${{ secrets.HIVEMIND_E2E_GOOGLE_API_KEY }} | ||
| run: | | ||
| args=() | ||
| if [ -n "${{ inputs.case_filter }}" ]; then args+=(--case "${{ inputs.case_filter }}"); fi | ||
| if [ -n "${{ inputs.agent_filter }}" ]; then args+=(--agent "${{ inputs.agent_filter }}"); fi | ||
| npm run e2e -- "${args[@]}" | ||
|
|
||
| - name: Upload summary artifact | ||
| if: always() | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: e2e-summary | ||
| path: tests/e2e/results/ | ||
| if-no-files-found: warn | ||
| retention-days: 30 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| # Cross-agent E2E matrix (tier 1) | ||
|
|
||
| This directory drives the five headless agent CLIs we support — claude-code, codex, cursor-agent, hermes, pi — through real prompts against a real Deeplake workspace, and asserts on real side effects (DB rows, hook log lines, captured stdout, inject text). It's the layer that catches plugin bugs that source + bundle tests can't, like: | ||
|
|
||
| - a hook bundle that imports correctly but throws at runtime under one agent's loader, | ||
| - a per-agent install path that drifted out of sync with the runtime expectation, | ||
| - a cross-agent inconsistency where claude-code returns the synthesized index but cursor-agent ENOENTs. | ||
|
|
||
| The matrix is **(plugin behavior × agent runtime)**. Add a new shipped behavior → add one case file → it's automatically asserted against all five agents. | ||
|
|
||
| Cursor IDE GUI inside the Snap sandbox and OpenClaw gateway live in tier 2 — separate infra, separate matrix (`tests/e2e-tier2/`, not built yet). Issues that only show up in those runtimes are flagged in the case docstring with `skipFor`. | ||
|
|
||
| ## Running it | ||
|
|
||
| ### Locally | ||
|
|
||
| ```bash | ||
| # One full pass of all cases × all agents — ~10 minutes, ~$1.50 in API | ||
| npm run e2e | ||
|
|
||
| # Single case across all agents | ||
| npm run e2e -- --case 02-cat-index-md | ||
|
|
||
| # Single agent across all cases | ||
| npm run e2e -- --agent claude-code | ||
|
|
||
| # Single point — fastest dev loop | ||
| npm run e2e -- --case 01-capture-smoke --agent claude-code | ||
|
|
||
| # Print the matrix without spawning anything | ||
| npm run e2e -- --list | ||
|
|
||
| # Leave tmp HOMEs on disk for inspection | ||
| npm run e2e -- --keep-sandbox | ||
| ``` | ||
|
|
||
| Required env vars: | ||
|
|
||
| - `HIVEMIND_E2E_CREDS_JSON` — full `credentials.json` blob for the dedicated `hivemind-e2e` workspace under the `activeloop` org. Get this from the team password manager or generate via `hivemind login` against a token that has access to the e2e workspace. | ||
| - `ANTHROPIC_API_KEY` — needed for claude-code's points (others skip cleanly). | ||
| - `OPENAI_API_KEY` — needed for codex + cursor-agent. | ||
| - `GOOGLE_API_KEY` — needed for hermes + pi. | ||
|
|
||
| A missing provider key results in a **skip** (not a failure) for that agent's points, with the reason printed inline. The exit code stays 0 unless an actually-run point fails an assertion. | ||
|
|
||
| Optional: | ||
|
|
||
| - `HIVEMIND_E2E_TABLE_SUFFIX` — appended to sessions/memory table names. Use `HIVEMIND_E2E_TABLE_SUFFIX=$(whoami)` locally so two devs running concurrently don't collide on the same row paths. | ||
|
|
||
| ### In CI | ||
|
|
||
| Trigger `.github/workflows/e2e.yml` manually from the GitHub Actions tab, optionally with the `case_filter` / `agent_filter` inputs. There is **no schedule and no PR trigger** — every run costs money and burns ~10 minutes; we run it as a release-readiness gate, not as a per-PR gate. The unit/source/bundle tests in `npm test` keep gating merges. | ||
|
|
||
| ## How a case works | ||
|
|
||
| Each file in `cases/` exports one `E2ECase` object: | ||
|
|
||
| ```ts | ||
| export const myCase: E2ECase = { | ||
| id: "05-my-behavior", | ||
| description: "what this case asserts about the plugin", | ||
| prompt: "instruct the agent to do something that exercises the hook", | ||
| // optional: seed test data the agent will retrieve | ||
| async setup(ctx) { | ||
| // ctx.creds is a configured DeeplakeApi target | ||
| // ctx.sessionId is unique to this (case, agent, runId) | ||
| }, | ||
| assertions: [ | ||
| { type: "hook-log-contains", substring: "what the hook logs when this fires" }, | ||
| { type: "stdout-contains", substring: "what the agent says when it works" }, | ||
| { | ||
| type: "select-from-db", | ||
| sql: ({ ctx, run }) => `SELECT count(*) AS n FROM "${ctx.creds.sessionsTable}" WHERE path ILIKE '%${run.sessionId}%'`, | ||
| expect: (rows) => { if (Number(rows[0].n) < 1) throw new Error("no rows"); }, | ||
| }, | ||
| ], | ||
| // optional: this case doesn't apply to these agents (rationale required) | ||
| skipFor: ["pi"], // pi doesn't ship the X bundle; tracked in #NNN | ||
| }; | ||
| ``` | ||
|
|
||
| Then register it in `matrix.ts`: | ||
|
|
||
| ```ts | ||
| import { myCase } from "./cases/05-my-behavior.js"; | ||
| export const ALL_CASES: E2ECase[] = [..., myCase]; | ||
| ``` | ||
|
|
||
| That's the entire change. The harness handles sandboxing, install, spawn, cleanup, and reporting for all five agents. | ||
|
|
||
| ## How a driver works | ||
|
|
||
| Each file in `agents/` exports one `AgentDriver` object: | ||
|
|
||
| ```ts | ||
| export const myAgentDriver: AgentDriver = { | ||
| id: "my-agent", | ||
| async install(home, repoRoot) { | ||
| // copy the bundle into <home>/<agent-path>, write any config file | ||
| }, | ||
| async run(prompt, opts) { | ||
| // spawn the real CLI with HOME=opts.home + HIVEMIND_DEBUG=1 | ||
| // forward opts.providerEnv to the spawn env | ||
| // return { stdout, stderr, exitCode, sessionId, costCents, durationMs } | ||
| }, | ||
| }; | ||
| ``` | ||
|
|
||
| Drivers are 50–80 lines each. `runProcess` in `agents/claude-code.ts` is exported and reusable — most drivers just compose the right argv + env and delegate. | ||
|
|
||
| Assertions are **not** a driver concern. Drivers don't know what the case wants; they just spawn and capture. | ||
|
|
||
| ## How session_id flows | ||
|
|
||
| 1. Harness generates a deterministic **seed** session_id `e2e-<runId>-<case>-<agent>` (see `sandbox.ts:buildSessionId`). | ||
| 2. The seed goes into the spawn so cleanup can find rows even if the agent didn't print its own session_id. | ||
| 3. The agent generates its own UUID session_id at start. Driver reads it from `hook-debug.log` via the `session=<uuid>` line every hivemind hook writes. | ||
| 4. Assertions use `run.sessionId` (the real one). | ||
| 5. Cleanup uses `run.sessionId` (or falls back to the seed if discovery failed). | ||
|
|
||
| ## How cleanup works | ||
|
|
||
| After each case: | ||
|
|
||
| 1. Runner calls `cleanupSessionRows(ctx, run.sessionId)` — DELETEs from `sessions` + `memory` where path ILIKE `%<sid>%`. | ||
| 2. The tmp HOME is rm-rf'd unless `--keep-sandbox` was passed. | ||
| 3. Cleanup failures are warned but **don't fail the case** — a leftover row is a small workspace-debris cost, not a signal we want to gate on. | ||
|
|
||
| A daily cron in the test workspace sweeps `WHERE creation_date < now() - interval '24h' AND agent ILIKE 'e2e-%'` as belt-and-suspenders against killed runs. | ||
|
|
||
| ## Why this isn't run on every PR | ||
|
|
||
| Three reasons: | ||
|
|
||
| 1. **Cost** — every run is ~$1.50 in provider API calls. PR-gating × dozens of PRs/day = real money. | ||
| 2. **Flake surface** — upstream agent CLIs change flag shapes between minor releases. A PR unrelated to e2e would gate-fail because hermes 1.4.3 renamed `--yolo`. | ||
| 3. **Wall time** — ~10 minutes vs the current 23-second `npm test`. Slows the merge loop for marginal incremental value (most regressions also surface in unit tests). | ||
|
|
||
| Once we have a week of stable nightly runs and a flake budget < 5%, we can promote to PR-gating with a path filter on `src/hooks/**` etc. (separate PR.) | ||
|
|
||
| ## What this matrix does NOT cover | ||
|
|
||
| - **OpenClaw gateway** — tier 2 (no `openclaw -p <prompt>` CLI). | ||
| - **Cursor IDE GUI inside Snap** — tier 2 (issue-class that only shows up under the Snap sandbox; needs a long-lived test VM). | ||
| - **Pure source-level logic** — tests that don't actually need an agent runtime stay as vitest unit tests in `claude-code/tests/`. Don't pad the matrix with cases the agent runtime adds no signal to. | ||
| - **Model-quality regression** — we test what the *plugin* does, not what the model says. Asserting "agent gave a good answer" is out of scope; that's a separate evaluation problem with a separate tool. | ||
|
|
||
| ## Adding tier-2 cases | ||
|
|
||
| Don't put them here. Create `tests/e2e-tier2/` with the same matrix shape (driver + case + runner). Tier 2 needs separate infrastructure (long-lived VM, Xvfb, tmux for OpenClaw) and we don't want it gating the tier-1 invocation surface. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: activeloopai/hivemind
Length of output: 93
🏁 Script executed:
Repository: activeloopai/hivemind
Length of output: 4198
Pin and verify the agent installers.
This step pulls unpinned CLI versions, making runs non-reproducible across days or re-runs. More significantly, the curl-piped installer at line 64 executes a mutable remote script from cursor.com without checksum verification—a supply-chain risk. Pin CLI versions and replace the curl installer with a verified binary or checksum-validated script.
🤖 Prompt for AI Agents