Skip to content

Central machine log streaming + tail (fold logs) - #2

Merged
blakebauman merged 1 commit into
mainfrom
feat/central-machine-logs
Jun 18, 2026
Merged

Central machine log streaming + tail (fold logs)#2
blakebauman merged 1 commit into
mainfrom
feat/central-machine-logs

Conversation

@blakebauman

Copy link
Copy Markdown
Owner

Context

Next roadmap item after the security/reliability hardening: operability. App stdout/stderr was only available as a one-shot per-machine snapshot pulled from the host admin port (FetchMachineLogs) — not centralized, not tailable, capped at a tail, lost when the machine/host dies, and requiring direct host reachability. For an AI-agent runtime, not being able to see what your deployed agent prints is the biggest user-facing gap. This wires up a full central log path so fold logs tails live app output.

What changed

Capture (agent/runtime). Runtime gains LogsSince for incremental capture:

  • dockerdocker logs --timestamps with a full-precision per-machine cursor, so lines ship exactly once across ticks (the cursor isn't lost to the millisecond rounding of the wire type).
  • wasm — tracks a read offset on the in-memory buffer.
  • firecracker/microvm — no-op; the serial console still goes to the host journal (per-machine capture there stays roadmap).

Ship (agent → control). Agents push new lines each heartbeat to a new agent-authed POST /v1/agent/logs, separate from the heartbeat payload so the reconcile path stays lean. Per-tick payload is capped.

Persist (control/store). Control resolves each line's machine to its owning app (dropping lines for unknown machines) and writes to a new machine_logs table, bounded to the most recent lines per app (oldest trimmed on insert).

Tail (api/cli). GET /v1/apps/{app}/applogs streams over SSE (recent backlog, then follow), filterable by ?machine=. The CLI:

  • fold logs — tails app stdout/stderr across machines (default follow)
  • fold logs --machine <id> — filter to one machine
  • fold logs --no-follow — print backlog and exit
  • fold logs --system — the activity stream (deploy/scale/build), unchanged

fold deploy's live build-output streaming is untouched (still the activity SSE).

CLI UX note

fold logs now defaults to app output (conventional, like fly/heroku/kubectl) rather than the activity event stream. Activity moves under --system. Chosen deliberately over keeping the old default.

Tests

  • store: roundtrip, machine filter, cursor, tail, retention trim
  • agent: docker --timestamps parser + dedup cursor, wasm buffer ReadNew
  • control: machine→app mapping drops lines from unknown machines
  • api: agent-only ingest auth (user token rejected), owner tail returns shipped line, cross-org tail is 404

Verification

  • gofmt -l clean · go vet ./... clean · go test ./... all pass · go build -tags firecracker ./... compiles
  • make e2e-local passes (no regression in deploy/scale-to-zero/wake)
  • Manual: deployed hello-go locally and confirmed fold logs shows its stdout exactly once (dedup verified), --system shows build/deploy events

🤖 Generated with Claude Code

App stdout/stderr was only available as a one-shot per-machine snapshot
pulled from the host admin port — not centralized, not tailable, lost on
restart, and requiring host reachability. This wires up a full path so
`fold logs` shows live app output.

- Runtime gains LogsSince for incremental capture: docker uses
  `docker logs --timestamps` with a full-precision per-machine cursor so
  shipping is de-duplicated across ticks; wasm tracks a buffer read offset;
  firecracker/microvm are no-ops (serial console still goes to the host
  journal — per-machine capture there stays roadmap).
- Agents ship new lines each heartbeat to POST /v1/agent/logs (separate
  from the heartbeat payload to keep the reconcile path lean).
- Control resolves each line's machine to its app and persists to a new
  machine_logs table, bounded to the most recent lines per app.
- GET /v1/apps/{app}/applogs streams them over SSE (recent backlog, then
  follow), filterable by ?machine=.
- `fold logs` now tails app output by default (--machine to filter,
  --no-follow for a snapshot); `fold logs --system` keeps the activity
  stream (deploy/scale/build). `fold deploy`'s build streaming is
  unchanged.

Tests: store roundtrip + retention, docker log parser + dedup cursor,
wasm buffer cursor, control machine->app mapping (drops unknown machines),
and an API end-to-end (agent-only ingest auth + owner tail + cross-org
denial). Verified with make e2e-local and a manual deploy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@blakebauman
blakebauman merged commit 4161eca into main Jun 18, 2026
0 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant