Skip to content

feat: add experimental coderd_default_agents_model resource#378

Merged
ethanndickson merged 4 commits into
mainfrom
ethan/default-agents-model-resource
Jul 1, 2026
Merged

feat: add experimental coderd_default_agents_model resource#378
ethanndickson merged 4 commits into
mainfrom
ethan/default-agents-model-resource

Conversation

@ethanndickson

@ethanndickson ethanndickson commented Jun 30, 2026

Copy link
Copy Markdown
Member

TL;DR — Coder enforces exactly one default Agents chat model and flips that flag server-side, so a per-model is_default boolean can't be represented in Terraform without perpetual plan noise or "inconsistent result after apply." #371 removed is_default from coderd_agents_model; this PR adds the replacement — a small coderd_default_agents_model pointer resource that names which model is the default. This is the standard Terraform pattern for a server-enforced "exactly one of N" default.

Stack — builds on #371 (coderd_agents_model), which removed is_default from the member resource in anticipation of this one. Review/merge the downstack PRs first.

Relates to CODAGT-607

Summary

Adds coderd_default_agents_model, a singleton pointer resource that selects which coderd_agents_model is the deployment-wide default chat model for Coder Agents:

resource "coderd_agents_model" "sonnet" {
  ai_provider_id = coderd_ai_provider.anthropic.id
  model          = "claude-3-5-sonnet-20241022"
  context_limit  = 200000
}

resource "coderd_default_agents_model" "this" {
  model_id = coderd_agents_model.sonnet.id
}

This completes the redesign started in #371. is_default was removed from coderd_agents_model there because Coder enforces exactly one default via a partial-unique index and mutates the flag out-of-band; a per-member Optional + Computed boolean either plans as perpetual (known after apply) noise or, if pinned, trips "Provider produced inconsistent result after apply" when a sibling is set default in the same apply and demotes this model after the plan is frozen. Moving the choice to its own resource removes both failure modes by construction: there's no per-member computed flag to go unknown, and nothing for a sibling to mutate underneath it.

Why a separate pointer resource

This is the established Terraform pattern for "exactly one of N is the server-enforced default" — model the pointer as its own resource, never a per-member flag. Three mainstream providers do exactly this:

Provider Resource Stack Delete semantics
AWS aws_ec2_default_credit_specification terraform-plugin-framework (our stack) No-op — can't "unset", only repoint
AWS aws_ssm_default_patch_baseline SDKv2 Restores the AWS-owned built-in default
GitHub github_branch_default SDKv2 No-op, drops from state

The closest analog is aws_ec2_default_credit_specification: same framework stack, and almost exactly our shape — a chosen value plus a scope, Create/Update both call the same "set default" API, and Delete is a deliberate no-op because AWS always keeps some default per scope and you can't reach "zero defaults." That's precisely Coder's constraint (the partial-unique index plus ensureDefaultChatModelConfig force-promotion). HashiCorp's resource package docs also explicitly sanction the create-only / no-meaningful-delete shape for one-time operations that need tracking, so a singleton pointer is a documented, intended use.

The counter-precedent is instructive: aws_launch_template.default_version keeps an Optional + Computed flag on the member and tolerates the churn — but only because launch-template versions are not a mutually-exclusive singleton across siblings, so it never hits the "sibling demoted out-of-band during the same apply" inconsistency. That structural difference is exactly why they can keep the per-member flag and we can't.

Behavior

  • Create / Update issue a single-field PATCH: UpdateChatModelConfig(model_id, { is_default: true }). The server's update handler is a read-modify-write merge that only overrides fields the request supplies, so a body of just {"is_default": true} preserves everything else, and the demote-of-previous + promote-of-new happens atomically in one transaction. This is an in-place update (like aws_ec2_default_credit_specification), not a replace.
  • Read lists model configs and reflects whichever one the server currently reports as default into model_id; if no models exist it removes the resource from state.
  • Delete is a no-op that just drops the resource from state. Coder always keeps exactly one model marked default and force-promotes a replacement when the current default is removed, so there's nothing to clear server-side — destroying the resource only stops Terraform from managing which model is the default. This is documented on the resource.
  • Import takes the coderd_agents_model UUID and passes it through to model_id; Read then reconciles to the server's actual default, so a stale ID self-corrects on the next plan (mirrors aws_ebs_default_kms_key, which imports by the value it points at).

id is a computed constant (default) since the default is a global singleton with no scope key.

Notes

The chat-model endpoints are experimental (/api/experimental), so the resource is marked experimental via a warning callout in its docs, matching coderd_agents_model. Ships with generated docs/examples, unit + acceptance tests, and an integration-test resource that points the default at a non-first model to prove it overrides the server's automatic election end-to-end.

@linear-code

linear-code Bot commented Jun 30, 2026

Copy link
Copy Markdown

CODAGT-607

@ethanndickson ethanndickson marked this pull request as ready for review June 30, 2026 06:33
@ethanndickson

Copy link
Copy Markdown
Member Author

@codex review

@ethanndickson

Copy link
Copy Markdown
Member Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Jun 30, 2026

Copy link
Copy Markdown

Chat: Review posted | View chat
Requested: 2026-06-30 13:42 UTC by @ethanndickson
Spend: $46.24 / $100.00

Review history
  • R1 (2026-06-30): 15 reviewers, 3 Nit, 3 P3, 1 P4, COMMENT. Review
  • R2 (2026-06-30): 7 reviewers, 3 Nit, 4 P3, 1 P4, COMMENT. Review
  • R3 (2026-06-30): 3 reviewers, 3 Nit, 4 P3, 1 P4, APPROVE. Review

deep-review v0.9.0 | Round 3 | e61034a..57df609

Last posted: Round 3, 8 findings (4 P3, 1 P4, 3 Nit), APPROVE. Review

Finding inventory

Finding inventory

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 Nit Author fixed (50b203a) default_agents_model_resource_test.go:24 Redundant assertion duplicates line 23 R1 Netero Yes
CRF-2 P3 Author fixed (50b203a) default_agents_model_resource_test.go:27 No deferral test for unknown model_id (project convention from #305) R1 Meruem P3, Bisky P4, Chopper Note, Ryosuke Note Yes
CRF-3 P3 Author fixed (50b203a) default_agents_model_resource.go:142 Read warning lacks len(configs); operators can't distinguish zero models from invariant violation R1 Chopper P3 Yes
CRF-4 P3 Author fixed (50b203a) default_agents_model_resource.go:172 Import comment says "to mark as default" but import doesn't mutate server state R1 Ryosuke P3, Leorio Nit, Gon P2 Yes
CRF-5 Nit Author fixed (50b203a) default_agents_model_resource.go:75 UseStateForUnknown dead code alongside Default: stringdefault.StaticString() R1 Zoro Nit, Meruem Note, Knov observation Yes
CRF-6 P4 Dropped by orchestrator (style preference; other reviewers found docs clear and proportional) default_agents_model_resource.go:163 Comment verbosity pattern (16/22 comments restate code) R1 Gon P2 No
CRF-7 Nit Author fixed (50b203a) default_agents_model_resource.go:56 Experimental warning contradicts schema docs (class issue, same in sibling) R1 Leorio Nit Yes
CRF-8 P4 Author accepted R2 (resolved thread without fix; P4 defensive edge-case test) default_agents_model_resource.go:140 No test for Read "no models exist" path (warning + RemoveResource) R1 Bisky P4 Yes
CRF-9 P3 Author fixed (57df609) examples/resources/coderd_default_agents_model/import.sh:1 import.sh still says "to mark as the deployment-wide default" (CRF-4 sibling; propagates to generated docs) R2 Gon P3, Leorio P3 Yes

Contested and acknowledged

CRF-8 (P4, default_agents_model_resource.go:140) - No test for Read "no models exist" path

  • Finding: Reviewer proposed adding a test for the Read fallback when no models exist (warning + RemoveResource at lines 140-143). The path has zero test coverage.
  • Author accepted: Thread resolved in GitHub without verbal response or code change. The code path is defensive (requires all models deleted externally), protected by the server's partial-unique-index invariant, and the finding was P4 severity.

Round log

Round 1

Panel. 0 P0-P2, 3 P3, 1 P4, 3 Nit. 1 dropped (comment style). Reviewed against bc80b0c..ddf528f.
Panel: Bisky, Hisoka, Mafu-san, Mafuuu, Pariston, Ging-Go, Gon, Leorio, Komugi, Meruem, Chopper, Ryosuke, Robin, Zoro, Knov.
Convergent findings: CRF-2 (4 reviewers), CRF-4 (3 reviewers), CRF-5 (3 reviewers).

Round 2

Churn guard: 6 addressed, 1 accepted (CRF-8, P4, resolved thread). Overrode BLOCKED to PROCEED (explicit thread resolution on P4). Reviewed against e61034a..50b203a.
Panel: Bisky, Mafuuu, Pariston, Gon, Leorio, Meruem, Chopper.
1 new P3 (CRF-9, convergent: Gon + Leorio). CRF-4 fix was partial: Go comment corrected, import.sh missed.

Round 3

Churn guard: PROCEED (CRF-9 addressed). Reviewed against e61034a..57df609.
Netero: no findings. Panel: Mafuuu, Leorio, Kite. No findings.
All 9 findings resolved (7 fixed, 1 accepted, 1 dropped). APPROVE.

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Komugi flake/determinism
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: ddf528f998

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@coder-agents-review coder-agents-review 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.

Clean pointer-resource implementation. The design correctly solves the cardinality mismatch that #371 identified: a global singleton modeled as a per-member boolean breaks Terraform's per-resource state model when the server mutates siblings out-of-band. The pointer resource eliminates that class of failure by construction.

Strengths: server-side verification in tests (checkServerDefaultMatchesResource queries the API directly), drift detection with out-of-band SDK mutation, no-op delete with CheckDestroy proving the server's default is untouched, steady-state re-plan proving no perpetual diff, and the integration test proving the pointer overrides Coder's automatic election end-to-end.

3 P3, 1 P4, 3 Nit. No blockers.

One observation not filed as a finding: Gon audited all 22 comments and found 16 restate identifiers, signatures, or framework behavior before reaching the useful information. The comments are accurate, and other reviewers found the documentation clear, so this is noted as a style observation rather than a defect. The minimum-draft rewrites in the Gon report are available if the author wants to tighten the prose.

"Boring, correct code." (Hisoka)

🤖 This review was automatically generated with Coder Agents.

Comment thread internal/provider/default_agents_model_resource_test.go Outdated
Comment thread internal/provider/default_agents_model_resource.go Outdated
Comment thread internal/provider/default_agents_model_resource.go Outdated
Comment thread internal/provider/default_agents_model_resource.go Outdated
Comment thread internal/provider/default_agents_model_resource.go
Comment thread internal/provider/default_agents_model_resource.go
Comment thread internal/provider/default_agents_model_resource_test.go Outdated
@ethanndickson ethanndickson force-pushed the ethan/integration-testcontainers branch from bc80b0c to e61034a Compare June 30, 2026 13:03
@ethanndickson ethanndickson force-pushed the ethan/default-agents-model-resource branch from a123fc9 to 50b203a Compare June 30, 2026 13:03
@ethanndickson

Copy link
Copy Markdown
Member Author

/coder-agents-review

@coder-agents-review coder-agents-review 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.

All six R1 fixes verified: redundant assertion removed, deferral test added and passing, warning includes config count, import comment corrected, dead UseStateForUnknown removed, experimental warning aligned. Each fix addresses its root cause without introducing new issues. CRF-8 (P4, accepted) remains unchanged.

One new finding: the CRF-4 fix corrected the Go source comment but missed the sibling text in import.sh, which feeds the generated docs.

0 P0-P2, 1 P3, 0 P4, 0 Nit.

"Every test in this suite proves behavior, not coverage." (Bisky)

🤖 This review was automatically generated with Coder Agents.

Comment thread examples/resources/coderd_default_agents_model/import.sh Outdated
@ethanndickson

Copy link
Copy Markdown
Member Author

/coder-agents-review

@coder-agents-review coder-agents-review 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.

All findings resolved across 3 rounds. 9 total findings: 7 fixed by the author, 1 accepted (CRF-8 P4, defensive edge-case test coverage), 1 dropped by orchestrator (CRF-6, comment style).

The CRF-9 fix (import.sh rewrite, 57df609) completes the import-documentation alignment. Generated docs match. Netero and panel (Mafuuu, Leorio, Kite) found no new issues.

Clean pointer-resource implementation with thorough test coverage. Ship it.

🤖 This review was automatically generated with Coder Agents.

@ethanndickson ethanndickson requested a review from johnstcn June 30, 2026 14:00

@johnstcn johnstcn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice work. Just some non-blocking nits about the comments. My general assumption is that when this passes the integration tests it's good to go, although a manual smoke-test is probably not a bad idea.

Comment thread internal/provider/default_agents_model_resource_test.go Outdated
Comment thread internal/provider/default_agents_model_resource_test.go Outdated
Comment thread internal/provider/default_agents_model_resource.go Outdated

ethanndickson commented Jul 1, 2026

Copy link
Copy Markdown
Member Author

Merge activity

@ethanndickson ethanndickson force-pushed the ethan/integration-testcontainers branch from e61034a to 7c1cbed Compare July 1, 2026 06:14
@ethanndickson ethanndickson force-pushed the ethan/default-agents-model-resource branch from 0771788 to cb3a4e2 Compare July 1, 2026 06:14
@ethanndickson ethanndickson changed the base branch from ethan/integration-testcontainers to graphite-base/378 July 1, 2026 06:18
@ethanndickson ethanndickson changed the base branch from graphite-base/378 to main July 1, 2026 06:19
@ethanndickson ethanndickson force-pushed the ethan/default-agents-model-resource branch from cb3a4e2 to 16e854f Compare July 1, 2026 06:20
@ethanndickson ethanndickson merged commit c97bd6c into main Jul 1, 2026
4 checks passed
@ethanndickson ethanndickson deleted the ethan/default-agents-model-resource branch July 1, 2026 06:21
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.

2 participants