Skip to content

Acknowledge a request in the connector before handing it off - #918

Merged
robzolkos merged 3 commits into
mainfrom
connect-ack-boost
Oct 20, 2026
Merged

robzolkos merged 3 commits into
mainfrom
connect-ack-boost

Conversation

@robzolkos

@robzolkos robzolkos commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

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 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) 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

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.

View guided diff Turn on auto-fix

@github-actions github-actions Bot added commands CLI command implementations tests Tests (unit and e2e) skills Agent skills labels Oct 19, 2026
@robzolkos
robzolkos force-pushed the boost-update-cmd branch 2 times, most recently from 99b508f to 973e182 Compare October 20, 2026 01:16
@robzolkos
robzolkos force-pushed the connect-ack-boost branch 2 times, most recently from 5adcb7a to d4cfae8 Compare October 20, 2026 01:25
@robzolkos
robzolkos force-pushed the connect-ack-boost branch 2 times, most recently from dc3c00f to cf7e568 Compare October 20, 2026 02:30
Base automatically changed from boost-update-cmd to main October 20, 2026 02:48
--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.
@robzolkos
robzolkos marked this pull request as ready for review October 20, 2026 02:49
@robzolkos
robzolkos requested a review from a team as a code owner October 20, 2026 02:49
Copilot AI balanced review requested due to automatic review settings October 20, 2026 02:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Replay and persistence failures can duplicate boosts, and a concurrent hold can still allow request dispatch.

3 open issues
What changed in this PR

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.

File Description
skills/​basecamp-connect/​subagent.md Clarifies when acknowledgement remains owed.
skills/​basecamp-connect/​SKILL.md Documents boost reuse and configuration.
skill-evals/​cases/​basecamp-connect/​project-agents-repo.yml Rejects boost updates during repo selection.
internal/​connector/​setup/​file.go Defines and validates acknowledgement settings.
internal/​connector/​setup/​checks.go Updates the project-check comment.
internal/​connector/​setup/​apply.go Applies settings and omits defaults.
internal/​connector/​setup/​ack_test.go Tests validation and configuration persistence.
internal/​connector/​sdk_booster.go Adds the SDK boost adapter.
internal/​connector/​request_contract_test.go Adds optional acknowledgement fields.
internal/​connector/​ledger.go Registers migration 16.
internal/​connector/​ledger_acks.go Stores and retrieves acknowledgements.
internal/​connector/​handoff.go Boosts requests before handoff.
internal/​connector/​handoff_ack_test.go Tests acknowledgement and replay behavior.
internal/​commands/​connect.go Adds setup flags and setting display.
internal/​commands/​connect_setup_guided.go Recognizes acknowledgement policy flags.
internal/​commands/​connect_run.go Wires boosting into connector execution.
internal/​commands/​connect_ack_test.go Tests setup flags and display output.

Review effort: Balanced

Comment thread internal/connector/handoff.go
Comment thread internal/connector/handoff.go Outdated
Comment thread internal/connector/handoff.go Outdated
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.
Copilot AI balanced review requested due to automatic review settings October 20, 2026 03:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium severity 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.

Medium severity 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.

3 resolved since last review

Review effort: Balanced

@robzolkos
robzolkos merged commit 3776fcd into main Oct 20, 2026
32 checks passed
@robzolkos
robzolkos deleted the connect-ack-boost branch October 20, 2026 03:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commands CLI command implementations skills Agent skills tests Tests (unit and e2e)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants