Skip to content

[Feature] Per-worktree isolated test server for parallel E2E tests #1067

Description

@cgcardona

Problem

All dispatched agents are asyncio tasks running inside the single agentception-app container. When an agent runs npm run test:e2e, its Playwright tests hit the live server on localhost:1337 — the same process that is simultaneously serving other agents' requests and managing their runs.

When two agents run E2E tests concurrently:

  • Both test suites mutate the same application state
  • One agent's test setup (seeded data, triggered dispatches, SSE subscriptions) can corrupt another agent's assertions
  • Failures are non-reproducible and hard to attribute to the correct agent's change

Proposed solution

Spin up a lightweight, isolated test server per worktree at E2E test time: a second uvicorn process bound to a dynamically allocated port, running in a clean state against a test-scoped database, reachable only for the duration of that agent's test run.

The canonical AgentCeption server on :1337 is never targeted by agent E2E tests.

Architecture

agentception-app container

  uvicorn :1337           →  live orchestrator (persistent)

  uvicorn :dynamic_port_A →  test server for issue-123 (spawned at test start, torn down after)
  uvicorn :dynamic_port_B →  test server for issue-456

Implementation steps

  1. Port registry (agentception/services/port_registry.py)

    • Async in-memory registry backed by a Postgres table (ac_worktree_ports)
    • allocate(run_id) -> int: atomically reserves an unused port in a configurable range (default 50000–60000)
    • release(run_id): marks the port free
    • Config vars: AC_WORKTREE_PORT_RANGE_START, AC_WORKTREE_PORT_RANGE_END
  2. Test server lifecycle (agentception/services/test_server.py)

    • start_test_server(run_id, port) -> asyncio.subprocess.Process: spawns uvicorn agentception.main:app --port {port} --env AC_WORKER_MODE=true
    • AC_WORKER_MODE=true disables the agent loop and the worktree reaper, leaving only the HTTP API layer for E2E target
    • stop_test_server(run_id): kills the spawned process and calls port_registry.release(run_id)
  3. E2E test integration

    • playwright.config.ts: read AC_BASE_URL from env; default to http://localhost:1337
    • The agent's worktree env gets AC_BASE_URL=http://localhost:{dynamic_port} injected at dispatch time so npm run test:e2e targets the correct test server
  4. Teardown (agentception/services/teardown.py, agentception/services/worktree_reaper.py)

    • stop_test_server + port_registry.release called in both normal teardown and reaper cleanup
  5. DB migration

    • dynamic_port: int | None column on ac_agent_runs
    • ac_worktree_ports(run_id TEXT PK, port INT UNIQUE, allocated_at TIMESTAMPTZ) table
  6. Config additions (agentception/config.py)

    • AC_WORKTREE_PORT_RANGE_START: int = 50000
    • AC_WORKTREE_PORT_RANGE_END: int = 60000
    • AC_WORKER_MODE: bool = False

Acceptance criteria

  • Two agents running npm run test:e2e concurrently each target a distinct port
  • The canonical :1337 server is not targeted by any agent E2E test
  • Test server processes are killed and ports released on teardown and reaper cleanup
  • Port exhaustion surfaces a clear error at dispatch time
  • mypy clean, zero Any, unit + integration tests

Notes

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions