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
30 changes: 30 additions & 0 deletions .go-arch-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,16 @@ vendors:
in:
- golang.org/x/sync/errgroup

huma:
in:
- github.com/danielgtaylor/huma/v2
- github.com/danielgtaylor/huma/v2/**

chi:
in:
- github.com/go-chi/chi/v5
- github.com/go-chi/chi/v5/**

components:
# DOMAIN LAYER
domain-workflow:
Expand Down Expand Up @@ -277,6 +287,9 @@ components:
interfaces-tui:
in: interfaces/tui

interfaces-api:
in: interfaces/api

# TEST UTILITIES
testutil:
in: testutil
Expand Down Expand Up @@ -570,6 +583,7 @@ deps:
- infra-xdg
- interfaces-cli-ui
- interfaces-tui
- interfaces-api
canUse:
- go-stdlib
- go-sync
Expand Down Expand Up @@ -604,6 +618,22 @@ deps:
- bubbletea
- zap

interfaces-api:
mayDependOn:
- application
- domain-workflow
- domain-ports
- domain-errors
- domain-plugin
- domain-operation
canUse:
- go-stdlib
- go-sync
- huma
- chi
- zap
- uuid

interfaces-cli-ui:
mayDependOn:
- domain-workflow
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **F097**: HTTP REST API server (`awf serve`) — new `internal/interfaces/api/` adapter alongside `cli/` and `tui/` exposing workflow discovery (`GET /api/workflows`, `GET /api/workflows/{name}`, `POST /api/workflows/{name}/validate`), async execution (`POST /api/workflows/{name}/run` returning 202 + `execution_id`), lifecycle control (`GET /api/executions`, `GET /api/executions/{id}`, `DELETE /api/executions/{id}`, `POST /api/executions/{id}/resume`), Server-Sent Events streaming (`GET /api/executions/{id}/events` emitting `step.started`, `step.completed`, `step.failed`, `workflow.completed`, `workflow.failed`, `output`), and history queries (`GET /api/history`, `GET /api/history/stats`); Huma v2 + chi v5 generate OpenAPI 3.1 spec served at `/openapi.json`, `/openapi.yaml`, and Swagger UI at `/docs`; Bridge adapter pattern mirrors `tui/bridge.go` with `sync.Map` tracking active executions; SSE polling at 200ms cadence matching TUI; graceful shutdown via `signal.NotifyContext` waits up to 30s for active streams; default binding `127.0.0.1:2511` (loopback-only, non-loopback opt-in via `--host`); arch-lint enforces `interfaces-api` may import only `application/` and `domain/*` (no `infrastructure/`, `cli/`, or `tui/`); see [ADR-016](docs/ADR/016-http-interface-adapter-huma-sse-streaming.md)

## [0.9.0] - 2026-05-14

### Added
Expand Down
10 changes: 5 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,6 @@ func TestWorkflowValidation(t *testing.T) {

## Architecture Rules

- Validate agent provider options only against what each CLI actually accepts; do not validate against API documentation if the underlying CLI rejects the option
- Plugin binaries must be discoverable at <plugins_dir>/<plugin_name>/awf-plugin-<plugin_name>; host validates binary existence and version compatibility via gRPC handshake after process start
- Commit generated protobuf files (.pb.go, _grpc.pb.go) to git; treat as source artifacts for build reproducibility, not ephemeral build outputs
- CLI command implementations must call infrastructure layer methods rather than reimplementing HTTP requests, parsing, or validation; avoid logic duplication
- Application layer must persist source metadata (SetSourceData) after successful infrastructure installation; omitting state blocks downstream operations like updates
Expand All @@ -240,10 +238,11 @@ func TestWorkflowValidation(t *testing.T) {
- When integrating external UI frameworks, create Bridge adapters in the interface layer that wrap application services; maintain zero infrastructure imports in bridge implementation
- Enforce event propagation depth limits to prevent infinite event loops; set maxPropagationDepth in EventBus and include propagation_depth in protocol buffer event definitions
- Use provider name prefixes for all infrastructure provider helper methods (buildCopilot, extractCopilot, parseCopilot, validateCopilot) to prevent naming collisions across implementations
- Use mutex-protected getter/setter methods (Get*/Set*) for concurrent shared state; apply consistently across all goroutine-accessed fields
- Server owns background task coordination (WaitGroup); pass by pointer to handlers and coordinate shutdown: httpSrv.Shutdown() then sseWG.Wait()

## Common Pitfalls

- When enabling session persistence in CLI providers, force JSON output format for reliable field extraction; document as known limitation that overrides user-specified format
- Always provide graceful fallback to stateless mode when optional session ID extraction fails; never fail the entire operation due to extraction errors
- When migrating API JSON field names, parse both old and new keys with new key preferred; use dual-key parsing for backwards compatibility without validation errors
- Leverage Go's map[string]any behavior to silently ignore unsupported provider options; avoids validation errors while maintaining clear intent
Expand Down Expand Up @@ -284,11 +283,10 @@ func TestWorkflowValidation(t *testing.T) {
- Always test unplanned file modifications discovered during implementation; update task plan if intentional, revert if accidental
- Never use standard YAML unmarshaling for skill metadata; implement frontmatter parsing (YAML header between --- delimiters) to preserve metadata
- Never skip testing XDG directory fallback paths; code will fail on systems without XDG_DATA_HOME and XDG_CONFIG_HOME variables set
- Major feature implementations require supporting infrastructure changes (ExecutionContext getters, helper modifications); document rationale in commit message and update validation plan if discovered

## Test Conventions

- Mock evaluators must have pre-configured results for every expression input; unconfigured expressions return zero value, which may bypass validation checks in evaluation pipelines
- Distinguish fixture path updates (allowed without review) from content changes (require explicit review); document rationale for content modifications in commit message
- Use _Integration suffix for tests requiring live agent execution or system dependencies; keep unit tests suffix-less in domain/application/infrastructure packages
- Separate provider output format validation tests into dedicated *_extract_test.go files; verify extraction patterns before session resume integration tests
- Document provider output format assumptions (JSON wrapper field names, text patterns) in code comments; validate assumptions with assertion-based tests before production
Expand All @@ -307,6 +305,8 @@ func TestWorkflowValidation(t *testing.T) {
- When testing YAML unmarshaling, assert on all nested struct fields; verify that arrays like Events.Subscribe and Events.Emit are populated, not defaulted to empty
- New gRPC and concurrency-heavy infrastructure requires >85% test coverage; run 'make test-race' to verify no data races in stream managers and lock-protected sections.
- Always write unit tests for CLI helper functions; parseInputFlags, resolvePromptInput, categorizeError must have >80% coverage before commit
- HTTP servers require unit tests for the server struct itself: route registration, API initialization, graceful shutdown, not just individual handlers
- Organize interface layer test fixtures in tests/fixtures/<interface-type>/ with descriptive names (e.g., api-simple-success.yaml, api-failing.yaml)

## Review Standards

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ A Go CLI tool for orchestrating AI agents (Claude, Gemini, Codex, GitHub Copilot
- **Built-in HTTP Operation** - Declarative REST API calls (GET, POST, PUT, DELETE) with configurable timeout, response capture, and retryable status codes
- **Built-in Notification Plugin** - Workflow completion alerts via desktop and webhooks with configurable backends
- **Terminal User Interface (TUI)** - Full-screen interactive dashboard (`awf tui`) with tab-based navigation for workflow browsing, real-time execution monitoring, history exploration, agent conversation rendering, and Claude Code session tailing; built on Bubble Tea with Lip Gloss styling and Glamour Markdown rendering
- **HTTP REST API Server** - `awf serve` exposes workflow discovery, async execution, SSE event streaming, lifecycle control, and execution history over HTTP with auto-generated OpenAPI 3.1 spec and Swagger UI at `/docs`; built on Huma v2 + chi v5; defaults to `127.0.0.1:2511` (loopback-only) with `--host`/`--port` overrides

## Installation

Expand Down Expand Up @@ -136,6 +137,7 @@ AWF is a powerful orchestration tool that grants AI agents and workflows direct
| `awf workflow update [name]` | Update an installed workflow pack |
| `awf workflow remove <pack>` | Remove an installed workflow pack |
| `awf workflow search [query]` | Search for workflow packs on GitHub |
| `awf serve` | Start HTTP API server for remote workflow execution and monitoring |
| `awf tui` | Launch the interactive terminal UI |
| `awf upgrade` | Upgrade AWF to the latest version |
| `awf version` | Show version information |
Expand Down
76 changes: 76 additions & 0 deletions docs/ADR/016-http-interface-adapter-huma-sse-streaming.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
title: "016: HTTP Interface Adapter with Huma v2 and SSE Streaming"
---

**Status**: Accepted
**Date**: 2026-05-17
**Issue**: F097
**Supersedes**: N/A
**Superseded by**: N/A

## Context

ADR-001 listed "API (future)" as a planned interface layer alongside CLI. F097 implements that delivery mechanism: a REST/HTTP API that lets external systems (CI pipelines, dashboards, IDE extensions) trigger, monitor, and query AWF workflow executions without shelling out to the CLI.

Two decisions within F097 are architecturally load-bearing beyond the feature itself:

1. **HTTP framework selection** — introducing Huma v2 + chi v5 as new infrastructure. Huma generates OpenAPI 3.1 schemas automatically from Go types, which locks in how the API contract is expressed and validated for all future endpoints. Replacing it later requires rewriting all handler signatures.

2. **Streaming protocol for execution events** — workflow executions are long-running (seconds to minutes). External subscribers need real-time step updates. The choice between SSE, WebSockets, or a push-from-application model defines the client integration surface for every future streaming feature.

## Candidates

### HTTP Framework

| Option | Pros | Cons |
|--------|------|------|
| **Huma v2 + chi v5** | OpenAPI 3.1 auto-generation from Go types; type-safe input validation; chi is standard `net/http` compatible; no separate doc tooling | Less battle-tested than gin/echo; Huma v2 handler signature is non-standard Go |
| **gin** | Mature, widely known, fast | No native OpenAPI generation; separate swagger annotation toolchain needed; docs drift from code |
| **echo** | Balanced performance and ergonomics | Same OpenAPI gap as gin; less active maintenance |
| **net/http + ogen** (spec-first codegen) | Strict contract; spec drives implementation | Requires maintaining a `.yaml` spec separately; adds codegen step to CI |

### Streaming Protocol

| Option | Pros | Cons |
|--------|------|------|
| **SSE (polling-based)** | Unidirectional; simple client-side (`EventSource` API); firewall-friendly (HTTP/1.1); stateless per subscriber | O(subscribers) polling goroutines; couples cadence to internal poll interval |
| **WebSockets** | Bidirectional; lower per-message overhead for high-frequency events | Overkill for unidirectional workflow events; more complex lifecycle (upgrade, ping/pong, reconnect) |
| **Long-polling** | Universally compatible; no keep-alive concern | Thundering herd on state change; harder to implement correctly at scale |
| **Push from ExecutionService** | Zero polling overhead; events pushed on state transition | Requires a new observer port in the application layer — cross-layer coupling that violates Principle 6 for an interface-layer concern |

## Decision

**Framework:** Huma v2 + chi v5.

Huma v2 is the only Go library that generates valid OpenAPI 3.1 (not 2.0 or 3.0) directly from Go struct types without a separate code-gen step. Chi's standard `net/http` compatibility avoids wrapping the existing request context. AWF's primary API consumers are developer tooling and CI; an always-in-sync OpenAPI spec eliminates documentation maintenance burden.

**Streaming:** SSE with 200ms polling of `ExecutionContext.GetAllStepStates()`.

AWF workflow events are unidirectional (server → client). SSE is the standard HTTP mechanism for this pattern. The 200ms cadence matches the existing TUI poll interval (`tui/tab_monitoring.go:71: monitoringTickInterval`) and satisfies NFR-002 (p95 ≤ 100ms latency at ≤ 50 subscribers). The push-from-ExecutionService alternative was rejected because it would require a new domain port and observer registration pattern — introducing application-layer complexity to solve an interface-layer concern.

**Arch-lint scoping:** Huma and chi are declared as vendor blocks usable only by `interfaces-api`, mirroring how `bubbletea` is scoped to `interfaces-tui`. This prevents accidental import from domain or application layers.

## Consequences

**What becomes easier:**
- External systems integrate with AWF without shelling out to the CLI.
- OpenAPI 3.1 spec is always in sync with the implementation; no separate doc maintenance.
- SSE subscribers can use the standard browser `EventSource` API or `curl --no-buffer`.
- New endpoints follow the established Huma handler pattern without further architectural decisions.

**What becomes harder:**
- Replacing Huma v2 requires rewriting all handler signatures and regenerating the OpenAPI spec.
- SSE polling creates O(subscribers) goroutines per active execution; large subscriber counts require monitoring.
- WebSocket upgrades are not possible through the same SSE endpoint; bidirectional communication would require a separate endpoint and a new framework decision.
- Breaking changes to endpoint paths or response shapes require semver major bumps once external consumers build against the OpenAPI contract.

## Constitution Compliance

| Principle | Status | Justification |
|-----------|--------|---------------|
| Hexagonal Architecture | Compliant | HTTP types confined to `interfaces-api`; Huma + chi vendor-scoped to that layer; infrastructure wiring in `interfaces-cli/serve.go`; no HTTP imports in domain or application |
| Go Idioms | Compliant | Standard `net/http` compatible chi router; `context.Context` propagation through all handlers; SSE goroutines select on `r.Context().Done()` before every poll iteration |
| Minimal Abstraction | Compliant | SSE polling reuses existing `GetAllStepStates()` with no new domain ports; 3 local port interfaces are intentional consumer-defined redundancy per ADR-001 pattern |
| Error Taxonomy | Compliant | Existing `StructuredError` codes map to HTTP semantics via middleware (`USER→400`, `WORKFLOW→422`, `EXECUTION→500`, `SYSTEM→503`); no new exit codes required |
| Security First | Compliant | Default `--host=127.0.0.1` loopback binding; non-loopback is opt-in via `--host`; secret masking unchanged at infrastructure layer |
| Test-Driven Development | Compliant | Unit tests per handler; goroutine-leak test for SSE (delta ≤ 5); 50-concurrent-subscriber test for NFR-002; `make test-race` required before merge |
2 changes: 2 additions & 0 deletions docs/ADR/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Numbers are never reused. If a decision is reversed, the original ADR is marked
| [012](012-runtime-shell-detection.md) | Runtime Shell Detection with $SHELL Environment Variable | Accepted |
| [013](013-context-aware-input-ports.md) | Context-Aware Input Ports | Accepted |
| [014](014-shebang-execution-for-script-files.md) | Shebang Execution for Script Files | Accepted |
| [015](015-grpc-go-plugin-transport-for-external-plugins.md) | gRPC via go-plugin as External Plugin Transport | Accepted |
| [016](016-http-interface-adapter-huma-sse-streaming.md) | HTTP Interface Adapter with Huma v2 and SSE Streaming | Accepted |

## Creating a New ADR

Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Learn how to use AWF effectively:
- [Plugins](user-guide/plugins.md) - Extend AWF with custom operations, validators, and step types; transport security (AutoMTLS, binary integrity verification) and log forwarding
- [Plugin Events](user-guide/plugin-events.md) - Real-time event reactivity between plugins and core
- [Workflow Packs](user-guide/workflow-packs.md) - Install, execute (`awf run pack/workflow`), and manage reusable workflow packs with 3-tier path resolution
- [HTTP API](user-guide/api.md) - REST API server with OpenAPI 3.1 spec, async workflow execution, real-time SSE streaming, and remote integration
- [Terminal UI (TUI)](user-guide/tui.md) - Interactive dashboard for workflow browsing, monitoring, history, and agent conversations
- [Upgrading AWF](user-guide/upgrade.md) - Self-update command with version checking, checksum verification, and atomic binary replacement
- [Audit Trail](user-guide/audit-trail.md) - Structured execution audit log with JSONL output
Expand Down
Loading
Loading