Ctx serve
The agent context layer — converge messy multi-session AI-agent work into one lean, LIVING, spec-centered source of truth that doesn't rot. Organize docs by lifetime, keep the spec generative, and let an agent (or a human three months later) build from it.
npx -y skills add motiful/ctx --skill ctx-serveAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Hosts a project's long-running processes — dev servers, watchers, build daemons — in tmux so they survive session exit and are shared across parallel agent sessions on one repo, recording their durable topology in a committed service manifest and detecting live status rather than storing it. Use when a task needs a dev server or watcher running, when parallel sessions must share one running service, when a process must outlive a /compact or session reset, or when reattaching to or restarting a service a prior session started. Records topology in ctx/services.md; a narrow operational companion in a ctx knowledge base. Not for one-shot foreground commands.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
9.4 KB, as published. Nobody here has run it
ctx-serve — host long-running processes across sessions
The service manifest is a LIVING doc (lifetime model in
../ctx). ctx records the diffable topology (what runs, where, how) — not the running process and not its live status; live status is detected, never stored.
Execution Procedure
ctx-serve hosts the processes a project needs — a dev server, a file watcher, a build daemon — detached in tmux so one running instance survives a session exit and is shared by every parallel session on the repo. Its own job is small: ensure the host · detect-before-start · start namespaced + readiness-polled · record the durable topology · reattach/restart.
serve(process) → one shared instance, survivable across sessions
# STEP 0 — Ensure the host (tmux) — a precondition gate
command -v tmux ?
present → continue
absent → install with a one-line OK (macOS `brew install tmux` · Debian/Ubuntu
`apt-get install tmux` · …), verify `tmux -V`, continue
can't install AND the process needs no stdin
→ setsid headless fallback: start detached + tee a logfile + kill-by-port
restart (NO send-keys — setsid cannot feed stdin)
# STEP 1 — Detect before you start (live status is DETECTED, never stored)
up = lsof -ti:PORT (or curl the endpoint)
up → USE it — a parallel session already hosts it; do NOT double-start
down → start it (STEP 2)
# STEP 2 — Start detached, namespaced, readiness-polled
name = "<repo>-<service>" # namespaced so parallel sessions cannot collide
tmux has-session -t name ? → reuse : create # never blind-create; never kill another session's server
tmux new-session -d -s name '<cmd, teed to a logfile>'
# build the argv as an ARRAY / write a temp launch script — never shell-interpolate the command
poll( lsof -ti:PORT || ready-line in the logfile ) until ready or timeout
# new-session returning ≠ the service is up — the port is not bound yet
# STEP 3 — Interact / read
tmux send-keys -t name '<input>' Enter # feed stdin (tmux only)
tmux capture-pane -t name -p -S -<n> # PEEK, bounded history — a debug snapshot, NOT the log
# STEP 4 — Record the durable topology (once; and again only when it changes)
write ctx/services.md # 5 fields per service (below); COMMIT it
on a topology change (new service · port change):
point the active progress leaf at ctx/services.md # the one tracking pointer (serve ⟂ progress, below)
# STEP 5 — Stop / restart
tmux kill-session -t name | kill $(lsof -ti:PORT)
# Before committing — apply the cross-cutting constraints
apply("../ctx/references/consistency.md") # same-change (services.md ↔ the launch cmd ↔ the progress pointer) · gate
Why tmux (and when setsid)
tmux is the default host because it is a strict superset of a bare background process: it starts detached (survives the session that launched it), lets any later session reattach (tmux attach / capture-pane), and — uniquely — feeds stdin via send-keys. setsid/nohup also survive a session exit but cannot feed stdin once detached, so they are the headless fallback only: a dev server that just needs starting, logging, and killing, on a box with no tmux. Prefer tmux; drop to setsid only when it is absent and the process needs no input.
The service manifest — ctx/services.md
A small, committed, LIVING file: the durable topology of what this project hosts. It is not progress and not live status — it is "how this project runs," the thing a new session, a new machine, or a teammate reads to bring the services up. Five fields per service:
| field | example |
|---|---|
| name | myapp-web (the namespaced tmux session name) |
| endpoint | http://localhost:3000 |
| start | pnpm dev (the command STEP 2 launches, teed to the log) |
| log | ctx/scratch/logs/myapp-web.log |
| check / restart | lsof -ti:3000 to detect · re-run start to restart |
COMMIT it — it is durable topology, not volatile state (the same reason a docker-compose.yml is committed). It changes rarely; when the topology genuinely changes (a new service, a moved port), update the manifest in the same commit as the change that caused it (consistency.md Rule 2). It records topology + intent only, never the running bytes — the content boundary a ctx store holds (diffable intent in, process/binary out).
serve ⟂ progress — orthogonal, coupled by ONE tracking pointer
Serving and progress are two axes, and keeping them separate is deliberate:
| ctx-serve (infrastructure axis) | ctx-progress (work axis) | |
|---|---|---|
| tracks | what runs · ports · start/restart | where the work is · next · read-first |
| truth | services.md (topology, committed) + live status (detected, never stored) | the progress tree |
| a dev server running | = infrastructure state | ≠ a work milestone |
So do not merge them: a service's up/down is not progress (and it is live status → never stored); work state is not a manifest fact. The only coupling is a single tracking pointer: when a session changes the topology (first stands a server up, moves a port), that is a durable change this turn, so the active progress leaf records a one-line pointer to ctx/services.md — exactly as it points to a new ADR or an edited spec (ctx-progress's pointer, don't restate). On the ordinary days a service just runs, progress says nothing about it. That pointer is the whole of the "keep progress in the loop" coupling — no new mechanism.
Live status is detected, never stored
The manifest says a service should be at :3000; whether it is up right now is read on demand (lsof / a request), never written to a file that would go stale. An agent hits the endpoint, gets connection-refused, confirms it is down, restarts per the manifest — the manifest never changes. This is why there is no "status" field: a detected truth cannot rot, a stored one can.
Hosting hygiene (hard constraints)
Domain MUST/NEVER for hosting a process — binding, load-before-act. The cross-cutting rules (single-source, same-change, verify-canonical, the gate) live in
../ctx/references/consistency.mdand apply on top.
- MUST poll for readiness after start.
tmux new-session -d(orsetsid) returning means the process was launched, not that the port is bound — an immediatelsofcheck false-negatives. Poll the port or a log ready-line with a timeout before reporting "up." (so-that: a parallel session doesn't declare the server ready and connect to nothing.) - MUST namespace the session and check existence before creating. Derive
namefrom<repo>-<service>andtmux has-session -t namefirst. Parallel agent sessions on one repo share a tmux server; a blindnew-sessionor a barekill-sessioncan collide with — or kill — another session's service. (so-that: one session's serve action cannot start a duplicate on, or kill, another live session's server.) - MUST pass the launch command safely. Build the tmux argv as an array (no
bash -c "…<interpolated cmd>…"); for anything with quotes/$/spaces, write a temp launch script and exec that. A command spliced into a shell string breaks or injects. - MUST read logs with a bounded history, and treat the logfile — not the pane — as the durable log.
capture-pane -palone returns only the visible screen (bursts scroll off); use-S -<n>, and for any load-bearing decision (health, error surfacing) read the tee'd logfile.capture-paneis a debug snapshot that reflows on resize — never the source of truth for a service's output. - NEVER build an input-protection / "who typed this" layer. That machinery (save-and-restore a human's in-progress input, distinguish injected vs human keystrokes) solves a conversational agent-supervising-agent problem. A hosted dev server has no user draft to protect and does not type back as a human — porting it is pure complexity ctx-serve does not need.
Honest limits
tmux must be learned — it is not a native agent tool, which is the one real cost of hosting this way (the STEP-0 gate pays it once). setsid cannot feed stdin, so an interactive process on a tmux-less box is out of reach. capture-pane is a snapshot, not a stream — the logfile is the real record. And ctx-serve hosts processes; it does not supervise a conversational agent in a pane (that is a different tool with the input-protection problem this skill deliberately avoids).