Acknowledge a request in the connector before handing it off
Problem
The connector sees a person's request at once, but the acknowledgement boost comes later, from the orchestrating Claude session. On R2-D2, the person's ping line was created at 22:29:06.4Z and the agent's boost landed at 22:29:19.7Z, 13.3s later, though the connector had the line from the live feed straight away.
What changes
The connector now posts the acknowledgement itself. When it hands off a request someone made of the agent (acknowledge: true: mentioned, assigned, pinged), it first boosts the request's recording as the agent with the configured content, then writes the request line. The default is 👀, so existing setups get this on upgrade. The session then rewords that boost with basecamp boost update (#917), off the critical path.
Context is not acknowledged: subscribed, completed and boosted (a go included, as before) get no boost. The target is the recording the skill boosted before: recording.recording_id, the same recording as recording.url (the chat line in a Ping or Campfire, the comment, the card or to-do for an assignment).
Data shape
The request line gains one optional key, present only when the boost landed:
"ack": {"boost_id": 555, "content": "👀"}
connect.json gains one optional key, written only when the setting differs from the default:
"ack_boost": {"content": "On it"} // --ack-boost 'On it'
"ack_boost": {"off": true} // --no-ack-boost
connect show reports the setting in effect, the default included (ack_boost in JSON, an ack line for a person).
Failure and crash handling
- A failed boost never blocks a request. Any error is logged at WARN, and the line goes out without
ack. The session then boosts as it did before. - No retries. The SDK sends a boost POST once (it is non-idempotent there), and the connector does not retry it, because a failed request may have landed. The boost has a 10s timeout, and a shutdown does not cancel it midway.
- A reprint does not boost twice. The boost id is written to the ledger (migration 16, a new
ackstable keyed by event id) before the line is written. If the connector crashes after printing the line but before closing the record, the restart reprints the request with the recordedackand does not boost again. The remaining window is a crash between Basecamp creating the boost and the ledger write, one local SQLite insert. That window is the same double post a timed-out boost that landed would cause, and the session's fallback already risks that. --shadowand--holdpost nothing. A shadow run does not start the handoff at all. A hold keeps every record out of the handoff query, so nothing is boosted untilbasecamp connect release.
Setup
basecamp connect setup -P <profile> --ack-boost '<content>' # at most 16 characters, counted as runes like boost create; empty refused
basecamp connect setup -P <profile> --no-ack-boost # the session acknowledges, as today
The two flags are mutually exclusive. Like any setup change, a new setting needs a connector restart.
Older CLIs. connect.json is parsed with DisallowUnknownFields. Setup therefore writes ack_boost only when it differs from the default, so an untouched file still opens in an older CLI. After --ack-boost or --no-ack-boost, an older CLI refuses the file (unknown field "ack_boost"). To downgrade, run --ack-boost 👀 first, which removes the key. Migration 16 works like every earlier migration: an older build refuses the newer ledger with ErrLedgerSchema.
Skill
- a. Acknowledge. With
ackon the line, the session runsbasecamp boost update <ack.boost_id> '<phrase>'in the same retry loop. If the update fails, the 👀 still acknowledges, and the handoff says so. Withoutack, the session boosts withboost createas before. - Removed the "the reply itself can be the acknowledgement" shortcut. With the connector's boost already in place by default, it no longer saved anything.
- Documented the new pieces. The
ackfield is in the request-line list and example,--ack-boostand--no-ack-boostare in the setup table and an Acknowledgement paragraph, and the description and "Three pieces" now say the connector boosts. - subagent.md. An acknowledgement is owed only when no boost landed. A line with
ackis already acknowledged. - Compaction budget. The per-request section ends at byte 13,877 of the 14,000 budget, where it ended at 13,951 before. To make room, the "starts from the present" note moved to Checking on it.
- Eval. The repo-selection eval now rejects
boost updateas well asboost create.
Tests
Each test was run red against a mutation of the code it guards, then green:
| Test | Mutation that turns it red |
|---|---|
TestARequestIsAcknowledgedBeforeItsLineIsWritten (boost before any stdout, line carries ack) | no acknowledge call |
TestTheAckOnTheLineIsItsBoostIDAndContent | no acknowledge call |
TestAFailedAcknowledgementStillHandsTheRequestOff (line without ack, WARN logged, one attempt) | failure returned instead of logged |
TestARequestHandedOffAgainIsNotBoostedTwice | ledger lookup removed (2 boosts) |
TestNoAcknowledgementWhenItIsTurnedOff, TestContextIsNotAcknowledged (subscribed, completed, boosted) | guards on AckContent and Acknowledge removed |
TestAHoldAcknowledgesNothing | hold clause dropped from the handoff query |
TestTheAckBoostRoundTripsAndTheDefaultIsNotWritten, TestSettingTheDefaultAgainRemovesTheKey | default no longer normalized to no key |
TestAnAckBoostBasecampWouldRefuseIsRefused (17 runes, empty, blank, off with content; also a hand-edited file) | File.Validate check removed |
TestConnectSetupRefusesABadAckBoost (both flags) | mutual exclusion removed |
TestConnectShowSaysHowRequestsAreAcknowledged | show's resolved ack_boost and display line removed |
TestConnectSetupSetsTheAckBoost covers set, keep and off through the command. The request contract (requestLineContract, the skill example) includes ack.boost_id and ack.content as optional keys.
Shadow has no new test. Its guarantee is that runConnect never starts the handoff under --shadow, and nothing here runs runConnect end to end.
bin/ci exits 0. The SDK provenance check skips, as expected while the replace is active.
Dependencies
- basecamp-sdk#1016 (
UpdateBoost), through the stack's temporaryreplace. - #917 (
basecamp boost update), the base of this PR, which is stacked on #914.
Not live-verified here. The coordinator runs R2-D2 live.
Summary by cubic
The connector now acknowledges each trusted request itself with a boost before handing it off, instead of waiting for the orchestration session, which on R2-D2 took 13.3 seconds.
- Boosts the request's recording as the agent with the configured content (default 👀, set with
--ack-boost, off with--no-ack-boost) before writing the request line; the line carries"ack": {"boost_id", "content"}andacknowledge: false, so a skill that predatesackdoes not put a second boost beside the connector's. - A failed boost never blocks a request: it is logged and the line goes out without
ack, for the session to acknowledge as before. Boosts are never retried, since a failed POST may have landed. - The boost id is recorded in the ledger (migration 16) before the line is written, so a reprinted request reuses its boost instead of posting twice; a recorded boost is reused even after boosting is turned off, and recording has its own deadline so a boost that landed late is still noted.
- Context (
subscribed,completed,boosted) and shadow runs boost nothing. A hold boosts nothing, and one that lands while the boost is in flight stops the handoff, leaving the request held with its boost recorded for a later redispatch. - The skill now rewrites the connector's boost with
boost updatewhenackis on the line, and creates one itself when it is not.
Migration
connect.jsonwritesack_boostonly when it differs from the default, so existing setups get 👀 on upgrade and untouched files still open in older CLIs.connect showreports the boost in effect. After--ack-boostor--no-ack-boost, an older CLI refuses the file; to downgrade, run--ack-boost 👀.