Skip to content
🧬 The Digital Twin🟣 Code🚧

🧬 The Digital Twin ​

🚧 Work in progress

Scenario 1 is still being built and tested. Steps, downloads, and screenshots may change before the event.

You'll build this in code – VS Code, GitHub Copilot, and the Copilot CLI.

You get the contract and the plumbing. You write the agents.

What you're solving ​

Copilot already personalizes. It has memory, and Work IQ reads your mail, calendar, and files.

What it doesn't have is anything you've explicitly decided – how you rank competing priorities, which promises you protect, what you'd refuse outright. That gets inferred, you can't inspect or correct it, and it doesn't travel between tools.

Written down, it becomes a first line of defense: something that reads what arrived and gives you an opening position before you've touched it.

There's a second problem this altitude solves: a decision under conflict isn't one voice. When an exec ask lands on top of a peer promise, you're weighing ambition against obligation against capacity. A single prompt can role-play one of those at a time. It can't run the argument and tell you what it overruled.

What your team will have built ​

One system, not five. Everyone shares a single spec, and each person owns one agent that reasons over it.

PieceWhat it does
The specThree plain-text files: how the person decides, how they write, what their calendar shows
The councilThree agents that argue, one that decides and publishes what it overruled
The criticWatches a miss, works out which line of the spec caused it, proposes a patch
The serverExposes the whole thing as MCP tools, so any agent can call it
The guardrailRefuses actions outside the spec's boundaries – enforced at the tool, not suggested in a prompt

The same spec folder runs in Cowork and the CLI unmodified.

The council takes three kinds of situation. Same agents, same spec – only the input changes:

InputWhat the council returns
What landed – an email, a request, a messageA position, a draft, and the dissent it overruled
What's ahead – the next week of calendarWhat to protect, what to cut, what to move
Who I'm facing – a person or an upcoming meetingHow this person is handled, and what's off-limits in writing

Before you start ​

Unzip it wherever you keep projects, then open the folder in VS Code. No cloning, no repo to fork.

Prefer one line in a terminal?

PowerShell – downloads, unzips, and opens it in VS Code:

powershell
$u='https://microsoftlearning.github.io/AI-Flight-Academy/downloads/digital-twin-starter.zip'
$z="$env:TEMP\dts.zip"; iwr $u -OutFile $z
Expand-Archive $z -DestinationPath "$HOME\digital-twin" -Force
code "$HOME\digital-twin\digital-twin-starter"

macOS / Linux:

bash
curl -L -o /tmp/dts.zip https://microsoftlearning.github.io/AI-Flight-Academy/downloads/digital-twin-starter.zip
unzip -q /tmp/dts.zip -d ~/digital-twin && code ~/digital-twin/digital-twin-starter

Add the persona pack the same way, swapping in avery-persona-pack.zip.

Check this first, before anything else: open Copilot Chat in VS Code and confirm you can switch to Agent mode. It's an org-managed setting and it may be off. If it is, use the Copilot CLI instead – everything here works either way.

Want a step spelled out?

The Guides in the top nav cover the general skills used here – setting up, connecting to your work, and running things on a schedule. They're not scenario-specific, so open one in a new tab if you get stuck on a mechanic.


1 Β· Seal your answers ​

Do this before you build anything. It takes four minutes and you can't do it later.

powershell
python test/take_test.py

Fifteen forced-choice work dilemmas. Answer fast and honestly – what you actually did last time, not what you'd like to have done. It writes test/sealed-answers.md and you don't look at it again until the end.

Don't deliberate

The script flags any answer that took more than 25 seconds. A considered answer is an aspirational one, and aspirational answers make the comparison worthless.

At the end of the session your twin answers the same fifteen cold, and you diff them. That's not a score – it's the fastest way to find which rules you got wrong.

Done when: test/sealed-answers.md exists and you haven't looked at it since.

2 Β· Split the work ​

Five people, five agents, one shared spec. Decide this in the first two minutes.

WhoAgentArgues forReturns
1AmbitionThe visible, strategic, reusable workposition Β· because Β· cost if ignored
2ObligationPromises already made, people waitingposition Β· because Β· cost if ignored
3CapacityWhat the calendar says you can actually absorbposition Β· because Β· cost if ignored
4ArbiterNothing – it decides, in voice, and publishes the dissentdecision Β· draft Β· dissent Β· confidence Β· gap
5CriticNothing – it diagnoses misses and patches the specroot cause Β· diff Β· net lines

Fewer than five? Fold the Critic into the Arbiter. More? Add someone on tests and someone on the demo.

Done when: every person knows which file they own.

The drives should be biased

Ambition shouldn't be balanced. Neither should Obligation or Capacity. Each one argues its corner as hard as it can – the Arbiter is where nuance happens. Balanced sub-agents produce mush.

3 Β· Write the spec ​

Three files, shared by everyone. Get a rough version fast, then improve it all session.

soul – how the person decides. Aim for about a page. The critical part is tiebreakers, not values:

❌ Valueβœ… Tiebreaker
I value responsiveness.When a same-day exec ask collides with a peer promise, cut scope before slipping the peer.
I care about quality.When a claim can't be verified before the deadline, cut that section and hold the date.

A value tells an agent nothing. A tiebreaker tells it what to do.

Done when: the spec is about a page and every rule resolves a conflict.

voice – 5–10 real sent emails, verbatim, plus the rules those samples imply. Do not clean them up; the punctuation and signoff habits are the part a description would lose.

revealed – what the calendar actually shows.

Shorter really is better

Resist adding one more clause. Anthropic found while building Constitutional AI that broad principles beat long specific ones. "Lost in the Middle" (peer-reviewed, Transactions of the ACL) showed models pay least attention to whatever's in the middle of a long prompt. Your spec is a prompt.

4 Β· Ground it in real behavior ​

This is the half a person can't self-report. Pull it from evidence.

Your own data – let Cowork's Work IQ do the retrieval and export the summary. Ask for percentages, not raw entries:

text
Summarize my last 30 days of calendar: time by category, accept/decline/tentative
ratio, self-organized vs invited ratio, recurring load. Use percentages.
Leave out private meeting titles.

Avery Washington – the persona pack has a 30-day calendar with everything already in it.

Either way, extract these four:

  • time by category
  • response latency by sender – the honest stakeholder ranking, whatever the org chart says
  • accept / decline / tentative ratio
  • self-organized vs. invited

Done when: revealed.md cites at least one number that contradicts the spec.

Don't build a Graph integration

Azure AD app registration plus MSAL will take 30–60 minutes and eat your session. Roughly a third of teams that try it never get past it. Let Cowork retrieve, or use the persona pack. Your working style is slow-moving data – a monthly snapshot is plenty.

5 Β· Write the agents ​

Five files in .github/agents/. The starter repo has the required frontmatter and return contract for each – the bodies are yours.

This one will cost you 20 minutes if you miss it

The Arbiter won't actually delegate unless you tell it to, in those words:

text
You MUST invoke each agent as a subagent before synthesizing.

Without that line it'll just answer directly and you'll wonder why the council never runs.

Two more that bite:

  • Force short returns. Three verbose sub-agents will blow the Arbiter's context window before it reasons.
  • Run with --allow-all-tools in your own repo, or you'll spend the session clicking approval prompts.
Agent file anatomy

Each agent is a markdown file in .github/agents/ with frontmatter declaring what it can reach:

md
---
name: capacity
description: Argues from measured load. Subagent β€” never replies to the user.
tools: ['read']
---

You are one of three competing drives. You are CAPACITY.

Read `digital-twin/references/revealed.md` FIRST, then `soul.md`.
You argue from measured reality, not stated intent.

You are biased. That is your job. Do not be balanced.

**You are a subagent. Do NOT reply to the user. Return your position to the arbiter.**

Return exactly:
POSITION: <one sentence>
BECAUSE: <two sentences max, citing a measured number>
COST IF IGNORED: <one sentence>

The Arbiter is the only one that talks to the user, and it declares its children:

md
---
name: arbiter
description: Polls the drives, decides in voice, publishes the dissent.
tools: ['read', 'edit', 'agent']
agents: ['ambition', 'obligation', 'capacity']
---

Running it from the CLI:

powershell
copilot -p "Run the council on this dilemma: <text>" --allow-all-tools

On Windows the command line caps at 8191 characters, so long prompts have to go through a file – twinlib.py in the starter repo handles that for you.

6 Β· Make it answerable to other agents ​

Here's the honest question about this step: if it's your twin and only you call it, why does it need to be a server?

It doesn't. Reading three files works fine. The server earns its place the moment something that isn't you needs to ask:

Who's askingWhat they need
A teammate's agent, before drafting something for you"Would they sign off on this, or should I ask first?"
A triage agent watching your inboxYour boundaries, before it replies to anything
An agent in a product you don't controlA twin it can call, not a file it can't see

And the part that only works as a server: your boundaries hold. If your twin is a file, a calling agent can read your rules and ignore them. If it's a tool that returns NEVER, it can't. That's the difference between a preference and a policy.

So: the file is your twin. The server is what makes it a service.

Thin tools first. These just read files, so they're instant and free:

text
soul_spec()   voice_rules()   revealed_behavior()
check_boundary(action, recipient)   soul_gap()

Thick tools if you have time. These spend model calls:

text
twin_decide(situation)   twin_draft(recipient, intent)
twin_plan(window)        twin_prep(person_or_meeting)
propose_soul_patch(what_it_said, what_they_would_do)

twin_plan and twin_prep are the same council over a different question – no new agents, no new files.

The split matters: Cowork connectors need answers in under 30 seconds, which the thin tools clear easily and twin_decide never will.

Prove it in your demo

Don't just show the tool list – that proves nothing. Wire a second agent to your server and have it ask before acting. One agent checking with another person's twin is the whole point, and it takes about two minutes to show.

Check it works before you build anything on top:

powershell
python test/mcp_smoke.py

That connects the same way VS Code will, lists your tools, and runs your guardrail against four sample actions. If it returns NEVER / ASK_FIRST / ALLOW, another agent can call your twin and your boundaries will hold.

Done when: the smoke test returns real verdicts instead of "not implemented".

Building the server
python
from mcp.server.mcpserver import MCPServer

mcp = MCPServer(name="digital-twin", version="1.0.0")

@mcp.tool(description="How this person decides. Call before drafting or prioritizing anything.")
def soul_spec() -> str:
    return (REFS / "soul.md").read_text(encoding="utf-8")

if __name__ == "__main__":
    mcp.run(transport="stdio")            # VS Code, Claude Desktop
    # mcp.run(transport="streamable-http", host="127.0.0.1", port=8848)

The description is not documentation – it's how a calling agent decides whether to use your tool. Write it as an instruction: "Call this before drafting anything on their behalf."

Wire it into VS Code with .vscode/mcp.json (already in the starter):

json
{ "servers": { "digital-twin": {
  "type": "stdio", "command": "python",
  "args": ["${workspaceFolder}/mcp_server.py"] } } }

Then Ctrl+Shift+P β†’ MCP: List Servers β†’ start it. Switch Copilot Chat to Agent mode and your tools appear under the πŸ”§ icon.

Two transports, two audiences. stdio for local editors. streamable-http is the shape a Cowork connector needs – JSON-RPC 2.0 over HTTPS. Production would also need TLS and OAuth; local is fine today.

7 Β· Add the guardrail ​

One check that runs before anything leaves the system:

text
ALLOW | ASK_FIRST | NEVER
rule:   <the rule that governs this>
source: <which file and section>

Run it before any send, share, commit, decline, or external message.

Enforce it at the tool, not in a prompt. That's the difference between a suggestion and a boundary – yours has to hold even when the caller is an agent you didn't write.

What a guardrail looks like
python
@mcp.tool(description="Check whether an action is allowed before taking it. "
                      "Call before any send, commit, decline, or external message.")
def check_boundary(action: str, recipient: str = "") -> str:
    a = f"{action} {recipient}".lower()

    if any(k in a for k in ("why", "reason", "travel", "calendar", "ooo")):
        return ("NEVER\nrule: never disclose calendar reasons or travel. "
                "A decline says WHEN I'm free, never WHY I'm not.\n"
                "source: soul.md > Boundaries")

    if any(k in a for k in ("external", "customer", "commit", "deadline")):
        return ("ASK_FIRST\nrule: anything external, or any date commitment.\n"
                "source: soul.md > Boundaries")

    return "ALLOW\nrule: no boundary governs this\nsource: soul.md > Boundaries"

Note it returns the rule and where it came from, not just a verdict – so a calling agent can explain itself, and a human can audit it.

Try this in your demo: have another agent ask your twin to explain why the person is out next week. Watching it return NEVER – with a citation – is the moment the room understands the difference between a boundary and a polite request.

8 Β· ⚑ The twist ​

Your facilitator will hand this out partway through. Run it through the full council:

text
A senior executive wants a new customer narrative by 3 PM today.
You already promised a peer their launch review notes by 4 PM.
Decide what to do, what to say to each of them, and what gets cut.

You are checking for three positions, one decision, and an explicit statement of what got overruled and why.

9 Β· Let the critic patch it ​

Find one thing the twin got wrong. Have the Critic diagnose which line of the spec caused it – not "the answer was bad."

text
HARD CAP: net growth of +2 lines.
Human approves before anything is written.

Without the cap, the critic fixes every miss by adding lines, and the spec degrades as it grows.

Say the honest limit out loud in your demo

The file gets better. The model doesn't learn. Every run reloads an improved file. That's real and useful, and it isn't training – claiming otherwise is the fastest way to lose a technical room.

10 Β· The reveal ​

Last thing. Your twin answers the fifteen dilemmas you sealed at the start, cold:

powershell
python test/compare.py

You get a line-by-line comparison, and for every mismatch it names which field of soul.md that dilemma was probing – so a miss points straight at the rule to fix.

This isn't a score

There's no percentage and no leaderboard. Mismatches are the useful output: each one is a place where what you wrote down doesn't match what you'd actually do. Expect several. The interesting ones are where the twin picked the sensible, well-adjusted answer and you wouldn't have.

Fix one and re-run it if you have time. That's the whole loop in miniature.


Show it off ​

60–90 seconds. Hit these:

  • [ ] The folder – spec, agents, server
  • [ ] One thing the calendar revealed that self-report would never have caught
  • [ ] The council: who owns what, and the short return shape
  • [ ] The twist, run live
  • [ ] Arbiter output – especially the dissent
  • [ ] One of the other two modes – the week plan, or the person prep
  • [ ] The guardrail returning NEVER on something
  • [ ] One critic patch, +2 lines or fewer
  • [ ] The same spec folder running somewhere else – CLI, VS Code, or Cowork
  • [ ] One dilemma your twin got wrong, and the rule you'd add because of it

Lead with the dissent

Show what the twin decided against and why. That is the part a single prompt cannot produce, and it is visible in one screen.

Stuck? ​

What you're seeingWhat to do
Agent mode is missing in VS CodeOrg-managed setting. Use the Copilot CLI instead
The Arbiter answers without consulting anyoneAdd "You MUST invoke each agent as a subagent before synthesizing"
Output truncates, context blows upSub-agents are too verbose – force short structured returns
Endless approval prompts--allow-all-tools, in your own repo only
Long prompts fail on WindowsCommand line caps at 8191 chars – pass via a file. twinlib.py does this
Custom agents aren't picked upThey must be in .github/agents/ with the right frontmatter
Every drive agrees with every otherThey're too balanced. Make each one biased
mcp_smoke.py fails to connectRun it from the repo root, not from inside test/

← Back to start Β· What this scenario is about