microsoft/cowork-roi-report
| Generates a Microsoft-branded "What Cowork Did for Me" self-contained HTML impact report from the signed-in user's own Copilot Cowork session history in OneDrive. Quantifies leverage as a speed multiplier and a professional-services-equivalent value using research-anchored task-category bands and an artifact-scaled two-clock model, with a live hourly-rate control, KPIs, a work-by-business-process breakdown, a projects-by-category view, and an activity heatmap. Use when the user asks to "generate my impact summary report", "generate my impact report", "my impact summary", "my Cowork ROI", "what Cowork did for me", "Cowork impact report", "Copilot Cowork ROI report", "how much time has Cowork saved me", "my Cowork value report", or any request for a personal impact / ROI / value report on Copilot Cowork usage. or daily briefings.
npx skills add https://github.com/microsoft/What-I-did-with-Cowork --skill cowork-roi-report
Builds a personal, shareable impact report from the user's own Copilot Cowork footprint. Self-service:
reads the signed-in user's Cowork session workspaces in OneDrive, classifies the work into the eight
methodology categories, applies research-anchored time-savings bands, and renders a Microsoft-branded HTML
web app; optionally automates itself and emails a digest. Generic — works for any user; no data hard-coded.
microsoft/What-I-Did-Copilot run locallystakeholder-comms; single-meeting recaps → meeting-intel; daily wrap-up → daily-briefingAskUserQuestion, two questions)Do not schedule until the user explicitly chooses the automate option in Q2 (the platform also shows its own approval dialog).
GetMyDetails(select="mail,userPrincipalName,displayName") → user name + email.after = N days ago 00:00 local; before = today 23:59 local; window.label = "Last N days", window.months = N/30 rounded (legacy).Cowork persists each session's workspace to OneDrive under Documents/Cowork/ — the artifacts are the
signal. Harvest all three layouts (users/versions differ):
Documents/Cowork/Tasks/<goal-slug>-<YYYY-MM-DD>/ → input/+output/Documents/Cowork/<goal-slug>-<YYYY-MM-DD>/ → input/+output/Documents/Cowork/sessions/<session-uuid>/ → input/+output/GetDefaultDrive() → personal OneDrive drive_id.Cowork 2, Colaborar, …). Try/Documents/Cowork; on 404 list /Documents, pick the child starting Cowork (prefer exact, else highest N),
else /Cowork, else ask once. Carry the resolved name forward.
Tasks/, root goal-folders, sessions/) and follow pagination toexhaustion — a page caps at ~20–100 items.
createdBy.application.id = the **Cowork app id6ab48b67-cd74-4ad4-81af-5932984589be** — never key on folder *names*.
Documents/Apps/… — that tree is M365 Copilot "Scout" (Graph app 99fa64eb-…), not Cowork.createdDateTime/lastModifiedDateTime falls in the window.GetDriveChildren into output/ (and input/) to collect filenames, extensions andper-file createdDateTime (parallel batches).
exec_min fromfile-timestamp spans (leave null; prefer telemetry). Fold supporting files (screenshots, variant HTML,
READMEs, lock files) into the session's primary deliverable. Keep output-less sessions (empty outputs →
classify.py tags general).
mine_session.py --log …/cowork-session-telemetry.json logs each session's exec_min, tool intensity and artifacts. In the harvest, merge in any session id not covered by a Cowork folder (has_folder:false, outputs:[]); prefer telemetry exec_min where both exist. Forward-only.A session contains one or more run tasks; each run task maps to exactly one of the eight categories
below.
Value model = RUNS × BAND. Time saved = Σ over runs of each run's category band (minutes saved/run); each
band already sums the activity chain inside one run, so count runs × band — never per-LOC or per-artifact
add-ons. Count runs from tool-chains (code run ≈ 6 code-edits; analysis run ≈ 5 research calls); mine_session.py
writes a runs:{category:count} field. Without telemetry, estimate runs conservatively and label as estimates.
compute.py applies Σ runs × CATS[band].
Use the deterministic classifier — do NOT hand-tag categories. Write the harvested sessions (with
inputs, outputs and exec_min) to working/cowork_raw.json, then run:
python scripts/classify.py --in working/cowork_raw.json --out working/cowork_sessions.json --overrides working/process_overrides.json.
It maps each session's real artifact extensions to categories (e.g. .xlsx/.csv→analysis, .docx/.pptx/.pdf→document,
.html/.py/.ps1→code, .zip→special), caps ~2 run tasks/session, and tags output-less sessions general.
This is the fix for the failure mode where every session was stamped with the same category pair and every
goal collapsed to the same hours — never assign the same default categories to every session. You may bump
a clearly analytical deliverable (a synthesis report saved as .docx/.pptx) to analysis, but the extension
map is the default. Be conservative — credibility matters more than a big number.
Category choice follows the Cowork usage taxonomy (Description/Examples). classify.py is description-driven: Analysis & Research only from analytical goal text (synthesize, compare, brief from multiple sources), NOT a file type; a built spreadsheet is Document & content creation, not analysis. Email workflows (Outlook) and Communication workflows (Teams) are the same ideas on different surfaces. The 8 labels, 2-per-session cap, PRIORITY tie-break and document output-gate are unchanged; deterministic. Full rules: references/classification-methodology.md.
Extension→category heuristics, counting discipline, and the exact working/cowork_raw.json
schema classify.py consumes are in references/classification-reference.md.
Key rules: cap ~2 run tasks/session, fold supporting files into the primary task, and report
categories with no artifacts as zero (a conservative floor).
Required — else the Skills-augmented and Deliverables tables render empty. Tag each output (and each
chat-only session) with the professional skills Cowork exercised, ONLY from scripts/skills_vocabulary.json:
a skills:[...] array per outputs[] item (or session-level for chat-only). Also tag professional_roles:[...]
per session — the 1–2 roles a billing firm would charge (guide: scripts/roles_taxonomy.json) — driving the
"Roles Cowork assembled for me" section; if omitted, classify.py keyword-matches. **Tag conservatively from the
deliverable** (e.g. .pptx→*Presentation Design*; .docx→*Technical Writing*; skill .zip→*System Architecture*/
*Prompt Engineering*; .xlsx→*Data Analysis*) — never invent a skill outside the vocabulary. Past OneDrive-only
sessions are inferred, not measured — note that in the report.
Business Process is the aggregation anchor; JTBD and Project nest under it. Process/Project names are
kept STABLE across runs by a durable taxonomy memory, so the model doesn't re-invent names each run.
The memory is PER-USER and never shared. The registry filename embeds a sanitized key from the user's
email (/mnt/user-config/.claude/cowork-process-registry.<userkey>.json) and carries an owner field on the
user's own mount. reconcile_taxonomy.py derives path+owner from meta.email (pass --owner) and **ignores
any registry whose owner ≠ the invoking user**, so a first run mints processes from the user's OWN sessions.
Nothing user-specific is committed to the skill folder; overrides are scratch under working/.
working/cowork_raw.json (step 3) andBEFORE classify.py, run:
python scripts/reconcile_taxonomy.py --in working/cowork_raw.json \
--owner "<signed-in user's mail>" --overrides working/process_overrides.json
Per session it (a) matches a known Project and reuses {process,pillar,jtbd}; else (b) matches an
existing Process by keyword and registers a new project under it; else (c) mints a new Process
("new":true). Writes working/process_overrides.json and persists the owner-stamped registry.
NEW processes minted, tell theuser the new name(s) and offer to rename — edit the registry's processes/projects and re-run
reconcile_taxonomy.py. On unattended/scheduled runs it auto-creates the flagged entry and never
blocks. (On a genuine first run EVERY process is new — that is expected, not an error.)
classify.py then reads the overrides via --overrides working/process_overrides.json (eachsession → {process, pillar, job, jtbd}; job is retained = the process name only for back-compat
with the not-yet-migrated member skill — it is not shown in this report). Pillars follow
references/value-pillars.md; the registry stores each process's pillar.
reconcile_taxonomy.py can't run, classify.py falls back to scripts/apqc_taxonomy.json. Optional:
references/map-my-work-playbook.md to enrich novel process/JTBD naming.
The report's Work by business process section pivots on Process: each process is an accordion with
its subtotal (sessions · hours · value · % of time), the distinct JTBD(s) it served, and the
projects beneath it. A secondary By pillar toggle groups the same projects by value pillar.
(Report layout: a single "Your projects" table is the one place projects are listed, sitting under the KPI cards. A single Group-by dropdown (Process/Category) regroups rows in place, read once; rows render server-side (never blank). Columns: Project · Cowork-fit · Hours · Value. Each row carries a Cowork-fit dot (single-surface test): H green = build/automation, any Specialized workflow, or cross-surface; M yellow = borderline; L red = one surface. Two-layer hybrid: a deterministic rule sets the baseline, then an LLM review may confirm/adjust each grade (a keyword can’t truly judge capability). Each dot is flagged rule-based/AI-reviewed; hover shows reason + method. The Roles × projects heatmap replaces the flat role list under "Roles Cowork assembled for me"; only Deliverables stays behind a toggle.)
> Memory-first + packaging: each run locates the user's own owner-scoped registry and aligns to it (only
> novel work adds a name). NEVER bundle the registry, any cowork-process-registry*.json, or a populated
> process_overrides.json when sharing — overrides ship as {}. Personal processes leaking into another
> user's run is a fatal flaw the owner guard + working/ overrides path exist to prevent.
python scripts/compute.py --in working/cowork_sessions.json --out working/cowork_roi_data.jsonpython scripts/build_report.py --data working/cowork_roi_data.json --out output/cowork-roi-report.htmlGlob output/cowork-roi-report.html; if missing, locate + move into output/.scripts/to_csv.py --estimate). On the user's yes: python scripts/to_csv.py --data working/cowork_roi_data.json --out output/cowork-sessions.csv (one row/session, atomic grain, pipe-delimited, UTF-8 BOM, Cowork-fit columns).python -c (a wrong guess exits non-zero and marks the whole step Failed). Trust each script's printed summary.Present a short highlights summary (or render_ui card): speed multiplier, expert-equivalent hours, value, top 3 categories/goals. Tell the user the HTML report is saved. Then offer the optional session CSV with its estimated credit cost (scripts/to_csv.py --estimate; the script itself is ~0 credits — local compute — so the estimate is the one extra agent step). Generate only if the user opts in.
SetupScheduledPrompt with execution_mode="inline", frequency Day, interval = N (7/15/30), hours
["8"], name "Cowork ROI report (every N days)", and a self-contained description restating the full
workflow (harvest last N days → classify → two-clock model at $72/hr → render HTML to output/ → email me the
highlights with the HTML attached). Confirm: "Done — I'll rebuild every N days and email the digest."
`SendEmailWithAttachments(to=[<user's own email>], subject="My Copilot Cowork impact — <window label>",
body="<highlights HTML>", content_type="HTML", direct_attachment_file_paths=["output/cowork-roi-report.html"])`.
Other recipient only if named.
Each category's research-anchored Typical band (Low/High = range); an expert clock vs a modeled
assisted clock yields the speed multiplier; value = expert-hours × rate. No ROI/seat figure.
Bands table + sources + two-clock formulas: references/methodology.md.
compute.py holds the constants — never hand-compute.
compute.py.reconcile_taxonomy.pyignores any non-owner file. NEVER bundle the registry, any cowork-process-registry*.json, or a
populated process_overrides.json — overrides ship as {}, live under working/ at runtime.
scripts/mine_session.py — telemetry: run time, tool intensity, artifacts per session.scripts/reconcile_taxonomy.py — per-user taxonomy memory (align-first, create-if-novel); runs before classify.py; --owner.scripts/classify.py — deterministic ext→category classifier; reads --overrides; emits compute.py’s input schema.scripts/compute.py — research-anchored bands + two-clock model → payload JSON.scripts/build_report.py — renders the single-file HTML (Process-anchored work-by-process, projects-by-category, glossary, live rate, PDF).scripts/to_csv.py — opt-in tidy CSV export (one row/session, atomic grain, Cowork-fit columns); --estimate prints its credit cost (~0; local compute)./mnt/user-config/.claude/cowork-process-registry.<userkey>.json — the user's own owner-stamped taxonomy memory (Processes + Projects + JTBDs); <userkey> from email, on the per-user mount. Read/aligned/persisted by reconcile_taxonomy.py each run; non-owner files ignored. Member/aggregated skills use the same scheme./mnt/user-config/.claude/cowork-session-telemetry.json · …-credits.json · …-session-costs.json — measured run-time / credit / cost logs (optional).Take microsoft/cowork-roi-report from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.