Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ AWS_PROFILE=devops-agent-readonly # optional — use named profile (local dev)
# Agent behavior
MAX_TOOL_CALLS=20
INVESTIGATION_TIMEOUT=120
LOG_LEVEL=INFO
LOG_LEVEL=DEBUG # DEBUG | INFO | WARNING | ERROR
LOG_CONSOLE_ENABLED=true # false → suppress all console output (e.g. log to file only)
LOG_CONSOLE_COLORIZE=true # false → strip ANSI colour codes (CI pipelines, Docker logs)
# TOOL_RESPONSE_MAX_CHARS=40000 # cap large tool responses before they reach the LLM; 0 = disabled
# SUMMARIZATION_ENABLED=true
# SUMMARIZATION_THRESHOLD_CHARS=60000 # compact session when total message chars exceed this (~15K tokens)
Expand Down
11 changes: 9 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,15 @@ OpenDevOps Agent is an open-source AWS incident investigation tool powered by an
# Install / update dependencies
uv sync

# Run the web UI (FastAPI backend + serves built frontend)
# Development server — FastAPI with hot reload (equivalent to uv run python src/run.py)
uv run dev

# Production web UI (FastAPI backend + serves built frontend, no reload)
uv run devops-agent ui

# Apply SQL migrations to PostgreSQL (requires CHECKPOINT_BACKEND=postgres + DATABASE_URL)
uv run migrate

# CLI investigation
uv run devops-agent investigate "Lambda high error rate on payment service"
uv run devops-agent ask "Why would an ECS task OOM?"
Expand All @@ -39,7 +45,6 @@ uv run devops-agent mcp --http # HTTP+SSE transport, port 8001

# Tests
uv run pytest
uv run pytest tests/test_api/ -v

# Lint / format
uv run ruff check src/
Expand Down Expand Up @@ -191,6 +196,8 @@ AWS_PROFILE= # optional named ~/.aws profile
MAX_TOOL_CALLS=20 # recursion_limit = MAX_TOOL_CALLS * 3 + 15
INVESTIGATION_TIMEOUT=120 # seconds before asyncio.TimeoutError
LOG_LEVEL=INFO
LOG_CONSOLE_ENABLED=true # false = suppress all console output
LOG_CONSOLE_COLORIZE=true # false = strip ANSI colours (CI / Docker)
TOOL_RESPONSE_MAX_CHARS=40000 # 0 = disabled; ~10K tokens at 4 chars/token

# Storage backend — pick exactly one
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ CHECKPOINT_BACKEND=postgres
DATABASE_URL=postgresql://dev:dev@localhost:5433/opendevops

# Create app tables (safe to re-run)
uv run python scripts/setup_db.py
uv run migrate
```

### 5. Run
Expand All @@ -114,8 +114,8 @@ an IAM role to the instance/task instead.
**Option B — Local dev (two terminals)**

```bash
# Terminal 1 — FastAPI backend
uv run uvicorn api.app:app --reload
# Terminal 1 — FastAPI backend with hot reload
uv run dev
```

```bash
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ dependencies = [

[project.scripts]
devops-agent = "cli.main:app"
dev = "cli.dev:dev_cmd"
migrate = "cli.migrate:migrate_cmd"

[build-system]
requires = ["hatchling"]
Expand All @@ -54,6 +56,7 @@ dev = [

[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]

[tool.ruff]
line-length = 100
Expand Down
25 changes: 25 additions & 0 deletions src/cli/dev.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import sys
from typing import Annotated

import typer
import uvicorn
from rich.console import Console

if sys.platform == "win32":
import asyncio
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

console = Console()


def dev_cmd(
host: Annotated[str, typer.Option("--host", help="Bind host.")] = "127.0.0.1",
port: Annotated[int, typer.Option("--port", "-p", help="Port to listen on.")] = 8000,
) -> None:
"""Start the development server with hot reload enabled."""
console.print(
f"[bold green]OpenDevOps Dev Server[/bold green] → "
f"[link=http://{host}:{port}]http://{host}:{port}[/link] "
"[dim](reload on)[/dim]"
)
uvicorn.run("api.app:app", host=host, port=port, reload=True)
11 changes: 10 additions & 1 deletion src/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@

def _setup_logging() -> None:
logger.remove()
logger.add(sys.stderr, level=settings.log_level.upper())
if settings.log_console_enabled:
logger.add(
sys.stderr,
level=settings.log_level.upper(),
colorize=settings.log_console_colorize,
)


@app.callback()
Expand All @@ -30,9 +35,13 @@ def main() -> None:
from cli.report import report_cmd # noqa: E402
from cli.ui import ui_cmd # noqa: E402
from cli.mcp import mcp_cmd # noqa: E402
from cli.dev import dev_cmd # noqa: E402
from cli.migrate import migrate_cmd # noqa: E402

app.command("investigate")(investigate_cmd)
app.command("ask")(ask_cmd)
app.command("report")(report_cmd)
app.command("ui")(ui_cmd)
app.command("mcp")(mcp_cmd)
app.command("dev")(dev_cmd)
app.command("migrate")(migrate_cmd)
56 changes: 56 additions & 0 deletions src/cli/migrate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Run all SQL migrations in order against the configured PostgreSQL database."""

from __future__ import annotations

import sys
from pathlib import Path

import typer
from rich.console import Console

console = Console()


def migrate_cmd() -> None:
"""Apply all pending SQL migrations to the configured database."""
from config import settings

if settings.checkpoint_backend != "postgres" or not settings.database_url:
console.print(
"[bold red]migrate requires CHECKPOINT_BACKEND=postgres and DATABASE_URL to be set.[/bold red]"
)
raise typer.Exit(1)

try:
import psycopg
except ImportError:
console.print("[bold red]psycopg not installed. Run: uv add psycopg[binary,pool][/bold red]")
raise typer.Exit(1)

migrations_dir = Path(__file__).parent.parent.parent / "migrations"
sql_files = sorted(migrations_dir.glob("*.sql"))
if not sql_files:
console.print("[yellow]No migration files found in migrations/[/yellow]")
raise typer.Exit(0)

console.print(f"[bold]Running {len(sql_files)} migration(s) against database...[/bold]")

try:
conn = psycopg.connect(settings.database_url, autocommit=True)
except Exception as e:
console.print(f"[bold red]Could not connect to database: {e}[/bold red]")
raise typer.Exit(1)

with conn:
for path in sql_files:
sql = path.read_text(encoding="utf-8")
try:
conn.execute(sql)
console.print(f" [green]✓[/green] {path.name}")
except Exception as e:
console.print(f" [red]✗[/red] {path.name} — {e}")
conn.close()
raise typer.Exit(1)

conn.close()
console.print("[bold green]All migrations applied successfully.[/bold green]")
2 changes: 2 additions & 0 deletions src/config/appsettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class Settings(BaseSettings):
max_tool_calls: int = 20
investigation_timeout: int = 120
log_level: str = "INFO"
log_console_enabled: bool = True
log_console_colorize: bool = True

# Cap tool responses before feeding them back to the LLM.
# Prevents large CloudWatch / CloudTrail payloads from exhausting the context window.
Expand Down
Loading