provision_blob_storage.py¶
Purpose¶
Provisions the Azure resources behind the cloud-hosted BlobBackend used for EntraBot's operational memory — the interaction log, daily summaries, promises, and chat cursors, each of which is routed through MemoryBackend (get_backend()). It is an Azure resource-management script, not a data-migration tool: it creates infrastructure (resource group, storage account, container, RBAC) and never reads, writes, or copies the JSON/JSONL content that lives inside ~/.entrabot/data/ or an existing container.
The watched-chats registration (data_dir/watched_chats) and the email poll cursor (data_dir/email_cursor.txt) are always plain local files under cfg.data_dir and are never routed through get_backend(), so enabling BlobBackend does not move their ongoing reads/writes into Blob — see Storage and Memory.
Moving existing local data into the container this script creates is a separate step, handled by setup.sh's post-provision migration prompt or by calling entrabot.storage.migration.migrate_local_to_backend directly. That migration walks the whole local data directory, so it can copy watched_chats and email_cursor.txt into Blob as migration artifacts alongside the backend-routed keys — but copying them there doesn't change which component is authoritative at runtime; the MCP server still reads and writes both files locally regardless of backend configuration. This script also has no relationship to Claude Code / persona memory — that sync is owned by the persona-sati MCP server and, for one-off manual use, claude_memory_sync.py.
Requirements¶
- The Azure CLI (
az) installed and onPATH. - An active
az loginsession (user or service principal) with rights in the target subscription to create resource groups and storage accounts and to write role assignments (Microsoft.Authorization/roleAssignments/write— typicallyOwnerorUser Access Administratorat the subscription or resource group scope). Signing in withaz loginusing a plain Azure CLI token is fine here — this script only calls Azure Resource Manager, not the Agent Identity Graph APIs that rejectaztokens. - The Entra tenant ID and the Agent User's object ID. Both are available from
.entrabot-state.jsonafter provisioning, or viashow_agent_status.py --json. - No EntraBot Python environment or package install is required — the script only uses the standard library and shells out to
az.
Usage¶
# Deterministic per-tenant account, container named for the Agent User
python3 scripts/provision_blob_storage.py \
--tenant-id 11111111-2222-3333-4444-555555555555 \
--agent-user-object-id 66666666-7777-8888-9999-000000000000
# Attach to (or create) a specific, named storage account
python3 scripts/provision_blob_storage.py \
--tenant-id 11111111-2222-3333-4444-555555555555 \
--agent-user-object-id 66666666-7777-8888-9999-000000000000 \
--with-storage-account entclawteam01
# Force a brand-new, randomly-suffixed account even if the
# deterministic-name account already exists
python3 scripts/provision_blob_storage.py \
--tenant-id 11111111-2222-3333-4444-555555555555 \
--agent-user-object-id 66666666-7777-8888-9999-000000000000 \
--create-new-storage
# Custom container name and region
python3 scripts/provision_blob_storage.py \
--tenant-id 11111111-2222-3333-4444-555555555555 \
--agent-user-object-id 66666666-7777-8888-9999-000000000000 \
--with-container team-shared-agent \
--location westus2
| Option | Default | Notes |
|---|---|---|
--tenant-id |
(required) | Entra tenant GUID. |
--agent-user-object-id |
(required) | Agent User object GUID; also used to derive the default container name and as the RBAC assignee. |
--location |
eastus |
Azure region used only when the resource group or storage account is created for the first time; ignored for resources that already exist. |
--with-storage-account NAME |
unset | Use (creating if needed) the named account instead of the deterministic per-tenant name. Mutually exclusive with --create-new-storage. |
--with-container NAME |
unset | Use (creating if needed) the named container instead of agent-<agent-user-object-id>. |
--create-new-storage |
false |
Forces a fresh, randomly-suffixed account even when the deterministic-name one already exists. Mutually exclusive with --with-storage-account. |
In normal operation you won't invoke this directly — ./scripts/setup.sh --use-cloud-memory calls it for you and then wires the printed values into .env. Run it by hand when provisioning storage ahead of setup, attaching a second device to an existing account, or scripting infrastructure outside of setup.sh.
Effects¶
- Resource group
entrabot-rgis created in--locationif it doesn't already exist (az group showshort-circuitsaz group create). - Storage account name resolution, in priority order:
--with-storage-account(validated: 3–24 lowercase letters/digits) → a random per-tenant name if--create-new-storage(entclaw<8-hex tenant hash><8-hex random>) → the deterministic per-tenant defaultentclaw<16-hex sha256(tenant_id)>, so every developer in the same tenant converges on the same account without a global-uniqueness race. Created withStandard_LRS, kindStorageV2, public blob access disabled, and a minimum TLS version of 1.2. - Container name resolution:
--with-container(validated: 3–63 lowercase alphanumeric/dash, no leading/trailing/consecutive dashes) or the defaultagent-<agent-user-object-id, lowercased>. - RBAC:
Storage Blob Data Contributoris assigned to the Agent User's object ID, scoped to the container, not the account or resource group — so multiple Agent Users sharing a per-tenant account cannot read each other's data.
Every step checks for an existing resource before creating one, so re-running with the same arguments is safe and only fills in whatever is missing. A role assignment that already exists is treated as success rather than an error.
On success, exactly two KEY=value lines are printed to stdout for shell capture:
BLOB_ENDPOINT=https://entclaw<hash>.blob.core.windows.net
BLOB_CONTAINER=agent-<agent-user-object-id>
All progress messages and a monthly cost estimate go to stderr, keeping stdout script-friendly.
This script does not write .env, does not grant the Agent User's user_impersonation consent on Azure Storage (that's grant_agent_user_storage_consent, called from create_entra_agent_ids.py during setup), and does not copy any existing local or blob data.
Exit behavior¶
- Exit 0 — provisioning completed;
BLOB_ENDPOINTandBLOB_CONTAINERare printed to stdout. - Exit 1 — an
azcommand failed (surfaced asERROR: az ... failed: <az stderr>) or a name/argument was invalid (e.g. an out-of-spec--with-storage-account/--with-containername). - Exit 2 — argparse usage error: a required argument (
--tenant-id,--agent-user-object-id) is missing, or--with-storage-accountand--create-new-storagewere both passed on the command line.
Common failure causes and recovery:
- Not signed in —
azcalls fail with an authentication error. Runaz login(andaz account set --subscription <id>if you have more than one) and re-run; the script is idempotent and resumes from whatever hasn't been created yet. - Insufficient RBAC to assign roles — the account/container are created but
az role assignment createfails. You (or whoever runs this) needsMicrosoft.Authorization/roleAssignments/writeat the resource group or subscription scope; ask a subscription owner to grant it, then re-run. --with-storage-accountname already taken by another subscription — storage account names are globally unique; pick a different name or omit the flag to use the deterministic per-tenant default.- RBAC propagation delay — a freshly assigned role can take a few minutes to become effective for Graph/data-plane calls; this script itself will report success once the assignment call succeeds, but callers reading/writing blobs immediately afterward may need to retry.
Related commands¶
deprovision_blob_storage.py— inverse: removes the resources this script creates.setup.sh— the usual caller, via--use-cloud-memory; also prompts to migrate existing local data into the new container afterward.create_entra_agent_ids.py— grants the Agent User'suser_impersonationconsent on Azure Storage that the RBAC assignment here depends on at request time.
See also Storage scripts, the Storage Configuration guide (including its Troubleshooting section), and the Storage and Memory architecture page.