Scaffold agent API with Railway deployment
FastAPI server with /health, /agents, /jobs endpoints. Includes railway.toml, Dockerfile, CI/CD workflows, and pytest tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
26
.github/workflows/ci.yml
vendored
Normal file
26
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt
|
||||||
|
pip install pytest
|
||||||
|
- name: Validate syntax
|
||||||
|
run: find . -name "*.py" -type f -exec python3 -m py_compile {} \;
|
||||||
|
- name: Test
|
||||||
|
run: pytest tests/ -v
|
||||||
37
.github/workflows/deploy-railway.yml
vendored
Normal file
37
.github/workflows/deploy-railway.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
name: Deploy to Railway
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
name: Deploy to Railway
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Railway CLI
|
||||||
|
run: npm install -g @railway/cli
|
||||||
|
|
||||||
|
- name: Deploy to Railway
|
||||||
|
run: railway up --detach
|
||||||
|
env:
|
||||||
|
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
|
||||||
|
RAILWAY_PROJECT_ID: ${{ secrets.RAILWAY_PROJECT_ID }}
|
||||||
|
|
||||||
|
- name: Wait for deployment
|
||||||
|
run: sleep 30
|
||||||
|
|
||||||
|
- name: Health Check
|
||||||
|
if: success()
|
||||||
|
run: |
|
||||||
|
HEALTH_URL="${{ secrets.RAILWAY_SERVICE_URL }}/health"
|
||||||
|
if [ -n "${{ secrets.RAILWAY_SERVICE_URL }}" ]; then
|
||||||
|
echo "Checking health at: $HEALTH_URL"
|
||||||
|
curl -f --retry 3 --retry-delay 10 "$HEALTH_URL" || echo "Health check pending"
|
||||||
|
else
|
||||||
|
echo "RAILWAY_SERVICE_URL not set yet, skipping health check"
|
||||||
|
fi
|
||||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.11
|
||||||
7
Dockerfile
Normal file
7
Dockerfile
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
WORKDIR /app
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
COPY . .
|
||||||
|
EXPOSE 8080
|
||||||
|
CMD ["python", "server.py"]
|
||||||
31
README.md
31
README.md
@@ -1,2 +1,33 @@
|
|||||||
# blackroad-agents
|
# blackroad-agents
|
||||||
|
|
||||||
Agent definitions, prompts, and orchestration schemas for BlackRoad OS.
|
Agent definitions, prompts, and orchestration schemas for BlackRoad OS.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
python server.py # Start server on :8080
|
||||||
|
pytest tests/ -v # Run tests
|
||||||
|
```
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| GET | `/health` | Health check |
|
||||||
|
| GET | `/agents` | List agents |
|
||||||
|
| POST | `/jobs` | Submit agent job |
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
Deploys to Railway on push to `main`. See `railway.toml` for config.
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `PORT` | `8080` | Server port |
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Proprietary - BlackRoad OS, Inc. All rights reserved.
|
||||||
|
|||||||
1
agent/__init__.py
Normal file
1
agent/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""BlackRoad Agent API package."""
|
||||||
46
agent/api.py
Normal file
46
agent/api.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
"""FastAPI application for BlackRoad Agent API."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
app = FastAPI(title="BlackRoad Agent API", version="0.1.0")
|
||||||
|
|
||||||
|
VERSION = "0.1.0"
|
||||||
|
|
||||||
|
|
||||||
|
class JobRequest(BaseModel):
|
||||||
|
agent: str
|
||||||
|
task: str
|
||||||
|
payload: dict[str, Any] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
@app.get("/healthz")
|
||||||
|
def healthcheck() -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"status": "ok",
|
||||||
|
"service": "blackroad-agents",
|
||||||
|
"version": VERSION,
|
||||||
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/agents")
|
||||||
|
def list_agents() -> dict[str, Any]:
|
||||||
|
return {"status": "ok", "agents": []}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/jobs")
|
||||||
|
def submit_job(request: JobRequest) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"status": "ok",
|
||||||
|
"job_id": None,
|
||||||
|
"agent": request.agent,
|
||||||
|
"task": request.task,
|
||||||
|
"message": "Job submission not yet implemented",
|
||||||
|
}
|
||||||
9
railway.toml
Normal file
9
railway.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[build]
|
||||||
|
builder = "NIXPACKS"
|
||||||
|
|
||||||
|
[deploy]
|
||||||
|
startCommand = "python server.py"
|
||||||
|
healthcheckPath = "/health"
|
||||||
|
healthcheckTimeout = 300
|
||||||
|
restartPolicyType = "ON_FAILURE"
|
||||||
|
restartPolicyMaxRetries = 10
|
||||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
fastapi>=0.115.0
|
||||||
|
uvicorn>=0.34.0
|
||||||
|
pydantic>=2.0.0
|
||||||
|
httpx>=0.27.0
|
||||||
9
server.py
Normal file
9
server.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
"""BlackRoad Agents API Server."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import uvicorn
|
||||||
|
from agent.api import app
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
port = int(os.environ.get("PORT", 8080))
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=port)
|
||||||
37
tests/test_api.py
Normal file
37
tests/test_api.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
"""Tests for the BlackRoad Agent API."""
|
||||||
|
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
from agent.api import app
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
|
||||||
|
|
||||||
|
def test_health():
|
||||||
|
response = client.get("/health")
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data["status"] == "ok"
|
||||||
|
assert data["service"] == "blackroad-agents"
|
||||||
|
|
||||||
|
|
||||||
|
def test_healthz():
|
||||||
|
response = client.get("/healthz")
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_list_agents():
|
||||||
|
response = client.get("/agents")
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert isinstance(data["agents"], list)
|
||||||
|
|
||||||
|
|
||||||
|
def test_submit_job():
|
||||||
|
response = client.post("/jobs", json={
|
||||||
|
"agent": "test-agent",
|
||||||
|
"task": "test-task",
|
||||||
|
})
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data["status"] == "ok"
|
||||||
|
assert data["agent"] == "test-agent"
|
||||||
Reference in New Issue
Block a user