This guide covers running, configuring, and operating IssueLens. For a project overview, see the README. To work on the code or run offline tests without service credentials, see CONTRIBUTING.md.
- Running locally
- Environment variables
- Requests, chat, and media inputs
- Repository configuration and built-in commands
- GitHub Actions automation
- Team memory and post-merge automation
- Foundry model authentication
- Deployment
- Architecture and protocols, observability, and troubleshooting
Commands below assume you are in the repository root. Live requests can perform authorized GitHub writes; use a test repository and review the requested scope. Deployment requires explicit approval and is separate from local development.
Both protocols run in the same process and share the same orchestrator, skills, four sub-agents, and bundled GitHub App MCP reads. Wiki writes use a separate, parent-configured MCP server local to the maintenance agent.
The team-memory agent makes minimal, evidence-backed Markdown wiki updates
through write_wiki_pages. The shared team-memory skill remains read-only and
is preloaded on every agent, including the orchestrator. Agents retrieve relevant
knowledge directly without delegating ordinary reads to the maintenance agent.
Both paths first call issuelens-config with the explicit source project and
domain="team_memory". Read its returned wiki_repository for the validated
destination and content for organization and topics. For example, to store
microsoft/project memory in microsoft/team-knowledge.wiki.git, configure the
source project's .github/issuelens.yml:
version: 1
instructions:
team_memory:
path: .github/issuelens/team-memory.md
wiki_repository: microsoft/team-knowledgeThe policy path remains required when the domain is present. Optional
wiki_repository must pass GitHub parent repository identifier validation as
owner/repository; it is not a wiki UI name, .wiki.git URL, or arbitrary Git
remote. The shared package policy parser returns its resolved value alongside
content in the config-tool response. Markdown supplies only organization,
navigation, topics, inclusion/exclusion, and evidence guidance; it cannot override
the target or supply arbitrary Git URLs, tokens, or shell settings.
All wiki read/write MCP calls in this example still use
repository="microsoft/project", not microsoft/team-knowledge. Every tool
independently re-reads and validates the same mapping, then resolves credentials
and Git transport to the destination. See the policy example and
IssueLens's own customization. An absent
field, config, or domain defaults to the source project's own wiki. Invalid
configuration or an inaccessible target stops memory access without silent
source-wiki fallback. Other work may continue with authorized source evidence
while reporting that memory was unavailable.
Validated team_memory.wiki_repository is a narrow scope exception selecting
only the wiki capability's destination. It grants no other writes, additional
source repositories, or notification scope. Never publish private/internal-source
knowledge to a public wiki or read a private/internal wiki for public-source context.
Cross-repository mappings between private/internal repositories are rejected
for both reads and writes because their audience relationship cannot be
verified; use the source project's own wiki. Same-repository and public-to-public
mappings remain supported. A private/internal source may read a public wiki,
and a public source may write public information to a private/internal wiki,
subject to job authorization and destination App access. Source-user authorization is
separate from App installation access.
Writes require an explicit current request or parent handoff authorizing a wiki
update for the source project and its mapped wiki, regardless of origin.
Existing issue-loop commands and repository policy alone do not authorize them. Sensitive, conflicting,
destructive, or unsupported changes need ordinary human interaction.
Only the team-memory agent-local server exposes write_wiki_pages; the parent
supplies its internal --wiki-writer launch mode automatically. Users need no
environment flag or per-repository App environment configuration. Shared
reader/triage servers do not expose the writer. The existing
GITHUB_MCP_ENABLE_WRITES gate remains for triage issue writes, not wiki writes.
An initialized destination wiki is required. The App must be installed
at the actual destination with Contents: read for wiki reads and Contents:
write for maintenance; source installation alone is insufficient. Tokens are
scoped to that destination and the operation's required permission.
Wiki Git network and object operations use the Dulwich Python library (1.2.14),
not git.exe, the Git CLI, or a Git subprocess. The Foundry ZIP
codeConfiguration uses remote_build with runtime: python_3_13 and installs
the root requirements.txt; the standalone MCP github_app_mcp/pyproject.toml
declares the same Dulwich dependency. No Git installation, Dockerfile change,
or runtime installer is needed in either mode. IssueLens still launches its
stdio MCP server as a Python subprocess; the wiki backend never spawns Git,
SSH, or credential helpers.
Maintenance pins reads to one full wiki SHA and sends full UTF-8 page contents with both required preconditions from the same snapshot:
snapshot_response = get_wiki_snapshot(repository="microsoft/project")
if not snapshot_response["success"]:
raise RuntimeError(snapshot_response["error"]["message"])
read_snapshot = snapshot_response["result"]
write_response = write_wiki_pages(
repository="microsoft/project",
pages={"Architecture.md": full_utf8_content},
expected_wiki_repository=read_snapshot["wiki_repository"],
expected_base=read_snapshot["sha"],
message=short_summary,
)expected_wiki_repository is a precondition, never a destination override.
The writer validates the identifier and compares it case-insensitively with the
freshly resolved policy destination before destination metadata, token lookup,
or wiki access. Policy still selects the actual destination and scoped App
credentials; a mismatch is rejected even if the SHA is unchanged.
Pages cite evidence and the full source commit SHA, not an
abbreviation, where relevant; the short commit summary also includes that full
SHA. No force option is exposed. The MCP wiki backend
persists pages and history in an atomic Git commit. No knowledge change means no
write. Limits are 20 .md pages, 64 KiB each, 256 KiB total; deletion/rename are
unsupported. Unchanged assets are preserved byte-for-byte; diffs report binary
changes as notices, not binary patches. Only SHA-1 Git repositories (GitHub's
current format) are supported; SHA-256 repositories are rejected.
The writer makes at most one atomic publication attempt against expected_base;
it never rebases or retries internally. A stale base or competing update returns
a classified failure to the agent. The team-memory agent decides whether to
read fresh state, reconcile minimal edits while preserving concurrent changes,
and make a separate write, or stop for human direction. Matching desired content
may return no-change without publishing, but partial matches do not bypass the
base check. Conflicts require re-reading and regeneration; a lost response
requires comparing current content before retrying. Only tool-confirmed status
and wiki SHAs are reported. On a destination mismatch, read a fresh snapshot and
re-establish destination, authorization, and evidence; never overwrite
automatically, reuse edits for another wiki, or merely replace the expected
repository to retry. See MCP details.
Tool design: every repository-owned MCP and host tool returns
{success, outcome, result, error}. Read the domain payload from result;
failures include error.type, an agent-facing error.message, a static
error.telemetry_message, and error.http_status (null when unknown).
Outcomes distinguish completed, not_applied, and
unknown; native MCP/Copilot error flags agree with the envelope. Tools perform
one logical operation, not recovery workflows. Authorization, validation, and
bounded prerequisite reads remain internal; recovery decisions belong to the
owning agent. Unknown write outcomes require state inspection rather than blind
retry. This applies to GitHub,
repository configuration, email, and Teams tools. External tools retain their
own documented contracts. Telemetry records allowlisted classifications and
known HTTP statuses plus content-free static messages from
IssueLens-owned tool failures. It exports only the explicit static
error.telemetry_message, not agent-facing error.message, raw exception
objects, tool arguments/results, credentials, or unstructured SDK exception
strings.
Integration scope: this simplifies direct maintenance, without a standalone host publisher or database/proposal/approval persistence. The opt-in team-memory coordinator validates merged PRs and submits queued wiki-maintenance jobs. There is no durable job ledger, reconciliation service, or guaranteed exactly-once delivery. Git is knowledge, history, and conflict detection, not an external workflow scheduler. These local contracts do not establish live hosted sub-agent dispatch or deployment.
- Add a caller workflow on the source repository's default branch using the
reusable IssueLens action with
request-type: team-memory. The guide includes a complete example pinned to a reviewed action commit; external consumers need no checkout. It uses default-branch pushes, including pushes that land fork PRs, so the trusted base workflow has endpoint credentials without apull_request_targetpolicy exception. Set thepush.branchesfilter to the source repository's default branch (mainin this repository). IssueLens's own push workflow calls only an immutable remotequeue-team-memoryaction, with no checkout or preparation script. The coordinator loads and calls onlyissuelens, usinggithub.workflow_shawith credentials not persisted. Neither path executes PR-head code. Protect workflow and action changes as privileged code. - Reuse the issue-loop Actions secrets:
AZURE_CLIENT_ID,AZURE_TENANT_ID,AZURE_SUBSCRIPTION_ID,ISSUELENS_AGENT_URL(the complete Foundry invocations endpoint), andISSUELENS_AGENT_SCOPE. The old skeleton'sISSUELENS_AGENT_ENDPOINTis not used. Configure Azure OIDC federation for this repository and the actual workflow event/ref subject, including manual dispatch if used. In IssueLens's two-workflow pilot, only the coordinator uses Azure OIDC, under its default-branchworkflow_dispatchidentity; also verify any workflow-specific federation conditions. The dispatcher needs only repository-scopedGITHUB_TOKENActions write access. No new cross-repository credential is required for this same-repository pilot. Migrating frompull_request_targetcan change the OIDC subject; verify the actual default-branch subject, including immutable repository/owner IDs where enabled. Do not assume the old PR-scoped credential coverspush, or broaden federation to untrusted refs. The identity needs permission to invoke the existing Foundry agent, not deployment or GitHub wiki-write credentials. No App private key is stored here. - Use a deployed agent with wiki-maintenance support and source-independent
request handling, configure optional
instructions.team_memorypolicy, initialize the chosen wiki, and confirm destination App Contents read/write access. The workflow neither initializes the wiki nor changes permissions or agent deployments. - Set the repository Actions variable
ISSUELENS_TEAM_MEMORY_ENABLED=truewhen ready. This opts into automatic maintenance; it is not an agent environment flag or a wiki-destination allowlist. Without it, the job skips before acquiring Azure credentials.
Each eligible push produces one invocation containing its verified merged PRs, not one invocation per PR. The preflight checks the complete fast-forward commit inventory and discovers associated PRs through bounded metadata-only GitHub queries. It does not send raw combined diffs to the model. Direct pushes without newly merged PRs skip before Azure login. Missing/truncated inventories, force pushes, and ambiguous discovery fail without submitting a partial source list; use manual PR dispatch to recover. This batches PRs within one push, not across separate pushes, so an ordinary one-PR merge still usually produces one run.
IssueLens coordinator pilot: the
push-triggered workflow no
longer calls Foundry. It calls the reusable
queue-team-memory action, which
is a standalone generic target dispatcher. The source workflow rejects unsafe
push flags and mismatched workflow/head SHAs, then supplies only
source_repository, source_run_id, source_run_attempt, push_before, and
push_after as string values. There is no source checkout, script, artifact
upload, or download. The dispatcher uses only a target token and these JSON inputs
to dispatch the
coordinator workflow.
No source code, commit messages, issue/PR bodies, agent output, or credentials
are included.
The coordinator keeps the queue but only invokes the shared issuelens action
after trusted sparse checkout. Before Azure login, that action authenticates
the allowed source and exact default-branch push workflow/run/attempt, matches
push_after to its head, verifies current default-branch ancestry, and retrieves
the full fast-forward requested range in pages of 100 commits (at most 1,000).
It then discovers all eligible merged PRs, rejecting incomplete inventories,
duplicates, identity mismatches, divergent ranges, or a ref race. Source and
coordinator heads remain distinct; a newer coordinator head never replaces
the requested range.
Range trust: this authorizes reconciliation of a selected ancestor range,
not attestation of the original push event. The source run API cannot prove
original before or push flags. push_before is explicitly a requested
boundary, and this limitation is included in the agent task. No original-event
snapshot, artifact digest, or seven-day lifetime is claimed.
Both automatic requests and manual PR updates execute in the coordinator
repository under issuelens-team-memory-wiki-microsoft-IssueLens, using
queue: max and cancel-in-progress: false. There is one active coordinator
run through invocation and result validation, plus at most 100 pending runs.
An accepted dispatch is not a completed maintenance job; inspect the
coordinator's outcome. Monitor queue overflow and failed/canceled runs rather
than assuming guaranteed delivery. The queue wait happens before an agent
invocation, outside its 15-minute response-read budget.
This rollout accepts only microsoft/IssueLens sources. Its request requires
the agent to verify the validated wiki destination matches microsoft/IssueLens
and stop without writing if not; the action rejects receipts naming another
wiki. The mapping in .github/issuelens.yml is unchanged. Other
source repositories and caller-selected wiki destinations are not accepted.
The wrapper executes in the source workflow; only the dispatched coordinator
workflow owns the central concurrency queue.
Java tooling rollout is separate: those repositories share the
microsoft/vscode-java-pack wiki and will need their own central queue and
trusted source configuration. The existing direct-action consumers continue
working unchanged, but are not serialized by this pilot.
The standalone dispatch action supports coordinator-repository (default
calling repository), required coordinator-workflow (YAML basename),
coordinator-ref (default calling ref name), and caller-owned workflow-inputs
JSON. It independently validates target identities using dispatch-token and
never prepares artifacts or synthesizes source identity fields.
The coordinator branch is independent of the source default branch.
See the queue action contract
for the exact transport and reconciliation request contracts.
Other coordinators can configure a reviewed source-repositories JSON map of
canonical source names to immutable numeric IDs, with independent
source-github-token Contents, Actions, and Pull requests read access.
Never forward dispatched inputs into that map. Cross-repository sources and
coordinators must both be public; the IssueLens workflow's empty map continues
to accept only its own source. Coordinated receipts must name the coordinator's
wiki, verified against source policy rather than overriding it.
Receivers still own source allowlists, range validation, privacy, wiki scope, and maintenance outcomes;
this transport interface does not enable other workflows or provide credentials.
This deliberately replaces the coordinated artifact input with before/after
inputs and removes the obsolete preparation/download entrypoints; the generic
dispatch interface is unchanged. Java Pack remains pinned to the older revision
and needs a separate migration to the range request/shared invocation path;
this refactor does not alter it.
Only PRs merged into the current default branch are accepted. Manual Run
workflow now uses the coordinator, with a positive pull_request_number
instead of source identifiers, and the default branch;
it revalidates the same merged PR instead of inventing a merge event. GitHub
repository and PR metadata are re-read before Azure login. PR titles, bodies,
comments, and fork source are not embedded in the trusted task. The authenticated
request authorizes only minimal wiki maintenance; the agent independently
rechecks the merge and treats retrieved content as untrusted evidence. Endpoint
authentication alone does not cryptographically attest the JSON metadata.
Issue-loop and the team-memory coordinator use one composite action with three
request adapters:
issue-loop, team-memory, and task. The selected adapter prepares and
validates the request before shared Azure OIDC login and invocation. The helper is
a normal, directly testable Python module; no Python is embedded in the YAML.
The action is one client of the same agent used by chat and other callers.
It supplies its origin, merge evidence, validation constraints, no-reaction
instruction, and requested response format in input. Its JSON result schema
belongs to this caller, not to the shared agent prompts or knowledge policy.
Other callers may request different formats or updates unrelated to a merge;
the same authorization, evidence, and wiki preconditions still apply. Shared
instructions use explicit request context rather than assuming a workflow,
Teams conversation, or other delivery surface. Changes to those shared
instructions require normal deployment approval; changing caller metadata or
formatting does not introduce a new agent protocol.
The job has a 30-minute timeout, providing setup and discovery headroom around
the bounded streamed response. It submits once
without following redirects or automatically retrying a write-capable request.
It requires an SSE completion event and a final structured result matching the
submitted repository and source revisions. Push results must account for every
submitted PR exactly once. The agent may publish an independent, fully verified
subset in one atomic wiki update, while deferring incomplete or dependent changes.
Mixed completed/incomplete outcomes are partial: the job fails for attention,
but preserves the per-PR response and any confirmed wiki SHA rather than claiming
that nothing was written. Only all-complete updated or no-change results
with a verified wiki repository and full SHA succeed. The Actions summary records
source identities and per-PR outcomes as tables; the default hybrid log streams sanitized agent text
and compact tool activity, without raw event JSON or tool payloads. Publication
is configurable through output-mode and summary-mode; use activity/status
or quiet/none when the log audience should not see agent text.
needs-review, failures, invalid results, and incomplete streams fail the job.
GitHub readers can reject oversized metadata or patches. For change evidence,
the agent uses small file pages and targeted source reads instead of repeating
the same oversized request. Unsupported content and missing evidence remain
explicit limitations, never unverified maintenance success.
The batch request identifies complete range/inventory validation as preflight's
responsibility; it does not require the agent to repeat it with the unpaginated
compare_commits tool. That patch-bearing response can exceed its 128-KiB limit
even for one merge. The agent revalidates each listed PR and uses small file pages
and targeted pinned-source reads instead. A needs-review receipt from that
optional bulk call is not an Actions-variable or Azure-login failure. After
merging a caller-request fix, start a new authorized manual coordinator run for
the affected PR; rerunning the old run retains its original code revision.
IssueLens's automatic and manual maintenance runs share one queued concurrency group. Other repositories using the direct-action example retain independent per-push groups and can overlap; neither reusable workflows nor matching group names in different repositories create a shared queue. This pilot does not serialize interactive chat or issue-loop work. Queue order is based on when runs start waiting, not source merge order: the agent uses pinned evidence, current knowledge, and wiki compare-and-swap to avoid overwriting newer edits. Cancellation or a client timeout does not prove the hosted invocation stopped; a workflow slot is not a distributed agent lock. The bounded queue and seven-day metadata artifacts do not provide durable recovery or exactly-once execution. After an ambiguous failure or partial publication, inspect the mapped wiki/history and per-PR receipt before rerunning or manually dispatching deferred PRs. Replays compare current content and skip unchanged knowledge; run IDs are not proof of publication. Sensitive/conflicting changes require human review.
- Receives a JSON task. The payload requires
input(the task, a free-form text prompt), with optional inlineattachments, e.g.{"input": "Triage open issues in owner/repo"}. - Creates a fresh Copilot session per request configured with:
- the Foundry model (BYOK via Microsoft Entra identity) or the GitHub Copilot model for inference;
- the bundled GitHub App stdio MCP server, whose process and token cache belong only to that Copilot session;
- the constrained in-process
issuelens-configtool, backed by a separate request-local read-only App client; - in-process notification tools when their Logic App endpoints are configured.
- The preselected
issuelensagent gets its global identity and orchestration rules fromagents/issuelens.md. It routes issue-level work totriage, critical-issue scans tofind-criticals, and planning work toplan. For trusted issue-loop events it re-reads current issue context and chooses initial triage, re-triage, initial planning, re-planning, or no action. Thetriagesub-agent runs thefind-duplicates,label-issue,assign-issue, andnotifyskills for requested follow-up actions. Theplansub-agent investigates a triaged issue, returns an action plan followed by a design specification, reports readiness, and waits for human direction. - Each Copilot
SessionEventis streamed back as an SSEdata:event; a finalevent: donemarks the end. Critical-issue scans end with a JSON report.
- Receives an OpenAI Responses request (Foundry playground, Teams, or any Responses client), including inline
input_imageandinput_filecontent. - Starts the bundled GitHub App stdio MCP server for the Copilot session. It
resolves the installation for each
owner/repository, mints tokens limited to that repository and the minimum required permissions, and never returns credentials to the model. - Uses a request-local read-only App client for issue-body images and the
constrained
issuelens-configtool. - Attaches the Foundry toolbox for non-GitHub capabilities such as notifications. The toolbox must not contain a GitHub MCP connection.
- Performs only the bundled issue-triage operations: repository/file reads,
issue and comment reads/searches, fixed-eyes activity acknowledgements, label
reads/additions, assignee updates, and explicitly requested issue comments.
Wiki retrieval is read-only; separately authorized maintenance uses only the
team-memoryagent-local writer. - Resumes the conversation's Copilot session each turn and streams the reply as Responses SSE events.
The triage, plan, and team-memory agents preload the change-analysis
skill. It guides normal Copilot tool/model turns using existing GitHub reads:
PR metadata, small list_pull_request_files pages, and targeted get_file
reads at verified commit SHAs. A size error prompts smaller pages, down to one
file per call, with pagination restarted when the page size changes.
get_commit now supports upstream-style detail="none" | "stats" | "full_patch"
and file pagination. The default is stats, without patches; callers that
need patches must explicitly request full_patch. Commit identity, parents,
and tree metadata remain available. Its transport allowance is separate from
the projected model-result limit, so discarding patches happens before the
model-result size check. No custom diff reader, analysis controller, or
additional SDK runtime is involved.
| Boundary | Default limit |
|---|---|
| REST HTTP response | 128 KiB; commit detail only: 1 MiB before projection |
| REST JSON result | 100,000 serialized bytes |
| SDK output-file threshold | 128 KiB, above the PR/commit result budget to avoid requiring local file access |
get_file text |
64 KiB |
| PR/commit file pagination | 1-100 files per page, at most 3,000 pages; GitHub's 3,000-file ceiling still applies |
Pages remain in the ordinary SDK conversation; this is not a hard total-context bound or a guarantee of exhaustive analysis. PR metadata is revalidated around paging because the file-list endpoint is not commit-pinned. A final commit of a multi-commit rebase merge does not cover the whole PR. Missing patches, API ceilings, unsupported files, and unresolved source questions remain explicit limitations. Wiki publication still requires its existing authorization, source verification, and destination preconditions. See the MCP read contracts for details.
Content-free run accounting is always on for both protocols through the existing Foundry/Application Insights pipeline. Content capture stays disabled. See the observability guide for configuration, privacy, measurement definitions and limitations, the Azure Monitor Workbook, and copyable KQL reports. Reports keep transport, execution, confirmed operations and telemetry coverage separate; missing usage is not zero, and HTTP success is not business success. Assets are checked offline; live ingestion/import validation and any deployment need separate authorization.
| Variable | Required | Description |
|---|---|---|
FOUNDRY_PROJECT_ENDPOINT |
For Foundry model | Azure AI Foundry project endpoint URL. Auto-injected when hosted — only needed locally |
AZURE_AI_MODEL_DEPLOYMENT_NAME |
For Foundry model | Model deployment name (e.g. gpt-4o) |
GITHUB_TOKEN |
For Copilot model | GitHub fine-grained PAT with Copilot Requests → Read-only permission |
Setting FOUNDRY_PROJECT_ENDPOINT selects Foundry exclusively and requires a
non-empty AZURE_AI_MODEL_DEPLOYMENT_NAME. Missing configuration or failed
authentication never falls back to GitHub. Foundry uses Microsoft Entra tokens,
not model API keys; see identity and migration guidance.
Configure the IssueLens GitHub App registration for both protocols. Every tool
names its target as owner/repository, and the server resolves the matching App
installation dynamically:
| Variable | Required | Description |
|---|---|---|
GITHUB_APP_ID |
Yes | Numeric GitHub App ID |
GITHUB_APP_PRIVATE_KEY_SECRET_URI |
Yes | Azure Key Vault secret URI containing the App PEM |
Store the PEM in Key Vault; never place it in .env, an azd environment, or a
deployment manifest. Grant the hosted agent's managed identity Key Vault
Secrets User, then configure only the App ID and secret URI:
# Run directly in your own terminal so the PEM never passes through chat.
az keyvault secret set --vault-name <vault> --name issuelens-github-app-key `
--file .secrets/issuelens.pem
azd env set GITHUB_APP_ID <app-id>
azd env set GITHUB_APP_PRIVATE_KEY_SECRET_URI `
"https://<vault>.vault.azure.net/secrets/issuelens-github-app-key"Target repositories and all repositories receiving writes must be included in an installation of the App. Bounded REST reads prefer App authentication but fall back to anonymous access for public repositories when no installation is available. Private repository reads still require an installation. Each Copilot session owns its stdio MCP processes. Each process caches tokens only in memory by repository and permission set, refreshes them five minutes before expiry, and discards them when the process exits. Configure the App with Metadata: Read, Issues: Read and write, Pull requests: Read and write, and Contents: Read. Wiki reads require destination App access; maintenance additionally requires Contents: Write for that actual destination. Tokens and the private key never enter model context.
A target repository may select capability-specific Markdown instructions with
one case-insensitive filename match for .github/issuelens.yml. The .github
directory and every configured instruction path use their exact repository
casing. See examples/issuelens.yml and validate files
against schemas/issuelens.schema.json.
version: 1
instructions:
criticality:
path: .github/issuelens/criticality.md
duplicate_detection:
path: .github/issuelens/duplicates.md
labeling:
path: .github/issuelens/labels.md
assignment:
path: .github/issuelens/assignment.md
notification_content:
path: .github/issuelens/notifications.md
planning:
path: .github/issuelens/planning.md
team_memory:
path: .github/issuelens/team-memory.md
wiki_repository: owner/project-knowledgeEvery instruction domain is optional:
| Domain | Repository-specific policy it may contain |
|---|---|
criticality |
Criticality criteria, thresholds, core functions, known workarounds, and priority presentation |
duplicate_detection |
Matching evidence, confidence thresholds, canonical issue conventions, exclusions, and related repositories for read-only candidate search |
labeling |
Existing-label mappings, priority rubric, and component classification |
assignment |
Area owners, keyword/path mappings, routing rules, and default owners |
notification_content |
Report title, grouping, emphasis, and presentation only |
planning |
Required planning sections, repository design expectations, readiness statuses, and human signals |
team_memory |
Required policy path for organization, topics, and evidence guidance; optional structured wiki_repository selects the GitHub wiki destination, never write authorization |
Target repositories do not need .github/issuelens.yml or any customization
Markdown files. When the config is absent, or when it omits a capability,
IssueLens uses that capability's legacy or built-in behavior. Only a present but
invalid config or an unreadable configured instruction stops the capability.
Within each sub-agent's role, behavior precedence is:
- Explicit instructions from the current user
- Validated capability customization from the target repository
- Built-in defaults
User instructions and customization may replace default workflows, criteria,
thresholds, mappings, readiness states, publication behavior, and response
presentation. They cannot change the owning sub-agent's role, required
parent-facing data contract, security or repository-scope boundaries, or write
authorization. Explicit user instructions win when they conflict with
customization within content guidance; they cannot override the structured wiki
destination. Validated team_memory.wiki_repository is the narrow wiki-only
scope exception described above, not access to additional source repositories,
other writes, or notifications. Omitting that field defaults to the source's
own wiki without making the domain's policy path optional.
The global IssueLens command language is outside this precedence and has no configuration domain. Target repositories cannot rename commands, add aliases, change command routing or authorization, or assign a meaning to a reserved command.
Planning instructions can replace the built-in readiness names and define how
explicit human signals move a proposal between states. They cannot authorize a
GitHub write or implementation. Without configured planning instructions,
IssueLens uses draft, needs-review, needs-clarification, blocked, and
approved. Even approved describes only the planning artifact.
IssueLens recognizes these immutable commands in the current Responses user turn or a validated GitHub issue comment:
| Command | Current behavior |
|---|---|
@issuelens triage |
Run initial issue triage through triage |
@issuelens retriage |
Re-run triage from current evidence through triage |
@issuelens plan |
Create initial planning artifacts through plan |
@issuelens replan |
Revise planning artifacts through plan |
@issuelens go |
Reserved for a future coding loop; currently no action or write |
A turn or comment may contain one command together with additional prose, for
example Verify whether the issue still needs work. @issuelens retriage.
IssueLens routes the command and passes the remaining text to its fixed owner as
scoped guidance. Inputs with multiple commands are rejected as ambiguous, and
commands inside Markdown block quotes, inline code, fenced code blocks, or
pasted logs are ignored. Supplemental text cannot change command ownership,
repository scope, security rules, or write authorization.
Responses chat clients, including Teams, treat the current authenticated user
as a trusted team maintainer. A command may include an explicit target such as
@issuelens plan microsoft/IssueLens#14, or use an issue already established
unambiguously in the conversation. IssueLens asks for a target when neither is
available. The Responses endpoint is therefore a trusted team surface and must
remain protected by the hosting platform's access controls.
For a GitHub issue-loop invocation, the issue containing the comment is the
target. IssueLens accepts a command only for an issue_comment.created event,
after using the trusted repository, issue number, comment ID, actor, and author
association to retrieve and verify the authoritative comment. The author must
be a human OWNER, MEMBER, or COLLABORATOR in both the event and current
authoritative comment snapshots. The two trusted association labels may differ,
for example MEMBER and COLLABORATOR, because GitHub can classify the same
maintainer differently across API contexts. Reporter commands, bot comments,
edited comments, actor mismatches, untrusted associations, aliases, commands
inside Markdown block quotes, inline code, fenced code blocks, or pasted logs,
and ambiguous multiple-command inputs are rejected.
@issuelens go is not planning approval or a readiness signal. Planning
artifacts may be explicitly accepted as approved, but that status still does
not authorize coding, pull requests, merges, or deployment.
Planning is available on demand through both protocols and does not change the
issue-triage workflow trigger. For an initial request, the plan sub-agent:
- Loads the validated
planninginstruction domain. - Re-reads the authoritative issue and inspects the relevant implementation, interfaces, configuration, and tests through bounded repository reads.
- Produces an action plan, then a design specification.
- Reports readiness, assumptions, risks, open questions, and the human input needed next.
- By default, posts the Action Plan and Design Specification to the target issue as two separate comments, in that order. User instructions or validated planning customization may replace this publication behavior.
- Stops and waits for human review, approval, clarification, or revision.
Triage also performs targeted source and test inspection whenever its conclusion depends on current implementation state, such as deciding whether a requested feature already exists, whether an issue remains actionable, or whether a root cause is supported. For both triage and planning, issue history is supporting context rather than proof of current code behavior. If necessary source evidence cannot be accessed, IssueLens reports the limitation instead of presenting an unsupported technical conclusion.
The agent does not autonomously repeat review passes. In a Responses conversation, later feedback uses the resumed session context. Invocations are stateless, so a revision request must identify the issue and the planning artifact or requested section to revise.
The planning agent receives the shared tools, not the maintenance-only wiki
writer. A request
to plan or revise a specific issue authorizes publication of the planning
artifacts on that issue using explicit user instructions, validated planning
customization, or the two-comment default. Labels, assignments, notifications,
unrelated comments, and other writes still require an explicit request.
Planning approval never authorizes source changes, branches, pull requests,
commits, or deployment.
The orchestrator assigns work by responsibility rather than tool availability:
triage follow-up actions stay with triage, while planning-status labels,
planning-artifact comments, and planning notifications stay with plan. For a
planning-owned write, plan applies the same label, assignment, or notification
skill safeguards before using the shared tool. Those skills also use built-in
behavior when the target repository has no customization files.
Fallback behavior is backward compatible:
- If
.github/issuelens.ymldoes not exist, labeling still checks.github/label-instructions.md, assignment still checks.github/area_owners.md,docs/area_owners.md, thenarea_owners.md, and all other capabilities use their built-in behavior. - If the config exists but omits a domain, that domain uses the same legacy or built-in fallback.
- If more than one case variant exists, the YAML is invalid, or a configured file is missing or invalid, IssueLens stops that capability and does not perform its related write. It does not silently bypass a present but invalid configuration.
Configuration is limited to one 16 KB YAML document and 64 KB per UTF-8 Markdown instruction file. Paths must be repository-relative POSIX paths. Repository policy cannot change sub-agent roles, authorize writes, weaken security boundaries, choose notification recipients/channels, or replace a required parent-facing data contract. Within those boundaries it may replace built-in evidence criteria and response presentation. Duplicate instructions or explicit user instructions may name related repositories. IssueLens accesses them through the same MCP tools, using anonymous fallback for public repositories without an App installation, and never uses this scope for writes.
Set TOOLBOX_ENDPOINT to the versioned MCP endpoint for the toolbox containing
non-GitHub chat capabilities. GitHub must remain excluded from this toolbox;
GitHub access is provided only by the bundled stdio MCP server.
Email and Teams notification tools are registered in process when their Logic App endpoint variables are configured.
- Python 3.13 (the hosted runtime); the standalone MCP package also supports 3.12
- One model backend: a Foundry project and model deployment, or a GitHub Copilot token
- A GitHub App with its private key stored in Azure Key Vault; see GitHub resource access
- Azure credentials that can read the configured Key Vault secret
- An initialized destination wiki only if you use team memory
For the Copilot model option, create a fine-grained PAT at github.com/settings/personal-access-tokens/new with Account permissions → Copilot Requests → Read-only. Foundry inference does not need this token; see model authentication.
Note: Classic tokens (
ghp_) are not supported. Use a fine-grained PAT (github_pat_), OAuth token (gho_), or GitHub App user token (ghu_).
Show steps
Create a local .env file from the sample template. Configure one model backend
plus the GitHub App ID and Key Vault secret URI:
cp .env.example .env # skip if .env already exists
# Edit .env and set the model variables plus GITHUB_APP_ID and
# GITHUB_APP_PRIVATE_KEY_SECRET_URI.The sample loads .env automatically when running locally. GITHUB_TOKEN is
needed only when using the optional GitHub Copilot model for local inference;
Foundry BYOK deployments do not use or inject it.
Next, start the agent locally with the run command:
azd ai agent runThe agent starts on http://localhost:8088/.
The Foundry Toolkit VS Code extension has a built-in sample gallery. You can open this sample directly from the extension without cloning the repository, it scaffolds the project into a new workspace, generates agent.yaml, .env, and .vscode/tasks.json + launch.json automatically, and configures a one-click F5 debug experience.
Chat with a running agent using the Agent Inspector:
- Start the agent locally first using azd or manual setup. The agent listens on
http://localhost:8088/. - Open the Command Palette (
Ctrl+Shift+P) and run Foundry Toolkit: Open Agent Inspector. - The Inspector auto-connects to the running agent. Send messages to chat with the agent and watch the streamed responses.
pip install -r requirements.txt
cp .env.example .env # skip if .env already exists
# Edit .env and set the model variables plus GITHUB_APP_ID and
# GITHUB_APP_PRIVATE_KEY_SECRET_URI.
python main.pyThe agent starts on http://localhost:8088/.
Show steps
Bash:
azd ai agent invoke --local '{"input": "Triage open issues in microsoft/vscode-java-pack and label the critical ones"}'PowerShell:
azd ai agent invoke --local '{\"input\": \"Triage open issues in microsoft/vscode-java-pack and label the critical ones\"}'# Triage a repository (find critical issues) and notify
curl -N -X POST http://localhost:8088/invocations \
-H "Content-Type: application/json" \
-d '{"input": "Triage open issues updated in the last 24h in owner/repo, then send the report"}'
# Label a single issue
curl -N -X POST http://localhost:8088/invocations \
-H "Content-Type: application/json" \
-d '{"input": "Label issue owner/repo#123"}'
# Assign a single issue using area ownership and historical patterns
curl -N -X POST http://localhost:8088/invocations \
-H "Content-Type: application/json" \
-d '{"input": "Assign issue owner/repo#123 to the right owner"}'
# Find duplicate or related reports for a single issue
curl -N -X POST http://localhost:8088/invocations \
-H "Content-Type: application/json" \
-d '{"input": "Find duplicates for issue owner/repo#123"}'
# Create an action plan followed by a design specification
curl -N -X POST http://localhost:8088/invocations \
-H "Content-Type: application/json" \
-d '{"input": "Plan implementation for triaged issue owner/repo#123. Return an action plan followed by a design specification, then wait for human review."}'
# Free-form instruction
curl -N -X POST http://localhost:8088/invocations \
-H "Content-Type: application/json" \
-d '{"input": "Summarize open issues in owner/repo"}'
# Chat (responses protocol) — no token in the body; the bundled server resolves
# the repository's App installation internally.
curl -N -X POST http://localhost:8088/responses \
-H "Content-Type: application/json" \
-d '{"input": "Find duplicates for issue owner/repo#123", "stream": true}'Invocation clients send Copilot blob attachments. The data field is raw
base64 without a data-URL prefix:
{
"input": "Use this screenshot while triaging owner/repo#123",
"attachments": [
{
"type": "blob",
"data": "iVBORw0KGgo...",
"mimeType": "image/png",
"displayName": "screenshot.png"
}
]
}Responses clients use standard polymorphic message content. Images use a
base64 data URL; generic files use inline file_data:
{
"input": [
{
"type": "message",
"role": "user",
"content": [
{"type": "input_text", "text": "Triage owner/repo#123 using this evidence"},
{
"type": "input_image",
"image_url": "data:image/png;base64,iVBORw0KGgo...",
"detail": "auto"
},
{
"type": "input_file",
"filename": "diagnostics.txt",
"file_data": "data:text/plain;base64,ZXJyb3IgbG9n..."
}
]
}
],
"stream": true
}Only inline base64 media is accepted. Remote URLs, platform file_id values,
and invocation file paths are rejected to prevent server-side URL fetching
and arbitrary container-file access. Requests may contain up to 10 attachments,
20 MB each and 50 MB combined. The selected model must support the supplied
image or file MIME type.
Issue images are also loaded automatically during issue-link triage. Before the
agent turn, the trusted host loader resolves explicit GitHub issue URLs and
owner/repository#number references, reads each issue body, and adds validated
image bytes as Copilot blob attachments. Clients do not need to add those images
to the invocation payload. It accepts up to 5 PNG, JPEG, GIF, or WebP images,
5 MB each and 15 MB combined. Arbitrary image hosts and unsafe redirects are
rejected, and GitHub credentials are never forwarded to signed storage
redirects.
chat.py is a small REPL for the chat protocol — it chains previous_response_id
so turns stay in one conversation:
python chat.py # interactive
python chat.py "Triage open issues in owner/repo" # one-shot
python chat.py --attach screenshot.png "Triage owner/repo#123" # with mediaType new to start a fresh conversation, exit to quit.
Invocations (POST /invocations):
| Field | Required | Description |
|---|---|---|
input |
Yes | The task — a free-form text prompt describing what to triage, plan, label, or report |
attachments |
No | Inline Copilot blob attachments with base64 data, mimeType, and optional displayName |
Chat (POST /responses) takes a standard OpenAI Responses body; both protocols
use the same internal GitHub App MCP authentication.
Each Copilot SDK event is streamed via event.to_dict():
data: {"type": "assistant.message_delta", "data": {"delta_content": "Python is"}}\n\n
data: {"type": "assistant.message_delta", "data": {"delta_content": " a programming"}}\n\n
...
event: done
data: {"invocation_id": "...", "session_id": "..."}
The recommended trigger is a GitHub Actions workflow in the target repository. It authenticates only to the Foundry endpoint through Azure OIDC and sends the task. The hosted agent owns its Key Vault-backed App identity, so target repositories store no App private key and transmit no GitHub token.
- Event-driven: issue opened/reopened and human-authored issue-comment created/edited events send a neutral issue-loop task. IssueLens re-reads the issue and comments, then chooses triage, re-triage, planning, re-planning, or no action.
- Manual:
workflow_dispatchwith a requiredissue_numberinput sends the same neutral issue-loop task for an existing issue.
The workflow does not currently trigger on issue title/body edits or pull request comments. Its preflight step rejects PR-backed comments and comments whose sender or author is a bot, records the accepted/skipped reason before Azure login, and passes only trusted event metadata to the agent. It never copies issue or comment body text into the workflow-generated control prompt.
Runs are grouped by repository and issue. Different issues run independently; events for one issue are serialized. GitHub Actions keeps one active and one pending run per group by default, so bursts may coalesce by replacing an older pending run. The eventual invocation re-reads current issue state.
For an eligible event, the trusted workflow task authorizes only the selected role's bounded writes on that issue. Comment text remains untrusted context and cannot authorize implementation, deployment, external notification, cross-repository writes, or role changes. The sole command exception is an authoritative comment containing one valid built-in command occurrence whose maintainer association is validated by the global IssueLens contract against trusted event provenance. The workflow carries that provenance but does not parse commands. A no-action decision performs no GitHub write.
Use the IssueLens action guide to create
an external caller using request-type: issue-loop and a full action commit SHA.
The repository's issue workflow is a thin
local caller and shares the same action as team memory. External consumers
omit its local checkout step. Configure the Azure OIDC identity and agent
URL/scope, and use the default branch for manual dispatch. Keep repository-specific policy in
.github/issuelens.yml; the
workflow filename intentionally differs from the policy filename.
For explicit triage, planning, or other supported requests, use request-type: task with trusted input text. This does not synthesize issue-loop event or
maintainer-command authority. Both generic adapters accept a completed final
answer in the format chosen by the caller. status=completed confirms invocation
completion, not a successful requested write; the original answer is available
through a runner-local response-path. The default hybrid view streams readable
text and tool activity, and the full job summary renders the final answer. Use
the publication controls
for sensitive requests. Raw protocol events and tool payloads are not logged. Only
team-memory enforces the wiki-specific result schema. The agent retains role
routing and command validation; no new permanent agent contract is introduced.
To use your own Azure AI Foundry model instead of the Copilot model, set the Foundry variables (no GITHUB_TOKEN needed):
FOUNDRY_PROJECT_ENDPOINT=https://<account>.services.ai.azure.com/api/projects/<project> \
AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o \
python main.pyAuthentication uses Microsoft Entra bearer tokens only, requested for
https://ai.azure.com/.default through async DefaultAzureCredential. The
Copilot SDK's per-request bearer_token_provider callback (supported by the
existing minimum SDK 1.0.7) is registered on both new and resumed sessions.
Azure Identity's async get_bearer_token_provider owns in-memory token caching,
early refresh, and concurrent acquisition, including during long-running turns;
no static token is saved in session configuration. Credential acquisition does
not block the host event loop. The lazy credential is closed after the existing
Responses graceful-shutdown handler, without replacing its cleanup.
When hosted, FOUNDRY_PROJECT_ENDPOINT is platform-injected; set the deployment
name in azure.yaml / agent.yaml. Keep the project endpoint, rather than
switching to an account-level OpenAI endpoint. Three identities are distinct:
| Identity | Inference/deployment responsibility |
|---|---|
| Hosted agent runtime identity | Foundry supplies a dedicated Microsoft Entra service principal and the runtime credential flow. It has implicit model-inference access through its own project endpoint in the standard hosted case. |
| Project managed identity | Foundry proxies project-endpoint inference to the account's model deployment using this identity. It needs Foundry User (formerly Azure AI User) on the Foundry account. It is not the agent's runtime token principal. |
| GitHub Actions deployment service principal | OIDC authenticates the runner for deployment. Its AZURE_CLIENT_ID is not forwarded into the hosted process and its permissions do not authenticate runtime inference. |
See Microsoft's hosted agent identities and permissions reference. An administrator should verify the actual principal and scope of any existing role assignment; an assignment to a deployment principal is not proof of runtime or project-to-account access. This repository change does not verify or create Azure role assignments.
For local service-principal execution, use the existing Azure Identity
environment or workload-identity credential setup for that principal (for
example, AZURE_TENANT_ID, AZURE_CLIENT_ID, and a provisioned
AZURE_FEDERATED_TOKEN_FILE). Existing certificate/secret-based service-principal
credentials are also supported by DefaultAzureCredential; keep those values
outside source control and do not copy deployment credentials into the hosted
manifest. Without an application credential, local developer credentials can
also be selected by the default chain. The selected local principal needs
project-level model data-plane access, such as Foundry User or an approved
narrower custom role. ARM Contributor alone is not model authorization.
Migration and diagnosis: AZURE_AI_MODEL_API_KEY is no longer read for
authentication, passed by the deployment workflow/manifests, or forwarded to the
Copilot child process. A stale value cannot enable key authentication. Remove
obsolete values from local configuration and deployment secret stores through
your normal approved process; changing this code neither deletes existing
secrets nor updates a running deployment. Token-acquisition failures indicate a
missing/unusable runtime credential; check the credential source first. A
service-side 403 after token acquisition instead requires checking model
data-plane authorization, including the project identity's account access.
Neither case falls back to a key or to GitHub. GITHUB_TOKEN remains an inference
option only when the Foundry endpoint is absent.
Deploy IssueLens to Foundry follows
Microsoft's Set up CI/CD for a hosted agent:
install azd and microsoft.foundry, log in with Azure OIDC, configure the azd
environment, run azd deploy, inspect status, and invoke the agent.
It uses the existing repository-root azure.yaml Python 3.13 ZIP/remote-build
service directly, without a custom deployment helper or packaging hook.
Actions are commit-pinned; azd is 1.34.2 and the Foundry bundle is
1.0.0-beta.2. The bundle's dependency ranges are not a version lock, so the
workflow explicitly installs each component with --no-dependencies and verifies
the complete installed set's installedVersion fields before Azure login:
| Extension | Pinned version |
|---|---|
azure.ai.agents |
1.0.0-beta.16 |
azure.ai.connections |
1.0.0-beta.7 |
azure.ai.inspector |
1.0.0-beta.7 |
azure.ai.projects |
1.0.0-beta.11 |
azure.ai.routines |
1.0.0-beta.6 |
azure.ai.skills |
1.0.0-beta.6 |
azure.ai.toolboxes |
1.0.0-beta.7 |
microsoft.foundry |
1.0.0-beta.2 |
These component releases come from the azd 1.34.2 registry snapshot. Missing, extra, duplicate, or mismatched installed components stop the job. New registry releases do not change these pins; upgrades require a reviewed workflow, test, and documentation change. The executable workflow regression tests require Bash and jq, both provided by the configured Ubuntu runner.
Unlike the quickstart's push trigger, this workflow is manual only, restricted
to this repository's default branch, and requires approval through the fixed
foundry-production environment. It checks required reviewers, disabled
administrator bypass, and a successful push-CI run for the exact dispatched SHA
before Azure login. Self-review follows the GitHub Environment's configured
policy. Deployments are serialized without cancelling an active
publication. No live deployment, provisioning, or permission change is
authorized by creating or merging the workflow.
One-time setup
The Foundry project, model deployment, and IssueLens hosted agent must already
exist, as required by the quickstart. An administrator must create
foundry-production with required reviewers, disable
administrator bypass, and allow only the exact default branch (main), not tags.
Set Prevent self-review according to the team's approval policy; the workflow
accepts either setting while still requiring environment approval.
The workflow reuses the repository's existing Azure ID secret names. Reusing
their names does not grant deployment permissions: if the existing identity is
invocation-only, override the same secrets in foundry-production with a
dedicated deployment identity, rather than expanding the invocation identity's
permissions. Configure Azure OIDC with issuer https://token.actions.githubusercontent.com,
audience api://AzureADTokenExchange, and subject
repo:microsoft/IssueLens:environment:foundry-production. The referenced CI/CD
guide specifies Foundry User plus Contributor on the target project for
code deployment; use an approved narrower equivalent where available. Role
assignments and initial provisioning are separate administrator operations.
The hosted agent runtime identity, not the deployer, needs Key Vault Secrets User
on the App-key secret. Model inference always uses Entra authentication with the
runtime/project identity responsibilities described above.
The existing repository secrets AZURE_CLIENT_ID, AZURE_TENANT_ID, and
AZURE_SUBSCRIPTION_ID, and variable ISSUELENS_APP_ID, are reused directly.
Store additional deployment-only settings as environment-scoped secrets in
foundry-production. Same-named environment secrets override repository secrets.
Unlike the quickstart's variables, all Azure configuration is kept in secrets
to avoid exposing infrastructure details:
| Name | Storage | Purpose |
|---|---|---|
AZURE_CLIENT_ID |
Secret | Existing Azure identity secret; override for a deployment-specific identity if needed. |
AZURE_TENANT_ID |
Secret | Existing Azure tenant secret. |
AZURE_SUBSCRIPTION_ID |
Secret | Existing subscription secret. |
AZURE_LOCATION |
Secret | Existing project's Azure region. |
AZURE_AI_PROJECT_ID |
Secret | Full ARM resource ID ending in /accounts/<account>/projects/<project>. |
FOUNDRY_PROJECT_ENDPOINT |
Secret | Existing project's HTTPS endpoint on *.services.ai.azure.com. |
AZURE_AI_MODEL_DEPLOYMENT_NAME |
Secret | Existing model deployment used for inference. |
ISSUELENS_APP_ID |
Variable | Existing GitHub App registration variable, passed as runtime GITHUB_APP_ID. |
ISSUELENS_GITHUB_APP_PRIVATE_KEY_SECRET_URI |
Secret | Key Vault secret URI, passed as GITHUB_APP_PRIVATE_KEY_SECRET_URI, never PEM contents. |
TOOLBOX_ENDPOINT |
Optional secret | Existing non-GitHub toolbox endpoint in this Foundry project. |
MAILING_URL |
Optional secret | Secret-bearing Logic App email endpoint. |
PERSONAL_NOTIFICATION_URL |
Optional secret | Secret-bearing Logic App Teams endpoint. |
The existing ISSUELENS_AGENT_URL secret is an invocation endpoint, not the
project-level FOUNDRY_PROJECT_ENDPOINT; these are not interchangeable.
The App settings use an ISSUELENS_ prefix because GitHub reserves GITHUB_
configuration names. IssueLens uses AZURE_AI_MODEL_DEPLOYMENT_NAME instead of
the quickstart's example FOUNDRY_MODEL_NAME. No App PEM or GitHub user token is
passed to deployment. .agentignore controls the native code ZIP and excludes
local credentials, azd state, and Git metadata.
Run, verify, and recover
Wait for CI on the intended default-branch commit, select Actions > Deploy
IssueLens to Foundry > Run workflow, and approve the environment job. The
workflow checks that azd's recorded version is active, then invokes both
protocols in fresh version-bound sessions. Responses uses a plain-text prompt;
Invocations uses IssueLens's {"input": "..."} payload, not the quickstart's
generic message example. Both must complete with ISSUELENS_DEPLOYMENT_OK;
CLI headings or merely non-empty output do not count as a successful reply.
The fixed prompt requests no tools, repository access, wiki writes, or
notifications. These are protocol/inference smoke checks, not proof of App,
wiki, or notification access, nor a host-enforced tool-isolation mode.
Invocations permits the SDK's root subagent.selected event for issuelens
only when agentId, data.parentToolCallId, and data.toolCallId are absent
or null. Its available-tools inventory is not tool execution; tool requests,
tool events, and all other sub-agent events still fail the smoke check.
The job is limited to 40 minutes, the native deployment wait to 20 minutes, and
each invocation to 120 seconds within a 3-minute step. The summary records the
commit, logical GitHub environment, version, readiness, and protocol outcomes,
not Azure identifiers or endpoints. Azure CLI account output is disabled;
azd configuration/deployment output, readiness details, and raw agent responses
stay in runner-local files, including on failure. The workflow never prints
azd env get-values or uploads these files. Temporary files and .azure are
removed after the run; abrupt termination also relies on hosted-runner disposal.
No automatic retry or rollback is performed. A failed deployment or smoke check may occur after publication: inspect Foundry before another attempt. Recovery requires a reviewed fix/revert, successful CI, and separately authorized deployment with environment approval. Local validation does not establish live OIDC, RBAC, or hosted readiness. The configured GitHub-hosted runner needs network access to the target project; private-network targets require a separately approved runner/network arrangement.
Once you've tested locally and explicitly authorized the deployment, deploy to Microsoft Foundry:
# Provision Azure resources (skip if already done during local setup)
azd provision
# Build, push, and deploy the agent to Foundry
azd deployAfter deploying, invoke the agent running in Foundry:
Bash:
azd ai agent invoke '{"input": "What can you help me with?"}'PowerShell:
azd ai agent invoke '{\"input\": \"What can you help me with?\"}'To stream logs from the running agent:
azd ai agent monitorFor the full deployment guide, see Azure AI Foundry hosted agents.
- Open the Command Palette (
Ctrl+Shift+P) and run Foundry Toolkit: Deploy Hosted Agent. The extension opens a tab-based Deploy Hosted Agent wizard and readsagent.yamlto auto-populate what it can. - If prompted, complete Foundry Project Setup to pick the subscription and Foundry project (or create a new one) to deploy to.
- On the Basics tab, configure the core deployment settings:
- Deployment Method: Code (upload as a ZIP) or Container (Docker image via ACR).
- For Code, pick a packaging option: Remote or Local.
- For Container, pick a registry option: default ACR, your own ACR, or a prebuilt ACR image.
- Hosted Agent Name: confirm the name to register with the hosting service.
- On the Review + Deploy tab, finalize the runtime and resources:
- Confirm the auto-detected runtime details (language, entry point, or Dockerfile).
- Pick a CPU and Memory size.
- Click Deploy. Fields are validated inline, and the extension handles the build/upload, agent version creation, and RBAC role assignment.
- After deployment, invoke the agent in the Agent Playground and stream live logs from the Logs tab.
The default deployment uses Python ZIP/remote build, not a locally built
container. If you choose the optional container deployment path, build for
linux/amd64.
If you choose to build locally, and your machine is not linux/amd64 (for example, an Apple Silicon Mac), the image will not be compatible with our service, causing runtime failures.
Fix for local builds:
docker build --platform=linux/amd64 -t image .This forces the image to be built for the required amd64 architecture.