You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Acknowledge a request in the connector before handing it off - #918
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:
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 acks table 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 recorded ack and 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.
--shadow and --hold post 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 until basecamp 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 ack on the line, the session runs basecamp boost update <ack.boost_id> '<phrase>' in the same retry loop. If the update fails, the 👀 still acknowledges, and the handoff says so. Without ack, the session boosts with boost create as 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 ack field is in the request-line list and example, --ack-boost and --no-ack-boost are 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 ack is 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 update as well as boost 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)
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 temporary replace.
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"} and acknowledge: false, so a skill that predates ack does 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 update when ack is on the line, and creates one itself when it is not.
Migration
connect.json writes ack_boost only when it differs from the default, so existing setups get 👀 on upgrade and untouched files still open in older CLIs. connect show reports the boost in effect. After --ack-boost or --no-ack-boost, an older CLI refuses the file; to downgrade, run --ack-boost 👀.
Written for commit d1bb1f8. Summary will update on new commits.
--ack-boost sets the boost the connector acknowledges a request with
(at most 16 characters, counted as boost create counts them; empty is
refused), and --no-ack-boost turns it off. connect.json records it as
ack_boost only when it differs from the default, a 👀 boost, so a file
that keeps the default still opens in a CLI that predates the key.
connect show reports the setting in effect, the default included.
A request someone made of the agent (a mention, an assignment, a ping)
is boosted on its recording, as the agent, with the configured content
before its line is written, and the line carries the boost as
"ack": {"boost_id", "content"}. Measured on R2-D2, the session's own
boost landed 13.3s after the person's ping line; the connector saw the
line on the live feed at once.
A failed boost is logged and the request goes out without ack, for the
session to acknowledge as before. It is never retried: the SDK sends a
boost once, and one that failed may have landed. The boost's id is
recorded in the ledger (migration 16, acks) before the line is written,
so a request handed off again after a crash reuses it. Context
(subscribed, completed, boosted), a shadow run and a hold boost nothing.
The skill rewords the connector's boost with boost update when ack is
on the line, and boosts as before when it is not.
A line with ack says acknowledge false: a skill that predates ack boosts
whenever acknowledge is true, and would put a second boost beside the
connector's. The 👀 then stands, unreworded, until the skill updates.
Moves request acknowledgement into the connector so trusted requests receive a boost before session handoff.
Changes:
Adds configurable acknowledgement boosts, enabled by default.
Records boost IDs for replay and includes them in request lines.
Updates setup, tests, and skill instructions to reuse existing boosts.
[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.
Three edges of the connector's acknowledgement boost:
- A hold could land while the boost was in flight, up to AckTimeout. The
request still went out and the refused state change then stopped the
connector. handOff now reads the record again after the boost and leaves
one that changed state alone, its boost recorded for a redispatch.
- A restart with the boost turned off skipped the ledger, so a request
boosted before a crash went out again without its ack, and its reader
boosted a second time. A recorded boost is now read first.
- Recording the boost shared the boost's deadline, so one that landed late
could go unrecorded and be given again. It has a deadline of its own.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Cached handoff records can still trigger additional boosts after a hold commits.
No open issues 🎉
2 previously missed issues
In code that hasn't changed since last review
Escape angle brackets in custom boost display formatting
internal/commands/connect.go:240
strconv.Quote leaves < unescaped, so a valid custom boost such as <b>OK</b> is treated as HTML by formatDetailValue and rewritten for display. Human-readable connect show therefore misreports the configured acknowledgement. Escape < as the existing exact formatter does so the content stays literal.
Check live hold state before boosting cached records
internal/connector/handoff.go:260
handOffReady caches up to 50 records. If a hold commits during the first boost, the remaining records become held, but each cached record still starts a boost before the post-boost state check. That check prevents request lines, not these additional POSTs after the hold. Check the live hold and runnable record state before acknowledging each record, retaining the post-boost check. Extend the hold-during-ack test with a second pending request and assert that only the first is boosted.
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
commandsCLI command implementationsskillsAgent skillstestsTests (unit and e2e)
2 participants
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.
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 withbasecamp boost update(#917), off the critical path.Context is not acknowledged:
subscribed,completedandboosted(a go included, as before) get no boost. The target is the recording the skill boosted before:recording.recording_id, the same recording asrecording.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:
connect.jsongains one optional key, written only when the setting differs from the default:connect showreports the setting in effect, the default included (ack_boostin JSON, anackline for a person).Failure and crash handling
ack. The session then boosts as it did before.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
The two flags are mutually exclusive. Like any setup change, a new setting needs a connector restart.
Older CLIs.
connect.jsonis parsed withDisallowUnknownFields. Setup therefore writesack_boostonly when it differs from the default, so an untouched file still opens in an older CLI. After--ack-boostor--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 withErrLedgerSchema.Skill
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.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.ackis already acknowledged.boost updateas well asboost create.Tests
Each test was run red against a mutation of the code it guards, then green:
TestARequestIsAcknowledgedBeforeItsLineIsWritten(boost before any stdout, line carries ack)acknowledgecallTestTheAckOnTheLineIsItsBoostIDAndContentacknowledgecallTestAFailedAcknowledgementStillHandsTheRequestOff(line without ack, WARN logged, one attempt)TestARequestHandedOffAgainIsNotBoostedTwiceTestNoAcknowledgementWhenItIsTurnedOff,TestContextIsNotAcknowledged(subscribed, completed, boosted)AckContentandAcknowledgeremovedTestAHoldAcknowledgesNothingTestTheAckBoostRoundTripsAndTheDefaultIsNotWritten,TestSettingTheDefaultAgainRemovesTheKeyTestAnAckBoostBasecampWouldRefuseIsRefused(17 runes, empty, blank, off with content; also a hand-edited file)File.Validatecheck removedTestConnectSetupRefusesABadAckBoost(both flags)TestConnectShowSaysHowRequestsAreAcknowledgedack_boostand display line removedTestConnectSetupSetsTheAckBoostcovers set, keep and off through the command. The request contract (requestLineContract, the skill example) includesack.boost_idandack.contentas optional keys.Shadow has no new test. Its guarantee is that
runConnectnever starts the handoff under--shadow, and nothing here runsrunConnectend to end.bin/ciexits 0. The SDK provenance check skips, as expected while thereplaceis active.Dependencies
UpdateBoost), through the stack's temporaryreplace.basecamp boost update), the base of this PR, which is stacked on connect: hand off an operator's ping #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.
--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.ack, for the session to acknowledge as before. Boosts are never retried, since a failed POST may have landed.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.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 👀.Written for commit d1bb1f8. Summary will update on new commits.