Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
331 commits
Select commit Hold shift + click to select a range
3f4847b
Test that only this record's own later discard lets it close
jorgemanrubia Sep 17, 2026
d7c1ba3
Recognize a finished promote by its decision, not the hold's cause
jorgemanrubia Sep 17, 2026
926e897
Keep a superseded task from taking follow-ups, and let an import with…
jorgemanrubia Sep 17, 2026
e579a83
Preallocate the crash table
jorgemanrubia Sep 17, 2026
eb99cc6
Rebase onto card 20's new head: one CheckPrivateFile, the dispatch to…
jorgemanrubia Sep 17, 2026
ce9f151
Close the second adversarial review: nothing asks for a decision alre…
jorgemanrubia Sep 17, 2026
7beaf99
Give the schema tamper in the migration test a context
jorgemanrubia Sep 17, 2026
c3d6fd8
Guard the migration with the instance lock, and refuse an edit that n…
jorgemanrubia Sep 17, 2026
40fc541
Close the fourth adversarial review: one lock for an import, a notice…
jorgemanrubia Sep 17, 2026
1c8cdb7
Return only an error from the refusal helpers, and mark the migrating…
jorgemanrubia Sep 17, 2026
0ed774e
Re-render a completion notice only when a person decided one of its e…
jorgemanrubia Sep 17, 2026
dd6dff6
Ask the driver's one-owner rule before acting on a recorded worker
jorgemanrubia Sep 17, 2026
e1dc6e5
Close the fifth adversarial review: say what became of a worker exactly
jorgemanrubia Sep 17, 2026
c87755f
Close the sixth review round: authorized blocked records are decided,…
jorgemanrubia Sep 17, 2026
93ab880
An authorization answers for the outcome it was made on
jorgemanrubia Sep 17, 2026
51a7517
Close an imported done outcome for good, and refuse a ledger a newer …
jorgemanrubia Sep 17, 2026
6fa6cf3
Refuse a newer ledger on the connector's own open, and record what an…
jorgemanrubia Sep 17, 2026
7f77956
Regenerate the CLI surface after the rebase
jorgemanrubia Sep 17, 2026
1d3cf55
Never date an authorization before the block it answers for
jorgemanrubia Sep 17, 2026
c8752cd
Teach the basecamp-connect skill the operator commands, and point a m…
jorgemanrubia Sep 17, 2026
b67dd55
Close review round ten: the skill and the command catalog match the c…
jorgemanrubia Sep 17, 2026
20025ad
Own the doctor's MCP server process outright, so its group is ended b…
jorgemanrubia Sep 17, 2026
4c5519c
Name the held state in the dispatch lifecycle test's exhaustive switch
jorgemanrubia Sep 17, 2026
c1b604d
Refuse a shadowing token before a redispatch touches the ledger, and …
jorgemanrubia Sep 17, 2026
93ed837
Say the pointer lines exist but are not this skill's yet, and label d…
jorgemanrubia Sep 17, 2026
e28c5cf
Speak the CLI's JSON in the decision commands, and say what the hold …
jorgemanrubia Sep 17, 2026
10468a7
Prove a discard withdraws a redispatch still waiting for its task
jorgemanrubia Sep 17, 2026
9a1e405
Assert the withdrawn authorization itself
jorgemanrubia Sep 17, 2026
7c66028
Report the task token's taker in status, as the worker is reported
jorgemanrubia Sep 17, 2026
9a1042f
Read the token descriptor the way the flag parser will, or not at all
jorgemanrubia Sep 17, 2026
af7e411
The moved token socket is the connector's own: swept, checked, named …
jorgemanrubia Sep 17, 2026
fe11ddf
Keep the CLI building where the one-owner rule does not run
jorgemanrubia Sep 17, 2026
c4846df
Never post a still-running notice after its attempt ended
jorgemanrubia Sep 17, 2026
6796808
Tie retirement and pulls to the task, and read only this command's de…
jorgemanrubia Sep 17, 2026
7ace7a4
Kill the connector at every ledger state, and recover
jorgemanrubia Sep 17, 2026
7665f7a
Run the recovery harness against the real agents, opted into
jorgemanrubia Sep 17, 2026
46f41e4
Scale the prompt budget check by a measured tokenizer ratio
jorgemanrubia Sep 17, 2026
3719fff
Follow the rebase: bind the dispatch with its context, reconcile a re…
jorgemanrubia Sep 17, 2026
25bc026
Assert on the worker the ledger recorded, and join the follow-up befo…
jorgemanrubia Sep 17, 2026
c3a543e
Check the feed's checkpoint after the walk closed the loss
jorgemanrubia Sep 17, 2026
668e1cf
Put the import's tombstone entry first, so a crash at the first entry…
jorgemanrubia Sep 17, 2026
0cd2603
Watch every checkpoint the walk's run holds, not only its last
jorgemanrubia Sep 17, 2026
eef8b35
Satisfy the linter: the ledger's own context, and no needless conversion
jorgemanrubia Sep 17, 2026
0a986f1
Close the adversarial review: a real state directory, an ordered over…
jorgemanrubia Sep 17, 2026
5d60ed9
Keep the real-agent run off every Basecamp but a closed port, and exp…
jorgemanrubia Sep 17, 2026
359d86b
Floor the checkpoint watcher on what stands long enough to be seen
jorgemanrubia Sep 17, 2026
5f4e697
Order the overflow check by event, not by page: one page may serve both
jorgemanrubia Sep 17, 2026
e817578
Hold the harness to the one-owner rule: a surviving tree keeps its at…
jorgemanrubia Sep 17, 2026
625f151
Group the harness's imports
jorgemanrubia Sep 17, 2026
512b0ae
Close the second adversarial review: no line kill races the dispatche…
jorgemanrubia Sep 17, 2026
ac80714
Answer Copilot: a portable zombie check, a comparator that cannot ove…
jorgemanrubia Sep 17, 2026
547e3dc
Close the third adversarial review; rebase onto the outbox's start an…
jorgemanrubia Sep 17, 2026
67c6c0c
Take the task token from the connector's socket, as the bridge does, …
jorgemanrubia Sep 17, 2026
99293fc
Dial the token socket on the turn's context
jorgemanrubia Sep 17, 2026
f5e6ba1
Follow the prompt's URL cap: the worst case is the longest URL it rep…
jorgemanrubia Sep 17, 2026
ac289c6
Give the opt-in real run a stored credential, since the bridge hands …
jorgemanrubia Sep 17, 2026
7c48a0e
Group the real run's imports
jorgemanrubia Sep 17, 2026
25535bf
Let a loaded box take its time: a run fails on what the ledger says, …
jorgemanrubia Sep 17, 2026
f56ab34
Close the fourth adversarial review: watch the session directory, hol…
jorgemanrubia Sep 17, 2026
a089855
Let cobra say which invocation this is, and the database tie the rest…
jorgemanrubia Sep 17, 2026
bda9d9e
Finish a canceled guard, and say what waits when the task was already…
jorgemanrubia Sep 17, 2026
75a4c23
Hand a worker nothing new under the hold, and say the lock file is on…
jorgemanrubia Sep 17, 2026
2f77c5e
Make the credential check fail closed: what it could not read, and wh…
jorgemanrubia Sep 17, 2026
7e55fc4
Say what the still-running check actually covers, and test its missin…
jorgemanrubia Sep 17, 2026
c02672a
Fail doctor for what the run command refuses: worktrees, and a platfo…
jorgemanrubia Sep 17, 2026
7e49e63
A restarted MCP server takes the token again, and four paths that ans…
jorgemanrubia Sep 17, 2026
19ad2a4
Say what doctor does not write, and quote the profile in the command …
jorgemanrubia Sep 17, 2026
efc7c4a
Hold a later open to the owner the check passed, and read for one inv…
jorgemanrubia Sep 17, 2026
054a1fb
Stop a start on a ledger failure while sending, and give up on a trun…
jorgemanrubia Sep 17, 2026
d17eb08
The socket arms again only when the server holding the token is gone,…
jorgemanrubia Sep 17, 2026
f67ec26
Validate a reconciliation wherever it comes from, and say what a pid …
jorgemanrubia Sep 17, 2026
62a2888
Silence contextcheck on the import validation subtests
jorgemanrubia Sep 17, 2026
670c82e
Read the arguments without touching the command that runs them
jorgemanrubia Sep 17, 2026
e061398
A spent handoff budget is said out loud
jorgemanrubia Sep 17, 2026
59017dc
Write down what counts as one refusal, and why the handoff budget is …
jorgemanrubia Sep 17, 2026
bf4129f
Require the connector to say what it decided, where the ledger cannot…
jorgemanrubia Sep 17, 2026
e292e73
A hold is not a failed task, and a canceled guard is dated when it wa…
jorgemanrubia Sep 17, 2026
4098b87
Let a canceled notice hide nothing, and stop asking the ledger twice …
jorgemanrubia Sep 17, 2026
a74d10b
Keep the token descriptor from children instead of racing to read it
jorgemanrubia Sep 17, 2026
0e493b0
Vet the whole path on a later open, without opening the ledger
jorgemanrubia Sep 17, 2026
df2c4d6
Withhold, do not fail: the dispatcher reads a hold's refusal as a hold
jorgemanrubia Sep 17, 2026
0fc79b0
Say what became of every handoff, and count a nameless refusal every …
jorgemanrubia Sep 17, 2026
03856d5
Check every task token the connector minted, whoever its worker was
jorgemanrubia Sep 17, 2026
56042e6
Add the acp driver: the connector as an ACP v1 client
jorgemanrubia Sep 17, 2026
6f22d7d
acp: keep foreign MCP servers out, and no hang on a stuck adapter
jorgemanrubia Sep 17, 2026
51e8143
acp: bound the wait on a held output, and three review fixes
jorgemanrubia Sep 17, 2026
dbde762
acp: answer the second review round
jorgemanrubia Sep 17, 2026
a36e1d5
acp: a cancel ends one turn, and the preflight reads spaced keys
jorgemanrubia Sep 17, 2026
17e965c
acp: answer the third review round
jorgemanrubia Sep 17, 2026
c250d83
acp: bound the requests in flight, and steady two tests
jorgemanrubia Sep 17, 2026
f8bb120
acp: every refusal on its turn, and what a tool call may cost
jorgemanrubia Sep 17, 2026
b4f74f3
acp: ask the compatibility check's write twice before judging it
jorgemanrubia Sep 17, 2026
535901b
acp: confirm a failed handshake's group gone, and close the last refu…
jorgemanrubia Sep 17, 2026
701ab99
acp: a settled turn asks nothing more, and requests keep the turn the…
jorgemanrubia Sep 17, 2026
005afb6
acp: adopt the written driver contract, and settle a turn as its answ…
jorgemanrubia Sep 17, 2026
b03b140
acp: prove the token bridge against both pinned adapters
jorgemanrubia Sep 17, 2026
628a024
acp: a probe that proves something on any machine, and a strict install
jorgemanrubia Sep 17, 2026
f778ae5
acp: no session goes on without the MCP servers it was given
jorgemanrubia Sep 17, 2026
08f8847
acp: an agent that outruns even its refusals ends its session
jorgemanrubia Sep 17, 2026
5d78402
acp: adopt the shared redaction and refusal record, and answer the ni…
jorgemanrubia Sep 17, 2026
76e4d8d
acp: wrap the shared unverified sentinel, and prove the shared rules
jorgemanrubia Sep 17, 2026
124e5a7
acp: a server nobody gave it, an init for another session, and two lo…
jorgemanrubia Sep 17, 2026
cc6404c
acp: the eleventh review's fixes, and the three rules said once
jorgemanrubia Sep 17, 2026
7482ce6
acp: say exactly what is bounded, trusted and inherited
jorgemanrubia Sep 17, 2026
28c21f2
acp: bound what the agent holds, and gate evidence where decisions ar…
jorgemanrubia Sep 17, 2026
8191970
acp: read the whole of an adapter's stderr, and the socket's directory
jorgemanrubia Sep 17, 2026
d225e07
acp: ask twice before judging what an adapter does with a dead server
jorgemanrubia Sep 17, 2026
05ed104
acp: a name the session never gave is not kept as a name
jorgemanrubia Sep 17, 2026
439311a
acp: hold the nameless-refusal count with a test
jorgemanrubia Sep 17, 2026
4496992
Say why the descriptor number converts safely
jorgemanrubia Sep 17, 2026
87479f5
Add the Codex spawn driver: codex exec --json under the AgentDriver
jorgemanrubia Sep 17, 2026
0eeaaf3
Register codex as a worker: setup.Workers and spawn.New
jorgemanrubia Sep 17, 2026
99526f0
Give each task its own git worktree, and keep the ones holding work
jorgemanrubia Sep 17, 2026
40671a2
Add basecamp connect worktrees list and prune; wire --worktrees into …
jorgemanrubia Sep 17, 2026
e5a5cf4
Satisfy the linter
jorgemanrubia Sep 17, 2026
14a2e45
Account for connect worktrees in smoke coverage
jorgemanrubia Sep 17, 2026
1fe98cb
Test the worktree state edges
jorgemanrubia Sep 17, 2026
0c0920d
Prove a second prompt is refused as such
jorgemanrubia Sep 17, 2026
bbecfee
Blank every configured content filter when the connector runs git
jorgemanrubia Sep 17, 2026
992c134
A forced prune keeps a detached HEAD's unheld commit on a branch of i…
jorgemanrubia Sep 17, 2026
695b995
Codex: a failed turn waits for the policy check; a cancel before the …
jorgemanrubia Sep 17, 2026
178a5e4
Keep a worktree holding ignored files or index entries that hide edits
jorgemanrubia Sep 17, 2026
189a68f
Back off a failing worktree; keep settling worktrees after they are s…
jorgemanrubia Sep 17, 2026
1b06e84
Satisfy the linter
jorgemanrubia Sep 17, 2026
0f0f13f
Codex: a canceled turn reports a policy check that already failed
jorgemanrubia Sep 17, 2026
c644b0d
Name the ignored-file window the removal leaves
jorgemanrubia Sep 17, 2026
16603bc
Judge a worktree by its disk and every commit it reaches; blank filte…
jorgemanrubia Sep 17, 2026
8c83610
Codex: verify the filesystem policy the sandbox is built from
jorgemanrubia Sep 17, 2026
4b82914
Run with worktrees now that they exist: drop the refusal
jorgemanrubia Sep 17, 2026
02f48c9
Own the task branch before making it, and anchor every unheld HEAD
jorgemanrubia Sep 17, 2026
d31eff2
One place decides a task branch is ours to delete
jorgemanrubia Sep 17, 2026
780ae69
Close a Codex session without waiting on an escaped child, and keep a…
jorgemanrubia Sep 17, 2026
61e7512
Close the updates channel last, read stderr after the worker exits, a…
jorgemanrubia Sep 17, 2026
85fed18
Prove the last refusal and the unreadable path
jorgemanrubia Sep 17, 2026
14a3667
Say what a prune refuses, and clean up after a worktree that never ap…
jorgemanrubia Sep 17, 2026
912cbad
Refuse a prompt that arrives after the worker's output ended
jorgemanrubia Sep 17, 2026
2d0f147
Never let a worker that stops reading hold cancel or close
jorgemanrubia Sep 17, 2026
5e88a43
Prove Close alone survives a worker that stopped reading
jorgemanrubia Sep 17, 2026
47d6cbc
Prove Close alone survives a worker that stopped reading
jorgemanrubia Sep 17, 2026
5a3967b
Read a worktree's record as git writes it, and say a refused force wh…
jorgemanrubia Sep 17, 2026
78a92ea
A route that cannot take a worktree waits out of the dispatch window
jorgemanrubia Sep 17, 2026
650e35b
Never let git look inside a submodule, and never force one away
jorgemanrubia Sep 17, 2026
6d66e0e
Leave git's record of a missing worktree to git; report an unrecorded…
jorgemanrubia Sep 17, 2026
aaf5fad
Name the worktrees starvation test apart from the dispatcher's
jorgemanrubia Sep 17, 2026
5371964
Hold the codex driver to the credential rule's places; the strict no-…
jorgemanrubia Sep 17, 2026
a94bdbd
Keep a missing worktree whose record still holds work, abandon a stuc…
jorgemanrubia Sep 17, 2026
535384c
One worktree, one removal: the rule written once, and one function th…
jorgemanrubia Sep 17, 2026
f928465
Lock git's record while a worktree is frozen, and harden the rule's e…
jorgemanrubia Sep 17, 2026
c2de7ae
Codex: a cancel with no prompt ends the worker now; a failed turn's s…
jorgemanrubia Sep 17, 2026
590f3af
Codex: take the task token over the connector's socket; no env file, …
jorgemanrubia Sep 17, 2026
efaf918
Say what the codex tests now check
jorgemanrubia Sep 17, 2026
cbaee71
Prompt honors its context while its write blocks; a missing worktree …
jorgemanrubia Sep 17, 2026
799d2ac
Never remove git data of a repository inside a worktree; judge a work…
jorgemanrubia Sep 17, 2026
00707aa
Route the codex driver's errors, updates and stderr through the share…
jorgemanrubia Sep 17, 2026
40273e2
Route the codex driver's errors, updates and stderr through the share…
jorgemanrubia Sep 17, 2026
f74db69
Record every Codex refusal through the shared recorder, cancels included
jorgemanrubia Sep 17, 2026
51b3c96
Satisfy the linter
jorgemanrubia Sep 17, 2026
e95b5f0
Verify every holder in the transaction that ends a removal
jorgemanrubia Sep 17, 2026
9944de2
The connector never removes a worktree
jorgemanrubia Sep 17, 2026
e73ffbd
Say what a kept worktree takes up, and nothing of one that is gone
jorgemanrubia Sep 17, 2026
43ebf3c
Judge a missing worktree's record by everything it still reaches
jorgemanrubia Sep 17, 2026
27adb41
The connector deletes no ref of its own accord either
jorgemanrubia Sep 17, 2026
ae890be
Record every refusal Codex only logs, not just its last line
jorgemanrubia Sep 17, 2026
1414473
Leave a worktree someone else deleted exactly as it is
jorgemanrubia Sep 17, 2026
36a2a1c
Read a pseudo-ref as what it is, and say what an orphan's force does
jorgemanrubia Sep 17, 2026
17d5971
Close the last things reviews found in what is left of the judgment
jorgemanrubia Sep 17, 2026
c5999b4
Test the receipt arm this PR made load-bearing
jorgemanrubia Sep 17, 2026
4d91c1a
Take the connector's lock even when a promote has nothing left to move
jorgemanrubia Sep 17, 2026
97534fa
Let every canceled turn read the worker's last word, and say what is …
jorgemanrubia Sep 17, 2026
af15cd8
acp: a call this driver cannot place whole is refused, not judged in …
jorgemanrubia Sep 17, 2026
29abedc
Say the retained worktrees are unavailable, not that there are none
jorgemanrubia Sep 17, 2026
1bac99f
Seal inherited descriptors at startup instead of scanning argv
jorgemanrubia Sep 17, 2026
65972ca
Freeze the dispatcher's interfaces: driver, tasks and attempts, hooks
jorgemanrubia Sep 17, 2026
6fc3d9e
Run the connector: tests, the run command, and the worker seam
jorgemanrubia Sep 17, 2026
3d0d689
Terminate the leader by pid too; pin --setting-sources in the args test
jorgemanrubia Sep 17, 2026
84d8ba6
Launch on #736's createTask; one live task per event is retired_at's
jorgemanrubia Sep 17, 2026
cd90f34
Bound the wait on a worker's pipes, so a stray descendant cannot hang…
jorgemanrubia Sep 17, 2026
116c628
Fail, not hang, when a per-task workspace session never starts
jorgemanrubia Sep 17, 2026
95e2bea
Answer the first review: starvation, stop reasons, recovery, containment
jorgemanrubia Sep 17, 2026
ad7eaef
End an attempt through #736's supersedeTask, which returns unexposed …
jorgemanrubia Sep 17, 2026
75b8b99
Answer the second review: scope, authorization, and what a stop means
jorgemanrubia Sep 17, 2026
1b0c808
Preallocate the stranded query's arguments
jorgemanrubia Sep 17, 2026
ce9d89f
Answer the third review: groups, locations, slots, truncation, the skill
jorgemanrubia Sep 17, 2026
096a3d8
Name the one-owner rule and hold everything to it
jorgemanrubia Sep 17, 2026
f7e1632
One release point, and nothing may reach around it
jorgemanrubia Sep 17, 2026
c5ecb73
Write the driver contract down, and make the code keep it
jorgemanrubia Sep 17, 2026
9c82798
On #736's 67aac1d: settlement cannot meet a moved handed record; desc…
jorgemanrubia Sep 17, 2026
f8c8c22
The task token's carriage: a one-use socket and the worker-mcp bridge
jorgemanrubia Sep 17, 2026
3b1a566
Withdraw through #736's withdrawExposure, after the supersession it r…
jorgemanrubia Sep 17, 2026
2a1ef38
A worker's MCP server may be its descendant in a group of its own: Co…
jorgemanrubia Sep 17, 2026
1efa646
The prompt's worst case fits the budget: a URL over 120 characters is…
jorgemanrubia Sep 17, 2026
f59c304
A process group whose members are all zombies is gone
jorgemanrubia Sep 17, 2026
0ae03a4
drivertest: a secret scan never opens a SQLite database or its journals
jorgemanrubia Sep 17, 2026
3cd0869
Tests start their helper processes with a context
jorgemanrubia Sep 17, 2026
cacfe8c
The redaction rule: one function every text leaving a worker passes t…
jorgemanrubia Sep 17, 2026
d6bc577
The refusal rule: a refusal is recorded in the ledger as it happens, …
jorgemanrubia Sep 17, 2026
7cdae08
Take no descriptor's range on trust at the syscall boundary
jorgemanrubia Sep 17, 2026
91507de
Copilot: a stub that matches its Unix twin, a turn that keeps its ref…
jorgemanrubia Sep 17, 2026
1252f15
The token's window is the worker's MCP server's, and starts when the …
jorgemanrubia Sep 17, 2026
d1f2054
The release point ends the MCP server the agent started outside the w…
jorgemanrubia Sep 17, 2026
d89aa88
A restart ends the MCP server that took the token, and a clean finish…
jorgemanrubia Sep 17, 2026
6bb9d95
A token socket always has a path a unix socket can carry
jorgemanrubia Sep 17, 2026
08dc6a9
The moved token socket is the connector's own: swept, checked, named …
jorgemanrubia Sep 17, 2026
cbcb460
A restarted MCP server takes the token again, and four paths that ans…
jorgemanrubia Sep 17, 2026
d2b1bf5
The socket arms again only when the server holding the token is gone,…
jorgemanrubia Sep 17, 2026
76069af
A spent handoff budget is said out loud
jorgemanrubia Sep 17, 2026
21e4175
Write down what counts as one refusal, and why the handoff budget is …
jorgemanrubia Sep 17, 2026
2f98e27
Say what became of every handoff, and count a nameless refusal every …
jorgemanrubia Sep 17, 2026
27e5702
Report the refusals of a session stopped for its policy, and the comm…
jorgemanrubia Sep 17, 2026
aba1db5
Refuse the other first hand-off under the hold: a worker's first pull
jorgemanrubia Sep 17, 2026
caab8f1
A worker acknowledges and completes what it pulled
jorgemanrubia Sep 17, 2026
031b77c
Merge main: 736 landed, and its migration 6 is not this branch's
jorgemanrubia Sep 18, 2026
2116ae1
Drop the token reader main renamed away
jorgemanrubia Sep 18, 2026
3064b45
acp: a session runs only the MCP servers the adapter says it got
jorgemanrubia Sep 18, 2026
211cae6
Merge connect-dispatcher, which is now on main after #736 squashed
jorgemanrubia Sep 18, 2026
19ac0ed
Merge connect-dispatcher: 736 landed as one commit, and the worktrees…
jorgemanrubia Sep 18, 2026
21bad61
Merge connect-dispatcher: #736 squashed onto main, so the numbers move
jorgemanrubia Sep 18, 2026
a029c95
Merge remote-tracking branch 'origin/connect-outbox' into connect-ope…
jorgemanrubia Sep 18, 2026
23c0391
Judge a worktree by the commits its refs reach
jorgemanrubia Sep 18, 2026
97623f4
Merge 739 lifecycle outbox
jorgemanrubia Sep 18, 2026
de9725e
Merge 740 Codex worker and worktrees
jorgemanrubia Sep 18, 2026
2bc1459
Merge the ACP driver
jorgemanrubia Sep 18, 2026
1a3052a
Carry the operator commands onto the merged base
jorgemanrubia Sep 18, 2026
b974283
Merge the recovery harness
jorgemanrubia Sep 18, 2026
f800b95
Take the third return ResolveStateDir gained
jorgemanrubia Sep 18, 2026
ba9afea
Merge the operator commands
jorgemanrubia Sep 18, 2026
d70fd6c
Identify a recorded worker by the kernel's own start time, exactly
jorgemanrubia Sep 18, 2026
e8a5062
Register --driver once
jorgemanrubia Sep 18, 2026
cb60cfa
Regenerate the CLI surface for the six merged commands
jorgemanrubia Sep 18, 2026
195b13d
Hold an attempt whose task token went to a process nobody can account…
jorgemanrubia Sep 18, 2026
b180f48
Follow a dangling symlink to where it points before deciding it is in…
jorgemanrubia Sep 18, 2026
70a5465
Finish recovery on a context a shutdown does not cancel, and stop ado…
jorgemanrubia Sep 18, 2026
1e7dddd
Run the connector on Linux only
jorgemanrubia Sep 18, 2026
720827e
Refuse a negative --since, and give a shadow run its own feed lineage
jorgemanrubia Sep 18, 2026
f212ba4
Say which worker the driver runs in connect show
jorgemanrubia Sep 18, 2026
4164395
Tell adoption about a shutdown with a channel rather than a second co…
jorgemanrubia Sep 18, 2026
2eeb937
Hold an attempt whose handoff was still deciding when the socket was …
jorgemanrubia Sep 18, 2026
db2e19e
End an ACP session that can no longer record a refusal only once, and…
jorgemanrubia Sep 18, 2026
5479d00
Merge the dispatcher's review round: token holders and process identity
jorgemanrubia Sep 18, 2026
d4a9bdc
Keep the tests both sides of the merge wrote
jorgemanrubia Sep 18, 2026
a213b58
Refuse a task token the socket did not finish handing over
jorgemanrubia Sep 18, 2026
f5bd290
Take terminal control characters out of everything the connector sani…
jorgemanrubia Sep 18, 2026
097b36a
Settle the token socket before a failed start reads its holder, and s…
jorgemanrubia Sep 18, 2026
432307a
Read a recorded process back out of the ledger as an identity, and sa…
jorgemanrubia Sep 18, 2026
2b47084
Say that connect runs on Linux only
jorgemanrubia Sep 18, 2026
ab8a610
Record the kernel's start time for what the recovery harness must signal
jorgemanrubia Sep 18, 2026
64a21a0
Write the control characters the tests check for as escapes
jorgemanrubia Sep 18, 2026
25268bd
Give the race detector twenty minutes, not ten
jorgemanrubia Sep 18, 2026
7051c1c
Say the constraint that actually applies when connect doctor refuses …
jorgemanrubia Sep 18, 2026
5a372ff
Run the recovery harness against every driver the connector can start
jorgemanrubia Sep 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,11 @@ jobs:
go-version-file: 'go.mod'

- name: Run tests with race detector
run: go test -tags dev -race -v ./...
# 20 minutes, not Go's default 10 per package. internal/connector runs
# real sockets, real processes and the recovery harness; under -race it
# takes about five minutes on a fast box and roughly twice that on a
# runner. The default was not a hung test, it was the budget.
run: go test -tags dev -race -v -timeout 20m ./...

integration:
name: Integration Tests
Expand Down
2 changes: 2 additions & 0 deletions .naming-allowlist
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ keyring(bcq
legacy bcq
# RELEASING.md — actual GitHub App name
bcq-release-bot
# npm integrity hashes are base64 and can contain any letters
./internal/connector/driver/acp/adapters/package-lock.json
266 changes: 266 additions & 0 deletions .surface

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,33 @@ qa-report:
echo ""; \
fi

# The connector's acp driver runs pinned ACP adapters, installed here once by
# an operator and never downloaded at dispatch time.
# Where basecamp connect looks by default: an absolute $XDG_DATA_HOME, else
# ~/.local/share (a relative XDG_DATA_HOME is ignored there too).
ACP_ADAPTERS_DIR ?= $(if $(filter /%,$(XDG_DATA_HOME)),$(XDG_DATA_HOME),$(HOME)/.local/share)/basecamp/acp-adapters

# Install the pinned ACP adapters (internal/connector/driver/acp/adapters).
# --engine-strict: an adapter whose Node version requirement this machine does
# not meet fails the install, not the first dispatch.
.PHONY: acp-adapters
acp-adapters:
@mkdir -p "$(ACP_ADAPTERS_DIR)"
cp internal/connector/driver/acp/adapters/package.json internal/connector/driver/acp/adapters/package-lock.json "$(ACP_ADAPTERS_DIR)/"
npm ci --prefix "$(ACP_ADAPTERS_DIR)" --ignore-scripts --no-audit --no-fund --engine-strict

# The ACP adapter-compatibility test: eight checks through the acp driver
# against each installed adapter (the spike's four, the worker shell's
# environment, a decoy MCP server in the working directory, the task
# token's bridge, and what an adapter does when a session's MCP server dies
# mid-session). Sends real prompts (model quota); skipped
# for an adapter that is not installed. ACP_TRANSCRIPTS=<dir> keeps redacted
# JSON-RPC transcripts.
.PHONY: test-acp-compat
test-acp-compat: check-toolchain
BASECAMP_ACP_ADAPTERS_DIR="$(ACP_ADAPTERS_DIR)" BASECAMP_ACP_TRANSCRIPTS="$(ACP_TRANSCRIPTS)" \
$(GOTEST) -tags acpcompat -run TestAdapterCompat -count=1 -timeout 30m -v ./internal/connector/driver/acp/

# Run tests with race detector
.PHONY: race-test
race-test: check-toolchain
Expand Down Expand Up @@ -294,6 +321,9 @@ provenance-check:
.PHONY: vet
vet: check-toolchain
$(GOVET) $(BUILD_TAGS) ./...
@# The adapter-compatibility test builds only with its own tag, so
@# nothing else would notice it rotting.
$(GOVET) -tags acpcompat ./internal/connector/driver/acp/

# Format code
.PHONY: fmt
Expand Down
6 changes: 6 additions & 0 deletions STYLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ recording's change history and predates the account-wide event feed that
rather than becoming a group: turning it into one would break every existing
`basecamp events <id>` invocation to gain nothing.

`connect` is the other exception. The spec names the connector's run as the bare
`basecamp connect -P <agent>`, a long-running foreground command in the grain of
`basecamp mcp`, with `setup` beside it as the one-off that prepares it. Making the
run a `connect run` subcommand would put a verb under a command that is already
the verb.

`scripts/check-bare-groups.sh` enforces this with an allowlist; a command added
there belongs in this section too, with the reason it is an exception.

Expand Down
36 changes: 36 additions & 0 deletions e2e/smoke/smoke_lifecycle.bats
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,42 @@ load smoke_helper
mark_out_of_scope "Reads the connector policy a connected profile's setup wrote — covered by Go tests in internal/commands"
}

@test "connect status is out of scope" {
mark_out_of_scope "Reads a local connector ledger the smoke account does not have — covered by Go tests in internal/commands and internal/connector"
}

@test "connect doctor is out of scope" {
mark_out_of_scope "Needs a set-up connector profile and starts its MCP server — covered by Go tests in internal/commands"
}

@test "connect redispatch is out of scope" {
mark_out_of_scope "Decides a record in a local connector ledger — covered by Go tests in internal/commands and internal/connector"
}

@test "connect discard is out of scope" {
mark_out_of_scope "Decides a record in a local connector ledger — covered by Go tests in internal/commands and internal/connector"
}

@test "connect release is out of scope" {
mark_out_of_scope "Clears the hold in a local connector ledger — covered by Go tests in internal/commands and internal/connector"
}

@test "connect shadow promote is out of scope" {
mark_out_of_scope "Moves a local shadow ledger — covered by Go tests, including a process killed at every step, in internal/connector"
}

@test "connect import is out of scope" {
mark_out_of_scope "Applies a reconciliation file to a local connector ledger — covered by Go tests in internal/commands and internal/connector"
}

@test "connect worktrees list is out of scope" {
mark_out_of_scope "Reads a local connector's ledger — covered by Go tests in internal/commands and internal/connector"
}

@test "connect worktrees prune is out of scope" {
mark_out_of_scope "Removes local git worktrees a connector kept — covered by Go tests in internal/commands and internal/connector"
}

@test "auth refresh is out of scope" {
mark_out_of_scope "Requires OAuth credentials"
}
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -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: "Set up a local agent connector for a Basecamp agent", Actions: []string{"setup", "show"}},
{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", "worktrees"}},
{Name: "help", Category: "additional", Description: "Show help"},
{Name: "version", Category: "additional", Description: "Show version"},
},
Expand Down
54 changes: 49 additions & 5 deletions internal/commands/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os"
"path/filepath"
"runtime"
"slices"
"strconv"
"strings"
"time"
Expand All @@ -29,20 +30,53 @@ import (

// NewConnectCmd is the local agent connector's command group.
func NewConnectCmd() *cobra.Command {
var run connectRunFlags
cmd := &cobra.Command{
Use: "connect",
Short: "Set up a local agent connector for a Basecamp agent",
Short: "Run a local agent connector for a Basecamp agent",
Long: `Run a local agent connector: it listens to the account event feed as a
Basecamp agent, admits what a trusted person asks of that agent, and hands
the work to a local coding agent that replies in Basecamp as the agent.

Connect the agent to a profile first (basecamp auth agent connect -P <profile>),
then run setup on that profile: it records who may drive the agent, maps
projects to the directories their work runs in, and checks the connector is
ready. Show prints what setup recorded.`,
ready. Show prints what setup recorded. Then run the connector on it:

basecamp connect -P <profile> [--project <id>]... [--shadow]

It runs in the foreground until interrupted. Stdout is a wire of one JSON
object per line (events seen, verdicts, dispatches, lifecycle messages;
never content), and logs
go to stderr. SIGINT and SIGTERM cancel live workers with stop reason
shutdown, settle them, and exit 130 and 143. --shadow admits and logs in an
isolated state directory and dispatches nothing. --hold sets a durable hold:
intake and admission run, nothing dispatches or posts, and earlier records
wait for review, until basecamp connect release. Linux only.

basecamp connect status what it heard, holds and ran
basecamp connect doctor what it needs to run
basecamp connect redispatch <id> authorize a record to run
basecamp connect discard <id> close a record without running it
basecamp connect release clear the hold
basecamp connect shadow promote make the shadow ledger the connector's, held
basecamp connect import <file> apply a cutover reconciliation file`,
Example: ` basecamp connect setup -P agent --operator-profile me --route 12345=/src/app
basecamp connect -P agent
basecamp connect -P agent --project 12345 --shadow`,
Args: cobra.NoArgs,
Annotations: map[string]string{
"agent_notes": "Long-running; stdout is NDJSON pointer lines, logs on stderr. Not for interactive use.",
"stdout_wire": "connect",
},
RunE: func(cmd *cobra.Command, _ []string) error {
return runConnect(cmd, &run)
},
}
cmd.AddCommand(newConnectSetupCmd())
cmd.AddCommand(newConnectShowCmd())
addConnectRunFlags(cmd, &run)
cmd.AddCommand(newConnectSetupCmd(), newConnectWorkerMCPCmd(), newConnectShowCmd(), newConnectStatusCmd(), newConnectDoctorCmd(),
newConnectRedispatchCmd(), newConnectDiscardCmd(), newConnectReleaseCmd(), newConnectShadowCmd(), newConnectImportCmd(),
newConnectWorktreesCmd())
return cmd
}

Expand Down Expand Up @@ -157,7 +191,11 @@ func connectShowDisplay(path string, f setup.File, markdown bool) map[string]any
"agent": agent,
"operator": fmt.Sprintf("person %d", f.Trust.OperatorID),
"trust": trust,
"workers": fmt.Sprintf("%s, concurrency %d, deadline %s, worktrees %s", f.Driver, f.Concurrency, time.Duration(f.Deadline), worktrees),
// The worker as well as the driver: the file records which coding
// agent the driver runs, and a file written before that field
// existed still means the default, which is what a person reading
// show needs to see.
"workers": fmt.Sprintf("%s running %s, concurrency %d, deadline %s, worktrees %s", f.Driver, f.WorkerName(), f.Concurrency, time.Duration(f.Deadline), worktrees),
"projects": strconv.Itoa(len(f.Projects)) + " routed",
}
for id, r := range f.Projects {
Expand Down Expand Up @@ -233,6 +271,7 @@ type connectSetupFlags struct {
unwatch []string
unroute []string
driver string
worker string
parallel int
deadline time.Duration
worktrees bool
Expand Down Expand Up @@ -315,6 +354,7 @@ Examples:
fl.StringArrayVar(&f.watch, "watch-completions", nil, "Admit every trusted completion in a routed project (repeatable)")
fl.StringArrayVar(&f.unwatch, "no-watch-completions", nil, "Stop watching a project's completions (repeatable)")
fl.StringVar(&f.driver, "driver", "", "How workers are run: spawn or acp (default spawn)")
fl.StringVar(&f.worker, "worker", "", fmt.Sprintf("The coding agent workers run: %s (default %s)", strings.Join(setup.Workers, ", "), setup.DefaultWorker))
fl.IntVar(&f.parallel, "concurrency", 0, fmt.Sprintf("Workers at once (default %d)", setup.DefaultConcurrency))
fl.DurationVar(&f.deadline, "deadline", 0, fmt.Sprintf("Deadline per task (default %s)", setup.DefaultDeadline))
fl.BoolVar(&f.worktrees, "worktrees", false, "Give each task its own git worktree")
Expand Down Expand Up @@ -749,6 +789,10 @@ func (f *connectSetupFlags) changes(cmd *cobra.Command) (setup.Changes, error) {
default:
return ch, output.ErrUsage(fmt.Sprintf("Invalid --driver %q: use spawn or acp", f.driver))
}
if f.worker != "" && !slices.Contains(setup.Workers, f.worker) {
return ch, output.ErrUsage(fmt.Sprintf("Invalid --worker %q: use %s", f.worker, strings.Join(setup.Workers, ", ")))
}
ch.Worker = f.worker
// A typed zero is out of range, not a request for the default: the flags
// are read as typed, not as their zero values.
if cmd.Flags().Changed("concurrency") {
Expand Down
Loading
Loading