Repository navigation
Python: Preserve empty hosted MCP allowlists - #29
Closed
eavanvalkenburg wants to merge 30 commits into
Closed
eavanvalkenburg wants to merge 30 commits into
eavanvalkenburg wants to merge 30 commits into
Conversation
…osoft#8505) * Python: Restrict Hyperlight to Python 3.14 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Refresh development dependencies Consolidate the open Python dependency updates, remove pandas from the shared development environment, and update checker compatibility. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Keep pandas sample-local Allow sample type checkers to validate the PEP 723 script without requiring pandas in the shared development environment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Gate Hyperlight backend below 3.15 Keep the Hyperlight connector compatible with the workspace Python range while installing the native Wasm backend only where wheels are published. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Gate Psycopg binary below 3.15 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…8432) * Bind always approval responses to surfaced requests * Addrses PR comments
…icrosoft#8512) * Added pycharm ignore entry to .gitignore * Added hooks for name and id for OTEL * Added project ID for trace surfacing + inner response id preserved for traces * restored .gitignore * clarified name to project_arm_id and moved method to match code base patterns * Restore uv.lock after branch merge Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5641780c-318a-4def-8692-710b2b061c60 * Fix Foundry attribution setup and agent response ownership Separate per-agent project identity from exporter configuration, preserve export on optional discovery failures, and capture response identity from the owned chat result. Consolidate invocation telemetry state and document both setup paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: bfc650ea-638e-4c20-aae6-f63cca98462e * Simplified the ADR * Remove redundant Azure Monitor dependency test Keep the tracing dependency and documentation fix without testing behavior owned by Azure Monitor. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Clarify required PromptAgent version in tracing sample Document FOUNDRY_AGENT_VERSION as required for PromptAgents and optional for HostedAgents, preserving runtime behavior. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Simplify Foundry tracing README guidance Keep the package README focused on installation, the setup helper and the sample link. Retain sample prerequisites and run instructions without repeating the telemetry investigation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5641780c-318a-4def-8692-710b2b061c60 Copilot-Session: bfc650ea-638e-4c20-aae6-f63cca98462e
Wait for the inner stream to block before signalling shutdown or cancellation, independently of the retained preemption deadline. Drain pending test tasks and verify that the blocked call is cancelled before cleanup. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…microsoft#8509) * Python: preserve expression results in SendActivity output Emit evaluated expressions as data while retaining authored-template interpolation. Add regression coverage for string and mapping activity forms. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Python: preserve expression results in SendActivity output Emit evaluated expressions as data while retaining authored-template interpolation. Add regression coverage for string and mapping activity forms. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> (cherry picked from commit e52b5c7) * Python: document and test SendActivity output migration Cover authored templates and explicit expressions in both activity forms, and document the intentional single-pass expression output contract. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Python: keep SendActivity guidance with the executor Move the concise migration examples into the class documentation and remove the detailed package README section. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* Python: avoid repeated workflow reference traversal Walk shared workflow mappings and lists once by identity, preserve environment-reference discovery, and reject ancestor cycles with a definition error. Add deterministic regressions and document the discovery contract. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Python: avoid repeated workflow reference traversal Walk shared workflow mappings and lists once by identity, preserve environment-reference discovery, and reject ancestor cycles with a definition error. Add deterministic regressions and document the discovery contract. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Python: type the shared workflow definition test fixture Declare the heterogeneous workflow fixture as dict[str, Any] so all test type checkers can validate its nested immutability assertions without changing runtime behavior. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Python: keep workflow discovery details in API docstrings Remove the detailed traversal section from the package README. Keep traversal limitations with the helper and document cycle errors on workflow creation methods. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ft#8508) * Python: evaluate autoSend expressions before emitting output Evaluate configured autoSend values against workflow state in function-tool and agent actions. Preserve output bindings, defaults, and resume behavior, and cover automatic output with local regression tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Python: evaluate autoSend expressions before emitting output Evaluate configured autoSend values against workflow state in function-tool and agent actions. Preserve output bindings, defaults, and resume behavior, and cover automatic output with local regression tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Python: refresh autoSend decisions when resuming actions Defer tool output expression evaluation until execution is approved, and reevaluate agent output expressions on resumed turns. Cover changed-state and evaluator-error cases without changing stored output bindings. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Python: keep autoSend details in executor docstrings Remove the granular automatic-output section from the package README and document the behavior alongside the executor classes and approval handler. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* fix: preserve dynamic function middleware Keep harness callbacks consistent as tool collections change. Preserve existing function wrappers and request-local composition without coupling reusable options to agent instances. * fix: compose harness middleware across clients Keep callback composition independent of client service discovery. Use request-local execution scopes without changing reusable options. Cover scope restoration and tool repair after collection updates. * fix: retain middleware through options clones Keep harness callback composition consistent across function-loop iterations by retaining the full chain on function wrappers. Cover real client-driven clones, repeated calls and approval flows. * test: extend dynamic tool middleware coverage Cover Python harness callback consistency across progressive tool loading, continuation changes, failures, approvals and reused options. * test: simplify streaming response collection * fix: preserve harness pipeline boundaries Build callback chains around factory results instead of sharing them between active client calls. Track active invocation callbacks across opaque function decorators while preserving explicit continuations. Cover replacement factories, captured clients and failure cleanup. * docs(dotnet): explain function callback flow Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…oft#8372) * Python: Add request-scoped Foundry agent factories * Python: Refine Foundry factory ownership and session persistence Validate functional response batches before state changes, persist ordinary Invocations factory sessions, and document fresh execution objects as the factory author responsibility. Stabilize recovery fixtures and correct CI test typing. * fix: align workflow continuation checks Require recorded completion for both Invocations workflow types. Preserve stored state when incomplete continuation is rejected. * refactor: simplify hosting checkpoint handling Keep checkpoint error policy with the workflow runtime. Add HTTP coverage for request-scoped factory behavior. * Python: Simplify request-scoped Foundry agents * Python: Fix ResponseStream test typing * Python: Align request agent validation and stream cleanup * Python: Manage request-scoped Invocations agents * Python: Validate request agent callable shape * Python: Address request agent review feedback * docs: Clarify request-scoped Foundry agent creation
…FICC (microsoft#8531) * Pass ChatClientAgent tools per run only rather than setting on FICC * Fix test errors
…s (incl. per-session GitHubToken) (microsoft#5735) Built on top of upstream's v1.0.0 migration (microsoft#6381). This change: - Bumps SDK from 1.0.0 to 1.0.1 (no breaking changes in usage) - Forwards all newly-added SessionConfig properties through CopyResumeSessionConfig (CopySessionConfig already uses source.Clone() so it's fine). Most importantly GitHubToken (per-session GitHub identity for multitenant scenarios), plus ClientName, ModelCapabilities, OnElicitationRequest, OnEvent, EnableConfigDiscovery, IncludeSubAgentStreamingEvents, DefaultAgent, Agent, Commands, CreateSessionFsProvider, and ~25 more - Makes sessionConfig non-nullable on the SessionConfig constructor (SDK requires SessionConfig.OnPermissionRequest, so a null SessionConfig was a runtime trap) - Makes onPermissionRequest a required (non-nullable) parameter on the tools/instructions constructor and the corresponding AsAIAgent extension overload, positioned before optional params - Removes the fallback 'new SessionConfig { Streaming = true }' path in RunCoreStreamingAsync since _sessionConfig is now non-nullable - Updates unit tests, integration tests, and sample for the new required-param signatures Co-authored-by: Sai Kothuri <saik@microsoft.com> Co-authored-by: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com>
…#8507) * Improve regex validation for shell and file access * Address PR comments * Fix build errors * Address PR comment
…soft#7902) (microsoft#8547) * Python: Include InvokeAzureAgent input.arguments in agent text Fold evaluated input.arguments into the text passed to agent.run() so argument-only InvokeAzureAgent actions no longer silently invoke with an empty string. * Python: skip implicit fallback when InvokeAzureAgent arguments are present Addresses review on microsoft#7907: arguments-only actions no longer append System.LastMessage / Workflow.Inputs fallback text to their structured fields in chained workflows. Adds a regression test with poisoned fallback state.
…8560) * Require expected keys in tool argument evaluation * Test missing and explicit-null tool arguments
…handoff (microsoft#8551) clean_conversation_for_handoff dropped every non-text content type to avoid replaying runtime-only tool-control payloads, but that also discarded genuine user input such as images, files, and vector store references, so agents receiving a handoff lost the user's multimodal context. Keep semantic multimodal content (data, uri, hosted_file, hosted_vector_store) on user messages while keeping assistant and other non-user messages text-only, since providers treat multimodal parts as input-only and reject them when replayed on assistant turns. Tool-control payloads (function_call, function_result, approval payloads) remain excluded as before. Fixes microsoft#7822
…e default branch (microsoft#8490) * Python: surface switch-case condition errors instead of routing to default SwitchCaseEdgeGroup wrapped every case predicate in a bare `except Exception` that only logged a warning, so a predicate that raised was treated exactly like one that returned False: the message fell through to the default branch and the workflow completed successfully. A broken routing predicate became silently wrong routing rather than a visible failure. This contradicted `Edge.should_route`, which documents that predicate errors are deliberately allowed to surface "to avoid masking logic bugs", and made the existing `FanOutEdgeRunner` error path (span status EXCEPTION, re-raise) unreachable for switch-case groups, so the failures were missing from telemetry too. The block was marked `# pragma: no cover`. Also align `FanOutEdgeRunner` with `SingleEdgeRunner`, which checks `_can_handle` before evaluating a condition: drop messages no target can handle before running the selection function. Without this, removing the catch would turn an undeliverable message into a raised AttributeError instead of a type-mismatch drop. Fixes microsoft#8489 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Python: drop target-mismatched messages before the fan-out selection function The runner fans every message out to all of the source's edge runners and gathers them with `gather_cancelling_siblings_on_error`. `FanOutEdgeRunner` checked `message.target_id` only after running the selection function, so a message addressed to an executor reached through a different edge still had this group's selection function evaluated against it - and a raise there cancelled the sibling runner that was actually delivering the message. Check `message.target_id` against `_target_map` first and return DROPPED_TARGET_MISMATCH, ahead of the `_can_handle` check, matching `SingleEdgeRunner`, which checks the target and then `_can_handle` before evaluating `Edge.should_route`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…icrosoft#8464) * Python: preserve AgentResponse metadata in WorkflowAgent streaming * Python: preserve agent ID in response aggregation --------- Co-authored-by: WhaleTech <304937387+ryo-whaletech@users.noreply.github.com>
* Python: bound PowerFx state construction Validate structural budgets before declarative state copies and PowerFx symbol conversion. Preserve copy isolation and temporary bindings, reject budget failures explicitly, and add focused boundary regressions. Fixed limits and the new Core hook require compatibility and dependency-floor review before release. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Python: bound PowerFx state construction Validate structural budgets before declarative state copies and PowerFx symbol conversion. Preserve copy isolation and temporary bindings, reject budget failures explicitly, and add focused boundary regressions. Fixed limits and the new Core hook require compatibility and dependency-floor review before release. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Python: keep PowerFx limit documentation near implementation Remove the detailed package README section and document per-traversal counting next to the budget implementation. Preserve the limits, runtime behavior, and method error documentation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* Python: Fail closed on unresolved agent-tool approvals * Python: Model workflow approval as child return * Python: Route workflow delegation with structured output * Python: Reject shared approval state collisions * Python: Isolate delegated approval continuation state * Python: Isolate custom child approval state * Python: Track delegated approval ownership * Python: Isolate parent approval middleware state
prepend_agent_framework_to_user_agent looked up the exact key
"User-Agent" when deciding whether an incoming headers dict already
carries a user agent. HTTP header names are case-insensitive, so a caller
passing default_headers={"user-agent": "my-app/1.0"} (for example to
OpenAI clients or the A2A agent) ended up with two User-Agent entries:
their original one plus a new agent-framework one. httpx then sends both
header lines, and which value a server honors is undefined.
Find the existing entry case-insensitively and prepend to it in place, so
exactly one User-Agent header survives.
Co-authored-by: Manohar Paturi <186662190+ManoharPaturi@users.noreply.github.com>
) encode_checkpoint_value used isinstance checks for dict and list, so dict subclasses (defaultdict, Counter, OrderedDict) nested in checkpoint state were silently flattened to plain dicts on save. The save-time validation passed because decoding still succeeded, but restored workflows got a plain dict: a defaultdict access pattern like state["missing"].append(x) then raises KeyError on resume, and Counter/OrderedDict types are lost. Only plain dict/list values now take the JSON path; subclasses are pickled like tuples and sets already were, preserving type and behavior through the round trip. collections.Counter joins OrderedDict, defaultdict, and deque in the built-in restricted-unpickler allowlist so the common stdlib dict subclasses restore under restricted decoding too. Co-authored-by: Manohar Paturi <186662190+ManoharPaturi@users.noreply.github.com>
A2AAgent._create_timeout_config only accepted float and httpx.Timeout, so A2AAgent(url=..., timeout=30) raised TypeError. Per PEP 484's numeric tower an int is a valid float, and httpx.Timeout itself accepts ints. Accept int (and float) while continuing to reject bool and other types. Co-authored-by: Manohar Paturi <186662190+ManoharPaturi@users.noreply.github.com>
…h finish reasons (microsoft#8478) * Python: mark hosted /responses incomplete on content_filter and length finish reasons The hosted /responses path iterated each AgentResponseUpdate's contents but never read its finish_reason, so a turn the model stopped early (Azure OpenAI content filter, token limit) ended with status "completed" and no trace of the cut-off. Callers could only detect a filtered turn by matching the canned refusal text. _OutputItemTracker now records truncating finish reasons from both the plain-agent and workflow update loops, and _handle_response ends the response with response.incomplete plus incomplete_details.reason ("content_filter" / "max_output_tokens") instead of response.completed, mirroring the OpenAI Responses shape. The refusal text is still delivered as an output item. Fixes microsoft#8475 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Persist the incomplete reason across resilient checkpoint recovery Recovery rebuilds _OutputItemTracker from the persisted response, so a marker held only in memory was lost if the crash landed between a filtered update and a later one. Mirror it into the stream's internal_metadata (persisted with every checkpoint, like the last checkpoint id) and restore it in the tracker constructor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Handle updates through one tracker entry point and pair snapshots with the checkpoint an update follows _OutputItemTracker.handle_update records the update's finish reason and then handles its contents, so the plain-agent and workflow loops share one ordering rule. The workflow loop snapshotted the response against the latest checkpoint in storage at the time an update was consumed. _SignalledIterator drives the workflow one update ahead, so by then the runner could already have checkpointed past the update; a crash after that snapshot resumed the workflow with the update never replayed and its output (and any incomplete reason) lost. _SignalledIterator now takes an optional stamp coroutine that the driver awaits right after each item is produced, before advancing the wrapped iterator again, and the loop pairs snapshots with that stamped checkpoint id. After the workflow completes, its final checkpoint is paired with the full output so recovery does not replay the last superstep. The real-crash recovery integration tests that were xfailed against microsoft#7809 pass with this. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fix pre-commit and typing failures - Drop the redundant `None, None` Generator parameters flagged by pyupgrade. - Type the `finish_reason` test parameters as `FinishReasonLiteral` instead of `str` + `type: ignore`, which `ty` does not honour. - Read the text delta with `.get()` so the `ResponseStreamEvent` TypedDict union does not need narrowing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…osoft#8201) (microsoft#8284) * Python: add public TypedDict for AgentExecutor checkpoint state (microsoft#8201) Expose AgentExecutorCheckpointState / AgentSessionCheckpointState, validate restore field types, and cover partial/malformed/forward-compatible payloads. * fix(checkpoint): address microsoft#8284 review on session TypedDict ownership (#1) - Define AgentSessionDict on AgentSession (to_dict return) so the shape is not duplicated in AgentExecutor; keep AgentSessionCheckpointState as alias. - Include ServiceSessionId mapping in service_session_id. - Type on_checkpoint_restore as AgentExecutorCheckpointState only. - Sync root stub exports (__init__.pyi) with runtime __all__. * fix(checkpoint): validate AgentExecutor restore element types (microsoft#8284) Enforce Message/Content element types, string pending-request keys, and required agent_session.session_id so malformed checkpoints fail at restore. * fix(checkpoint): validate AgentSessionDict fields on restore - Validate agent_session.state / service_session_id types - Raise WorkflowCheckpointException when session restore fails - Drop unshipped AgentSessionCheckpointState alias; use AgentSessionDict * fix(checkpoint): keep session/executor checkpoint payloads as dict TypedDict schemas remain public for documentation and validation, but to_dict and on_checkpoint_save/restore stay dict[str, Any] so subclasses, samples, and pyright stay compatible. * fix(checkpoint): keep AgentSessionDict optional keys under postponed annotations Use a required base plus total=False subclass, stop exporting AgentSessionDict from the public package root, and tighten AgentExecutor restore validation with walrus checks. --------- Co-authored-by: minelhi <3417378192@qq.com> Co-authored-by: LI <2484593937@qq.com> Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com> Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
…ft#8542) * feat(core): add session-scoped mode to FileAccessProvider Add an opt-in session_scoped mode (with an optional explicit scope) that confines file-access tool operations to a working folder derived from the session id via the shared _storage_key_segment derivation under the file-access-specific "~access-" prefix, mirroring FileMemoryProvider. The mode fails closed (ValueError) when neither a scope nor a session id is available, creates the session folder in before_run, appends a session-isolation note to the tool instructions, and logs the derived folder at debug level. All eight tools route store calls through a single _session_path helper; ls/grep treat an empty directory as the session root and grep results stay session-relative. Default mode is byte-for-byte unchanged. Adds seven regression tests, including the COLLIDING_IDENTIFIERS attack strings parametrization, fail-closed coverage, scope override, and shared-store default regression. All harness file-access tests pass; the only full-suite failure is the pre-existing test_feature_stage.py::test_feature_id_allows_lowercase_values on main. Addresses microsoft#8539. * feat(core): wire file_access_session_scoped through create_harness_agent Expose the FileAccessProvider session-scoped mode from create_harness_agent via a new file_access_session_scoped flag (default False, preserving shared-store semantics), threaded through _assemble_context_providers and synchronized across the .pyi stub and the package AGENTS.md file-access section. Adds a pass-through regression test. Addresses microsoft#8539. * docs(core): describe scoped file access isolation relative to the workspace The session-scoped instruction suffix claimed files are never visible to other sessions, which is wrong when an explicit scope intentionally shares a workspace across sessions. Rephrase the suffix, the class docstring, and the session_scoped parameter docs to describe isolation relative to the resolved workspace (session or configured scope). Addresses review feedback on PR microsoft#8542. * fix(core): harden session-scoped file access boundaries Address review feedback on PR microsoft#8542: - Normalize ls/grep directories with _normalize_relative_path(..., is_directory=True) in scoped mode so a ".." directory cannot escape the session workspace through a permissive custom store. - Place every scoped working folder under a fixed ~access- namespace directory, so file access and file memory working folders cannot collide when both providers share one store root and a literal-safe identifier. - Treat a non-empty explicit scope as enabling scoped mode by itself, aligning with FileMemoryProvider(scope=...). Adds parent-traversal, namespace-collision, and scope-only regression tests and updates layout assertions to the namespaced folder shape. All harness suites pass; ruff clean.
eavanvalkenburg
had a problem deploying
to
github-app-auth
September 21, 2026 10:39 — with
GitHub Actions
Failure
eavanvalkenburg
had a problem deploying
to
github-app-auth
September 21, 2026 10:39 — with
GitHub Actions
Failure
eavanvalkenburg
had a problem deploying
to
github-app-auth
September 21, 2026 10:39 — with
GitHub Actions
Failure
eavanvalkenburg
temporarily deployed
to
integration
September 21, 2026 10:39 — with
GitHub Actions
Inactive
eavanvalkenburg
temporarily deployed
to
integration
September 21, 2026 10:39 — with
GitHub Actions
Inactive
This branch had an error being deployed
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.
Motivation & Context
Preserve explicitly supplied empty
allowed_toolslists when constructing hosted MCP tool configurations.Description & Review Guide
Nonefrom an empty list, and document the distinction.Related Issue
N/A.
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after a language prefix) — a workflow keeps the label and title prefix in sync automatically.