Changelog¶
Unreleased¶
Added¶
- Rename-safe agent matching through canonical
ENTRABOT_AGENT_UPNwith object-ID fallback. - Boundary-owned XPIA envelopes for model-facing Teams, email, Files, and Work IQ content.
read_email,read_interactions, andbootstrap_body_state, bringing the MCP surface to 37 tools.list_workbook_worksheetsandread_workbook_rangefor bounded, read-only Excel access through Microsoft Graph, bringing the MCP surface to 39 tools.- Work IQ Excel adapter with
read_excel_workbook,create_excel_workbook,add_excel_comment, andreply_to_excel_comment, bringing the MCP surface to 43 tools. Work IQ setup now addsmcp_ExcelServerand requestsMcpServers.Excel.All. - Windows status tooling and expanded Windows setup/teardown guidance.
Changed¶
- Blueprint, BlueprintPrincipal, and Agent Identity creation now use the dedicated Microsoft Graph v1.0 subtype endpoints; Agent User creation remains on beta.
- The public docs site was reorganized into nine task-oriented sections; plans, specs, and investigations were moved to non-published
engineering-history/; source-audited MCP and script references were added; and legacy URLs redirect to current pages. - GitHub Pages builds run in strict mode.
- Breaking: Work IQ Word and ODSP tools no longer return
raw.read_word_documentnow returnscommentsas a wrapped string, plusdrive_id,document_id, andsensitivity_label;create_word_documentaddsdrive_idanditem_id. - Breaking:
get_a365_file_metadata_by_urlnow returnsnameinside the XPIA envelope. Work IQ ODSP tools raise a typed error when Work IQ reportsisError, instead of returning empty fields. add_word_commentreturns awarningwhen Work IQ reports success without a comment ID.ENTRABOT_FILES_MAX_TEXT_BYTESfalls back to the default, with a logged warning, for a zero, negative, or unparseable value. A negative value used to disable the cap.ENTRABOT_FILES_DENIED_SITESis now checked against the site Graph reports for the item inlist_workbook_worksheets,read_workbook_range,read_excel_workbook,add_excel_comment, andreply_to_excel_comment, and these refuse the request if the site can't be confirmed. Those checks only run when a denylist is set. The denylist is not enforced by the Work IQ Word and ODSP tools, andread_fileandadd_file_commentstill check thesite_idthe caller supplies.scripts/spike_a365_work_iq.pynow validates both Word and Excel, including their scope and audience.- With cloud memory, the watched-chat list and email cursor now live in the storage container instead of local files, so instances on one container share them and a machine moved to another container can't carry over stale values. Upgrading a cloud deployment: on first boot the email cursor starts at "now", so mail received before that first boot isn't pushed but stays readable through
read_email; after that, a restart reuses the cursor and mail received during downtime is pushed as normal. The watched list comes from the cloud copy plus auto-discovery, and old localwatched_chatsandemail_cursor.txtfiles are ignored and can be removed by hand. Upgrade every instance on a container together. See "Upgrading to shared poll state" in Migrations and upgrades. - Chat auto-discovery reads every page of
/me/chats, up to 20 pages. A chat first seen through discovery or the shared list surfaces its newest message only if it is under 24 hours old. - Setup's cloud-memory migration no longer copies local poll state: the watched-chat list, both email cursor files, the legacy
chat_idkey, and the per-chat cursors underchat_cursors/. A machine's copies may be stale or belong to another agent, and a copied cursor would skip the 24-hour first-sight check.
Fixed¶
- Two instances on one container can no longer push the same email: each poll claims its window of mail with a compare-and-swap before pushing.
- A failed Graph read during a chat's bootstrap no longer leaves it with no watermark, which could push up to 10 old messages on the next cycle.
bootstrap_body_statereports the real watched-chat count on a cloud backend, andnullrather than0if the list can't be read (#64).scripts/catch_up.pyreads the configured backend instead of a hardcoded home-directory path.- Channel pushes now mark outside content as data. A Teams push lost the XPIA envelope to the push sanitizer and an email push never had one, so
wrap_for_channelwrites a bracket form a push can carry. A verified sponsor's Teams message stays outside the envelope, and all email is wrapped. Sender names and subjects on an email push also lose their angle brackets, as a Teams push already did. - Attaching a machine with
setup.sh --use-blueprintno longer removes other machines' Blueprint certificates: it adds to the list, and--remove-all-existing-certsis the only way to replace it. That flag removes certificates only and does not delete the Blueprint.deploy-windows.ps1rotation also keeps other machines' certificates (#124). setup-windows.ps1now registers the Blueprint certificate it generates; before, a fresh Windows machine held a certificate Entra had never seen and failed at Hop 1. Re-running setup registers an existing local certificate the Blueprint lacks, other machines' certificates are kept, and the.envthumbprint lines are replaced instead of appended.setup-windows.ps1now writes the Blueprint, agent and Agent User IDs and a default sponsor to.env. The runtime reads identity only from the environment and.env, so a machine set up with it before had no identity to sign in with.- Re-running
setup-windows.ps1no longer moves a machine from cloud memory to local memory. Without-UseCloudMemoryit addedENTRABOT_KEEP_MEMORY_LOCAL=true, which outranks the blob settings; the memory lines are now replaced instead of appended, and a machine already on cloud memory stays there. A blob provisioner that prints no endpoint now falls back as intended instead of stopping setup. - A failed Graph read can no longer look like an empty certificate list.
verify_blueprint_cert.pyandfind_local_blueprint_cert.pyexit3for "could not check",list_blueprint_certs.pyexits non-zero and prints no count, and setup stops instead of regenerating. - A new Blueprint private key stays under a temporary keystore entry until its certificate is confirmed on the Blueprint, so a failed upload no longer strands the machine.
setup.shno longer trusts the thumbprint cached in the state file. It reuses a certificate only when this machine's private key matches one registered on the Blueprint, and it always runsregister_blueprint_cert.pywhile a new key is still pending, so a re-run after an interrupted or unconfirmed certificate update recovers instead of carrying on with a credential that is about to be revoked.--remove-all-existing-certschanges nothing on the machine until the replacement is confirmed.verify_blueprint_cert.pyis now an operator tool that setup does not call.- An expired Blueprint certificate is no longer reused:
find_local_blueprint_cert.pyand pending-key recovery treat it as no match, so setup registers a new one (the expired entry stays on the list). Tworegister_blueprint_cert.pyruns by the same OS user can no longer overlap and clobber the shared pending key, and the state file is written atomically so a full disk can't leave it truncated. - Re-running
setup.shfor the same Agent User no longer changes the sponsor or moves an agent on cloud memory to an empty local store. Setup reads the existing.envfirst and keeps the sponsor (ENTRABOT_HUMAN_*) unless--teams-useror--switch-useris passed, and keeps the blob settings unless--keep-memory-localor--use-cloud-memoryis passed; the repair commandsetup.sh --use-blueprint=<id>passes neither. A failed blob provisioning now shows the provisioner's output and keeps the cloud settings (or falls back to local) instead of ending setup silently after.envwas already rewritten. A run for a different Agent User carries nothing over, and other lines added to.envby hand are still dropped. - Self-authored Teams messages remain filtered after an Agent User display-name change.
- Forged
<external_content>text can no longer bypass the authoritative XPIA boundary. - A UTC-midnight-dependent test no longer fails intermittently on Windows CI.
- Work IQ Word and ODSP tool output now passes model-facing content only through the XPIA boundary, and Word comments are no longer dropped from the parsed result.
Removed¶
- Bot Gateway mode and its M365 Agents SDK dependency (ADR-006).
- The unused Claude pull-request review workflow.
v0.1 — 2026-05-21¶
First public release. Reference implementation for Microsoft Entra Agent ID and Microsoft Agent 365 (GA 2026-05-01). MIT licensed. Research repo, not production-ready — see Known Limitations below.
Added¶
Identity & auth
- Three-hop certificate chain: Blueprint → Agent Identity → Agent User. Private key in OS keystore (Keychain / Windows TPM via CNG / Linux Secret Service). No client secret in flight, no .env secrets on disk.
- Three auth modes: agent_user (full three-hop), delegated (MSAL interactive), bot (Bot Framework + M365 Agents SDK).
- Progressive identity state machine (UNAUTHENTICATED → DELEGATED → PROVISIONING → AGENT_USER).
- Cross-tenant federated B2B chat — guest UPN auto-decoded, home tenant resolved via OIDC discovery.
- Sponsor gate enforcement on every mutating tool (share_file, add_teams_member); requester must be an authorized sponsor of the chat.
Agent capabilities (34 MCP tools)
- Teams: send, read, create chat, list/add members, watch replies, placeholder + resolve, delete own message, image view.
- Outlook: send_email with thread-preserving replies, daily summary at 5pm PDT.
- Files: resolve_file_url, list_recent_files, read_file, add_file_comment.
- Agent 365 Work IQ Word: create/read documents, add/reply to comments.
- Promises (durable across restart): add, list, resolve.
- Identity: whoami, audit_log, on-demand daily summary.
Channel push & polling fallback
- notifications/claude/channel push extension — Claude Code gets inbound Teams DMs and emails as next-turn system reminders. The Teams conversation IS the conversation with the agent.
- send_teams_message auto-blocks on non-Claude-Code hosts (Copilot CLI, Codex, Cursor) for the sponsor's reply. Deterministic, host-detected, no parameter the model can disable.
Body prompt architecture
- Non-overridable body prompt at prompts/agent_system.md with @include expansion of prompts/anatomy/*.md. Security, channel discipline, identity/tools rules load below the persona line.
- Body-first instruction boundaries and channel discipline designed to reduce instruction-injection and operator-impersonation risk.
Mind / persona (optional)
- Persona-sati MCP integration. Body composes body + persona at boot when PERSONA_SATI_MCP_URL is set. Clean fallback to body-only mode when persona-sati is unreachable.
Storage - ADR-005 Phases 1, 2, 5, 6a: opt-in cloud-hosted memory via Azure Blob. Per-Agent-User container, RBAC scoped to the agent's object ID. Local filesystem fallback.
Agent 365 Work IQ provider
- Reusable entrabot.a365 provider boundary for Work IQ MCP servers (Word, Mail, Copilot, Dataverse). Manifest loading, audience-specific token acquisition, MCP-client calls. Teams intentionally remains Graph-native.
Discipline
- 1,237 tests, ruff clean, 80% coverage threshold enforced.
- 66 hard-won learnings (now archived at engineering-history/research/hard-won-learnings.md).
- Full docs site at https://microsoft.github.io/entrabot/ with API reference, script reference, ADRs, platform learnings, and runbooks (the original structure; later reorganized — see Unreleased → Changed).
Known limitations¶
- Windows port is acceptance-tested on one ARM64 VM only. Verified end-to-end (cert provisioning, three-hop CNG signing, MCP registration, Teams DM round-trip) but not exercised on broad hardware, intel x64, or in long-running sessions.
- Bot Gateway needs live Azure Bot Service registration. The local server + Dev Tunnel path works; the productized "bot has its own Teams app identity" path needs an Azure Bot resource and Teams app manifest signed in a tenant.
- Persona-sati 12h MCP bearer refresh is open (draft PR persona-sati#47 paused at Agent Blueprint OAuth client constraints). Affects only sessions that connect to persona-sati after the 12h boundary; restart the MCP host to recover.
- Agent Identity provisioning omits
Application.Read.All, whichwait_for_sponsor_dmneeds to call the sponsors API. Manual grant required as a workaround untilscripts/create_entra_agent_ids.pyis updated. add_file_commenton.docxfiles returns 404 on Graph beta/comments— Microsoft does not expose Word document comments there. Use the Work IQ Word path instead (add_word_comment/reply_to_word_comment).- CLI commitment-language detection is not enforced. Server-side detection fires on outbound Teams text; commitments made to the operator in the host terminal silently drift. Mitigation is body-prompt strengthening, not mechanical enforcement.
APIs may change in 0.x releases. Pin to a commit SHA or a release tag for stability.
Provenance¶
History was rewritten with git filter-repo before this release to remove pre-sanitization PII commits (real Microsoft employee names, emails, production tenant GUIDs, internal codenames). The repo went from private to public at this release. Commits before f2a3c18 no longer exist.