# Agent Gateway

> A FastAPI extension for building API-first AI agent services. Define agents, tools, and skills as markdown files, then serve them as a production-ready API.

Agent Gateway (`agents-gateway` on PyPI) lets you build AI agent APIs by defining agents, tools, and skills as markdown files in a workspace directory. It extends FastAPI and provides authentication, persistence, scheduling, notifications, telemetry, memory, and more out of the box.

## Installation

```bash
pip install agents-gateway[all]
```

Extras: sqlite, postgres, redis, rabbitmq, oauth2, slack, webhooks, dashboard, otlp

## Quick Start

```bash
agents-gateway init myproject
cd myproject
agents-gateway serve
```

## Core Concepts

### Gateway

The main entry point. Subclasses FastAPI.

```python
from agent_gateway import Gateway

gw = Gateway(workspace="./workspace")

# Fluent configuration
gw.use_sqlite("data.db")
gw.use_api_key_auth(api_key="your-key")
gw.use_dashboard()

# Register code tools
@gw.tool(agent="assistant")
def add(a: float, b: float) -> float:
    """Add two numbers."""
    return a + b

# Lifecycle hooks
@gw.on("agent.invoke.after")
async def on_complete(agent_id, result, **kw):
    print(f"Agent {agent_id} completed")

# Run
gw.run()
```

### Agents (workspace/agents/<id>/AGENT.md)

```markdown
---
description: A helpful assistant
skills:
  - general-tools
memory:
  enabled: true
model:
  name: gemini/gemini-2.0-flash
  temperature: 0.1
  fallback: gemini/gemini-1.5-flash  # fallback model if primary fails
execution_mode: sync
delegates_to:            # optional allow-list; omit to delegate to any agent
  - researcher
  - writer
schedules:
  - name: daily
    cron: "0 9 * * 1-5"
    message: "Generate report"
    instructions: "Focus on error rates and system uptime. Use bullet points."
notifications:
  on_error:
    - channel: slack
      target: "#alerts"
---
You are a helpful assistant. Answer questions clearly.
```

Personal agents require per-user configuration before use:

```yaml
scope: personal
setup_schema:
  type: object
  required: [api_key]
  properties:
    api_key:
      type: string
      sensitive: true
```

API: `POST /v1/agents/{id}/config` to save user config, `GET /v1/agents/{id}/setup-schema` for schema. Fields marked `sensitive: true` are encrypted at rest. Requires `AGENT_GATEWAY_SECRET_KEY` env var.

### Tools

File-based (workspace/tools/<id>/TOOL.md + handler.py):

```markdown
---
name: fetch-url
description: Fetch a URL
parameters:
  url:
    type: string
    required: true
---
```

```python
# handler.py
async def handler(url: str) -> str:
    import httpx
    async with httpx.AsyncClient() as c:
        return (await c.get(url)).text
```

Code-based:

```python
@gw.tool(name="add", allowed_agents=["assistant"])
def add(a: float, b: float) -> float:
    """Add two numbers."""
    return a + b
```

### MCP Servers

Connect to external MCP (Model Context Protocol) servers to discover and use their tools:

```python
gw.add_mcp_server(
    name="my-tools",
    transport="stdio",
    command="python",
    args=["-m", "my_server"],
)
```

Supports `stdio` (subprocess) and `streamable_http` (remote) transports. Tools are namespaced as `{server}__{tool}`. Assign servers to agents via `mcp_servers` in AGENT.md frontmatter. Admin CRUD API at `/v1/admin/mcp-servers`. Credentials/env are encrypted at rest.

### Skills (workspace/skills/<id>/SKILL.md)

Composable workflow units grouping tools with optional sequential/parallel steps.

## OpenAPI Documentation

Interactive API docs are available at `/docs` (Swagger UI) and `/redoc` out of the box. All built-in endpoints have summaries, descriptions, tags, and error response schemas. Routes are organised into 12 tag groups: Health, Agents, Chat, Sessions, Conversations, Executions, Schedules, Tools, Skills, User Config, Notifications, Admin.

Add custom tag groups via the `openapi_tags` constructor kwarg — they are merged with the built-in tags (de-duplicated by name).

Use `build_responses` from `agent_gateway.api.openapi` when writing custom routes to produce a consistent `responses` dict that matches built-in endpoint style:

```python
from agent_gateway.api.openapi import build_responses

@gw.get("/v1/reports/{id}", responses=build_responses(auth=True, not_found=True))
async def get_report(id: str): ...
```

Flags: `auth` (401/403), `not_found` (404), `conflict` (409), `rate_limit` (429). All error bodies use the standard `ErrorResponse` schema.

Guide: https://vince-nyanga.github.io/agents-gateway/guides/openapi/

## API Endpoints

All endpoints include OpenAPI summaries, descriptions, and error response documentation. The interactive API docs at `/docs` are grouped by tag (Health, Agents, Chat, Sessions, Conversations, Executions, Schedules, Tools, Skills, User Config, Admin).

| Method | Path | Tag | Description |
|--------|------|-----|-------------|
| GET | /v1/health | Health | Health check |
| GET | /v1/agents | Agents | List agents |
| GET | /v1/agents/{id} | Agents | Agent details |
| POST | /v1/agents/{id}/invoke | Agents | Single-turn invocation |
| POST | /v1/agents/{id}/chat | Chat | Multi-turn chat |
| GET | /v1/executions | Executions | List executions |
| GET | /v1/executions/{id} | Executions | Execution status |
| GET | /v1/executions/{id}/workflow | Executions | Full delegation workflow tree |
| POST | /v1/executions/{id}/cancel | Executions | Cancel execution |
| GET | /v1/sessions | Sessions | List chat sessions |
| GET | /v1/sessions/{id} | Sessions | Session details |
| DELETE | /v1/sessions/{id} | Sessions | Delete session |
| GET | /v1/users/me/conversations | Conversations | List conversations |
| GET | /v1/users/me/conversations/{id}/messages | Conversations | Get conversation messages |
| GET | /v1/schedules | Schedules | List schedules |
| GET | /v1/schedules/{id} | Schedules | Schedule details |
| POST | /v1/schedules | Schedules | Create admin schedule |
| DELETE | /v1/schedules/{id} | Schedules | Delete admin schedule |
| POST | /v1/schedules/{id}/pause | Schedules | Pause schedule |
| POST | /v1/schedules/{id}/resume | Schedules | Resume schedule |
| POST | /v1/schedules/{id}/trigger | Schedules | Trigger schedule |
| GET | /v1/skills | Skills | List skills |
| GET | /v1/skills/{id} | Skills | Skill details |
| GET | /v1/tools | Tools | List tools |
| GET | /v1/tools/{id} | Tools | Tool details |
| GET | /v1/agents/{id}/setup-schema | User Config | Get agent setup schema |
| POST | /v1/agents/{id}/config | User Config | Save user config |
| GET | /v1/agents/{id}/config | User Config | Get user config |
| DELETE | /v1/agents/{id}/config | User Config | Delete user config |
| GET | /v1/notifications | Notifications | List notification delivery records |
| POST | /v1/admin/mcp-servers | Admin | Create MCP server config |
| GET | /v1/admin/mcp-servers | Admin | List MCP servers |
| GET | /v1/admin/mcp-servers/{id} | Admin | Get MCP server details |
| PUT | /v1/admin/mcp-servers/{id} | Admin | Update MCP server config |
| DELETE | /v1/admin/mcp-servers/{id} | Admin | Delete MCP server |
| POST | /v1/admin/mcp-servers/{id}/test | Admin | Test connection (ephemeral connect/list/disconnect) |
| POST | /v1/admin/mcp-servers/{id}/refresh | Admin | Reconnect and rediscover tools |
| GET | /v1/admin/mcp-servers/{id}/tools | Admin | List discovered MCP tools |
| POST | /v1/agents/{id}/memory/compact | Admin | Compact agent memory |
| POST | /v1/reload | Admin | Reload workspace |

## Configuration (workspace/gateway.yaml)

```yaml
server:
  host: "0.0.0.0"
  port: 8000

model:
  default: "gemini/gemini-2.0-flash"
  temperature: 0.1
  max_tokens: 4096

auth:
  enabled: true
  mode: api_key  # api_key | oauth2 | composite | none

persistence:
  backend: sqlite  # sqlite | postgres
  url: "sqlite+aiosqlite:///data.db"

memory:
  enabled: true
  auto_extract: true

mcp:
  tool_call_timeout_ms: 30000
  connection_timeout_ms: 10000

cors:
  enabled: true
  allow_origins: ["*"]

rate_limit:
  enabled: true
  default_limit: "100/minute"
  # storage_uri: "redis://localhost:6379"  # for multi-worker
  # trust_forwarded_for: false

security:
  enabled: true  # on by default (opt-out)
  x_frame_options: "DENY"
  content_security_policy: "default-src 'self'"
  # strict_transport_security: ""  # disable HSTS for local dev

dashboard:
  enabled: true
  title: My Agent Service
  subtitle: "Powered by ACME Corp"
  logo_url: /static/logo.png      # branding image for sidebar + login page
  favicon_url: /static/favicon.ico
  auth:
    username: user
    password: userpass
    admin_username: admin
    admin_password: supersecret

queue:
  backend: none  # none | memory | redis | rabbitmq

scheduler:
  enabled: true
  misfire_grace_seconds: 60
  max_instances: 1
  coalesce: true
  distributed_lock:
    enabled: false  # set true for multi-instance deployments
    backend: auto   # auto | redis | postgres | none
    redis_url: null # defaults to queue.redis_url
    key_prefix: "ag:sched-lock:"
    lock_ttl_seconds: 300
```

Session rehydration: when persistence is enabled, chat sessions survive server restarts. If an in-memory session is not found (cache miss), it is automatically rehydrated from the `conversations` table. TTL is respected — expired sessions are not restored. Session metadata and tool-call messages are not restored on rehydration.

Distributed scheduler locking: when `scheduler.distributed_lock.enabled: true`, only one gateway instance fires each scheduled job. The `backend: auto` setting selects Redis (via `SET NX EX`) when a Redis queue is configured, or PostgreSQL (`pg_try_advisory_lock`) when a PostgreSQL persistence backend is in use. The lock expires after `lock_ttl_seconds` so a crashed instance cannot permanently block a schedule. Manual triggers via the API are not affected by the lock.

Per-schedule instructions: each schedule definition in `AGENT.md` accepts an optional `instructions` field (string). When the schedule fires, the value is injected into the agent's system prompt as a "Schedule Instructions" section after the base prompt and any user personalization. This allows a single agent to behave differently per schedule — for example, a social media agent that writes tips posts on Mondays and news roundups on Fridays using the same underlying agent. Instructions also apply to manual triggers (`POST /v1/schedules/{id}/trigger`). The field is visible and editable in the dashboard schedule edit form; runtime edits update APScheduler and the database but do not modify `AGENT.md`. Returned in `GET /v1/schedules/{id}` response body.

Notification delivery tracking: when persistence is configured, Agent Gateway automatically records every notification dispatch in a `notification_log` table (`NotificationDeliveryRecord`). Each record captures the channel, target, status (`delivered`/`failed`), attempt count, error message, and timestamps. Query records via `GET /v1/notifications` (filterable by `status`, `agent_id`, `channel`, `execution_id`; paginated with `limit`/`offset`). Failed deliveries can be retried from the dashboard notifications page at `/dashboard/notifications`; the retry preserves the original execution message so the re-fired notification is identical to the initial attempt.

Admin-created dynamic schedules: admins can create and delete system-level schedules for any agent at runtime without modifying `AGENT.md` or redeploying. Create via `POST /v1/schedules` (requires `schedules:manage` scope) or `gw.create_admin_schedule()`. Delete via `DELETE /v1/schedules/{id}` or `gw.delete_admin_schedule()`. Every schedule record includes a `source` field: `"workspace"` for schedules defined in `AGENT.md` frontmatter, and `"admin"` for runtime-created schedules. Admin schedules are persisted in the database with IDs in the format `admin:{agent_id}:{name}`, survive workspace reloads and gateway restarts, and are re-registered with APScheduler on startup. Workspace schedules (`source="workspace"`) cannot be deleted via the API — the delete endpoint returns `400` for those. `ScheduleConflictError` is raised (HTTP 409) if a schedule with the same name already exists for the agent; `ScheduleValidationError` is raised (HTTP 400) for invalid cron expressions or unknown agent IDs. The dashboard shows Workspace/Admin badges on the schedules page and provides a New Schedule form and delete button for admin schedules (admin users only).

Dashboard branding: `DashboardConfig` accepts `title` (browser tab title and sidebar heading), `subtitle` (tagline beneath the title, default `"AI Control Plane"`), `logo_url` (branding image for both the sidebar header and login page — when not set, the default Material hub icon is shown), and `favicon_url` (browser tab icon). All three are also available as keyword arguments on `gw.use_dashboard()`. Fluent API values take precedence over `gateway.yaml`. The topbar user avatar uses `display_name` for initials when available, falling back to `username`.

Environment variable overrides use prefix `AGENT_GATEWAY_` with `__` nesting:
`AGENT_GATEWAY_SERVER__PORT=9000`

## Programmatic Usage

```python
async with Gateway(workspace="./workspace") as gw:
    # Single-turn
    result = await gw.invoke("assistant", "Hello!")
    print(result.output)

    # Multi-turn chat
    session_id, result = await gw.chat("assistant", "Hi!")
    _, result2 = await gw.chat("assistant", "Follow up", session_id=session_id)

    # Structured output
    from pydantic import BaseModel
    from agent_gateway.engine.models import ExecutionOptions

    class Answer(BaseModel):
        value: float

    result = await gw.invoke(
        "assistant", "What is 2+2?",
        options=ExecutionOptions(output_schema=Answer),
    )
```

## Sub-App Mounting

Mount Gateway into an existing FastAPI app:

```python
from fastapi import FastAPI
from agent_gateway import Gateway

app = FastAPI()
gw = Gateway(workspace="./workspace")
gw.use_dashboard(auth_password="secret", admin_username="admin", admin_password="admin")
gw.mount_to(app, path="/ai")

# API at /ai/v1/..., dashboard at /ai/dashboard/
```

All features work identically when mounted: dashboard, auth, OAuth2, static assets, HTMX, scheduling, MCP, and chat streaming.

## CLI Commands

| Command | Description |
|---------|-------------|
| `agents-gateway init <name>` | Scaffold project |
| `agents-gateway serve` | Start server |
| `agents-gateway invoke <agent> <msg> [-f table\|json]` | Invoke agent |
| `agents-gateway chat <agent>` | Interactive chat |
| `agents-gateway agents [-f table\|json\|csv]` | List agents |
| `agents-gateway skills [-f table\|json\|csv]` | List skills |
| `agents-gateway schedules [-f table\|json\|csv]` | List schedules |
| `agents-gateway check` | Validate workspace |
| `agents-gateway db upgrade` | Run migrations |
| `agents-gateway db downgrade` | Rollback migration |
| `agents-gateway version` | Show version |

## Admin Dashboard Management

- Admin dashboard supports agent editing (frontmatter fields: description, display_name, tags, model, execution_mode)
- Agents can be disabled at runtime via dashboard; disabled agents return 422 from invoke/chat
- Agent enable/disable writes `enabled: true/false` to AGENT.md frontmatter and reloads the workspace
- Schedules can be edited at runtime (cron, message, timezone, enabled, instructions) via admin dashboard
- Runtime schedule edits update APScheduler + persistence but do NOT update AGENT.md
- Admin users can create new schedules for any agent via the dashboard "New Schedule" form or `POST /v1/schedules`
- Admin-created schedules show an "Admin" badge; workspace schedules show a "Workspace" badge
- Admin schedules can be deleted from the dashboard or via `DELETE /v1/schedules/{id}`; workspace schedules cannot be deleted
- When OAuth2 is the primary auth method, admin credentials (`admin_username`/`admin_password`) can be configured alongside it for break-glass access. The login page shows a collapsible admin password form below the SSO button.

## Links

- Documentation: https://vince-nyanga.github.io/agents-gateway/
- Getting Started: https://vince-nyanga.github.io/agents-gateway/getting-started/installation/
- Guides: https://vince-nyanga.github.io/agents-gateway/guides/agents/
- API Reference: https://vince-nyanga.github.io/agents-gateway/api-reference/gateway/
- Configuration Reference: https://vince-nyanga.github.io/agents-gateway/api-reference/configuration/
- Deployment: https://vince-nyanga.github.io/agents-gateway/deployment/production/
