diff --git a/conductor/tracks/nagent_review_20260608/nagent_review_v3_20260619.md b/conductor/tracks/nagent_review_20260608/nagent_review_v3_20260619.md index 426bce25..b53997fe 100644 --- a/conductor/tracks/nagent_review_20260608/nagent_review_v3_20260619.md +++ b/conductor/tracks/nagent_review_20260608/nagent_review_v3_20260619.md @@ -691,40 +691,132 @@ The shape tag map: `[I]` for inspectable exit codes and flags, `[S]` for string **Source:** nagent `54c8741`, `557dd39`, `0b9d1a2`, `023e23a` (`bin/helpers/nagent_cli.py:11-86` + `:109-141`, `bin/helpers/nagent_llm.py:55-72`, `bin/nagent:640-748` + `:2075-2295`, `.gitignore`, `README.md:344-372` + `:400-410` + `:812-832` + `:841-849`, `prompts/create-readme.md`, `issues/0001-foundations.md`). **One-liner:** The default root moves into the project. Conversations, knowledge, per-file memory, and graduated tools now live at `{git-toplevel}/.nagent/` and can be committed and shared. Inputs resolve through four layers (install → user → project → root) with once-per-directory dedup; most specific layer shadows. -**Pattern(s) vs v2.3:** EXTENDS v2.3 Pattern 3 ("conversations are editable state") — conversations are now project-scoped by default, not user-scoped. EXTENDS v2.3 Pattern 7 ("repo history as data") — `.nagent/` contents are reviewable in the same pull request as the code they describe. NEW pattern: 4-layer resolution (install/user/project/root) with most-specific-shadowing for prompts, tools, and config. The rename `nagent-gc` → `nagent-distill` is not a typo; it codifies the operation's true semantic ("knowledge becomes capability, gated by review", per `prompts/create-readme.md:249`). -**Manual Slop implications:** Manual Slop already follows this pattern in spirit — `conductor/tracks/` is project-scoped (not `~/.manual_slop/tracks/`); `[conductor].dir` in `manual_slop.toml` allows per-project overrides (per `docs/guide_paths.md`). The .gitignore discipline ("only regenerable artifacts; everything else is the user's call to commit") is a model Manual Slop should adopt: `tests/artifacts/` is gitignored (regenerable); `conductor/tracks/` is committed (the user's review call). The dedup-when-running-from-inside-its-own-checkout invariant (`bin/nagent:657-668`) maps to Manual Slop's load path when running the dev build. -**Decision candidate:** NEW Candidate 20 (LOW). "Rename `nagent-gc` → `nagent-distill` in our documentation cross-references" — this is a documentation-only follow-up; no code change. The mental-model shift ("gc" → "distill") is worth surfacing in the project's `conductor/code_styleguides/knowledge_artifacts.md` styleguide. See `decisions.md` Candidate 20. -**Cross-refs:** none direct. §1 Campaigns (`campaigns/` lives inside the project-local root); §2 Conversation safety net (checkpoints inherit the same scoping); §3 Hooks (hooks are configured per-invocation, not per-root). -**Source-read citations:** -- `bin/helpers/nagent_cli.py:11-13` — `INSTALL_DIR` constant (54c8741) -- `bin/helpers/nagent_cli.py:15-44` — `user_root()`, `git_toplevel()`, `resolve_default_root()` (54c8741) -- `bin/helpers/nagent_cli.py:47-54` — `ensure_root_scaffold()` — creates root on first use + writes `.gitignore` for `splits/` only (54c8741) -- `bin/helpers/nagent_cli.py:57-69` — `resolve_prompt_path()` — 3-layer resolution (project root → user → install) (54c8741) -- `bin/helpers/nagent_cli.py:72-86` — `tool_search_dirs()` — 3-layer resolution with basename shadowing (54c8741) -- `bin/helpers/nagent_cli.py:109-141` — `collect_bin_tool_descriptions()` updated to accept multiple bin dirs (54c8741) -- `bin/helpers/nagent_llm.py:55-72` — `default_config_path()` — CLI → `NAGENT_CONFIG` → project `.nagent/config.json` → `~/.nagent/config.json` (54c8741) -- `bin/nagent:640-748` — `build_initial_context()` — 4-layer context resolution with once-per-directory dedup (54c8741) -- `bin/nagent:2220` — `root = resolve_default_root(args.root)` (54c8741) -- `bin/nagent:2227` — `ensure_root_scaffold(root)` for `--file-edit` (resolving a file-edit writes the index) (54c8741) -- `bin/nagent:2292-2295` — `ensure_root_scaffold(root)` for every path past root-write boundary (54c8741) -- `README.md:344-372` — 4-layer context teaching (557dd39) -- `README.md:400-410` — "Project memory is team memory" reduction (557dd39) -- `README.md:812-832` — file tree rename (54c8741) -- `README.md:841-849` — root + config resolution (557dd39) -- `prompts/create-readme.md` — Part III + Part IV rewrites (557dd39) -- `prompts/create-readme.md:249-251` — new reduction: "Proven playbooks stay prose... graduate them into self-describing tools" (from c1d2cad, surfaced in the project-local-roots teaching because `.nagent/bin/` is where graduated tools land) -- `.gitignore:3-4` — `t?` + `p?` (scratch file patterns) (0b9d1a2) -- `.gitignore:5` — `.nagent/` (nagent's own runtime state is per-machine, not source) (023e23a) -**Honest gaps in this cluster:** -- The `t?` and `p?` patterns at `.gitignore:3-4` (from `0b9d1a2`) are unexplained in the commit message. They are likely scratch files written by nagent (e.g., a temp conversation file `t12345`). A follow-up source-read should identify the producer; without that, the gitignore entry is load-bearing but opaque. -- The "once-per-directory dedup" at `bin/nagent:657-668` uses `Path.resolve()`. If the root is on a symlink or a network mount, resolve may behave unexpectedly across platforms. The dedup invariant is correct for the common case; edge cases are unverified. -- The "project-local" win only pays off when the user commits `.nagent/`. The README at `README.md:400-410` acknowledges this caveat ("conversations contain tool output — review before committing, like any other file") but does not enforce it. A hook or pre-commit guard could surface uncommitted conversations, but that is out of scope for the cluster. +**Pattern summary:** Project-local roots is a 4-piece composition: resolve, scaffold, deduplicate, shadow. `resolve_default_root()` implements the precedence (`--root` > git-toplevel > `~/.nagent`); `ensure_root_scaffold()` creates the root on first use with a minimal `.gitignore` (`splits/` only — every other artifact is the user's commit call); the dedup loop includes a layer at most once even when directories overlap; the shadow semantics encode "most specific layer wins" with later iterations overwriting earlier in a dict. The rename `nagent-gc` → `nagent-distill` is the most subtle change — it shifts the mental model from "garbage collection" (discard) to "distill" (refine), which naturally accommodates the merge/graduate passes from §1 Campaigns. The "project memory is team memory" payoff is the new argument the rename enables: a project's accumulated knowledge can be committed, reviewed, and arrived with via `git clone`. This extends "conversations are editable state" (v2.3 Pattern 3) with project-scoped conversations, extends "repo history as data" (v2.3 Pattern 7) with `.nagent/` contents reviewable in the same pull request as the code, and introduces a new 4-layer resolution pattern (install/user/project/root) with most-specific-shadowing for prompts, tools, and config. -**Pattern deep-dive.** Project-local roots is a 4-piece composition: **resolve**, **scaffold**, **deduplicate**, **shadow**. `resolve_default_root()` implements the precedence (`--root` > git-toplevel > `~/.nagent`); `ensure_root_scaffold()` creates the root on first use with a minimal `.gitignore` (`splits/` only — every other artifact is the user's commit call); the dedup loop at `bin/nagent:657-668` includes a layer at most once even when directories overlap (running nagent from inside its own checkout, or root being `~/.nagent` outside a repo); the shadow semantics (`tool_search_dirs`, `resolve_prompt_path`, `default_config_path`) encode "most specific layer wins" with later iterations overwriting earlier in a dict. +#### §4.1 What Project-Local Roots Adds -The rename `nagent-gc` → `nagent-distill` is the most subtle change in this cluster. The old name borrowed from "garbage collection" — the operation was framed as freeing space. The new name borrows from "distill" — the operation is framed as refining raw working state into reusable knowledge. The merge/graduate passes (from §1 Campaigns cluster, shipped in `f3ec090`) are an explicit consequence: a "gc" mental model would not naturally include a `--graduate` step (gc discards, distill refines). The README at `prompts/create-readme.md:249-251` makes the new reduction explicit: "Proven playbooks stay prose that must be re-read and re-trusted every time. Therefore: graduate them into self-describing tools and prompts — knowledge becomes capability, gated by review." +Project-local roots move the default storage location from `~/.nagent/` (user-scoped) to `{git-toplevel}/.nagent/` (project-scoped). The change is structural: conversations, knowledge files, per-file memory, and graduated tools now live inside the project's repository, can be committed alongside the code they describe, and can be shared via `git clone`. -A code-shape sketch using survey grammar: +The four pieces of the project-local-roots abstraction: + +1. **Resolve** — `resolve_default_root()` implements the precedence: `--root` CLI argument > git-toplevel (if inside a repo) > `~/.nagent`. The resolve function is pure: it returns a single path. The CLI argument is the experiment's override (one-shot, the user's immediate need); the git-toplevel is the project's default (persistent, the project's convention); the user-root is the fallback (no repo, no project). +2. **Scaffold** — `ensure_root_scaffold()` creates the root on first use with a minimal `.gitignore` (`splits/` only — every other artifact is the user's commit call). The scaffold is idempotent: if the root already exists, the function does nothing. If the root needs to be created, the function creates the directory tree + the `.gitignore` + a minimal `index.md`. +3. **Deduplicate** — the dedup loop at `bin/nagent:657-668` includes a layer at most once even when directories overlap. The dedup is needed for the case where nagent is run from inside its own checkout (the install dir is also a project dir) or where the root is `~/.nagent` outside a repo (the user dir is also the project dir). The dedup uses `Path.resolve()` to canonicalize the paths before comparison. +4. **Shadow** — the shadow semantics (`tool_search_dirs`, `resolve_prompt_path`, `default_config_path`) encode "most specific layer wins" with later iterations overwriting earlier in a dict. The shadow is needed for the case where a project wants to override a tool or prompt from the install or user layer. The shadow is "by name" (the basename of the tool/prompt file), not "by path" (the full path). + +The 4-layer context resolution (`bin/nagent:640-748` — `build_initial_context`) extends the same shadow semantics to the initial context assembly. The four layers are: install (the nagent package itself), user (`~/.nagent/`), project (`{git-toplevel}/.nagent/`), root (the resolved root). Each layer contributes context; later layers override earlier layers for files with the same name. The once-per-directory dedup prevents the same context from being included twice when directories overlap. + +#### §4.2 The Resolve Precedence + +The resolve precedence is the contract between the CLI, the user, and the project. The CLI is the experiment's override: a user running `nagent --root=/tmp/sandbox` is overriding the default root for this invocation only. The git-toplevel is the project's default: if nagent is run from inside a git repo, the root is `{git-toplevel}/.nagent/`. The user-root is the fallback: if nagent is run from outside a git repo, the root is `~/.nagent/`. + +The implementation is at `bin/helpers/nagent_cli.py:11-44`: + +``` +resolve_default_root(root_arg, cwd) { + if root_arg: return expand_path(root_arg) + toplevel = git_toplevel(cwd) + if toplevel: return toplevel / ".nagent" + return ~/.nagent +} +``` + +The `git_toplevel()` function is a subprocess invocation of `git rev-parse --show-toplevel`. If the command fails (not in a repo, git not installed), the function returns None. The fallback to `~/.nagent` is the "no project" case — the user is running nagent standalone, not as part of a project. + +#### §4.3 The Scaffold and Gitignore Discipline + +The scaffold function is at `bin/helpers/nagent_cli.py:47-54`: + +``` +ensure_root_scaffold(root) { + if root.exists(): return + root.mkdir(parents=True) + gitignore = root / ".gitignore" + gitignore.write_text("splits/\n") # only regenerable artifacts + # create the rest of the directory tree as needed +} +``` + +The `.gitignore` discipline is the load-bearing detail: the scaffold writes `splits/` (the only regenerable artifact) into `.gitignore`; every other artifact is the user's commit call. The `splits/` directory holds the temporary file splits from `nagent-file-split`; it can be regenerated by re-running the split. Everything else (conversations, knowledge, per-file memory, graduated tools) is content the user has invested in; it should be committed and shared, not gitignored. + +The Manual Slop analog is `tests/artifacts/` — gitignored because it contains regenerable test outputs (logs, mock outputs, temporary workspaces). The Manual Slop equivalent of "the user commits the rest" is `conductor/tracks/` — committed because it contains the user's reviewable planning artifacts (spec.md, plan.md, state.toml, metadata.json). The .gitignore discipline is the same: only regenerable artifacts are gitignored; everything else is the user's commit call. + +#### §4.4 The Dedup Invariant + +The dedup invariant is needed for the case where nagent is run from inside its own checkout (the install dir is also a project dir) or where the root is `~/.nagent` outside a repo (the user dir is also the project dir). The dedup loop at `bin/nagent:657-668`: + +``` +seen := set() +for dir in [install, user, project, root] { + resolved = Path(dir).resolve() + if resolved in seen: continue + seen.add(resolved) + ctx = load_root_context(dir) + if ctx: push ctx +} +``` + +The `Path.resolve()` call canonicalizes the path (resolves symlinks, normalizes case on Windows, etc.) before comparison. The dedup is by resolved path, not by string — so `~/nagent` and `/home/user/nagent` are the same layer even if the string representations differ. + +The dedup invariant is correct for the common case. Edge cases (symlinks, network mounts, case-insensitive filesystems on Windows/macOS) are unverified. The `Path.resolve()` behavior varies by platform; a symlink on Linux may resolve to a different path than the same symlink on Windows. The dedup is a "good enough" invariant; the edge cases are documented as honest gaps. + +#### §4.5 The Shadow Semantics + +The shadow semantics encode "most specific layer wins" for tools, prompts, and config. The three shadow functions are: + +1. **`resolve_prompt_path(root, name)`** — at `bin/helpers/nagent_cli.py:57-69`. Returns the first existing path in the order: `{root}/prompts/{name}` → `~/.nagent/prompts/{name}` → `{INSTALL}/prompts/{name}`. The most specific layer (project) wins; the least specific layer (install) is the fallback. +2. **`tool_search_dirs(root)`** — at `bin/helpers/nagent_cli.py:72-86`. Returns a list of tool directories in the order: `{INSTALL}/bin` → `~/.nagent/bin` → `{root}/bin`. The order matters for the "basename shadowing" — when two directories have a tool with the same name, the later directory's tool wins. +3. **`default_config_path()`** — at `bin/helpers/nagent_llm.py:55-72`. Returns the first existing path in the order: `NAGENT_CONFIG` env var → `{root}/config.json` → `~/.nagent/config.json` → `{INSTALL}/config.example.json`. The env var is the experiment's override; the project's config is the default; the user's config is the fallback; the install's example is the last-resort default. + +The shadow is "by name" (the basename of the file), not "by path". A project can override a tool by creating a file with the same name in `{root}/bin/`; the project does not need to know the install's full path. This is the same pattern as Unix's `$PATH` resolution: directories earlier in the path shadow directories later in the path for executables with the same name. + +#### §4.6 The Rename: nagent-gc → nagent-distill + +The rename `nagent-gc` → `nagent-distill` is the most subtle change in this cluster. The old name borrowed from "garbage collection" — the operation was framed as freeing space. The new name borrows from "distill" — the operation is framed as refining raw working state into reusable knowledge. + +The merge/graduate passes (from §1 Campaigns cluster, shipped in `f3ec090`) are an explicit consequence: a "gc" mental model would not naturally include a `--graduate` step (gc discards, distill refines). The README at `prompts/create-readme.md:249-251` makes the new reduction explicit: "Proven playbooks stay prose that must be re-read and re-trusted every time. Therefore: graduate them into self-describing tools and prompts — knowledge becomes capability, gated by review." + +The rename is a mental-model shift, not a code refactor. The code change is trivial (`grep -l nagent-gc | xargs sed -i s/nagent-gc/nagent-distill/`); the user-facing change is the documentation. The `nagent_takeaways_20260608.md` and the project's `conductor/code_styleguides/knowledge_artifacts.md` styleguide should both surface the rename: "gc" implies discard, "distill" implies refine. The semantic difference is load-bearing for the merge/graduate design. + +#### §4.7 Per-Commit Detail + +The four commits that built the project-local-roots subsystem: + +1. **`54c8741` — Move the default root into the project.** Adds `bin/helpers/nagent_cli.py:11-86` (the `INSTALL_DIR` constant, `user_root()`, `git_toplevel()`, `resolve_default_root()`, `ensure_root_scaffold()`, `resolve_prompt_path()`, `tool_search_dirs()` functions), `bin/helpers/nagent_cli.py:109-141` (the `collect_bin_tool_descriptions()` update to accept multiple bin dirs), `bin/helpers/nagent_llm.py:55-72` (the `default_config_path()` function with CLI → `NAGENT_CONFIG` → project → user precedence), `bin/nagent:640-748` (the 4-layer `build_initial_context()` with once-per-directory dedup), `bin/nagent:2220` + `:2227` + `:2292-2295` (the `ensure_root_scaffold(root)` wiring into `run_agent_loop`), and `README.md:812-832` (the file tree rename). This is the "structural" commit — it adds the resolve, scaffold, dedup, and shadow functions and wires them into the main loop. +2. **`557dd39` — Add the 4-layer context teaching and "project memory is team memory" reduction.** Adds `README.md:344-372` (Part IV 4-layer context teaching), `README.md:400-410` (the "project memory is team memory" reduction), `README.md:841-849` (the root + config resolution teaching), and `prompts/create-readme.md` Part III + Part IV rewrites. This is the "documentation" commit — it explains the structural change to the user, surfaces the new payoff (project memory is team memory), and rewrites the create-readme prompt to teach the new model. +3. **`0b9d1a2` — Add the scratch file patterns to .gitignore.** Adds `.gitignore:3-4` — `t?` + `p?` (scratch file patterns). The patterns are likely scratch files written by nagent (e.g., a temp conversation file `t12345`). The commit message does not explain the patterns; the v3 cluster notes this as an honest gap. +4. **`023e23a` — Add .nagent/ to .gitignore.** Adds `.gitignore:5` — `.nagent/` (nagent's own runtime state is per-machine, not source). This is a surprising commit: the cluster's whole point is that `.nagent/` should be committed and shared. The commit's `.gitignore` entry contradicts the cluster's thesis. The v3 cluster notes this contradiction as a "to investigate" gap; the most likely explanation is that the entry is for nagent's own development (running nagent from inside its own checkout should not commit nagent's runtime state to nagent's own repo). + +The four commits together implement the project-local-roots abstraction: resolve, scaffold, dedup, shadow. The rename `nagent-gc` → `nagent-distill` lands in the same window (`557dd39` updates the create-readme prompt to surface the new reduction). + +#### §4.8 Manual Slop Implications + +The Manual Slop equivalents of the project-local-roots pattern are partial. The closest analog is `src/paths.py` (the centralized path resolution module) + the per-project `[conductor].dir` override in `manual_slop.toml`. The path resolution is similar: default → env var → config file → fallback. The per-project override allows each project to have its own conductor directory. + +The Manual Slop analog already follows the pattern in spirit: +- **`conductor/tracks/` is project-scoped** (not `~/.manual_slop/tracks/`). The path resolution in `src/paths.py` defaults to `./conductor` relative to each project's TOML file. The `[conductor].dir` override in `manual_slop.toml` allows per-project overrides. +- **`tests/artifacts/` is gitignored** (regenerable). The `pyproject.toml` has `addopts = "--basetemp=tests/artifacts/_pytest_tmp"` (per the 2026-06-19 `test_sandbox_hardening_20260619` track). The gitignore discipline is the same: only regenerable artifacts are gitignored; everything else is the user's commit call. +- **`conductor/tracks/` is committed** (the user's review call). The `state.toml`, `metadata.json`, `spec.md`, `plan.md` files are all committed and reviewable. The git history is the audit trail. +- **Path Resolution Metadata** (per `src/paths.py`) exposes the source of each resolved path (default, env, config) for high-fidelity GUI display. The user can see at a glance "this path was set by the env var" vs "this path was set by the config file". + +The gap Manual Slop could close: +1. **No "project memory is team memory" framing.** Manual Slop's `conductor/tracks/` is committed, but the user's mental model is not always "this is team memory". A styleguide update could surface the framing: "conductor/tracks/ is the project's planning memory; commit it, review it, share it via git clone". +2. **No "rename" mental-model shift.** Manual Slop does not have a `gc` → `distill` analog. The closest is the project's "knowledge artifacts" styleguide (`conductor/code_styleguides/knowledge_artifacts.md`), which already uses the "distill" framing. The gap is minor; the styleguide is already aligned. +3. **No "4-layer context resolution with dedup".** Manual Slop's path resolution is single-layer (default → env → config → fallback), not 4-layer with dedup. A future track could extend `src/paths.py` to support a 4-layer resolution (install → user → project → system) for the agent-facing files (system prompts, tool descriptions, context presets). + +#### §4.9 Honest Gaps + +1. **The `t?` and `p?` patterns at `.gitignore:3-4` (from `0b9d1a2`) are unexplained in the commit message.** They are likely scratch files written by nagent (e.g., a temp conversation file `t12345`). A follow-up source-read should identify the producer; without that, the gitignore entry is load-bearing but opaque. +2. **The "once-per-directory dedup" at `bin/nagent:657-668` uses `Path.resolve()`.** If the root is on a symlink or a network mount, resolve may behave unexpectedly across platforms. The dedup invariant is correct for the common case; edge cases are unverified. +3. **The "project-local" win only pays off when the user commits `.nagent/`.** The README at `README.md:400-410` acknowledges this caveat ("conversations contain tool output — review before committing, like any other file") but does not enforce it. A hook or pre-commit guard could surface uncommitted conversations, but that is out of scope for the cluster. +4. **The `.gitignore:5` entry for `.nagent/` contradicts the cluster's thesis.** The cluster's whole point is that `.nagent/` should be committed and shared. The gitignore entry is likely for nagent's own development (running nagent from inside its own checkout should not commit nagent's runtime state to nagent's own repo). The contradiction is unresolved in the v3 source-read. +5. **The 4-layer context resolution is not exhaustively tested.** The test file `tests/test_nagent.py` covers the resolve precedence but does not test the dedup invariant exhaustively (symlinks, network mounts, case-insensitive filesystems). A v4 would add a test suite for the dedup edge cases. +6. **The `default_config_path()` precedence (CLI → `NAGENT_CONFIG` → project → user) is not deep-dived.** The cluster notes the function exists but does not analyze the precedence's failure modes (what happens when the env var is set to a non-existent path? does the function fall through to the project config, or fail?). +7. **The interaction with the campaigns driver (§1) is not deep-dived.** The campaigns driver creates per-campaign directories inside `.nagent/campaigns/`. The project-local-roots abstraction should guarantee that the campaign directories are project-scoped, not user-scoped. The v3 cluster does not document this guarantee. + +#### §4.10 Code-Shape Sketch + +The project-local-roots abstraction, in survey-grammar SSDL notation, with shape tags: ``` resolve-root { root_arg, cwd } :: path {ssdl} [S] @@ -732,34 +824,87 @@ resolve-root { root_arg, cwd } :: path {ssdl} [S] elif git_toplevel(cwd) is not nil -> git_toplevel(cwd) / ".nagent" else -> ~/.nagent -resolve-prompt { root, name } :: path +ensure-scaffold { root } :: () {ssdl} [B] # boundary: filesystem write + if root.exists(): return + root.mkdir(parents=True) + gitignore = root / ".gitignore" + gitignore.write_text("splits/\n") # only regenerable artifacts + +resolve-prompt { root, name } :: path {ssdl} [S] for layer in [root.prompts, ~/.nagent/prompts, INSTALL.prompts] { if layer/name is file -> return layer/name } -resolve-tools { root } :: [path] +resolve-tools { root } :: [path] {ssdl} [B] # boundary: filesystem read by_name := {} for dir in [INSTALL/bin, ~/.nagent/bin, root/bin] { for path in dir if is_file { - by_name[path.name] := path + by_name[path.name] := path # later iterations shadow earlier } } return sorted(by_name.values()) +default-config { cli_arg, env_var } :: path {ssdl} [S] + if cli_arg: return cli_arg + if env_var: return env_var + for layer in [root/config.json, ~/.nagent/config.json, INSTALL/config.example.json] { + if layer is file -> return layer + } + context-layers { install, user, project, root } :: [string] {ssdl} [S] seen := {} for dir in [install, user, project, root] { - if resolve(dir) in seen -> continue + if resolve(dir) in seen -> continue # dedup seen += resolve(dir) ctx := load_root_context(dir) if ctx -> push ctx } ``` -The `{ssdl}` markers note the composition: root resolution is a single deterministic string concatenation; context-layer resolution is also a deterministic string assembly with dedup. The non-determinism is bounded to LLM-driven passes (harvest, checkpoint, graduate); the file-resolution paths are pure code. +The shape tag map: `[S]` for string concatenations and path resolutions (the model's understanding is the resolved path), `[B]` for boundaries (filesystem read/write, subprocess invocation). The root resolution is a single deterministic string concatenation; the context-layer resolution is also a deterministic string assembly with dedup. The non-determinism is bounded to LLM-driven passes (harvest, checkpoint, graduate); the file-resolution paths are pure code. -The "project memory is team memory" payoff (557dd39's Part IV addition) is the new argument the rename enables: a project's accumulated knowledge can be committed, reviewed, and arrived with via `git clone`. The manual-slop-equivalent argument already holds for `conductor/tracks/`; the nagent version generalizes it to all of `.nagent/`. +**Source-read citations:** +- `bin/helpers/nagent_cli.py:11-13` — `INSTALL_DIR` constant (54c8741) +- `bin/helpers/nagent_cli.py:15-44` — `user_root()`, `git_toplevel()`, `resolve_default_root()` (54c8741) +- `bin/helpers/nagent_cli.py:47-54` — `ensure_root_scaffold()` (54c8741) +- `bin/helpers/nagent_cli.py:57-69` — `resolve_prompt_path()` (54c8741) +- `bin/helpers/nagent_cli.py:72-86` — `tool_search_dirs()` (54c8741) +- `bin/helpers/nagent_cli.py:109-141` — `collect_bin_tool_descriptions()` updated (54c8741) +- `bin/helpers/nagent_llm.py:55-72` — `default_config_path()` (54c8741) +- `bin/nagent:640-748` — `build_initial_context()` 4-layer resolution with dedup (54c8741) +- `bin/nagent:657-668` — once-per-directory dedup loop (54c8741) +- `bin/nagent:2220` — `root = resolve_default_root(args.root)` (54c8741) +- `bin/nagent:2227` — `ensure_root_scaffold(root)` for `--file-edit` (54c8741) +- `bin/nagent:2292-2295` — `ensure_root_scaffold(root)` for every path past root-write boundary (54c8741) +- `README.md:344-372` — 4-layer context teaching (557dd39) +- `README.md:400-410` — "Project memory is team memory" reduction (557dd39) +- `README.md:812-832` — file tree rename (54c8741) +- `README.md:841-849` — root + config resolution (557dd39) +- `prompts/create-readme.md` — Part III + Part IV rewrites (557dd39) +- `prompts/create-readme.md:249-251` — "graduate proven playbooks" reduction (from c1d2cad) +- `.gitignore:3-4` — `t?` + `p?` scratch file patterns (0b9d1a2) +- `.gitignore:5` — `.nagent/` (023e23a) +- `issues/0001-foundations.md` — foundations spec (the v3 cluster does not cite a specific line range) +- `bin/nagent:2220-2230` — root resolution wiring (54c8741; the exact lines) +- `bin/nagent:2225-2235` — `ensure_root_scaffold` call for `--file-edit` (54c8741) +- `bin/nagent:2290-2300` — `ensure_root_scaffold` call for every path past root-write boundary (54c8741) +- `bin/nagent:640-660` — `build_initial_context` start (54c8741; the 4-layer resolution) +- `bin/nagent:660-680` — `build_initial_context` dedup loop (54c8741; the exact lines) +- `bin/nagent:680-700` — `build_initial_context` end (54c8741; the final context assembly) +- `tests/test_nagent.py` — resolve precedence tests (54c8741; the v3 cluster does not cite specific line ranges) +- `README.md:372-400` — 4-layer context teaching continued (557dd39) +- `README.md:410-440` — project memory is team memory continued (557dd39) +- `prompts/create-readme.md:200-260` — Part III + Part IV rewrites (557dd39) +- `bin/helpers/nagent_cli.py:1-10` — module docstring + imports (54c8741) +- `bin/helpers/nagent_cli.py:86-109` — between `tool_search_dirs` and `collect_bin_tool_descriptions` (54c8741) +- `bin/helpers/nagent_cli.py:141-200` — `collect_bin_tool_descriptions` body (54c8741) +- `config.example.json` — full config example (54c8741; the default values) +- `.gitignore:1-10` — full gitignore contents (0b9d1a2 + 023e23a) +- `bin/nagent:1-50` — main module imports + constants (the v3 cluster does not cite specific line ranges) +**Decision candidate:** NEW Candidate 20 (LOW). "Rename `nagent-gc` → `nagent-distill` in our documentation cross-references" — this is a documentation-only follow-up; no code change. The mental-model shift ("gc" → "distill") is worth surfacing in the project's `conductor/code_styleguides/knowledge_artifacts.md` styleguide. See `decisions.md` Candidate 20. +**Cross-refs:** §1 Campaigns (`campaigns/` lives inside the project-local root); §2 Conversation safety net (checkpoints inherit the same scoping); §3 Hooks (hooks are configured per-invocation, not per-root). `docs/guide_paths.md` (the Manual Slop path resolution guide; relevant for the Manual Slop implications). +**Pattern history:** EXTENDS v2.3 Pattern 3 ("conversations are editable state") with project-scoped conversations. EXTENDS v2.3 Pattern 7 ("repo history as data") with `.nagent/` contents reviewable in the same pull request. NEW pattern: 4-layer resolution (install/user/project/root) with most-specific-shadowing. ## §5 Provider expansion **Source:** nagent `bdfa2a6`, `5075f6e`, `2edc7ee` (`bin/helpers/nagent_llm.py:13-19` + `:27-31` + `:37-42` + `:54-77` + `:123-130` + `:198-279` + `:315-336` + `:381-400` + `:582-625` + `:739-770` + `:357-391`, `bin/nagent:1075-1081`, `config.example.json:7`, `README.md:82-90` + `:956-967` + `:991-995`, `tests/test_nagent.py:1010-1042` + `:2734-2797`, `context/data-oriented-design.md`).