diff --git a/docs/architecture/index.md b/docs/architecture/index.md new file mode 100644 index 00000000..98cad0d6 --- /dev/null +++ b/docs/architecture/index.md @@ -0,0 +1,11 @@ +# Forge Architecture + +Architecture reference for Forge, an AI-powered SDLC orchestrator. Covers system structure, runtime topology, state management, failure modes, security boundaries, and key design decisions. + +For workflow details, see the [Feature](../guide/feature-workflow.md), [Bug](../guide/bug-workflow.md), and [Task](../guide/task-workflow.md) guides. For API reference, see the OpenAPI spec at `/docs` when the gateway is running. + +| Part | Contents | +|------|----------| +| [System & Components](overview.md) | System context, external actors, component responsibilities | +| [Internals](internals.md) | Runtime topology, state and concurrency, failure recovery, security | +| [Reference](reference.md) | Architectural decisions, known limitations, workflow lifecycles | diff --git a/docs/architecture/internals.md b/docs/architecture/internals.md new file mode 100644 index 00000000..f349d88d --- /dev/null +++ b/docs/architecture/internals.md @@ -0,0 +1,56 @@ +# Internals + +## Runtime Topology + +Forge runs as two process types plus Redis: + +- **Gateway**: Single FastAPI/Uvicorn process. Stateless; can be load-balanced. +- **Worker(s)**: One or more `OrchestratorWorker` processes. Each joins the Redis consumer group. **Must run on a host with Podman installed.** Each worker handles up to 20 concurrent tasks (configurable via `QUEUE_MAX_CONCURRENT_TASKS`). +- **Redis**: Single-instance server. No built-in HA; must be provided externally if required. + +Gateway and Worker communicate only through Redis and can be deployed on separate hosts. Horizontal Worker scaling has a limitation: per-ticket event serialization uses an in-process `asyncio.Lock`, not a distributed lock (see [Known Limitations](reference.md#known-limitations)). + +## State and Event Processing + +**Delivery guarantee:** At-least-once. Messages are acknowledged (`XACK`) only after successful processing. The system does not provide exactly-once semantics. + +**Checkpointing:** LangGraph workflow state is persisted via `AsyncRedisSaver`, keyed by Jira ticket key (e.g., `AISOS-123`). Checkpoints are written after each graph node completes. When a new event arrives for an existing ticket, the workflow resumes from its last checkpoint. + +**Idempotency:** A `DeduplicationService` exists but is not yet wired into the webhook routes. Branch creation and label operations are naturally idempotent; Jira comment posting is not. + +**Consistency caveat:** Checkpoint writes and external side effects (Jira comments, GitHub PRs) are not transactional. A crash between a side effect and its checkpoint write can cause duplicate actions on retry. + +## Failure and Recovery + +| Component | Failure impact | Recovery | +|-----------|---------------|----------| +| Gateway | Incoming webhooks dropped | Jira/GitHub retry delivery per their own policies | +| Worker | In-flight messages stay in Redis PEL | Restart consumes new messages; PEL requires manual `XCLAIM` | +| Redis | Complete system outage; all state at risk | Configure Redis persistence (RDB/AOF) externally | +| LLM provider | Planning/code generation fails | Retried up to 3 times, then moved to dead-letter queue | +| Container | Non-zero exit captured by orchestrator | Retry mechanism determines re-attempt | + +**Retry policy:** Up to 3 attempts with exponential backoff (30s initial, 2x multiplier, capped at 1 hour). Failed messages go to a dead-letter queue for manual investigation. + +**Blocked workflows:** The `forge:blocked` label is applied to Jira tickets in error state. Adding `forge:retry` triggers re-entry at the failed step. + +**Approval gates:** Workflows pause indefinitely at human review gates. There is no automatic timeout or escalation. + +## Security Boundaries + +**Webhook authentication:** HMAC-SHA256 validation via `hmac.compare_digest()`. Validation is conditional: it only runs when secrets are configured (`JIRA_WEBHOOK_SECRET`, `GITHUB_WEBHOOK_SECRET`). **Always configure secrets in production.** + +**Credential distribution:** + +| Credential | Worker | Container | +|------------|--------|-----------| +| Redis | Yes | No | +| Jira API token | Yes | No | +| GitHub App credentials | Yes | No | +| LLM provider (API key or Vertex AI) | Yes | Yes | +| Langfuse | Yes | Yes (when enabled) | +| Git identity | No | Yes | + +Containers do not receive Jira, GitHub, or Redis credentials. All external platform operations are performed by the orchestrator after the container exits. + +**Container isolation:** Rootless Podman with configurable network mode (`slirp4netns` default), memory limit (4GB), CPU limit (2 cores), and 30-minute timeout. Workspace mounted read-write at `/workspace`; task file read-only at `/task.json`. diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md new file mode 100644 index 00000000..8d135775 --- /dev/null +++ b/docs/architecture/overview.md @@ -0,0 +1,85 @@ +# System & Components + +## System Context + +Forge sits between project management (Jira), source control (GitHub), and LLM providers, orchestrating work from ticket creation through merged PR. + +```mermaid +flowchart LR + A["Jira / GitHub\n(webhooks)"] --> B["Gateway\n(FastAPI)"] + B --> C["Redis\n(Streams + State)"] + C --> D["Workers\n(LangGraph)"] + D --> E["Podman\nContainers"] + D <--> F["LLM\n(Claude / Gemini)"] + E <--> F + D --> A +``` + +**External actors:** + +- **Jira**: Source of ticket lifecycle events (issue and comment webhooks) +- **GitHub**: Source of PR, CI, and code review events (PR, check suite, and review webhooks) +- **LLM providers**: Anthropic (direct API) and Google Vertex AI (Claude and Gemini models) +- **Langfuse**: Optional observability for LLM call tracing and cost tracking +- **Human reviewers**: Approve or revise artifacts at defined workflow gates + +## Component Responsibilities + +```mermaid +flowchart TD + subgraph External["External Systems"] + Jira + GitHub + Langfuse["Langfuse (Observability)"] + end + + subgraph Gateway["FastAPI Gateway (:8000)"] + JiraWH["POST /api/v1/webhooks/jira"] + GitHubWH["POST /api/v1/webhooks/github"] + end + + subgraph Queue["Redis"] + Streams["Streams: forge:events:jira\nforge:events:github"] + State["AsyncRedisSaver\nLangGraph checkpointing"] + end + + subgraph Workers["Worker Processes (consumer group: forge-workers)"] + Router{"WorkflowRouter\nroute by issue type"} + Feature["FeatureWorkflow\n(Feature/Story)"] + Bug["BugWorkflow\n(Bug)"] + Task["TaskTakeoverWorkflow\n(Task/Epic)"] + end + + subgraph Container["Podman Container (ephemeral)"] + Agent["Deep Agents + MCP\n/workspace (repo mounted)"] + end + + LLM["LLM Backends\nAnthropic API (Claude)\nVertex AI (Claude/Gemini)"] + + Jira -- webhooks --> JiraWH + GitHub -- webhooks --> GitHubWH + JiraWH --> Streams + GitHubWH --> Streams + Streams --> Router + Router --> Feature + Router --> Bug + Router --> Task + Feature --> Container + Bug --> Container + Task --> Container + Workers <--> LLM + Container <--> LLM + Workers --> Jira + Workers --> GitHub + Workers --> Langfuse +``` + +**Gateway (FastAPI)**: Accepts webhooks over HTTPS, validates HMAC-SHA256 signatures, and publishes events to Redis Streams. Performs no workflow logic. + +**Worker**: Consumes events from Redis Streams via the `forge-workers` consumer group. The `WorkflowRouter` resolves the target LangGraph workflow (Feature, Bug, or Task Takeover) based on Jira issue type and drives execution through planning, implementation, CI repair, and human review stages. + +**Redis**: Event bus (Redis Streams), workflow state store (LangGraph `AsyncRedisSaver` checkpoints per ticket), retry queue, dead-letter queue, and supporting indexes (PR-to-ticket mapping, deduplication keys). + +**Podman Container**: Ephemeral rootless containers that execute implementation tasks. Each container receives the repo at `/workspace` (read-write), a task file at `/task.json` (read-only), and LLM credentials. Runs Deep Agents with MCP tool access. The orchestrator handles pushing and PR creation after the container exits. + +**LLM Backends**: Claude and Gemini models called by both orchestrator nodes (planning, review) and container agents (code generation). Supports Anthropic direct API and Google Vertex AI, selected automatically based on configured credentials. diff --git a/docs/architecture/reference.md b/docs/architecture/reference.md new file mode 100644 index 00000000..983fb39e --- /dev/null +++ b/docs/architecture/reference.md @@ -0,0 +1,95 @@ +# Reference + +## Key Architectural Decisions + +### Redis Streams for Event Bus + +Use Redis Streams with consumer groups instead of a dedicated message broker (RabbitMQ, Kafka). Redis already serves as the checkpoint store, so reusing it for event queuing eliminates an infrastructure dependency. The tradeoff: no built-in dead-letter queues or cross-datacenter replication. + +### LangGraph for Workflow Orchestration + +Use LangGraph `StateGraph` with `AsyncRedisSaver` checkpointing instead of Temporal or Airflow. LangGraph provides native LLM-driven decision nodes, conditional routing, and checkpointed pause/resume. The tradeoff: a less mature ecosystem with fewer operational tools. + +### Host-Level Podman for Code Execution + +Run implementation tasks in rootless Podman containers on the Worker host instead of Kubernetes jobs or remote VMs. This simplifies the container lifecycle but requires Podman on every Worker host. + +### Workflow Separation by Issue Type + +Three separate LangGraph workflow definitions (Feature, Bug, Task Takeover) rather than one parameterized workflow. Each has fundamentally different planning stages. Shared implementation/CI/review nodes are reused across all three. + +### Human Approval Gates + +Workflows pause at defined gates and wait indefinitely for human approval. The `forge:yolo` label provides an opt-in escape hatch for autonomous operation. The tradeoff: increased latency for every ticket. + +## Known Limitations + +- **No PEL reclaim**: Unacknowledged messages from crashed workers remain in Redis PEL indefinitely. Recovery requires manual `XCLAIM`. +- **No distributed per-ticket lock**: Multiple workers can process events for the same ticket concurrently, causing potential checkpoint conflicts. +- **Webhook deduplication not wired**: `DeduplicationService` exists but is not connected to webhook routes. +- **Webhook signature validation is optional**: Endpoints accept unsigned payloads when secrets are not configured. +- **No approval gate timeout**: Paused workflows wait indefinitely with no escalation. +- **Single Redis dependency**: No Sentinel, Cluster, or HA. Redis is a single point of failure. +- **Container security hardening gaps**: No `--cap-drop ALL`, `--no-new-privileges`, or `--read-only` root filesystem. +- **No cross-stream ordering**: Jira and GitHub streams are consumed independently with no ordering guarantee. + +## Workflow Lifecycles + +`>>` marks human approval gates. Gates are auto-approved when the `forge:yolo` label is set. For detailed node-level flows, see the [Feature](../guide/feature-workflow.md), [Bug](../guide/bug-workflow.md), and [Task](../guide/task-workflow.md) workflow guides. + +### Feature Lifecycle + +```mermaid +flowchart TD + A["Ticket created\n(Feature/Story)"] --> B["Generate PRD"] + B --> C[">> PRD approval"] + C --> D["Generate technical spec"] + D --> E[">> Spec approval"] + E --> F["Decompose into epics"] + F --> G[">> Plan approval"] + G --> H["Generate tasks"] + H --> I[">> Task approval"] + I --> J["Route tasks by repo"] + + J --> K["Implement in container"] + K --> L["Review and open PR"] + L --> M["CI repair loop"] + M --> N[">> Human code review"] + N -->|merged| O["Aggregate status\nComplete"] +``` + +### Bug Lifecycle + +```mermaid +flowchart TD + A["Ticket created\n(Bug)"] --> B["Triage check"] + B -->|"missing info"| C[">> Ask reporter"] + C --> B + B -->|"sufficient"| D["Root cause analysis"] + D --> E[">> Present fix options\n(user selects >option N)"] + E --> F["Generate fix plan"] + F --> G[">> Plan approval"] + + G --> H["Implement in container"] + H --> I["Review and open PR"] + I --> J["CI repair loop"] + J --> K[">> Human code review"] + K -->|merged| L["Post-merge summary\nComplete"] +``` + +### Task Lifecycle + +```mermaid +flowchart TD + A["Ticket created\n(Task/Epic)"] --> B["Triage check"] + B -->|"missing context"| C[">> Ask for details"] + C --> B + B -->|"sufficient"| D["Generate plan"] + D --> E[">> Plan approval"] + + E --> F["Implement in container"] + F --> G["Review and open PR"] + G --> H["CI repair loop"] + H --> I[">> Human code review"] + I -->|merged| J["Complete"] +``` diff --git a/zensical.toml b/zensical.toml index 692a4acb..ea9cbdb1 100644 --- a/zensical.toml +++ b/zensical.toml @@ -18,6 +18,12 @@ nav = [ ]}, {"Developer Guide" = [ {"Overview" = "developer-guide.md"}, + {"Architecture" = [ + {"Overview" = "architecture/index.md"}, + {"System & Components" = "architecture/overview.md"}, + {"Internals" = "architecture/internals.md"}, + {"Reference" = "architecture/reference.md"}, + ]}, {"Local Setup" = "dev/setup.md"}, {"Testing" = "dev/testing.md"}, {"Contributing" = "dev/contributing.md"},