diff --git a/website/blog/2026-07-14-capture-layer-for-portable-context.md b/website/blog/2026-07-14-capture-layer-for-portable-context.md
new file mode 100644
index 00000000..652ae294
--- /dev/null
+++ b/website/blog/2026-07-14-capture-layer-for-portable-context.md
@@ -0,0 +1,204 @@
+---
+slug: /2026-07-14-capture-layer-for-portable-context
+date: 2026-07-14
+canonical_url: https://dfberry.github.io/blog/2026-07-14-capture-layer-for-portable-context
+custom_edit_url: null
+sidebar_label: "2026.07.14 Capture layer"
+title: "I Built the Capture Layer for My Portable Personal Context"
+description: "I built a Copilot CLI extension that turns my sessions into structured YAML observations for a human-reviewed portable personal context pipeline."
+tags:
+ - ai
+ - copilot
+ - personal context
+ - github
+ - markdown
+ - productivity
+ - architecture
+ - memory
+ - copilot cli
+ - plugin
+ - extension
+ - observability
+ - yaml
+keywords:
+ - copilot cli plugin
+ - copilot cli extension
+ - portable personal context
+ - ai memory feed
+ - ai observation log
+ - copilot response logging
+ - yaml ai logs
+ - personal context pipeline
+ - observation candidate ratification context
+ - local first ai memory
+ - developer productivity ai
+ - ai context architecture
+ - prompt injection retrieval boundary
+ - second brain for developers
+draft: true
+---
+
+
+
+*The capture layer is just this: every finished session leaves behind a note I can review later.*
+
+I built the missing memory half from my last post.
+
+In [Portable Personal Context Across AI Client Surfaces](/2026-07-10-portable-personal-context), I separated **personal context** from **memory**. Context is the small set of reviewed facts I want tools to trust. Memory is what happened while I worked.
+
+The gap was capture. I needed a way for Copilot CLI sessions to leave behind structured observations I could review later. So I built `copilot-cli-log-to-file`: a Copilot CLI extension that writes each finished turn to a timestamped file I own.
+
+> πΌοΈ **Image prompt:** Create a dark developer-architecture hero image on background #1a1a2e showing a terminal window labeled "Copilot CLI" emitting small timestamped YAML documents into a folder labeled "memory feed". A separate curated markdown repo labeled "portable personal context" sits to the right, connected by a narrow gate labeled "human ratification". Style: clean vector, subtle cyan and violet accents, no people, no logos, high contrast, suitable for a technical blog header.
+
+---
+
+## Capture the memory feed first
+
+The stronger angle is **memory feed**, not cross-computer sync.
+
+Sync is useful, but it comes later. What I needed first was a raw feed: timestamped session evidence that could become an observation in the pipeline from the first post.
+
+```text
+observation β candidate β [ratification gate] β context
+ (memory) (proposed) (a human decision) (canonical)
+```
+
+A capture file is not context. It can prove that I asked a question, that Copilot answered, and that a tool ran if I opted into tool capture. It should not silently become an instruction. I still want the human gate because one weird session should not rewrite my operating rules.
+
+## Show what my extension writes
+
+`copilot-cli-log-to-file` runs after a Copilot CLI turn finishes. By default, it writes a complete YAML document with `timestamp`, `sessionId`, `prompt`, and `response`.
+
+The default filename pattern is `{timestamp}-{prompt30}.yaml`, so a log folder looks like this:
+
+```text
+copilot-response-log/
+ 2026-07-13T11-21-45Z-list-all-my-files.yaml
+ 2026-07-13T14-05-02Z-explain-this-function.yaml
+```
+
+That filename format mattered more than I expected. I can sort by time, skim the prompt slug, and delete one turn without touching the rest of the history.
+
+I kept enriched capture off by default. If I want more detail, I opt in by category: attachments, reasoning, tool calls, tool results, usage, model changes, skills, subagents, permissions, errors, lifecycle events, turn boundaries, schedules, or notifications. There is also a `COPILOT_LOG_CAPTURE_ALL=true` override, but I treat that as a deliberate choice because these files can hold private data.
+
+Here is a small capture with usage and tools enabled:
+
+```yaml
+timestamp: "2026-07-14T18:21:45.000Z"
+sessionId: "9f4c2a7b12345678"
+prompt: |-
+ Summarize the current branch and suggest the next test to run.
+tools:
+ - toolCallId: "call_abc123"
+ toolName: "git"
+ arguments: '{"command":"status --short"}'
+ success: true
+ result: "M website/blog/2026-07-14-capture-layer-for-portable-context.md"
+usage:
+ - model: "gpt-5"
+ inputTokens: 1840
+ outputTokens: 420
+ cacheReadTokens: 600
+ cacheWriteTokens: 0
+ duration: 1310
+ finishReason: "stop"
+response: |-
+ The branch has one modified blog post. The next targeted validation is to re-read
+ the file for frontmatter, link, and YAML-example accuracy before committing.
+```
+
+The `toolCalls` and `toolResults` toggles feed one merged `tools:` block. That was a small design choice, but it keeps the question I care about in one place: what tool ran, with which arguments, and what happened.
+
+Two implementation details saved me from future debugging. Copilot CLI reserves stdout for JSON-RPC, so I use `session.log(...)` instead of `console.log(...)`. The runtime also provides `@github/copilot-sdk`; I do not ship it as an npm runtime dependency. `js-yaml` stays dev-only for tests because the extension has a hand-rolled YAML emitter.
+
+> π¨ **Diagram prompt:** Create a dark-background technical diagram (#1a1a2e) titled "Copilot CLI capture layer". Left node: "User prompt". Middle node: "Copilot CLI turn" with two smaller internal labels: "assistant response" and "events". Right node: "timestamped YAML file". Under the YAML file, list optional sections: "attachments", "tools", "usage", "skills", "errors". Add a small note beside the optional sections: "all enriched captures default OFF". Use rounded boxes, cyan arrows, and compact developer-diagram styling.
+
+
+
+## Promote observations only after review
+
+The first post's pipeline feels less abstract now that I have real files on disk.
+
+```text
+copilot-response-log/*.yaml
+ β
+observation
+ β
+candidate
+ β
+[ratification gate]
+ β
+portable personal context repo
+```
+
+The YAML file is the observation. A candidate is the proposed durable fact I might extract from one or more observations. The context repo is where the approved rule lives.
+
+For example, a few sessions might suggest that I prefer targeted validation before full-suite validation for docs-only work. That is still only evidence. I decide whether it belongs in `process/quality-bar.md`, `decisions/_active.md`, or nowhere.
+
+| Pipeline stage | Source | Status | Example |
+|---|---|---|---|
+| Observation | YAML capture | Raw evidence | "This session used a targeted validation step." |
+| Candidate | Extracted proposal | Not authoritative | "Maybe targeted validation is preferred." |
+| Ratification | Human review | Decision point | "Yes, make this a workflow rule." |
+| Context | Markdown repo | Canonical | "For docs-only changes, re-read the edited file first." |
+
+Boring is good here. The capture layer does not promote anything by itself. That keeps mistakes, one-off exceptions, and prompt-injection garbage out of my canonical context unless I approve them.
+
+> π¨ **Diagram prompt:** Create a dark vector flow diagram on #1a1a2e showing four horizontal stages: "Observation (YAML log)", "Candidate (proposed fact)", "Ratification gate (human review)", and "Context (canonical markdown)". Make the ratification gate visually distinct as a narrow checkpoint with a lock icon. Add a red bypass arrow labeled "do not auto-promote" that is crossed out. Style should match a precise engineering architecture diagram.
+
+
+
+## Keep the feed in files I can inspect
+
+I chose files because I wanted the memory feed to be boring to operate.
+
+A hidden store might be easier for a product team to manage. It is less helpful when I want to understand what happened. With YAML files, I can open a turn, grep the folder, parse a subset, redact a bad capture, or delete a whole day.
+
+| Choice | Why I used it |
+|---|---|
+| **YAML documents** | A parser can load each file directly. I can still read it. |
+| **One file per turn** | I can archive or delete one capture without editing a combined log. |
+| **Configurable folder** | I can keep logs local or point them at a private synced location. |
+| **Plain text option** | I can choose readability when structured ingestion is not the goal. |
+
+I do not need a private service to prove the idea. I need a folder that my tools can read and that I can clean up when a log catches something I do not want to keep.
+
+## Treat logs as private-tier data
+
+These logs hold my secrets if I let them.
+
+A prompt can include file paths, pasted snippets, internal project names, tool arguments, tool results, permission prompts, errors, and final responses. Enriched capture defaults to off because the safe default is a small capture. When I turn on tools or usage, I am choosing to record more evidence.
+
+So I treat `copilot-response-log` like the private tier from the first post:
+
+- I keep raw captures local unless I have a private sync policy.
+- I scrub before committing any log-derived material.
+- I do not point every AI surface at the raw folder.
+- I enforce trust tiers before retrieval.
+
+That last point is the security boundary. If an untrusted surface can read my raw logs, filtering the final answer is too late.
+
+> π¨ **Diagram prompt:** Create a dark security-boundary diagram (#1a1a2e) with a folder labeled "raw Copilot CLI logs" inside a red-outlined zone labeled "private tier". Outside the zone, show three consumers: "local parser", "curated context repo", and "untrusted AI surface". Draw an allowed arrow from logs to local parser, an allowed arrow through "scrub + ratify" to curated context, and a blocked arrow from logs directly to untrusted AI surface. Use red for blocked access, cyan for allowed access.
+
+
+
+## Sync only after the capture layer exists
+
+Once the feed is files, cross-computer sync becomes straightforward. I can point the extension at a folder backed by git, OneDrive, or another private file-sync tool.
+
+That still does not make every surface smarter. Each surface needs wiring, and I do not want most surfaces reading raw logs anyway.
+
+```text
+Computer A: Copilot CLI β copilot-response-log/*.yaml
+Computer B: Copilot CLI β same synced folder
+Review step: observations β candidates β approved context
+Wired surfaces: read approved context, not raw logs by default
+```
+
+The central second brain is not the log folder. It is the loop: capture evidence, review candidates, then promote the facts I trust into portable context. The raw feed helps me stop losing useful session evidence. The curated repo helps tools stop relearning approved facts.
+
+## Keep the next step boring
+
+This is not automatic yet. Ratification is still human. Candidate extraction still needs review. Each AI surface still needs its own way to read approved context. There is no shared `$AI_CONTEXT_PATH` that every tool honors.
+
+That is okay for this step. I built one small piece: a Copilot CLI extension that captures my missing memory feed as structured files. Now the architecture from the first post has something concrete to promote.
diff --git a/website/blog/2026-07-17-capture-layer-for-portable-context.md b/website/blog/2026-07-17-capture-layer-for-portable-context.md
new file mode 100644
index 00000000..7c471f0d
--- /dev/null
+++ b/website/blog/2026-07-17-capture-layer-for-portable-context.md
@@ -0,0 +1,196 @@
+---
+slug: /2026-07-17-capture-layer-for-portable-context
+date: 2026-07-17
+canonical_url: https://dfberry.github.io/blog/2026-07-17-capture-layer-for-portable-context
+custom_edit_url: null
+sidebar_label: "2026.07.17 Capture layer"
+title: "I Built the Capture Layer for My Portable Personal Context"
+description: "I built a Copilot CLI extension that turns my sessions into structured YAML observations for a human-reviewed portable personal context pipeline."
+tags:
+ - AI
+ - Personal Context
+ - Copilot CLI
+ - Architecture
+keywords:
+ - portable personal context pipeline
+ - copilot session logging
+ - ai observation capture
+ - local-first ai memory
+ - context architecture
+ - prompt injection retrieval boundary
+ - second brain for developers
+---
+
+
+
+Every session now leaves behind a timestamped note I can review, audit, or delete. No cloud sync. No hidden database. Just files I own.
+
+I built the missing memory half from [my personal context blog post](/2026-07-10-portable-personal-context).
+
+In [Portable Personal Context Across AI Client Surfaces](/2026-07-10-portable-personal-context), I separated **personal context** from **memory**. Context is the small set of reviewed facts I want tools to trust. Memory is what happened while I worked.
+
+The gap was capture. I needed a way for Copilot CLI sessions to leave behind structured observations I could review later. So I built `copilot-cli-log-to-file`: a Copilot CLI extension that writes each finished turn to a timestamped file I own.
+
+The result: a reproducible, human-controlled pipeline from raw evidence to trusted rulesβwithout accidental promotions.
+
+---
+
+## Capture the memory feed first
+
+The stronger angle is **memory feed**, not cross-computer sync.
+
+Sync is useful, but it comes later. What I needed first was a raw feed: timestamped session evidence that could become an observation in the pipeline.
+
+```text
+observation β candidate β [ratification gate] β context
+ (memory) (proposed) (a human decision) (canonical)
+```
+
+A capture file is not context. It can prove that I asked a question, that Copilot answered, and that a tool ran if I opted into tool capture. It should not silently become an instruction. I still want the human gate because one weird session should not rewrite my operating rules.
+
+## Extension output
+
+`copilot-cli-log-to-file` runs after a Copilot CLI turn finishes. By default, it writes a complete YAML document with `timestamp`, `sessionId`, `prompt`, and `response`.
+
+The default filename pattern is `{timestamp}-{prompt30}.yaml`, so a log folder looks like this:
+
+```text
+copilot-response-log/
+ 2026-07-13T11-21-45Z-list-all-my-files.yaml
+ 2026-07-13T14-05-02Z-explain-this-function.yaml
+```
+
+That filename format mattered more than I expected. I can sort by time, skim the prompt slug, and delete one turn without touching the rest of the history.
+
+I kept enriched capture off by default. If I want more detail, I opt in by category: attachments, reasoning, tool calls, tool results, usage, model changes, skills, subagents, permissions, errors, lifecycle events, turn boundaries, schedules, or notifications. There is also a `COPILOT_LOG_CAPTURE_ALL=true` override, but I treat that as a deliberate choice because these files can hold private data.
+
+Here is a small capture with usage and tools enabled:
+
+```yaml
+timestamp: "2026-07-14T18:21:45.000Z"
+sessionId: "9f4c2a7b12345678"
+prompt: |-
+ Summarize the current branch and suggest the next test to run.
+tools:
+ - toolCallId: "call_abc123"
+ toolName: "git"
+ arguments: '{"command":"status --short"}'
+ success: true
+ result: "M website/blog/2026-07-17-capture-layer-for-portable-context.md"
+usage:
+ - model: "gpt-5"
+ inputTokens: 1840
+ outputTokens: 420
+ cacheReadTokens: 600
+ cacheWriteTokens: 0
+ duration: 1310
+ finishReason: "stop"
+response: |-
+ The branch has one modified blog post. The next targeted validation is to re-read
+ the file for frontmatter, link, and YAML-example accuracy before committing.
+```
+
+The `toolCalls` and `toolResults` toggles feed one merged `tools:` block. That was a small design choice, but it keeps the question I care about in one place: what tool ran, with which arguments, and what happened.
+
+Two implementation details saved me from future debugging. Copilot CLI reserves stdout for JSON-RPC, so I use `session.log(...)` instead of `console.log(...)`. The runtime also provides `@github/copilot-sdk`; I do not ship it as an npm runtime dependency. `js-yaml` stays dev-only for tests because the extension has a hand-rolled YAML emitter.
+
+The capture layer transforms a CLI turn into structured data. A turn contains two key outputs: the `assistant response` (Copilot's answer) and `events` (the structured record of what happenedβtool calls, model usage, and other metadata). Both flow into the timestamped YAML file.
+
+
+
+## Promote observations only after review
+
+The previous blog post's pipeline feels less abstract now that I have real files on disk.
+
+```text
+copilot-response-log/*.yaml
+ β
+observation
+ β
+candidate
+ β
+[ratification gate]
+ β
+portable personal context repo
+```
+
+The YAML file is the observation. A candidate is the proposed durable fact I might extract from one or more observations. The context repo is where the approved rule lives.
+
+For example, a few sessions might suggest that I prefer targeted validation before full-suite validation for docs-only work. That is still only evidence. I decide whether it belongs in `process/quality-bar.md`, `decisions/_active.md`, or nowhere.
+
+| Pipeline stage | Source | Status | Example |
+|---|---|---|---|
+| Observation | YAML capture | Raw evidence | "This session used a targeted validation step." |
+| Candidate | Extracted proposal | Not authoritative | "Maybe targeted validation is preferred." |
+| Ratification | Human review | Decision point | "Yes, make this a workflow rule." |
+| Context | Markdown repo | Canonical | "For docs-only changes, re-read the edited file first." |
+
+Boring is good here. The capture layer does not promote anything by itself. That keeps mistakes, one-off exceptions, and prompt-injection garbage out of my canonical context unless I approve them.
+
+
+
+## Keep the feed in files I can inspect
+
+I chose files because I wanted the memory feed to stay under my controlβfully inspectable and modifiable.
+
+A hidden store might be easier for a product team to manage. It is less helpful when I want to understand what happened. With YAML files, I can open a turn, grep the folder, parse a subset, redact a bad capture, or delete a whole day.
+
+| Choice | Why I used it |
+|---|---|
+| **YAML documents** | A parser can load each file directly. I can still read it. |
+| **One file per turn** | I can archive or delete one capture without editing a combined log. |
+| **Configurable folder** | I can keep logs local or point them at a private synced location. |
+| **Plain text option** | I can choose readability when structured ingestion is not the goal. |
+
+I do not need a private service to prove the idea. I need a folder that my tools can read and that I can clean up when a log catches something I do not want to keep.
+
+## Treat logs as private-tier data
+
+These logs hold my secrets if I let them.
+
+A prompt can include file paths, pasted snippets, internal project names, tool arguments, tool results, permission prompts, errors, and final responses. Enriched capture defaults to off because the safe default is a small capture. When I turn on tools or usage, I am choosing to record more evidence.
+
+So I treat `copilot-response-log` like the private tier from [my personal context blog post](/2026-07-10-portable-personal-context):
+
+- I keep raw captures local unless I have a private sync policy.
+- I scrub before committing any log-derived material.
+- I do not point every AI surface at the raw folder.
+- I enforce trust tiers before retrieval.
+
+That last point is the security boundary. If an untrusted surface can read my raw logs, filtering the final answer is too late.
+
+
+
+## Sync across computers only after the capture layer exists
+
+Once the feed is files, cross-computer sync becomes straightforward. I can point the extension at a folder backed by git, OneDrive, or another private file-sync tool.
+
+That still does not make every surface smarter. Each surface needs wiring, and I do not want most surfaces reading raw logs anyway.
+
+```text
+Computer A: Copilot CLI β copilot-response-log/*.yaml
+Computer B: Copilot CLI β same synced folder
+Review step: observations β candidates β approved context
+Wired surfaces: read approved context, not raw logs by default
+```
+
+The central second brain is not the log folder. It is the loop: capture evidence, review candidates, then promote the facts I trust into portable context. The raw feed stops useful session evidence from vanishing. The curated repo stops tools from relearning the same facts twice.
+
+## Keep the next step boring
+
+This is not automatic yet. Ratification is still human. Candidate extraction still needs review. Each AI surface still needs its own way to read approved context. There is no shared `$AI_CONTEXT_PATH` that every tool honors.
+
+That is okay for this step. I built one small piece: a Copilot CLI extension that captures my missing memory feed as structured files. Now the architecture from [my personal context blog post](/2026-07-10-portable-personal-context) has something concrete to promote.
+
+## What's next: candidate extraction and ratification
+
+The capture layer answers "how do I collect evidence?" The next piece is "how do I turn evidence into trusted rules?"
+
+That means three things:
+1. **Candidate extraction** β Tooling that reads a week of captures and proposes facts worth considering. "You ran targeted validation before full-suite validation in 5 docs-only sessions. Is this a pattern?"
+2. **Ratification interface** β A simple review UI where I approve, reject, or refine each candidate before it becomes canonical. No auto-promotion. No surprises.
+3. **Context repo publishing** β Once approved, a candidate becomes a rule in the portable personal context repo. Then every surface that reads that repo knows about it.
+
+Until then, the captures sit in their folder as raw evidence. They prove what happened. They don't decide anything. That's the safety boundary I chose to keep.
+
+Next session, I'll build the ratification loop.
diff --git a/website/blog/media/2026-07-14-capture-layer-for-portable-context/batch_2026-07-14-capture-layer-for-portable-context.json b/website/blog/media/2026-07-14-capture-layer-for-portable-context/batch_2026-07-14-capture-layer-for-portable-context.json
new file mode 100644
index 00000000..65c29749
--- /dev/null
+++ b/website/blog/media/2026-07-14-capture-layer-for-portable-context/batch_2026-07-14-capture-layer-for-portable-context.json
@@ -0,0 +1,8 @@
+[
+ {
+ "prompt": "Watercolor illustration, soft wet-on-wet washes, warm muted tones. White female with pink hair seated at a wooden desk, one hand holding a pen, the other hand resting on paper, warm evening lamplight, visible paper texture, loose brushwork, no text, no labels",
+ "output": "outputs/2026-07-14-capture-layer-for-portable-context/watercolor-01-capture-hero.png",
+ "seed": 71,
+ "negative_prompt": "text, letters, words, watermark, signature, photorealistic, 3D render, cartoon, anime, ink, line art, bold outlines, hard edges, comic, missing arm, extra arm, extra limbs, deformed hands, mutated hands, bad anatomy, disfigured"
+ }
+]
\ No newline at end of file
diff --git a/website/blog/media/2026-07-14-capture-layer-for-portable-context/capture-layer-architecture.mmd b/website/blog/media/2026-07-14-capture-layer-for-portable-context/capture-layer-architecture.mmd
new file mode 100644
index 00000000..78c0dbd6
--- /dev/null
+++ b/website/blog/media/2026-07-14-capture-layer-for-portable-context/capture-layer-architecture.mmd
@@ -0,0 +1,23 @@
+%%{init: {'theme':'base', 'themeVariables': {'background':'#1a1a2e','mainBkg':'#202040','primaryColor':'#7B2FA0','primaryTextColor':'#FFFFFF','primaryBorderColor':'#8BE9FD','lineColor':'#8BE9FD','edgeLabelBackground':'#1a1a2e','fontFamily':'inherit'}}}%%
+flowchart LR
+ Prompt["User prompt"] --> TurnShell
+ subgraph Turn["Copilot CLI turn"]
+ direction TB
+ TurnShell[" "]
+ Response["assistant response"]
+ Events["events"]
+ TurnShell --- Response
+ TurnShell --- Events
+ end
+ TurnShell --> YAML["timestamped YAML file"]
+ YAML --> Optional["optional sections
attachments
tools
usage
skills
errors"]
+ Note["all enriched captures
default OFF"] -.-> Optional
+
+ classDef cyan fill:#102A43,stroke:#8BE9FD,color:#FFFFFF,stroke-width:2px;
+ classDef violet fill:#3D236B,stroke:#BD93F9,color:#FFFFFF,stroke-width:2px;
+ classDef note fill:#2B2640,stroke:#F1FA8C,color:#FFFFFF,stroke-dasharray:4 3;
+ class Prompt,YAML,Optional cyan;
+ class Response,Events violet;
+ class Note note;
+ style Turn fill:#202040,stroke:#BD93F9,stroke-width:2px,color:#FFFFFF
+ style TurnShell fill:transparent,stroke:transparent,color:transparent
diff --git a/website/blog/media/2026-07-14-capture-layer-for-portable-context/capture-layer-architecture.png b/website/blog/media/2026-07-14-capture-layer-for-portable-context/capture-layer-architecture.png
new file mode 100644
index 00000000..2ea6b4cc
Binary files /dev/null and b/website/blog/media/2026-07-14-capture-layer-for-portable-context/capture-layer-architecture.png differ
diff --git a/website/blog/media/2026-07-14-capture-layer-for-portable-context/observation-pipeline.mmd b/website/blog/media/2026-07-14-capture-layer-for-portable-context/observation-pipeline.mmd
new file mode 100644
index 00000000..74fd9b0b
--- /dev/null
+++ b/website/blog/media/2026-07-14-capture-layer-for-portable-context/observation-pipeline.mmd
@@ -0,0 +1,16 @@
+%%{init: {'theme':'base', 'themeVariables': {'background':'#1a1a2e','mainBkg':'#202040','primaryColor':'#7B2FA0','primaryTextColor':'#FFFFFF','primaryBorderColor':'#8BE9FD','lineColor':'#8BE9FD','edgeLabelBackground':'#1a1a2e','fontFamily':'inherit'}}}%%
+flowchart LR
+ Observation["Observation
(YAML log)"] --> Candidate["Candidate
(proposed fact)"]
+ Candidate --> Gate["π Ratification gate
(human review)"]
+ Gate --> Context["Context
(canonical markdown)"]
+ Candidate -. "do not auto-promote" .-> Stop["β blocked"]
+ Stop -.-> Context
+
+ classDef stage fill:#102A43,stroke:#8BE9FD,color:#FFFFFF,stroke-width:2px;
+ classDef gate fill:#3D236B,stroke:#BD93F9,color:#FFFFFF,stroke-width:3px;
+ classDef blocked fill:#3A1118,stroke:#FF5555,color:#FFFFFF,stroke-width:3px;
+ class Observation,Candidate,Context stage;
+ class Gate gate;
+ class Stop blocked;
+ linkStyle 0,1,2 stroke:#8BE9FD,stroke-width:3px;
+ linkStyle 3,4 stroke:#FF5555,stroke-width:3px,stroke-dasharray:6 4;
diff --git a/website/blog/media/2026-07-14-capture-layer-for-portable-context/observation-pipeline.png b/website/blog/media/2026-07-14-capture-layer-for-portable-context/observation-pipeline.png
new file mode 100644
index 00000000..ac28d8b2
Binary files /dev/null and b/website/blog/media/2026-07-14-capture-layer-for-portable-context/observation-pipeline.png differ
diff --git a/website/blog/media/2026-07-14-capture-layer-for-portable-context/private-tier-boundary.mmd b/website/blog/media/2026-07-14-capture-layer-for-portable-context/private-tier-boundary.mmd
new file mode 100644
index 00000000..c28fa5f1
--- /dev/null
+++ b/website/blog/media/2026-07-14-capture-layer-for-portable-context/private-tier-boundary.mmd
@@ -0,0 +1,27 @@
+%%{init: {'theme':'base', 'themeVariables': {'background':'#1a1a2e','mainBkg':'#202040','primaryColor':'#7B2FA0','primaryTextColor':'#FFFFFF','primaryBorderColor':'#8BE9FD','lineColor':'#8BE9FD','edgeLabelBackground':'#1a1a2e','fontFamily':'inherit'}}}%%
+flowchart LR
+ subgraph Private["private tier"]
+ direction TB
+ Logs["π raw Copilot CLI logs"]
+ end
+
+ Parser["local parser"]
+ Scrub["scrub + ratify"]
+ Context["curated context repo"]
+ Untrusted["untrusted AI surface"]
+
+ Logs --> Parser
+ Logs --> Scrub --> Context
+ Logs -. "BLOCKED" .-> Untrusted
+
+ classDef privateNode fill:#3A1118,stroke:#FF5555,color:#FFFFFF,stroke-width:2px;
+ classDef allowed fill:#102A43,stroke:#8BE9FD,color:#FFFFFF,stroke-width:2px;
+ classDef scrub fill:#3D236B,stroke:#BD93F9,color:#FFFFFF,stroke-width:2px;
+ classDef blocked fill:#3A1118,stroke:#FF5555,color:#FFFFFF,stroke-width:3px;
+ class Logs privateNode;
+ class Parser,Context allowed;
+ class Scrub scrub;
+ class Untrusted blocked;
+ style Private fill:#241424,stroke:#FF5555,stroke-width:3px,color:#FFFFFF
+ linkStyle 0,1,2 stroke:#8BE9FD,stroke-width:3px;
+ linkStyle 3 stroke:#FF5555,stroke-width:4px,stroke-dasharray:6 4;
diff --git a/website/blog/media/2026-07-14-capture-layer-for-portable-context/private-tier-boundary.png b/website/blog/media/2026-07-14-capture-layer-for-portable-context/private-tier-boundary.png
new file mode 100644
index 00000000..1ec8a9e5
Binary files /dev/null and b/website/blog/media/2026-07-14-capture-layer-for-portable-context/private-tier-boundary.png differ
diff --git a/website/blog/media/2026-07-14-capture-layer-for-portable-context/watercolor-01-capture-hero.png b/website/blog/media/2026-07-14-capture-layer-for-portable-context/watercolor-01-capture-hero.png
new file mode 100644
index 00000000..45679c26
Binary files /dev/null and b/website/blog/media/2026-07-14-capture-layer-for-portable-context/watercolor-01-capture-hero.png differ
diff --git a/website/blog/media/2026-07-17-capture-layer-for-portable-context/capture-layer-architecture.png b/website/blog/media/2026-07-17-capture-layer-for-portable-context/capture-layer-architecture.png
new file mode 100644
index 00000000..d3e5aed4
Binary files /dev/null and b/website/blog/media/2026-07-17-capture-layer-for-portable-context/capture-layer-architecture.png differ
diff --git a/website/blog/media/2026-07-17-capture-layer-for-portable-context/observation-pipeline.png b/website/blog/media/2026-07-17-capture-layer-for-portable-context/observation-pipeline.png
new file mode 100644
index 00000000..6db8d2a8
Binary files /dev/null and b/website/blog/media/2026-07-17-capture-layer-for-portable-context/observation-pipeline.png differ
diff --git a/website/blog/media/2026-07-17-capture-layer-for-portable-context/private-tier-boundary.png b/website/blog/media/2026-07-17-capture-layer-for-portable-context/private-tier-boundary.png
new file mode 100644
index 00000000..9e2baff3
Binary files /dev/null and b/website/blog/media/2026-07-17-capture-layer-for-portable-context/private-tier-boundary.png differ
diff --git a/website/blog/media/2026-07-17-capture-layer-for-portable-context/watercolor-01-capture-hero.png b/website/blog/media/2026-07-17-capture-layer-for-portable-context/watercolor-01-capture-hero.png
new file mode 100644
index 00000000..45679c26
Binary files /dev/null and b/website/blog/media/2026-07-17-capture-layer-for-portable-context/watercolor-01-capture-hero.png differ
diff --git a/website/blog/mermaid/2026-07-17-capture-layer-for-portable-context-capture-layer-architecture.mmd b/website/blog/mermaid/2026-07-17-capture-layer-for-portable-context-capture-layer-architecture.mmd
new file mode 100644
index 00000000..79c03845
--- /dev/null
+++ b/website/blog/mermaid/2026-07-17-capture-layer-for-portable-context-capture-layer-architecture.mmd
@@ -0,0 +1,47 @@
+%%{init: {
+ "theme": "base",
+ "themeVariables": {
+ "fontFamily": "Inter, ui-sans-serif, system-ui",
+ "primaryColor": "#f5e6d3",
+ "primaryTextColor": "#3d2817",
+ "primaryBorderColor": "#d4896b",
+ "lineColor": "#c9956b",
+ "secondaryColor": "#fdf4e8",
+ "secondaryTextColor": "#3d2817",
+ "secondaryBorderColor": "#b8836f",
+ "tertiaryColor": "#f9ead8",
+ "tertiaryTextColor": "#4a3428",
+ "tertiaryBorderColor": "#d4896b",
+ "background": "#fef9f5",
+ "mainBkg": "#fef9f5",
+ "secondBkg": "#fef9f5",
+ "tertiaryBkg": "#fef9f5",
+ "textColor": "#3d2817",
+ "fontSize": "16px"
+ }
+}}%%
+
+graph LR
+ subgraph done["β
Implemented in This Post: Capture Layer"]
+ prompt["π€ User prompt"]
+ turn["βοΈ Copilot CLI turn"]
+ response["π assistant response"]
+ events["π events"]
+ yaml["π timestamped
YAML file"]
+ optional["β optional sections:
attachments
tools
usage
skills
errors"]
+
+ prompt -->|input| turn
+ turn -->|output| response
+ turn -->|output| events
+ response --> yaml
+ events --> yaml
+ yaml -->|contains| optional
+ end
+
+ style done fill:#fef9f5,stroke:#d4896b,stroke-width:3px,color:#3d2817
+ style prompt fill:#f5e6d3,stroke:#d4896b,stroke-width:3px,color:#3d2817,rx:12,ry:12,font-weight:700,font-size:15px
+ style turn fill:#fdf4e8,stroke:#b8836f,stroke-width:3px,color:#3d2817,rx:12,ry:12,font-weight:700,font-size:15px
+ style response fill:#f9ead8,stroke:#d4896b,stroke-width:2.5px,color:#3d2817,rx:10,ry:10,font-weight:600,font-size:14px
+ style events fill:#f9ead8,stroke:#d4896b,stroke-width:2.5px,color:#3d2817,rx:10,ry:10,font-weight:600,font-size:14px
+ style yaml fill:#f5e6d3,stroke:#c9956b,stroke-width:3px,color:#3d2817,rx:12,ry:12,font-weight:700,font-size:15px
+ style optional fill:#fef9f5,stroke:#b8836f,stroke-width:2.5px,color:#4a3428,rx:10,ry:10,font-weight:500,font-size:13px
diff --git a/website/blog/mermaid/2026-07-17-capture-layer-for-portable-context-observation-pipeline.mmd b/website/blog/mermaid/2026-07-17-capture-layer-for-portable-context-observation-pipeline.mmd
new file mode 100644
index 00000000..28d2e2dd
--- /dev/null
+++ b/website/blog/mermaid/2026-07-17-capture-layer-for-portable-context-observation-pipeline.mmd
@@ -0,0 +1,47 @@
+%%{init: {
+ "theme": "base",
+ "themeVariables": {
+ "fontFamily": "Inter, ui-sans-serif, system-ui",
+ "primaryColor": "#f5e6d3",
+ "primaryTextColor": "#3d2817",
+ "primaryBorderColor": "#d4896b",
+ "lineColor": "#c9956b",
+ "secondaryColor": "#fdf4e8",
+ "secondaryTextColor": "#3d2817",
+ "secondaryBorderColor": "#b8836f",
+ "tertiaryColor": "#f9ead8",
+ "tertiaryTextColor": "#4a3428",
+ "tertiaryBorderColor": "#d4896b",
+ "background": "#fef9f5",
+ "mainBkg": "#fef9f5",
+ "secondBkg": "#fef9f5",
+ "tertiaryBkg": "#fef9f5",
+ "textColor": "#3d2817",
+ "fontSize": "16px"
+ }
+}}%%
+
+graph LR
+ subgraph done["β
This Post: Observation Capture"]
+ observation["π Observation
YAML log
Raw evidence"]
+ end
+
+ subgraph future["π Future Posts: Candidate β Context"]
+ candidate["π Candidate
Proposed fact
Not authoritative"]
+ ratify["π Ratification
Human Review
Decision point"]
+ context["β Context
Canonical markdown
Approved rule"]
+ blocked["β Do not
auto-promote"]
+ end
+
+ observation -->|propose| candidate
+ candidate -->|review| ratify
+ ratify -->|approve| context
+ ratify -.->|reject| blocked
+
+ style done fill:#fef9f5,stroke:#d4896b,stroke-width:3px,color:#3d2817
+ style future fill:#fef9f5,stroke:#a0734d,stroke-width:2px,stroke-dasharray:5,color:#6b4423
+ style observation fill:#f5e6d3,stroke:#d4896b,stroke-width:3px,color:#3d2817,rx:12,ry:12,font-weight:700,font-size:15px
+ style candidate fill:#fdf4e8,stroke:#b8836f,stroke-width:3px,color:#3d2817,rx:12,ry:12,font-weight:700,font-size:15px
+ style ratify fill:#f9ead8,stroke:#d4896b,stroke-width:4px,color:#3d2817,rx:12,ry:12,font-weight:700,font-size:15px
+ style context fill:#f5e6d3,stroke:#d4896b,stroke-width:3px,color:#3d2817,rx:12,ry:12,font-weight:700,font-size:15px
+ style blocked fill:#fef9f5,stroke:#a0734d,stroke-width:2px,stroke-dasharray:5,color:#6b4423,rx:10,ry:10,font-weight:600,font-size:13px
diff --git a/website/blog/mermaid/2026-07-17-capture-layer-for-portable-context-private-tier-boundary.mmd b/website/blog/mermaid/2026-07-17-capture-layer-for-portable-context-private-tier-boundary.mmd
new file mode 100644
index 00000000..b2300720
--- /dev/null
+++ b/website/blog/mermaid/2026-07-17-capture-layer-for-portable-context-private-tier-boundary.mmd
@@ -0,0 +1,52 @@
+%%{init: {
+ "theme": "base",
+ "themeVariables": {
+ "fontFamily": "Inter, ui-sans-serif, system-ui",
+ "primaryColor": "#f5e6d3",
+ "primaryTextColor": "#3d2817",
+ "primaryBorderColor": "#d4896b",
+ "lineColor": "#c9956b",
+ "secondaryColor": "#fdf4e8",
+ "secondaryTextColor": "#3d2817",
+ "secondaryBorderColor": "#b8836f",
+ "tertiaryColor": "#f9ead8",
+ "tertiaryTextColor": "#4a3428",
+ "tertiaryBorderColor": "#d4896b",
+ "background": "#fef9f5",
+ "mainBkg": "#fef9f5",
+ "secondBkg": "#fef9f5",
+ "tertiaryBkg": "#fef9f5",
+ "textColor": "#3d2817",
+ "fontSize": "16px"
+ }
+}}%%
+
+graph LR
+ subgraph private["β
This Post: Private Tier Capture"]
+ logs["π raw Copilot
CLI logs
Sensitive data"]
+ parser["π local parser
candidate
extraction"]
+ end
+
+ subgraph future["π Future Posts: Approval & Publishing"]
+ ratify["π scrub +
ratify
human review"]
+ approved["β curated
context repo
approved rules"]
+ end
+
+ subgraph untrustedZone["β οΈ Untrusted Surfaces"]
+ untrusted["β οΈ untrusted
AI surface
blocked access"]
+ end
+
+ logs -->|private read| parser
+ parser -->|propose| ratify
+ ratify -->|promote| approved
+ untrusted -.->|blocked| logs
+ approved -->|allowed read| untrusted
+
+ style private fill:#fef9f5,stroke:#d4896b,stroke-width:3px,color:#3d2817
+ style future fill:#fef9f5,stroke:#a0734d,stroke-width:2px,stroke-dasharray:5,color:#6b4423
+ style untrustedZone fill:#fef9f5,stroke:#a0734d,stroke-width:2px,stroke-dasharray:5,color:#6b4423
+ style logs fill:#f5e6d3,stroke:#d4896b,stroke-width:3px,color:#3d2817,rx:12,ry:12,font-weight:700,font-size:15px
+ style parser fill:#fdf4e8,stroke:#b8836f,stroke-width:3px,color:#3d2817,rx:12,ry:12,font-weight:700,font-size:15px
+ style ratify fill:#f9ead8,stroke:#d4896b,stroke-width:3px,color:#3d2817,rx:12,ry:12,font-weight:700,font-size:15px
+ style approved fill:#f5e6d3,stroke:#d4896b,stroke-width:3px,color:#3d2817,rx:12,ry:12,font-weight:700,font-size:15px
+ style untrusted fill:#fef9f5,stroke:#a0734d,stroke-width:2px,stroke-dasharray:5,color:#6b4423,rx:12,ry:12,font-weight:600,font-size:15px
diff --git a/website/docs/reference/mermaid-diagrams.md b/website/docs/reference/mermaid-diagrams.md
new file mode 100644
index 00000000..81eb0dbe
--- /dev/null
+++ b/website/docs/reference/mermaid-diagrams.md
@@ -0,0 +1,199 @@
+# Mermaid Diagram Documentation
+
+This directory contains mermaid diagram source files and a reusable theme for blog post visualizations.
+
+## Overview
+
+- **Source files (.mmd)**: Text-based diagram definitions
+- **Generated files (.png)**: Rendered diagram outputs
+- **Theme (watercolor-theme.json)**: Reusable styling for consistent visual identity
+
+## Theme: Watercolor
+
+The watercolor theme provides warm, cohesive styling that matches the portable context blog hero illustration. It emphasizes readability on light backgrounds with:
+
+- **Color palette**: Warm earth tones (cream #fef9f5, peach #f5e6d3, warm-brown #d4896b)
+- **Typography**: Clear hierarchy via font-weight and font-size variations
+- **Styling**: Rounded corners (12px primary, 10px secondary) and medium strokes (2.5-3px)
+- **Background**: Off-white cream (#fef9f5) for luminous effect
+
+### Color Reference
+
+| Name | Hex | Use |
+|------|-----|-----|
+| cream | #fef9f5 | Main background |
+| peach | #f5e6d3 | Primary nodes |
+| warm-cream | #fdf4e8 | Secondary nodes |
+| light-peach | #f9ead8 | Tertiary nodes (e.g., decision points) |
+| warm-brown | #d4896b | Primary borders, connector lines |
+| muted-brown | #b8836f | Secondary borders |
+| tan | #c9956b | Connector lines |
+| dark-brown | #3d2817 | Text color |
+
+## How to Apply the Theme
+
+### In a New Mermaid Diagram
+
+Copy the `themeVariables` block from `watercolor-theme.json` into your diagram's init block:
+
+```mermaid
+%%{init: {
+ "theme": "base",
+ "themeVariables": {
+ "fontFamily": "Inter, ui-sans-serif, system-ui",
+ "primaryColor": "#f5e6d3",
+ "primaryTextColor": "#3d2817",
+ ...
+ }
+}}%%
+```
+
+Then use standard mermaid syntax for diagram elements.
+
+## File Naming Convention
+
+Blog diagrams follow this naming pattern for version control clarity:
+
+```
+{date}-{blog-slug}-{diagram-name}.mmd
+{date}-{blog-slug}-{diagram-name}.png
+```
+
+**Example:**
+- Source: `2026-07-17-capture-layer-for-portable-context-capture-layer-architecture.mmd`
+- Output: `../media/2026-07-17-capture-layer-for-portable-context/capture-layer-architecture.png`
+
+Media folder: `../media/{date}-{blog-slug}/`
+
+## Regenerating Diagrams
+
+To regenerate PNG files from updated mermaid source:
+
+```bash
+cd website/blog/mermaid
+
+# Regenerate a single diagram
+mmdc -i 2026-07-17-capture-layer-for-portable-context-capture-layer-architecture.mmd \
+ -o ../media/2026-07-17-capture-layer-for-portable-context/capture-layer-architecture.png
+
+# Regenerate all diagrams for a post (example: 2026-07-17)
+for f in 2026-07-17-capture-layer-for-portable-context-*.mmd; do
+ name=$(echo "$f" | sed 's/^2026-07-17-capture-layer-for-portable-context-//' | sed 's/\.mmd$//')
+ mmdc -i "$f" -o "../media/2026-07-17-capture-layer-for-portable-context/${name}.png"
+done
+```
+
+## Customization
+
+### Changing Colors
+
+Edit `watercolor-theme.json` or override `themeVariables` directly in a diagram's init block:
+
+```mermaid
+%%{init: {
+ "themeVariables": {
+ "primaryColor": "#new-hex-color"
+ }
+}}%%
+```
+
+### Node Styling
+
+Apply inline styles for emphasis:
+
+```mermaid
+style nodeId fill:#custom-color,stroke:#border-color,stroke-width:3px,color:#text-color,rx:12,ry:12
+```
+
+Use the `nodeStyles` object in `watercolor-theme.json` as templates:
+- **primary**: Main emphasis (bold, large font)
+- **secondary**: Supporting elements (medium emphasis)
+- **tertiary**: Details (lighter emphasis)
+- **muted**: Reference/background (least emphasis)
+- **subgraph**: Container grouping
+
+### Scope Bounding
+
+Use subgraphs with distinct borders to show implemented vs. future work:
+
+```mermaid
+subgraph done["β
This Post: Feature X"]
+ ...
+end
+
+subgraph future["π Future Posts: Feature Y"]
+ ...
+end
+
+style done fill:#fef9f5,stroke:#d4896b,stroke-width:3px
+style future fill:#fef9f5,stroke:#a0734d,stroke-width:2px,stroke-dasharray:5
+```
+
+- **Solid borders (3px)**: Implemented content (This Post)
+- **Dashed borders (2px)**: Future content (Future Posts)
+
+## Syntax Reference
+
+### Flowchart
+
+```mermaid
+flowchart TD
+ A[Start] --> B{Decision}
+ B -->|Yes| C[Action]
+ B -->|No| D[End]
+```
+
+**Shapes:**
+- `[text]` β rectangle
+- `{text}` β diamond (decision)
+- `(text)` β rounded rectangle
+- `[(text)]` β database/storage
+- `[[text]]` β subroutine
+
+### Subgraph (Grouping)
+
+```mermaid
+subgraph group["Group Label"]
+ A[Item 1]
+ B[Item 2]
+end
+```
+
+### Connections
+
+- `-->` β solid arrow (default)
+- `-->|label|` β arrow with label
+- `-.->` β dotted arrow
+- `-.-` β dotted line
+
+## Best Practices
+
+1. **Keep labels short** (\<50 chars) for readability
+2. **Use consistent emoji conventions** (β
done, π future, β οΈ warning)
+3. **Limit diagram complexity** (5-7 levels deep, \<15 nodes for flowcharts)
+4. **Apply watercolor theme** to all blog diagrams for visual cohesion
+5. **Use node styles consistently** (same semantic use = same style)
+6. **Test on both light and dark backgrounds** (theme designed for light)
+
+## Troubleshooting
+
+**Diagram not rendering:**
+- Check mermaid syntax: `https://mermaid.live/` for live editor
+- Verify themeVariables are valid hex colors
+- Ensure quotes match (`"` or `'`, not mixed)
+
+**Colors look different than expected:**
+- Verify `themeVariables` hex values match desired colors
+- Check inline `style` statements override themeVariables correctly
+- Note: PNG output quality depends on mmdc version β `npm install -g @mermaid-js/mermaid-cli@latest`
+
+**Text not readable:**
+- Increase font-size in themeVariables or node styles
+- Ensure text color (#3d2817) contrasts with background (#fef9f5)
+- Use lighter backgrounds for dark text, darker text for light backgrounds
+
+## Resources
+
+- **Mermaid syntax:** https://mermaid.js.org/
+- **Mermaid CLI:** https://github.com/mermaid-js/mermaid-cli
+- **Watercolor hero illustration:** See blog post images for design reference
diff --git a/website/docs/reference/watercolor-theme.json b/website/docs/reference/watercolor-theme.json
new file mode 100644
index 00000000..291bcc85
--- /dev/null
+++ b/website/docs/reference/watercolor-theme.json
@@ -0,0 +1,85 @@
+{
+ "description": "Watercolor theme for mermaid diagrams - warm earth tones matching portable context blog hero illustration",
+ "colors": {
+ "cream": "#fef9f5",
+ "warm-cream": "#fdf4e8",
+ "peach": "#f5e6d3",
+ "light-peach": "#f9ead8",
+ "warm-brown": "#d4896b",
+ "muted-brown": "#b8836f",
+ "tan": "#c9956b",
+ "dark-brown": "#3d2817",
+ "medium-brown": "#4a3428",
+ "muted-tan": "#a0734d",
+ "reference-brown": "#6b4423"
+ },
+ "themeVariables": {
+ "fontFamily": "Inter, ui-sans-serif, system-ui",
+ "primaryColor": "#f5e6d3",
+ "primaryTextColor": "#3d2817",
+ "primaryBorderColor": "#d4896b",
+ "lineColor": "#c9956b",
+ "secondaryColor": "#fdf4e8",
+ "secondaryTextColor": "#3d2817",
+ "secondaryBorderColor": "#b8836f",
+ "tertiaryColor": "#f9ead8",
+ "tertiaryTextColor": "#4a3428",
+ "tertiaryBorderColor": "#d4896b",
+ "background": "#fef9f5",
+ "mainBkg": "#fef9f5",
+ "secondBkg": "#fef9f5",
+ "tertiaryBkg": "#fef9f5",
+ "textColor": "#3d2817",
+ "fontSize": "16px",
+ "fontFace": "Inter, ui-sans-serif"
+ },
+ "nodeStyles": {
+ "primary": {
+ "fill": "#f5e6d3",
+ "stroke": "#d4896b",
+ "stroke-width": "3px",
+ "color": "#3d2817",
+ "rx": "12",
+ "ry": "12",
+ "font-weight": "700",
+ "font-size": "15px"
+ },
+ "secondary": {
+ "fill": "#fdf4e8",
+ "stroke": "#b8836f",
+ "stroke-width": "3px",
+ "color": "#3d2817",
+ "rx": "12",
+ "ry": "12",
+ "font-weight": "700",
+ "font-size": "15px"
+ },
+ "tertiary": {
+ "fill": "#f9ead8",
+ "stroke": "#d4896b",
+ "stroke-width": "2.5px",
+ "color": "#3d2817",
+ "rx": "10",
+ "ry": "10",
+ "font-weight": "600",
+ "font-size": "14px"
+ },
+ "muted": {
+ "fill": "#fef9f5",
+ "stroke": "#b8836f",
+ "stroke-width": "2.5px",
+ "color": "#4a3428",
+ "rx": "10",
+ "ry": "10",
+ "font-weight": "500",
+ "font-size": "13px"
+ },
+ "subgraph": {
+ "fill": "#fef9f5",
+ "stroke": "#d4896b",
+ "stroke-width": "3px",
+ "color": "#3d2817"
+ }
+ },
+ "usage": "Add to mermaid diagram init block: %%{init: { \"theme\": \"base\", \"themeVariables\": {...} }}%%"
+}