Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions cli/testdata/coder_server_--help.golden
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ OPTIONS:
the workspace serves malicious JavaScript. This is recommended for
security purposes if a --wildcard-access-url is configured.

--disable-workspace-agent-context-sync bool, $CODER_DISABLE_WORKSPACE_AGENT_CONTEXT_SYNC
Stop persisting workspace agent context snapshots (instructions,
skills, and MCP state used for pinned chat context). When set, coderd
rejects agent context pushes as unimplemented and agents stop sending
them; chats cannot pin workspace context. Use this to shed the
database write load of context sync on large deployments.

--disable-workspace-sharing bool, $CODER_DISABLE_WORKSPACE_SHARING
Disable workspace sharing. Workspace ACL checking is disabled and only
owners can have ssh, apps and terminal access to workspaces. Access
Expand Down
7 changes: 7 additions & 0 deletions cli/testdata/server-config.yaml.golden
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,13 @@ disableWorkspaceSharing: false
# their chats.
# (default: <unset>, type: bool)
disableChatSharing: false
# Stop persisting workspace agent context snapshots (instructions, skills, and MCP
# state used for pinned chat context). When set, coderd rejects agent context
# pushes as unimplemented and agents stop sending them; chats cannot pin workspace
# context. Use this to shed the database write load of context sync on large
# deployments.
# (default: <unset>, type: bool)
disableWorkspaceAgentContextSync: false
# These options change the behavior of how clients interact with the Coder.
# Clients include the Coder CLI, Coder Desktop, IDE extensions, and the web UI.
client:
Expand Down
6 changes: 5 additions & 1 deletion coderd/agentapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ type Options struct {
Pubsub pubsub.Pubsub
// ContextDirtyMarker is the chatd-backed hydrate/dirty fan-out invoked
// from PushContextState. Nil when chatd is disabled.
ContextDirtyMarker ContextDirtyMarker
ContextDirtyMarker ContextDirtyMarker
// ContextSyncDisabled makes PushContextState reject pushes with a dRPC
// Unimplemented code so agents stop sending context snapshots.
ContextSyncDisabled bool
ConnectionLogger *atomic.Pointer[connectionlog.ConnectionLogger]
DerpMapFn func() *tailcfg.DERPMap
TailnetCoordinator *atomic.Pointer[tailnet.Coordinator]
Expand Down Expand Up @@ -257,6 +260,7 @@ func New(opts Options, workspace database.Workspace, agent database.WorkspaceAge
Clock: opts.Clock,
Database: opts.Database,
DirtyMarker: opts.ContextDirtyMarker,
Disabled: opts.ContextSyncDisabled,
}

// Start background cache refresh loop to handle workspace changes
Expand Down
16 changes: 16 additions & 0 deletions coderd/agentapi/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"golang.org/x/xerrors"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"storj.io/drpc/drpcerr"

"cdr.dev/slog/v3"
agentproto "github.com/coder/coder/v2/agent/proto"
Expand Down Expand Up @@ -67,6 +68,13 @@ type ContextAPI struct {
// snapshot persisted by a push. It is nil when chatd is not running,
// in which case PushContextState stays a pure write path.
DirtyMarker ContextDirtyMarker
// Disabled rejects every push with a dRPC Unimplemented code. The
// agent's DRPCPusher translates that code into ErrPushUnimplemented,
// which terminates its RunPush loop for the life of the connection,
// exactly as if coderd predated the v2.10 Agent API. This is the
// deployment-wide kill switch for context sync write load
// (CODER_DISABLE_WORKSPACE_AGENT_CONTEXT_SYNC).
Disabled bool
}

// ContextDirtyMarker hydrates chats from, and marks chats dirty against, a
Expand Down Expand Up @@ -103,6 +111,14 @@ type ContextDirtyMarker interface {
// authorizes the actor (the agent's token subject) against the
// workspace that owns the agent.
func (a *ContextAPI) PushContextState(ctx context.Context, req *agentproto.PushContextStateRequest) (*agentproto.PushContextStateResponse, error) {
if a.Disabled {
// The Unimplemented code (not a plain error) is what tells the
// agent to stop pushing instead of retrying with backoff.
return nil, drpcerr.WithCode(
xerrors.New("agentapi: workspace agent context sync is disabled on this deployment"),
drpcerr.Unimplemented,
)
}
if req == nil {
return nil, xerrors.New("agentapi: PushContextState request is nil")
}
Expand Down
22 changes: 22 additions & 0 deletions coderd/agentapi/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/lib/pq"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"storj.io/drpc/drpcerr"

"cdr.dev/slog/v3"
"cdr.dev/slog/v3/sloggers/slogtest"
Expand Down Expand Up @@ -58,6 +59,27 @@ func TestPushContextState(t *testing.T) {
)
}

t.Run("DisabledReturnsUnimplemented", func(t *testing.T) {
t.Parallel()

// No InTx or query expectations: a disabled push must return
// before touching the store. The Unimplemented dRPC code is
// load-bearing; the agent's DRPCPusher translates it into
// ErrPushUnimplemented, which stops its RunPush loop instead
// of retrying with backoff.
api, _ := makeAPI(t)
api.Disabled = true

resp, err := api.PushContextState(context.Background(), &agentproto.PushContextStateRequest{
Version: 1,
AggregateHash: []byte{0x01, 0x02},
Initial: true,
})
require.Error(t, err)
require.Nil(t, resp)
require.EqualValues(t, drpcerr.Unimplemented, drpcerr.Code(err))
})

t.Run("AcceptsInitialPush", func(t *testing.T) {
t.Parallel()

Expand Down
3 changes: 3 additions & 0 deletions coderd/apidoc/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions coderd/apidoc/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 49 additions & 0 deletions coderd/workspaceagents_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"github.com/coder/coder/v2/agent/agentcontainers"
"github.com/coder/coder/v2/agent/agentcontainers/acmock"
"github.com/coder/coder/v2/agent/agentcontainers/watcher"
"github.com/coder/coder/v2/agent/agentcontext"
"github.com/coder/coder/v2/agent/agenttest"
agentproto "github.com/coder/coder/v2/agent/proto"
"github.com/coder/coder/v2/coderd/agentapi/metadatabatcher"
Expand Down Expand Up @@ -3374,6 +3375,54 @@ func TestWorkspaceAgentPushContextState(t *testing.T) {
require.False(t, resp.GetAccepted())
}

// TestWorkspaceAgentPushContextStateDisabled verifies the
// --disable-workspace-agent-context-sync kill switch end to end over a
// real dRPC connection: the handler's Unimplemented code must survive
// the transport and be translated by the agent's DRPCPusher into
// ErrPushUnimplemented, which is what terminates the agent's RunPush
// loop instead of retrying with backoff. Nothing may be persisted.
func TestWorkspaceAgentPushContextStateDisabled(t *testing.T) {
t.Parallel()

dv := coderdtest.DeploymentValues(t)
dv.DisableWorkspaceAgentContextSync = true
client, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: dv,
})
user := coderdtest.CreateFirstUser(t, client)
r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
OrganizationID: user.OrganizationID,
OwnerID: user.UserID,
}).WithAgent().Do()
require.Len(t, r.Agents, 1)
agentID := r.Agents[0].ID

ctx := testutil.Context(t, testutil.WaitLong)

agentClient := agentsdk.New(client.URL, agentsdk.WithFixedToken(r.AgentToken))
aAPI, _, err := agentClient.ConnectRPC210(ctx)
require.NoError(t, err)
defer func() {
cErr := aAPI.DRPCConn().Close()
require.NoError(t, cErr)
}()

// Push through the same adapter the agent's RunPush loop uses so
// the test breaks if either side of the Unimplemented contract
// changes.
pusher := agentcontext.NewDRPCPusher(aAPI)
resp, err := pusher.PushContextState(ctx, &agentcontext.PushRequest{
Version: 1,
Initial: true,
})
require.ErrorIs(t, err, agentcontext.ErrPushUnimplemented)
require.Nil(t, resp)

// The rejected push must not have persisted anything.
_, err = db.GetLatestWorkspaceAgentContextSnapshot(dbauthz.AsSystemRestricted(ctx), agentID) //nolint:gocritic // Test assertions read agent-pushed rows directly from the store.
require.ErrorIs(t, err, sql.ErrNoRows)
}

func requireGetManifest(ctx context.Context, t testing.TB, aAPI agentproto.DRPCAgentClient) agentsdk.Manifest {
mp, err := aAPI.GetManifest(ctx, &agentproto.GetManifestRequest{})
require.NoError(t, err)
Expand Down
1 change: 1 addition & 0 deletions coderd/workspaceagentsrpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ func (api *API) workspaceAgentRPC(rw http.ResponseWriter, r *http.Request) {
// Optional:
UpdateAgentMetricsFn: api.UpdateAgentMetrics,
ContextDirtyMarker: contextDirtyMarker,
ContextSyncDisabled: api.DeploymentValues.DisableWorkspaceAgentContextSync.Value(),
}, workspace, workspaceAgent)

streamID := tailnet.StreamID{
Expand Down
10 changes: 10 additions & 0 deletions codersdk/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,7 @@ type DeploymentValues struct {
DisableOwnerWorkspaceExec serpent.Bool `json:"disable_owner_workspace_exec,omitempty" typescript:",notnull"`
DisableWorkspaceSharing serpent.Bool `json:"disable_workspace_sharing,omitempty" typescript:",notnull"`
DisableChatSharing serpent.Bool `json:"disable_chat_sharing,omitempty" typescript:",notnull"`
DisableWorkspaceAgentContextSync serpent.Bool `json:"disable_workspace_agent_context_sync,omitempty" typescript:",notnull"`
ProxyHealthStatusInterval serpent.Duration `json:"proxy_health_status_interval,omitempty" typescript:",notnull"`
EnableTerraformDebugMode serpent.Bool `json:"enable_terraform_debug_mode,omitempty" typescript:",notnull"`
UserQuietHoursSchedule UserQuietHoursScheduleConfig `json:"user_quiet_hours_schedule,omitempty" typescript:",notnull"`
Expand Down Expand Up @@ -3781,6 +3782,15 @@ communicating directly.`,
Value: &c.DisableChatSharing,
YAML: "disableChatSharing",
},
{
Name: "Disable Workspace Agent Context Sync",
Description: "Stop persisting workspace agent context snapshots (instructions, skills, and MCP state used for pinned chat context). When set, coderd rejects agent context pushes as unimplemented and agents stop sending them; chats cannot pin workspace context. Use this to shed the database write load of context sync on large deployments.",
Flag: "disable-workspace-agent-context-sync",
Env: "CODER_DISABLE_WORKSPACE_AGENT_CONTEXT_SYNC",

Value: &c.DisableWorkspaceAgentContextSync,
YAML: "disableWorkspaceAgentContextSync",
},
{
Name: "Session Duration",
Description: "The token expiry duration for browser sessions. Sessions may last longer if they are actively making requests, but this functionality can be disabled via --disable-session-expiry-refresh.",
Expand Down
8 changes: 8 additions & 0 deletions docs/admin/setup/configuration-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ Disable workspace apps that are not served from subdomains. Path-based apps can
- CLI flag: [`--disable-path-apps`](../../reference/cli/server.md#--disable-path-apps)
- YAML key: `disablePathApps`

### Disable workspace agent context sync

Stop persisting workspace agent context snapshots (instructions, skills, and MCP state used for pinned chat context). When set, coderd rejects agent context pushes as unimplemented and agents stop sending them; chats cannot pin workspace context. Use this to shed the database write load of context sync on large deployments.

- Environment variable: `CODER_DISABLE_WORKSPACE_AGENT_CONTEXT_SYNC`
- CLI flag: [`--disable-workspace-agent-context-sync`](../../reference/cli/server.md#--disable-workspace-agent-context-sync)
- YAML key: `disableWorkspaceAgentContextSync`

### Disable workspace sharing

Disable workspace sharing. Workspace ACL checking is disabled and only owners can have ssh, apps and terminal access to workspaces. Access based on the 'owner' role is also allowed unless disabled via --disable-owner-workspace-access.
Expand Down
1 change: 1 addition & 0 deletions docs/reference/api/general.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions docs/reference/api/schemas.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions docs/reference/cli/server.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions enterprise/cli/testdata/coder_server_--help.golden
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ OPTIONS:
the workspace serves malicious JavaScript. This is recommended for
security purposes if a --wildcard-access-url is configured.

--disable-workspace-agent-context-sync bool, $CODER_DISABLE_WORKSPACE_AGENT_CONTEXT_SYNC
Stop persisting workspace agent context snapshots (instructions,
skills, and MCP state used for pinned chat context). When set, coderd
rejects agent context pushes as unimplemented and agents stop sending
them; chats cannot pin workspace context. Use this to shed the
database write load of context sync on large deployments.

--disable-workspace-sharing bool, $CODER_DISABLE_WORKSPACE_SHARING
Disable workspace sharing. Workspace ACL checking is disabled and only
owners can have ssh, apps and terminal access to workspaces. Access
Expand Down
1 change: 1 addition & 0 deletions site/src/api/typesGenerated.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading