feat(directives): scavenge from nagent_review_20260608/: 5 directives
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# decompose_or_isolate_never_offload
|
||||
|
||||
## v1
|
||||
|
||||
**Why this iteration:** Lifted from `conductor/tracks/nagent_review_20260608/nagent_takeaways_v3_20260619.md:71-72` (Candidate 22 — "Tier 3 worker contract 'decompose or isolate, never offload'"). The two reasons delegation is worth its cost are named explicitly: decomposition (genuinely complex, with parts) and context isolation (noisy step, small result). The recursion bug fixed by this naming is in the same source.
|
||||
**Source:** `conductor/tracks/nagent_review_20260608/nagent_takeaways_v3_20260619.md:71-72`
|
||||
|
||||
---
|
||||
**Lifted:** 2026-07-02 (scavenge pass — directive library expansion from nagent_review track)
|
||||
@@ -0,0 +1,27 @@
|
||||
# Delegation is justified by decomposition or context isolation — never as a single-action offload
|
||||
|
||||
## The two-reason rule
|
||||
|
||||
A Tier 2 agent (or any orchestrator) may delegate a task to a sub-agent only when one (or both) of the following is true:
|
||||
|
||||
1. **Decomposition.** The task is genuinely complex, with multiple parts. The sub-agent handles one part; the parent composes the answers.
|
||||
2. **Context isolation.** The step is noisy (large file reads, long shell output, retries) but the *result* is small. The sub-agent absorbs the noise; the parent gets a distilled artifact.
|
||||
|
||||
Delegation is NOT justified for:
|
||||
|
||||
- A single small action whose result is no smaller than doing it directly.
|
||||
- A trivial computation that the parent could do inline.
|
||||
- "It feels cleaner" without one of the two reasons above.
|
||||
|
||||
Per `conductor/tracks/nagent_review_20260608/nagent_takeaways_v3_20260619.md:71-72`: the v3 delegation rewrite "fixes a recursion bug (file-edit agent → worker → nagent-file-edit → file-edit agent → ... hangs the tree) by naming the two reasons delegation is worth its cost: decomposition ... and context isolation. 'Don't offload a single small action whose result is no smaller than doing it yourself.'"
|
||||
|
||||
## Why
|
||||
|
||||
Delegation has costs: serialization, IPC, model invocation latency, the prompt-boundary summary. If the delegated task is one trivial operation, the parent pays all those costs to get back what it could have computed in one line. Worse, delegation creates a recursion bug: the parent → worker → sub-worker → sub-sub-worker → ... loop that hangs the tree.
|
||||
|
||||
## What this means in practice
|
||||
|
||||
- The Tier 3 worker system prompt carries this rule as an explicit contract.
|
||||
- A test (`tests/test_<delegation>.py`) asserts the worker contract prefix is present in the initial context.
|
||||
- A test asserts the recursion case is caught (a worker that tries to delegate to itself is rejected).
|
||||
- "Reasonable" judgment calls are explicit: when in doubt, do it inline; when the noise is large, delegate.
|
||||
@@ -0,0 +1,9 @@
|
||||
# file_id_stable_across_rename
|
||||
|
||||
## v1
|
||||
|
||||
**Why this iteration:** Lifted from `conductor/tracks/nagent_review_20260608/nagent_takeaways_20260608.md:128-130` (Pattern 4 — "File-identity over file-path — a stable st_dev:st_ino is rename-safe"). The nagent primitive is `file_id_for_path(path) -> "{st_dev}:{st_ino}"`; the same primitive applies to Manual Slop's `FileItem` and any per-file durable cache.
|
||||
**Source:** `conductor/tracks/nagent_review_20260608/nagent_takeaways_20260608.md:128-130`
|
||||
|
||||
---
|
||||
**Lifted:** 2026-07-02 (scavenge pass — directive library expansion from nagent_review track)
|
||||
@@ -0,0 +1,26 @@
|
||||
# Durable per-file memory is keyed by file_id (st_dev:st_ino), not by path — renames preserve identity
|
||||
|
||||
## The identity primitive
|
||||
|
||||
When memory is attached to a specific file on disk (a per-file conversation, a curation snapshot, a summary cache), the lookup key is the file's inode pair `{st_dev}:{st_ino}`, not the path string.
|
||||
|
||||
Per `conductor/tracks/nagent_review_20260608/nagent_takeaways_20260608.md:128-130`: "nagent_file_edit_lib.py:file_id_for_path(path) -> '{st_dev}:{st_ino}'. The per-file conversation index keys by inode, not by path. Rename the file in place (same inode) → same conversation."
|
||||
|
||||
## Why
|
||||
|
||||
Path-keyed memory is fragile. A user renames `src/foo.py` to `src/bar.py`, and the curation snapshot, the per-file conversation log, and the summary cache all silently orphan. The data is still on disk; the *index* lost its key.
|
||||
|
||||
Inode-keyed memory survives:
|
||||
- Rename in place (`mv src/foo.py src/bar.py`) — same inode.
|
||||
- Move across directories (`mv src/foo.py lib/foo.py`) — same inode on the same filesystem.
|
||||
- Editor refactor that preserves the file content.
|
||||
|
||||
It does NOT survive:
|
||||
- `git checkout` to a different commit (inode may change on checkout depending on filesystem).
|
||||
- `rm` + recreate — new inode, new identity.
|
||||
|
||||
## What this means in practice
|
||||
|
||||
- Any `FileItem`, `ContextPreset`, or per-file cache field that tracks durable state has a `file_id: str = "{st_dev}:{st_ino}"` field populated at load time.
|
||||
- Lookups prefer `file_id`; fallback to fuzzy match on basename + directory tree.
|
||||
- The schema migration: existing path-only files get a `file_id` added on first read; the path field stays for human-readable display.
|
||||
@@ -0,0 +1,9 @@
|
||||
# parse_failure_visible_to_conversation
|
||||
|
||||
## v1
|
||||
|
||||
**Why this iteration:** Lifted from `conductor/tracks/nagent_review_20260608/nagent_takeaways_20260608.md:171-178` (Pattern 6 — "Visible retry on protocol failure — turn errors into conversation data"). The nagent pattern: bad output + `<system>` correction appended to the conversation; `MAX_FORMAT_RETRIES = 3`; the conversation is greppable for every retry.
|
||||
**Source:** `conductor/tracks/nagent_review_20260608/nagent_takeaways_20260608.md:171-178`
|
||||
|
||||
---
|
||||
**Lifted:** 2026-07-02 (scavenge pass — directive library expansion from nagent_review track)
|
||||
@@ -0,0 +1,21 @@
|
||||
# Parse failures become conversation data — the bad output and the system correction are appended so the next call sees them
|
||||
|
||||
## The visibility contract
|
||||
|
||||
When a model's output fails to parse (malformed tool call, broken tag, missing closing delimiter), the failure is NOT silently retried inside the loop. The loop:
|
||||
|
||||
1. Appends the bad output to the conversation as a quoted block, with a system-level correction message ("Invalid format: {parse_error}. Respond only with valid tags.").
|
||||
2. Lets the next call see its own previous failure + the correction.
|
||||
3. Increments a visible counter so the user (and Tier 2 code review) can spot persistent parse failures.
|
||||
|
||||
Per `conductor/tracks/nagent_review_20260608/nagent_takeaways_20260608.md:171-178`: the nagent pattern is `MAX_FORMAT_RETRIES = 3` + the bad output + the `<system>` correction are appended to the conversation. "The user can grep the conversation for `<system>` to find every retry."
|
||||
|
||||
## Why
|
||||
|
||||
Silent retries hide the model's actual failure mode. If the model keeps emitting bad XML because it misunderstood the schema, a silent retry loop burns context without the user (or Tier 2) ever seeing *what* the model is getting wrong. Surfacing the failure in the conversation makes the loop debuggable and gives the user a chance to fix the prompt.
|
||||
|
||||
## What this means in practice
|
||||
|
||||
- The diagnostic panel shows the last N parse failures: the model output + the error message + the timestamp.
|
||||
- A persistent parse failure (> 3 retries) escalates: the conversation pauses with a "fix your prompt or accept this garbage" dialog.
|
||||
- The conversation file is greppable for parse failures (`grep "<system>Invalid"`); the JSON-L comms log is not sufficient on its own.
|
||||
@@ -0,0 +1,9 @@
|
||||
# state_visible_at_the_right_layer
|
||||
|
||||
## v1
|
||||
|
||||
**Why this iteration:** Lifted from `conductor/tracks/nagent_review_20260608/nagent_takeaways_20260608.md:33-38` (Pattern 1 — "State visibility — files for the things that matter, processes for the things that don't"). The nagent framing: state that *survives* is in `~/.nagent/`; state that doesn't survive is in the running process. The boundary is sharp.
|
||||
**Source:** `conductor/tracks/nagent_review_20260608/nagent_takeaways_20260608.md:33-38`
|
||||
|
||||
---
|
||||
**Lifted:** 2026-07-02 (scavenge pass — directive library expansion from nagent_review track)
|
||||
@@ -0,0 +1,31 @@
|
||||
# Durable state lives in files the user can inspect — transient process state is acceptable only if it has an inspection surface
|
||||
|
||||
## The visibility boundary
|
||||
|
||||
The boundary is sharp: anything the user might want to inspect, diff, copy, or back up lives in a file. Anything else (an in-flight LLM call result, a parse state, a one-shot retry counter) stays in process memory.
|
||||
|
||||
Per `conductor/tracks/nagent_review_20260608/nagent_takeaways_20260608.md:33-38`: "The state that *doesn't survive* is the running process: LLM call result, current turn, parse state. The boundary is sharp: anything the user might want to inspect, diff, copy, or back up is a file."
|
||||
|
||||
## What goes in files (durable)
|
||||
|
||||
- Project config: `manual_slop.toml`, project presets, personas
|
||||
- Track state: `conductor/tracks/<id>/{spec,plan,state.toml,metadata.json}`
|
||||
- Discussion history: `disc_entries` flushed to project TOML
|
||||
- Logs: `comms.log` JSON-L, session artifacts
|
||||
- Knowledge harvest: `~/.manual_slop/knowledge/*.md` (per the knowledge harvest pattern)
|
||||
|
||||
## What stays in process (transient, but inspectable)
|
||||
|
||||
- Provider history lists (`_anthropic_history`, etc.) — acceptable *if* a "Live State Inspector" GUI panel exposes them.
|
||||
- Current `disc_entries` *before* the user flushes the discussion — the user's view is the source of truth; the JSON-L is the durable form.
|
||||
- Cache state, token budgets, ephemeral worker scratch — short-lived; not a durability concern.
|
||||
|
||||
## Why
|
||||
|
||||
The user must be able to `cat`, `git diff`, `grep`, and `cp` the state that matters. Process globals make this impossible. The trade-off (latency for disk writes) is acceptable because Manual Slop is a single-developer expert tool, not a high-throughput service.
|
||||
|
||||
## What this means in practice
|
||||
|
||||
- New stateful subsystems land with a file-backed surface from the start; "we'll add disk persistence later" is rejected.
|
||||
- Process globals that survive across turns must be exposed via a GUI inspector panel (the "Live State Inspector" pattern) or via the Hook API.
|
||||
- A piece of state without a `cat`-able form is a bug, not a feature.
|
||||
@@ -0,0 +1,9 @@
|
||||
# subagent_returns_artifact_not_transcript
|
||||
|
||||
## v1
|
||||
|
||||
**Why this iteration:** Lifted from `conductor/tracks/nagent_review_20260608/nagent_takeaways_20260608.md:285-296` (Pattern 10 — "Sub-agents return a concise artifact, not a full transcript"). The nagent pattern is the `<nagent-conversation-result>` tag with body + exit + tokens only; Manual Slop's `SubConversationResult` type is specified as `artifact: str + tokens_in + tokens_out + exit_code + errors: list[str]`.
|
||||
**Source:** `conductor/tracks/nagent_review_20260608/nagent_takeaways_20260608.md:285-296`
|
||||
|
||||
---
|
||||
**Lifted:** 2026-07-02 (scavenge pass — directive library expansion from nagent_review track)
|
||||
@@ -0,0 +1,24 @@
|
||||
# Sub-agents return a concise artifact, never their full conversation log
|
||||
|
||||
## The artifact contract
|
||||
|
||||
When a sub-agent (a child invocation of a Tier 3 worker, a sub-conversation, or a Tier 4 QA call) finishes, the parent's context must receive only:
|
||||
|
||||
- The distilled answer (a string artifact — the response content)
|
||||
- Token usage (in + out)
|
||||
- Exit code / success status
|
||||
- Errors (a short list of error strings, if any)
|
||||
|
||||
The sub-agent's intermediate reads, retries, tool calls, scratch reasoning, and dead-end attempts do NOT propagate to the parent's context. They are persisted to a separate log file (e.g., `~/.manual_slop/sub_conversations/<uuid>.jsonl`) for debugging but are invisible to the parent's next turn.
|
||||
|
||||
Per `conductor/tracks/nagent_review_20260608/nagent_takeaways_20260608.md:285-296`: the child's `<nagent-conversation-result>` contains "only the child's `<nagent-response>` body + exit code + stderr. The parent's conversation is *not* polluted with the child's intermediate reads, shell calls, or retries."
|
||||
|
||||
## Why
|
||||
|
||||
If every sub-agent's full conversation streamed into the parent, the parent's context would explode after 2-3 invocations. Worse, the parent would start reasoning about the *sub-agent's reasoning* rather than the *answer*, doubling the cognitive load and introducing cascading hallucinations.
|
||||
|
||||
## What this means in practice
|
||||
|
||||
- The `SubConversationResult` (or equivalent) type is `artifact: str + tokens_in: int + tokens_out: int + exit_code: int + errors: list[str]` — never a transcript.
|
||||
- Parent's `disc_entries` gets exactly one new entry per sub-agent call: the artifact, as a User or System role.
|
||||
- The sub-agent's full transcript is on disk; the parent does not see it.
|
||||
Reference in New Issue
Block a user