Skip to content

Optional resident serve mode for warm backlog reads (addresses #13) - #18

Closed
dan-huminology wants to merge 1 commit into
sandover:mainfrom
Huminology:resident-server
Closed

Optional resident serve mode for warm backlog reads (addresses #13)#18
dan-huminology wants to merge 1 commit into
sandover:mainfrom
Huminology:resident-server

Conversation

@dan-huminology

Copy link
Copy Markdown

Summary

This PR adds an opt-in resident command server for operators who keep full backlog.jsonl history and hit read latency as the log grows (#13).

Default behavior is unchanged: every command still works one-shot with no server running. When an operator starts ergo serve against a repo, subsequent CLI invocations proxy over a per-repo Unix socket and reuse a warm in-memory Graph instead of calling inspectEventLog on every command.

  • No JSONL schema changes — same event types, same flock + optimistic append protocol
  • --no-server — forces today's one-shot path even when a socket is live
  • Reload on mismatch — out-of-band writes (--no-server, crash mid-append) trigger a reload before the next request

Problem

Each CLI invocation opens the repository and scans the full log. On a busy project that grows to tens of MB, list costs on the order of 0.6 s user CPU per call (#13 remeasure comment: 26 MB, 0.62–0.66 s on ergo 6.0.1). Parallel watchers and agents multiply that cost. Prune shrinks the file but discards audit history.

The missing piece is not a new store — it is a process that keeps the derived Graph and applies incremental updates.

Approach

  1. Session — holds Repository, *Graph, eventLogRead, journal, and a file fingerprint. Mutations use warm updateLoaded / updateLoadedWithJournal (same validation and append as Update, without loadWithRead on the hot path).

  2. ergo serve — one process per .ergo directory; listens on .ergo/ergo.sock, writes .ergo/ergo.pid. One mutex serializes mutations.

  3. CLI proxy — proxied commands dial the socket, send a length-prefixed JSON envelope (method, dir, payload, optional stdin for body), and print the response. Missing socket or dial failure falls back to the existing one-shot path.

  4. Body on the wire — body bytes travel in stdin with a small metadata struct; []byte is not JSON-marshaled on the wire.

Commands that never proxy: init, serve, version, quickstart, where, info, help.

Out of scope

  • Auto-start when the socket is missing
  • TCP / Windows named pipes
  • Mongo, SQLite, or any alternate durable store
  • Changes to list JSON shape or event record fields

Operator UX

ergo --dir /path/to/repo serve          # terminal 1
ergo --dir /path/to/repo list --json    # terminal 2 — proxied when socket is live
ergo --dir /path/to/repo --no-server list   # escape hatch

serve logs each request to stderr (method, latency, client pid/ppid/argv).

Performance

Measured 2026-08-30 on ergo resident-server, live backlog 6.8 MB / 10,195 lines. Five consecutive /usr/bin/time runs, stdout discarded, warm serve running.

Command One-shot user CPU Proxied client wall Server handler
list --json 0.14–0.17 s ~0.01 s ~20–30 ms
list --ready ~0.19 s ~0.01 s ~20–30 ms
show <id> ~0.15 s ~0.00 s negligible

Proxied CLI user CPU is near zero because the scan runs in serve, not in each short-lived client. The win is faster end-to-end return (~15× on list --json for this file size) and eliminating repeated full-file reads from frequent callers.

Unit test: OpenSession + two List calls → injected inspectEvents counter stays at 1.

Tests

go test ./...
Area File
Warm session: no rescan on second List; out-of-band reload; warm claim internal/ergo/session_test.go
Wire protocol: body roundtrip, dir mismatch internal/ergo/protocol_test.go
Serve roundtrip; second serve fails when socket live cmd/ergo/serve_roundtrip_test.go

Existing cmd/ergo integration tests pass with no socket present.

Reviewer notes

  • Application methods are thin wrappers: OpenSessionSession method → Close
  • plan* extracts share mutation logic between cold Update helpers and warm Session
  • compact / prune run the cold path, then Session.reload()
  • Wire dir must match the directory the server opened
  • Unix domain socket only (Linux/macOS)

Test plan

  • go test ./...
  • One-shot path unchanged (--no-server)
  • Proxied list, show, claim, body match one-shot output
  • Second serve on same .ergo exits non-zero
  • Out-of-band append visible after reload
  • Timings above on live backlog

— Dan's AI Helper

Introduce Session with in-memory graph, Unix-socket proxy in the CLI,
ergo serve, reload-on-mismatch, and --no-server one-shot fallback.
No JSONL schema changes; existing tests pass.
@sandover

sandover commented Sep 9, 2026

Copy link
Copy Markdown
Owner

I appreciate the PR, but I came up with a solution that gives us about a 10x speedup for many operations, without changing the operating model of ergo. Closing this for now, but please lmk if the new approach works for you.

@sandover sandover closed this Sep 9, 2026
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.

3 participants