From 97329b19ca4e6b7c333b6d8cb13e57fe1dfc6964 Mon Sep 17 00:00:00 2001 From: Rob Zolkos Date: Wed, 30 Sep 2026 09:47:42 -0400 Subject: [PATCH 1/2] Hide connect service until it is ready The service isn't ready to hand out. Its unit runs the connector in the home directory, where workers may change files without asking; it freezes PATH at install, so upgrading the worker can quietly break it; it is Linux-only, where most people will be on Macs; and it hasn't been tried on a real machine. `connect service` is hidden from help and the command catalog, and the connect skill no longer tells assistants to install it. It says instead that the person starts the connector in the folder it should work in and leaves it running, that stopping is safe, and that a restarted connector catches up. The code and its tests stay for the follow-up that makes the service safe on Linux and Macs. The command was never in a release, so its removal from the surface snapshot is acknowledged in .surface-breaking. --- .surface | 72 ----------------------- .surface-breaking | 72 +++++++++++++++++++++++ internal/commands/commands.go | 2 +- internal/commands/connect.go | 4 +- internal/commands/connect_service.go | 10 +++- skills/basecamp-connect/SKILL.md | 85 +++++++++++----------------- 6 files changed, 116 insertions(+), 129 deletions(-) diff --git a/.surface b/.surface index dc6d51651..68eba3d39 100644 --- a/.surface +++ b/.surface @@ -677,9 +677,6 @@ CMD basecamp connect doctor CMD basecamp connect import CMD basecamp connect redispatch CMD basecamp connect release -CMD basecamp connect service -CMD basecamp connect service install -CMD basecamp connect service uninstall CMD basecamp connect setup CMD basecamp connect shadow CMD basecamp connect shadow promote @@ -5489,72 +5486,6 @@ FLAG basecamp connect release --stats type=bool FLAG basecamp connect release --styled type=bool FLAG basecamp connect release --todolist type=string FLAG basecamp connect release --verbose type=count -FLAG basecamp connect service --account type=string -FLAG basecamp connect service --agent type=bool -FLAG basecamp connect service --cache-dir type=string -FLAG basecamp connect service --count type=bool -FLAG basecamp connect service --help type=bool -FLAG basecamp connect service --hints type=bool -FLAG basecamp connect service --ids-only type=bool -FLAG basecamp connect service --in type=string -FLAG basecamp connect service --jq type=string -FLAG basecamp connect service --json type=bool -FLAG basecamp connect service --markdown type=bool -FLAG basecamp connect service --md type=bool -FLAG basecamp connect service --no-hints type=bool -FLAG basecamp connect service --no-stats type=bool -FLAG basecamp connect service --profile type=string -FLAG basecamp connect service --project type=string -FLAG basecamp connect service --quiet type=bool -FLAG basecamp connect service --stats type=bool -FLAG basecamp connect service --styled type=bool -FLAG basecamp connect service --todolist type=string -FLAG basecamp connect service --verbose type=count -FLAG basecamp connect service install --account type=string -FLAG basecamp connect service install --agent type=bool -FLAG basecamp connect service install --cache-dir type=string -FLAG basecamp connect service install --count type=bool -FLAG basecamp connect service install --help type=bool -FLAG basecamp connect service install --hints type=bool -FLAG basecamp connect service install --hold type=bool -FLAG basecamp connect service install --ids-only type=bool -FLAG basecamp connect service install --in type=string -FLAG basecamp connect service install --jq type=string -FLAG basecamp connect service install --json type=bool -FLAG basecamp connect service install --markdown type=bool -FLAG basecamp connect service install --md type=bool -FLAG basecamp connect service install --no-enable type=bool -FLAG basecamp connect service install --no-hints type=bool -FLAG basecamp connect service install --no-stats type=bool -FLAG basecamp connect service install --profile type=string -FLAG basecamp connect service install --project type=string -FLAG basecamp connect service install --quiet type=bool -FLAG basecamp connect service install --shadow type=bool -FLAG basecamp connect service install --stats type=bool -FLAG basecamp connect service install --styled type=bool -FLAG basecamp connect service install --todolist type=string -FLAG basecamp connect service install --verbose type=count -FLAG basecamp connect service uninstall --account type=string -FLAG basecamp connect service uninstall --agent type=bool -FLAG basecamp connect service uninstall --cache-dir type=string -FLAG basecamp connect service uninstall --count type=bool -FLAG basecamp connect service uninstall --help type=bool -FLAG basecamp connect service uninstall --hints type=bool -FLAG basecamp connect service uninstall --ids-only type=bool -FLAG basecamp connect service uninstall --in type=string -FLAG basecamp connect service uninstall --jq type=string -FLAG basecamp connect service uninstall --json type=bool -FLAG basecamp connect service uninstall --markdown type=bool -FLAG basecamp connect service uninstall --md type=bool -FLAG basecamp connect service uninstall --no-hints type=bool -FLAG basecamp connect service uninstall --no-stats type=bool -FLAG basecamp connect service uninstall --profile type=string -FLAG basecamp connect service uninstall --project type=string -FLAG basecamp connect service uninstall --quiet type=bool -FLAG basecamp connect service uninstall --stats type=bool -FLAG basecamp connect service uninstall --styled type=bool -FLAG basecamp connect service uninstall --todolist type=string -FLAG basecamp connect service uninstall --verbose type=count FLAG basecamp connect setup --account type=string FLAG basecamp connect setup --agent type=bool FLAG basecamp connect setup --allow type=stringArray @@ -18859,9 +18790,6 @@ SUB basecamp connect doctor SUB basecamp connect import SUB basecamp connect redispatch SUB basecamp connect release -SUB basecamp connect service -SUB basecamp connect service install -SUB basecamp connect service uninstall SUB basecamp connect setup SUB basecamp connect shadow SUB basecamp connect shadow promote diff --git a/.surface-breaking b/.surface-breaking index 48c4d5bb5..f4e10ee61 100644 --- a/.surface-breaking +++ b/.surface-breaking @@ -1148,3 +1148,75 @@ SUB basecamp uploads vaults create SUB basecamp uploads vaults list FLAG basecamp connect setup --remove-route type=stringArray FLAG basecamp connect setup --route type=stringArray +CMD basecamp connect service +CMD basecamp connect service install +CMD basecamp connect service uninstall +FLAG basecamp connect service --account type=string +FLAG basecamp connect service --agent type=bool +FLAG basecamp connect service --cache-dir type=string +FLAG basecamp connect service --count type=bool +FLAG basecamp connect service --help type=bool +FLAG basecamp connect service --hints type=bool +FLAG basecamp connect service --ids-only type=bool +FLAG basecamp connect service --in type=string +FLAG basecamp connect service --jq type=string +FLAG basecamp connect service --json type=bool +FLAG basecamp connect service --markdown type=bool +FLAG basecamp connect service --md type=bool +FLAG basecamp connect service --no-hints type=bool +FLAG basecamp connect service --no-stats type=bool +FLAG basecamp connect service --profile type=string +FLAG basecamp connect service --project type=string +FLAG basecamp connect service --quiet type=bool +FLAG basecamp connect service --stats type=bool +FLAG basecamp connect service --styled type=bool +FLAG basecamp connect service --todolist type=string +FLAG basecamp connect service --verbose type=count +FLAG basecamp connect service install --account type=string +FLAG basecamp connect service install --agent type=bool +FLAG basecamp connect service install --cache-dir type=string +FLAG basecamp connect service install --count type=bool +FLAG basecamp connect service install --help type=bool +FLAG basecamp connect service install --hints type=bool +FLAG basecamp connect service install --hold type=bool +FLAG basecamp connect service install --ids-only type=bool +FLAG basecamp connect service install --in type=string +FLAG basecamp connect service install --jq type=string +FLAG basecamp connect service install --json type=bool +FLAG basecamp connect service install --markdown type=bool +FLAG basecamp connect service install --md type=bool +FLAG basecamp connect service install --no-enable type=bool +FLAG basecamp connect service install --no-hints type=bool +FLAG basecamp connect service install --no-stats type=bool +FLAG basecamp connect service install --profile type=string +FLAG basecamp connect service install --project type=string +FLAG basecamp connect service install --quiet type=bool +FLAG basecamp connect service install --shadow type=bool +FLAG basecamp connect service install --stats type=bool +FLAG basecamp connect service install --styled type=bool +FLAG basecamp connect service install --todolist type=string +FLAG basecamp connect service install --verbose type=count +FLAG basecamp connect service uninstall --account type=string +FLAG basecamp connect service uninstall --agent type=bool +FLAG basecamp connect service uninstall --cache-dir type=string +FLAG basecamp connect service uninstall --count type=bool +FLAG basecamp connect service uninstall --help type=bool +FLAG basecamp connect service uninstall --hints type=bool +FLAG basecamp connect service uninstall --ids-only type=bool +FLAG basecamp connect service uninstall --in type=string +FLAG basecamp connect service uninstall --jq type=string +FLAG basecamp connect service uninstall --json type=bool +FLAG basecamp connect service uninstall --markdown type=bool +FLAG basecamp connect service uninstall --md type=bool +FLAG basecamp connect service uninstall --no-hints type=bool +FLAG basecamp connect service uninstall --no-stats type=bool +FLAG basecamp connect service uninstall --profile type=string +FLAG basecamp connect service uninstall --project type=string +FLAG basecamp connect service uninstall --quiet type=bool +FLAG basecamp connect service uninstall --stats type=bool +FLAG basecamp connect service uninstall --styled type=bool +FLAG basecamp connect service uninstall --todolist type=string +FLAG basecamp connect service uninstall --verbose type=count +SUB basecamp connect service +SUB basecamp connect service install +SUB basecamp connect service uninstall diff --git a/internal/commands/commands.go b/internal/commands/commands.go index 30f473b32..a742a76a3 100644 --- a/internal/commands/commands.go +++ b/internal/commands/commands.go @@ -146,7 +146,7 @@ func CommandCategories() []CommandCategory { {Name: "bonfire", Category: "additional", Description: "Multi-chat orchestration", Actions: []string{"split", "layout"}, Experimental: true, DevOnly: true}, {Name: "api", Category: "additional", Description: "Raw API access"}, {Name: "mcp", Category: "additional", Description: "Serve Basecamp to MCP clients over stdio"}, - {Name: "connect", Category: "additional", Description: "Run a local agent connector for a Basecamp agent, and see and decide what it runs", Actions: []string{"setup", "show", "status", "doctor", "redispatch", "discard", "release", "shadow", "import", "service"}}, + {Name: "connect", Category: "additional", Description: "Run a local agent connector for a Basecamp agent, and see and decide what it runs", Actions: []string{"setup", "show", "status", "doctor", "redispatch", "discard", "release", "shadow", "import"}}, {Name: "help", Category: "additional", Description: "Show help"}, {Name: "version", Category: "additional", Description: "Show version"}, }, diff --git a/internal/commands/connect.go b/internal/commands/connect.go index 5f46a60b0..50d88cb3e 100644 --- a/internal/commands/connect.go +++ b/internal/commands/connect.go @@ -61,7 +61,9 @@ wait for review, until basecamp connect release. Linux only. basecamp connect release clear the hold basecamp connect shadow promote make the shadow ledger the connector's, held basecamp connect import apply a cutover reconciliation file - basecamp connect service install have the OS keep it running`, + +Start it in the folder it should work in and leave it running: workers run +there and may change files in it without asking.`, Example: ` basecamp connect setup -P agent --operator-profile me --serve 12345 basecamp connect -P agent basecamp connect -P agent --project 12345 --shadow`, diff --git a/internal/commands/connect_service.go b/internal/commands/connect_service.go index f3a58d6cd..824940ff1 100644 --- a/internal/commands/connect_service.go +++ b/internal/commands/connect_service.go @@ -101,8 +101,14 @@ type connectServiceFlags struct { // newConnectServiceCmd is the service group. func newConnectServiceCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "service", - Short: "Install or remove the OS service that keeps the connector running", + Use: "service", + // Hidden until it's ready: the unit runs the connector in the home + // directory, where the worker may change files without asking, + // freezes PATH at install, is Linux-only, and is untried on real + // machines. People start the connector themselves in the folder it + // should work in. + Hidden: true, + Short: "Install or remove the OS service that keeps the connector running", Long: `Install or remove a systemd user unit that runs the connector and starts it again when it stops. diff --git a/skills/basecamp-connect/SKILL.md b/skills/basecamp-connect/SKILL.md index a274d8831..ff7a05f77 100644 --- a/skills/basecamp-connect/SKILL.md +++ b/skills/basecamp-connect/SKILL.md @@ -7,9 +7,9 @@ description: | and readiness (basecamp connect setup). Explains every setup result and failure. Also reads what the connector ran (status, doctor) and carries out a person's decisions on its records (redispatch, discard, - release, the cutover's shadow promote and import). Starts and supervises the - connector through its systemd service, and reads the pointer lines it - writes. + release, the cutover's shadow promote and import). Tells the person how to + start the connector in the folder it should work in, and reads the pointer + lines it writes. Use when asked to connect an agent, set up or change the connector, add or remove a project, change who can drive the agent, find out why setup says the connector is not ready, or see, retry, close or release what the @@ -31,7 +31,6 @@ triggers: - release the hold - start the connector - stop the connector - - basecamp connect service install - is the connector running --- @@ -465,65 +464,45 @@ consent. **Never run `basecamp connect` yourself.** It runs in the foreground until it is interrupted, so a session that starts it never gets its turn back, and a -connector that lives only as long as your session dies with it. It is a -service. Install the service and let the OS run it. - - basecamp connect service install -P '' - -That writes a systemd user unit, enables it and starts it. The unit restarts -the connector whenever it stops, so a crash or a kill brings it back. - -**A reboot is the exception.** A user manager starts at login, so unless -lingering is on, the connector stays down after a restart until someone signs -in. Install says so when it finds lingering off, and gives the -`sudo loginctl enable-linger` command. Pass that on; do not run it, and do not -tell anyone an unattended machine will come back on its own until they have. -Pass the run's shape to install, not to the connector: `--project ` -(repeatable) to hear only some projects, `--shadow` to admit and log without -dispatching or posting anything, `--hold` to run with the durable hold set. -Installing again over an existing service rewrites the unit and restarts it, so -that is how a project is added or shadow is turned off. - -`basecamp connect service uninstall -P ''` stops it and removes the -unit. The stop is a SIGTERM, which is how the connector is meant to be asked: -it cancels its live workers, posts their completions and exits. Nothing else is -removed — the ledger, the checkpoint and connect.json stay, and installing -again resumes from them. - -Linux only, and the refusal says so: the connector runs nowhere else, so there -is no macOS service to install. If a person on a Mac asks, say that rather than -looking for a launchd flag. +connector that lives only as long as your session dies with it. The person +runs it, in a terminal of their own, and leaves it running: -Quote the profile, as everywhere else in this skill. + cd '' && basecamp connect -P '' -### Seeing what it is doing +**The folder is the agent's workspace.** Every worker runs there and may change +files in it without asking. Suggest a folder made for the agent, or the project +the person wants it to work on. Never suggest their home directory, and if the +connector is already running from it, say so. -Two different questions, two different places. +Stopping it is safe: Ctrl-C (a SIGINT) cancels live workers, posts their +completions and exits. Nothing is lost while it is stopped: started again, it +catches up on what people asked in the meantime. On a server, the person can +keep it running in `tmux` or `screen`. -**What it has heard and run** is `basecamp connect status -P ''`, -which reads the ledger. That is the answer to "is it working", "what is it -holding", "did that mention get picked up". It works whether or not the service -is running. +There is no background service yet. If someone asks for one, say it isn't +available, and that the connector catches up whenever it is started again. -**Whether it is up** is the service: +Pass the run's shape to the connector itself: `--project ` (repeatable) to +hear only some projects, `--shadow` to admit and log without dispatching or +posting anything, `--hold` to run with the durable hold set. - systemctl --user is-active basecamp-connect-''.service - systemctl --user status basecamp-connect-''.service +Linux only for now: on any other system the connector refuses to start, and +says so. If a person on a Mac asks, say that. -A unit in `failed` after several restarts is a connector that cannot start: -the unit gives up after five tries rather than retrying forever and reporting -`activating` while nothing works. Run `basecamp connect doctor -P ''` -and explain the failed check. Once the cause is fixed, `service install` again -— it clears the failure counter, which a bare `systemctl --user restart` does -not, so restarting by hand from `failed` is refused until the window passes. +Quote the profile, as everywhere else in this skill. + +### Seeing what it is doing + +**What it has heard and run** is `basecamp connect status -P ''`, +which reads the ledger. That is the answer to "is it working", "what is it +holding", "did that mention get picked up". It also says whether a connector +holds the ledger, which is whether one is running. ### The pointer lines While it runs, the connector writes one JSON object per line on stdout — a pointer line per event it saw and per decision it took — and its logs on -stderr. Under the service both land in the journal: - - journalctl --user -u basecamp-connect-''.service -n 50 -o cat +stderr, in the terminal the person started it from. **A pointer line carries no content, and you must not try to make it.** It names ids, the trigger, the class, the route and the state, and nothing of what @@ -532,6 +511,6 @@ person wants to know what was said, the answer is in Basecamp, not here — open the recording the pointer names. Never quote a pointer line into a Basecamp comment, and never reconstruct a message from one. -Use the journal to answer "what happened at 14:32" or "why did that event not +Use those lines to answer "what happened at 14:32" or "why did that event not dispatch". For anything about the current state of a record, prefer `status`: -it is the ledger's own answer, and the journal is a log of how it got there. +it is the ledger's own answer, and the lines are a log of how it got there. From bb72ae2e3b608e3d6c03245e5dc9cd053d71f5b1 Mon Sep 17 00:00:00 2001 From: Rob Zolkos Date: Wed, 30 Sep 2026 10:14:04 -0400 Subject: [PATCH 2/2] Don't promise more than status and catch-up deliver The connect skill said a restarted connector loses nothing, and that `connect status` says whether a connector is running. Neither is quite true. A connector with no stored feed position enters at the present, so what was asked before its very first run isn't picked up. And status only shows what the last connector wrote beside its lock: a crash leaves that behind and a pid can be reused, so it's a hint, not an answer. The skill now says catch-up starts once the connector has run, and sends the question "is it running?" to the person's terminal. --- skills/basecamp-connect/SKILL.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/skills/basecamp-connect/SKILL.md b/skills/basecamp-connect/SKILL.md index ff7a05f77..2e3554b7a 100644 --- a/skills/basecamp-connect/SKILL.md +++ b/skills/basecamp-connect/SKILL.md @@ -475,12 +475,15 @@ the person wants it to work on. Never suggest their home directory, and if the connector is already running from it, say so. Stopping it is safe: Ctrl-C (a SIGINT) cancels live workers, posts their -completions and exits. Nothing is lost while it is stopped: started again, it -catches up on what people asked in the meantime. On a server, the person can -keep it running in `tmux` or `screen`. +completions and exits. Once it has run and read the feed, it keeps its place: +started again, it catches up on what people asked while it was stopped. Its +very first start has no place to resume from, so it begins at that moment, and +anything asked before then is not picked up. On a server, the person can keep +it running in `tmux` or `screen`. There is no background service yet. If someone asks for one, say it isn't -available, and that the connector catches up whenever it is started again. +available, and that once it has run, the connector catches up whenever it is +started again. Pass the run's shape to the connector itself: `--project ` (repeatable) to hear only some projects, `--shadow` to admit and log without dispatching or @@ -495,8 +498,10 @@ Quote the profile, as everywhere else in this skill. **What it has heard and run** is `basecamp connect status -P ''`, which reads the ledger. That is the answer to "is it working", "what is it -holding", "did that mention get picked up". It also says whether a connector -holds the ledger, which is whether one is running. +holding", "did that mention get picked up". It also shows what the last +connector wrote beside its lock, but that is a hint, not an answer: a crashed +connector leaves it behind, and a process id can since belong to something +else. Whether the connector is running is in the person's terminal: ask them. ### The pointer lines