Copilot preview operations¶
Operational reference for the default-off internal Copilot harness preview and its
MCP, scoped skills, and native session persistence. Design rationale lives in
architecture.md; the
runnable local walkthrough lives in the
preview sample.
Status. This preview is not production-activated. Nothing here authorizes enabling the flag on a customer-facing app. Microsoft Agent Framework (MAF) remains the default and is unchanged while the flag is off.
Qualification scope¶
Offline filesystem checks establish adapter behavior, not real-service authentication, SDK continuation or Functions hosting. Earlier Blob continuation and compaction runs exercised a different persistence implementation and do not qualify this thin filesystem adapter. Functions-host packaging, startup, provider auth, storage selection and restart behavior, and overlapping same-session behavior across workers or instances remain unqualified.
Actual Linux native skill-helper execution and SDK managed-settings/approval scenarios remain unqualified. Earlier Windows mechanics checks are not Linux or production qualification.
The SDK owns continuation, recovery and compaction. The host does not promise transactional turns or exactly-once tool effects.
Opt in and opt out¶
AZURE_FUNCTIONS_AGENTS_ENABLE_COPILOT is the only harness selector and is read
once per app construction. Standalone runners retain a separate first-use
default per resolved app root.
| Value | Result |
|---|---|
Unset, false, 0 |
MAF (default). No Copilot SDK import, native process, download, auth or telemetry. |
true, 1 |
Internal Copilot preview for qualification of every role served by that app. |
| Present but empty, whitespace-only, or any other text | Explicit configuration error; the app does not start. |
Values are trimmed and case-insensitive. Changing the variable does not
affect an app or standalone default that has already been constructed:
restart the Functions host to switch in either direction. There is no
per-agent selector, no mixed-harness app, and no automatic fallback to MAF once
Copilot is selected. The legacy runtime: front-matter field remains ignored.
Restarting with the flag off selects MAF and its existing history provider. Only the selected harness constructs, uses and closes its persistence adapter; shared configuration does not initialize or probe the other implementation.
Runtime-owned telemetry keeps the same invocation surface under either
harness. Direct, streaming, built-in chat, inbound MCP, non-HTTP, delegate, and
leaf runtime spans use agent.run {slug} plus the shared af.agent.*
attributes and af.agent.harness=maf|copilot. Copilot adds one semantic execution
child span plus observed tool spans and usage events without changing that outer
surface or MAF's framework-owned instrumentation.
Authored Python tools¶
Authored Python tools follow the normal runtime surface: runtime @tool
descriptors or local public functions discovered from tools/*.py. Runtime
filters, authentication, and input schemas still apply.
MCP and scoped skills¶
These are outbound capabilities of the existing direct, non-streaming HTTP
preview, not new triggers or endpoint support. Keep the same SessionFs,
lock, and create/resume/disconnect lifecycle. The checked-in sample does not
require MCP credentials or skills.
MCP configuration and auth¶
Use existing remote HTTP/streamable-HTTP mcp.json entries and frontmatter
mcp: false / mcp.exclude filters. The per-server tools field keeps its
existing all/none/exact-name semantics, not wildcard patterns; see
per-server tool selection.
Discovery reads declarations only; the selected SDK connects and loads tools.
MCP header handling preserves the existing auth contract:
- Without
auth, static headers pass through unchanged, includingAuthorization. - Empty or whitespace-only
auth.scopewarns and stays static-only, with no token attempt. - A nonempty scope uses the authored scope and existing credential precedence:
a resolved
auth.client_idselects that client's credential; an absent, empty, or unresolved client ID uses the app-wide/default credential selection. - Generated
Authorizationreplaces staticAuthorization. Headers and tokens are never logged.
Copilot receives freshly materialized static headers at session create/resume, between completed turns on the same native session ID. There is no mid-turn refresh or full MAF per-request-auth parity. Auth, connection, resume, and tool errors use normal SDK/runtime paths; there is no automatic retry of possibly side-effecting calls, stale-token fallback, dropped capability, or reset to an empty session.
Ordinary configured MCP requests are noninteractive. The internal permission
callback is installed on create and resume; only kind == "mcp" delegates to
the SDK's PermissionHandler.approve_all approve-once helper. Native server tool
filters still apply. There is no user callback setting or global approve-all
policy. Standard SDK managed settings and approval restrictions remain in force.
Skill ownership and helpers¶
Use existing SKILL.md, skills: false, and skills.exclude authoring.
Both harnesses receive the same filtered directory candidates from shared discovery.
The scan stops at SKILL.md roots and searches through two child levels; it does
not read metadata or resources. Copilot receives individual approved directories
and disabled directory names. The host adds no skill catalog to the authored
instructions; the existing replace prompt mode is unchanged. The SDK advertises, validates, and loads
skill content; a candidate is not a confirmed loaded skill. Native skill loads
instructions, view reads approved resources, and bash can start only approved
literal script invocations. Other ambient SDK tools remain excluded or denied.
Every resource or script target must belong to an approved most-specific
canonical discovered skill root. Excluded roots remain ownership metadata:
independently supplied overlapping roots keep separate grants. An enabled parent
cannot authorize an explicitly indexed excluded child. Implicit nested documents
belong to the containing skill rather than becoming independently selectable roots.
Traversal, symlink escapes, ambiguous ownership, and general project-file access
are denied. skills: false exposes no skill/helper capabilities.
Working directory, a loaded skill, and caller/model intent do not grant access. An approved script invocation may start from any turn; the SDK does not attest that it originated in a skill. Skills are trusted deployment-owned code. Permitted scripts run with host privileges, not in an OS sandbox, and the policy does not constrain their internal effects. MAF retains its existing skill behavior, including nested resource recursion; this does not establish MAF file-script parity. Native automatic skill advertising and instruction loading under the unchanged replace prompt mode have not been live-qualified. Python session options alone do not prove that this mode preserves native advertising.
Supported native script forms¶
The target must be an absolute approved .py or .sh file under its owning
skill's scripts/ tree. Interpreter forms are python / python3 for .py and
bash for .sh; direct POSIX invocation requires an executable script.
Arguments must be literal. Quote paths or arguments containing spaces.
These are native helper command-shape examples with placeholders, not PowerShell setup commands or evidence of Linux execution:
python "<absolute-approved-script.py>" "literal argument"
python3 "<absolute-approved-script.py>" "literal argument"
bash "<absolute-approved-script.sh>" "literal argument"
"<absolute-approved-executable.py>" "literal argument"
"<absolute-approved-executable.sh>" "literal argument"
Relative or ambiguous commands, general shell commands, chaining/operators, substitution, pipelines, and redirection are denied. The host adds no script runner, loader, prompt engine, or new script settings.
Storage selection¶
There is no Copilot-specific storage selector. Blob is selected whenever
AzureWebJobsStorage or AzureWebJobsStorage__blobServiceUri is configured;
local files are selected only when neither is configured. This applies to
local hosts as well as storage configured for an app. Configured Blob auth,
network or configuration failures surface as errors, never a local fallback.
| Setting | Purpose |
|---|---|
AzureWebJobsStorage |
Existing Functions storage connection string; takes precedence over the service URI. Azurite also selects Blob storage. |
AzureWebJobsStorage__blobServiceUri |
Existing identity-based Blob service URI, used with DefaultAzureCredential when no connection string is configured. |
AzureWebJobsStorage__clientId / AZURE_CLIENT_ID |
Optional managed-identity client ID for storage; the storage-specific clientId takes precedence. |
AZURE_FUNCTIONS_AGENTS_SESSION_CONTAINER |
Blob container name; defaults to azure-functions-agents (the same container the MAF history provider uses, in a disjoint prefix). |
AZURE_FUNCTIONS_AGENTS_SESSION_DIR |
Existing local root override; defaults to ~/.azure-functions-agents. Determines the local SDK workspace even with Blob persistence, and the native file storage root when no Blob setting is configured. Keep it stable when resuming SDK sessions. |
AZURE_FUNCTIONS_AGENTS_REASONING_EFFORT / AZURE_FUNCTIONS_AGENTS_REASONING_SUMMARY |
Optional native Copilot reasoning settings captured once per app. Valid effort values are low, medium, high, xhigh, max; valid summary values are none, concise, detailed. Invalid values emit a warning and are omitted so the native SDK/provider default applies. |
Storage configuration is captured per app. Restart the host after changing
captured settings, and check both process variables and local.settings.json
for conflicting values. Never put storage credentials in a checked-in template.
For an optional local-host Blob run, use an already-approved disposable target. For example, the identity-based path uses these existing settings:
$env:AzureWebJobsStorage__blobServiceUri = "https://<account>.blob.core.windows.net"
$env:AZURE_FUNCTIONS_AGENTS_SESSION_CONTAINER = "<approved-sample-container>"
Ensure no connection string is configured if you intend to use the service URI. Use your approved credential with the required Blob data-plane access; this guide does not authorize access changes or real-service test runs.
Where state lives¶
The SDK reads and writes opaque files through SessionFs. The host stores an
ordinary local file or one Blob for each SDK file, under this readable name:
agent_id is the unchanged shared _agent_identity.agent_id(slug) result:
trimmed, lower-case WEBSITE_SITE_NAME plus canonical agent slug. An unset,
empty or whitespace-only site name uses local; owner and deployment values
do not affect it. The adapter calls this helper directly rather than caching
or reconstructing an identity prefix. For agent main without a site name,
the session prefix is copilot-native/local/main/{session_id}/.
The name is relative to the configured Blob container or local storage root.
Session IDs retain the ^[A-Za-z0-9._-]{1,128}$ validation rule. SDK paths are
normalized and contained within the adapter's configured roots, including
handling the SDK's declared host-path convention. The adapter does not parse
file contents to resolve paths or restrict persistence to known filenames.
Only the exact current host workspace is accepted. Unknown paths recorded by
the SDK are denied; restoring after a workspace change is not qualified.
MAF history uses its own provider and agent-sessions/ namespace. Neither
harness imports, initializes, probes or cleans up the other's persistence.
Filesystem and concurrency boundary¶
The adapter implements read, write, append, exists, stat, directory listing with entry types, mkdir, remove and rename, using SDK-shaped metadata and errors. Blob rename can require copy/delete and is not crash-atomic. These file operations are not a whole-session transaction.
Turns for the same (agent, session) are serialized within one Python
process, with bounded waiting. Independent sessions remain concurrent.
Supported Azure Functions invocations in that process share the worker event
loop while reusing the same app-owned runtime and native client. There is no
distributed lock across workers or scaled-out instances. Sequential requests
can resume on another worker only when both see the same persisted namespace:
Blob when configured, or a genuinely shared local filesystem if one exists.
Configured Blob failures still error rather than falling back to local files.
If overlapping same-session requests land on different workers, each can read
prior state before the other writes. On the MAF path, append-blob transcript
entries may arrive in either order. On the Copilot path, opaque multi-file
SessionFs updates can lose updates or leave a mixed-file combination because
the adapter performs overwrite, read-modify-write append, and copy/delete
rename operations. The runtime does not guarantee corruption on every race,
but it also does not provide exactly-once coordination or magical host-side
recovery for these overlaps. Native SDK/provider formats, compaction, and
recovery remain native responsibilities.
Runtime lifetime and shutdown¶
Each Copilot-enabled app binding owns one lazy SDK client and one shared
credential for that warm worker. Requests reuse that client, but each request
owns its own SessionFs adapter: the adapter stays open until session
disconnect cleanup completes, then closes before the request returns, including
on failure or cancellation. Startup failures attempt bounded immediate cleanup
and report the startup failure rather than any cleanup follow-on error.
There is no retained failed-client/filesystem retry registry and no process-exit
atexit fallback. The supported shutdown path is the runtime's explicit async
shutdown_runtime(), which attempts a
bounded graceful stop and then bounded force_stop, still attempts credential
cleanup, reports cleanup failures immediately, and clears cached handles so a
stopped or closing resource is never reused. This preview does not claim
that every host termination awaits that async shutdown path.
Telemetry addendum¶
The semantic execution boundary follows FRD 0009 ยง4.10.
Copilot telemetry reuses the active turn's existing public session-event subscription and custom-tool wrapper, with the existing tracer, provider, resource, sampling policy, processors, and exporter. It remains no-op when observability is inactive and leaves MAF's framework-owned instrumentation unchanged. There is no native file bridge, OTLP receiver, new dependency, telemetry setting, or native telemetry opt-in; native session-telemetry policy is unchanged.
Each actual Copilot execution has exactly one
invoke_agent {readable-agent-name} semantic span beneath the unchanged neutral
agent.run {slug} outer span. It has gen_ai.operation.name=invoke_agent and
kind INTERNAL. It starts after resolved request identity, before native
session acquisition, and continues through adapter cleanup. Its duration includes
setup and cleanup, not just model inference. Direct runner calls without an outer
runtime span still emit this span under available caller context, without inventing
ingress or mutating an unrelated caller span.
gen_ai.agent.id and gen_ai.agent.name reuse the existing stable ID and
app-qualified readable name. gen_ai.conversation.id is the resolved public
session ID, never a native storage/session identifier; gen_ai.request.model
is the configured model when known. Provider fields require proven provider
semantics, not a harness label or a legacy Azure compatibility assumption.
Unavailable values are omitted.
Each unique public ASSISTANT_USAGE event is recorded as a custom
af.agent.model.usage span event on the owning runtime invocation span, or on
the semantic span when no runtime span exists. Name-only skill observations
follow the same rule; runtime summaries and lifecycle events remain outer.
It uses standard gen_ai.usage.* fields where reported semantics match
exactly; reported duration, TTFT, and inter-token measures remain custom
af.agent.usage.* values. The existing once-only eight-field invocation totals
stay unchanged, as do deduplication, public results, and accounting. Token totals
on the semantic invocation are recommended optional enrichment, not a semantic
discovery or compatibility prerequisite, and are explicitly deferred. Missing
usage and recursive child totals are not inferred.
Completion records, including completion-only records, continue to contribute
to AgentResult and aggregate tool counts. A timed telemetry span requires a
coherent call-ID/timestamp-matched tool.execution_start /
tool.execution_complete pair. Custom tools restore captured host context
before invocation, and the wrapper owns each wrapped call ID so one wrapped
call is counted once. Both custom-wrapper and event-derived native tool spans
explicitly use the captured semantic invocation parent, rather than incidental
native callback context. This preserves the host's nested coordinator semantic invocation
-> execute_tool delegate_<slug> -> specialist execution (including its outer
runtime span where present and semantic invocation) trace shape for delegates,
with isolated contexts for concurrent agents. Coordinator-visible SSE remains
limited to the delegate tool boundary and final child text.
| Source | Expectation |
|---|---|
| Direct HTTP and built-in chat UI turns | Use the same turn-scoped runtime invocation span, tool accounting, and usage observations. |
/chatstream SSE |
Mirrors translated callbacks for the current turn only; it is not evidence of full native child-span hierarchy. |
| Delegates | Preserve the nested host trace shape above; native specialist session events do not appear as a second coordinator stream. |
| Workflow Sub Agent leaves | Each leaf has its own semantic execution span including setup/cleanup; its configured model is attached when known. Inference timing is not fabricated. |
| Non-HTTP triggers, inbound MCP, and Debug UI continuation | Follow the same turn-scoped rules when routed through the shared runner, but remain offline-only expectations rather than live host/Application Insights qualification. |
ENABLE_SENSITIVE_DATA may enable prompt/request and response capture, but
Copilot run-log payloads never include full tool_calls records; MAF run
logging is unchanged. Credentials, authorization headers, raw event envelopes,
reasoning text, and skill paths or content are never exported; skill telemetry
uses the sanitized skill name only. Diagnostics and telemetry cleanup stay
best-effort and non-throwing so they do not change request outcomes,
cancellation, or SSE termination.
Caught Python exception messages and stack traces are recorded regardless of
ENABLE_SENSITIVE_DATA, matching MAF behavior; application exception text
may contain sensitive data.
Actual invocation errors receive error status, error.type, and existing
fault-domain/exception diagnostics. Recovered tool errors do not fail a successful
invocation; cancellation is a distinct nonfailure outcome. Original errors remain
authoritative over cleanup errors. This adds no fabricated inference spans/latency,
standard model metrics, cost, or complete native hierarchy. Agent-level telemetry
does not establish portal parity or live Azure/Application Insights verification.
Inspecting a session¶
Use the app's shared agent identity and returned session ID to identify only the session you own. For a dedicated local sample run, inspect file metadata without assuming a native file format:
Get-ChildItem -LiteralPath "<sample-run-directory>" -Recurse -File |
Select-Object FullName, Length, LastWriteTimeUtc
For one known Blob file, a read-only metadata query is:
az storage blob show --account-name "<account>" --container-name "<container>" `
--name "copilot-native/<agent-id>/<session-id>/<sdk-relative-path>" `
--auth-mode login --query "{size:properties.contentLength, modified:properties.lastModified}"
Native files may contain conversation content or tool output. Do not publish their contents in diagnostics or hand-edit them; the SDK owns their format.
Every caller-supplied primary session ID uses open-or-create, including HTTP,
inbound MCP, direct runner API, and HostedSkill calls. This is the same
surface-neutral continuity behavior as MAF. After the Copilot session namespace
opens, an empty root or only an empty, correctly typed /workspace directory
is treated as host scaffolding and creates the requested native session. Any
other entry, including a zero-byte file, empty native directory, or nonempty
workspace, selects SDK resume. The host reads entry names and types only; it
does not parse native filenames or contents.
Error behavior¶
Missing files, existing destinations, wrong entry types, nonempty directory
removal and denied paths use the SDK's filesystem error contract. The pinned
SDK narrows some result codes to ENOENT or UNKNOWN and can map some exists
errors to false. That SDK behavior is not evidence that storage is healthy;
the adapter must report failures correctly rather than manufacture absent or
empty data.
Configured Blob auth, network and configuration failures are errors, not reasons to change backend or switch to MAF. Session and provider failures surface through the runtime's existing error responses, without prompts, tool payloads, file contents or credentials in diagnostics. Resume and format compatibility decisions belong to the SDK. State-classification failures never authorize create, and a failed resume never triggers create, deletion, reset, or repair.
Retention and targeted cleanup¶
The host adapter adds no TTL or background pruning. SDK-requested removal is a filesystem operation, not a public session-management endpoint. Operator retention and Blob soft-delete, version, snapshot and backup policies remain the storage owner's responsibility.
For sample cleanup, stop the host first and identify the exact test-owned session prefix or dedicated run directory. Review the files before removal. Do not remove shared containers, unrelated sessions, MAF history or SDK caches. Do not prune apparent compaction artifacts by filename; their relationships belong to the SDK. Removing live files does not erase service-retained copies.
Native compaction¶
Native compaction is enabled with the SDK's own defaults; the host adds no
summarizer, no second compacted-context format and no token-threshold mapping.
Legacy agent_configuration.agent_framework.compaction.max_context_window_tokens
configuration emits a warning and is ignored; MAF and Copilot each use their
native defaults. On the Copilot path, agent_configuration.max_output_tokens
maps to the request-local native ProviderConfig.max_output_tokens override.
The host does not add a second truncation layer or mutate the shared app-level
provider configuration.
All compaction artifacts are persisted as opaque SDK files. Filesystem tests alone do not prove native compaction or session restoration, and the host adds no guarantee about the SDK's internal restore behavior.
Negative-case walkthrough¶
Use isolated local settings or offline fixtures, not a customer's storage:
- Invalid harness selector โ an empty or invalid
AZURE_FUNCTIONS_AGENTS_ENABLE_COPILOTvalue fails app construction. - Configured Blob failure โ an invalid or unavailable configured storage target surfaces an error rather than writing session files locally.
- Transcript replay โ
/agents/main/historyreturns a 501 JSON error rather than an empty success shape (the Debug UI turns it into a "transcript not restored" notice)./agents/main/chatstreamsupports SSE; a native creation/catalog failure emits terminalerrorwithout advertising a session.
Known limits¶
- History projection remains unsupported on the Copilot path. Native session files are never projected into MAF history.
- The inbound built-in MCP tool (
builtin_endpoints.mcp: true) is enabled through the existing bound runner. It validatesarguments.prompt, returns the agent's session ID, and normalizes the extension-owned transportsessionid(also accepting the existingsessionIdspelling) for native continuation. This is not a public tool argument for choosing an agent session. A transport ID creates with that ID only when typed storage listings confirm no state beyond empty host scaffolding; otherwise it requests strict native resume. A failed resume is returned without retry-as-create. Omitted transport IDs create fresh sessions. The Functions MCP extension owns/runtime/webhooks/mcpand its system-key authentication (x-functions-key);builtin_endpoints.http_authcontrols HTTP chat only, not this webhook. This is distinct from outboundmcp.jsontools. Broader endpoint/session API improvements are deferred to Azure/azure-functions-bucees-planning#1366. - Registered non-HTTP triggers and the built-in Debug UI (live chat, streaming, and continuation by pasting or picking a session ID) are enabled through the shared registration and runner path. Resuming an explicit ID continues the native session without restoring earlier transcript messages; the UI says so. A failed resume is an error and never creates a replacement session. These surfaces and inbound MCP have offline unit/component coverage only. Core Tools/local-host evidence would separately establish indexing, actual binding delivery, and extension transport/auth behavior; none was collected for this change. Deployed-host evidence is a separate category and was not collected either. Not live-qualified describes these evidence gaps, not an unsupported runtime verdict. A missing binding extension that prevents host indexing is a setup/integration failure, not something the runtime must recover from.
- HTTP streaming, declared delegates, Workflow Sub Agents, and workflow management are supported by the adapter. Leaf state is isolated locally and deleted; it does not use the primary persistent storage route. These adapter tests do not qualify hosted execution or real-provider/service parity.
- Same-session serialization is process-local only. Sequential cross-worker resume depends on a consistent accessible backend namespace and never on request affinity. Overlap across workers or instances can observe stale state, commit in nondeterministic order, lose Copilot file updates, or leave mixed SessionFs state; the runtime does not promise exactly-once tool effects or host-managed recovery for those races.