copilot-harness-sdk · tutorials
Rung 1 · proved 3/3

Starter with live tools: three agent.py files that really run

Hacker News, ManageMemory and ContextMemory are the three agents every RAPP brainstem ships with. Here they become one Copilot Studio agent whose tools are real: a custom connector plus an agent flow for Hacker News, and two Dataverse tools for memory. The agent's answers come from those tools, not from the model's memory.

Copilot Studio Build page of the Brainstem Starter agent: three skills (recall-memory, fetch-hacker-news, manage-memory) and three tools (Run RAPP Hacker News workflow, List rows, Add a new row) beside the generated instructions
What the published agent looks like in the Studio: three skills, three tools, the generated routing instructions.

What goes in

agent.pyProfileProvisioned in the environment
HackerNewshackernewscustom connector "RAPP Hacker News" (code-based, no credentials), an agent flow that runs the original aggregation, a WorkflowTool, the fetch-hacker-news skill
ManageMemorymemory-writeDataverse "Add a new row" tool on the annotations table, the manage-memory skill
ContextMemorymemory-recallDataverse "List rows" tool, the recall-memory skill

The profiles live in tutorial/profiles/. An agent that matches none is still deployed, as a reasoning-only skill carrying its code.

Build and deploy

  1. Sign in: az login --tenant <tenant> --allow-no-subscriptions as a member of the environment's tenant, then pac auth create --environment https://<org>.crm.dynamics.com/.
  2. Run the tutorial. The default pulls the three agents from the public RAPP Agent Registry; --agent-files takes your own copies instead.
    npm run tutorial -- --environment https://<org>.crm.dynamics.com/ --name "Brainstem Starter" --publisher-prefix rapp \
      --token-command "az account get-access-token --resource https://<org>.crm.dynamics.com/ --tenant <tenant> --query accessToken -o tsv"
  3. The first run in an environment stops and prints two maker-portal links: create the "RAPP Hacker News" connection (no credentials, click Create) and the Dataverse connection (sign in as yourself). The script polls until both exist, then deploys. Later runs find them.
  4. Read the last lines: DEPLOYED (GitHub Copilot harness), the bot id, and the Studio link.

Prove it

Tool-backed agents refuse the anonymous route ("No user access information available for invoker mode"), so the proof signs in as a user. One-time setup in the environment's tenant: a public-client Entra app with the delegated Power Platform API permission CopilotStudio.Copilots.Invoke.

API=8578e004-a5c6-46e7-913e-12f58912df43     # Power Platform API (first-party service principal)
SCOPE=$(az ad sp show --id $API --query "oauth2PermissionScopes[?value=='CopilotStudio.Copilots.Invoke'].id | [0]" -o tsv)
APP=$(az ad app create --display-name "Harness Prover" --sign-in-audience AzureADMyOrg --is-fallback-public-client true --query appId -o tsv)
az ad sp create --id $APP; az ad app update --id $APP --public-client-redirect-uris http://localhost
az ad app permission add --id $APP --api $API --api-permissions "$SCOPE=Scope"
az ad app permission grant --id $APP --api $API --scope CopilotStudio.Copilots.Invoke

Then the proof. The first run prints a device-code sign-in; with cacheFile (the SDK's createDeviceCodeTokenProvider option) every later run is silent.

ENTRA_CLIENT_ID=$APP ENTRA_TENANT_ID=<tenant> node tools/prove-copilot.mjs --mode 3p \
  --environment-url https://<org>.crm.dynamics.com/ --environment-id <env-id> \
  --bot-id <bot-id> --schema rapp_BrainstemStarter --turns turns.json
PromptExpectedAnswer (16 Sep 2026)
What are the top 3 stories on Hacker News right now?a numbered list with points and comment links"Top Hacker News stories: 1. **[Introducing System One Models and Jev](…)** — 677 points, by albelfio · [217 comments](…) 2. …"
Remember this preference exactly: I read Hacker News every morninga stored confirmation"Successfully stored preference memory in shared memory: "I read Hacker News every morning""
What do you remember about my reading habits?Hacker News, morning"Here's what I remember from shared memory: - Memory content (verbatim): "I read Hacker News every morning" (Theme: preference, Recorded: 2026-09-16 00:31:17) …"

Same prompts in the Studio's Preview page show the skill being loaded and the tool being called; rung 2 has those screenshots.

The solution package

rappBrainstemStarterHarness.portable.zip is the solution exactly as the deploy imported it, with the environment's org URL and the custom connector's internal id replaced by {{ORG_URL}} and {{HN_CONNECTOR}}. scripts/portable-solution.mjs localize writes your values back before import. See the manual way, including why this rung's solution is the import package rather than a fresh export.