Note
Pre-Release Software - AgentSystems is in active development. Join our Discord for updates and early access. ⭐ Star the main repository to show your support!
This is the gateway and orchestration layer for AgentSystems. See the main repository for platform overview and documentation.
The Agent Control Plane (ACP) is the HTTP gateway and core services layer that fronts every AgentSystems deployment.
- Repo:
agentsystems/agent-control-plane - Image:
ghcr.io/agentsystems/agent-control-plane:<tag>(publicly available) - Part of the multi-repository platform – see the AgentSystems docs.
| Path | Purpose |
|---|---|
cmd/gateway/ |
FastAPI gateway & reverse-proxy (modularized components) |
├── main.py |
Core FastAPI application and endpoints |
├── database.py |
PostgreSQL connection pooling and job management |
├── docker_discovery.py |
Docker container discovery and agent registration |
├── egress.py |
Egress allowlist configuration and management |
├── lifecycle.py |
Container idle timeout and lifecycle management |
├── proxy.py |
HTTP CONNECT proxy server for agent egress |
├── models.py |
Pydantic data models |
└── exceptions.py |
Common HTTP exception patterns |
Dockerfile |
Container image build instructions |
tests/ |
Unit tests for gateway functionality |
.coveragerc |
Test coverage configuration |
CHANGELOG.md |
Version history and changes |
graph LR
C((Client)) -- 18080 --> G[Gateway]
subgraph "agents-int"
G -- 8080 --> A1((hello-world-agent))
G --> A2((your-agent))
end
G --> PG[(Postgres)]
- Gateway discovers containers with labels
agent.enabled=true&agent.port=<port>. - Auth (Bearer token placeholder) → forward to agent.
- Writes an append-only audit row (hash-chained).
Endpoint details: Gateway API.
# Install the SDK
pipx install agentsystems-sdk
# Initialize a deployment
agentsystems init
# Start the platform
cd agent-platform-deployments
agentsystems up• API Gateway: http://localhost:18080
• List agents: curl http://localhost:18080/agents -H "Authorization: Bearer demo"
# Pull the public image
docker pull ghcr.io/agentsystems/agent-control-plane:latest
# Run with Docker Compose (see agent-platform-deployments repo)
docker run -d \
--name agent-control-plane \
-p 18080:8080 \
-v /var/run/docker.sock:/var/run/docker.sock \
ghcr.io/agentsystems/agent-control-plane:latestcd agent-control-plane
python -m venv .venv && source .venv/bin/activate
pip install -r requirements-dev.txt
# override port via env if desired
ACP_BIND_PORT=8088 uvicorn cmd.gateway.main:app --reload --port ${ACP_BIND_PORT:-8080}# Run all tests with coverage
pytest --cov=cmd --cov-report=term-missing
# Run specific test file
pytest tests/test_gateway.py -v# Pull the latest published image from GitHub Container Registry
docker pull ghcr.io/agentsystems/agent-control-plane:latest
# Specific version
docker pull ghcr.io/agentsystems/agent-control-plane:0.3.3# Clone and build for development
git clone https://github.com/agentsystems/agent-control-plane.git
cd agent-control-plane
docker build -t my-custom-acp:dev .If you built a custom image, update the tag in your docker-compose.yml or agentsystems-config.yml.
The gateway supports file uploads to agents and manages a shared artifacts volume.
Send multipart requests to any agent:
# Upload single file with JSON payload
curl -X POST http://localhost:18080/invoke/agent-name \
-H "Authorization: Bearer token" \
-F "[email protected]" \
-F 'json={"sync": true, "format": "csv"}'
# Upload multiple files
curl -X POST http://localhost:18080/invoke/agent-name \
-H "Authorization: Bearer token" \
-F "[email protected]" \
-F "[email protected]" \
-F 'json={"sync": true}'The gateway automatically:
- Creates thread directories:
/artifacts/{thread-id}/{in,out}/for each request - Saves uploaded files: Files go to
/artifacts/{thread-id}/in/{filename} - Sets permissions: Attempts to configure agents (UID 1001) for file access
- Configured with limits: Default 200MB upload limit (configurable via
ACP_MAX_UPLOAD_MB)
/artifacts/
├── {thread-id-1}/
│ ├── in/ # Files uploaded by client
│ │ ├── data.csv
│ │ └── config.json
│ └── out/ # Files created by agent
│ └── result.txt
└── {thread-id-2}/
├── in/
└── out/
| Var | Default | Purpose |
|---|---|---|
ACP_MAX_UPLOAD_MB |
200 |
Maximum file upload size in MB |
ACP_BIND_PORT |
8080 |
Gateway listen port inside container |
ACP_AUDIT_DSN |
postgresql://... |
Postgres connection for audit logs |
ACP_PROXY_PORT |
3128 |
HTTP CONNECT proxy port for agent egress |
ACP_IDLE_TIMEOUT_MIN |
15 |
Default idle timeout for containers (minutes) |
AGENTSYSTEMS_CONFIG_PATH |
/etc/agentsystems/agentsystems-config.yml |
Path to agent configuration file |
View uploaded files and agent outputs:
# List all active threads
docker exec gateway ls -la /artifacts/
# Read specific files
docker exec gateway cat /artifacts/{thread-id}/out/result.jsonEvery pull request triggers ci.yml which now performs:
- Pre-commit hooks (ruff, black, shellcheck, hadolint).
docker buildof the gateway image.- Run the container on
localhost:8800(internal port 8080). - Poll
http://localhost:8800/health(30 × 2 s) and fail if not 200 OK. - Always remove the container in a cleanup step.
The gateway detects when the host Docker socket is absent (e.g. CI) and gracefully disables agent discovery, emitting the log line docker_unavailable. The health endpoint still reports OK, so the build remains deterministic.
| Var | Default | Purpose |
|---|---|---|
PG_HOST |
localhost |
Postgres host |
PG_DB |
agent_cp |
Postgres database name |
PG_USER |
agent |
Postgres user |
PG_PASSWORD |
agent |
Postgres password |
| Label | Example | Meaning |
|---|---|---|
agent.enabled |
true |
Opt-in to gateway routing. |
agent.port |
8000 |
TCP port the agent listens on. |
The gateway is organized into focused modules:
main.py: FastAPI application, request routing, and API endpointsdocker_discovery.py: Container discovery via Docker labels, maintains agent registrydatabase.py: PostgreSQL operations with automatic fallback to in-memory storageegress.py: Loads and manages per-agent egress allowlists from configproxy.py: HTTP CONNECT proxy server for controlled agent outbound requestslifecycle.py: Monitors agent activity and stops idle containersmodels.py: Shared Pydantic models for request/response validationexceptions.py: Standardized HTTP exception factories
- Auto-discovery: Finds agents by Docker labels (
agent.enabled=true) - Lazy start: Automatically starts stopped agent containers on first request
- Idle management: Stops containers after configurable idle timeout
- Egress control: HTTP proxy configured to restrict agent outbound requests to allowlisted URLs
- File uploads: Handles multipart uploads with automatic artifact management
- Async by default: Non-blocking invocations with status polling
- Type safety: Full type hints for better IDE support
See CONTRIBUTING.md for guidelines on contributing to this project.
- Add type hints to all new functions
- Include docstrings for public APIs
- Update CHANGELOG.md for notable changes
- Run tests before submitting PRs:
pytest
gateway[Gateway]
subgraph "Docker network: agents-int" gateway -- 8080 --> agent1((some-agent)) gateway --> agent2((another-agent)) end gateway --> pg[(Postgres)] gateway --> lf[Langfuse]
1. **Gateway** discovers containers on the `agents-int` internal network via Docker/Kubernetes labels `agent.enabled=true` and `agent.port=<port>`.
2. Requests are authenticated (Bearer token placeholder for now) and forwarded to the agent container.
3. Each round-trip is appended to the **audit** table (hash-chained rows) and optionally mirrored to Langfuse.
See the [Gateway API reference](../docs/reference/gateway-api) for endpoints.
## Quick start (compose)
The fastest path is the deployment repo:
```bash
# clone side-by-side
mkdir agents && cd agents
for repo in agent-control-plane agent-platform-deployments agent-template; do
git clone https://github.com/agentsystems/$repo.git
done
cd agent-platform-deployments
make up # docker compose up -d (gateway + Postgres + example agent)
Browse:
• Gateway Swagger UI → http://localhost:18080/hello-world-agent/docs
• List agents & states → curl http://localhost:18080/agents -H "Authorization: Bearer demo"
Returns JSON array of objects like { "name": "hello-world-agent", "state": "running" }
cd agent-control-plane
python -m venv .venv && source .venv/bin/activate
pip install -e .[dev]
# override port via env if desired
ACP_BIND_PORT=8088 uvicorn cmd.gateway.main:app --reload --port ${ACP_BIND_PORT:-8080}Point the deployment’s compose file at host.docker.internal:8080 or run an agent locally on port 8000 and the gateway will pick it up.
This repo is intended to be built into a container image and then orchestrated via the manifests in agent-platform-deployments.
# clone
git clone https://github.com/agentsystems/agent-control-plane.git
cd agent-control-plane
# build image (adjust tag as needed)
docker build -t agentsystems/agent-control-plane:<tag> .
Push the image to your registry of choice and update the image tag in the deployment repo’s Compose / Helm charts.
&& source .venv/bin/activate
pip install -e .[dev]
# override port via env if desired
ACP_BIND_PORT=8088 uvicorn cmd.gateway.main:app --port ${ACP_BIND_PORT:-8080}
But day-to-day you will spin it up via the deployment bundle, e.g.:
# in agent-platform-deployments
# clone
git clone https://github.com/agentsystems/agent-control-plane.git
cd agent-control-plane
# create venv & install
&& source .venv/bin/activate
pip install -e .[dev]
# run gateway
# override port via env if desired
ACP_BIND_PORT=8088 uvicorn cmd.gateway.main:app --reload --port ${ACP_BIND_PORT:-8080}
(either with Docker or uvicorn agent.main:app) and the gateway auto-registers when the container is labeled agent.enabled=true and exposes the configured port (once health checks pass).
(gateway + Postgres + example agent, etc.) use the agent-platform-deployments repo:
# in a separate clone
cd agent-platform-deployments
docker compose -f compose/local/docker-compose.yml up -dEnvironment variables (excerpt):
| Var | Default | Purpose |
|---|---|---|
ACP_BIND_PORT |
8080 |
Gateway listen port inside the container. |
ACP_AUDIT_DSN |
postgresql://user:pw@postgres:5432/acp |
Audit Postgres connection. |
ACP_ALLOWED_ORIGINS |
* |
CORS origins. |
Agent discovery labels:
| Label | Example | Meaning |
|---|---|---|
agent.enabled |
true |
Opt-in to gateway routing. |
agent.port |
8000 |
Container port to forward to. |
- Bump version in
pyproject.toml. - Build & push Docker image:
docker build -t agentsystems/agent-control-plane:<tag> .. - Create Git tag and release notes.
- Update Compose / Helm charts in
agent-platform-deploymentswith the new<tag>.
© 2025 AgentSystems
-
(includes Compose v2)
# repo root
docker compose build # build agents + gateway
docker compose up -d # start stack (detached)
curl http://localhost:18080/agents | jq . # → { "agents": [ {"name": "hello-world-agent", "state": "running"}, ... ] }
Swagger for any agent:
<http://localhost:18080/my_agent/docs>
Invoke an agent and poll status until it completes:
```bash
# 1. Start the job (returns thread_id and helper URLs)
resp=$(curl -s -X POST http://localhost:18080/invoke/my_agent \
-H "Content-Type: application/json" \
-d '{"today":"2025-06-13"}')
thread_id=$(echo "$resp" | jq -r .thread_id)
# 2. Poll lightweight status endpoint (state + progress only)
curl -s http://localhost:18080/status/$thread_id | jq .
# { "state": "running", "progress": { ... } }
# 3. Fetch the final result when state == completed
curl -s http://localhost:18080/result/$thread_id | jq .
# { "result": { ... } }
docker compose down # stop containers, keep images
docker system prune -f # optional: clear build cache# copy an existing folder
cp -R my_agent my_fourth_agent
# edit YAML metadata
sed -i '' 's/name:.*/name: my_fourth_agent/' my_fourth_agent/agent.yaml
# (optional) tweak greeting
sed -i '' 's/Hello!/Howdy from agent four!/' my_fourth_agent/main.pyAppend this to docker-compose.yml:
my_fourth_agent:
build: ./my_fourth_agent
expose: ["8000"]
labels:
- agent.enabled=true
- agent.port=8000Then:
docker compose build my_fourth_agent
docker compose up -d my_fourth_agent
curl http://localhost:18080/agents | jq . # shows `{ "name": "my_fourth_agent", "state": "running" }`
curl -X POST http://localhost:18080/invoke/my_fourth_agent \
-H "Content-Type: application/json" \
-d '{"today":"2025-06-13"}'- label discovery (
/gateway) Agents → FastAPI apps inmy_*_agent/, read their ownagent.yamlLabels →agent.enabled=true&agent.port=8000tell the gateway to route -->
Licensed under the Apache-2.0 license.