+
+## What Agent Relay is and isn't
+
+Agent Relay changes where a cloud agent's tool calls execute.
+It doesn't change where the agent's orchestration or AI inference run.
+Those stay with the cloud provider.
+
+Agent Relay is not:
+
+- A replacement for [Coder Agents](../agents/index.md), Coder's own AI workflow infrastructure that runs its native agent loop inside the Coder control plane and calls out to your configured LLM provider for inference.
+- A proxy or observability layer for a provider's AI inference.
+ Coder has no access to model selection or token usage for sessions that run through Agent Relay.
+- A self-hosted deployment of a cloud provider's control plane.
+ The provider's orchestration stays cloud-hosted.
+
+## Business value
+
+- Developers keep using the cloud agent client and workflow they already know.
+- Platform and security teams control the infrastructure where agent sessions execute and which internal resources those sessions can reach.
+- Agent sessions run in workspaces built from the same templates, networking, and governance controls as the rest of your Coder deployment.
+- Each session gets its own workspace, provisioned on demand and deleted when the session ends.
+
+## Current state
+
+Agent Relay is in [early access](../../install/releases/feature-stages.md#early-access-features) and is in closed preview with select customers.
+
+## Supported providers
+
+[Cursor](./cursor.md) is the first provider Agent Relay supports.
+Coder built Agent Relay to support additional cloud-hosted agent providers as they add support for self-hosted execution.
+
+## Get started
+
+If you want access to Agent Relay or want updates on the support status for your cloud-hosted agent provider of choice, talk to your [Coder account team](https://coder.com/contact) or email [sales@coder.com](mailto:sales@coder.com).
+
+## Learn more
+
+- [Agent Relay for Cursor](./cursor.md)
+- [Coder Agents](../agents/index.md)
+- [Architecture](../../admin/infrastructure/architecture.md)
diff --git a/docs/ai-coder/index.md b/docs/ai-coder/index.md
index 541cf536844..89af98020bc 100644
--- a/docs/ai-coder/index.md
+++ b/docs/ai-coder/index.md
@@ -1,52 +1,69 @@
# Run AI Coding Agents in Coder
-Learn how to run & manage coding agents with Coder, both alongside existing
-workspaces and for background task execution.
+Learn how to run & manage coding agents with Coder, both alongside existing workspaces and for background task execution.
-## Agents in the IDE
-
-Coder [integrates with IDEs](../user-guides/workspace-access/index.md) such as
-Cursor, Devin Desktop, and Zed that include built-in coding agents to work alongside
-developers. Additionally, template admins can
-[pre-install extensions](https://registry.coder.com/modules/coder/vscode-web)
-for agents such as GitHub Copilot.
+Coder supports several ways to run and [govern](#govern-ai-activity-with-ai-governance) coding agents, depending on how much control you need over execution and orchestration:
-These agents work well inside existing Coder workspaces as they can simply be
-enabled via an extension or are built-into the editor.
+- [Coder Agents](#coder-agents), self-hosted AI workflow infrastructure best suited for headless, automated background tasks and parallel agentic development in a conversational UI.
+- [Agent Relay](#agent-relay), best suited for preserving the cloud agent experience developers already know while running execution in self-hosted Coder workspaces.
+- [Agents in the IDE](#agents-in-the-ide), best suited for in-editor code assist use cases alongside a developer's existing workflow.
+- [Agents in workspace templates](#agents-in-workspace-templates), best suited for developers who want to pair one-on-one with an agent like Claude Code or Codex in a workspace.
## Coder Agents
-In cases where the IDE is secondary, such as prototyping, research, or
-long-running background jobs, [Coder Agents](./agents/index.md) is the
-recommended way to delegate development work to coding agents in your Coder
-deployment.
+In cases where the IDE is secondary, such as prototyping, research, or long-running background jobs, [Coder Agents](./agents/index.md) is the recommended way to delegate development work to coding agents in your Coder deployment.
-Coder Agents is a native AI coding agent built into Coder. The agent loop runs
-in the Coder control plane on your infrastructure rather than inside the
-workspace, so workspaces can be completely network isolated. Developers
-interact with agents through the web UI or the REST API.
+Coder Agents is a native AI coding agent built into Coder.
+The agent loop runs in the Coder control plane on your infrastructure rather than inside the workspace, so workspaces can be completely network isolated.
+Developers interact with agents through the web UI or the REST API.

-[Learn more about Coder Agents](./agents/index.md) for architecture details,
-supported LLM providers, and how to get started.
+[Learn more about Coder Agents](./agents/index.md) for architecture details, supported LLM providers, and how to get started.
+
+## Agent Relay
+
+[Agent Relay](./agent-relay/index.md) connects a supported cloud-hosted AI agent provider's hosted sessions to self-hosted Coder workspaces.
+The provider's orchestration and AI inference stay cloud-hosted; a worker process inside the workspace executes the agent's tool calls.
+[Cursor Cloud Agents](https://cursor.com/cloud) is the first supported provider.
+
+Agent Relay is in [early access](../install/releases/feature-stages.md#early-access-features) and is in closed preview with select customers.
+
+[Learn more about Agent Relay](./agent-relay/index.md) for architecture details and supported providers.
+
+## Agents in the IDE
+
+Coder [integrates with IDEs](../user-guides/workspace-access/index.md) such as Cursor, Devin Desktop, and Zed that include built-in coding agents to work alongside developers.
+Additionally, template admins can [pre-install extensions](https://registry.coder.com/modules/coder/vscode-web) for agents such as GitHub Copilot.
+
+These agents work well inside existing Coder workspaces as they can simply be enabled via an extension or are built-into the editor.
+
+## Agents in workspace templates
+
+Template admins can install terminal-based coding agents, such as Claude Code or Codex, directly into a workspace template using a [registry module](https://registry.coder.com).
+Pick from a curated list of agent modules in the [template builder](../admin/templates/creating-templates.md#template-builder), or add a module directly in Terraform:
+
+```tf
+module "claude-code" {
+ source = "registry.coder.com/coder/claude-code/coder"
+ version = "~> 5.2"
+ agent_id = coder_agent.main.id
+}
+```
+
+Visit the [Coder Registry](https://registry.coder.com) for the full list of available agent modules.
+
+[Learn more about extending templates](../admin/templates/extending-templates/index.md).
## Govern AI activity with AI Governance
-AI coding tools are quickly becoming core to how engineering teams ship
-software. As adoption grows, platform teams want a clear picture of how AI is
-being used, consistent guardrails across teams, and predictable cost controls
-so they can confidently scale AI tooling to the whole organization.
-
-[AI Governance](./ai-governance.md) is included with a Premium license and adds
-observability, management, and policy controls for AI tooling across your
-Coder deployment. It includes:
-
-- [AI Gateway](./ai-gateway/index.md) for centralized authentication, audit
- trails of prompts and tool invocations, and policy enforcement against
- upstream LLM providers.
-- [Agent Firewall](./agent-firewall/index.md) for process-level network and
- command policies that restrict what agents can reach and do inside a
- workspace.
-[Learn more about AI Governance](./ai-governance.md) for use cases, entitlements,
-and how to enable it in your deployment.
+AI coding tools are quickly becoming core to how engineering teams ship software.
+As adoption grows, platform teams want a clear picture of how AI is being used, consistent guardrails across teams, and predictable cost controls so they can confidently scale AI tooling to the whole organization.
+
+[AI Governance](./ai-governance.md) is included with a Premium license and adds observability, management, and policy controls for AI tooling across your Coder deployment.
+It includes:
+
+- [AI Gateway](./ai-gateway/index.md) for centralized authentication, audit trails of prompts and tool invocations, and policy enforcement against upstream LLM providers.
+- [Agent Firewall](./agent-firewall/index.md) for process-level network and command policies that restrict what agents can reach and do inside a workspace.
+
+[Learn more about AI Governance](./ai-governance.md) for use cases, entitlements, and how to enable it in your deployment.
diff --git a/docs/images/guides/ai-agents/agent-relay-stack.png b/docs/images/guides/ai-agents/agent-relay-stack.png
new file mode 100644
index 00000000000..ea4ab90e5ab
Binary files /dev/null and b/docs/images/guides/ai-agents/agent-relay-stack.png differ
diff --git a/docs/manifest.json b/docs/manifest.json
index f34b484f228..74289e51af8 100644
--- a/docs/manifest.json
+++ b/docs/manifest.json
@@ -1136,6 +1136,20 @@
}
]
},
+ {
+ "title": "Agent Relay",
+ "description": "Connect supported cloud-hosted AI agents to self-hosted Coder workspaces with Agent Relay.",
+ "path": "./ai-coder/agent-relay/index.md",
+ "state": ["early access"],
+ "children": [
+ {
+ "title": "Agent Relay for Cursor",
+ "description": "Run Cursor's cloud agent sessions inside self-hosted Coder workspaces with Agent Relay.",
+ "path": "./ai-coder/agent-relay/cursor.md",
+ "state": ["early access"]
+ }
+ ]
+ },
{
"title": "AI Governance",
"description": "Govern AI usage at scale with AI Governance: Agent Firewall, AI Gateway, and reporting.",
diff --git a/docs/reference/api/enterprise.md b/docs/reference/api/enterprise.md
index 6fb958ea3c9..70707e53ee0 100644
--- a/docs/reference/api/enterprise.md
+++ b/docs/reference/api/enterprise.md
@@ -356,6 +356,7 @@ curl -X GET http://coder-server:8080/api/v2/appearance \
}
],
"application_name": "string",
+ "codernauts_enabled": true,
"docs_url": "string",
"logo_url": "string",
"service_banner": {
@@ -408,6 +409,7 @@ curl -X PUT http://coder-server:8080/api/v2/appearance \
}
],
"application_name": "string",
+ "codernauts_enabled": true,
"logo_url": "string",
"service_banner": {
"background_color": "string",
@@ -437,6 +439,7 @@ curl -X PUT http://coder-server:8080/api/v2/appearance \
}
],
"application_name": "string",
+ "codernauts_enabled": true,
"logo_url": "string",
"service_banner": {
"background_color": "string",
diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md
index 4b1467bec02..74018f79d33 100644
--- a/docs/reference/api/schemas.md
+++ b/docs/reference/api/schemas.md
@@ -1684,6 +1684,7 @@ None
}
],
"application_name": "string",
+ "codernauts_enabled": true,
"docs_url": "string",
"logo_url": "string",
"service_banner": {
@@ -1708,6 +1709,7 @@ None
|------------------------|---------------------------------------------------------|----------|--------------|---------------------------------------------------------------------|
| `announcement_banners` | array of [codersdk.BannerConfig](#codersdkbannerconfig) | false | | |
| `application_name` | string | false | | |
+| `codernauts_enabled` | boolean | false | | |
| `docs_url` | string | false | | |
| `logo_url` | string | false | | |
| `service_banner` | [codersdk.BannerConfig](#codersdkbannerconfig) | false | | Deprecated: ServiceBanner has been replaced by AnnouncementBanners. |
@@ -15313,6 +15315,7 @@ Restarts will only happen on weekdays in this list on weeks which line up with W
}
],
"application_name": "string",
+ "codernauts_enabled": true,
"logo_url": "string",
"service_banner": {
"background_color": "string",
@@ -15328,6 +15331,7 @@ Restarts will only happen on weekdays in this list on weeks which line up with W
|------------------------|---------------------------------------------------------|----------|--------------|---------------------------------------------------------------------|
| `announcement_banners` | array of [codersdk.BannerConfig](#codersdkbannerconfig) | false | | |
| `application_name` | string | false | | |
+| `codernauts_enabled` | boolean | false | | |
| `logo_url` | string | false | | |
| `service_banner` | [codersdk.BannerConfig](#codersdkbannerconfig) | false | | Deprecated: ServiceBanner has been replaced by AnnouncementBanners. |
diff --git a/docs/reference/glossary.md b/docs/reference/glossary.md
index 63a67aed3f5..9233b955a9a 100644
--- a/docs/reference/glossary.md
+++ b/docs/reference/glossary.md
@@ -7,6 +7,7 @@ Each entry gives a short definition and, where it helps, links to the page that
> Several Coder terms share the word "agent" but mean different things:
>
> - [Coder Agents](#coder-agents) is the AI product for delegating development work to coding agents.
+> - [Agent Relay](#agent-relay) connects a cloud-hosted AI agent provider's hosted sessions to self-hosted workspaces. It is not Coder Agents.
> - A [workspace agent](#workspace-agent) is the process that runs inside a workspace to provide SSH, port forwarding, the web terminal, and other services.
> - [`coder_agent`](#coder_agent) is the Terraform resource in a template that declares a workspace agent.
@@ -24,6 +25,13 @@ It was previously named Agent Boundaries and uses a sandbox backend, `nsjail` by
This feature requires a Premium license.
Refer to [Agent Firewall](../ai-coder/agent-firewall/index.md).
+### Agent Relay
+
+A feature that connects a supported cloud-hosted AI agent provider's hosted sessions to self-hosted [workspaces](#workspace).
+The provider's orchestration and AI inference stay cloud-hosted; a worker process inside the workspace executes the agent's tool calls.
+In [early access](../install/releases/feature-stages.md#early-access-features).
+Refer to [Agent Relay](../ai-coder/agent-relay/index.md).
+
### AI Gateway
An LLM gateway in `coderd` that authenticates users, forwards traffic to providers such as OpenAI and Anthropic, audits prompts and tool invocations, and centralizes MCP administration.
diff --git a/docs/user-guides/workspace-management.md b/docs/user-guides/workspace-management.md
index 013b0a29ab8..1d2fbc37c19 100644
--- a/docs/user-guides/workspace-management.md
+++ b/docs/user-guides/workspace-management.md
@@ -1,8 +1,7 @@
# Workspaces
-A workspace is the environment that a developer works in. Developers in a team
-each work from their own workspace and can use
-[multiple IDEs](./workspace-access/index.md).
+A workspace is the environment where a developer or a coding agent works.
+Developers and agents in a team each work from their own workspace and can use [multiple IDEs](./workspace-access/index.md).
A developer creates a workspace from a
[shared template](../admin/templates/index.md). This lets an entire team work in
diff --git a/enterprise/coderd/aibridgeserve.go b/enterprise/coderd/aibridgeserve.go
index 09c48086a6d..44cd4dd16af 100644
--- a/enterprise/coderd/aibridgeserve.go
+++ b/enterprise/coderd/aibridgeserve.go
@@ -159,7 +159,7 @@ func (api *API) aiGatewayServe(rw http.ResponseWriter, r *http.Request) {
return
}
- server := drpcserver.NewWithOptions(&tracing.DRPCHandler{Handler: mux},
+ server := drpcsdk.NewServer(logger, &tracing.DRPCHandler{Handler: mux},
drpcserver.Options{
Manager: drpcsdk.DefaultDRPCOptions(nil),
Log: func(err error) {
diff --git a/enterprise/coderd/appearance.go b/enterprise/coderd/appearance.go
index db845fadea3..3bf28107879 100644
--- a/enterprise/coderd/appearance.go
+++ b/enterprise/coderd/appearance.go
@@ -66,6 +66,7 @@ func (f *appearanceFetcher) Fetch(ctx context.Context) (codersdk.AppearanceConfi
applicationName string
logoURL string
announcementBannersJSON string
+ codernautsEnabled bool
)
eg.Go(func() (err error) {
applicationName, err = f.database.GetApplicationName(ctx)
@@ -88,6 +89,13 @@ func (f *appearanceFetcher) Fetch(ctx context.Context) (codersdk.AppearanceConfi
}
return nil
})
+ eg.Go(func() (err error) {
+ codernautsEnabled, err = f.database.GetCodernautsEnabled(ctx)
+ if err != nil && !errors.Is(err, sql.ErrNoRows) {
+ return xerrors.Errorf("get codernauts enabled: %w", err)
+ }
+ return nil
+ })
err := eg.Wait()
if err != nil {
return codersdk.AppearanceConfig{}, err
@@ -99,6 +107,7 @@ func (f *appearanceFetcher) Fetch(ctx context.Context) (codersdk.AppearanceConfi
AnnouncementBanners: []codersdk.BannerConfig{},
SupportLinks: codersdk.DefaultSupportLinks(f.docsURL),
DocsURL: f.docsURL,
+ CodernautsEnabled: codernautsEnabled,
}
if announcementBannersJSON != "" {
@@ -206,5 +215,14 @@ func (api *API) putAppearance(rw http.ResponseWriter, r *http.Request) {
return
}
+ err = api.Database.UpsertCodernautsEnabled(ctx, appearance.CodernautsEnabled)
+ if err != nil {
+ httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
+ Message: "Unable to set Codernauts enabled",
+ Detail: err.Error(),
+ })
+ return
+ }
+
httpapi.Write(r.Context(), rw, http.StatusOK, appearance)
}
diff --git a/enterprise/coderd/appearance_test.go b/enterprise/coderd/appearance_test.go
index 8255dd4c8aa..8bfaae8b9b0 100644
--- a/enterprise/coderd/appearance_test.go
+++ b/enterprise/coderd/appearance_test.go
@@ -55,6 +55,52 @@ func TestCustomLogoAndCompanyName(t *testing.T) {
require.Equal(t, uac.LogoURL, got.LogoURL)
}
+func TestCodernautsEnabled(t *testing.T) {
+ t.Parallel()
+
+ adminClient, adminUser := coderdenttest.New(t, &coderdenttest.Options{DontAddLicense: true})
+ basicUserClient, _ := coderdtest.CreateAnotherUser(t, adminClient, adminUser.OrganizationID)
+
+ ctx := testutil.Context(t, testutil.WaitLong)
+
+ // With no stored setting, as after an upgrade, the game defaults to
+ // enabled. This deployment has no license, so the default appearance
+ // fetcher serves the value.
+ got, err := basicUserClient.Appearance(ctx)
+ require.NoError(t, err)
+ require.True(t, got.CodernautsEnabled)
+
+ // The setting can be disabled without any license entitlement.
+ err = adminClient.UpdateAppearance(ctx, codersdk.UpdateAppearanceConfig{
+ CodernautsEnabled: false,
+ })
+ require.NoError(t, err)
+
+ got, err = basicUserClient.Appearance(ctx)
+ require.NoError(t, err)
+ require.False(t, got.CodernautsEnabled)
+
+ // The stored value survives switching to the licensed fetcher.
+ coderdenttest.AddLicense(t, adminClient, coderdenttest.LicenseOptions{
+ Features: license.Features{
+ codersdk.FeatureAppearance: 1,
+ },
+ })
+
+ got, err = basicUserClient.Appearance(ctx)
+ require.NoError(t, err)
+ require.False(t, got.CodernautsEnabled)
+
+ err = adminClient.UpdateAppearance(ctx, codersdk.UpdateAppearanceConfig{
+ CodernautsEnabled: true,
+ })
+ require.NoError(t, err)
+
+ got, err = basicUserClient.Appearance(ctx)
+ require.NoError(t, err)
+ require.True(t, got.CodernautsEnabled)
+}
+
func TestAnnouncementBanners(t *testing.T) {
t.Parallel()
diff --git a/enterprise/coderd/coderd.go b/enterprise/coderd/coderd.go
index fcf9b8f09d7..ed40957d39f 100644
--- a/enterprise/coderd/coderd.go
+++ b/enterprise/coderd/coderd.go
@@ -1162,7 +1162,7 @@ func (api *API) updateEntitlements(ctx context.Context) error {
)
api.AGPL.AppearanceFetcher.Store(&f)
} else {
- f := appearance.NewDefaultFetcher(api.DeploymentValues.DocsURL.String())
+ f := appearance.NewDefaultFetcher(api.Database, api.DeploymentValues.DocsURL.String())
api.AGPL.AppearanceFetcher.Store(&f)
}
}
diff --git a/enterprise/coderd/provisionerdaemons.go b/enterprise/coderd/provisionerdaemons.go
index b6d0658433e..3b879bd3995 100644
--- a/enterprise/coderd/provisionerdaemons.go
+++ b/enterprise/coderd/provisionerdaemons.go
@@ -378,7 +378,7 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request)
_ = conn.Close(websocket.StatusInternalError, httpapi.WebsocketCloseSprintf("drpc register provisioner daemon: %s", err))
return
}
- server := drpcserver.NewWithOptions(mux, drpcserver.Options{
+ server := drpcsdk.NewServer(logger, mux, drpcserver.Options{
Manager: drpcsdk.DefaultDRPCOptions(nil),
Log: func(err error) {
if xerrors.Is(err, io.EOF) {
diff --git a/provisionersdk/serve.go b/provisionersdk/serve.go
index 4afcee96269..013626b22a4 100644
--- a/provisionersdk/serve.go
+++ b/provisionersdk/serve.go
@@ -92,7 +92,7 @@ func Serve(ctx context.Context, server Server, options *ServeOptions) error {
if err != nil {
return xerrors.Errorf("register provisioner: %w", err)
}
- srv := drpcserver.NewWithOptions(&tracing.DRPCHandler{Handler: mux}, drpcserver.Options{
+ srv := drpcsdk.NewServer(options.Logger, &tracing.DRPCHandler{Handler: mux}, drpcserver.Options{
Manager: drpcsdk.DefaultDRPCOptions(nil),
})
diff --git a/site/site.go b/site/site.go
index d607c02ce7a..a24954a7f39 100644
--- a/site/site.go
+++ b/site/site.go
@@ -89,7 +89,7 @@ type Options struct {
func New(opts *Options) (*Handler, error) {
if opts.AppearanceFetcher == nil {
daf := atomic.Pointer[appearance.Fetcher]{}
- f := appearance.NewDefaultFetcher(opts.DocsURL)
+ f := appearance.NewDefaultFetcher(opts.Database, opts.DocsURL)
daf.Store(&f)
opts.AppearanceFetcher = &daf
}
diff --git a/site/src/api/api.ts b/site/src/api/api.ts
index 2c98a77dcdb..7d6eba4b8cd 100644
--- a/site/src/api/api.ts
+++ b/site/src/api/api.ts
@@ -2532,6 +2532,7 @@ class ApiMethods {
docs_url: "",
logo_url: "",
announcement_banners: [],
+ codernauts_enabled: true,
service_banner: {
enabled: false,
},
diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts
index 5239f3fe02e..c12dc3b4bfe 100644
--- a/site/src/api/typesGenerated.ts
+++ b/site/src/api/typesGenerated.ts
@@ -1590,6 +1590,7 @@ export interface AppearanceConfig {
readonly service_banner: BannerConfig;
readonly announcement_banners: readonly BannerConfig[];
readonly support_links?: readonly LinkConfig[];
+ readonly codernauts_enabled: boolean;
}
// From codersdk/templates.go
@@ -9740,6 +9741,7 @@ export interface UpdateAppearanceConfig {
*/
readonly service_banner: BannerConfig;
readonly announcement_banners: readonly BannerConfig[];
+ readonly codernauts_enabled: boolean;
}
// From codersdk/chats.go
diff --git a/site/src/components/SyntaxHighlighter/SyntaxHighlighter.stories.tsx b/site/src/components/SyntaxHighlighter/SyntaxHighlighter.stories.tsx
new file mode 100644
index 00000000000..8740b3af492
--- /dev/null
+++ b/site/src/components/SyntaxHighlighter/SyntaxHighlighter.stories.tsx
@@ -0,0 +1,111 @@
+import type { Meta, StoryObj } from "@storybook/react-vite";
+import type * as Monaco from "monaco-editor";
+import * as monaco from "monaco-editor";
+import { useState } from "react";
+import { expect, userEvent, waitFor } from "storybook/test";
+import { withDashboardProvider } from "#/testHelpers/storybook";
+import { SyntaxHighlighter } from "./SyntaxHighlighter";
+
+const original = `resource "coder_agent" "main" {
+ os = "linux"
+ arch = "amd64"
+}
+`;
+
+const modified = `resource "coder_agent" "main" {
+ os = "linux"
+ arch = "arm64"
+}
+`;
+
+// The diff editor's gutter menu and occurrence highlighter register delayed
+// disposables whose teardown throws when editors unmount in Storybook tests.
+// They are irrelevant to model disposal, so we turn them off in stories to keep
+// the test runner clean without changing production behavior.
+const stableTeardownOptions: Monaco.editor.IStandaloneDiffEditorConstructionOptions =
+ {
+ minimap: { enabled: false },
+ renderSideBySide: true,
+ readOnly: true,
+ renderGutterMenu: false,
+ occurrencesHighlight: "off",
+ };
+
+const meta: Meta