diff --git a/AGENTS.md b/AGENTS.md
index e8e9fed..bc14c13 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -4,6 +4,6 @@ OpenTofu root for Make IT Work Cloud Twilio control-plane infrastructure.
This root owns only Twilio phone-number inventory and inbound messaging-webhook configuration. It must not own the OpenCode SMS bridge workload, Cloudflare workload route or DNS, number-to-agent mapping, approved-source allowlist, bridge credentials, or runtime encryption material. Those runtime concerns belong to `kustomize-cluster`.
-The root owns the SOPS-encrypted `secrets/secrets.yaml` provider credential file and no other secret material. It contains only the Twilio provider environment-variable names. The root configures the Twilio provider exclusively from those process-scoped environment variables, but contains no Twilio resources, state, or number identifiers. Use GitHub MCP and pull-request CI plans as validation authority. `main` is an environment-gated apply path; use scoped branches and pull requests, never direct pushes. Do not run OpenTofu, SOPS, state, import, or apply commands from this server.
+The root owns the SOPS-encrypted `secrets/secrets.yaml` provider credential file and no other secret material. It contains only the Twilio provider environment-variable names. The root configures the Twilio provider exclusively from those process-scoped environment variables and passes the account SID to declared resources only as `TF_VAR_account_sid` inside the same SOPS `exec-env` child. `numbers.tf` declares the four pilot phone-number resources; purchased number values live only in remote state and outputs. Use GitHub MCP and pull-request CI plans as validation authority. `main` is an environment-gated apply path; use scoped branches and pull requests, never direct pushes. Do not run OpenTofu, SOPS, state, import, or apply commands from this server.
The shared workflow is owned by `shared-workflows`; the runner image and canonical pre-commit configuration are owned by `images/tfroot-runner`. Keep any future SOPS data encrypted and never expose credentials, decrypted values, state, private keys, or sensitive plans.
diff --git a/Makefile b/Makefile
index e5cce01..c5594f8 100644
--- a/Makefile
+++ b/Makefile
@@ -21,18 +21,19 @@ init: clean
# Verify ciphertext and required keys without printing values. plan and apply run
# OpenTofu inside a separate SOPS exec-env child so the provider can authenticate.
+# The account SID reaches OpenTofu only as TF_VAR_account_sid inside that child.
credentials-check:
@test -f "${TWILIO_CREDENTIALS}"
@sops filestatus "${TWILIO_CREDENTIALS}" | jq -e '.encrypted == true' >/dev/null
@sops exec-env "${TWILIO_CREDENTIALS}" 'test -n "$$TWILIO_ACCOUNT_SID" && test -n "$$TWILIO_API_KEY" && test -n "$$TWILIO_API_SECRET"'
plan: init credentials-check
- @sops exec-env "${TWILIO_CREDENTIALS}" '${TERRAFORM} plan -refresh=false -input=false -compact-warnings'
+ @sops exec-env "${TWILIO_CREDENTIALS}" 'TF_VAR_account_sid="$$TWILIO_ACCOUNT_SID" ${TERRAFORM} plan -refresh=false -input=false -compact-warnings'
-# This root has no Twilio resources. Provider authentication may perform its
-# own read-only validation, but apply has no Twilio resource action to perform.
+# The four twilio_phone_number.agent resources purchase pilot numbers on apply.
+# No inbound webhook field is set until the bridge is deployed and healthy.
apply: init credentials-check
- @sops exec-env "${TWILIO_CREDENTIALS}" '${TERRAFORM} apply -auto-approve -refresh=false -input=false -compact-warnings'
+ @sops exec-env "${TWILIO_CREDENTIALS}" 'TF_VAR_account_sid="$$TWILIO_ACCOUNT_SID" ${TERRAFORM} apply -auto-approve -refresh=false -input=false -compact-warnings'
test: pre-commit-config pre-commit-install-hooks
@pre-commit run -a
diff --git a/README.md b/README.md
index bf81611..8f060fc 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,9 @@
## Providers
-No providers.
+| Name | Version |
+| ---- | ------- |
+| [twilio](#provider\_twilio) | 0.27.1 |
## Modules
@@ -16,13 +18,19 @@ No modules.
## Resources
-No resources.
+| Name | Type |
+| ---- | ---- |
+| [twilio_phone_number.agent](https://registry.terraform.io/providers/RJPearson94/twilio/0.27.1/docs/resources/phone_number) | resource |
## Inputs
-No inputs.
+| Name | Description | Type | Default | Required |
+| ---- | ----------- | ---- | ------- | :------: |
+| [account\_sid](#input\_account\_sid) | Twilio account SID, supplied only from SOPS exec-env through TF\_VAR\_account\_sid. | `string` | n/a | yes |
## Outputs
-No outputs.
+| Name | Description |
+| ---- | ----------- |
+| [agent\_phone\_numbers](#output\_agent\_phone\_numbers) | Purchased pilot number for each primary agent. |
diff --git a/docs/bootstrap.md b/docs/bootstrap.md
index fe08a96..3982513 100644
--- a/docs/bootstrap.md
+++ b/docs/bootstrap.md
@@ -2,13 +2,15 @@
## Scope
-This root validates the OpenTofu toolchain, remote state backend, encrypted provider-credential delivery, and Twilio provider authentication only. It creates, imports, updates, or deletes **no** Twilio resources. In particular, it does not purchase or configure phone numbers, webhooks, messaging services, or runtime routing.
+This root validates the OpenTofu toolchain, remote state backend, encrypted provider-credential delivery, and Twilio provider authentication. It now also owns the pilot phone-number inventory: exactly one purchased US local SMS/MMS-capable number per primary agent (`lawnmowerman`, `grillmaster`, `homesteader`, `homerepair`), declared in `numbers.tf`.
-The evaluated provider is `RJPearson94/twilio` `0.27.1`. It is community-maintained and requires existing account credentials. The provider is intentionally configured with no static attributes: OpenTofu receives its credentials only in a SOPS `exec-env` child. Its authentication validation may perform a read-only Twilio API request; no resource is declared or changed by this root.
+It configures **no** inbound messaging webhook. The `messaging` block on each number is added only in a separately reviewed change after the `kustomize-cluster` bridge is reconciled, healthy, and explicitly approved for functional testing. The root does not own the bridge workload, Cloudflare workload route or DNS, number-to-agent mapping, approved-source allowlist, bridge credentials, or runtime encryption material; those runtime concerns belong to `kustomize-cluster`.
+
+The evaluated provider is `RJPearson94/twilio` `0.27.1`. The provider is intentionally configured with no static attributes: OpenTofu receives its credentials only in a SOPS `exec-env` child, and the resource `account_sid` reaches OpenTofu only as `TF_VAR_account_sid` inside that same child process. The purchased number values live only in remote state and the `agent_phone_numbers` output.
## Intended message flow
-The owner’s intended outcome is one Twilio phone number for each primary agent: `lawnmowerman`, `grillmaster`, `homesteader`, and `homerepair`. Each number will initially accept inbound SMS/MMS only from the one owner-approved source phone number, then route the request to its corresponding agent for a timely reply using that agent’s existing context, knowledge base, and MCP integrations. The sender allowlist is intentionally extensible: adding a further approved source number after the pilot is a separate runtime-configuration change, not a Twilio-root redesign.
+The owner's intended outcome is one Twilio phone number for each primary agent. Each number will initially accept inbound SMS/MMS only from the one owner-approved source phone number, then route the request to its corresponding agent for a timely reply using that agent's existing context, knowledge base, and MCP integrations. The sender allowlist is intentionally extensible: adding a further approved source number after the pilot is a separate runtime-configuration change, not a Twilio-root redesign.
`kustomize-cluster` exclusively owns the bridge workload, number-to-agent map, sender allowlist and its later expansion, runtime secrets, `TunnelBinding`/DNS, and public route. This root may later own only the Twilio number inventory and the inbound messaging-webhook fields pointing at the already-healthy bridge. It must not duplicate any bridge/runtime configuration.
@@ -18,8 +20,8 @@ The owner’s intended outcome is one Twilio phone number for each primary agent
The checked-in `.sops.yaml` identifies the approved encryption recipient. `secrets/secrets.yaml` is the only accepted provider credential file. It holds the encrypted `TWILIO_ACCOUNT_SID`, `TWILIO_API_KEY`, and `TWILIO_API_SECRET` values; it must never contain bridge runtime inputs. `make plan` and `make apply` first require SOPS to recognize the file as encrypted, then run OpenTofu inside `sops exec-env`. They never write plaintext files, set static provider attributes, or use GitHub Actions secrets.
-This provider-only change must pass pull-request validation before later Twilio inventory or webhook work is proposed. Any actual Twilio provisioning or webhook update still requires explicit owner confirmation before merge because `main` invokes the environment-gated apply path.
+Any change that purchases numbers or updates a webhook requires explicit owner confirmation before merge because `main` invokes the environment-gated apply path.
## Central generated files
-`.github/dependabot.yml` and `.github/workflows/dependabot-notify.yml` are seeded here with the exact content generated by `tfroot-github`. They are not locally owned. This PR-based seed resolves the branch-protection bootstrap conflict; after merge, the central `github_repository_file` resources can adopt and continue managing them with `overwrite_on_create = true`.
+`.github/dependabot.yml` and `.github/workflows/dependabot-notify.yml` are centrally generated by `tfroot-github`. They are not locally owned; do not hand-edit them.
diff --git a/numbers.tf b/numbers.tf
new file mode 100644
index 0000000..ac3117c
--- /dev/null
+++ b/numbers.tf
@@ -0,0 +1,39 @@
+# One pilot number per primary agent. Purchased US local numbers with SMS
+# and MMS capability; no inbound webhook is configured here. The messaging
+# webhook is added only after the kustomize-cluster bridge is healthy.
+
+variable "account_sid" {
+ type = string
+ description = "Twilio account SID, supplied only from SOPS exec-env through TF_VAR_account_sid."
+}
+
+locals {
+ agent_numbers = {
+ lawnmowerman = "opencode-sms lawnmowerman"
+ grillmaster = "opencode-sms grillmaster"
+ homesteader = "opencode-sms homesteader"
+ homerepair = "opencode-sms homerepair"
+ }
+}
+
+resource "twilio_phone_number" "agent" {
+ for_each = local.agent_numbers
+
+ account_sid = var.account_sid
+ friendly_name = each.value
+
+ search_criteria {
+ iso_country = "US"
+ type = "local"
+
+ capabilities {
+ sms_enabled = true
+ mms_enabled = true
+ }
+ }
+}
+
+output "agent_phone_numbers" {
+ description = "Purchased pilot number for each primary agent."
+ value = { for name, number in twilio_phone_number.agent : name => number.phone_number }
+}