Repository navigation
Rebase cs serve HTTP+SSE API and OTel tracing onto upstream 1.0.20 - #2
Draft
BippleDops wants to merge 2 commits into
Draft
BippleDops wants to merge 2 commits into
BippleDops wants to merge 2 commits into
Conversation
Adds a `cs serve` subcommand that exposes claude-squad's session
lifecycle as an HTTP+SSE API, so an external orchestrator can drive
it without the TUI. Fully additive: the TUI, config format, on-disk
state, autoyes daemon, and every existing command behave byte-for-
byte the same as before.
claude-squad is excellent at the tmux-per-agent + worktree-per-agent
isolation model, but today the only way in is the Bubble Tea TUI.
External orchestrators that want to drive sessions from another
process have to scrape `tmux capture-pane` output and parse the
undocumented `~/.claude-squad/state.json` — fragile, and it makes
per-session tracing essentially impossible.
This PR adds the programmatic surface so external tools can reuse
claude-squad's session model without diverging from it. The first
real consumer is Paperclip, a multi-agent orchestration GUI; see
FORK.md for context.
Only the HTTP surface in this commit. A follow-up commit in the
same series adds OpenTelemetry instrumentation that uses the new
`eventBus` to emit `cs.instance.*` spans — reviewable independently.
Changes (all additive):
- `FORK.md` at the repo root explaining the fork's rationale.
- `server/` new package:
- `server.go` — net/http Server, bearer-token middleware, graceful
shutdown on SIGTERM/SIGINT.
- `handlers.go` — route handlers for `/v1/health`,
`/v1/instances` (GET list, POST create), and
`/v1/instances/{id}{,/pane,/input,/pause,/resume,/diff}` plus
`/v1/events` (SSE).
- `types.go` — JSON DTOs and the `Event` schema with its lifecycle
type constants.
- `store.go` — concurrency-safe in-memory registry of active
sessions, keyed by UUID. Not persisted to disk on purpose —
the orchestrator is the source of truth for what should exist.
- `events.go` — small pub/sub ring for the SSE endpoint with a
replay buffer so reconnecting consumers can catch up via the
`?since=N` query parameter.
- `main.go` gains the `serve` cobra subcommand, flag-configurable
bind address (`--addr`, default `:3200`) and bearer token
(`--auth-token`, or `CS_AUTH_TOKEN`). Empty token disables auth
for local dev only.
- `go.mod`/`go.sum` gain `github.com/google/uuid v1.6.0`.
| Method | Path | Purpose |
|---|---|---|
| GET | `/v1/health` | liveness (always unauth'd) |
| GET | `/v1/events?since=<seq>` | SSE stream of lifecycle events with optional replay |
| POST | `/v1/instances` | create a session; body accepts `title`, `program`, `branch`, `autoYes`, `prompt`, `workspaceBasePath` |
| GET | `/v1/instances` | list all active sessions |
| GET | `/v1/instances/{id}` | get one session |
| DELETE | `/v1/instances/{id}` | kill + cleanup |
| GET | `/v1/instances/{id}/pane?full=&ansi=` | tmux pane content |
| POST | `/v1/instances/{id}/input` | send text/keystrokes; body accepts `prompt`, `keys`, or `tapEnter` |
| POST | `/v1/instances/{id}/pause` | pause (remove worktree, keep branch) |
| POST | `/v1/instances/{id}/resume` | resume a paused session |
| GET | `/v1/instances/{id}/diff` | compute + return the session's git diff |
SSE events emitted: `instance.created`, `instance.started`,
`instance.status_changed`, `instance.diff_updated`,
`instance.pane_appended`, `instance.prompt_detected`,
`instance.prompt_auto_accepted`, `instance.paused`,
`instance.resumed`, `instance.killed`, `instance.exited`.
All events carry a monotonic `seq` so clients can reconnect with
`?since=N` and not miss anything in the replay window (512 events).
Bearer token in `Authorization: Bearer <token>`, configured via
`--auth-token` or `CS_AUTH_TOKEN`. `/v1/health` is always public.
Empty token disables auth entirely — intended for local dev.
Binary build is green. Manually verified against `cs serve`:
- `/v1/health` returns `{"ok":true,"service":"claude-squad-server","version":"1.0.17"}`
- `/v1/instances` returns `[]` on empty server
- `/v1/instances/nonexistent` returns 404 with a typed error body
- Missing bearer → 401 `missing bearer token`
- Wrong bearer → 401 `invalid bearer token`
- Correct bearer → 200
Create + kill + pane + SSE flows are exercised by the consumer (see
Paperclip harness). Not repeated here because driving a real `claude`
subprocess requires an API key and is more appropriately tested
end-to-end in the consumer's CI.
This is a FORK of upstream smtg-ai/claude-squad prepared for an
early-draft upstream PR.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: jondsussman <jondsussman@gmail.com>
Adds OTLP/HTTP trace export with W3C TRACEPARENT propagation to the
agent subprocess, activated when Langfuse (or any OTLP collector)
credentials are in env. Silent no-op otherwise — upstream behavior
preserved.
This commit is reviewable independently of the HTTP API commit. It
adds three small pieces:
1. **`otel/` package** (net-new) — tracer provider bootstrap and
subprocess env builder.
2. **A per-instance `SpawnEnv` field** on `session.Instance` and
`tmux.TmuxSession` (5-line additions to each). When set, the env
is merged into the `tmux new-session ... <program>` command so
the agent inherits `TRACEPARENT` + `OTEL_*` vars. When not set
(every upstream caller, always), the behavior is identical to
before.
3. **Span emission in the server handlers** — `cs.instance.create`,
`cs.instance.pause`, `cs.instance.resume`, `cs.instance.kill`.
Each parents under the inbound W3C trace context if any.
Service name: `claude-squad-server` (distinct from the orchestrator's
and the agent's service names so downstream observability tooling can
filter each tier cleanly).
Same pattern the downstream consumer (Paperclip) uses:
- `LANGFUSE_PUBLIC_KEY`
- `LANGFUSE_SECRET_KEY`
- `OTEL_EXPORTER_OTLP_ENDPOINT` (or `LANGFUSE_HOST`; default
`http://localhost:3050/api/public/otel`)
When the two keys are absent, `cs serve` boots without telemetry. No
spans, no exporter, no overhead.
`cs serve` extracts W3C trace context from inbound `traceparent`
HTTP headers so an orchestrator like Paperclip that creates a root
`paperclip.run` span can pass its id forward and see
`paperclip.run → cs.instance.create → claude_code.interaction` as
one connected trace in Langfuse.
The Claude Code CLI (v1+) honors `TRACEPARENT` from its environment
and parents its own spans accordingly. No Claude Code code changes
needed — just env injection, which this commit plumbs through.
Langfuse does NOT support OTLP/gRPC. The exporter is locked to
`http/protobuf`. The subprocess env builder hard-codes
`OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf` for the agent so it
doesn't drift.
- `otel/tracer.go` — BatchSpanProcessor + otlptracehttp exporter.
Resource carries `service.name=claude-squad-server` +
`service.version`. 1s batch interval so short-lived sessions don't
drop spans on exit.
- `otel/propagation.go` — `SubprocessEnv()` returns the KEY=VALUE
slice to hand to `exec.Cmd.Env` (via the Instance.SetSpawnEnv
plumbing below). `TraceparentFromContext()` is the W3C extract
for header/env injection.
- `server/server.go` — `Options` gains `OtelCfg otelpkg.Config`;
Server holds a `tracer` field initialized from the global
provider (no-op when none is registered).
- `server/handlers.go` — `cs.instance.create` span in
`handleCreate`; `quickSpan` helper reused by pause/resume/kill.
`handleCreate` extracts the inbound W3C traceparent and calls
`inst.SetSpawnEnv(otelpkg.SubprocessEnv(ctx, cfg, "cs-agent-<short>"))`.
- `session/instance.go` — one new field (`spawnEnv`) + one new
method (`SetSpawnEnv`). `Start()` forwards to the tmux session's
new setter before spawning.
- `session/tmux/tmux.go` — one new field (`spawnEnv`) + one new
method (`SetSpawnEnv`). `Start()` honors it when assembling the
`exec.Cmd` env; when empty, preserves upstream inherit-all
behavior.
- `main.go` — `serveCmd.RunE` calls `otelpkg.ConfigFromEnv()` +
`otelpkg.Init()` before starting the server, plus a deferred
flush.
- `go.mod`/`go.sum` — `go.opentelemetry.io/otel` and the OTLP HTTP
exporter.
Build green. Live boot with Langfuse keys produces the log line:
otel tracing enabled → localhost:3050/api/public/otel/v1/traces (service=claude-squad-server)
Without keys, the line is absent and the server runs exactly as
commit 1. The same smoke-suite from commit 1 still passes.
This commit is additive and gated — if OTEL doesn't fit your
project's direction, commit 1 stands on its own without it. The
only sub-commit-1 changes are the 5-line `SetSpawnEnv` hooks in
session/tmux and session/instance, which have no effect unless a
caller actively sets them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: jondsussman <jondsussman@gmail.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The fork's only owner work lives on
origin/paperclip(2 commits from 2026-04-18, the substrate forpaperclipPhase 5 and upstream draft PR smtg-ai#283). That branch is based on the April upstream. This PR re-applies those two commits on top of current upstreammain(1.0.20) so they stay mergeable:feat: cs serve — optional HTTP + SSE API for headless usefeat: optional OpenTelemetry tracing for cs serveConflicts were limited to
go.mod/go.sum(indirect deps); resolved by taking upstream's module files and runninggo mod tidy, which re-addedgithub.com/google/uuidand thego.opentelemetry.io/otel/*modules the new code needs.This PR is based on
cursor/sync-upstream-65a7(#1) so it shows only the two owner commits; GitHub will retarget it tomainonce #1 merges.Verification
go build ./...— passgo vet ./...— passgo test ./...— passNotes
origin/paperclipitself was not touched (no force-push). If you adopt this branch, you can point PR [draft / RFC] cs serve: optional HTTP+SSE API + OTEL — for API-shape discussion smtg-ai/claude-squad#283 at it or updatepaperclipafter merging.papercliprepo (docs/superpowers/patches/) by the companionpaperclipPR, so the work survives even if this fork is deleted later.Before merging