docs: add AGENTS.md contributor guide for AI agents#369
Merged
Conversation
9ea3d61 to
e326946
Compare
Member
Author
This was referenced Jun 24, 2026
f77324f to
2ec71e9
Compare
d51dad0 to
b1d81a3
Compare
2ec71e9 to
744b745
Compare
b1d81a3 to
6221b88
Compare
d7a0e3f to
3db900a
Compare
3db900a to
392562f
Compare
839989b to
0d0e3b8
Compare
faaed66 to
ce7bd51
Compare
de42560 to
2e2abe6
Compare
ce7bd51 to
ac6b0b1
Compare
2e2abe6 to
18615e5
Compare
ac6b0b1 to
44cd01d
Compare
johnstcn
approved these changes
Jun 26, 2026
13feeb1 to
0c4401c
Compare
Member
Author
Merge activity
|
0c4401c to
afe2f55
Compare
ethanndickson
added a commit
that referenced
this pull request
Jul 1, 2026
> **Stack** — builds on #369 (AGENTS.md contributor guide) and the `coderd_ai_provider` resource in #368; review/merge those first. Relates to CODAGT-607 Relates to CODAGT-736 ## Summary Adds `coderd_agents_model` for managing Coder Agents admin-managed chat model configurations from Terraform — binding a model identifier to a configured AI provider — with full CRUD + import, generated docs/examples, acceptance/unit tests and an integration test with the provider resource. The resource is marked experimental via a warning callout in its docs. ## Approach - **`model_config` as normalized JSON.** The open-ended per-call tuning blob (token limits, temperature, cost/pricing, provider options) is a single JSON string backed by a custom type that compares by JSON semantic equality, so whitespace, key ordering, and equivalent number spellings (a `3.00` cost returned as `3`) don't produce perpetual diffs. An empty `jsonencode({})` is rejected at plan time because Coder discards it. - **Provider-specific controls live under `provider_options`.** Common per-call knobs — reasoning effort, reasoning summary, parallel tool calls, and web search — are nested per provider rather than top-level, since each provider names them differently. Anthropic uses `effort` (and/or `thinking.budget_tokens`), `send_reasoning`, `web_search_enabled`, and `disable_parallel_tool_use`; OpenAI uses `reasoning_effort`, `reasoning_summary`, `parallel_tool_calls`, and `web_search_enabled`. The examples show both. - **`provider_type` derived from `ai_provider_id`.** A model takes its provider via a required `ai_provider_id`, and Coder derives the runtime `provider_type` from it. A plan modifier keeps the prior value while `ai_provider_id` is unchanged and lets it recompute when the binding changes, so it never shows stale plan noise or trips the post-apply consistency check. - **Default election stays server-side.** Coder elects exactly one default model itself and mutates the flag out-of-band, which a per-model computed boolean can't represent without perpetual plan noise or post-apply inconsistency — so `is_default` is intentionally omitted from this resource, with a follow-up adding a dedicated `coderd_default_agents_model` pointer resource (see *Why `is_default` is omitted* below). ### Why `model_config` is JSON, not typed attributes `provider_options` is a tagged union (exactly one of `openai`/`anthropic`/`google`/… with non-obvious aliases like Bedrock→`anthropic`), and the config carries `decimal` pricing and free-form `map[string]any` fields — none of which Terraform's type system can express without re-implementing the server's own validation. A single normalized JSON string sidesteps all of that and lets new upstream tuning fields land with a `codersdk` bump instead of schema churn. This mirrors the closest prior art: AWS's `bedrockagent` `additional_model_request_fields`, which models per-provider model overrides the same way. ### Why `is_default` is omitted `is_default` is a server-enforced singleton: Coder guarantees exactly one default chat model via a partial-unique index and mutates the flag out-of-band — it demotes the previous default when another model is set default, and auto-promotes a replacement when the current default is deleted. A per-model `Optional + Computed` boolean can't represent that safely. It plans as `(known after apply)` on every non-default model (perpetual plan noise), and pinning the planned value — via `UseStateForUnknown` or a plan-time read of the server — reintroduces "Provider produced inconsistent result after apply": a sibling set to default in the same apply demotes this model *after* the plan is frozen, so any known planned value is wrong. The unknown is load-bearing — it's the only value that stays consistent regardless of how the server resolves it. Rather than ship a flag that's either noisy or flaky, this PR omits `is_default` entirely. Models still apply normally, and the server continues to elect the default as it always has. A follow-up PR will add a dedicated `coderd_default_agents_model` pointer resource: ```hcl resource "coderd_default_agents_model" "this" { model_id = coderd_agents_model.sonnet.id } ``` This is the established pattern for a server-enforced "exactly one of N is the default" (cf. `aws_ec2_default_credit_specification`, `aws_ssm_default_patch_baseline`, `github_branch_default`). Create/Update issue a single-field `is_default` PATCH — the server atomically demotes the previous default — Read reflects the current server default, and Delete is a no-op that stops managing the pointer (the server always keeps some default). Moving the pointer 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. ## Schema ```hcl resource "coderd_ai_provider" "anthropic" { type = "anthropic" name = "anthropic" base_url = "https://api.anthropic.com" api_key_wo = var.anthropic_api_key api_key_wo_version = 1 } resource "coderd_agents_model" "sonnet" { ai_provider_id = coderd_ai_provider.anthropic.id model = "claude-3-5-sonnet-20241022" display_name = "Claude 3.5 Sonnet" enabled = true context_limit = 200000 model_config = jsonencode({ max_output_tokens = 8192 temperature = 0.7 cost = { input_price_per_million_tokens = "3" output_price_per_million_tokens = "15" } # Provider-specific controls. Anthropic uses an effort level (low, medium, # high, xhigh, max) and/or an extended-thinking token budget, plus common # knobs like reasoning visibility, web search, and parallel tool use. provider_options = { anthropic = { effort = "high" thinking = { budget_tokens = 4096 } send_reasoning = true web_search_enabled = true disable_parallel_tool_use = false } } }) } resource "coderd_agents_model" "gpt" { ai_provider_id = coderd_ai_provider.openai.id model = "gpt-5" display_name = "GPT-5" context_limit = 400000 model_config = jsonencode({ max_output_tokens = 8192 # OpenAI uses reasoning_effort (none, minimal, low, medium, high, xhigh), # plus common knobs like reasoning summary, parallel tool calls, and web search. provider_options = { openai = { reasoning_effort = "high" reasoning_summary = "detailed" parallel_tool_calls = true web_search_enabled = true } } }) } ``` Computed: `id`, `provider_type` (derived from `ai_provider_id`), `display_name`, `enabled`, `compression_threshold`, `created_at`, and `updated_at`. ## Notes No write-only arguments, so there's no Terraform 1.11+ requirement — the API key lives on the referenced `coderd_ai_provider`. The chat-model endpoints are experimental (`/api/experimental`); since there's no get-by-id route, Read lists model configs and filters by ID. Follow-up to #368 (`coderd_ai_provider`).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Adds an
AGENTS.mdcontributor guide at the repo root to give AI coding agents the high-signal context they need to work in this provider safely.The document captures the provider's framework idioms (terraform-plugin-framework, not SDKv2), the resource file layout, essential
makecommands, and — most importantly — the project-specific patterns and anti-patterns distilled from past fixes (e.g. handling unknown values at plan/validate time, refreshing cached entitlements after a license apply, not stripping cty sensitivity marks in plan modifiers, and avoidingdeferinside retry loops). Each anti-pattern references the PR/issue it was learned from.Contents were verified against the current tree:
Makefiletargets, theutil.gohelpers,provider.goentitlement plumbing, the CI Terraform1.0–1.9acceptance matrix, and the golangciparalleltestconfig.