Skip to content

patterns: add session-explainer — publish an agent session as a narrative - #41

Merged
utsengar merged 4 commits into
mainfrom
session-explainer-pattern
Aug 23, 2026
Merged

patterns: add session-explainer — publish an agent session as a narrative#41
utsengar merged 4 commits into
mainfrom
session-explainer-pattern

Conversation

@utsengar

Copy link
Copy Markdown
Owner

What

Adds a fourth official pattern, session-explainer: publishing an agent session as a narrative — the problem, the approach, the dead ends, what shipped.

Content only. No schema change, no new endpoint, no route work — the existing /.well-known/patterns/:filename handler picks it up.

Why

Engineers have started sharing agent session traces to show how they thought about a problem, not just what shipped. YC's Spring 2026 application asks for one ("attach a coding agent session you're particularly proud of", 10 MB cap); interviews are following.

"Sharing a session" turns out to be three products with contradictory requirements, split on whether curation helps or hurts:

Evidence Narrative Craft transfer
Answers "did you really think this?" "how should I think about this?" "how do good people use agents?"
Curation is a negative the whole value neutral
Size 6–21 MB <500 KB aggregate

This pattern serves only the middle one. The other two are explicit non-goals:

  • Evidence needs 10 MB+, tamper-evidence, and private-by-default. Measured across 1,293 real transcripts locally, the size distribution is bimodal — a tight cluster at ~150 KB and a tail from 6 MB to 21 MB, with nothing between. The long sessions actually worth sharing land 3–10× over MAX_HTML_BYTES, before HTML inflation. Curation is arithmetic here, not taste — which is exactly why this is a pattern and not a platform change. YC also accepts 10 MB direct uploads, so the need is already met.
  • Craft transfer needs an index and cross-session browse. That's a dashboard; hard rule docs: tighten README intro line (test preview deploy) #4.

Positioning vs prior art

Rendering a Claude Code session is commoditized — Lore, simonw/claude-code-transcripts, Mantra, claude-devtools, and assorted local viewers. We are deliberately not building a trace viewer (hard rule #1 applies directly to Lore).

But every one of those is a mechanical transcoder: JSONL in, faithful HTML out. None authors a narrative, and none redacts. That gap is the one thing an agent that just did the work is uniquely placed to fill — so the pattern carries a redaction floor: secrets scan, home-dir path stripping, no unreviewed excerpts, passcode-by-default for internal, and rotate anything the session touched.

Trace UX

Borrowed from Braintrust / Langfuse / LangSmith where the convention survives a static self-contained page — two altitudes (narrative + collapsed <details>), anchored linkable steps, one scrollable Ctrl-F-able body. Rejected where it doesn't: latency waterfalls, token/cost telemetry, span trees. Those are pipeline-debugging affordances for the wrong audience.

Files

  • patterns/session-explainer.md — source of truth
  • src/patterns.ts — byte-for-byte mirror + PatternMeta entry
  • scripts/agent-e2e.sh — count 3 → 4, added the name to the assertion loop
  • skill + CLAUDE.md references

Companion CLI PR (vendored pattern + htmlbin-publish skill list): utsengar/htmlbin-cli#13

Verification

  • npx tsc --noEmit — clean
  • npm run test:e2e — all 132 checks pass (was 126; +6 new pattern assertions)
  • Served body verified byte-identical to patterns/session-explainer.md by SHA-256 (ad4b0636…) — the mirror is hand-maintained, so this was checked rather than assumed
  • CLI install verified end-to-end from both the live local catalog and the offline bundled fallback; same SHA-256 in all three places

Notes for the reviewer (pre-existing, not from this PR)

  1. npm run dev is broken on main — bare wrangler hits the .bin shim __dirname bug CLAUDE.md predicted ("if they ever break the same way, copy the direct-invoke pattern"). Worked around locally with node node_modules/wrangler/bin/wrangler.js dev. Worth a follow-up applying the direct-invoke fix to dev/deploy/tail in package.json.
  2. src/skill.ts and skills/htmlbin/SKILL.md have ~52 lines of pre-existing drift. My one-line edit went into both, but the mirrors are otherwise out of sync and nothing enforces it. Worth its own PR.
  3. migrations/0001-github-oauth.sql fails against a local DB created from schema.sql (duplicate github_user_id), which leaves 0002-drop-metadata.sql unapplied and silently breaks 36 e2e checks locally. Not a production issue, but it makes a fresh local setup look broken.

🤖 Generated with Claude Code

…tive

Engineers have started sharing agent session traces to show how they
thought about a problem, not just what shipped. YC's Spring 2026
application asks for one; interviews are following.

"Sharing a session" turns out to be three products with contradictory
requirements, split on whether curation helps or hurts:

  - Evidence ("did you really think this?") wants raw and unedited.
  - Narrative ("how should I think about this?") wants curated.
  - Craft transfer ("how do good people use agents?") wants an index.

This pattern serves only the middle one, and the other two are explicit
non-goals. Evidence needs 10MB+ and tamper-evidence; measured across
1,293 real transcripts locally, the distribution is bimodal — a cluster
at ~150KB and a tail from 6MB to 21MB, with the shareable long sessions
landing 3-10x over MAX_HTML_BYTES. Curation is arithmetic here, not
taste, which is why this is a pattern and not a platform change. Craft
transfer needs a browse UI, which hard rule #4 forbids.

Every existing tool in this space (Lore, simonw/claude-code-transcripts,
Mantra, and assorted local viewers) is a mechanical transcoder: JSONL in,
faithful HTML out. None authors a narrative, and none redacts. That gap
is the one thing an agent that just did the work is uniquely placed to
fill, so the pattern carries a redaction floor — secrets, home-dir paths,
unreviewed excerpts, passcode-by-default for internal, rotate anything
the session touched.

Trace-UX conventions borrowed from Braintrust/Langfuse/LangSmith where
they survive a static self-contained page (two altitudes, anchored steps,
one scrollable Ctrl-F-able body) and rejected where they don't (latency
waterfalls, token/cost telemetry, span trees — debugging affordances for
the wrong audience).

Content only. No schema change, no new endpoint, no route work; the
existing /.well-known/patterns/:filename handler picks it up.

- patterns/session-explainer.md — source of truth
- src/patterns.ts — byte-for-byte mirror + PatternMeta entry (verified
  by SHA-256 against the served body)
- scripts/agent-e2e.sh — count 3 -> 4, add the name to the assertion loop
- skill + CLAUDE.md references

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@utsengar
utsengar force-pushed the session-explainer-pattern branch from 8d45474 to de026ec Compare August 22, 2026 19:32
@github-actions

github-actions Bot commented Aug 22, 2026

Copy link
Copy Markdown

🚀 Cloudflare preview

URL: https://72f75b95-htmlbin.utkarsh2012.workers.dev

Built from commit 34664705149fea49c113587a1419348fa4f04211.

Note: bindings (D1, KV, AI) are shared with production.
Avoid destructive PR tests on real data.

utsengar and others added 3 commits August 22, 2026 12:42
The previous commit added "session explainers" to a parenthetical in the
Patterns section, which was effectively invisible — the comprehensive
skill never enumerated the catalog at all, so no pattern was named as an
actual entry. An agent that reads SKILL.md without fetching index.json
had no way to learn session-explainer exists.

Two inconsistencies made this worth fixing rather than defending as
by-design: the CLI's htmlbin-publish skill already enumerates its bundled
set, and CLAUDE.md describes this skill as the comprehensive reference.

Adds a one-paragraph catalog listing after the resolution order, naming
all four patterns with their one-line job, and points at index.json as
authoritative so the list can't rot into a lie as the catalog grows.

Also adds an e2e guard: every name in patterns/index.json must appear in
SKILL.md. Driven off the catalog rather than hardcoded, so a future
pattern that lands in the catalog and never reaches the skill fails the
suite instead of shipping invisible.

Both src/skill.ts and skills/htmlbin/SKILL.md updated; the new block is
byte-identical in each (verified against the served body).

Verified: tsc clean, all 136 e2e checks pass (was 132).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pattern described a layout in prose and offered three options, which
is how the other patterns work. That is too loose for this one: these
pages are only worth comparing to each other if they share a shape, and
prose guidance produces a different page every time.

Structure is now fixed; palette and type still adapt.

Three mechanics carry it:

- patterns/session-explainer.template.html — a working skeleton with
  SLOT_* placeholders. Its CSS is split by a marked comment into a BRAND
  TOKENS block the agent adapts and a STRUCTURE block it must not touch.
  An agent filling a skeleton drifts far less than one authoring from a
  description.
- brand_sensing: colors-only, replacing true. A prescriptive layout and
  full brand sensing contradict each other, so the pattern says which
  half wins: adapt the palette, leave the structure.
- A conformance checklist of assertions the agent can actually run
  (rail present, both panels exist, zero script tags, every deep link
  resolves, no overflow at 360px, no absolute home paths).

Serving: getPatternAsset() replaces getPatternMd() at the route and
returns body + content-type, so .md goes out as text/markdown and
.template.html as text/html. buildPatternIndex() adds template_url for
any pattern declaring one, so agents find it from the index rather than
guessing the URL. PatternMeta gains an optional template field.

Also rewrote the pattern body around what a reader learns. The insights
block now holds transferable lessons with a 150-word cap, and says
explicitly that a fact about the artifact (file size, PR count) is not a
lesson. Hard numeric limits replace the soft "content checklist".

Enforcement stops here deliberately. The server does not validate
published HTML — that would break "HTML uploads exactly as posted" — and
./.htmlbin/patterns/ still overrides the catalog. Template plus a
self-checkable floor is the ceiling, and the pattern says so.

One thing testing the skeleton against its own checklist surfaced: a
hidden tab panel reports zero-size elements, so overflow and tap-target
checks silently pass for whichever tab is inactive. The checklist now
tells the agent to run the layout checks once per tab. Same failure mode
as verifying with a string count.

Verified: tsc clean, all 146 e2e checks pass (was 136, +10 for the
template), served .md and .template.html both byte-identical to
patterns/ by SHA-256.

The CLI's offline bundle globs *.md only, so the template is not
vendored; an offline init falls back to the pattern's prose. Noted in
CLAUDE.md as a follow-up rather than fixed here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`brand_sensing: colors-only` broke the CLI. Its validator requires that
field to be a boolean and throws on anything else, and because
`patterns init` loops every catalog entry inside one try/catch, the throw
dropped the whole run into the bundled fallback. Every published CLI
would have quietly installed 3 patterns and reported offline: true
against a catalog that was serving 4.

Reproduced it end to end before fixing: init against a local worker
returned offline: true with session-explainer missing.

Unknown front-matter keys are ignored by the existing validator, so the
scope moves to an additive `brand_scope: colors-only` alongside
`brand_sensing: true`. Old CLIs skip it, new ones read it, and the
catalog stays installable either way. Same reason `template` is a new key
rather than an extension of an existing one.

CLAUDE.md now records the constraint, since it applies to any future
pattern metadata: keep it additive, and never change the type of a field
an older validator already checks.

Verified: tsc clean, 146 e2e checks pass, and init against the local
worker returns offline: false with all 4 patterns.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
utsengar added a commit to utsengar/htmlbin-cli that referenced this pull request Aug 23, 2026
session-explainer is prescriptive: it mandates one structure and ships a
reference skeleton rather than describing a layout in prose. The bundle
globbed patterns/*.md only, so an offline `patterns init` installed the
pattern without the structure it points at.

The skeleton now travels with the pattern on every path.

- scripts/build-bundled-patterns.mjs picks up a <name>.template.html
  sibling and emits it as `template: { filename, body }`.
- init writes it from the bundle offline, and fetches it from the catalog
  online. The pattern's own front matter decides whether to look, so the
  three patterns without a skeleton cost no extra request.
- `patterns add <name>` gets it too, but only for catalog sources — a
  file/gist/github source has nowhere well-defined to look, and guessing
  would mean a speculative fetch.
- schema.ts learns two optional keys: `brand_scope` ("all" |
  "colors-only") and `template`.

Two things worth calling out.

The skeleton is nested on its pattern's InstallResult rather than pushed
into `installed` as a sibling. First pass pushed it, which broke an
existing assertion (`installed` had an entry whose name already carried
an extension) and, worse, made installed.length stop meaning "patterns
installed". Nesting keeps that invariant and matches what `add` returns.

The destination filename is derived from the validated pattern name, never
from the catalog. A catalog entry is remote input, and `template:
../../../etc/passwd` would otherwise be a write outside the patterns dir.
schema.ts pins the value to `<name>.template.html` and there are tests for
the traversal cases.

Verified: 171 tests pass (was 162). init installs the skeleton from both
the live catalog and the bundled fallback, byte-identical to the worker's
copy by SHA-256, idempotent on a second run, and `patterns list` still
reports 4 patterns rather than counting the skeleton as a fifth.

Companion worker PR: utsengar/htmlbin#41

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@utsengar
utsengar merged commit 3bcad13 into main Aug 23, 2026
1 check passed
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.

1 participant