agentleFS
Microsoft AzureKnown publisherAzure/azure-functions-python-extensions/.github/skills/add-agent-binding/SKILL.md

add-agent-binding

Skill7 starsChanged 16 days agoMITView on GitHub

Add a new SDK-backed Agent binding provider to azure-functions-python-extensions. USE WHEN: integrating another agent SDK, creating an AgentFunctionApp and markdown_agent decorator, injecting an SDK Agent/session type, forwarding SDK creation arguments, implementing provider lifecycle or capability adapters, adding Durable support, samples, tests, packaging, or CI wiring. NOT FOR: adding features to an existing provider without a new binding package, Connector SDK types (use add-connector-type), or application-only Agent samples.

  • Installs packages

The file

Show its rules and text

We split this skill into the rules it gives an agent

39 rules. About 0 are common, 26 are distinctive, and 13 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈26

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 13

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Use this workflow to add an SDK-backed provider package built on azurefunctions-agents-extensions-base.

    line 8
  2. Distinctive:

    Do not copy a lifecycle merely because it exists.

    line 26
  3. Distinctive:

    Before editing, identify and record these decisions in the working notes or PR:

    line 31
  4. Distinctive:

    If one of these is unknown, inspect the installed SDK signatures and lifecycle documentation or run a focused probe before implementing it.

    line 44
  5. Very specific:

    Use the provider ID {provider_id} and normalized package suffix {provider-id}:

    line 49
  6. Very specific:

    Add another module such as options.py only when it provides meaningful public typing or separates substantial logic.

    line 67
  7. Distinctive:

    Start with the intended Function code.

    line 75
  8. Distinctive:

    Use the SDK's names and types where practical.

    line 96
  9. Very specific:

    Add mcp, durable, and dev extras only when applicable.

    line 105
  10. Distinctive:

    Keep compilation side-effect-free.

    line 138
  11. Very specific:

    Use AsyncExitStack when several SDK, MCP, credential, or session contexts must close together.

    line 145
  12. Distinctive:

    Create fresh invocation state unless the SDK explicitly requires a shared process owner.

    line 147
  13. Distinctive:

    If a client is cached, make first creation thread-safe, validate its concrete type, share it only at the intended app/binding scope, and …

    line 149
  14. Distinctive:

    Do not close a cached process client after every invocation.

    line 152
  15. Distinctive:

    Document shutdown limitations if the host does not provide an awaited application-lifetime hook.

    line 153
  16. Very specific:

    Keep arg_name and agent_name extension-owned.

    line 178
  17. Distinctive:

    Do not silently drop unknown options.

    line 187
  18. Distinctive:

    Test representative direct arguments, extension-default overrides, and an unsupported argument.

    line 188
  19. Distinctive:

    If an SDK argument collides with an extension-owned name, document and test the chosen mapping.

    line 191
  20. Very specific:

    Keep app_root app-scoped; never expose it as a per-binding option.

    line 201
  21. Very specific:

    Put app-wide defaults on AgentFunctionApp(...).

    line 202
  22. Distinctive:

    Do not rediscover or reinterpret app files in the provider.

    line 220
  23. Distinctive:

    Keep credentials and tokens out of logs and persisted config.

    line 230
  24. Very specific:

    If the SDK cannot preserve a capability contract, omit it from supported_capabilities.

    line 234
  25. Very specific:

    Add the base package's durable extra.

    line 241
  26. Very specific:

    Add orchestration_trigger(...) to the provider app.

    line 243
  27. Very specific:

    Ensure run_agent() uses the same compiled instructions, options, and capabilities as direct injection.

    line 244
  28. Distinctive:

    Keep SDK calls in the generated activity, never in deterministic orchestrator code.

    line 246
  29. Distinctive:

    Test hidden activity registration, serialization, execution, failures, and missing parameter annotations.

    line 248
  30. Distinctive:

    Add a Durable sample with an HTTP starter, orchestrator, and Agent activity.

    line 251
  31. Distinctive:

    Run it with Core Tools and poll a real instance to a terminal state.

    line 252
  32. Distinctive:

    Use the extension's defaults in the primary sample.

    line 266
  33. Distinctive:

    When the SDK setup is non-trivial, add a standalone direct-SDK script that performs the same task.

    line 269
  34. Very specific:

    Add sample-indexing tests that import function_app.py in a subprocess and assert the exact Function names without making live SDK calls.

    line 274
  35. Distinctive:

    Use SDK fakes for focused unit tests.

    line 310
  36. Distinctive:

    Add the package to all relevant matrices and jobs, not just the PR build:

    line 315
  37. Distinctive:

    Run from the new package directory using its supported Python versions:

    line 326
  38. Distinctive:

    Keep the PR summary customer-facing and concise.

    line 346
  39. Distinctive:

    Do not lead with internal typing helpers or implementation-only classes.

    line 355

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Add an Agent Binding

Use this workflow to add an SDK-backed provider package built on azurefunctions-agents-extensions-base.

The binding should let customers author a normal Azure Function while receiving the SDK's native Agent or session type as an injected parameter. Keep SDK setup, configuration discovery, lifecycle management, and Durable execution inside the extension.

Reference Implementations

Choose the nearest existing provider before writing code:

ProviderUse as a reference when
azurefunctions-agents-extensions-agent-frameworkThe SDK creates a fresh client and Agent for each invocation.
azurefunctions-agents-extensions-copilot-sdkThe SDK has a process-owning client that should be cached while sessions remain invocation-scoped.
azurefunctions-agents-extensions-baseThe provider contract, discovery model, binding injection, and Durable bridge need clarification.

Do not copy a lifecycle merely because it exists. Determine what the target SDK owns and how each object is safely closed.

Required Decisions

Before editing, identify and record these decisions in the working notes or PR:

DecisionQuestions to answer
Injected SDK typeWhat native SDK type should the Function parameter receive? Is it async-context-managed?
Client lifecycleIs the client cheap and invocation-scoped, or a process/runtime owner that must be cached? Is construction thread-safe?
Invocation lifecycleWhat fresh Agent/session/context is created per invocation, and how is it closed on success, failure, and cancellation?
Execution APIHow does the native object execute a prompt, and how is the final text validated and returned from run_agent()?
ConfigurationWhich settings have secure defaults? Which require explicit environment variables or factories?
SDK argumentsWhich create_* arguments should customers pass directly on @app.markdown_agent(...)? Which names are reserved by the extension?
CapabilitiesCan the SDK support discovered Skills and remote HTTP MCP without changing their meaning or security?
DurableCan a compiled binding execute from the shared Durable activity without relying on a live injected object?

If one of these is unknown, inspect the installed SDK signatures and lifecycle documentation or run a focused probe before implementing it.

Package Shape

Use the provider ID {provider_id} and normalized package suffix {provider-id}:

azurefunctions-agents-extensions-{provider-id}/
├── LICENSE
├── MANIFEST.in
├── README.md
├── pyproject.toml
├── azurefunctions/agents/extensions/{provider_module}/
│   ├── __init__.py
│   ├── apps.py
│   ├── provider.py
│   └── py.typed
├── samples/
└── tests/

Add another module such as options.py only when it provides meaningful public typing or separates substantial logic. Do not create abstractions solely to match another provider's file count.

Procedure

1. Define the Public Customer Experience

Start with the intended Function code. The injected parameter should use the SDK-native type:

from target_sdk import NativeAgent
from azurefunctions.agents.extensions.target import AgentFunctionApp

app = AgentFunctionApp()


@app.route(route="orders", methods=["POST"])
@app.markdown_agent(
    arg_name="agent",
    agent_name="orders",
    streaming=True,
    reasoning_effort="high",
)
async def process_order(req: func.HttpRequest, agent: NativeAgent) -> str:
    ...

Use the SDK's names and types where practical. Avoid inventing an extension-only configuration vocabulary for arguments the SDK already exposes.

2. Register the Provider Package

In pyproject.toml:

  • Name the distribution azurefunctions-agents-extensions-{provider-id}.
  • Depend on azurefunctions-agents-extensions-base and the target SDK.
  • Add mcp, durable, and dev extras only when applicable.
  • Include py.typed as package data.
  • Register a zero-argument provider factory:
[project.entry-points."azurefunctions.agents.extensions.providers"]
{provider_id} = "azurefunctions.agents.extensions.{provider_module}.provider:create_provider"

The entry-point name, provider_id, and normalized distribution name must agree.

3. Implement the Provider Contract

In provider.py:

  1. Define a stable provider ID constant.
  2. Implement an immutable compiled binding that satisfies CompiledAgent:
    • open_agent(invocation) returns an async context manager yielding the injected SDK-native object.
    • run_agent(prompt, invocation) opens the same object, executes one prompt, validates the SDK response, and returns text for Durable activities.
  3. Implement AgentProvider with:
    • provider_id
    • distribution_name
    • supported_capabilities
    • compile_binding(...)
  4. Validate the injected parameter annotation when one is present. Durable compilation may provide inspect.Signature.empty, which must remain valid.
  5. Validate factories synchronously and reject awaitable results with a clear error rather than leaking an unawaited coroutine.
  6. Reject unsupported provider options with a sorted, provider-specific error.
  7. Return a zero-argument create_provider() factory.

Keep compilation side-effect-free. It should validate and freeze a recipe, not connect to networks or create invocation-scoped SDK objects.

4. Design and Test Lifecycle Ownership

Implement the lifecycle established in the Required Decisions section:

  • Use AsyncExitStack when several SDK, MCP, credential, or session contexts must close together.
  • Create fresh invocation state unless the SDK explicitly requires a shared process owner.
  • If a client is cached, make first creation thread-safe, validate its concrete type, share it only at the intended app/binding scope, and keep sessions fresh.
  • Close invocation objects on normal return, exception, and cancellation.
  • Do not close a cached process client after every invocation.
  • Document shutdown limitations if the host does not provide an awaited application-lifetime hook.

At minimum, tests must prove object counts, identity, entered/exited state, concurrent first use when caching, and failure/cancellation cleanup.

5. Expose SDK Creation Arguments Directly

The decorator should accept the arguments customers would pass to the target SDK's Agent/session creation method:

def markdown_agent(
    self,
    *,
    arg_name: str,
    agent_name: str,
    client_factory: ClientFactory | None = None,
    **sdk_options: Unpack[SdkCreationOptions],
) -> Callable[[_F], _F]:
    ...

Follow these rules:

  • Keep arg_name and agent_name extension-owned.
  • Reserve only options the extension must interpret, such as client_factory, provider credentials, or custom tools.
  • Forward all other supported SDK options unchanged to the SDK creation call.
  • Apply customer values after extension defaults so explicit values win.
  • Provide a TypedDict plus Unpack when the SDK does not publish a reusable typed kwargs object. Match the installed SDK signature exactly.
  • At runtime, compare option names with inspect.signature(...) or an equally authoritative SDK surface and fail with a clear list of unsupported names.
  • Do not silently drop unknown options.
  • Test representative direct arguments, extension-default overrides, and an unsupported argument.

If an SDK argument collides with an extension-owned name, document and test the chosen mapping. Prefer a top-level decorator argument when it is central to the binding, while still passing the corresponding value to the SDK.

6. Add AgentFunctionApp

In apps.py:

  • Subclass azure.functions.FunctionApp through a provider-specific mixin.
  • Configure the provider once with configure_app(...).
  • Keep app_root app-scoped; never expose it as a per-binding option.
  • Put app-wide defaults on AgentFunctionApp(...).
  • Let decorator values override app defaults for only that binding.
  • Delegate orchestration_trigger(...) to durable_orchestration_trigger(...) when Durable is supported.
  • Export only intentional public symbols from __init__.py.

The provider package owns this app class. Do not require changes to azure.functions.FunctionApp for a provider-specific API.

7. Translate Discovered Capabilities

Declare only capabilities the provider actually implements. Current neutral capabilities are skills and mcp.

For Skills:

  • Consume SkillDefinition paths supplied by the base package.
  • Let the SDK load content when that preserves its native behavior.
  • Do not rediscover or reinterpret app files in the provider.

For MCP:

  • Consume MCPServerDefinition values from the base package.
  • Preserve server names, remote HTTP URLs, tool allowlists, headers, and Entra authentication semantics.
  • Expand $VAR and %VAR% references at invocation time and report all missing names clearly.
  • Require HTTPS when credentials or headers leave loopback.
  • Keep credentials and tokens out of logs and persisted config.
  • Acquire and close credentials at the narrowest safe scope; refresh tokens per request when the SDK transport permits it.

If the SDK cannot preserve a capability contract, omit it from supported_capabilities; the base package will reject the configuration.

8. Add Durable Support

When supported:

  • Add the base package's durable extra.
  • Export DurableAgentContext.
  • Add orchestration_trigger(...) to the provider app.
  • Ensure run_agent() uses the same compiled instructions, options, and capabilities as direct injection.
  • Keep SDK calls in the generated activity, never in deterministic orchestrator code.
  • Test hidden activity registration, serialization, execution, failures, and missing parameter annotations.

Add a Durable sample with an HTTP starter, orchestrator, and Agent activity. Run it with Core Tools and poll a real instance to a terminal state.

9. Add Samples That Demonstrate the Value

Provide a minimal non-Durable sample containing:

  • function_app.py
  • host.json
  • local.settings.template.json with placeholders, never secrets
  • requirements.txt
  • at least one raw .agent.md file
  • Skill and mcp.json assets when those capabilities are supported
  • a concise README with runtime provisioning and local-run steps

Use the extension's defaults in the primary sample. Do not make customers recreate the client factory merely to run the happy path.

When the SDK setup is non-trivial, add a standalone direct-SDK script that performs the same task. It should honestly show the authentication, client and session lifecycle, instruction loading, capability wiring, option defaults, and response validation that the binding removes.

Add sample-indexing tests that import function_app.py in a subprocess and assert the exact Function names without making live SDK calls.

10. Document the Binding

The package README must state:

  • The SDK-native type injected by the binding.
  • What client is created by default and which settings it reads.
  • Which object is cached and which object is fresh per invocation.
  • That @app.markdown_agent(...) accepts SDK creation arguments directly, with a short example such as streaming=True and reasoning_effort="high".
  • Reserved extension arguments and app-wide defaults.
  • Permission/security defaults and when customers must replace them.
  • Skill, MCP, Durable, authentication, and runtime-provisioning behavior.
  • How to supply an advanced custom client factory.

Favor customer behavior over internal helper type names.

11. Add Tests

Mirror the provider modules under tests/. Cover:

  • Public imports and package version.
  • Provider entry-point discovery and metadata.
  • Correct annotation acceptance and rejection.
  • Required, defaulted, malformed, and unknown options.
  • Direct forwarding of SDK creation arguments.
  • App defaults and per-binding overrides.
  • Native response extraction and malformed responses.
  • Client and invocation lifecycle, including cancellation and concurrency.
  • Skills and MCP mapping, allowlists, environment expansion, HTTPS rules, authentication, and cleanup.
  • Durable compilation and activity execution when supported.
  • Sample indexing and standard setting names.

Use SDK fakes for focused unit tests. Add a real local smoke test for the primary sample and Durable workflow when credentials and emulators are available.

12. Wire Build and Release Pipelines

Add the package to all relevant matrices and jobs, not just the PR build:

  • eng/templates/jobs/build.yml
  • eng/templates/official/jobs/build-artifacts.yml
  • eng/templates/official/jobs/unit-tests.yml

Install the editable base package before provider tests. Build both the source distribution and wheel, and ensure package imports work from the built artifact.

Validation

Run from the new package directory using its supported Python versions:

python -m pip install -U -e .[dev]
python -m flake8 azurefunctions tests samples
python -m mypy azurefunctions
python -m pytest -q --instafail tests
python -m build
python -m twine check dist/*

Adjust paths only to match established package configuration. Also run:

  • Sample indexing tests.
  • A direct local invocation through Core Tools.
  • A complete Durable orchestration when Durable is supported.
  • git diff --check over the changed files.

PR Description

Keep the PR summary customer-facing and concise. Include:

  1. The package and SDK being integrated.
  2. The native SDK type injected into Functions.
  3. The default client and client/session lifecycle.
  4. That SDK creation arguments can be passed directly to @app.markdown_agent(...), with one short code example.
  5. Supported Skills, MCP, Durable, samples, and validation.

Do not lead with internal typing helpers or implementation-only classes.

Definition of Done

  • Provider package and entry point are discoverable.
  • SDK-native injection works with a fresh invocation context.
  • Client ownership and cleanup behavior are explicit and tested.
  • SDK creation arguments are directly usable and unknown names fail clearly.
  • Supported capabilities preserve the base package contracts.
  • Direct and Durable execution share one compiled binding behavior.
  • Samples show both the simple binding path and, when useful, direct SDK setup.
  • README, tests, build, official artifacts, and official test pipelines are updated.
  • Focused checks and relevant local end-to-end workflows pass.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.