diff --git a/apps/backend/go.mod b/apps/backend/go.mod index ccbd5de885..6a4371a6e7 100644 --- a/apps/backend/go.mod +++ b/apps/backend/go.mod @@ -116,4 +116,4 @@ require ( gotest.tools/v3 v3.5.2 // indirect ) -replace github.com/coder/acp-go-sdk => github.com/kdlbs/acp-go-sdk v0.13.6-0.20260609161428-91d5e43d6599 +replace github.com/coder/acp-go-sdk => github.com/kdlbs/acp-go-sdk v0.13.6-0.20260625174839-e37105a28386 diff --git a/apps/backend/go.sum b/apps/backend/go.sum index c21d8b82ee..ab51f92c73 100644 --- a/apps/backend/go.sum +++ b/apps/backend/go.sum @@ -217,8 +217,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/kdlbs/acp-go-sdk v0.13.6-0.20260609161428-91d5e43d6599 h1:VeNo6BcOkY18Hj9HopG7/3t5PpD4ocmuxMIuGSobzLQ= -github.com/kdlbs/acp-go-sdk v0.13.6-0.20260609161428-91d5e43d6599/go.mod h1:yKzM/3R9uELp4+nBAwwtkS0aN1FOFjo11CNPy37yFko= +github.com/kdlbs/acp-go-sdk v0.13.6-0.20260625174839-e37105a28386 h1:zUFkN1/+o3vEYqrkshpykUBAstk6NqiitMuh+dR+Tik= +github.com/kdlbs/acp-go-sdk v0.13.6-0.20260625174839-e37105a28386/go.mod h1:yKzM/3R9uELp4+nBAwwtkS0aN1FOFjo11CNPy37yFko= github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY= github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= diff --git a/apps/backend/internal/agentctl/server/adapter/transport/acp/adapter.go b/apps/backend/internal/agentctl/server/adapter/transport/acp/adapter.go index c1fec48615..fc18a82bfb 100644 --- a/apps/backend/internal/agentctl/server/adapter/transport/acp/adapter.go +++ b/apps/backend/internal/agentctl/server/adapter/transport/acp/adapter.go @@ -59,11 +59,11 @@ const wakeupPromptTimeout = 30 * time.Minute const notifQueueCapacity = 4096 // acpNotifQueueDefault is the per-connection capacity passed to the SDK's -// inbound notification queue. Larger than the SDK's own default (1024) so -// long session/load replays (auggie 500+ exchanges, claude with heavy tool -// use) don't overflow and tear the connection down. The SDK still bounds -// memory to (capacity * avg notification size). -const acpNotifQueueDefault = 16384 +// inbound notification queue. Long session/load replays can emit tens of +// thousands of notifications before the response, so default to the configured +// ceiling instead of requiring operators to know about KANDEV_ACP_NOTIF_QUEUE. +// The SDK still bounds memory to (capacity * avg notification size). +const acpNotifQueueDefault = 131072 // acpNotifQueueMin / acpNotifQueueMax clamp KANDEV_ACP_NOTIF_QUEUE so a // misconfigured value can't either re-introduce the overflow (too low) or diff --git a/apps/backend/internal/agentctl/server/adapter/transport/acp/adapter_queue_test.go b/apps/backend/internal/agentctl/server/adapter/transport/acp/adapter_queue_test.go new file mode 100644 index 0000000000..6ae5667d6a --- /dev/null +++ b/apps/backend/internal/agentctl/server/adapter/transport/acp/adapter_queue_test.go @@ -0,0 +1,56 @@ +package acp + +import "testing" + +func TestACPNotifQueueCapacity(t *testing.T) { + t.Run("default uses max capacity for long session replays", func(t *testing.T) { + t.Setenv("KANDEV_ACP_NOTIF_QUEUE", "") + + if got := acpNotifQueueCapacity(); got != acpNotifQueueDefault { + t.Fatalf("default queue capacity = %d, want %d", got, acpNotifQueueDefault) + } + if got := acpNotifQueueDefault; got != acpNotifQueueMax { + t.Fatalf("default queue capacity = %d, want max %d", got, acpNotifQueueMax) + } + }) + + t.Run("invalid env falls back to default", func(t *testing.T) { + t.Setenv("KANDEV_ACP_NOTIF_QUEUE", "not-a-number") + + if got := acpNotifQueueCapacity(); got != acpNotifQueueDefault { + t.Fatalf("invalid env queue capacity = %d, want %d", got, acpNotifQueueDefault) + } + }) + + t.Run("non-positive env falls back to default", func(t *testing.T) { + for _, value := range []string{"0", "-5"} { + t.Run(value, func(t *testing.T) { + t.Setenv("KANDEV_ACP_NOTIF_QUEUE", value) + + if got := acpNotifQueueCapacity(); got != acpNotifQueueDefault { + t.Fatalf("non-positive env queue capacity = %d, want %d", got, acpNotifQueueDefault) + } + }) + } + }) + + t.Run("in-range env is honored", func(t *testing.T) { + t.Setenv("KANDEV_ACP_NOTIF_QUEUE", "4096") + + if got := acpNotifQueueCapacity(); got != 4096 { + t.Fatalf("in-range env queue capacity = %d, want 4096", got) + } + }) + + t.Run("env is clamped", func(t *testing.T) { + t.Setenv("KANDEV_ACP_NOTIF_QUEUE", "1") + if got := acpNotifQueueCapacity(); got != acpNotifQueueMin { + t.Fatalf("low env queue capacity = %d, want %d", got, acpNotifQueueMin) + } + + t.Setenv("KANDEV_ACP_NOTIF_QUEUE", "999999") + if got := acpNotifQueueCapacity(); got != acpNotifQueueMax { + t.Fatalf("high env queue capacity = %d, want %d", got, acpNotifQueueMax) + } + }) +}