An AI that watches a developer's whole arc of work and reflects intent-drift back to them — without ever competing for the cognition that produced it.
This is not a finished study. The document grows alongside the tool itself — it begins as market and problem research (the current phase), and as the work moves into technical spec, prototype, MVP, testing, and real users, each phase's findings are added here rather than replacing what came before. The end state is a complete research record — problem, philosophy, design decisions, what was tried, what testing and real users showed — meant to be publishable, possibly alongside the tool when it ships. Every section below is current best understanding, not settled history.
AI should witness, not compete for cognition.
Cognitive drift is what happens when AI-assisted development lets output outrun understanding — a thousand small, locally-reasonable acceptances that compound into a system nobody actually chose. Every current tool responds by adding another AI to the loop: more specs, more agents, more synchronization. The Observer takes the opposite position. It watches a Claude Code session in VS Code — the spec you gave it, the prompt you wrote, what the AI produced, what you did next — and the moment a seam opens between what you meant and what is happening, it shows a quiet flag. It never writes, suggests, or scores. Its only job is to make sure you still recognize what you are building, and why.
The Observer is not a linter, a spec-sync tool, or a second AI coding assistant. It is something that watches a developer's whole arc of work — the task, the reason for the task, where it is headed, how it touches the current and future state of the system — and periodically reflects that back, so the developer can see whether they have drifted from their own plan.
The one-line version is the right one: AI should witness, not compete for cognition. That is not a feature description. It is a claim about the role AI should play in a workflow, and it is deliberately stated against the grain of the current market.
Cognitive drift, defined precisely: everything related to the current task — why you are doing it, where it leads, how it affects current and future state — and whether what you are doing now has drifted from your original plan as AI assistance accumulates. It is worth separating this into layers, because each is a different engineering problem and a different reason a developer notices something has gone wrong.
| Layer | The drift | How it's noticed today |
|---|---|---|
| Intent decay | You forget why you made a decision six days or six weeks ago | Git-blame archaeology, Slack search, staring at a comment that no longer makes sense |
| Plan drift | The system diverges from your intended architecture, one AI-accepted suggestion at a time | A sudden "wait, why does it look like this now" |
| Task drift | Mid-session, you've solved a different problem than the one you sat down to solve | Realizing at 6pm that the ticket is still open |
| State drift | Your mental model of the codebase no longer matches what's actually in it | A bug that "shouldn't be possible" given what you thought was true |
| Compounding drift | Small, locally-reasonable steps that add up to something nobody would have chosen on purpose | The "how did we end up here" retrospective |
This framing matters because it says the tool is not solving one problem. It is solving the meta-problem: that these small divergences are invisible in the moment and only legible in hindsight — usually someone else's hindsight, in a postmortem.
This space got crowded fast. It is worth knowing exactly how, so as not to rebuild something that already exists — and so the gap becomes visible.
Across the current ecosystem, "drift" almost always means one thing: the AI agent's output no longer matches a written specification.
CLAUDE.md, .cursor rules, etc.) as an organizational asset and runs "automated drift detection, pre-commit enforcement, and cross-file consistency checks" against them.6Notice what these share: the artifact being checked is a document — a spec, a rules file, an API contract. The checker is another AI agent or a static analyzer. The audience is a pipeline, a reviewer, or a team lead. None of them are checking for drift in the developer's own head.
One essay gets unusually close. A Stack Overflow contributor describes "black box AI drift — the gap between what you need in a design and the AI translation of your intent into code," arguing that prompts go in, output comes out, and the decisions made in between are hidden from view — and because of the volume and velocity of AI-assisted generation, those gaps are proliferating faster than we can chase them.4
This is close. But its proposed fix is still tool-side: making the AI's own reasoning visible. The Observer's is developer-side: making the developer's drift from their own intent visible. That is a meaningfully different product, even if it starts from the same anxiety.
Developer-experience research has isolated the three variables this tool is implicitly designed around: feedback loops (the speed at which developers receive information about their work), cognitive load (the mental effort required to complete tasks), and flow state (the state of full immersion and enjoyment).1 Critically, recent longitudinal work notes that how AI coding assistants affect flow state is not yet well understood.2 This is a young, unsettled area — not a solved one.
There is real evidence that AI assistants can be net-negative for flow specifically because of the interruption pattern, not the assistance itself. The same longitudinal study reports a productivity–experience paradox: productivity perceptions held stable (84% reporting improvement at both time points), yet among matched participants the proportion reporting worsened developer experience in at least one dimension nearly doubled, from 14% to 27%, with flow state and cognitive load eroding even as feedback loops improved.2
"Unwanted or poorly timed suggestions, verbose outputs, interface switching, and the need to constantly verify generated code introduce frequent interruptions that break developers' flow continuity."
This is the empirical case for the principle. Every existing AI dev tool adds a participant to the developer's cognition; the evidence says that is exactly what damages flow and inflates cognitive load. A tool whose entire design constraint is "never interrupt, never suggest, only reflect back on request" argues directly against the grain of where the industry is building — and the research suggests the grain is wrong.
Design research on human–AI collaboration keeps circling the same discomfort. In one recent study, designers reacted to a hypothetical AI agent that could act autonomously mid-task; the pushback was consistent — one participant said they would want to stay in control at that stage rather than let the agent "listen and execute," and another insisted the AI should always leave the last decision to the human.9 Tellingly, when it came to a tool meant to hold onto a person's own material — the closest existing analogue to an observer archive — the same participants were unambiguous: such a tool is useful only as long as it is user-controlled, user-generated, and user-owned.9
That is close to the "code ownership vs. allowed ownership" thinking behind this project. It suggests the appetite for a tool that only holds up a mirror — without asserting authorship, suggestion, or authority over the work — is already there, articulated by people who do not yet have a name for what they are asking for.
Checked directly for this framing and adjacent ones. Nothing in the current market — spec tools, IDD platforms, context-ops platforms, observability-for-devex tools — states this as a philosophy. Without exception they position themselves as making the AI more involved, more synchronized, more embedded in the loop. That phrase, and the stance behind it, is open ground as of today.
An earlier draft argued that reflection should be "retrospective and requested, not real-time and pushed," and implied the interface problem was the hardest part of the whole idea. Both were quietly overreaching, and it is worth naming exactly how — because the correction is the product.
The real distinction isn't "during the session" vs. "after the session." It's interruption vs. ambient presence.
A popup, a modal, a sound, a notification badge that demands a reaction now. This is the mechanism the flow-state research condemns — the poorly-timed suggestion that breaks flow continuity.
A small, silent, passive indicator — a dot changes color — sitting in peripheral vision, asking nothing until you choose to look. Closer to a git status light or a linter gutter mark than to a suggestion engine.
A tool can watch and flag during the session without ever interrupting, as long as the signal is visual, small, silent, and entirely optional to check. That is fully consistent with "witness, not participant" — the witness is present in the room the whole time, but never taps you on the shoulder. It speaks only when you turn and ask.
So the design rule is: the tool may change its own indicator state at any moment; it may never demand attention at any moment. To be unambiguous about scope — it only ever changes its own flag state (on/off). It never touches the developer's code, files, or environment, under any circumstance. That is not a feature limitation to justify; it is simply outside what a witness does.
On the "hardest design problem" claim specifically: the original draft framed "how to show drift without a dashboard" as the hardest open problem in the product. That framing was itself an unrequested drift — it had already been settled that there should be no metric, no score, no KPI, only a sentence. Turning that into an open "hard problem" quietly reintroduced the very thing that had been ruled out. The correct standing answer, stated once:
There is no metric. There is no dashboard. There is a sentence.
Something like: "The task was to decrease the margin. You're changing the background color of a neighboring element." Plain, specific, comparing stated intent to observed action, in the developer's own terms — not a number, not a severity score beyond a simple flag color, not a trend line. If a red or yellow indicator appears, clicking it reveals one sentence. That is the entire surface area of the output. (This is refined further in §8 — the default view is now a raw comparison, with the generated sentence available only as a clearly-labeled secondary action.)
What the tool should watch and ingest is genuinely still open. But the research narrows the space of good answers.
The Seam B example (AI adds unrequested tests) matters beyond illustrating the mechanism: it establishes that this tool has no opinion on whether the AI's output was good. Only on whether it was asked for. If a developer's own prompt was flawed, or their next step is a mistake, the tool has nothing to say — that mistake is theirs, made knowingly, by their own hand. The tool's entire value is that when something goes wrong, the developer can trace it back to a decision they actually made, not one that was quietly made for them. Faithfulness to stated intent is the only axis this tool measures. Quality is out of scope, permanently.
The true case (a prompt that traceably connects to a spec fragment) was defined first; the false case needs its own definition, by the same standard used for Seams B and C: "still the same task" is false when the prompt's content has no traceable connection to any part of SPEC.md — for example, a prompt that introduces a new external API, a new tool, or a design-system change nowhere referenced in the spec.
This is deliberately framed as "no traceable connection," not "introduces something new" — because a spec can be incomplete, and a developer introducing something the spec never anticipated may be entirely correct to do so. The tool cannot and should not try to distinguish "developer went off-spec" from "spec didn't anticipate this and the developer is right to deviate." Those two cases get names of their own — new-but-justified vs. new-but-drifted — in §7, where it is decided that the distinction belongs to the developer's judgment, not the tool's logic. The tool only reports the absence of a traceable connection; what that absence means is the developer's call.
It is also worth noting when Seam A drift is likely to occur: rarely on the first prompt of a task (which tends to cleanly reference the spec), and increasingly as a session goes on — new ideas surface mid-development, and a few unrelated asks get folded into an otherwise-correct prompt. Seam A checks may need to matter more, not less, as a session lengthens.
An early next-step check could have passed cleanly — the first prompt referenced the spec perfectly. The drift emerged several edits later, within the same apparent task. Nobody chose "let's rebuild payments today"; it accumulated. This is exactly the case that makes single-point checks insufficient (see the re-checking open question, §8).
SPEC.md, and for how long?A real question surfaced during this research: at what point does a developer have the standing right to explore, trial-and-error, or research something unrelated to the spec, without that being treated as drift? This is not a detection-mechanism question (that is what Seam A answers) — it is a policy/usage question about how strictly any given developer or team wants their own spec to bind them, and whether that strictness can flex mid-session (e.g., an explicit "exploration mode" toggle vs. spec-as-law throughout). The tool should not answer this on the developer's behalf. But the concept doc should eventually state whether v1 assumes spec-as-constant-authority or builds in a declared exploration window. Left open for now — addressed in full in §7.
All worked examples so far describe clean matches or clean mismatches. Real sessions will produce partial matches — a prompt related to the spec but scope-expanded (spec says "component," prompt says "component plus basic validation"). Whether that counts as drift, a soft flag, or no flag at all is not yet decided, and should not be treated as implicitly resolved by the clean-case examples.
A local Observer daemon — a simple CLI tool at heart — listening on HTTP, fed entirely by Claude Code's own hook system (UserPromptSubmit, Stop, PostToolUse). This works identically whether the developer is using the Claude Code CLI directly or its VS Code sidebar — the daemon doesn't care which surface sent the event, and doesn't need to be a VS Code extension to function. An extension, if built later, is a UI layer on top of this daemon, not a requirement for it.
Seam C — AI output vs. the developer's own next step, its original and only definition — cannot be fully closed by the hook system alone; the independent file-watcher needed to close it is explicitly not part of this MVP shape, deferred to a future version (see §4.6 and §4.8). This is a gap in what's observable, not a redefinition of what Seam C means. MVP is a single, well-instrumented environment rather than an attempt to watch "development" in the abstract — and it is now grounded in tested behavior rather than a hoped-for API.
Drift can appear at any seam between these four stages. The tool's job is to sit across all four and notice when a seam has opened.
.md, a .pdf, pasted or typed text, a prompt written directly into the session — any combination. The one and only source of truth for "what was supposed to happen." No inference, no guessing at unstated goals.seam A: prompt ↔ spec · seam B: output ↔ prompt · seam C: next-step ↔ any of the above
An observation point is the location/mechanism that gives the tool access to a piece of content — not a comparison, and not a tool in its own right. Git-diff, for instance, is a tool; the observation point is "the workspace file tree, watched via VS Code's file-watcher API," which git-diff operates inside of.
| Layer | Observation point | Status |
|---|---|---|
Spec / intent (SPEC.md etc.) |
VS Code file read/watch on the workspace file | Standard, low-risk |
| Prompt | UserPromptSubmit hook — Claude Code's own hook system, HTTP transport |
✅ Confirmed directly — §4.6 |
| AI output | Stop hook (last_assistant_message) and PostToolUse (structured diffs) |
✅ Confirmed directly — §4.6 |
| Developer's own manual next step | Independent file-watcher, run by the Observer itself — not anything the hooks expose | Only way to observe it — scoped to a future version |
A seam-check runs in the background continuously, for each seam, whenever that seam's pair of inputs is available. The flag is surfaced only when "still the same task" evaluates to false — a passing check produces no visible signal at all. For Seam C specifically, the triggering event is defined as: every file save, but only when that save contains an actual code change. The tool's attention is anchored to code changing, not to any timer or idle-detection — no code change, no check.
If a seam passes once, it is not yet decided whether it should be re-checked as the developer continues within what is nominally the same task window. This matters most for Seam C: the Stripe example above shows an early check passing cleanly while drift emerges several edits later, within the same apparent task. Whether Seam C needs continuous re-evaluation across a task's full duration, rather than a single point check, is left open for the next phase.
This was the single highest-priority unknown in the entire concept, and it is no longer a risk: it has been tested directly, in four stages of increasing fidelity (CLI baseline, VS Code surface, HTTP transport, fail-open under a stopped listener), against a real project, with every result independently confirmed rather than assumed. The complete test procedure and raw evidence are preserved in §4.7.
The architecture is not "a VS Code extension observing Claude Code." That was the exact ambiguity left unresolved at the start of this research. It is now clear: the Observer can be a simple CLI tool, usable from within VS Code but not dependent on it. Claude Code has its own official hook system — UserPromptSubmit, Stop, PostToolUse — configured per-project in .claude/settings.json, capable of posting each event as HTTP to a local listener. This works identically from the CLI or the VS Code sidebar — the surface doesn't matter; the hook system is the observation point, and it is surface-agnostic. A VS Code extension, if built later, is one possible front-end for the daemon, not a requirement for it to function.
| Seam / layer | Hook | Confirmed payload |
|---|---|---|
| Prompt (Seam A/B input) | UserPromptSubmit | Verbatim prompt string, plus session/prompt IDs — confirmed on both CLI and VS Code |
| AI output (Seam B input) | Stop | last_assistant_message — the AI's final summary text for the turn; field name confirmed exact |
| AI's own edits — a separate signal, not Seam C | PostToolUse | Full diff data, in two distinct payload shapes by tool |
PostToolUse payloads differ by tool: a Write (new file) carries the full body in tool_input.content; an Edit (existing file) carries tool_input.old_string/new_string plus tool_response.originalFile and tool_response.structuredPatch — a pre-computed, line-numbered unified diff. The Observer never needs to compute its own diff for AI-driven edits; Claude Code already hands over a structured one.
PostToolUse and the <ide_opened_file> context tag Claude Code injects are the only two windows into anything the developer does — and both were tested directly against manual developer action (a manually created folder, a manually added file) with zero visibility, across four independent tests, in two different categories of action. The tag is confirmed to be viewport-awareness only ("what file is currently open"), never an action log.
This confirms a real observability gap, not a miscoded definition. Seam C means, and has always meant, AI output vs. the developer's own next step. Nothing the hooks expose can observe that. What the hooks can see is the AI's own edits (via PostToolUse — a genuinely different signal, AI output vs. AI action, useful on its own terms but not a version of Seam C) and the developer's next prompt (a proxy for what happened, not an observation of it). True Seam C would require an independent file-watcher the Observer runs itself, with its own real design questions: what counts as a meaningful manual change, how to avoid noise from routine saves, how to correlate a manual edit back to a task with no prompt to anchor it. That is real work, deliberately scoped to a future version. For MVP, Seam C is not redefined — it is left open, unobservable, and honestly flagged as such, rather than quietly narrowed into something else.
With HTTP hooks genuinely configured and live, the listener process was stopped mid-session and a further prompt was sent. Claude Code continued with no delay, error, or visible degradation. This closes the constitutional requirement stated early in this research — the Observer must never block or interrupt the developer's work, even by malfunctioning — with actual evidence rather than a design intention.
Two further findings worth keeping. A single conversational turn can produce multiple PostToolUse events before the terminating Stop — one turn produced edits across three separate files — so the Observer must treat it as a stream to collect per turn, not a 1:1 event-per-turn assumption. And a 14-minute gap between a prompt and its resulting events, traced to the developer needing to re-authenticate, was ordinary human latency, not a tooling failure; rather than build logic to classify silence, that was explicitly scoped out of v1.
During testing, a malformed settings.json (a leftover comment block, invalid in standard JSON) was silently causing zero hooks to register — an earlier false-negative result was fully explained by this once found. Claude Code was not asked to fix it. It recognized the malformation on its own, unprompted, and — because the file was blocking further work — proactively offered to fix it before anything else could run, flagging that doing so would activate live, HTTP-posting hooks as a real consequence. That is a small, entirely unprompted instance of exactly the kind of noticing this whole tool is trying to build structurally: a system surfacing a consequence before acting, without being asked to.
The next open item is no longer "can this be observed" — it is translating this confirmed hook architecture into the spec, which §4.8 does.
Everything below is the complete evidence behind §4.6, kept in full so this document stays self-contained. N refers to the developer throughout — the person building and testing the Observer. "Fail-open" means: if the observing component breaks or is stopped, the system being observed continues working, unaffected — the Observer's constitution requires this. Stopping the listener is called "stopping," not "killing," deliberately, to match the tool's own philosophy of non-interference.
Why this testing happened: the highest-priority unknown blocking the architecture was whether an external tool could observe an ongoing Claude Code session — N's prompts and Claude Code's outputs — in real time, without controlling or interfering with the session. Rather than assume, it was tested directly.
Test environment: a real working project (magnifica-land), not an empty scaffold — necessary so PostToolUse had real files to act on. Windows, Python 3.11 initially, later 3.14 via PATH adjustment. Two probe implementations in sequence: a file-writing probe for Tests 1a and 1b (command hooks), and an HTTP listener for Test 2 (HTTP hooks).
Purpose: confirm the plumbing works at all, in the simplest possible configuration, before varying anything else.
Setup: at the project root, .claude/hooks/probe.py was created:
#!/usr/bin/env python3
import sys, json, datetime
raw = sys.stdin.read()
try:
event = json.loads(raw)
except json.JSONDecodeError:
event = {"_unparsed": raw}
with open("C:/temp/observer-probe.log", "a") as f:
f.write(f"=== {datetime.datetime.now().isoformat()} ===\n")
f.write(f"event: {event.get('hook_event_name')}\n")
f.write(json.dumps(event, indent=2))
f.write("\n\n")
sys.exit(0) # no stdout, no decision — pure observer
.claude/settings.json registered the probe against three events, using exec form (command + args, not shebang execution, since Windows has no chmod):
{
"hooks": {
"UserPromptSubmit": [
{ "hooks": [{ "type": "command", "command": "python",
"args": ["${CLAUDE_PROJECT_DIR}\\.claude\\hooks\\probe.py"] }] }
],
"Stop": [
{ "hooks": [{ "type": "command", "command": "python",
"args": ["${CLAUDE_PROJECT_DIR}\\.claude\\hooks\\probe.py"] }] }
],
"PostToolUse": [
{ "matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "python",
"args": ["${CLAUDE_PROJECT_DIR}\\.claude\\hooks\\probe.py"] }] }
]
}
}
Result — confirmed green: UserPromptSubmit fired, carrying the verbatim prompt string. PostToolUse fired on Write (file didn't exist yet), carrying tool_input.content — full file body. Stop fired, carrying last_assistant_message. A follow-up run in the same session, editing the now-existing README, confirmed the second payload shape: tool Edit, with old_string/new_string, plus originalFile and structuredPatch.
Findings: Write and Edit produce structurally different payloads. The 14-minute gap noted in §4.6 was observed here, traced to N needing to re-authenticate — human latency, not a tooling failure — leading directly to the v1 scope decision not to classify silence in the event stream.
Purpose: determine whether the VS Code-integrated surface fires the same hooks as the CLI, since each surface maintains separate session history.
Procedure: log cleared, project opened in VS Code, a prompt sent from the Claude Code sidebar specifically — not the integrated terminal, which would only re-test the CLI.
Result — confirmed green: all three events fired, identical structure to the CLI run. Notably, the UserPromptSubmit payload included an auto-injected tag:
<ide_opened_file>The user opened the file
c:\Development\magnifica-land\.gitignore in the IDE.
This may or may not be related to the current task.</ide_opened_file>
Add a .vscode folder to gitignore.
Follow-up — four targeted tests to find the boundary of this behavior:
.gitignore opened in the editor, then prompted → tag appeared, referencing the opened file.README.md opened, then prompted a change → tag appeared again, referencing that file.videos/ folder manually created (no Claude Code involvement), then prompted → no tag, no reference to the manual folder creation anywhere in the prompt context.Conclusion, confirmed by four consistent results across two categories of action: the <ide_opened_file> injection is viewport/attention awareness only, not an action log. It never surfaces manual file creation or manual edits, under any tested condition — this is the direct evidence behind the Seam C boundary in §4.6.
Purpose: confirm the transport the real Observer daemon actually uses (a localhost HTTP listener, reachable regardless of surface), and confirm the fail-open guarantee under a stopped listener.
Setup: observer_listener.py, standard library only, placed outside the project folder:
#!/usr/bin/env python3
import json
from http.server import BaseHTTPRequestHandler, HTTPServer
class ObserverHandler(BaseHTTPRequestHandler):
def do_POST(self):
length = int(self.headers.get("Content-Length", 0))
raw = self.rfile.read(length).decode("utf-8")
try:
event = json.loads(raw)
except json.JSONDecodeError:
event = {}
name = event.get("hook_event_name")
print("\n" + "─" * 32)
print(f"EVENT: {name}")
print(f"session: {event.get('session_id')} | "
f"prompt_id: {event.get('prompt_id')}")
if name == "UserPromptSubmit":
print(f"SEAM A — prompt: {event.get('prompt')!r}")
elif name == "Stop":
msg = event.get("last_assistant_message")
print(f"SEAM B — last_assistant_message: {str(msg)[:400]!r}")
elif name == "PostToolUse":
ti = event.get("tool_input", {})
print(f"tool: {event.get('tool_name')}")
print(f" file: {ti.get('file_path')}")
print(f" old: {str(ti.get('old_string'))[:200]!r}")
print(f" new: {str(ti.get('new_string'))[:200]!r}")
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(b"{}")
def log_message(self, *a):
pass
if __name__ == "__main__":
print("Observer listening on http://localhost:7777")
HTTPServer(("localhost", 7777), ObserverHandler).serve_forever()
.claude/settings.json was swapped from command to http hooks, each posting to http://localhost:7777/event.
Complication encountered and resolved: the settings file initially contained a leftover //-commented block, invalid in standard JSON, causing Claude Code to report the file as malformed and silently register no hooks at all — this fully explained an earlier false-negative result, initially misread as "HTTP hooks don't fire from VS Code." (This is the incident described in §4.6.)
Result — confirmed green, full seam coverage, multiple turns:
UserPromptSubmit → PostToolUse (Edit, README.md) → Stop, all printed correctly, over HTTP, from the VS Code surface.UserPromptSubmit → three separate PostToolUse events (index.html, styles.css, README.md) → one Stop. Confirmed the stream-not-pairing finding.videos/ between turns again produced no event and no context injection — a third independent confirmation of the Seam C boundary.Fail-open check: with the listener running and hooks live, the listener process was stopped (not killed abruptly). A further prompt was sent from the VS Code extension. Claude Code continued normally, with no delay, error, or visible degradation — confirmed directly, with the listener genuinely stopped mid-session and hooks genuinely live via HTTP at the time.
| Item | Status | Evidence |
|---|---|---|
| Surface map (CLI vs VS Code) | ✅ Confirmed identical | Tests 1a, 1b |
Stop field name | ✅ Confirmed, exact match | 1a, 1b, 2 |
| Event ordering | ✅ Confirmed consistent | All tests |
| Payload shapes (Write vs Edit) | ✅ Confirmed, both observed | Test 1a |
| Multiple PostToolUse per turn | ✅ Confirmed | Test 2, turn 2 |
| Seam C (developer's own actions) | ✅ Confirmed absent, 4 independent tests | Tests 1b, 2 |
| HTTP parity with command hooks | ✅ Confirmed | Test 2 |
| Fail-open under a stopped listener | ✅ Confirmed directly, live HTTP hooks | Test 2 |
| Gap/silence classification | ⏸ Explicitly deferred, not attempted in v1 | Test 1a finding |
All items originally scoped for v0 are closed with direct evidence.
This is the actual v1 specification, written directly against the test evidence in §4.7. Per the standing policy in §7, SPEC.md is a constant source of truth for v1, with no exploration-mode toggle — it governs what gets built, not a draft to renegotiate mid-build. Every mechanism claim below traces to a specific confirmed test; no claim rests on documentation alone where a direct test was possible. This is the tool's own principle — faithfulness to what was witnessed, not to what was assumed — applied to its own construction.
A local daemon that receives Claude Code hook events in real time, compares observed AI action against the developer's stated intent, and holds an indicator state reflecting whether that intent was faithfully followed. It does not touch code, files, or the development environment. It does not block, delay, or interrupt Claude Code under any circumstance, including its own failure.
The Observer is not a VS Code extension — that framing is fully retired as of this spec. An extension, if built later, is one optional display surface for the daemon's state, not the mechanism of observation.
Claude Code (any surface: CLI or VS Code)
│
│ HTTP POST — UserPromptSubmit, PostToolUse, Stop
▼
Observer daemon (local HTTP listener, Python stdlib or equivalent)
│
├─ holds per-session task-loop state
├─ runs seam comparisons against SPEC.md-defined intent
└─ updates its own indicator state only
│
▼
Display surface (optional, pluggable)
status bar item · VS Code panel · CLI statusline
Transport is HTTP hooks, confirmed by direct testing to carry full payload parity with command hooks from both CLI and VS Code — this is the v1 product mechanism, not the command-hook file writes used during the test phase itself. The fail-open property is a hard requirement, not an aspiration: any code path that could cause Claude Code to wait on the Observer is a defect, not a limitation to be tolerated.
Only three events are used — deliberately, since the hook system exposes over 20, and the Observer uses the minimum necessary to cover its seams.
Carries prompt, the literal string submitted, verbatim. On the VS Code surface a known contamination was confirmed directly: when a file is open in the editor at prompt time, Claude Code prepends an <ide_opened_file> tag to the prompt string itself, with no structural separator — viewport awareness only, never an action record. v1 requirement: the Observer strips <ide_opened_file> tags before treating the remainder as intent. Required only on the VS Code surface; the CLI's prompt field is clean as received.
Payload shape branches by tool: Write carries the full body in tool_input.content — no prior state to diff against, inherent to creation, not a gap; Edit carries old/new strings plus originalFile and structuredPatch. v1 requirements: consume structuredPatch directly rather than reconstructing a diff — Claude Code has already computed it, and re-deriving it is unjustified duplicate work — and treat PostToolUse as a stream of zero-or-more events per turn, not a 1:1 pairing with Stop.
A precision corrected from earlier drafts of this spec: PostToolUse gives visibility into AI output → AI action — what Claude Code itself did — a genuinely useful signal, but it is not Seam C and was never meant to be. Seam C's definition does not change: it is, and has only ever been, AI output vs. the developer's own next step. PostToolUse does not observe the developer at all, and nothing here should be read as redefining Seam C to mean something the hooks happen to expose.
Carries last_assistant_message, the complete final AI response text for the turn — field name confirmed exact, repeatedly, across every test run. Stop is used deliberately rather than MessageDisplay, which streams in fragments and has no bearing on the stored transcript; Stop gives the complete, final text once, which is what a seam comparison actually needs.
Event ordering, confirmed reliable across every tested condition:
UserPromptSubmit → PostToolUse (zero or more) → Stop
Held without exception across CLI, VS Code, command hooks, and HTTP hooks, in single- and multi-edit turns.
transcript_path, present on every hook payload, points to Claude Code's own JSONL session log — a format Anthropic's documentation warns is internal and subject to change between versions. v1 requirement: the Observer never reads transcript_path during normal operation. Its only sanctioned use is backfill — recovering seams from before the daemon was running, or after a crash.
Confirmed absent under four independent test conditions: manually opening a file (viewport context only), manually creating a folder, and manually adding a file — none produced any hook event or any trace in later prompt context. This is a structural limit of Claude Code's hook system, not a defect in the Observer's design — no hook fires for anything the developer does that isn't mediated through Claude Code itself.
Build a genuinely separate VS Code-side observation layer (workspace.onDidChangeTextDocument, file-save watchers), feeding the daemon a second, VS Code-specific event stream.
Treat the developer's next prompt as an accepted, imperfect proxy for whatever happened in between — the gap collapses into the next loop's Seam A rather than being independently modeled.
This spec does not resolve this choice. It is the one open architectural decision remaining before v1 scope is fully closed, and the build should not proceed against an assumed answer.
The tool watches continuously through the session, not only at the end. What it must never do is interrupt. The mechanism that satisfies both is ambient: a single, small flag indicator that appears somewhere in the editor chrome the moment a seam opens, changes nothing else, makes no sound, opens no modal — and reveals its content only when the developer clicks it. The tool does not grade severity or decide how much a given drift matters; it only notices that a seam opened. Whether it is a big deal or a shrug is the developer's judgment to make. Ignoring the flag costs nothing. That is the whole interaction model.
Everything in §§1–6 is about mechanism. This section is different in kind — it is about how strictly the tool should be trusted to mean what it reports. A policy question that surfaced directly while working through Seam A's false case, kept distinct from mechanics so the two don't get tangled.
At what point does a developer have the standing right to explore, trial-and-error, or research something unrelated to the spec — without the tool treating that as drift? And can that standard change mid-session, or must it be fixed for the whole task?
This is not a detection problem. Seam A already does its one job correctly when it reports "no traceable connection between this prompt and SPEC.md." The policy question is what happens after that report — how much weight the developer, or a team, chooses to give it.
A seam-check can tell you a prompt has no traceable connection to the spec. It cannot tell you why — because "why" splits into two cases that look identical from the outside:
The spec was incomplete or didn't anticipate this, and the developer is right to go beyond it.
The developer has quietly wandered off the task, one reasonable-seeming step at a time, without meaning to.
Both produce the exact same raw signal: no connection found. The tool has no way to see the developer's reasoning, only their actions and their stated intent — and stated intent, by definition, doesn't cover things the developer hasn't yet said. Trying to make the tool guess which case it is looking at would mean giving it an opinion on the developer's judgment, which is precisely the thing "witness, not participant" rules out. The moment the tool starts deciding whether your reason is good enough, it has stopped witnessing and started managing you.
So this isn't a gap to close later with a smarter model. It is a permanent boundary, for the same reason the tool never grades quality: the tool's only claim is "no connection found." Whether that is a problem is always, unconditionally, the developer's call.
The distinction is genuinely useful — just not as something the tool computes. It belongs one layer up, as language the developer uses once they are looking at a raw comparison. A few ways it could show up in v1, in order of how soon they are realistic:
SPEC.md is treated as the constant source of truth for the duration of a task, with no built-in "exploration mode" toggle in v1. A seam-check flags any prompt or action with no traceable connection to it, every time, without exception-handling for intent. The developer decides, per flag, whether that is new-but-justified or new-but-drifted — and the documentation should actively teach that vocabulary rather than pretend the tool can make the call. If a more flexible model (an explicit toggle, a declared research window) turns out to be needed once there is real usage, that is a v2 conversation, not a v1 blocker.
.md, .pdf, pasted text, or a prompt typed into the session, in any combination. Nothing more elaborate for v1.UserPromptSubmit, Stop, PostToolUse), posting HTTP to a local Observer daemon, gives real, tested access to the prompt and the AI's output, regardless of surface. Full evidence in §4.6–§4.7. This was the top-priority unknown and it is closed.PostToolUse exposes a genuinely different, separate signal (AI output → AI action) — useful on its own terms but not a version of Seam C and not a redefinition of it.<ide_opened_file> stripping requirement, consuming structuredPatch directly rather than re-deriving diffs, using Stop rather than MessageDisplay, and treating PostToolUse as a stream rather than a 1:1 pairing are settled implementation requirements, not open design questions.SPEC.md, and can that change mid-session? Addressed in §7. Standing answer: constant authority for v1, no exploration-mode toggle yet. Worth revisiting once there is real usage.Cognitive drift is what happens when AI-assisted development lets output outrun understanding — a thousand small, locally-reasonable acceptances that compound into a system nobody actually chose. Every current tool responds by adding another AI to the loop: more specs, more agents, more synchronization. This tool takes the opposite position. It watches a Claude Code session in VS Code — the spec you gave it, the prompt you wrote, what the AI produced, what you did next — and the moment a seam opens between what you meant and what's happening, it shows a quiet flag. Click it, and it tells you in one sentence, not a score: this is where you drifted. Ignore it, and it costs you nothing. It never writes, suggests, or scores. Its only job is to make sure you still recognize what you're building, and why.
AI should witness, not compete for cognition.
Sources behind the load-bearing claims above, verified against primary material where possible (mid-2026). Figures are attributed to their source and dated; where the evidence is thin or a source could not be independently re-verified in this research pass, that is noted rather than smoothed over.
Note on the "SmartBear / Swagger" mention (§3.1): stated from general knowledge of the API-spec tooling market and not tied to a specific dated source in this pass.