first-class-sessions (and the template)
diff --git a/_render.html b/_render.html
new file mode 100644
index 0000000..4dee485
--- /dev/null
+++ b/_render.html
@@ -0,0 +1,183 @@
+{{$path := .OriginalReq.URL.Path -}}
+{{$file := printf "%s.md" (trimSuffix "/" $path) -}}
+{{if not (fileExists $file)}}{{httpError 404}}{{end -}}
+{{$doc := include $file | splitFrontMatter -}}
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<meta name="color-scheme" content="light dark">
+<title>{{default (trimSuffix ".md" $file) $doc.Meta.title}} — kejadlen.dev</title>
+{{with $doc.Meta.description}}<meta name="description" content="{{.}}">
+{{end}}<script>try{var t=localStorage.getItem("theme");if(t)document.documentElement.dataset.theme=t}catch(e){}</script>
+<style>
+/* alphabaster — palette from kejadlen/dotfiles (.config/ghostty/config) */
+
+/* light-dark() reads color-scheme, so data-theme below is all the switch sets. */
+:root {
+ color-scheme: light dark;
+ --bg: light-dark(#F0F0E4, #121218);
+ --fg: light-dark(#121218, #F0F0E4);
+ --muted: light-dark(#63635C, #888888);
+ --border: light-dark(#C7C7B8, #44475A);
+ --lavender: light-dark(#6B4CA8, #C4AAEA);
+ --lavender-bright: light-dark(#513787, #D0BCF0);
+ --cyan: light-dark(#1F6B7A, #A2D6E0);
+ --green: light-dark(#28702C, #7BC275);
+ --red: light-dark(#B3261E, #E97C7C);
+ --code-bg: light-dark(#E4E4D6, #282A36);
+ --selection-bg: light-dark(#C6D0F5, #626880);
+ --selection-fg: light-dark(#303446, #c6d0f5);
+}
+
+:root[data-theme="light"] { color-scheme: light; }
+:root[data-theme="dark"] { color-scheme: dark; }
+
+* { box-sizing: border-box; }
+
+::selection { background: var(--selection-bg); color: var(--selection-fg); }
+
+a:focus-visible, button:focus-visible { outline: 2px solid var(--cyan); }
+
+#theme {
+ position: fixed;
+ top: 1rem;
+ right: 1rem;
+ width: 2.25rem;
+ height: 2.25rem;
+ background: var(--code-bg);
+ color: var(--fg);
+ border: 1px solid var(--border);
+ border-radius: 50%;
+ font-size: 1rem;
+ line-height: 1;
+ cursor: pointer;
+}
+
+#theme:hover { border-color: var(--lavender); }
+
+@media print { #theme { display: none; } }
+
+body {
+ margin: 0 auto;
+ padding: 2rem 1rem 4rem;
+ max-width: 44rem;
+ background: var(--bg);
+ color: var(--fg);
+ font: 16px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
+}
+
+/* Narrow viewports have no side margin for the button to sit in. */
+@media (max-width: 50rem) { body { padding-top: 4rem; } }
+
+a { color: var(--lavender); }
+a:hover { color: var(--lavender-bright); }
+
+h1, h2, h3, h4, h5, h6 { color: var(--cyan); line-height: 1.25; margin: 1.5em 0 0.5em; }
+h1:first-child { margin-top: 0; }
+
+blockquote {
+ border-left: 3px solid var(--border);
+ margin-left: 0;
+ padding-left: 1rem;
+ color: var(--muted);
+}
+
+code, pre, kbd { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 0.9em; }
+
+code {
+ background: var(--code-bg);
+ color: var(--lavender);
+ padding: 0.15em 0.35em;
+ border-radius: 4px;
+}
+
+pre {
+ background: var(--code-bg);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ padding: 1rem;
+ overflow-x: auto;
+ line-height: 1.5;
+}
+
+pre code { background: none; padding: 0; color: inherit; }
+
+hr { border: 0; border-top: 1px solid var(--border); }
+
+table { border-collapse: collapse; }
+th, td { border: 1px solid var(--border); padding: 0.25rem 0.75rem; }
+
+img { max-width: 100%; }
+
+ul { padding-left: 1.5rem; }
+
+/*
+ * chroma — alphabaster: only comments, strings and constants, and
+ * definitions get color; keywords and operators stay default fg.
+ */
+.chroma .c, .chroma .c1, .chroma .c2, .chroma .ch, .chroma .cm, .chroma .cp, .chroma .cs {
+ color: var(--muted);
+}
+
+.chroma .s, .chroma .s1, .chroma .s2, .chroma .sa, .chroma .sb, .chroma .sc,
+.chroma .sd, .chroma .se, .chroma .sf, .chroma .sh, .chroma .si, .chroma .sj,
+.chroma .sr, .chroma .ss, .chroma .sx,
+.chroma .m, .chroma .mb, .chroma .mf, .chroma .mh, .chroma .mi, .chroma .mo, .chroma .mx,
+.chroma .kc, .chroma .no {
+ color: var(--lavender);
+}
+
+.chroma .fm, .chroma .nc, .chroma .ne, .chroma .nf, .chroma .nl, .chroma .nn {
+ color: var(--cyan);
+}
+
+.chroma .gi { color: var(--green); }
+.chroma .gd { color: var(--red); }
+.chroma .err { color: var(--red); }
+.chroma .ge { font-style: italic; }
+.chroma .gs { font-weight: bold; }
+</style>
+</head>
+<body>
+<button id="theme" hidden></button>
+<article>
+{{$doc.Body | markdown}}
+</article>
+<script>
+(function () {
+ var root = document.documentElement;
+ var btn = document.getElementById("theme");
+ var system = matchMedia("(prefers-color-scheme: dark)");
+
+ function effective() {
+ return root.dataset.theme || (system.matches ? "dark" : "light");
+ }
+
+ function label() {
+ var next = effective() === "dark" ? "light" : "dark";
+ btn.textContent = next === "dark" ? "☽" : "☀";
+ btn.setAttribute("aria-label", "Switch to " + next + " theme");
+ }
+
+ btn.addEventListener("click", function () {
+ var next = effective() === "dark" ? "light" : "dark";
+ // Matching the system again drops back to following it.
+ if (next === (system.matches ? "dark" : "light")) {
+ delete root.dataset.theme;
+ try { localStorage.removeItem("theme"); } catch (e) {}
+ } else {
+ root.dataset.theme = next;
+ try { localStorage.setItem("theme", next); } catch (e) {}
+ }
+ label();
+ });
+
+ system.addEventListener("change", label);
+ label();
+ btn.hidden = false;
+})();
+</script>
+</body>
+</html>
diff --git a/first-class-sessions.md b/first-class-sessions.md
new file mode 100644
index 0000000..a035566
--- /dev/null
+++ b/first-class-sessions.md
@@ -0,0 +1,67 @@
+# First-class sessions
+
+This is a vision of how software teams work with coding agents: an idea
+held apart from any particular tool or project.
+
+Teams collaborate on agent work through the pull request today, and the
+pull request arrives too late: by the time anyone else can look, the
+intent, the conversation, and the dead ends have already evaporated,
+leaving a diff and a description of it. Collaboration does reach earlier
+than that — an agent working a chat thread already has people steering it
+together — but a thread stands apart from the changes it produces, and it
+fades as the channel scrolls on.
+
+The central claim is that the session is first-class: the primary
+component of agent-driven software work. A session is the whole unit: who
+asked for what, the conversation that steered the work, the actions agents
+and people took along the way, and the changes that came out of it. It is
+durable and addressable, at a URL that works while the work is happening
+and keeps working after. Everything else derives from the session — a pull
+request is one export of it, not the frame around it.
+
+## What changes when this works
+
+- Collaboration moves earlier, until review stops being a separate stage.
+ People join, watch, and steer work while it happens, and the session
+ absorbs review: by the time the work is done, the people who needed to
+ look already have.
+- Agent work becomes legible. A session keeps the wrong turns along with
+ the result, so a team can learn from how the work went and not only from
+ what landed.
+- Sessions feed the system's own improvement. A durable record of real
+ work — the ask, the steering, the corrections, the outcome — is an eval
+ corpus nobody had to construct: replay a past session against a new
+ agent, prompt, or policy and compare how the work goes, or mine sessions
+ for where agents stalled and where people had to step in. The same
+ record that makes work legible to the team lets the tooling study and
+ improve itself.
+- Work forks. A session is a starting point as well as a record: a new
+ session branched off an existing one carries its context and its
+ changes, so exploring an alternative does not mean starting over.
+- Sessions are context for other sessions. Anything in the ecosystem can
+ reach the session that produced a change, so reviewing agents can examine
+ work with the exact history of how it came to be, or deliberately without
+ it — blind review becomes a choice rather than the default. The
+ examination is itself a session, one the original work can consult in
+ turn.
+- Every participant is a peer on one record. Humans, agents, and channels
+ (a chat thread can be a participant) act through attributed turns, and
+ the session's own log of who did what is the shared truth.
+- Capability flows through the session. An agent holds nothing of its own;
+ it asks the session for what it needs (a repository, a pull request, a
+ build) and the grant carries the authority of the person behind the ask.
+ Shared sessions make this necessary: once several people are present,
+ "who may do this" has to have an answer.
+
+## What this is not
+
+- An agent framework. Agents are built elsewhere and reason however they
+ reason; the session decides what they may do.
+- A pipeline runner. Automation can open a session — a schedule, an alert,
+ a failing build — but what it opens is a session like any other, one
+ that people can join and steer. Work nobody could join is a different
+ tool.
+- A hosted product. It runs where the team runs, inside the team's own
+ trust boundary.
+
+_Composed by Claude_