name: hermes-a365
description: Use when integrating a Hermes agent into the Microsoft 365 ecosystem — agent-as-tenant-directory-identity (AI Teammate / agentic user) or Copilot Chat surfacing (Custom Engine Agent + Azure Bot Service). Distinct from Hermes' sibling Teams adapter (plugins/platforms/teams/) which covers classic Bot-Framework Teams chat. Wraps the GA Microsoft.Agents.A365.DevTools.Cli verbs, ships the BF activity bridge that backs the agent365 gateway platform, and emits both AI Teammate and Custom Engine Agent manifests.
version: 0.5.2
author: Hermes Agent
license: MIT
metadata:
hermes:
tags:
- microsoft
- agent-365
- a365
- entra
- bot-framework
- mcp
- cloud-platforms
- copilot-chat
related_skills:
- hermes-agent-skill-authoring
Hermes A365
Overview
Hermes-A365 is the M365 Copilot ecosystem path for Hermes agents. It covers the surfaces a classic Bot Framework Teams bot structurally cannot reach:
- Path A — AI Teammate (M365 agentic user): the Hermes agent appears as a first-class agentic identity in your M365 tenant directory, in the "Built for your org" picker, in M365 People search, and in agentic-user audit trails. Teams 1:1 chat with M365-native identity. No Azure subscription required.
- Path B — Custom Engine Agent (Azure Bot Service + 1.21 manifest): the Hermes agent appears in M365 Copilot Chat's agents picker, in Copilot side-panels inside Word / Excel / PowerPoint / Outlook, and reaches the Copilot fabric's invoke surfaces (Microsoft Search, Outlook compose-action). Requires an Azure subscription so the blueprint Entra app can be registered as an Azure Bot Service resource.
Both paths share the same blueprint Entra app + service principal + bot endpoint, and operators with both prerequisites can run both surfaces from one Hermes-A365 install.
Distinct from Hermes' sibling Teams adapter at
plugins/platforms/teams/adapter.py (shipped v2026.4.30 + in-flight
work in hermes-agent#10037 and #13767). That adapter is the right
tool for generic Teams chat bots — DM, channels, group chats,
threading, file attachments — using classic Bot Framework with an
Azure App Registration + client secret / certificate / Managed
Identity. It gives no M365 directory identity and no Copilot Chat
surfacing.
Microsoft Agent 365 (A365, GA 2026-05-01) is the governance / identity / observability control plane Hermes-A365 plugs into: Entra-backed agentic identity, tenant licensing, agent blueprints, MCP-mediated Microsoft 365 data access ("Work IQ"), OpenTelemetry, and the channel adapters for Teams / Outlook / M365 Copilot.
The wrapper is built directly on the GA Microsoft.Agents.A365.DevTools.Cli
verbs documented in references/a365-cli-reference.md.
The skill composes them into idempotent plan/apply flows, fills the
gaps the CLI doesn't cover (license decision, admin consent grant,
runtime .env generation, Custom Engine Agent manifest transform),
and ships a Bot Framework activity bridge + Hermes gateway platform
plugin (agent365) that round-trips messages between A365 and the
Hermes agent loop.
When to Use
Use when the user wants any of:
- Hermes registered as a first-class M365 agentic identity — appears in the tenant directory, agentic-user audit, "Built for your org" picker. Path A.
- Hermes available in M365 Copilot Chat (agents picker /
@-mention / side-panels in Word / Excel / PowerPoint / Outlook). Path B. Requires Azure subscription. - A Bot Framework activity bridge backed by A365 governance (Entra-backed identity, MCP-mediated Microsoft 365 data, OTLP audit trails).
- Migrating an OpenClaw-on-A365 deployment to Hermes (the blueprint stays; only the runtime endpoint changes).
Don't use when:
- The goal is a generic Teams chat bot with no M365 directory
identity or Copilot surfacing — use Hermes' sibling Teams adapter
(
plugins/platforms/teams/, shipped v2026.4.30). It handles DM / channels / group / threading / file attachments via classic Bot Framework without A365 prerequisites. - Generic Microsoft Graph access is the goal — use a Graph-only skill.
- Deploying a Bot Framework bot outside any M365 / A365 governance surface — pick a classic BF skill.
- Setting up OpenAI Agents SDK or another framework end-to-end — A365 governs the runtime; pick the appropriate framework skill for the agent itself.
Prerequisites
- A Microsoft 365 tenant where the operator has Global Administrator or Agent Administrator role and is enrolled in Microsoft's Frontier Preview Program.
- The A365 CLI on PATH:
Microsoft.Agents.A365.DevTools.Cli(.NET tool, ships asa365). Only the .NET tool ships at GA — the npmatkvariant referenced in pre-GA documentation never landed. No CLI build is currently live-verified clean for Microsoft#408: the secret-persistence regression still reproduced on 1.1.181 during the 2026-05-15 R9 walk. Doctor therefore warns for all versions and live setup should keep--auto-recover-secretenabled until a fixed build is walked clean. azCLI ≥ 2.55.0, signed into the target tenant. Manya365subcommands shell out toazfor Entra reads.- PowerShell 7+ (
pwsh) on PATH. The CLI invokespwshfor some setup steps; missingpwshcausesa365 setup requirementsto fail. - A custom Entra client app (Microsoft's convention: display name
Agent 365 CLI) registered in the tenant. The CLI uses it as the device-code/auth-code client. Doctor verifies discoverability via the signed-inazcontext. - An OS keychain: macOS Security or Linux libsecret (
secret-tool). Windows is not yet supported. - A tenant license: either the Agent 365 add-on ($15/user/month) or
Microsoft 365 E7 ($99/user/month). The skill never purchases — it
recommends; see
references/license-cost-table.md. - A Hermes harness with
hermes-a365installed into its venv (~/.hermes/hermes-agent/venv/bin/pip install 'hermes-a365[bridge]') so the plugin loader auto-discoversagent365via thehermes_agent.pluginsentry point, plusplugins.enabledand agateway.platforms.agent365block configured in~/.hermes/config.yaml. The README quickstart walks through this end-to-end.
Core procedures
State-mutating subcommands default to dry-run; pass --apply to
execute. Repeated invocation converges to the same state.
hermes a365 doctor
Read-only environment probe. Exit 0/1/2. Probes: a365, az (signed
in), pwsh, the Agent 365 CLI Entra app, network reachability
(login.microsoftonline.com / graph.microsoft.com), keychain,
~/.hermes/.env, Hermes harness. Frontier Preview enrollment is not
auto-verifiable.
hermes a365 license --users <n> --agents <n> --plan <E3|E5|...>
Read-only. Recommends the A365 add-on or E7 based on the decision matrix
in references/license-cost-table.md.
Records the chosen model in ~/.hermes/.env as A365_LICENSE_MODEL.
hermes a365 register --agent-name <name> [--tenant-id <id>] [--m365] [--no-endpoint] [--skip-requirements]
Composite plan that orchestrates the three real CLI steps a blueprint needs:
a365 setup blueprint --agent-name <name>— registers the Entra app and service principal that back the blueprint.a365 setup permissions mcp --agent-name <name>— configures MCP OAuth grants and inheritable permissions.a365 setup permissions bot --agent-name <name>— configures the Messaging Bot API OAuth grants.
The CLI itself owns idempotency, JSON shape, and Entra round-trips. The
skill's job is to compose the right argv per step, run them in order via
the Mutator protocol, persist derived display names to
a365.config.json (so subsequent commands refer to consistent
identities), and surface known auth errors:
AADSTS500011(license not propagated): retried up to--retriestimes with--backoffseconds (defaults: 3 × 30 s, mockable in tests viasleep_fn).AADSTS90094(admin consent required): surfaced as "deferred — runhermes a365 consent" rather than failing the run. The blueprint apps remain created.
--m365 registers the messaging endpoint via MCP Platform.
--aiteammate is intentionally unsupported on register: the wrapper
only runs blueprint + permission setup. Use
hermes a365 publish --aiteammate --apply, then upload and activate the
zip in M365 Admin Centre, to create/bind the AI Teammate agentic user.
--no-endpoint and --skip-requirements are passthroughs to
a365 setup blueprint.
hermes a365 consent
Renders the admin-consent URL from templates/consent-url.txt.j2, opens
it in the default browser (unless --no-open), then polls
a365 query-entra blueprint-scopes every 5 s up to a 5 min timeout.
Idempotent; re-running after grant is a no-op.
hermes a365 instance create <slug> --owner <email> --owner-aad-id <oid> [...]
Pure local config-file writer. The server-side agent identity is created
by a365 setup blueprint (driven by register); this command only
produces the per-agent ~/.hermes/agents/<slug>/.env that runtime
consumers read for slug, owner, OTLP endpoint, and business-hours
metadata.
Inherits A365_APP_ID, A365_TENANT_ID, HERMES_OTLP_ENDPOINT from
~/.hermes/.env. An existing AA_INSTANCE_ID is preserved across
re-runs; business-hours fields from a prior run are also preserved
unless explicitly overridden. The per-agent .env never contains the
blueprint client secret — see pitfall #7 below for where the secret
actually lives.
hermes a365 publish --agent-name <name> [--aiteammate] [--copilot-chat] [--bot-id <guid>] [--use-blueprint] [--tenant-id <id>]
Wraps a365 publish to package the agent manifest into a zip the
operator uploads to the relevant admin surface. The output mode
follows the M365-ecosystem path:
--aiteammate(Path A) — emits an AI Teammate manifest (agenticUserTemplates,manifestVersion: devPreview). Upload at M365 Admin Centre → Agents → Upload custom agent, then activate per-user under Agent 365 admin centre. Surfaces in Teams 1:1 "Built for your org".--copilot-chat(Path B, slice 19u-a in v0.4.0) — emits a Custom Engine Agent manifest (manifestVersion: "1.21",bots+copilotAgents.customEngineAgentsblocks). Upload at Teams Admin Center → Manage apps → Upload + assign per-user policy. Surfaces in M365 Copilot Chat's agents picker + Copilot side-panels. Implementation post-processes the GA CLI's AI Teammate zip in-place (or to a sibling.copilot-chat.zipwhen combined with--aiteammate).--aiteammate --copilot-chat— emits both side by side (Copilot Chat zip lands at<original>.copilot-chat.zip).--bot-id <guid>— overrides thebotIdwritten into the Custom Engine Agent manifest. Default extraction order:webApplicationInfo.id→bots[0].botId→ manifest top-levelid(the GA CLI 1.1.174+ AI Teammate emit only populates the last).- No surface flag — default
a365 publishbehaviour (registers the agent instance via Graph; no zip).
The wrapper surfaces the resulting package path(s) plus the right
admin-surface URL hint for each path. The name.short 30-char
auto-truncate (slice 19r-c) applies to both flavours. Channel
deployment is operator-side in all cases.
Path B additionally requires Azure Bot Service registration of the blueprint Entra app with the Microsoft Teams channel enabled — otherwise Microsoft's routing layer won't forward Copilot Chat activities to
/api/messagesregardless of manifest shape. Seereferences/m365-surface-coverage.mdfor the prerequisite detail.
hermes a365 status [<slug>]
Per-component report against the verified query-entra surface. Four
components only:
local_config— parent~/.hermes/.env(and per-agent .env if a slug is given) parseable + required keys present.blueprint_scopes—a365 query-entra blueprint-scopesfor the agent's blueprint.instance_scopes—a365 query-entra instance-scopesfor the agent's instance.activity_bridge— local PID-file probe (only when a slug is given ANDbridge.pidexists). When the bridge is running this row reportsok; absent pidfile ismissing(bridge not currently running) and a stale or unreadable pidfile iserror.
Exit codes: 0 ok, 1 partial, 2 broken, 3 skill not yet bootstrapped.
hermes a365 cleanup --agent-name <name> [--kinds=...] --confirm=<name> --apply
Destructive teardown. Drives a365 cleanup azure → instance →
blueprint (safe → unsafe — App Service first so the runtime stops
before the Entra identity is revoked). Local artefacts under
~/.hermes/agents/<slug>/ are removed after all cloud steps succeed.
--kinds=<subset> runs only the requested kinds. --confirm must
equal --agent-name. The plan is always printed for operator
audit before any mutation.
hermes a365 activity-bridge
The Bot Framework adapter daemon. Two main modes are available either
as a standalone process (operator launches it) or as the runtime that
backs the agent365 Hermes gateway platform plugin (the gateway
loads it in-process when an agent has the agent365 platform enabled).
verify --slug <slug>— one-shot diagnostic (config + auth + reachability). Exit 0/1/2.serve --slug <slug> --port 3978— BF webhook adapter daemon. Validates inbound activities as AAD-v2 (A365 / MCP Platform issues AAD-v2 tokens directly to bot endpoints, not classic BF tokens), dedupes BF retries, gatesserviceUrlagainst the Microsoft host suffix, and replies through the agentic three-stage user-FIC token chain (BF S2S → agent FMI → user FIC). Outbound goes viareplyToActivityagainst the cached inboundserviceUrl.update-endpoint --agent-name <n> --url <https>— wrapsa365 setup blueprint --m365 --update-endpoint <url>so operators can pin the agent's messaging endpoint to a tunnel URL. Auto-recovers from duplicate-name error #140.
Topology: Teams (or other M365 surface) → A365 BF → <tunnel>/api/messages
→ bridge → Hermes agent loop → reply via serviceUrl. Activity-shape
catalogue: references/activity-protocol-shapes.md;
operator-side network exposure options:
references/exposing-the-bot-endpoint.md.
Surfaces that work today:
- Path A (AI Teammate) Teams 1:1 chat — validated end-to-end across rounds 3 → 8, with BF streaming protocol round-trip on round-8 (2026-05-11, v0.3.0). Agent appears in "Built for your org" picker.
- Path A cron-driven proactive sends — shipped in v0.5.0 +
v0.5.1 (slices 19x-a..e, closes #4 and #27). Wire-validated
against the live tenant 2026-05-13.
Agent365Adapter.send()routes throughsendToConversation(noreplyToId) when the current gateway lifetime hasn't captured an inbound forchat_id; mints the agentic three-stage user-FIC chain against a target-spec built from the persistedConversationRegistry.prune_old_entries+pin/unpin/mark_usedmutators let operators manage the registry without restarting. - Path B (Custom Engine Agent) Copilot Chat — live since
v0.6.0 (#16 / #34 / #36 closed). Provisioned with the
bot-servicewrapper family (create/verify/update-endpoint/cleanup; slice 20), which registers an Azure Bot Service against a separate non-agentic Entra app (A365_BF_APP_ID/A365_BF_CLIENT_SECRET) — the blueprint app can't mint BF S2S tokens (AADSTS82001), so Path B needs its own identity. Inbound uses the BF-shaped JWT validator branch (#34); outbound replies go via classic BF S2S. Reply delivery: Copilot Chat arrives as agroupChatand does not render BF streaming, so non-personal turns are coalesced into one non-streamingsend_reply(#54 / #55); Teams 1:1 uses BF streaming with single-stream-per-turn + a stale-stream liveness guard (#62). Seereferences/activity-protocol-shapes.md→ Streaming and reply delivery; the §11 runbook inreferences/live-tenant-test.mdis the operator provisioning walk.
Sibling-plugin lane (Teams group chat / channels / threading /
file attachments / compose-extension invokes) is out of scope
for Hermes-A365 — use Hermes' classic Teams adapter for
those. See references/m365-surface-coverage.md
for the per-surface matrix and the architectural reasoning behind
the split.
Conflict resolution
| Conflict | Behaviour |
|---|---|
| Blueprint app already exists for the same name | a365 setup blueprint is itself idempotent; register re-runs are safe. |
Permissions step fails with AADSTS90094 |
Reported as "deferred — run hermes a365 consent"; blueprint stays created. |
Instance .env exists with a previous AA_INSTANCE_ID |
instance create preserves the id (cloud state is unchanged). |
| Cleanup target has no recorded state | The corresponding kind is skipped, not errored. |
| License missing or insufficient | register retries AADSTS500011 with backoff; on exhaustion the operator runs hermes a365 license and re-tries. |
pwsh missing on PATH |
Doctor flags it; a365 setup itself fails fast with a CLI error referencing setup requirements. |
Common pitfalls
- Channel deployment is operator-side. There is no
deployverb.publishproduces the zip; the M365 admin uploads and approves it. - Delegated permissions, not application permissions. A365 explicitly requires delegated permissions. Pasting an application- permission consent URL silently breaks at runtime.
- One agent name, derived sub-names.
--agent-name "Inbox Helper"producesInbox Helper IdentityandInbox Helper Blueprintinside the CLI. Don't pass those derived names directly — pass the base. - Blueprint slug ≠ agent name.
registeroperates on the CLI--agent-name. The local<slug>used ininstance create/status/cleanupis the per-agent dir name under~/.hermes/agents/. Keep them aligned (lowercased / hyphenated) by convention; the skill never silently re-derives one from the other. - License propagation lag. A365 license assignment can lag 5–30
min after purchase.
registerretriesAADSTS5000113× with 30 s backoff; if you're outside that window,hermes a365 doctoris the first port of call. AA_INSTANCE_IDreuse across re-runs.instance createpreserves the existing id. Don't manually edit the per-agent .env to "reset" it without first runningcleanup— the cloud instance will linger.- Blueprint client secret on disk in plaintext (macOS / Linux).
a365 setup blueprintwrites the secret toa365.generated.config.json— DPAPI-encrypted on Windows, plaintext elsewhere. That file and thecleanup -y-emitted*.backup-*.jsonare gitignored; treat both as keychain-grade. CLI versions 1.1.171, 1.1.174, and 1.1.181 reproduce Microsoft#408, where the secret is minted but persisted asnullon macOS / Linux. Keep--auto-recover-secretenabled for live setup regardless of CLI version until a later build is live-verified fixed; doctor stays warning-only across all version branches to avoid a false green.
Verification checklist
-
hermes a365 doctorexits 0. -
hermes a365 status <slug>shows: local_config ok, blueprint_scopes ok, instance_scopes ok. -
a365 publishzip uploaded via the M365 Admin Centre and approved for the target DLP scope. - Test message in Teams (or other approved channel) returns an Adaptive Card from the agent.
- OTLP trace visible in the A365 admin centre for the test message.
-
hermes a365 cleanup --agent-name <name>(dry-run) lists exactly the resources the operator expects to remove.
One-shot recipes
Bootstrap a single agent on a clean tenant
hermes a365 doctor # health check
hermes a365 license --users <n> --agents <n> --plan E5 # decide license
hermes a365 register --agent-name "<Display Name>" # plan
hermes a365 register --agent-name "<Display Name>" --apply --auto-recover-secret
hermes a365 consent # in-browser grant
hermes a365 instance create <slug> --owner <email> --owner-aad-id <oid> --apply
hermes a365 publish --agent-name "<Display Name>" --aiteammate --apply # produce zip
# Operator: upload the zip in the M365 Admin Centre and activate for users.
hermes a365 activity-bridge verify --slug <slug> # bridge preflight
# Run the gateway with the agent365 platform configured (loads the
# bridge in-process via the entry-point-discovered plugin):
hermes gateway run --profile <slug>
hermes a365 status <slug> # final verification
Re-target an existing OpenClaw-on-A365 agent at Hermes
The blueprint stays in place; only the runtime endpoint and per-agent config change:
hermes a365 instance create <slug> --owner <email> --owner-aad-id <oid> --apply
hermes a365 publish --agent-name "<Existing Display Name>" --apply
# Operator re-uploads the zip; the activity bridge then takes over the
# BF subscription URL the previous runtime was using.
Decommission an agent cleanly
hermes a365 cleanup --agent-name "<Display Name>" # plan
hermes a365 cleanup --agent-name "<Display Name>" --apply --confirm="<Display Name>"
--kinds=instance,blueprint skips Azure when the App Service was
provisioned out-of-band. Tenant-wide infrastructure (Frontier Preview
enrollment, the custom Agent 365 CLI client app, license) is never
touched.
Subcommand implementations live under src/hermes_a365/; each is a
thin CLI over a planner + applier pair parameterised by a Mutator
protocol so the apply path is unit-testable without the live A365 CLI.
Packaged Jinja templates resolve via importlib.resources against
hermes_a365._data.templates; dated reference snapshots live under
references/. For per-subcommand flags see hermes a365 <verb> --help;
the original v0.1 design draft is archived at
docs/historical/SPEC-v0.1-draft.md.