Operational task types (debug, review, refactor, test, explain) now gate
before orchestration in the keyword cascade. Previously, prompts like
"review the orchestration layer" or "refactor the pipeline dispatch"
matched "orchestrat"/"dispatch" and misclassified as TaskOrchestration.
Planning is also moved below the operational types.
Expanded orchestration keywords to cover common intent that the original
four keywords missed: "fan out", "subtask", "delegate to", "spawn elf".
Adds regression tests for false-positive cases and positive tests for new
keywords.
- store: validate session ID against store root to block path traversal in Load/Save
- local: seed turnCount from LocalConfig.TurnCount so resumed sessions keep correct turn count
- main: pass TurnCount from snapshot to LocalConfig on resume
- main: suppress quality.json save when --incognito is active
- main: handle UserConfigDir error in quality save defer instead of silently using wrong path
- test: add TestSessionStore_Load/Save_RejectsPathTraversal
- Add --resume/-r flags; empty = list sessions, ID = restore specific session
- Create SessionStore from config.ProjectRoot() and cfg.Session.MaxKeep
- Wire SessionID and Store into session.NewLocal
- Restore QualityTracker EMA data from ~/.config/gnoma/quality.json at startup
- Persist QualityTracker data to quality.json via defer on process exit
- Add SessionStore field to tui.Config
- Add /resume slash command: lists sessions or restores by ID
- Pass SessionStore to tui.New in main.go
- Update /help text to include /resume
- Add .gnoma/sessions/ to .gitignore
Refactor NewLocal to accept LocalConfig (matching engine/router patterns),
add persistence fields (SessionID, Store, Incognito, Logger), capture
finalState before releasing the lock to avoid data races, and auto-save
a Snapshot after each successful turn when a store is configured.
Add SessionID() to the Session interface and three new tests covering
auto-save, no-store no-panic, and SessionID accessors.
Add custom MarshalJSON/UnmarshalJSON on Content using string type discriminant
("text", "tool_call", "tool_result", "thinking"). Add json tags to Message.
provider/openai:
- Fix doubled tool call args (argsComplete flag): Ollama sends complete
args in the first streaming chunk then repeats them as delta, causing
doubled JSON and 400 errors in elfs
- Handle fs: prefix (gemma4 uses fs:grep instead of fs.grep)
- Add Reasoning field support for Ollama thinking output
cmd/gnoma:
- Early TTY detection so logger is created with correct destination
before any component gets a reference to it (fixes slog WARN bleed
into TUI textarea)
permission:
- Exempt spawn_elfs and agent tools from safety scanner: elf prompt
text may legitimately mention .env/.ssh/credentials patterns and
should not be blocked
tui/app:
- /init retry chain: no-tool-calls → spawn_elfs nudge → write nudge
(ask for plain text output) → TUI fallback write from streamBuf
- looksLikeAgentsMD + extractMarkdownDoc: validate and clean fallback
content before writing (reject refusals, strip narrative preambles)
- Collapse thinking output to 3 lines; ctrl+o to expand (live stream
and committed messages)
- Stream-level filter for model pseudo-tool-call blocks: suppresses
<<tool_code>>...</tool_code>> and <<function_call>>...<tool_call|>
from entering streamBuf across chunk boundaries
- sanitizeAssistantText regex covers both block formats
- Reset streamFilterClose at every turn start
Gap 11 (M6): Fixed context prefix
- Window.PrefixMessages stores immutable docs (CLAUDE.md, .gnoma/GNOMA.md)
- Prefix stripped before compaction, prepended after — survives all compaction
- AllMessages() returns prefix + history for provider requests
- main.go loads CLAUDE.md and .gnoma/GNOMA.md at startup as prefix
Gap 12 (M6): Deferred tool loading
- DeferrableTool optional interface: ShouldDefer() bool
- buildRequest() skips deferred tools until activated
- Tools auto-activate on first model request (activatedTools map)
- agent + spawn_elfs marked as deferrable (large schemas, rarely needed early)
- Saves ~800 tokens per deferred tool per request
Gap 13 (M6): Pre/post compact hooks
- OnPreCompact/OnPostCompact callbacks in WindowConfig
- Called in doCompact() (shared by CompactIfNeeded + ForceCompact)
- M8 hooks system will extend these to full protocol
Engine retries transient errors (429, 5xx) up to 4 times with
1s/2s/4s/8s backoff. Respects Retry-After header from provider.
Batch tool staggers elf spawns by 300ms to avoid rate limit bursts
when all elfs hit the API simultaneously (Mistral's 1 req/s limit).
- Elf tool calls show as 🦉 [elf] <prompt> (not ⚙ [agent])
- Live 2-line progress beneath the elf label showing what the
elf is currently outputting (grey, auto-updated)
- Agent tool forwards elf streaming events via progress channel
- Progress cleared on turn completion
- elfProgressCh wired from agent tool → TUI
internal/elf/:
- BackgroundElf: runs on own goroutine with independent engine,
history, and provider. No shared mutable state.
- Manager: spawns elfs via router.Select() (picks best arm per
task type), tracks lifecycle, WaitAll(), CancelAll(), Cleanup().
internal/tool/agent/:
- Agent tool: LLM can call 'agent' to spawn sub-agents.
Supports task_type hint for routing, wait/background mode.
5-minute timeout, context cancellation propagated.
Concurrent tool execution:
- Read-only tools (fs.read, fs.grep, fs.glob, etc.) execute in
parallel via goroutines.
- Write tools (bash, fs.write, fs.edit) execute sequentially.
- Partition by tool.IsReadOnly().
TUI: /elf command explains how to use sub-agents.
5 elf tests. Exit criteria: parent spawns 3 background elfs on
different providers, collects and synthesizes results.
- ctrl+o toggles between 10-line truncated and full tool output
- Label shows "ctrl+o to expand" (lowercase)
- Fixed: auto permission mode now sticks — config default was
overriding flag default ("default" → "auto" in config defaults)
SummarizeStrategy: calls LLM to condense older messages into a
summary, preserving key decisions, file changes, tool outputs.
Falls back to truncation on failure. Keeps 6 recent messages.
Tool result persistence: outputs >50K chars saved to disk at
.gnoma/sessions/tool-results/{id}.txt with 2K preview inline.
TUI: /compact command for manual compaction, /clear now resets
engine history. Summarize strategy used by default (with
truncation fallback).
- ❯ flush left for user input, continuation lines indented 2 spaces
- ◆ purple icon for AI responses, continuation indented
- User multiline messages: ❯ first line, indented rest
- Tool output: indented under parent
- System messages: • prefix with multiline indent
- Input area: no extra padding, ❯ at column 0
Switch from textinput to textarea bubble:
- Enter submits message
- Shift+Enter / Ctrl+J inserts newline
- Input area auto-expands from 1 to 10 lines based on content
- Line numbers hidden, prompt preserved