Reference/analysis track. Produces 0 code changes.
Artifacts (conductor/tracks/nagent_review_20260608/):
- spec.md (240 lines) - track wrapper with Application/Meta-Tooling framing
- report.md (571 lines) - 14-section deep-dive; primary deliverable
- comparison_table.md (79 lines) - flat side-by-side reference
- decisions.md (286 lines) - 10 future-track candidates with priority matrix
- nagent_takeaways_20260608.md (363 lines) - 10 actionable patterns grounded
in code (file:line refs into nagent source and Manual Slop source)
- metadata.json (132 lines) - structured metadata + verification criteria
- state.toml (113 lines) - per-task tracking + user-corrections log (7 entries)
14 nagent principles covered in report.md (durable work, text-in/text-out,
editable state, visible protocol, the loop, per-file memory, repo history,
neighborhoods, sub-conversations, controlled writes, large files, tool
discovery, framework differences, build your own).
6 pitfalls (revised from 8 after user-corrections):
1. No structured output protocol in Application AI (opaque function calling)
2. Provider-specific history in process globals (ai_client._anthropic_history
+ _deepseek_history + _minimax_history)
3. RAG is not 'history as data' (fuzzy, not auditable)
4. AI client is a stateful singleton (2,685-line ai_client.py)
5. No non-MMA disposable sub-conversations (1:1 gap; user-flagged want)
6. Hard-coded tool discovery (45-tool if/elif in mcp_client.py)
User-corrections applied (3 rounds, 7 total corrections recorded):
- Editable discussions: PARTIAL -> PARITY (DIFFERENT FOCUS) with full A1-A7
per-entry + B1-B11 discussion-level + C1-C5 undo/redo operation matrix
- Per-file memory: DOMAIN MISMATCH -> MANUAL SLOP IS STRONGER IN
CURATION DIMENSION (FileItem + ContextPreset vs nagent's inode-keyed
conversation log; complementary, not equivalent)
- Sub-conversations: MMA has it; 1:1 does not -> 'PARITY for MMA; GAP for
1:1 discussions' (user wants this)
- RAG: opt-in, not gap; user wants pre-staging via sub-conversation
- Personas: config bundling (can opt out via AI settings)
- Tool discovery: deferred (user has 'intent based DSL' idea but 'no where
near that ideation yet')
10 actionable takeaways (separate from the 6 pitfalls - those are
diagnosis, these are prescription):
1. State visibility (UI inspector for in-process state)
2. Readable conversation log (text-greppable, not just JSON-L)
3. Sub-agents for 1:1 (HIGH priority - user-flagged)
4. File-identity over file-path (st_dev:st_ino rename-safe)
5. One loop shape visible in diagnostics
6. Visible retry on protocol failure
7. Meta-Tooling DSL (intent-based, deferred)
8. Self-describing tools (subsumed by mcp_architecture_refactor_20260606)
9. Single source of truth for disc_entries + provider history
10. Sub-agent return type constraint (bake into candidate #1 spec)
Domain classification: every recommendation tagged Application / Meta-Tooling
/ Both per docs/guide_meta_boundary.md. nagent lives in the Meta-Tooling
domain; Manual Slop's Application AI is a different kind of thing.
No code modified by this track (reference/analysis only). All 7 files
parse cleanly (JSON, TOML, Markdown). All internal cross-links resolve.
Track is 'active' awaiting human review; future-track candidates live in
decisions.md and nagent_takeaways_20260608.md.
ROOT CAUSE: imgui_md (mekhontsev/imgui_md) BLOCK_P does NOT call ImGui::NewLine()
when m_list_stack is non-empty (verified in imgui_md.cpp). So a multi-paragraph
list item like:
- bullet text (long, wraps to 2 lines)
continuation paragraph
renders BOTH paragraphs at the same Y because the second BLOCK_P enters/exits
without advancing the cursor. The continuation crashes into the previous
paragraph's last wrapped line.
FIX: Add MarkdownRenderer._normalize_list_continuations preprocessor that
strips blank lines between a list item and its indented continuation. The
continuation then becomes a lazy continuation of the first paragraph (single
BLOCK_P in imgui_md, proper text wrapping, no overlap). Trade-off: users
cannot have separate paragraphs within a single list item. Acceptable.
Also: fixed a pre-existing bug in _normalize_nested_list_endings where a
duplicate conditional caused the function to return empty string (the
out.append(line) was inside the wrong scope). It was silently corrupting
all list content since fd5f4d0e.
TESTS: 23/23 markdown unit tests pass. 3 new tests for the new preprocessor
covering: blank-strip case, blank-preservation case, simple-list passthrough.
FIX 1 (src/markdown_table.py): Cells now use imgui_md.render(c) instead of
imgui.text_wrapped(c). imgui_md uses MD4C which strips backtick-delimited
inline code spans BEFORE rendering, so backticks no longer appear as
literal characters in cell content. Side benefit: inline emphasis
(*foo*, **bar**) now renders in cells too.
FIX 2 (src/markdown_helper.py): Added MarkdownRenderer._normalize_nested_list_endings.
Upstream imgui_md (mekhontsev/imgui_md) BLOCK_UL exit only calls
ImGui::NewLine() for top-level list endings. For nested list endings, no
NewLine is emitted, so the next text starts at the same Y as the last
list item, causing visual overlap. The preprocessor inserts a blank
line before any line that follows a list item with MORE indent than
itself, forcing a paragraph break. Cannot fix the C++ from Python.
Tests:
- test_markdown_table_wrapped.py: updated to assert imgui_md.render is
called for cell content (not imgui.text_wrapped).
- test_markdown_helper_bullets.py: added 4 tests for the new preprocessors
(nested-list blank insertion + bullet delimiter conversion + edge cases).
20/20 markdown unit tests pass. 1-space indentation throughout.
KNOWN LIMITATIONS (cannot fix without forking imgui_md C++):
- Inline code spans render as plain text (no monospace font in cells)
- The ' * ' bullet delimiter has a Y-overlap bug upstream
(workaround: pre-convert to '- ' via _normalize_bullet_delimiters)
- Nested list ending overlap (workaround: insert blank line via
_normalize_nested_list_endings)
Table fix (src/markdown_table.py):
- Add TableColumnFlags_.width_stretch to each table_setup_column call
(was missing — columns had no width to wrap against, so text_wrapped
couldn't grow row height → all rows squished together)
- Remove the explicit for-h-in-headers: table_next_column + text_wrapped(h)
loop. table_headers_row() already renders the header from the
table_setup_column() names; the explicit loop was drawing it AGAIN on
top → double-rendered header rows.
Bullet fix (src/markdown_helper.py):
- Revert _render_md_no_bullet_overlap → simple imgui_md.render(chunk);
imgui.spacing() (the original af0bbe97 approach). The complex
workaround was stripping '- ' and rendering stripped text to imgui_md,
which double-rendered '- 1. ...' content (imgui.bullet from my code +
numbered list marker from imgui_md).
- Add MarkdownRenderer._normalize_bullet_delimiters: regex-converts
'* ' markers to '- ' before passing to imgui_md. This works around
the upstream bug in mekhontsev/imgui_md BLOCK_LI where the '*' case
calls ImGui::Bullet() without ImGui::SameLine(), causing the bullet
to render on its own Y with the text on the next Y. The '-' case
uses Text+SameLine which is correct. Cannot fix from Python (we
can't subclass the C++ class) — pre-conversion is the cheapest fix.
Tests:
- test_markdown_table_wrapped.py: updated to assert new behavior
(text_wrapped count == cell count, not header+cell).
- test_markdown_table_columns.py: updated to assert exactly 6
table_next_column calls (cells only, not 9).
- test_markdown_helper_bullets.py: rewrote for new public-API behavior
(imgui_md.render called with the unstripped chunk).
16/16 markdown unit tests pass.