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 +--- + +![A developer writing the day's notes by lamplight, watercolor illustration](./media/2026-07-14-capture-layer-for-portable-context/watercolor-01-capture-hero.png) + +*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. + +![Copilot CLI capture layer architecture](./media/2026-07-14-capture-layer-for-portable-context/capture-layer-architecture.png) + +## 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. + +![Observation to context ratification pipeline](./media/2026-07-14-capture-layer-for-portable-context/observation-pipeline.png) + +## 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. + +![Private-tier boundary for raw Copilot CLI logs](./media/2026-07-14-capture-layer-for-portable-context/private-tier-boundary.png) + +## 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 +--- + +![A developer writing the day's notes by lamplight, watercolor illustration](./media/2026-07-17-capture-layer-for-portable-context/watercolor-01-capture-hero.png) + +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. + +![Copilot CLI capture layer architecture](./media/2026-07-17-capture-layer-for-portable-context/capture-layer-architecture.png) + +## 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. + +![Observation to context ratification pipeline](./media/2026-07-17-capture-layer-for-portable-context/observation-pipeline.png) + +## 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. + +![Private-tier boundary for raw Copilot CLI logs](./media/2026-07-17-capture-layer-for-portable-context/private-tier-boundary.png) + +## 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\": {...} }}%%" +}