Skip to content

feat(api): add opt-out usage metrics (spec 133) - #923

Merged
arielshad merged 14 commits into
mainfrom
feat/133-telemetry
Oct 11, 2026
Merged

arielshad merged 14 commits into
mainfrom
feat/133-telemetry

Conversation

@arielshad

@arielshad arielshad commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

What

Opt-out usage metrics for Shep (spec 133).

Every process (CLI, daemon, feature-agent workers) records about ten content-free events into a local SQLite telemetry_outbox. Only the long-lived server process sends them: the shep start daemon or shep ui. Events go to PostHog's EU /batch/ endpoint over plain fetch.

Controls:

  • shep telemetry on|off|identity|contact|status|show
  • a Settings section
  • a first-run notice in both the CLI and web onboarding

Why

This implements specs/133-telemetry/, the "Metrics" section of the T3 Code product review. Today nobody can tell which areas, commands or agents are used, or whether runs end in merged PRs. The north star is merged PRs per weekly-active install.

The product owner fixed these decisions:

  • Usage events are on by default. A notice shows once, in the CLI (stderr) and in web onboarding, with one-click Turn off.
  • Always off under CI, DO_NOT_TRACK=1, SHEP_TELEMETRY_DISABLED=1 and test runners.
  • Identity is opt-in. Every event carries a random installId.
    • Only after the user turns Include my identity on (off by default) do events also carry:
      • a SHA-256 hash of the agent account id
      • the GitHub username (gh api user)
      • the GitHub owners of the repos' remotes
    • Both notices list these fields under their own heading and say how to turn them on: shep telemetry identity on, or the switch in the notice and in Settings.
    • The setting defaults to off everywhere: the settings default, the migration column default and the mapper fallback all read one constant.
  • Contact consent. A separate The Shep team may contact me on GitHub flag (off by default) is sent as a property.
  • Agent-agnostic identity. Where to find the account id is data on the agent catalog: an accountIdSource on the Claude and Codex entries. Core has no if (agentType === …).
  • Events. install.heartbeat, cli.command, web.area.viewed, feature.created, feature.run.finished, pr.opened, pr.merged, decision.answered (defined only), onboarding.step, error.unhandled.
    • The event names are a TypeSpec enum.
    • TelemetryEventPropertyMap types each event's properties, so a content property does not compile.
  • Trust promise. The README Trust and safety section, the FAQ and CLAUDE.md now say what is sent. The full list is in docs/telemetry.md.

Delivery details:

  • Each event gets a UUID.
  • A failed batch backs off with jitter (2 s, doubling up to 5 min) and is dropped after 5 attempts.
  • The outbox is capped at 5,000 events.
  • Batches are claimed with a two-minute lease, so the daemon and shep ui never send the same batch twice.
  • pr.opened / pr.merged are once-keyed per feature: the merge node, both PR-sync paths and the webhook all see a merge, and it counts once.
  • Identity is attached at send time, so turning identity off also covers events already queued.
  • Turning metrics off deletes everything queued.

Owner actions before release

  1. PostHog project key.
    • Set SHEP_POSTHOG_KEY in the release environment, or fill BUILT_IN_POSTHOG_KEY in packages/core/src/infrastructure/services/telemetry/telemetry-config.ts.
    • Until then nothing is sent; events wait in the capped outbox.
  2. PostHog project setting. Turn on Discard client IP data. GeoIP is already disabled per event.

The GDPR question in the spec is resolved by the owner's decision to make identity opt-in.

Screenshots / Recording

Storybook captures from pnpm build:storybook, in light and dark.

Web onboarding notice (one-click Turn off; identity fields listed apart, identity off by default) Settings → Usage metrics
notice light settings light
notice dark settings dark

When the environment forces metrics off (DO_NOT_TRACK), the switches are locked:

forced off

The CLI first-run notice, printed once to stderr:

Shep collects usage metrics
To learn which features are used, Shep sends usage events to the Shep team (PostHog, EU region). It never sends prompts, code, file paths, repository or branch names, feature titles or error messages.
What is sent:
  - a random install id
  - which commands, pages and features you use, and how runs end
  - Shep version, operating system and Node.js version
  - whether the Shep team may contact you on GitHub (no, unless you allow it)
Sent only if you turn identity on (off by default):
  - a SHA-256 hash of your Claude or Codex account id
  - your GitHub username (read with gh)
  - the GitHub owners (users or organisations) of the repositories you use with Shep
These identify you, so they stay off unless you turn them on: shep telemetry identity on
Turn metrics off: shep telemetry off (or set DO_NOT_TRACK=1). See what is sent: shep telemetry show

Testing

All of these ran locally on the final tree, after merging main:

  • pnpm lint, pnpm format:check, pnpm typecheck, pnpm typecheck:web and pnpm lint:web pass.
  • pnpm generate leaves no diff.
  • pnpm test:unit: 1,319 files, 14,830 tests, 0 failed.
  • pnpm test:int passes.
  • pnpm build and pnpm build:storybook succeed, and check:stories is OK.

TDD, RED first. The identity default had RED tests in four places:

  • the settings defaults
  • the migration column default (PRAGMA table_info)
  • the mapper fallback in both directions
  • the status use case with no stored telemetry

The split notice had its own RED tests: a domain field-list test, the use case, the CLI hook output order, and the web identity section.

Earlier coverage:

  • Domain: env-state matrix, backoff and buckets, stack-frame reduction, owner parsing.
  • Use cases: flush, backoff, drop, opt-out clears, no key sends nothing, the identity toggle strips identity from queued events, heartbeat, notice.
  • Adapters: the PostHog body against a stubbed fetch; the identity reader against a temp home.
  • Persistence: the outbox against real SQLite; a settings round-trip with two different non-default writes.
  • DI: a bootstrap token test.
  • Emit sites, CLI and web: component and hook tests.

End to end: I posted the real wire body shape to https://eu.i.posthog.com/batch/ and got 200; a malformed body gets 400. PostHog answers 200 for any key, so this proves the format, not a project.

Checklist

  • pnpm lint passes
  • pnpm format:check passes
  • pnpm typecheck passes
  • pnpm test:unit and pnpm test:int pass
  • pnpm build succeeds
  • (UI only) pnpm build:storybook succeeds; every new component has a colocated .stories.tsx
  • (Domain changes) pnpm generate ran; output.ts and apis/json-schema/ are committed
  • (New use case) Tests landed RED-first per the TDD guide
  • No domain/ or application/ file imports anything from infrastructure/
  • Commit messages follow Conventional Commits
  • Updated LESSONS.md

Notes for reviewers

🤖 Generated with Claude Code

https://claude.ai/code/session_01Y84mkYCEBtd3RMaSvpSLz5

claude and others added 11 commits October 9, 2026 09:35
Claude-Session: https://claude.ai/code/session_01Y84mkYCEBtd3RMaSvpSLz5

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
Claude-Session: https://claude.ai/code/session_01Y84mkYCEBtd3RMaSvpSLz5

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
Claude-Session: https://claude.ai/code/session_01Y84mkYCEBtd3RMaSvpSLz5

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
…cases

TelemetryEvent and TelemetryConfig in TypeSpec; pure domain rules for the
enabled state, retry backoff, duration buckets and error frames; an
ITelemetry port backed by a SQLite outbox (migration 166) with once-key
dedupe; a PostHog /batch/ transport over fetch; agent account ids read
through catalog descriptors; and the flush, status, preferences, preview,
notice and heartbeat use cases wired by string token.

Claude-Session: https://claude.ai/code/session_01Y84mkYCEBtd3RMaSvpSLz5

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
…cher

shep telemetry on|off|identity|contact|status|show; a one-time notice on
stderr listing every collected field; cli.command recorded with the
command path only; error.unhandled from the crash handlers; and a
one-minute flush watcher in the daemon and shep ui that records the daily
heartbeat and sends leased batches, so two servers never double-send.

Claude-Session: https://claude.ai/code/session_01Y84mkYCEBtd3RMaSvpSLz5

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
feature.created from CreateFeatureUseCase; feature.run.finished from the
worker's guarded terminal writes (status, agent type, duration bucket);
pr.opened and pr.merged from the merge node, both PR sync paths and the
GitHub webhook, once-keyed per feature so a merge seen three times counts
once. pr.merged says whether a pull request was merged or the branch was
squash-merged locally.

Claude-Session: https://claude.ai/code/session_01Y84mkYCEBtd3RMaSvpSLz5

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
Web onboarding shows the usage-metrics notice with one-click turn off,
the identity toggle and the GitHub contact consent; Settings gains a
Usage metrics section; AppShell records web.area.viewed as a route
template built from Next params; onboarding steps are recorded. The
README trust section, FAQ and CLAUDE.md now say what is sent, with the
full list in docs/telemetry.md.

Claude-Session: https://claude.ai/code/session_01Y84mkYCEBtd3RMaSvpSLz5

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
…test

The identity switch is on by default, so its hint now says to turn it off
to stay anonymous, in all nine locales. The _serve command test stub names
ITelemetry, which background sync now resolves for the PR sync watcher.

Claude-Session: https://claude.ai/code/session_01Y84mkYCEBtd3RMaSvpSLz5

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
Claude-Session: https://claude.ai/code/session_01Y84mkYCEBtd3RMaSvpSLz5

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
The windows-latest runner now ships git 2.56, which rejects the device
name NUL as GIT_CONFIG_GLOBAL ("unable to access 'NUL': Invalid
argument"), so every harness builtin-tools integration test failed in
createTempGitRepo. Git for Windows maps /dev/null itself, and the
merge-step real-git harness already isolates that way and passes on the
same runner.

Claude-Session: https://claude.ai/code/session_01Y84mkYCEBtd3RMaSvpSLz5

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
The settings page now fires other server actions on load (telemetry status
and the route-view event). selectLanguage resolved on whichever answered
first, so a reload could abort the real save and leave the language unset.

Claude-Session: https://claude.ai/code/session_01Y84mkYCEBtd3RMaSvpSLz5

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>

Copy link
Copy Markdown
Contributor Author

Unit Tests (windows-latest) on 5d8c7f8: 14,683 tests passed. The job still failed because the Vitest worker running tests/unit/infrastructure/services/agents/dev-server-agent/dev-server-agent.service.test.ts exited unexpectedly ("Worker forks emitted error", no stack). Every other file reported (1306/1307).

  • That test only uses in-memory mocks and spawns no processes. This PR doesn't touch it or the service it tests.
  • It passes 16/16 locally, in 5 out of 5 runs.
  • The same job was green on ac8979f.

The job is running again on the new head 70a0529. I'll investigate the worker exit if it recurs there.


Generated by Claude Code

Brings in spec 134 (unified decisions, migration 166). Telemetry's migration
moves to 168, and identity becomes opt-in by owner decision: includeIdentity
defaults to false in settings, the migration and the mapper, and both
first-run notices list the identity fields apart, with how to turn them on.

Claude-Session: https://claude.ai/code/session_01Y84mkYCEBtd3RMaSvpSLz5

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
arielshad added a commit that referenced this pull request Oct 11, 2026
## What

Adds `docs/competitors/t3code.md`, a product and architecture review of
T3 Code (pingdotgg/t3code) compared with Shep, and links it from
`docs/competitors/README.md`. The doc covers four topics:

- **What to adopt:** the inline-options decision pattern, plan capture,
hidden-ref checkpoints and capability records.
- **What to skip.**
- **What to cut or consolidate in Shep.**
- **A telemetry design.**

It ends with the owner's decision on every recommendation.

## Why

This is the record behind the follow-up work:

- spec 133 metrics (#923)
- spec 134 unified decisions (#922, #924)
- spec 135 cleanup (#925)
- the Effect-TS evaluation issue (#921)

## Screenshots / Recording

N/A — non-UI change.

## Testing

Docs only. Markdown is excluded from Prettier (`*.md` in
`.prettierignore`). I checked the claims in the doc against
`main@0304cfc` and `pingdotgg/t3code@a4c9494b`.

## Checklist

- [x] Commit messages follow [Conventional
Commits](https://www.conventionalcommits.org/) — `docs`, so no release

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01SGhpsQ4T9PshbZFxzCQQMy

---
_Generated by [Claude
Code](https://claude.ai/code/session_01SGhpsQ4T9PshbZFxzCQQMy)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
claude and others added 2 commits October 11, 2026 10:11
Claude-Session: https://claude.ai/code/session_01Y84mkYCEBtd3RMaSvpSLz5

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
@arielshad
arielshad marked this pull request as ready for review October 11, 2026 10:50
@arielshad
arielshad merged commit d97bc6f into main Oct 11, 2026
23 checks passed
@arielshad
arielshad deleted the feat/133-telemetry branch October 11, 2026 10:50
arielshad pushed a commit that referenced this pull request Oct 11, 2026
<p align="center">
  <a href="https://github.com/shep-ai/shep">
    <img src="https://raw.githubusercontent.com/shep-ai/shep/main/docs/screenshots/shep-card.jpg" alt="Shep — run multiple AI agents in parallel" width="720" />
  </a>
</p>

# 🚀 Shep [v1.239.0](/compare/v1.238.0...v1.239.0) · _2026-10-11_

> Your organization does not have access to Claude. Please login again or contact your administrator.

### ✨ Features

* **api:** add opt-out usage metrics (spec 133) ([#923](#923)) ([d97bc6f](d97bc6f)), closes [#922](#922) [#924](#924)

  ![notice light](https://raw.githubusercontent.com/shep-ai/shep/v1.239.0/specs/133-telemetry/evidence/notice-card-light.png)
  ![settings light](https://raw.githubusercontent.com/shep-ai/shep/v1.239.0/specs/133-telemetry/evidence/settings-section-light.png)
  ![notice dark](https://raw.githubusercontent.com/shep-ai/shep/v1.239.0/specs/133-telemetry/evidence/notice-card-dark.png)
  ![settings dark](https://raw.githubusercontent.com/shep-ai/shep/v1.239.0/specs/133-telemetry/evidence/settings-section-dark.png)

## 📦 Install or update

```bash
# upgrade an existing install
npm i -g @shepai/cli@1.239.0

# or run instantly without installing
npx @shepai/cli@latest
```

## 💬 Join the community

[💬 **Discord**](https://discord.gg/ES6tdVFfur) · [📖 **Docs**](https://github.com/shep-ai/shep#readme) · [⭐ **Star on GitHub**](https://github.com/shep-ai/shep) · [🐛 **Report an issue**](https://github.com/shep-ai/shep/issues)

---

<sub>🤖 Released autonomously by Shep — built by parallel AI agents working in isolated git worktrees. Try it: `npx @shepai/cli`</sub>

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
arielshad pushed a commit that referenced this pull request Oct 11, 2026
…up-cuts

Bring in telemetry (#923, migration 168); the feature-flag migration stays at 169.
Take main's settings-rows extraction and stories, keeping the row's flex basis so
switches stay beside long descriptions. Keep the i18n spec's save-matching without
response.finished(), which hung in CI.

Claude-Session: https://claude.ai/code/session_01TxtN2VcWzg6NTCLj4DbmfL

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
arielshad added a commit that referenced this pull request Oct 11, 2026
…lt (#925)

## What

This is the cleanup PR from the T3 Code product review. It covers the
items the owner agreed to, with one commit per item:

| Commit | Item | Change |
| --- | --- | --- |
| `bb81a67` | **C1** | The stale good-first-issue and monthly recap
watchers no longer run in users' daemons (`_serve`, `shep ui`,
`dev:web`). They now run from the new scheduled workflow
`.github/workflows/contributor-maintenance.yml`, through two new
commands, `shep contributors stale-issues` and `shep contributors
recap`. Both reuse the existing use cases. The watcher services are
deleted. The contributor view (leaderboard, contributor doctor) moves to
`/contributors`, which is linked from CONTRIBUTING.md and not from the
sidebar. `/onboarding` keeps only the collaboration tutorial and leaves
the sidebar. |
| `2810b71` | **C3** | `featureFlags.aspm` now defaults to `false` in
TypeSpec, the defaults factory and the web env fallback. Values users
already saved are kept; no migration touches them. ROADMAP.md is updated
to match. The e2e tests that visit `/aspm` turn the flag on for the test
and back off afterwards. |
| `0b9221f` | **C4** | Supply-chain security is folded into ASPM,
because folding was cheap and removing it was not. Its own
`supplyChainSecurity` flag is gone, and every surface follows `aspm`
through `isSupplyChainSecurityEnabled()`: the feature-agent pre-check,
the canvas badge, the Settings section and `shep security enforce`.
`SHEP_SUPPLY_CHAIN_SECURITY` still overrides the flag in either
direction. Shep's own `security-enforce` CI job already sets it to
`true`, so the release gate keeps running. The
`feature_flag_supply_chain_security` column stays and is no longer read.
|
| `d3a2157` | **C7** | Removes `system.autoUpdate` and the placeholder
agent types `aider` and `continue`. The `sys_auto_update` column is NOT
NULL with no default, so it is still written as `1` but never read. A
settings row that still holds `aider` or `continue` reads back as the
default agent. `agentQuestionBridge`, `AgentQuestionExecutorBridge` and
`InteractionBubble` are untouched. |
| `ef9c9ae` | **C2** | Adds 12 software-factory flags, all **on by
default**: `spaces`, `trackers`, `knowledge`, `signals`,
`opportunities`, `feedback`, `discovery`, `incidents`, `outcomes`,
`docsFirst`, `autopilot`, `factory`. They are stored in migration 169
(`DEFAULT 1`). Each flag gates its area's nav entry, pages, intake API,
CLI group, daemon loop and, for `docsFirst`, the docs gate. A new view
at **`/settings/feature-flags`** lists every flag with a one-line
description, its default and an on/off switch; Settings links to it.
**`shep settings flags [enable\|disable <flag>]`** does the same from
the CLI. |
| `1b681a2` | **C5** | Deletes the Vercel, Netlify, AWS Amplify and GCP
Cloud Run stub providers, their enum members and all "Coming soon" UI.
Cloudflare Pages is unchanged. An application that saved a removed
provider reads it back as "no provider selected". |

`b4e7420` renumbers the spec to 135: the telemetry and unified-decisions
workstreams took 133 and 134.

## Why

Implements `specs/135-review-cleanup-cuts/`, from the "Where Shep Is
Today and What to Cut" section of `docs/competitors/t3code.md`.

## Screenshots / Recording

I checked the feature-flags view at desktop and phone width in the
running app with `pnpm dev:web` and Playwright. Turning `spaces` off
removed the Spaces link from the sidebar and made `/spaces` return 404;
turning it back on restored both. (The screenshots are local to the
session; I can attach them if needed.)

## Testing

Local runs, on the merge of `main` at `d97bc6f` (telemetry #923,
decisions #922/#924):

| Check | Result |
| --- | --- |
| `pnpm generate` | committed output matches what it generates |
| `pnpm tsp:compile` | no warnings |
| `pnpm lint`, `pnpm format:check` | pass |
| `pnpm typecheck` | pass |
| `pnpm test:unit` | 14,927 passed |
| `pnpm test:int` | 2,139 passed |
| `pnpm check:stories` | pass |
| `pnpm build:release` | pass |
| `pnpm build:storybook` | pass |
| Web e2e (CI settings, a flaky test counts as a failure) | 86 of 86
passed |

New tests:
- feature-flag catalog completeness
- List/SetFeatureFlag use cases
- the CLI gate helper
- `shep settings flags`
- the background-sync gating
- `requireFeaturePage`
- feedback and alerts returning 404 while their flag is off
- the docs-first gate
- migration 169
- a repository round-trip of every new flag
- mapper fallbacks for the removed agent and provider values
- the new contributor commands

Stories added: FeatureFlagsList, FeatureFlagsPageClient, ProviderList,
CloudProviderIcons. `SettingsRows` (and its stories) now come from
`main`; this PR keeps only the row's flex basis so switches stay beside
long descriptions.

## Notes for the reviewer

- **Recap data in the workflow:** the recap use case reads recognition
events from the local database. In Actions that database starts empty,
which matches what the committed `recaps/` files already show (zero
events), since recognition is still recorded by hand (see CONTRIBUTING).
The stale-issues job reads GitHub and works as is.
- **Migration number:** 169. `main` holds 166 (agent-question
decisions), 167 (decision timeout) and 168 (telemetry).
- **i18n e2e:** the language spec waits for the language save by its
request body and no longer on `response.finished()`, which hung for 60s
in CI while the re-rendered page streamed.
- **Windows test harness:** the git-config isolation now uses `main`'s
empty-config-file approach; the pty teardown tolerates a lingering
ConPTY host (`4d1b86f`).
- **CLI name:** `shep security enforce` keeps its name. Moving it under
`shep aspm` would have broken existing CI scripts.

## Checklist

- [x] `pnpm lint` passes
- [x] `pnpm format:check` passes
- [x] `pnpm typecheck` passes
- [x] `pnpm test:unit` and `pnpm test:int` pass
- [x] `pnpm build` succeeds
- [x] `pnpm build:storybook` succeeds and every new component has a
colocated `.stories.tsx`
- [x] `pnpm tsp:compile` ran and `output.ts` is committed
- [x] Tests landed RED-first
- [x] No `domain/` or `application/` file imports from `infrastructure/`
- [x] Conventional Commits; `feat`/`fix` for user-visible changes
- [x] LESSONS.md updated: the flag checklist points at the catalog, plus
notes on agent worktrees, Windows git-config isolation and waiting for a
specific server action in e2e

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01TxtN2VcWzg6NTCLj4DbmfL

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Shep Bot <shep-agent@users.noreply.github.com>
arielshad pushed a commit that referenced this pull request Oct 11, 2026
<p align="center">
  <a href="https://github.com/shep-ai/shep">
    <img src="https://raw.githubusercontent.com/shep-ai/shep/main/docs/screenshots/shep-card.jpg" alt="Shep — run multiple AI agents in parallel" width="720" />
  </a>
</p>

# 🚀 Shep [v1.240.0](/compare/v1.239.0...v1.240.0) · _2026-10-11_

> Your organization does not have access to Claude. Please login again or contact your administrator.

### ✨ Features

* **web:** review cleanup — per-area feature flags, ASPM off by default ([#925](#925)) ([58df74f](58df74f)), closes [#923](#923) [922/#924](#924)

  ![notice light](https://raw.githubusercontent.com/shep-ai/shep/v1.240.0/specs/133-telemetry/evidence/notice-card-light.png)
  ![settings light](https://raw.githubusercontent.com/shep-ai/shep/v1.240.0/specs/133-telemetry/evidence/settings-section-light.png)
  ![notice dark](https://raw.githubusercontent.com/shep-ai/shep/v1.240.0/specs/133-telemetry/evidence/notice-card-dark.png)
  ![settings dark](https://raw.githubusercontent.com/shep-ai/shep/v1.240.0/specs/133-telemetry/evidence/settings-section-dark.png)

## 📦 Install or update

```bash
# upgrade an existing install
npm i -g @shepai/cli@1.240.0

# or run instantly without installing
npx @shepai/cli@latest
```

## 💬 Join the community

[💬 **Discord**](https://discord.gg/ES6tdVFfur) · [📖 **Docs**](https://github.com/shep-ai/shep#readme) · [⭐ **Star on GitHub**](https://github.com/shep-ai/shep) · [🐛 **Report an issue**](https://github.com/shep-ai/shep/issues)

---

<sub>🤖 Released autonomously by Shep — built by parallel AI agents working in isolated git worktrees. Try it: `npx @shepai/cli`</sub>

Co-Authored-By: Shep Bot <shep-agent@users.noreply.github.com>
arielshad added a commit that referenced this pull request Oct 11, 2026
… (spec 136) (#929)

## What

This is the telemetry follow-up noted in #922 and #924. Every recorded
answer to an agent decision now emits `decision.answered`, and a
background agent's question that reaches its deadline emits the new
`decision.defaulted` event. Both go through the `ITelemetry` port from
#923.

| Event | Properties | Recorded by |
| ----- | ---------- | ----------- |
| `decision.answered` | `kind` (`DecisionKind`), `surface` (new
`DecisionSurface`: web / cli / chat / supervisor / other), `latency`
(bucket from asked to answered), `pickedRecommended` |
`AnswerAgentQuestionUseCase`, once per answer it actually records |
| `decision.defaulted` | `kind`, `timeout` (bucket of the deadline the
agent set) | `AskAgentDecisionUseCase`, when its deadline settle wins |

## Why

Spec 133 defined `decision.answered` but did not emit it, and its `kind`
and `surface` were raw strings. The T3 Code review asks three things
that need data: whether people answer agent questions, where they answer
them, and whether they take the recommendation. It also asks how often
agents proceed on their own at the deadline.

- **Emit points:** each event is emitted right after the use case's
atomic `settlePending` succeeds. Each decision is therefore counted
exactly once without an `onceKey`. A lost race (CLI vs web), a refused
answer, or agent questions being off records nothing. A person answering
at the deadline records an answer, not a default.
- **Surface:** each caller passes its own surface: the web action
(`web`, which overrides anything the client sends), the CLI (`cli`), the
chat bridge (`chat`) and the supervisor router (`supervisor`). Nothing
parses the free-form `answeredBy` actor.
- **Typing:** in the property map, `kind` and `surface` are now enums.
That makes the compiler reject a content property at every emit site.
- **Refactor while touching the file:** the `AskAgentDecisionOutcome`
string union is now the `DecisionOutcome` enum, with a new `Disabled`
member. The MCP tool's wire values are unchanged. Stored questions use
`RecordedDecisionOutcome`, which excludes `Disabled`, so the activity
log needs no UI for a state it can never show.
- **Docs:** `docs/telemetry.md` lists both events. Spec:
`specs/136-decision-telemetry/`. It was renumbered from 135, which
belongs to #925; the branch name still says 135.

## Screenshots / Recording

No UI changes.

## Testing

- `answer-agent-question.use-case.test.ts`:
- Properties for a decision answered on the web, including the latency
bucket with a faked clock and `pickedRecommended: true`.
  - Typed text counts as not the recommendation.
- A legacy question is reported as `Legacy`, with `Other` as the default
surface.
  - Answering twice records one event.
  - Agent questions off, or a refused answer, records nothing.
- `ask-agent-decision.use-case.test.ts`:
- The deadline default records `decision.defaulted` with the timeout
bucket.
  - A person answering at the deadline records no default.
  - Cancelled and disabled outcomes record no default.
- Caller tests assert the surface they pass: web action, CLI `answer`
(with `--answer` and interactive), chat bridge, supervisor router.

Ran locally: `pnpm lint`, `format:check`, `check:stories`, `typecheck`,
`test:unit` (14864 passed), `test:int` (2132 passed), `build`,
`build:web`, `build:storybook`, and `generate` with a clean diff. After
the renumbering: lint, format:check, typecheck and the affected test
files.

## Checklist

- [x] `pnpm lint` passes
- [x] `pnpm format:check` passes
- [x] `pnpm typecheck` passes
- [x] `pnpm test:unit` and `pnpm test:int` pass
- [x] `pnpm build` succeeds
- [x] (Domain changes) `pnpm tsp:compile` ran and
`packages/core/src/domain/generated/output.ts` is committed
- [x] Tests landed RED-first per the TDD guide
- [x] No `domain/` or `application/` file imports anything from
`infrastructure/`
- [x] Commit messages follow Conventional Commits

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_014Zwb23kb4xTBxJtqc2hKbU

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Shep Bot <shep-agent@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants