Skip to content

Connect ACP clients to a live REPL Sidekick - #989

Merged
taras merged 14 commits into
mainfrom
agent/sidekick-acp-access-961
Oct 27, 2026
Merged

taras merged 14 commits into
mainfrom
agent/sidekick-acp-access-961

Conversation

@taras

@taras taras commented Oct 24, 2026

Copy link
Copy Markdown
Owner

Why

Connect an ACP client to the Sidekick already held by a live REPL, so it can send a text request and inspect the resulting public activity and entry evidence.

This is a dependent draft on #988. Its initial head is 806dc51cde6b9ad32b63cfaeeb8f39a17fea786e; the selected parent branch is agent/testagent-model-options-984-delivery at d9c43800b491271a1ad07f9cda24caa66ea8c830.

What changes

Before: the REPL's Sidekick has no external ACP attachment or passive command-line read path.

After: start or resume a REPL with an established selected Sidekick conversation and opt in to a private attachment file. Use an existing owner-controlled parent directory:

xmd repl 'xmd://repl/<execution>/entries' --sidekick-acp /absolute/private/attachment.json

Configure an ACP client to launch this stdio backend:

xmd sidekick acp --attach /absolute/private/attachment.json

A separate command can read current public status without sending a prompt:

xmd sidekick read --attach /absolute/private/attachment.json status

Message reads can follow an acknowledged message reference; entry reads return bounded Source, Prompt, Output or Journal pages. The attachment creates no underlying conversation and keeps the selected provider and permissions.

How it works

ACP text prompt or UI Send
  -> one live REPL dispatch and admission policy
  -> existing Sidekick generation, evaluation and feedback
  -> public updates, evaluated Output and joined exchange result

passive read or exact load
  -> existing owner's public state
  -> no new prompt

The opt-in endpoint uses a same-user POSIX Unix socket and a private descriptor bound to the live owner, execution, conversation and cwd. Strict requests and bounded observer queues keep malformed or slow clients from blocking the producer.

Disconnect detaches observation while the REPL continues its exchange. Explicit ACP cancel/close captures and joins only that attachment's exchange. Busy work refuses before a message is saved. External messages retain origin and an acknowledged opaque ID without clearing the human's composer or taking over a held draft.

Review guide

Start with specs/repl-spec.md, “Attaching to the live Sidekick”.

Then review:

  1. packages/cli/src/repl/sidekick/dispatch.ts and its application integration for shared admission and producer ownership.
  2. packages/cli/src/sidekick-acp/ for descriptor identity, bridge mappings, safe reads and exact cancellation.
  3. repl-sidekick-acp.test.ts and repl-sidekick-dispatch.test.ts for the attachment and shared-dispatch regressions.

Look carefully at completed-load replay, disconnect versus cancellation, replaced-file cleanup, and a failed follow-up after an earlier answer.

What must stay true

  • One Sidekick conversation and ordinary entry admission remain authoritative. ACP adds no evaluator, journal writer or entry controller.
  • Saved-message acceptance, joined exchange completion and the evaluated product verdict are distinct. An earlier answer, preview or bare end_turn cannot establish a missing completion.
  • Passive read/load/follow sends no prompt. UI question answering and independently admitted entry ownership remain separate.
  • The host adapters own socket and filesystem resources; shared orchestration remains scoped Effection operations.

The dispatch, attachment, lifecycle, control and repository regression coverage checks these boundaries.

How to verify it

Recorded focused evidence passed on the candidate whose complete source corresponds to this exact head: the eight-file attachment/help/boundary/lifecycle/control/observation/repository/Session-configuration selection reported 39 tests, 186 steps, zero failures, with two real-child summaries of one test and six steps each.

The controlled integration exercises the released ACPX 0.12.0 CLI and XMD's distinct patched provider/runtime, including serial requests, passive reconnect/load, cancellation and incomplete-result controls. The first selection exposed an A6 fixture teardown failure; the correction drains the real event iterator before closing the runtime. The first failure remains retained. A temporary-file collision is a scheduling inference, not an instrumented interleaving.

Both original independent source reviewers passed this exact head. One committed deno task verify:clean passed on it; that proves dependency ownership and non-interference, separately from product correctness.

These are recorded local results on Darwin arm64/Deno 2.9.7, not final CI results or a live operator Sidekick recovery trial. No tests were rerun to prepare this description.

Scope

Included: shared Sidekick dispatch, opt-in local ACP text/load/close support, passive status/message/entry reads, runtime adapter installation, and the associated help, specifications and regressions.

Deferred scope includes queueing, coediting, cross-client question answers, live relationship recovery, remote hosting and repository-wide ACP. The current --observe-worktree spelling remains unchanged; its separate naming proposal has no human approval.

Refs #961, #963.

New abstractions and dependencies

The private dispatch handle serves UI Send and ACP while keeping the producer under the REPL root. The private transport supports the ACP bridge and passive CLI reader without exposing the execution controller.

The CLI declares its direct @agentclientprotocol/sdk@1.3.0 import for the stdio bridge, using the already selected SDK version. deno.lock records that real dependency edge. ACPX and vendor bytes are unchanged; no generated browser or vendor output is added.

Risks and remaining delivery gates

This remains a draft. Combined-source runner measurement and authentic artifact/weight integration are pending delivery work; this description claims no run ID, outcome or consumed artifact. Existing d9 measurements and CI retain their own commit provenance.

Required CI must prove the actual final published head and current main. Any resulting weight or integration commit needs affected evidence correspondence and both original final-head reviews. Final Planner metadata APPLY and independent VERIFY remain outstanding after the configured CI and artifact gates.

The endpoint is POSIX-only and requires an established selected conversation. Switching conversations invalidates the attachment. Controlled client evidence does not resolve the held live Sidekick's unknown feedback acceptance.

Scope confirmation

  • Every changed file supports the purpose described above.
  • Unrelated cleanup and formatting changes are excluded.
  • The lock change and absence of generated output are identified.
  • Final delivery results and metadata are verified on the final head.

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

PR #989: Connect ACP clients to a live REPL Sidekick

36 files, +4397 / -213

Scope

🔴 PR has 4610 lines changed. Split into focused PRs.

🟡 4610 lines changed. PRs under 400 receive more thorough review.

🟡 36 files changed. Are all changes related?

🟡 PR mixes config and source changes.

Structural

🟡 3 console statements.

Oxlint structural signals:

  • no-unused-vars ×23: packages/cli/tests/fixtures/repl/issue-883/sidekick-harness.ts, packages/cli/src/repl/application.ts, packages/cli/src/cli.ts (+1)
  • no-redundant-type-constituents ×7: packages/cli/src/sidekick-acp/posix.ts
  • no-empty-function ×5: packages/cli/src/sidekick-acp/bridge.ts, packages/cli/src/sidekick-acp/posix.ts, packages/cli/src/cli.ts
  • no-unnecessary-type-arguments ×5: packages/cli/src/sidekick-acp/server.ts, packages/cli/src/repl/sidekick/dispatch.ts, packages/cli/src/repl/sidekick/owner.ts (+1)
  • no-unnecessary-boolean-literal-compare ×1: packages/cli/src/repl/application.ts
  • no-unnecessary-type-assertion ×1: packages/cli/tests/fixtures/repl/issue-883/sidekick-harness.ts

Slop

Oxlint slop signals:

  • no-console ×6: packages/cli/src/sidekick-acp/command.ts, packages/cli/src/cli.ts

Static Analysis

Oxlint: 177 diagnostics across 13 files (20 rules)
Density: 0.040 violations/added-line

no-shadow (71): packages/cli/src/repl/sidekick/dispatch.ts, packages/cli/src/repl/sidekick/history.ts, packages/cli/src/repl/sidekick/model.ts (+5)
no-unused-vars (23): packages/cli/tests/fixtures/repl/issue-883/sidekick-harness.ts, packages/cli/src/repl/application.ts, packages/cli/src/cli.ts (+1)
no-floating-promises (18): packages/cli/src/sidekick-acp/server.ts, packages/cli/src/repl/sidekick/dispatch.ts, packages/cli/src/cli.ts (+2)
consistent-return (7): packages/cli/src/repl/application.ts, packages/cli/src/cli.ts, packages/cli/src/repl/program.ts
no-redundant-type-constituents (7): packages/cli/src/sidekick-acp/posix.ts
no-console (6): packages/cli/src/sidekick-acp/command.ts, packages/cli/src/cli.ts
no-underscore-dangle (6): packages/cli/src/sidekick-acp/bridge.ts
unbound-method (6): packages/cli/src/cli.ts, packages/cli/tests/fixtures/repl/issue-883/sidekick-harness.ts, packages/cli/src/repl/program.ts
no-empty-function (5): packages/cli/src/sidekick-acp/bridge.ts, packages/cli/src/sidekick-acp/posix.ts, packages/cli/src/cli.ts
no-unnecessary-type-arguments (5): packages/cli/src/sidekick-acp/server.ts, packages/cli/src/repl/sidekick/dispatch.ts, packages/cli/src/repl/sidekick/owner.ts (+1)
no-array-sort (4): packages/cli/src/repl/sidekick/model.ts, packages/cli/src/repl/application.ts
no-unsafe-type-assertion (4): packages/cli/src/cli.ts, packages/cli/tests/fixtures/repl/issue-883/sidekick-harness.ts, packages/cli/src/deno.ts
consistent-function-scoping (3): packages/cli/src/repl/application.ts
no-useless-default-assignment (3): packages/cli/src/repl/application.ts, packages/cli/src/repl/program.ts
no-duplicate-type-constituents (3): packages/cli/src/repl/application.ts
no-useless-spread (2): packages/cli/src/repl/sidekick/dispatch.ts, packages/cli/src/cli.ts
no-unmodified-loop-condition (1): packages/cli/src/cli.ts
no-misused-spread (1): packages/cli/src/repl/application.ts
no-unnecessary-boolean-literal-compare (1): packages/cli/src/repl/application.ts
no-unnecessary-type-assertion (1): packages/cli/tests/fixtures/repl/issue-883/sidekick-harness.ts

Correctness

No extraneous code patterns detected.

@taras
taras force-pushed the agent/testagent-model-options-984-delivery branch from d9c4380 to e016900 Compare October 26, 2026 17:03
Base automatically changed from agent/testagent-model-options-984-delivery to main October 26, 2026 17:53
taras and others added 10 commits October 26, 2026 16:13
Carries the conflict resolution of 806dc51, which merged the earlier
TestAgent delivery into the attachment branch: the completed-turn row
joins the turn's event stream before reading its result, because the
result settles before the final store writes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every saved Sidekick message carries an opaque ID and a UI or attachment
origin. Both are required: a record missing either is unreadable rather
than read as an earlier shape, and nothing projects a substitute origin
for one. The chat item carries the ID and origin the record holds, so the
live attachment's own saved message is found by that ID alone.

There is one supported format for this data, so the stored records move to
it in place: the two pinned chat fixtures gain those fields on their
message records, and each receipt restates its own byte count and digest.
The architecture and REPL specification say the fields are required.

Formatting of the files this feature already touches is brought to oxfmt's
output, which `deno task lint` checks.
@taras
taras force-pushed the agent/sidekick-acp-access-961 branch from 806dc51 to 40be3e3 Compare October 26, 2026 21:01
Three failures CI demonstrated on 40be3e3, each at a boundary the
focused selection had not reached.

An error thrown out of the Sidekick owner is an authority — persistence,
the files provider, disclosure — ending the command, not one message
refusing. The shared dispatch caught every throw and reported it as a
failed exchange, so a fatal authority failure stopped nothing: the REPL
stayed up and `REPL.Test retained and fatal outcomes` waited for a
resolution that could never come. The producer now records the failed
result for this exchange's observers, releases the reservation, and
raises the error again to the owner it belongs to. Ordinary refusals
still arrive as a failed Result and still end at the chat.

The attachment bridge imports `@agentclientprotocol/sdk`, which only
`packages/cli/deno.json` declared. There are two dependency layouts: Deno
resolved it and pnpm and Bun never linked it under `packages/cli`, so
every Node and Bun invocation of this CLI failed to load the module. It
is declared in `packages/cli/package.json` at the one version `deno.json`
pins, with the lockfiles that declaration implies, and a boundary case
holds the two manifests together.

The A5 attachment case reached for `Promise.withResolvers`, which the
Node project's ES2022 library does not carry, failing `tsc` before any
Node shard ran a test. It builds the one raw promise its callback needs
without an ES2024 builtin.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Found 1 redundant comment. Inline suggestions to remove them below.

// which arrive as a failed Result: it is an authority — persistence,
// the files provider, disclosure — saying this command is over, so it
// reaches the owner of this producer rather than ending here as one
// message's failure.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Redundant comment — restates what the code does.

Suggested change
// message's failure.

The attachment case that proves the bridge against the real released
client located it at `node_modules/acpx/package.json`, a path under the
repository root. Nothing at the root declares ACPX: `packages/acp` and
`packages/test-agent` do, and each runtime's installer places a declared
package differently. Deno's install links it at the root and pnpm's links
it beside the declarer, so that path resolved under both. Bun's install
does neither, so both rows that establish client provenance failed on
`lstat` before reaching the bridge.

The resolver answers instead of a guess: a require rooted at
`packages/acp/package.json` resolves `acpx/package.json`, which ACPX
exports, and every layout that package can resolve under is a layout this
finds. Nothing else about the rows changes, and the vendored snapshot
stays the only ACPX production reaches.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Found 3 redundant comments. Inline suggestions to remove them below.

// which arrive as a failed Result: it is an authority — persistence,
// the files provider, disclosure — saying this command is over, so it
// reaches the owner of this producer rather than ending here as one
// message's failure.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Redundant comment — restates what the code does.

Suggested change
// message's failure.

) {
throw new Error("The attachment file must be a private regular file of at most 16 KiB.");
}
// The open descriptor, rather than a second path read, fences symlink replacement.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Redundant comment — restates what the code does.

Suggested change
// The open descriptor, rather than a second path read, fences symlink replacement.

});
try {
const temp = yield* until(realpath(tmpdir()));
// Cancellation must join creation and identity before removing the owned directory.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Redundant comment — restates what the code does.

Suggested change
// Cancellation must join creation and identity before removing the owned directory.

@taras taras left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Permanent reviewer: correctness, performance impact, minimalism

Reviewed the exact published head 8cf8756c1c40c64441714631cd4aa406a2368988 against base 71083a5093e52fa2a2e95cf3b6c0fc7669c13ffd (36 files, +4283/−213), read against AGENTS.md, the Effection v4 contract, architecture.md (State ownership, REPL kernel, Local Sidekick attachments, Materialization) and the touched sections of specs/repl-spec.md and specs/acp-client-spec.md. Required green is reported passing for this head (https://github.com/taras/executable.md/actions/runs/40411665313).

Verdict: no blocking defect found. The accepted behaviour holds on direct trace: one root-owned dispatch spans each exchange (packages/cli/src/repl/sidekick/dispatch.ts), UI Send/Retry/Stop and the ACP bridge share the same reservation and admission, origin is captured lexically before the producer spawns, acceptance is published only after the message append is acknowledged, exact-handle cancel joins the owner's halt and an old handle cannot stop a successor, fresh --sidekick-acp startup joins one real initialization turn (and Control-C/EOF during it return through the normal exit) and existing selected history reuses its conversation. The POSIX transport creates every native resource after the cleanup that releases it and removes only names whose identity it still owns.

Three findings, each as an inline comment with evidence and a concrete recommendation:

  1. Correctness (narrow, non-blocking): packages/cli/src/sidekick-acp/posix.ts removes the connection listener in the resource body's finally, before the asynchronous teardown reaches server.close(). A peer that connects in that window is counted by net.Server but never tracked or destroyed, so close() cannot drain until that peer goes away. One-line fix.
  2. Performance (measured, conditional): packages/cli/src/cli.ts now statically imports the bridge, so every xmd invocation loads @agentclientprotocol/sdk. Under Node the SDK import costs roughly 60–70 ms wall, about 40 ms beyond the zod the CLI already loads. A focused base-vs-head measurement of xmd --help is identified in the comment; a lazy import behind xmd sidekick acp removes the cost if confirmed.
  3. Minimalism: two filter+map pairs in packages/cli/src/sidekick-acp/server.ts carry unreachable unknown/null branches that flatMap removes, which also lets the bridge's loaded schema drop the unknown variant and its continue.

Accepted consequences noted, not findings: requiring id and origin on every saved message record (commit 40be3e3) makes a sidekick.jsonl written by an earlier build unreadable rather than read as an older shape; that is the ruled current-format policy and the two pinned fixtures were migrated in place with restated digests. A fresh --sidekick-acp start draws no frame until the initialization turn completes; interrupts still work, as the bootstrap shutdown cases show.

Evidence limits: I traced source and the retained review artifacts and ran one read-only module-load timing; I re-ran no product tests, installs or link changes, and the local timing is not a clean-install or compiled-binary number. verify:clean, a current compiled candidate and external provider smoke remain delivery work owned by the existing delivery actors; this review does not approve or integrate.

Comment thread packages/cli/src/sidekick-acp/posix.ts Outdated
},
});
} finally {
server?.off("connection", connected);

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Correctness (narrow): a connection accepted during teardown can stall server.close().

This finally runs when provide() unwinds, which is before the ensure() registered at line 300 starts (Effection runs the task body's own finally first, then the scope destructors — createTask wraps the body as try { … } finally { destroy }). Between this off("connection") and the held.close() at line 312 the teardown suspends on Promise.allSettled(acquisitions), directoryAcquisition and one closeSocket() per tracked socket, while the listener is still bound to the socket path. A peer that connects in that window is still accepted and counted by net.Server (onconnection increments _connections whether or not a listener exists), but connected no longer runs, so the socket is never added to sockets and never destroyed. server.close(cb) then waits for that peer to disconnect, and the peer's wire.receive() is itself waiting for a hello reply with no timeout, so command exit blocks until the client side gives up.

A realistic trigger is the bridge's session/cancel handler (bridge.ts line 312), which opens a fresh connection right after its in-flight prompt connection is closed by this very teardown; cross-process timing makes it unlikely but not impossible, and the consequence is a hung REPL exit.

Recommendation: delete this server?.off("connection", connected) line and keep only the one at line 309, which runs synchronously immediately before held.close() with no suspension in between, so every connection accepted until the listener closes is tracked and closed by the loop at line 305 (a Set iterates entries added during iteration). The off("error", serverFailed) on the next line is fine to keep here. Verification: the existing A5/A6: enclosing owner teardown case still passes; a focused regression would connect once from inside a wrapped closeSocket during teardown and assert the owner scope exits.

Comment thread packages/cli/src/cli.ts
@@ -1,4 +1,6 @@
import { resolve } from "node:path";
import { runSidekickCommand, SIDEKICK_HELP } from "./sidekick-acp/command.ts";

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Performance (measured, conditional): every xmd command now loads the ACP SDK at startup.

This static import chain is cli.ts → sidekick-acp/command.ts → sidekick-acp/bridge.ts → @agentclientprotocol/sdk, so xmd run, xmd --help, the credential helper path and so on pay for the SDK module graph even though only xmd sidekick acp uses it. Read-only timing on this head (Node v26.5.1, arm64 macOS, prepared worktree, three runs):

import('@agentclientprotocol/sdk')  69.4 ms / 59.2 ms / 66.1 ms
import('zod') alone                 22.6 ms   (already a CLI dependency)
deno run packages/cli/src/deno.ts --help  0.88 s / 0.54 s / 0.52 s (whole command, head only)

So the marginal cost is on the order of 40 ms per process under Node source; I did not measure base, the compiled binary or Bun, so treat this as a bound to confirm rather than a regression number.

Recommendation: measure xmd --help base vs head under Node and the compiled binary (for example hyperfine --warmup 3 'node packages/cli/src/node.ts --help' on each, or the compiled xmd --help). If the delta is confirmed, load the bridge lazily behind the sidekick acp branch of runSidekickCommand with a literal dynamic import("./bridge.ts") adapted through until() — the CLI already does this for the workflow host (packages/cli/src/workflow.ts line 1045), so deno compile and knip handle the pattern. protocol.ts/client.ts can stay static; they only pull zod. This changes no behaviour and keeps the manifest-boundary regression in repl-boundaries.test.ts valid.

Comment thread packages/cli/src/sidekick-acp/server.ts Outdated
? { kind: "person", text: one.text }
: one.kind === "reply"
? { kind: "reply", text: one.text }
: { kind: "unknown", text: "" },

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Minimalism: two filter+map pairs carry branches the filter already made unreachable.

replay() filters to person/reply and then maps with an unknown fallback that can never be produced; the bridge mirrors it with an "unknown" member in the loaded schema and a continue (bridge.ts lines 37 and 125–127). status().messages (lines 114–125) does the same with a null branch, plus an one.id !== undefined test that is always true now that id is a required string on the chat item.

Recommendation (behaviour-preserving):

function replay(root: AttachmentRoot, descriptor: Descriptor) {
  const read = root.owner.reading;
  if (!read.ok) {
    throw read.error;
  }
  return read.value.chat.flatMap((one) =>
    one.conversation === descriptor.conversation && (one.kind === "person" || one.kind === "reply")
      ? [{ kind: one.kind, text: one.text }]
      : [],
  );
}
messages: read.value.chat.flatMap((one) =>
  one.kind === "person" && one.conversation === descriptor.conversation
    ? [{ execution: descriptor.execution, conversation: one.conversation, id: one.id }]
    : [],
),

Then drop "unknown" from the bridge's loaded.replay enum and the if (item.kind === "unknown") continue;. The wire shapes are unchanged: the current code never emits unknown or null, and the attachment test's statusSchema already requires non-null entries. Minor, same spirit: application.ts, model.ts and owner.ts reference import("./history.ts").MessageOrigin inline where the rest of this change (dispatch.ts) uses a top-level import type.

taras added 2 commits October 26, 2026 19:15
Review 5660106220 on PR 989 traced a window in the POSIX attachment's
teardown. The resource body's finally detached the connection handler,
and that block runs before the teardown it encloses: the listener was
still accepting while cleanup awaited its acquisitions and closed the
sockets it knew about. A peer arriving in that window was accepted and
counted by the listener, tracked by nobody, and destroyed by nothing, so
`close()` could not drain and the command stayed up until that peer hung
up. Only the error handler comes off there now; the teardown already
takes the connection handler off synchronously, in the statement before
it closes the listener.

The replay and status readings say what they mean with `flatMap`: both
filtered to person and reply rows and then mapped with branches those
filters had already excluded, including one that invented a message with
an empty body. The bridge's replay schema drops the `unknown` kind it
could no longer receive, and the saved-message reading drops a test for
an ID that every record now carries. `MessageOrigin` is imported where
its three readers declare it.
The listener correction had only a direct trace behind it. This is the
observation the A5 journey asks for: a peer accepted after teardown
begins stays owned, closes, and lets the owner join.

The case publishes a real private attachment, has the listener accept one
peer, then begins halting the owner and stops inside that peer's own
cleanup, immediately before its close. The listener still exists at that
barrier and the owner has not joined. A second peer connects there, and
the listener's own acceptance of it — not a client connect, which proves
only that the kernel queued something — is what the case waits for.
Releasing the barrier must then join the owner and must close that late
peer without the fixture touching it, leaving no descriptor, socket or
socket directory behind. Restoring the early handler removal fails it on
the late acceptance, so it discriminates the defect rather than the trace.

Holding that boundary takes one optional operation on the transport,
invoked inside the accepted socket's existing cleanup just before the
close it already owned. It is passed by that one case; the four runtime
entrypoints keep calling the transport as they did, and the close stays
where it was. Watchdogs only fail the case; they order nothing, and
production gains no timeout.
@taras
taras marked this pull request as ready for review October 26, 2026 23:55
@taras
taras enabled auto-merge October 26, 2026 23:55
@taras
taras merged commit 53cdd36 into main Oct 27, 2026
44 checks passed
@taras
taras deleted the agent/sidekick-acp-access-961 branch October 27, 2026 00:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant