Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
---
status: accepted
---

# 0019 · An embedded agent that reads freely and writes through the gate

## Context

`adr:0003-mcp-as-the-only-bridge-to-the-llm` decided that **the application does not
call an LLM**. `adr:0013-the-project-directory-is-the-unit` superseded that record but
kept three of its clauses, calling them the substance of it: the application does not
call an LLM, the agent writes the pages, and write-time validation is what replaces the
writer.

The argument for the first clause was competition, and it was a good argument: the user
already pays for an agent, already configured it, already trusts it. A second writing
engine inside the application is duplicated work that delivers less.

**What changed is who installs the application.** 0003 was written for a user who
already had a harness open — for them the argument still holds completely, and this
record does not touch it. But the product now ships a signed installer, and somebody who
downloads it and has no harness has nothing: a window that scaffolds a project, validates
what is written into it, records every write, and cannot write a single page. The desktop
application says so on screen in as many words — *there is no model behind this window* —
which is honest and is also a description of a dead end.

The user with a harness is served, and `plans/harness-portability.md` is about serving
them better — `ow init` taking harnesses plural, so a project reaches whoever clones it
whichever one they use. That plan is written and not yet built; either way it is about
somebody who already has an agent. The user with none is the whole of what is left, and
nothing in this repository is addressed to them.

**And the reason the methodology works turns out to be a constraint on the answer.** The
LLM-Wiki convention works in Claude Code because the agent can explore: grep for a term
before coining a second name for it, glob the tree to see how pages are organised, read a
neighbouring page before writing one beside it.

The project already answers part of that. `ow search` is a lexical scan over every page's
title and body, and `ow graph` walks the structure. But `runSearch` returns
`{ slug, title, matches }` — **which pages mention a term and how many times, not the
passage**. `adr:0010-a-derived-index-engine-behind-a-cli` described "lexical hits with
page or source, passage, anchor"; what was built is narrower, and the difference decides
this. An agent told "three pages mention *cutover*" must read all three in full to learn
how the term is used there; `grep` hands it the line. For the cheap models this door
exists for, that is the context window.

So the gap is not that content search is missing. It is that one query returning counts
is not a discovery loop, and discovering that a concept already has a name is the exact
failure `docs/glossary.md` exists to prevent.

## Decision

**The application may run an embedded agent. It reads the project the way a harness
does, and it writes only through the path the editor writes through.**

Three parts, and the split between the first two is the whole record.

**Reading is unrestricted within the project.** The agent gets the harness set — list,
glob, grep, read — over the project directory, every path confined with `assertWithin`
the way `packages/mcp` already confines its own. This is a reversal of nothing: reads
Comment on lines +58 to +60

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Document and enforce the LLM data-egress policy.

The embedded agent can read every path inside the project. The Groq credential also serves agent requests. When file content enters a prompt, that content leaves the project and reaches Groq.

Define consent, sensitive-file handling, redaction, retention, and provider failure behavior. assertWithin limits path traversal, but it does not limit disclosure to the LLM provider.

Also applies to: 138-143

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@docs/adr/0019-an-embedded-agent-that-reads-freely-and-writes-through-the-gate.md`
around lines 58 - 60, The ADR currently describes unrestricted project reads
without defining the resulting data-egress controls. Update the embedded-agent
policy section to specify user consent, sensitive-file handling, redaction,
retention, and behavior when the LLM provider fails, while preserving
assertWithin as the path-traversal boundary and explicitly distinguishing it
from provider disclosure control.

were never what the guarantees rested on.

**MCP's read-only rule does not transfer, and is not the reason for the next paragraph.**
That rule exists because one resident process serves *many* projects to a caller it does
not know, so the blast radius is every wiki on the machine and read-only has to be what
the process *can* do. The embedded agent is scoped to the project this window opened, in
this process, started by the person who clicked. Neither half of that reasoning applies.

**What does apply is the store's own invariant: nothing enters `wiki/` unvalidated.**
That is not distrust of the agent — the human typing in the editor goes through the same
door, and so does every hook. Frontmatter against the schema, wikilinks that resolve,
citations that point at a source and an instant that exist, the write atomic, the
operation logged with its origin and undoable. So the agent creates, edits, renames and
deletes pages through tools that do those things, and **no tool writes into `wiki/`
without passing through them**. Nothing is taken from the agent by this: `write_file` and
`writePage` are the same act, and only one of them is recoverable.

Outside `wiki/` the rule does not apply. A scratchpad — in memory, or under the
application's own temp — is where a proposal lives before anyone has approved it, and
that is a place the agent may write freely.
Comment on lines +69 to +80

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 5 --glob '!docs/**' \
  'assertWithin|writePage|write_file|edit_file|execute|wiki/' .

Repository: protonspy/open-wiki

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- relevant source files ---'
fd -t f . packages/access/src packages/cli/src packages/cli/tests | sort | \
  rg '(paths|atomic-write|write|gate|hooks|commands/(edit|page|init)|main)\.(ts|tsx)$'

printf '%s\n' '--- hook implementation ---'
cat -n packages/cli/src/hooks.ts | sed -n '100,360p'

printf '%s\n' '--- gate command and dispatch references ---'
rg -n -C 8 'runPreToolUse|preWrite|writePage|atomicWrite|recordWrite|write_file|edit_file|execute|Bash|MultiEdit|Write|Edit' \
  packages/cli/src packages/cli/tests plugins apps/desktop/src

printf '%s\n' '--- path and write implementations ---'
cat -n packages/access/src/paths.ts
cat -n packages/access/src/write/atomic-write.ts
cat -n packages/access/src/write/record.ts 2>/dev/null || true
cat -n packages/access/src/write/log.ts

Repository: protonspy/open-wiki

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- ADR contexts ---'
cat -n docs/adr/0019-an-embedded-agent-that-reads-freely-and-writes-through-the-gate.md | sed -n '55,95p'
cat -n docs/adr/0019-an-embedded-agent-that-reads-freely-and-writes-through-the-gate.md | sed -n '124,145p'

printf '%s\n' '--- direct filesystem write calls outside tests ---'
rg -n --glob '*.ts' --glob '*.tsx' --glob '!**/*.spec.ts' --glob '!**/*.test.ts' \
  '\b(writeFileSync|appendFileSync|renameSync|copyFileSync|rmSync|mkdirSync|createWriteStream|writeFile|appendFile|rename|copyFile|rm|mkdir)\s*\(' \
  packages/access/src packages/cli/src apps/desktop/src plugins

printf '%s\n' '--- configured agent tools and hook matchers ---'
cat -n plugins/open-wiki/hooks/hooks.json
rg -n -C 10 'HOOK_MATCHERS|PreToolUse|PostToolUse|write_file|edit_file|execute|conformance|directly' \
  packages/cli/src packages/cli/tests plugins docs/adr

Repository: protonspy/open-wiki

Length of output: 50377


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

# Read-only structural inventory of filesystem writes and hook decisions.
roots = [Path("packages/access/src"), Path("packages/cli/src"), Path("apps/desktop/src")]
write_re = re.compile(r'\b(?:writeFileSync|appendFileSync|renameSync|copyFileSync|rmSync|mkdirSync|createWriteStream)\s*\(')
for root in roots:
    for p in sorted(root.rglob("*.ts")):
        text = p.read_text()
        hits = [(i, line.strip()) for i, line in enumerate(text.splitlines(), 1) if write_re.search(line)]
        if hits:
            print(f"{p}:")
            for i, line in hits:
                print(f"  {i}: {line}")
PY

Repository: protonspy/open-wiki

Length of output: 3872


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- narrowed hook and gate files ---'
wc -l packages/cli/src/hooks.ts packages/cli/src/commands/gate.ts packages/cli/src/main.ts packages/access/src/paths.ts packages/access/src/write/atomic-write.ts
cat -n packages/cli/src/hooks.ts | sed -n '1,280p'
cat -n packages/cli/src/commands/gate.ts | sed -n '1,240p'
cat -n packages/access/src/paths.ts | sed -n '1,220p'
cat -n packages/access/src/write/atomic-write.ts | sed -n '1,150p'

printf '%s\n' '--- relevant tests only ---'
rg -n -C 6 'outside|project|\.claude|CLAUDE|README|notes|raw/|wiki/|Bash|Write|Edit|MultiEdit|execute|deny|allow' \
  packages/cli/tests/hooks.spec.ts packages/cli/tests/gate.spec.ts packages/access/tests apps/desktop/tests 2>/dev/null || true

printf '%s\n' '--- ADR references and conformance wording ---'
rg -n -C 6 'conformance|writes directly|write_file|edit_file|execute|scratchpad|outside `wiki/`|application.*temp|project path|filesystem' docs/adr/0019-an-embedded-agent-that-reads-freely-and-writes-through-the-gate.md docs/adr

Repository: protonspy/open-wiki

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- exact boundary logic ---'
sed -n '1,260p' packages/cli/src/hooks.ts
sed -n '1,220p' packages/cli/src/commands/gate.ts
sed -n '1,180p' packages/access/src/paths.ts

printf '%s\n' '--- configured hook tools ---'
cat plugins/open-wiki/hooks/hooks.json
rg -n -C 8 'does not flag a write outside|outside the wiki|\.claude/settings|preWrite\(|detectShellWrite\(|Write|Edit|MultiEdit|Bash' packages/cli/tests/hooks.spec.ts packages/cli/tests/release.spec.ts

printf '%s\n' '--- ADR target passages ---'
sed -n '64,92p' docs/adr/0019-an-embedded-agent-that-reads-freely-and-writes-through-the-gate.md
sed -n '128,140p' docs/adr/0019-an-embedded-agent-that-reads-freely-and-writes-through-the-gate.md

Repository: protonspy/open-wiki

Length of output: 35516


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
for p in [
    Path("packages/cli/src/hooks.ts"),
    Path("packages/cli/tests/hooks.spec.ts"),
    Path("plugins/open-wiki/hooks/hooks.json"),
]:
    print(f"--- {p} ---")
    lines = p.read_text().splitlines()
    for n, line in enumerate(lines, 1):
        if any(term in line for term in (
            "function preWrite", "function runPreToolUse", "detectShellWrite",
            "isProtected", "wiki/", "notes/", "README.md", "permissionDecision",
            "Write", "Edit", "MultiEdit", "Bash", "matcher", "hook",
        )):
            lo, hi = max(1, n-2), min(len(lines), n+3)
            for i in range(lo, hi+1):
                print(f"{i}: {lines[i-1]}")
            print()
PY

Repository: protonspy/open-wiki

Length of output: 29933


Enforce a deny-by-default write boundary for agent tools.

packages/cli/src/hooks.ts allows non-wiki Write/Edit paths, only detects selected Bash writes to wiki/, and returns without handling configured MultiEdit. Restrict agent writes to validated page operations and application-owned scratch storage. Deny all other project paths and direct filesystem writes. Add coverage in packages/cli/tests/hooks.spec.ts and packages/access/tests/gate-decision.spec.ts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@docs/adr/0019-an-embedded-agent-that-reads-freely-and-writes-through-the-gate.md`
around lines 69 - 80, The agent-tool write boundary must be deny-by-default:
update the hook handling around Write, Edit, MultiEdit, and Bash so only
validated wiki page operations and application-owned scratch storage are
permitted, while all other project paths and direct filesystem writes are
denied. Ensure configured MultiEdit is explicitly handled, then add coverage in
the existing hooks and gate-decision test suites for allowed scratch/page
operations and denied non-wiki or direct-write attempts.


`adr:0003` closed by naming the shape that would preserve its decision if this day came:
"an embedded agent speaking the same MCP tools — **not a second writer with direct disk
access**". MCP is no longer the bridge, so the first half is now "the same tools the
external agent gets"; the second half is untouched and is the load-bearing half of this
record. An agent toolkit's filesystem surface — `write_file`, `edit_file`, `execute` —
is exactly what it excludes.

That is a restriction the toolkits support rather than resist, and saying otherwise
would be the easy overstatement here: `deepagents@1.12.1` defaults its filesystem to an
in-memory backend, hides `execute` unless the backend can execute at all, and takes an
allowlist whose own worked example is `["read_file", "ls", "glob", "grep"]`. **What is
not the default is the guarding.** Its path permissions are permissive when no rule
matches, and confinement to a root directory is opt-in. So the constraint costs a
configuration, and holding it means proving the refusal rather than reading the option
back.
Comment on lines +89 to +96

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 6 \
  'deepagents|assertWithin|read_file|write_file|edit_file|execute|writePage|atomic|undo|allowlist|permission' .

Repository: protonspy/open-wiki

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- ADR 0019 ---'
sed -n '1,190p' docs/adr/0019-an-embedded-agent-that-reads-freely-and-writes-through-the-gate.md

printf '%s\n' '--- candidate agent/config files ---'
git ls-files | rg -i 'deepagents|agent|mcp|claude|access|write|gate|package\.json|lock' | head -200

printf '%s\n' '--- focused access symbols ---'
rg -n -C 5 \
  'export .*assertWithin|function assertWithin|gateWrite|writePage|atomicWrite|read_file|write_file|edit_file|execute|allowlist|permission|deepagents' \
  packages/access packages/cli apps docs/adr/0019-an-embedded-agent-that-reads-freely-and-writes-through-the-gate.md \
  --glob '!**/node_modules/**' --glob '!**/dist/**' | head -500

Repository: protonspy/open-wiki

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- deepagents references and dependency declarations ---'
rg -n -i 'deepagents|langchain|filesystem|backend|allowlist|assertWithin|embedded agent|embedded-agent' \
  --glob '!**/node_modules/**' --glob '!**/dist/**' . | head -400

printf '%s\n' '--- package manifests ---'
for f in package.json packages/access/package.json packages/cli/package.json packages/mcp/package.json apps/desktop/package.json; do
  if test -f "$f"; then
    echo "### $f"
    cat "$f"
  fi
done

printf '%s\n' '--- relevant gate and path implementation ---'
sed -n '1,180p' packages/access/src/gate/gate.ts
sed -n '1,130p' packages/access/src/paths.ts
sed -n '1,240p' packages/access/tests/gate-guard.spec.ts

printf '%s\n' '--- relevant application-tool tests ---'
sed -n '1,230p' packages/cli/tests/e2e.spec.ts
sed -n '1,180p' packages/cli/tests/gate.spec.ts

Repository: protonspy/open-wiki

Length of output: 49117


🌐 Web query:

deepagents 1.12.1 filesystem backend allowlist path permissions assertWithin execute read_file ls glob grep

💡 Result:

In deepagents 1.12.1, the FilesystemMiddleware supports a tools allowlist to explicitly control which built-in filesystem tools are exposed to an agent [1][2]. Filesystem Tool Allowlist You can restrict an agent's filesystem capabilities by passing a tools set to the FilesystemMiddleware constructor [1][2]. The allowlist governs the eight built-in tool names: ls, read_file, write_file, edit_file, delete, glob, grep, and execute [1][2]. Note that read_file is mandatory and must be included in any provided allowlist; omitting it will raise a ValueError [3][2]. Any tool name not in this list, or any custom tool added via the agent's primary tools argument, remains unaffected [1][3]. If you use a backend that does not support the execute tool, including execute in the allowlist is a no-op [2]. Filesystem Permissions and assertWithin Filesystem permissions provide path-based access control (e.g., allow/deny operations on specific paths) for the built-in filesystem tools [4][3]. These are defined using a list of FilesystemPermission rules passed to the agent during creation [4][3]. While there is no direct public method named assertWithin in the filesystem API, the system enforces path restrictions primarily through virtual mode [5][6] and FilesystemPermission middleware [4][7]. Key Security Considerations: 1. Permissions vs. Execution: Filesystem permissions do not apply to sandbox backends that support arbitrary shell command execution via the execute tool [4][3]. Attempting to use path-based permissions with a backend that supports execution will raise a NotImplementedError, unless all paths are scoped under known route prefixes [7]. 2. Virtual Mode: Using virtual_mode=True in your FilesystemBackend is recommended to anchor operations to a specific root directory and prevent path traversal attacks (e.g.,../, ~) [5][6]. 3. Middleware Override: If you replace the default FilesystemMiddleware, you must manually pass the backend and permissions to your custom instance, as they will not be automatically inherited from the parent create_deep_agent configuration [8][9].

Citations:


Define and test the embedded-agent boundary before implementation.

deepagents@1.12.1 limits built-in tools only; custom application tools are unaffected. Specify read-only built-ins (read_file, ls, glob, grep), root confinement, no write_file, edit_file, delete, or execute, and fail-closed unmatched permissions. Enforce assertWithin in application tools. Route page writes through gateWrite and writePage. Add integration tests for valid page and temp writes, and for denied direct filesystem, configuration, traversal, and execute attempts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@docs/adr/0019-an-embedded-agent-that-reads-freely-and-writes-through-the-gate.md`
around lines 89 - 96, Update the ADR to define the embedded-agent security
boundary before implementation: limit built-in tools to read_file, ls, glob, and
grep; configure root confinement and fail-closed unmatched permissions; exclude
write_file, edit_file, delete, and execute; require application tools to enforce
assertWithin; and route page writes through gateWrite and writePage. Specify
integration coverage for valid page/temp writes and denied direct filesystem,
configuration, traversal, and execute attempts.


**The convention is carried in, never re-authored.** The agent's instructions are the
generated `CLAUDE.md` — or the entry file whichever harness the project was scaffolded
for reads — and the scaffolded skills, unchanged. One convention, two consumers. A system
prompt written by hand beside them would recreate 0003's two-authors problem inside one
product, where it would be harder to see: two agents writing the same folder by two
conventions, both passing every check.

`scaffoldSkills` already writes `.claude/skills/<name>/SKILL.md` with `name` and
`description` in the frontmatter, which is the layout the agent toolkits converged on, so
this is a path to point at rather than a format to convert. That is luck rather than
foresight — `adr:0015-the-convention-ships-as-skills` chose it to be read by Claude Code —
and it is worth naming as luck, because the day the two shapes diverge nothing will fail
loudly.

**Neither the skills nor `CLAUDE.md` name a search tool, and both assume one.** They
instruct the agent to use the project's own term, one page per concept, aliases for the
names to avoid. Obeying that requires finding out which terms are already in use. The
methodology assumed a harness that could look before it wrote, without ever saying so —
which is why this record has to say so.

**This narrows `adr:0013` rather than superseding it.** One of its three surviving
clauses falls — the application may now call an LLM. The other two stand, and the second
stands harder than before: write-time validation was the thing that replaced the writer,
and it is now the only thing standing between a cheap model and the wiki.

**The embedded agent is the lesser door and the product says so.** It exists for the
user who has no harness. It is not positioned as equivalent to Claude Code or Codex, and
where the two disagree the external agent is the one the product was designed around.

Rejected: **a second writer with direct disk access**, which would delete the guarantee
the product sells for an ergonomic saving of nothing. Rejected: **giving the agent only
the derived index**, which is safe, cheap, and produces an agent that cannot tell whether
a concept already has a name.

## Consequences

**A well-formed and wrong page passes.** 0003 wrote this about the external agent and it
is sharper here, because the models this door is for are the cheap ones. The gate holds
form and cannot hold meaning: a page with three concepts in it, or the non-canonical
term, or a superseded decision quietly overwritten, passes every check the product has.
A wiki can now be filled with plausible material that validates, and the trust that
material destroys is the only thing the product sells. This is the cost of the record and
it is not mitigated by anything in it.

The mitigation is elsewhere and belongs in the plan, not here: distillation proposes and
the user approves, conformance work writes directly, and every write carries its origin
so a bad run is one undo rather than an archaeology.

**The line has to be proved, not configured.** The read/write split is enforceable with
what exists — the toolkit checked while writing this restricts its filesystem tools to an
allowlist, and a tool the application supplies is untouched by that restriction, so the
gate stays the only way in. But two of the guards fail open: a permission model that
allows when no rule matches, and path confinement that is opt-in per backend. A
configuration that looks right and a configuration that refuses are different claims, and
only the second one is testable. Every constraint this record makes needs a test that
attempts the write and watches it fail, in the same spirit as the gate's own tests.

**A second credential purpose.** `adr:0007-plaintext-credentials-in-the-config` was
walked back to one secret and `adr:0013` made a point of it. The Groq credential now has
two uses, transcription and the agent, which means revoking it breaks two things and the
settings screen has to say so. A project on whisper.cpp has no credential and therefore
no embedded agent — which the settings screen also has to say, before the user finds out
by opening a chat that cannot answer.

**Model choice becomes a product decision.** The provider's model list is not a menu the
user has the information to choose from: most entries are bad at tool calling and none of
them says so. Offering the raw list is handing over a decision and then inheriting the
blame for the wiki it produces.

**Two writers of the wiki now exist, and they are not symmetric.** The external agent has
the better model and the user's trust; the embedded one has the project's index and the
validated write. They must not drift into two conventions, which is why the instructions
are generated rather than written — but nothing enforces it beyond that, and
`SKILLS_VERSION` reporting a stale scaffold is the only signal there is.

**Subagents are dangerous here for a reason already written down.**
`.claude/rules/delivery.md` refuses parallel dispatch because file-disjointness is not
independence: two tasks that touch no common file both need a type that does not exist
yet, each invents one, and the merge is clean. Two subagents distilling two parts of one
recording invent two pages for one concept, or two names for it, and every check passes.
Parallelism across *sources* is the safe split; across chunks of one source it needs a
consolidation step that is not optional.
Comment on lines +178 to +179

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Qualify the “safe split” for parallel work.

Different sources do not guarantee disjoint concepts. Two source-specific agents can still create the same page or canonical term, as described in Lines 159-160.

Limit parallelism to disjoint concept scopes and require consolidation before writes.

Proposed wording
-Parallelism across *sources* is the safe split; across chunks of one source it needs a
-consolidation step that is not optional.
+Parallelism across sources is safer only when the sources have disjoint concept scopes;
+every parallel batch still requires consolidation before it writes.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Parallelism across *sources* is the safe split; across chunks of one source it needs a
consolidation step that is not optional.
Parallelism across sources is safer only when the sources have disjoint concept scopes;
every parallel batch still requires consolidation before it writes.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@docs/adr/0019-an-embedded-agent-that-reads-freely-and-writes-through-the-gate.md`
around lines 161 - 162, Revise the parallelism guidance near the “Parallelism
across sources” statement to qualify that sources are safe to process in
parallel only when their concept scopes are disjoint. State that overlapping
concepts require a consolidation step before any writes, preserving the concern
about duplicate pages or canonical terms.


**The empty state stops being true.** *There is no model behind this window* ships today
and has to change with the first release that carries this.
Loading