* feat(backend): HTTP daemon skeleton — config, health, runfile, graceful shutdown (#10) Phase 1a of the Go HTTP daemon lane (#10). Stands up the loopback-only sidecar skeleton the later REST/SSE/WS/static surfaces build on: - config: env-driven (AO_HOST/PORT/ENV/timeouts/run-file) with zero-config defaults; binds 127.0.0.1:3001; validates and fails fast on bad input. - httpd: chi router with the recoverer → request-id → logger → real-ip middleware stack and /healthz + /readyz probes. Per-request timeout is carried in config but intentionally not global — it scopes to /api/v1 in Phase 1b so it never throttles SSE/WS/health. - runfile: atomic PID + port handshake (running.json) for the Electron supervisor, with a dead-PID stale check so a crashed predecessor doesn't block startup while a live one fails fast. - server: bind-before-publish (port conflict fails fast), graceful shutdown on SIGINT/SIGTERM via signal.NotifyContext with a 10s hard timeout, and run-file cleanup on exit. Why: the daemon must be safely supervisable as a child process — the supervisor needs a discoverable PID/port and the daemon must not leave a half-started process or stale handshake behind. Locking the lifecycle down now keeps the future port split a small change rather than a rewrite. Tests cover config defaults/overrides/validation, run-file round-trip and live/dead PID detection, health probes, full Run lifecycle, and port-conflict fail-fast. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(backend): drop Env config field — not needed yet (#10) Per review on #14: AO_ENV / Config.Env / IsProduction() weren't load-bearing for Phase 1a — they only switched the slog handler. Removing them now keeps the surface minimal; the env knob can come back later when a real consumer needs it. - config: remove Env field, AO_ENV parsing, and IsProduction helper. - main: collapse newLogger to a single text-handler path. - httpd: drop the env field from the listening log line. - tests: drop the env assertions and AO_ENV fixture. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs: add backend run + config quick-start to README (#10) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(backend): address Phase 1a review comments (#10) - config: drop AO_HOST entirely — the daemon is loopback-only by design, so making the bind host env-configurable was a security footgun - config: use net.JoinHostPort in Addr() so IPv6 literals stay valid - config: reject zero/negative AO_REQUEST_TIMEOUT and AO_SHUTDOWN_TIMEOUT (time.ParseDuration accepts both; either would silently break the daemon — instant request expiry / no graceful drain) - runfile: split processAlive into unix/windows build-tagged files so liveness detection is reliable on both platforms (Windows uses OpenProcess; POSIX keeps signal 0) - runfile: document os.Rename overwrite semantics (atomic on POSIX, REPLACE_EXISTING on Windows) so the temp-then-rename pattern's cross-platform behaviour is explicit - httpd tests: give probe/waitForHealth clients an explicit per-request timeout so a stalled connect can't hang the test on the outer deadline * fix(backend): strip trailing blank line from runfile.go (#10) gofmt CI was failing because removing the orphan processAlive doc comment left an extra newline at EOF. * fix(backend): cross-platform run-file replace + AO_HOST rationale (#10) - runfile: introduce build-tagged atomicReplace — POSIX rename(2) on Unix, MoveFileEx with MOVEFILE_REPLACE_EXISTING on Windows. The Go runtime happens to do the Windows call internally already, but invoking it directly makes the cross-platform contract explicit instead of a runtime implementation detail - runfile: tighten process_unix.go build tag from `!windows` to `unix` so plan9/js/wasm fail to build rather than silently using a broken signal-0 probe - runfile: add TestWriteOverwritesExisting covering the stale run-file replace path that none of the previous tests exercised - config: anchor the loopback-only decision in the LoopbackHost doc so the next contributor doesn't reintroduce AO_HOST without the security rationale * fix(backend): route chi access logs through slog/stderr (#10) chi's middleware.Logger writes via stdlib log to stdout, but the daemon's slog logger writes to stderr — so REST traffic and daemon logs landed on different streams in different formats. Replace it with a small slog-backed requestLogger that: - Wraps the response writer via middleware.NewWrapResponseWriter so status/bytes are accurate even when handlers return without an explicit WriteHeader. - Reads the request id off the context set by middleware.RequestID (kept mounted just before this middleware so the id is available). - Emits one structured Info line per request with method, path, status, bytes, duration, and remote — same key=value shape as the rest of the daemon, one stream for the Electron supervisor to capture. --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|---|---|---|
| .github/workflows | ||
| backend | ||
| docs | ||
| frontend | ||
| .gitignore | ||
| README.md | ||
README.md
agent-orchestrator
Rewrite of the agent-orchestrator: a long-running Go backend daemon (backend/)
paired with an Electron + TypeScript frontend (frontend/).
See docs/ for architecture and status — start with the
Lifecycle Manager + Session Manager lane in docs/architecture.md.
Backend daemon
The Go binary in backend/ is the HTTP daemon — a loopback-only
sidecar the Electron supervisor will spawn (Phase 1c). Phase 1a landed the
skeleton: chi router, middleware stack (recoverer → request-id → logger →
real-ip), /healthz + /readyz, atomic running.json PID/port handshake,
graceful shutdown on SIGINT/SIGTERM.
Run
cd backend
go run . # binds 127.0.0.1:3001 with all defaults
AO_PORT=3019 go run . # override per invocation
Health check:
curl localhost:3001/healthz # {"status":"ok"}
curl localhost:3001/readyz # {"status":"ready"}
Configuration (env only)
The bind host is always 127.0.0.1: the daemon is a loopback-only sidecar
and binding any other interface would be a security regression, so the host
is intentionally not env-configurable.
| Var | Default | Purpose |
|---|---|---|
AO_PORT |
3001 |
bind port; fails fast if taken |
AO_REQUEST_TIMEOUT |
60s |
per-request timeout (Go duration) |
AO_SHUTDOWN_TIMEOUT |
10s |
graceful-shutdown hard cap |
AO_RUN_FILE |
<UserConfigDir>/agent-orchestrator/running.json |
PID + port handshake path |
Test
cd backend
gofmt -l . && go build ./... && go vet ./... && go test -race ./...