---
name: python-development
description: >
Coding standards, conventions, and patterns for developing Python code in the
Agent Framework repository. Use this when writing or modifying Python source
files in the python/ directory.
---
# Python Development Standards
## File Header
Every `.py` file must start with:
```python
# Copyright (c) Microsoft. All rights reserved.
```
## Type Annotations
- Always specify return types and parameter types
- Use `Type | None` instead of `Optional[Type]`
- Use `from __future__ import annotations` to enable postponed evaluation
- Use suffix `T` for TypeVar names: `ChatResponseT = TypeVar("ChatResponseT", bound=ChatResponse)`
- Use `Mapping` instead of `MutableMapping` for read-only input parameters
- Prefer `# type: ignore[...]` over unnecessary casts, or `isinstance` checks, when these are internally called and executed methods
But make sure the ignore is specific for both mypy and pyright so that we don't miss other mistakes
- Internal private helpers may be used across `agent_framework*` modules when intentional; use a targeted
`# pyright: ignore[reportPrivateUsage]` instead of making the helper public just to satisfy pyright.
- Do not add trivial pass-through or one-line helper functions solely to appease typing. Prefer targeted ignores,
casts, or clearer annotations over adding runtime overhead without a design benefit.
## Function Parameters
- Positional parameters: up to 3 fully expected parameters
- Use keyword-only arguments (after `*`) for optional parameters
- Provide string-based overrides to avoid requiring extra imports:
```python
def create_agent(name: str, tool_mode: Literal['auto', 'required', 'none'] | ChatToolMode) -> Agent:
if isinstance(tool_mode, str):
tool_mode = ChatToolMode(tool_mode)
```
- Avoid shadowing built-ins (use `next_handler` instead of `next`)
- Avoid `**kwargs` unless needed for subclass extensibility; prefer named parameters
## Docstrings
Use Google-style docstrings for all public APIs:
```python
def equal(arg1: str, arg2: str) -> bool:
"""Compares two strings and returns True if they are the same.
Args:
arg1: The first string to compare.
arg2: The second string to compare.
Returns:
True if the strings are the same, False otherwise.
Raises:
ValueError: If one of the strings is empty.
"""
```
- Always document Agent Framework specific exceptions
- Explicitly use `Keyword Args` when applicable
- Only document standard Python exceptions when the condition is non-obvious
## Import Structure
```python
# Core
from agent_framework import Agent, Message, tool
# Components
from agent_framework.observability import enable_sensitive_telemetry
# Connectors (lazy-loaded)
from agent_framework.openai import OpenAIChatClient
from agent_framework.foundry import FoundryChatClient
```
## Public API and Exports
In `__init__.py` files that define package-level public APIs, use direct re-export imports plus an explicit
`__all__`. Avoid identity aliases like `from ._agents import Agent as Agent`, and avoid
`from module import *`.
Do not define `__all__` in internal non-`__init__.py` modules. Exception: modules intentionally exposed as a
public import surface (for example, `agent_framework.observability`) should define `__all__`.
```python
__all__ = ["Agent", "Message", "ChatResponse"]
from ._agents import Agent
from ._types import Message, ChatResponse
```
Special case: the root `agent_framework/__init__.py` uses lazy runtime exports. For root public API changes:
- Add the symbol to `_LAZY_MODULE_EXPORTS` and keep `_LAZY_EXPORTS` derived from it.
- Keep the explicit runtime `__all__` synchronized; it is still required for `from agent_framework import *`.
- Add the same public symbol to `agent_framework/__init__.pyi` so pyright, mypy, and editors see the typed surface.
- Put runtime deprecation behavior in the owning module via that module's `__getattr__`; avoid root-level
special-case branches for individual deprecated exports.
- Identity aliases are appropriate in `.pyi` stubs because they mark re-exported names for type checkers; avoid them
in runtime `.py` modules unless there is a specific compatibility reason.
## Performance Guidelines
- Cache expensive computations (e.g., JSON schema generation)
- Prefer `match/case` on `.type` attribute over `isinstance()` in hot paths
- Avoid redundant serialization — compute once, reuse
## Style
- Line length: 120 characters
- Format only files you changed, not the entire codebase
- Prefer attributes over inheritance when parameters are mostly the same
- Async by default — assume everything is asynchronous
## Naming Conventions for Connectors
- `_prepare_<object>_for_<purpose>` for methods that prepare data for external services
- `_parse_<object>_from_<source>` for methods that process data from external services
# GitHub Copilot Instructions
Microsoft Agent Framework - a multi-language framework for building, orchestrating, and deploying AI agents.
## Repository Structure
- `python/` - Python implementation → see [python/AGENTS.md](../python/AGENTS.md)
- `dotnet/` - C#/.NET implementation → see [dotnet/AGENTS.md](../dotnet/AGENTS.md)
- `docs/` - Design documents and architectural decision records
## Architectural Decision Records (ADRs)
ADRs in `docs/decisions/` capture significant design decisions and their rationale. They document considered alternatives, trade-offs, and the reasoning behind choices.
**Templates:**
- `adr-template.md` - Full template with detailed sections
- `adr-short-template.md` - Abbreviated template for simpler decisions
When proposing architectural changes, create an ADR to capture options considered and the decision rationale. See [docs/decisions/README.md](../docs/decisions/README.md) for the full process.
---
name: pull-requests
description: >
Guidance for creating pull requests and handling PR review comments in the
Agent Framework repository. Use this when writing a PR description (filling out
the PR template) or when responding to and resolving review comments on an
existing PR.
---
# Pull Request Workflow
This skill covers two tasks: (1) writing a high-quality PR description, and
(2) handling review comments on an existing PR.
## 1. Writing the PR description
Always follow the repository PR template at
[`.github/pull_request_template.md`](../../pull_request_template.md). Keep its
exact structure and headings. Fill every section:
### `### Motivation & Context`
Explain *why* the change is needed: the problem it solves and the scenario it
contributes to. Describe the net change relative to `main` — this is implied, so
do **not** spell out "vs main" explicitly.
### `### Description & Review Guide`
Describe the changes, the overall approach, and the design. Answer the three
prompts:
- **What are the major changes?**
- **What is the impact of these changes?**
- **What do you want reviewers to focus on?** — This item is for **human
reviewers only**. Automated/AI reviewers must ignore it and review the entire
change rather than narrowing scope to it.
### `### Related Issue`
Link the issue the PR fixes using a GitHub closing keyword (`Fixes #123` /
`Closes #123`) so it closes automatically on merge. A PR with no linked issue may
be closed regardless of how valid the change is. Before opening, confirm there is
no other open PR for the same issue; if there is, explain how this PR differs.
### `### Contribution Checklist`
Check every item that applies. For the breaking-change item:
- Leave **"This is not a breaking change."** checked for the common case.
- If the change **is** breaking, add the `breaking change` label **or** put
`[BREAKING]` in the title prefix, before or after a language prefix such as
`Python:` or `.NET:` — workflows keep the label and the title prefix in sync
automatically (see `.github/workflows/label-title-prefix.yml` and
`.github/workflows/label-pr.yml`).
### Do not
- Do **not** add ad-hoc sections such as "Validation" or "Tests run"; CI/CD and
the checklist already cover validation status.
- Do **not** remove or reorder the template's headings.
### Creating the PR
Open new PRs as **drafts** until they are ready for review. Example:
```bash
gh pr create --repo microsoft/agent-framework --base main \
--head <your-fork-owner>:<branch> --draft \
--title "<concise title>" --body "<body following the template>"
```
## 2. Handling review comments
When a PR receives review comments, follow this sequence — **do not start editing
code before the user has reviewed the plan**:
1. **Review the comments.** Read every review comment and thread on the PR,
including inline code comments and general review summaries.
2. **Make a plan.** Produce a concrete plan describing how each comment will be
addressed (or why it should not be, with reasoning).
3. **Let the user review the plan.** Present the plan and wait for the user's
approval or adjustments before implementing anything.
4. **Implement.** Make the agreed changes.
5. **Reply to every comment.** Add a reply to **all** comments explaining how it
was addressed (or the agreed outcome) — leave none unanswered.
6. **Resolve resolved threads.** Mark a review thread as resolved only when the
comment has actually been addressed.
### Useful commands
List review comments and threads:
```bash
# Inline review comments
gh api repos/{owner}/{repo}/pulls/{pr}/comments
# Review threads with resolution state (GraphQL)
gh api graphql -f query='
query($owner:String!,$repo:String!,$pr:Int!){
repository(owner:$owner,name:$repo){
pullRequest(number:$pr){
reviewThreads(first:100){
nodes{ id isResolved comments(first:50){ nodes{ id body author{login} } } }
}
}
}
}' -F owner={owner} -F repo={repo} -F pr={pr}
```
Reply to an inline review comment:
```bash
gh api repos/{owner}/{repo}/pulls/{pr}/comments/{comment_id}/replies \
-f body="Addressed in <commit>: <explanation>"
```
Resolve a review thread (needs the thread node id from the GraphQL query above):
```bash
gh api graphql -f query='
mutation($threadId:ID!){
resolveReviewThread(input:{threadId:$threadId}){ thread{ isResolved } }
}' -F threadId={thread_id}
```
---
name: python-feature-lifecycle
description: >
Guidance for package and feature lifecycle in the Agent Framework Python
codebase, including stage meanings, feature-stage decorators, feature enums,
and how to move APIs from one stage to the next.
---
# Python Feature Lifecycle
## Two lifecycle levels
Agent Framework uses lifecycle at two different levels:
1. **Package lifecycle** — the maturity of the package as a whole
2. **Feature lifecycle** — the maturity of a specific API or feature inside that package
These are related, but they are **not the same thing**.
- The **package stage is the default** for everything in the package.
- **Feature-stage decorators are only for exceptions** when a feature is behind the package's default stage.
- Do **not** decorate every class or function just because the package is experimental or release candidate.
### Important default
If a package is still in **beta / experimental preview**, all public APIs in that package are experimental by default.
- Do **not** add `@experimental(...)` everywhere in that package.
- The package stage already communicates that default.
Once a package moves forward, you can keep individual features behind:
- If a package moves to **release candidate**, a feature may remain **experimental**
- If a package moves to **released / GA**, a feature may remain **experimental** or **release candidate**
That is the main use case for feature-stage decorators.
## The four stages
### 1. Experimental
Use for features that are still unstable and may change or be removed without notice.
Feature-level code pattern:
```python
from ._feature_stage import ExperimentalFeature, experimental
@experimental(feature_id=ExperimentalFeature.MY_FEATURE)
class MyFeature:
...
```
Behavior:
- Adds an experimental warning block to the docstring
- Records feature metadata on the decorated object
- Emits a runtime warning the first time the feature is used (once per feature by default)
Enum setup:
- Add an all-caps member to `ExperimentalFeature`
- Reuse the same feature ID across all APIs that belong to the same conceptual feature
### 2. Release candidate
Use for features that are nearly stable but may still receive small refinements before GA.
Feature-level code pattern:
```python
from ._feature_stage import ReleaseCandidateFeature, release_candidate
@release_candidate(feature_id=ReleaseCandidateFeature.MY_FEATURE)
class MyFeature:
...
```
Behavior:
- Adds a release-candidate note to the docstring
- Records feature metadata on the decorated object
- Does **not** emit the experimental warning
Enum setup:
- Add an all-caps member to `ReleaseCandidateFeature`
### 3. Released
Use for stable GA APIs.
Code pattern:
- **No feature-stage decorator**
- **No entry** in `ExperimentalFeature`
- **No entry** in `ReleaseCandidateFeature`
If a feature is fully released, remove any stage-specific feature annotation.
### 4. Deprecated
Use for APIs that still exist but should not be used for new code.
Code pattern:
```python
import sys
if sys.version_info >= (3, 13):
from warnings import deprecated # type: ignore # pragma: no cover
else:
from typing_extensions import deprecated # type: ignore # pragma: no cover
@deprecated("MyOldFeature is deprecated. Use MyNewFeature instead.")
class MyOldFeature:
...
```
Behavior:
- Uses the repository's version-conditional deprecation import pattern
- Should describe what to use instead
Deprecated APIs should not also carry feature-stage decorators.
## Expected decorators by stage
| Feature stage | Expected annotation |
| --- | --- |
| Experimental | `@experimental(feature_id=ExperimentalFeature.X)` |
| Release candidate | `@release_candidate(feature_id=ReleaseCandidateFeature.X)` |
| Released | No feature-stage decorator |
| Deprecated | `@deprecated("...")` |
## Feature enums
The feature enums are the inventory of currently staged features:
- `ExperimentalFeature`
- `ReleaseCandidateFeature`
Guidance:
- Use one enum member per conceptual feature, not per class
- Ideally, an ADR already defines the overall feature boundary and therefore the feature ID that staged APIs for that feature should reuse
- Keep feature IDs all caps
- Reuse the same member across related APIs for the same feature
- Remove enum members when the feature no longer belongs to that stage
- Treat these enums as **current-stage inventories**, not as a stable consumer introspection API
Minimal consumer guidance:
- Treat `__feature_stage__` and `__feature_id__` as optional staged metadata, not as stable contracts
- Use `getattr(obj, "__feature_stage__", None)` and `getattr(obj, "__feature_id__", None)` rather than direct attribute access
- Treat missing metadata as "no explicit feature-stage annotation"
- For warning filters while a feature is staged, match the literal feature ID string
- Do **not** rely on `ExperimentalFeature.X`, `ReleaseCandidateFeature.X`, or the continued presence of `__feature_id__` after a feature moves stages or is released
For consumers, the enums are also re-exported from `agent_framework`.
For internal implementation code inside `agent_framework`, continue to import the enums and decorators from `._feature_stage`.
## Package stage vs feature stage
Use the following rules:
### Package is experimental / beta
- All public APIs are experimental by default
- Do **not** add feature-stage decorators just to restate that
- Only introduce feature-level annotations later if the package advances first
### Package is release candidate
- All public APIs are RC by default
- Do **not** decorate everything
- Add `@experimental(...)` only for features that are intentionally still behind the package
### Package is released / GA
- All public APIs are released by default
- Add `@experimental(...)` or `@release_candidate(...)` only for features still being held back
## Moving a feature from one stage to the next
### Experimental -> Release candidate
1. Move the feature ID from `ExperimentalFeature` to `ReleaseCandidateFeature`
2. Replace `@experimental(...)` with `@release_candidate(...)`
3. Update any tests or docs that mention the old stage
### Experimental -> Released
1. Remove `@experimental(...)`
2. Remove the feature from `ExperimentalFeature`
3. Do not add a replacement feature-stage decorator
### Release candidate -> Released
1. Remove `@release_candidate(...)`
2. Remove the feature from `ReleaseCandidateFeature`
3. Leave the API undecorated
### Any stage -> Deprecated
1. Remove any feature-stage decorator
2. Remove the feature from the stage enum
3. Add `@deprecated("...")`
4. Update docs/tests to reflect the replacement path
## Promotion guidance
Features do **not** have to pass through every stage.
- It is usually a good idea to move features in order when that reflects reality
- But it is completely acceptable to go **experimental -> released**
- Do **not** force a feature through release candidate if there is no real RC period
Likewise, when a package advances, do not automatically move every feature with it.
- Promote features based on actual readiness
- Keep lagging features explicitly marked only when they are behind the package default
## Practical rules of thumb
- **Package default first, feature exceptions second**
- **Do not decorate everything in preview packages**
- **Do not double-annotate members of an already-staged class**
- **Use enums only for currently staged features**
- **Do not treat stage enums as a compatibility contract**
- **Treat `__feature_stage__` and `__feature_id__` as optional metadata; use `getattr`**
- **Remove stage annotations once a feature is released or deprecated**