> For the complete documentation index, see [llms.txt](https://dyns.ntd.asia/power-dynamics/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://dyns.ntd.asia/power-dynamics/artificial-intelligence/study-copilot-studio/enterprise-integration-and-governance/alm-foundations-solutions-and-environment-variables.md).

# ALM Foundations: Solutions and Environment Variables

Copilot Studio Session 7.3: solutions as ALM containers, managed vs. unmanaged, environment strategy, and environment variables.

{% hint style="info" %}
**Group 7 — Enterprise Integration & Governance · Session 7.3 of 4**
{% endhint %}

7.1 and 7.2 both asked what keeps one agent safe and identifiable. This session asks a different question: what keeps the whole Northwind build — five agent connections, a REST tool, an approval flow, a return-follow-up trigger — intact and movable as it goes from a maker's dev environment to something a real retailer would run in production? The answer Microsoft ships is a container (the **solution**) and a substitution mechanism (the **environment variable**) — neither optional if the build needs to survive contact with more than one environment.

Every agent is born inside a solution whether a maker notices or not. Whether that solution can actually move between environments — and what happens to a setting like an API endpoint or a manual-authentication flag along the way — depends on choices this session makes explicit: custom vs. default solution, managed vs. unmanaged, and environment variable vs. the topic/global variables 2.2 already taught under the same word.

## What ALM is, and why "solution" is the unit of it

Application Lifecycle Management, in Microsoft's own definition, is *"a set of processes, people, and tools that govern an agent's lifecycle. It starts with the initial idea and requirements gathering and continues through development, testing, deployment, and ongoing maintenance until decommissioning."* \[1] That's the whole arc — not just "how do I publish this."

Copilot Studio doesn't leave you to invent a container for that arc. Every agent is created inside one automatically: *"A solution acts as a 'carrier' for your agents. When you create an agent, the agent is automatically added to a default solution, which you can change."* \[2] That default solution is fine for a single maker experimenting. It stops being fine the moment you need to move the agent anywhere: *"Copilot Studio agents are created in the default solution automatically. To export, import, and manage agents between environments, you need to create and use a custom solution."* \[2]

Microsoft's own ALM golden rules name the discipline directly: don't customize outside a development environment, always work in the context of solutions, use a custom publisher and prefix, and create separate solutions only when you need to deploy components independently. \[1] None of that is Copilot-Studio-specific — it's the same solution discipline Power Apps and Power Automate makers have followed for years, which is exactly why Copilot Studio's own docs keep pointing back to Power Apps solution concepts rather than reinventing them. \[2]

## Managed vs. unmanaged: which one you're allowed to touch

A solution comes in one of two states, and the difference isn't cosmetic — it decides whether you're even allowed to edit what's inside.

| Property                          | <mark style="color:$warning;">Unmanaged</mark>                                              | <mark style="color:$success;">Managed</mark>                                                                                                     |
| --------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| Where it's used                   | Development — where you're actively making changes \\\[3]                                   | Any non-development environment — test, UAT, SIT, production \\\[3]                                                                              |
| Can you edit components directly? | Yes — this is the working layer                                                             | No — "You can't edit components directly within a managed solution. To edit managed components, first add them to an unmanaged solution." \\\[3] |
| Can you export it?                | Yes — as unmanaged or as managed \\\[3]                                                     | "You can't export a managed solution." \\\[3] Start over from an unmanaged one instead.                                                          |
| What deleting it does             | Removes only the container — customizations stay, absorbed into the default solution \\\[3] | Removes the customizations and extensions along with it \\\[3]                                                                                   |

The layering behind this: work in a development environment happens in an **unmanaged layer**. Export that as managed and import it elsewhere, and it lands in a **managed layer** on top. \[3] If you need to change a managed component, Copilot Studio's own solution explorer gives you the door back into that unmanaged layer directly:

{% stepper %}
{% step %}

### Open the solution and the agent

Solution explorer → select the solution → select **Agents** → select your agent.
{% endstep %}

{% step %}

### Go to Advanced → See solution layers

The three-dot menu on the agent exposes **Advanced**, then **See solution layers**.
{% endstep %}

{% step %}

### Select the unmanaged layer and remove it

**Remove unmanaged layer** strips a stray edit back out, leaving only the managed layer underneath. \[2]
{% endstep %}
{% endstepper %}

One constraint trips up a first attempt at testing a managed export: *"You can't import a managed solution into the same environment that contains the originating unmanaged solution. To test a managed solution, you need a separate environment to import it into."* \[3] — part of why the three-environment model below needs at least one environment beyond development.

The golden rule this cashes out to: *"Export and deploy solutions as managed, unless setting up a development environment."* \[1] Managed is the deliberately locked-down default — it stops a tester or a production admin from quietly hand-editing a component the way a maker would in development, which is the whole point of separating the environments in the first place.

## Environment strategy: three environments, two types

The recommended shape is unsurprising once solutions are in place: *"A healthy ALM strategy includes at least three environments: development, test, and production. After a developer makes changes to the agent in the development environment, they promote it to the test environment. If testers uncover bugs, the developer fixes them in development and promotes the agent again. Once testing passes, the agent is deployed to production."* \[1]

What's less obvious is that "development," "test," and "production" aren't three different environment *types* in Power Platform's own system — they're two. Microsoft's own configuration guidance for this exact model says to configure production as a **production type** environment, and development and test both as **sandbox type** environments. \[1] The platform's own definitions explain why that pairing works:

| Type                                            | What it's for                                                                                                                                                                                                                                  |
| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <mark style="color:$success;">Production</mark> | "Intended to be used for permanent work in an organization... Production environments are what you should use for any environments on which you depend." \\\[4]                                                                                |
| <mark style="color:$warning;">Sandbox</mark>    | "Nonproduction environments, which offer features like copy and reset. Sandbox environments are used for development and testing, separate from production." \\\[4]                                                                            |
| <mark style="color:$danger;">Trial</mark>       | "Intended to support short-term testing needs and are automatically cleaned up... They expire after 30 days." \\\[4] Not part of the standing three-environment model — everything inside is deleted on expiry unless converted to production. |

Sandbox's "copy and reset" features are exactly what a dev or test environment benefits from and a production one shouldn't have applied to it casually — the practical reason the two non-production stages share a type while production stands alone. One more piece ties straight back to 7.1: *"Secure every environment by applying an Entra Security Group to limit access to only its members."* \[1] Three environments with the same agent inside them are still three separate access boundaries, each needing its own membership list.

## What doesn't travel with the solution

A solution is thorough, not total. Microsoft names a specific list of Copilot Studio settings that a solution export simply doesn't carry, meaning they need a manual step after every import into a new environment: Azure Application Insights settings, manual authentication settings, Direct Line and Web channel security settings, deployed channels, and sharing (with other makers or end users). \[1]

{% hint style="warning" %}
**A direct callback to 7.1** — "Manual authentication settings" on that list is the exact setting 7.1 taught as one of the three agent-level authentication options: **Authenticate manually**, the one Northwind's public support agent would realistically use. Move that agent through a solution to a new environment and the authentication configuration doesn't just follow along. The import instructions confirm this from the receiving side too: *"Configure user authentication again for the imported agent"* is a required step, not an optional cleanup one. \[5]
{% endhint %}

The export/import mechanics carry their own limitations worth knowing before a real migration, not after: you can't export topic-level or node-level comments, you can't export a solution containing an agent with a period (`.`) in any topic's name, and an agent with more than 250 topics or more than 100 entities needs a different export path than the default flow. \[5] After import, *"publish your imported agent before sharing"* is the closing step — an imported agent isn't live for end users until that publish happens in the new environment, same as any other change. \[5]

## Environment variables: separating what changes across environments

Solutions move components. Environment variables move the *settings* those components need without baking a specific value into the component itself: *"Separating the parameters from the consuming objects allows you to change the values within the same environment or when you migrate solutions to other environments."* \[6] An API endpoint, a SharePoint site URL, a connector's target — anything genuinely different in dev than in production is a candidate.

Six data types are available when you create one: **Decimal number**, **Text**, **JSON**, **Two options**, **Data source** (a connector-backed reference), and **Secret** (backed by Azure Key Vault — its own section below). \[6]

The mechanism that makes the same variable usable in three environments with three different values is a deliberate split between two properties:

* **Default value** — part of the variable's *definition*, travels with the solution, used only when no current value exists. \[6]
* **Current value** — part of the variable's separate *value* record, optional, and always wins over the default when present. \[6]

Microsoft's stated reason for keeping these apart is a useful mental model: *"an application publisher might list their offer on Marketplace with a default value. Then optionally, the customer can provide a new value. When the application publisher publishes updates to the application, the value set by the customer isn't overwritten."* \[6] Northwind's own maker plays exactly the publisher role here — shipping a solution with sensible defaults, while each environment's admin sets the current value that actually applies there.

Two naming rules matter enough to catch before a flow fails to save: variable names must be unique, and `$authentication` and `$connection` are reserved — *"Flow save is blocked if environment variables with those names are used."* \[6] And the best-practice answer to "should a value ship inside the solution?" is a flat no: *"Environment variable definitions should be included in your solution but the values should be provided for the target environment during deployment."* \[6] Two hard limits round this out: a value tops out at 2,000 characters, and while there's no cap on how many environment variables a solution can hold, the solution itself is capped at 95 MB total. \[6]

## The other kind of "variable" — a genuine same-word trap

This course has used the word "variable" since 2.2, for something that has nothing to do with any of the above. 2.2 and 2.4's topic and global variables are conversation-time values — created inline on the authoring canvas, scoped to one conversation (topic variables) or the whole agent's running session (global variables), used to carry a captured entity or a computed value from one node or topic to the next. \[8] This session's environment variables are solution-time values — stored in Dataverse as part of a solution's definition, with none of 2.2's canvas-node mechanics, existing specifically to be different per environment rather than to persist within one conversation.

{% hint style="warning" %}
**Same word, same Learn page, two unrelated mechanisms** — the trap here isn't just naming, it's that the primary source for both is literally the same Learn page. `authoring-variables` is 2.2 and 2.4's source for topic-scoped conversation variables (pass and return values between topics) — and it's *also* this session's source for secret environment variables and their Key Vault setup, in a section titled "Use environment variables for Azure Key Vault secrets." \[7] One page, one word, two mechanisms that share no data types, no scope model, and no storage layer. Nothing here is a documentation error — it's one page doing double duty for two features that happen to share a name.
{% endhint %}

## Secrets: environment variables that need Key Vault

A Secret-type environment variable references a value in Azure Key Vault rather than storing the value itself in Dataverse — a deliberate design for credentials that shouldn't sit in plain solution metadata. \[6] Copilot Studio needs three things configured before an agent can actually read one:

{% stepper %}
{% step %}

### Configure the Key Vault vault itself

Following the standard Power Apps Key Vault setup for environment variables — this part isn't Copilot-Studio-specific. \[7]
{% endstep %}

{% step %}

### Authorize Copilot Studio to read it

Assign the **Key Vault Secrets User** role to the *Microsoft Copilot Studio Service* application — without this, the platform itself has no read path to the vault regardless of what the agent asks for. \[7]
{% endstep %}

{% step %}

### Scope which agents can use the secret

Tag the secret with `AllowedEnvironments` (comma-separated environment IDs) to authorize every agent in those environments, or `AllowedAgents` (format `{envId}/{schemaName}`, comma-separated, overflowing into `AllowedAgents2` if you run out of room) to scope it down to specific agents instead. \[7]
{% endstep %}
{% endstepper %}

Two behavioral details worth knowing before relying on this in a live build: the Dialog runtime caches a successful secret read for five minutes, and caches a failed read for 30 seconds \[7] — so a Key Vault permission fix doesn't take effect on the very next turn, and a genuinely broken secret won't hammer Key Vault with retries every turn either. The documentation is direct about a risk this creates:

{% hint style="danger" %}
**The warning, verbatim** — *"By adding the secret in the agent, you might expose the value of this secret. Anyone who can edit the agent in the environment could add a Message node and return the value of the secret environment variables in a message."* \[7] A secret environment variable is safer than a hardcoded credential in a topic, but it isn't safe from anyone with maker access to the agent itself — the same least-privilege instinct 5.1's guardrail categories already named for autonomous triggers applies here to who gets edit rights on an agent holding a secret reference at all.
{% endhint %}

## Applying it to Northwind

Northwind's build so far — the eligibility REST API tool (4.3), the return-approval flow (4.2/4.5), the manually-authenticated public agent (7.1), and all five multi-agent connections (Group 6) — has lived in one working environment across every session. Moving it toward something a real retailer would actually run means applying this session's model directly:

| Northwind piece                                                        | What ALM does with it                                                                                                                                                                                                       |
| ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The agent itself, its topics, and its tools                            | Moves in a custom solution, exported unmanaged from dev, imported managed into test and then production                                                                                                                     |
| The eligibility REST API's endpoint URL (4.3)                          | Becomes a Text environment variable — dev, test, and production point at three different endpoints without touching the tool's configuration on each move                                                                   |
| The manual-authentication OAuth2 provider setting (7.1)                | Not solution-aware — reconfigured by hand in every new environment after import                                                                                                                                             |
| Meridian Freight's A2A connection and the Fabric data agent link (6.4) | Connection references travel with the solution, but the underlying credentials and endpoint access still need re-authorizing per environment — the same category of external dependency 7.1 flagged as an open DLP question |

Nothing here is a build Northwind has actually done yet — this session designs the movement, the same way 5.1 designed the return-follow-up trigger on paper before 5.2 built it. The concrete artifact worth carrying forward: at minimum one Text environment variable for the REST API endpoint, created in a custom solution with Northwind's own publisher prefix, its default value pointing at a safe non-production endpoint and its current value overridden per environment at import time.

## Moving a build across three environments

```mermaid
flowchart TD
    D["Development environment (sandbox type)"] -->|"export solution as unmanaged"| DX["Unmanaged solution (source of truth)"]
    DX -->|"export as managed"| TM["Managed solution"]
    TM -->|"import"| T["Test environment (sandbox type)"]
    T -->|"bugs found"| D
    T -->|"tests pass — import same managed solution"| P["Production environment (production type)"]
    EV["Environment variable current value"] -.->|"set fresh at each import"| T
    EV -.->|"set fresh at each import"| P
    NA["Not-solution-aware settings (manual auth, channels, sharing)"] -.->|"reconfigured by hand, every environment"| T
    NA -.->|"reconfigured by hand, every environment"| P
```

## Check your retrieval

{% tabs %}
{% tab title="Question 1 of 4" icon="circle-question" %}
An agent is created in Copilot Studio and left in its default solution. What's the practical consequence when it's time to move that agent to a test environment?
{% endtab %}

{% tab title="Answer" icon="unlock" %}
**It can't be exported/imported for ALM purposes — a custom solution has to be created and the agent added to it first.** Copilot Studio's own docs are explicit: agents are created in the default solution automatically, but to export, import, and manage agents between environments you need a custom solution.
{% endtab %}
{% endtabs %}

{% tabs %}
{% tab title="Question 2 of 4" icon="circle-question" %}
A managed solution has already been imported into a test environment. A tester wants to tweak one of its topics directly. What actually happens?
{% endtab %}

{% tab title="Answer" icon="unlock" %}
**The edit is blocked** — managed components can't be edited directly; they'd need to be added to an unmanaged solution first. This is deliberate: it's what stops ad hoc edits from happening anywhere except the development environment.
{% endtab %}
{% endtabs %}

{% tabs %}
{% tab title="Question 3 of 4" icon="circle-question" %}
Why does a healthy environment strategy configure development and test as the same environment TYPE (sandbox), even though they're two different environments in the promotion pipeline?
{% endtab %}

{% tab title="Answer" icon="unlock" %}
**Sandbox environments offer copy/reset features suited to active development and testing, while production is reserved for permanent, depended-upon work** — the type reflects purpose, not pipeline stage. Development and test share the sandbox type because they share that purpose.
{% endtab %}
{% endtabs %}

{% tabs %}
{% tab title="Question 4 of 4" icon="circle-question" %}
This session's environment variables and 2.2's topic/global variables share the word "variable." What's the actual relationship between them?
{% endtab %}

{% tab title="Answer" icon="unlock" %}
**They're unrelated mechanisms** — one is a solution-level, Dataverse-stored setting used to vary configuration across environments; the other is a conversation-time value scoped to a topic or agent session — and they happen to be documented, in part, on the very same Learn page.
{% endtab %}
{% endtabs %}

## Reflection

<details>

<summary>Golden rule #6 says to export and deploy solutions as managed "unless setting up a development environment" — even though managed solutions can't be edited directly and can't be re-exported. Why would the recommended default be the more restrictive option rather than the more flexible one?</summary>

{% hint style="info" %}
**ONE REASONABLE ANSWER** The restriction is the point, not a tradeoff to tolerate. Test and production environments exist specifically so that changes only arrive through a controlled promotion from development — the whole three-environment model assumes a tester or production admin isn't supposed to be editing components directly in place. A managed solution enforces that by construction: there's no unmanaged layer sitting there inviting an ad hoc edit, and the only way to change something is to go back to development, make the change properly, and promote it again through the same path everything else went through. Unmanaged solutions keep their flexibility specifically for the one environment — development — where ad hoc editing is exactly what's supposed to happen.
{% endhint %}

</details>

## Key takeaway

A solution is what makes an agent movable between environments; a managed solution is what stops someone from quietly editing it once it's there. Environment variables carry the settings that should change with the move — everything on the not-solution-aware list has to be reset by hand, every time.

**Read next:** [Use environment variables in Power Apps](https://learn.microsoft.com/en-us/power-apps/maker/data-platform/environmentvariables) — the full reference this session only partly drew on, including the Dataverse/SharePoint/SQL Server-specific nuances for Data source-type variables not covered above.

1. [Establish an ALM strategy](https://learn.microsoft.com/en-us/microsoft-copilot-studio/guidance/alm) — the ALM definition, the three-environment model, the environment-type recommendation, the Entra Security Group guidance, the golden rules, and the not-solution-aware settings list.
2. [Create and manage custom solutions](https://learn.microsoft.com/en-us/microsoft-copilot-studio/authoring-solutions-overview) — the solution-as-carrier definition, default vs. custom solution, and the solution-layers UI procedure.
3. [Solution concepts with Power Platform](https://learn.microsoft.com/en-us/power-platform/alm/solution-concepts-alm) — managed vs. unmanaged definitions, solution layering, and the same-environment import restriction.
4. [Environments overview](https://learn.microsoft.com/en-us/power-platform/admin/environments-overview) — the production, sandbox, and trial environment-type definitions.
5. [Export and import agents using solutions](https://learn.microsoft.com/en-us/microsoft-copilot-studio/authoring-solutions-import-export) — the export/import procedure, the post-import authentication reconfiguration step, and the export limitations.
6. [Use environment variables in Power Apps](https://learn.microsoft.com/en-us/power-apps/maker/data-platform/environmentvariables) — data types, default vs. current value, naming rules, and size/quantity limits.
7. [Work with variables](https://learn.microsoft.com/en-us/microsoft-copilot-studio/authoring-variables) — the secret environment variable / Azure Key Vault authorization steps, caching behavior, and the Message-node exposure warning; also 2.2 and 2.4's source for topic variables, the same-page same-word trap named above.
8. [Variables overview](https://learn.microsoft.com/en-us/microsoft-copilot-studio/authoring-variables-about) — 2.2's original source for the topic/global variable scope model contrasted against environment variables above.
