Contract Readiness
Contract Readiness
Section titled “Contract Readiness”Building Gormes now treats docs/content/building-gormes/architecture_plan/progress.json
as the canonical roadmap. Priority roadmap items can carry optional contract
metadata:
contractcontract_statustrust_classdegraded_modefixturesource_refsblocked_byunblocksacceptance
Those fields turn upstream study into execution rules. A subsystem is not ready for implementation just because a donor file exists. It is ready when the contract is named, the allowed caller class is explicit, degraded mode is operator-visible, and a local fixture proves compatibility.
Completion-Ready Row Gate
Section titled “Completion-Ready Row Gate”The Completion Plan raises the bar for every executable row. A row may enter the builder queue only when a worker can start TDD without rediscovering architecture:
| Field | Completion expectation |
|---|---|
contract | Names one observable Hermes, Honcho, Gormes-owned, or Gormes-owned behavior. |
execution_owner | Names the package/system that owns the result: gateway, provider, memory, tools, docs, planner, builder, etc. |
slice_size | small, medium, or large for executable work; umbrella remains inventory only. |
ready_when | Lists concrete prerequisites a builder can check locally. |
not_ready_when | Lists concrete blockers that prevent selection. |
write_scope | Names exact files/directories the builder may edit. |
source_refs | Points to upstream and current Gormes evidence. |
test_commands | Proves the row through hermetic tests, or no_test_required explains why no focused executable proof exists. |
acceptance | States user/operator-visible behavior, not implementation steps. |
done_signal | States what the builder reports when the row is complete. |
Rows that fail this gate should go through gormes-planner before
gormes-builder. Rows with uncertain upstream coverage should go through
gormes-parity-auditor first. Rows with uncertain package/API shape should go
through gormes-interface-designer first.
Completion Skill Routing
Section titled “Completion Skill Routing”| Problem found while reviewing a row | Use |
|---|---|
| Upstream behavior is unclear | gormes-parity-auditor |
| The row is too broad or missing handoff fields | gormes-planner |
| The public interface is unclear | gormes-interface-designer |
| The row is ready to implement | gormes-builder + gormes-tdd-slice |
Current Contract Rows
Section titled “Current Contract Rows”| Phase | Progress item | Contract status | Owner | Size | Trust class | Fixture | Degraded mode |
|---|---|---|---|---|---|---|---|
| 1 / 1.C | Orchestrator failure-row stabilization for 4-8 workers — Worker verification and failure-taxonomy contract | validated | orchestrator | large | operator, system | scripts/orchestrator/tests/unit/{failures,soft-success}.bats | The orchestrator records precise failure reasons, poisoned-task thresholds, soft-success decisions, and original exit codes instead of collapsing failures into one generic status. |
| 1 / 1.C | Soft-success-nonzero bats coverage — Soft-success nonzero recovery guard | validated | orchestrator | small | operator, system | scripts/orchestrator/tests/unit/soft-success.bats | When the recovery guard refuses a non-zero exit, the worker state keeps the original exit reason and does not promote the run. |
| 1 / 1.C | Planner wrapper/test consistency closeout — Planner wrapper compatibility contract | validated | orchestrator | small | operator, system | internal/plannerloopagent_test.go | Planner callers using the old wrapper names continue to exec the renamed planner-tasks manager instead of failing before a run can start. |
| 1 / 1.C | Autoloop row health and quarantine contract — Autoloop records per-row health, failure categories, quarantine state, stale-spec recovery, and backend penalties without letting planner writes mutate runtime-owned health blocks | validated | orchestrator | medium | operator, system | internal/progress/health_test.go and internal/builderloop/health_writer_test.go | Rows with repeated worker failures are skipped or demoted with visible health/quarantine evidence instead of being retried indefinitely. |
| 1 / 1.C | Planner self-healing verdict loop — Architecture planner consumes autoloop health/ledger outcomes, stamps planner-owned verdicts, retries validation-safe regenerations, and escalates rows that stay failing after repeated reshapes | validated | orchestrator | medium | operator, system | internal/plannerloop/lifecycle_test.go | Rows that resist planner reshapes are marked needs_human and removed from default autoloop selection instead of silently cycling. |
| 1 / 1.C | Planner divergence and provenance awareness — Architecture planner distinguishes upstream-led, converged, and Gormes-owned surfaces using per-row provenance, subphase drift state, and implementation inventory from repo paths | validated | orchestrator | medium | operator, system | internal/plannerloop/divergence_lifecycle_test.go | Planner status reports porting/converged/owned buckets and recent promotions so Gormes-original surfaces stop receiving fabricated upstream refs. |
| 1 / 1.C | Watchdog checkpoint coalescing — Pure helper internal/builderloop/watchdog_coalesce.go exposes type Decision string with constants DecisionFirst=“first”, DecisionAmend=“amend”, DecisionNoop=“noop”; type CheckpointState struct{LastCheckpointAt time.Time; LastSubject string; WindowID string}; type CoalesceConfig struct{WindowSeconds int; Dirty bool; NextWindowID func() string} and one function DecideCheckpoint(now time.Time, st CheckpointState, cfg CoalesceConfig) (Decision, CheckpointState). Window math: windowSeconds <= 0 falls back to 600. Algorithm: when cfg.Dirty is false, return DecisionNoop and the input state unchanged. When cfg.Dirty is true and (st.WindowID == "" OR now.Sub(st.LastCheckpointAt) >= window), return DecisionFirst with state {LastCheckpointAt: now, LastSubject: st.LastSubject (caller fills), WindowID: cfg.NextWindowID()}. Otherwise (cfg.Dirty and inside the window) return DecisionAmend with state {LastCheckpointAt: now, LastSubject: st.LastSubject, WindowID: st.WindowID} (preserve the existing WindowID). No git invocation, no shell-script change, no live filesystem mutation in this slice; cfg.NextWindowID is the only source of new IDs and is supplied by the caller (typically a small monotonic counter or a uuid stub in tests). | validated | orchestrator | small | operator, system | internal/builderloop/watchdog_coalesce_test.go | Watchdog status reports checkpoint_coalesce_active, checkpoint_coalesce_window_seconds, and the existing dirty-worktree checkpoint commit ID instead of emitting a fresh commit on every tick. |
| 1 / 1.C | PR-intake idle backoff — Pure helper internal/builderloop/pr_intake_backoff.go exposes type BackoffState struct{ConsecutiveEmpty int; SuppressUntil time.Time; Threshold int; Baseline, Idle time.Duration} and methods (s BackoffState) ShouldPoll(now time.Time) bool and (s BackoffState) RecordResult(now time.Time, listed int) BackoffState. After Threshold consecutive listed=0 results, ShouldPoll returns false until now >= SuppressUntil; any listed > 0 result resets ConsecutiveEmpty and clears SuppressUntil. No GitHub API calls, no goroutines, no live clock in this slice. | validated | orchestrator | small | system | internal/builderloop/pr_intake_backoff_test.go | pr_intake_started/completed events carry an idle_backoff field with the active interval and the consecutive-empty count so dashboards can distinguish “intake was suppressed” from “intake ran and found nothing”. |
| 1 / 1.C | Watchdog dead-process vs slow-progress separation — TDD packet for a missing pure helper; create exactly two files and do not wire them into the watchdog loop. STEP 1: cd into the repo root and run ls internal/builderloop/watchdog_state*.go — both files must be absent before the worker writes them. STEP 2: write internal/builderloop/watchdog_state_test.go in package builderloop with one TestDiagnose function holding a t.Run-driven table. The subtests are named exactly zero_pid_dead, pid_not_live_after_dead_threshold, live_after_slow_threshold, dead_wins_when_both_thresholds_fire, healthy_recent_live, and pid_live_silent_for_a_year_is_slow_not_dead. Use a fixed time anchor now := time.Date(2026,4,26,18,0,0,0,time.UTC) and pass deadAfter=120time.Second, slowAfter=600time.Second. STEP 3: write internal/builderloop/watchdog_state.go exposing type Verdict string with constants VerdictHealthy=“healthy”, VerdictSlow=“slow”, VerdictDead=“dead”; type WorkerVitals struct{PID int; LastCommitAt time.Time; PIDIsLive bool}; and func Diagnose(now time.Time, v WorkerVitals, deadAfter, slowAfter time.Duration) Verdict. Verdict precedence (evaluate in this order): if v.PID==0 return VerdictDead; let elapsed := now.Sub(v.LastCommitAt); if !v.PIDIsLive && elapsed>=deadAfter return VerdictDead; if v.PIDIsLive && elapsed>=slowAfter return VerdictSlow; otherwise return VerdictHealthy. STEP 4: the helper uses only caller-injected now and PIDIsLive — no os.FindProcess, signal delivery, time.Now, goroutines, watchdog wiring, config validation, or imports beyond time. | validated | orchestrator | small | operator, system | internal/builderloop/watchdog_state_test.go (new file)::TestDiagnose/zero_pid_dead+dead_wins_when_both_thresholds_fire | Watchdog status reports worker_state ∈ {alive_progressing, alive_silent, dead, unknown} and the threshold each one tripped; record_run_health carries the worker_state and which threshold (dead_after_seconds, silent_after_seconds) fired. |
| 1 / 1.C | Builder-loop self-improvement vs user-feature ratio metric — TDD packet for a missing pure helper; create exactly two files and do not parse the live ledger. STEP 1: cd into the repo root and run ls internal/builderloop/ship_ratio*.go — both files must be absent before the worker writes them. STEP 2: write internal/builderloop/ship_ratio_test.go in package builderloop with two top-level test functions. TestClassifySubphase uses a t.Run-driven table with named subtests bare_1c, path_1c, bare_5o, path_5o, control_plane_prefix, user_feature_4a, user_feature_4h, user_feature_6a, user_feature_7b, blank_input, whitespace_only, and unknown_99x; each asserts ClassifySubphase returns the expected RowKind. TestComputeShipRatio uses a fixed time anchor now := time.Date(2026,4,26,18,0,0,0,time.UTC) and a 24h window; subtests inclusive_window_start, future_event_excluded, per_kind_counts, and zero_events_returns_zero_total. STEP 3: write internal/builderloop/ship_ratio.go exposing type RowKind string with constants RowKindSelfImprovement=“self_improvement”, RowKindUserFeature=“user_feature”, RowKindUnclassified=“unclassified”; type ShippedRowEvent struct{SubphaseID string; ShippedAt time.Time}; type ShipRatio struct{SelfImprovement int; UserFeature int; Unclassified int; Total int}; func ClassifySubphase(subphaseID string) RowKind; and func ComputeShipRatio(events []ShippedRowEvent, window time.Duration, now time.Time) ShipRatio. ClassifySubphase trims spaces, splits on ”/” and inspects the second segment if the first is a phase number (“1”,“4”,“5”,“6”,“7”), and treats bare “1.C”, “5.O”, and any string with prefix “control-plane/” as RowKindSelfImprovement. “4.A”, “4.H”, “6.”, “7.” map to RowKindUserFeature; everything else (including blank input) returns RowKindUnclassified. ComputeShipRatio is a pure aggregator over the provided slice. STEP 4: no file I/O, no ledger parsing, no record_run_health emission, no goroutines, no live clock; imports only strings and time plus testing in the test file. | validated | orchestrator | small | system | internal/builderloop/ship_ratio_test.go (new file)::TestClassifySubphase+TestComputeShipRatio/no-ledger-parser | When the ship_ratio cannot be computed (insufficient history, classification ambiguous), record_run_health carries ship_ratio=null and reports ship_ratio_evidence with the reason instead of fabricating zero. |
| 1 / 1.D | Skill control-plane docs and Hugo navigation closeout — AGENTS.md, CLAUDE.md, building-gormes docs, generated progress schema, and Hugo progress data route planning and building through repo-local skills and never instruct agents to run or recreate cmd/planner-loop or cmd/builder-loop. | validated | docs | small | operator, system | docs/content/building-gormes/builder-loop/builder-loop-handoff.md + docs/content/building-gormes/builder-loop/progress-schema.md | Progress validation, docs tests, and stale-reference scans fail the handoff if active documentation drifts back to loop-binary execution or side queues. |
| 1 / 1.D | Skill-manager selection matrix hardening — gormes-skill-manager deterministically routes each substantial Gormes pass to exactly one primary repo-local skill or a bounded skill chain, and records when a repeated workflow requires a new skill instead of ad hoc instructions. | validated | skills | small | operator, system | docs/development-skills/gormes-skill-manager/SKILL.md | If the manager cannot route the pass, it emits skill_route_unclear or new_skill_needed with the missing capability and sends the work back to gormes-planner instead of silently proceeding skill-less. |
| 1 / 1.D | Skill-pack coverage audit for Hermes-in-Go completion — The repo-local Gormes skills cover every remaining completion lane: upstream parity mapping, row planning, interface design, implementation, TDD, e2e verification, docs/Hugo updates, release/web work, and skill creation, with missing repeated workflows converted into new skill rows. | validated | skills | medium | operator, system | docs/development-skills/gormes-skill-manager/references/skill-routing.md::Completion Lane Coverage | Uncovered work types are reported as skill_gap rows with lane, missing input, expected output, and proposed skill name instead of being handled by free-form prompts. |
| 1 / 1.D | Canonical development-skills directory and loader symlinks — All repo-local skills live canonically under docs/development-skills/ | validated | skills | small | operator, system | docs/development-skills/README.md | If a loader path is not a symlink or points to a missing skill, gormes-skill-manager reports skill_loader_drift and repairs the symlink instead of copying files into another source of truth. |
| 1 / 1.D | External review feedback ingestion for planner rows — gormes-planner and gormes-review-loop define a bounded, source-backed workflow for ingesting external review feedback such as Greptile/Grep-style PR reviews, GitHub review comments, CI annotations, and static-analysis findings into existing progress rows or one new builder-ready row without creating a side backlog. | validated | orchestrator | small | operator, system | docs/development-skills/gormes-planner/SKILL.md::external review ingestion + docs/development-skills/gormes-review-loop/SKILL.md::target gate capture | If exact review evidence is unavailable, agents must stop and ask for reviewer text, PR/check URLs, file paths, command output, or copied review logs before shaping rows. |
| 1 / 1.E | Shared Bubble Tea wizard step chassis under internal/tui/wizard — internal/tui/wizard exposes a small Wizard interface (Run(ctx, steps…) (Result, error)) that drives a sequence of Step values — Text, MultiLine, Password, Pick (single-select), Confirm — under a Bubble Tea program. The chassis owns: (a) TTY detection (refuse to start when stdin is not a terminal, return a typed ErrRequiresTTY so callers emit *_requires_tty evidence), (b) bypass-when-fully-specified (callers compose ‘if all inputs already supplied via flags, do not run the wizard’), (c) Ctrl-C / escape returning ErrAbort, (d) golden-snapshot testability via charmbracelet/x/exp/teatest. The chassis must not import any cmd/gormes package; admin-TUI screens (1.E.3+) compose it from their screen models, and stand-alone command callers can compose it independently if needed. | validated | tools | medium | operator | internal/tui/wizard/wizard_test.go teatest scripts | Without the chassis, every admin-TUI screen would reinvent step UI, TTY detection, and golden testing; the admin-shell rows (1.E.2-1.E.5) cannot land cleanly. |
| 1 / 1.E | Unified admin TUI shell with tab navigation — cmd/gormes/admin.go adds a new top-level gormes admin command that opens a Bubble Tea program in internal/tui/admin. The shell owns: (a) a top tab bar listing registered Screen titles, (b) a status bar showing the active screen + global hints, (c) a keybinding map (tab/shift+tab to cycle screens, digit keys 1..9 for direct jump, ? for help, q/ctrl+c to quit), (d) a Screen interface (Title(), Init() tea.Cmd, Update(tea.Msg) (Screen, tea.Cmd), View() string, ShortHelp() []KeyHelp), (e) registry for ordered screen insertion, (f) TTY detection so non-TTY invocations exit with admin_tui_requires_tty evidence instead of hanging. At this slice the shell ships with one Welcome screen as proof; the three concrete screens land in 1.E.3-1.E.5. | validated | tools | medium | operator | internal/tui/admin/shell_test.go teatest scripts + cmd/gormes/admin_test.go entry point test | Without the shell, every admin screen would have to ship its own keybindings/navigation/quit handling; 1.E.3-1.E.5 cannot land cleanly. Operators stuck on gormes setup, doctor readiness output, and per-subcommand flows cannot see config drift or jump to chat in one place. |
| 1 / 1.E | Admin TUI: Setup health screen with missing-config callouts — internal/tui/admin/screen_health.go ships a Setup screen (the default-focused tab) that lists health items derived from internal/doctor + config presence + the dynamic agent registry. Each item renders as one row with: severity icon (✓/!/✗), short title, one-line detail, and optional [Fix] action key. Items include: provider configured, auth credentials present, agent template seeded, default agent created, Goncho memory.db reachable, gateway tokens (Telegram/Discord/Slack), profile initialized. Pressing Enter on a row with a [Fix] action launches the matching 1.E.1 wizard inline (e.g., Fix on ‘no provider’ opens a provider+key wizard; result writes through the same auth/config call as gormes auth add / gormes setup provider). The screen refreshes on focus and after any successful Fix. Read-only checks (memory.db reachable) have no Fix action. No new persistence surface — the screen reads existing config and registry state. | validated | tools | medium | operator | internal/tui/admin/health_test.go teatest + tempdir GORMES_HOME | Without the health screen, operators rely on gormes doctor --offline output and CLI exit codes to discover misconfiguration; the user’s explicit ‘show missing config’ anchor is unmet. |
| 1 / 1.E | Admin TUI: Chat tab with keybinding to jump in from any screen — internal/tui/admin/screen_chat.go ships a Chat screen registered as a tab in the admin shell, plus a global keybinding (c from any other screen) that switches focus to it. The screen renders: a scroll buffer of messages from the active session, a single-line input (or expanding textarea via bubbles/textarea), and a status line showing the current agent. Sending a message routes through the existing one-shot provider call path used by gormes chat -q; responses stream into the scroll buffer. The active agent defaults to the static config default; an operator can press a to swap to a runtime-spawned agent from the 2.H.1 registry. Chat history is in-memory for the admin TUI session (not yet persisted to Goncho session — that follow-up row will land when admin TUI scope justifies it). The admin Chat tab is intentionally small: full session history, tool calls, slash commands, and the rich chrome stay in the dedicated gormes TUI. | validated | tools | medium | operator | internal/tui/admin/chat_test.go teatest fixtures + fake provider seam | Without the chat tab, every admin TUI session forces operators to leave the TUI (exit, run gormes) to test a configured provider; the user’s explicit ‘open chat easily’ anchor is unmet. |
| 1 / 1.E | Admin TUI: Agents screen wired to the 2.H dynamic registry — internal/tui/admin/screen_agents.go ships an Agents tab listing DynamicAgentRegistry.List output. Keybindings: ‘n’ opens a Spawn wizard (Text name → MultiLine persona → Confirm) that writes through goncho.DynamicAgentRegistry.Create; ‘b’ opens a Bind wizard (Pick agent → Pick channel → Pick peer kind → Text peer ID → Text optional thread → Confirm) that writes through Bind; ‘u’ opens an Unbind confirm dialog for the currently-selected binding; ‘i’ opens an Inspect overlay showing the resolved agent for a peer tuple. The screen renders an empty-state CTA (‘press n to spawn your first agent’) when the registry is empty. All wizards compose 1.E.1 Step values; no new persistence surface is added. | validated | tools | medium | operator | internal/tui/admin/agents_test.go teatest fixtures + tempdir Goncho | - |
| 1 / 1.E | Admin TUI: Commands catalog over the root CLI tree — cmd/gormes/admin.go walks the live Cobra root command tree at gormes admin startup and passes a deterministic list of non-hidden commands into internal/tui/admin. internal/tui/admin/screen_commands.go registers a Commands tab after Setup, Chat, and Agents. The tab renders command count, command use lines, short descriptions, selection movement, and a selected-command detail pane. The screen is read-only: operators discover commands in the unified TUI, then run mutating commands explicitly from the shell. The dedicated root gormes chat TUI remains unchanged. | validated | tools | small | operator | internal/tui/admin/commands_test.go teatest fixture + cmd/gormes/admin_test.go command-tree catalog assertion | Without the Commands tab, the admin TUI covers setup, chat, and agents but still forces operators back to gormes --help or scattered subcommand help to discover the rest of the CLI surface. |
| 1 / 1.E | Admin TUI: Safe command execution from the Commands tab — internal/tui/admin/screen_commands.go accepts an injected CommandRunner and marks only allowlisted CommandEntry values as Runnable. Pressing Enter on a runnable entry runs it through the injected runner and renders stdout/stderr/error output inline; pressing Enter on non-runnable entries renders a refusal that tells the operator to run mutating commands from the shell. cmd/gormes/admin.go owns the allowlist and production runner: doctor runs as gormes doctor --offline, auth status resolves the configured provider, gateway status runs directly, and kanban list runs directly through a fresh Cobra root with buffered stdio. internal/tui/admin still does not import Cobra or command implementations. | validated | tools | small | operator | internal/tui/admin/commands_test.go teatest runner fixtures + cmd/gormes/admin_test.go safe allowlist and runner tests | Without safe inline execution, the Commands tab is only discoverability; operators still need to leave gormes admin for routine read-only checks such as doctor, auth status, gateway status, and kanban list. |
| 1 / 1.E | Admin TUI: Searchable Commands palette — internal/tui/admin/screen_commands.go lets operators press / in the Commands tab, type a query, filter the live command catalog by path/use/description/run label, move through the filtered result set with arrow/home/end keys, press Enter to run the selected safe command through the existing injected runner, and press Escape to clear search. While search text entry is active, the shell delivers focused keypresses to the Commands screen before global rune shortcuts so queries such as codex do not jump tabs or quit the admin TUI. Ctrl-C and tab navigation remain global. | validated | tools | small | operator | internal/tui/admin/commands_test.go teatest search fixtures | Without search, the Commands tab can render and safely run commands, but operators still have to scroll through the whole CLI tree to find routine commands. |
| 1 / 5.X | Gormes Termux Runtime Compatibility — Gormes treats no-root Termux on Android arm64/aarch64 as a first-class runtime for PC-like operator workflows: same gormes command names and flags, same config/auth/model/session/Goncho/gateway surfaces, repo-root install.sh release-binary install into $PREFIX/bin/gormes, and Android-specific doctor/degraded-mode guidance. Local Docker, GPU/local LLM, heavy browser automation, and large builds are explicitly delegated or remote/degraded rather than required for Termux compatibility. | validated | orchestrator | umbrella | operator, gateway, system | Phase 5.X child rows plus real-device smoke evidence | Without a named milestone, Termux work stays split across release, installer, doctor, gateway, storage, and docs rows; public claims can drift ahead of real-device proof. |
| 1 / 5.X | Termux runtime doctor check — gormes doctor --offline --json detects Termux via TERMUX_VERSION or standard com.termux PREFIX/HOME paths and emits a Termux runtime check only in that environment. The check proves desktop-like command-path readiness through $PREFIX/bin on PATH, reports optional termux-api wake-lock/notification command availability, and warns about Android lifecycle constraints with tmux/termux-wake-lock/battery-optimization guidance. It must not require live Android APIs, network access, root, or a real Termux device in unit tests. | validated | orchestrator | small | operator, gateway, system | internal/doctor/termux_test.go; cmd/gormes/doctor_runE_test.go::TestDoctorCommand_JSONIncludesTermuxRuntimeWhenDetected | Non-Termux hosts omit the check from command output. Termux hosts missing $PREFIX/bin on PATH or optional termux-api commands receive WARN evidence, not failure. Android lifecycle caveats always remain WARN because background survival is best effort. |
| 1 / 5.X | Termux install and release smoke guide — Document and test the official Termux install path around the repository-root install.sh: prebuilt android-arm64 release binary first, published to $PREFIX/bin/gormes, with source build only as fallback or contributor path. The guide must include exact no-root Termux commands, minimum packages, doctor/config smoke commands, and the boundary that install.sh stays in the repo root. | validated | docs | small | operator, system | webpages/docs/install/termux_guide_test.go::TestLinuxMacInstallGuideDocumentsTermuxReleaseFirstPath; internal/installtest/install_method_test.go::TestInstall_DryRunTermuxArm64_PrefersAndroidReleaseAsset; tests/install/e2e.sh::case_termux_detection_drives_plan; tests/install/e2e.sh::case_termux_local_install_doctor_smoke | Unsupported Termux architectures or missing release assets fall back to source build with explicit no-published-asset evidence. The guide must not make source build the primary product UX. |
| 1 / 5.X | Termux storage and path safety audit — Audit and test Gormes path selection under synthetic Termux env so config, dotenv secrets, sessions, gateway state, SQLite/Goncho, browser temp dirs, and generated files land only under configured GORMES_HOME/XDG/HOME locations while install publication remains $PREFIX/bin/gormes. No runtime code may hardcode desktop workspace paths such as /home/xel or workspace-mineru. | validated | orchestrator | medium | operator, system | internal/config Termux path fixtures, cmd/gormes doctor/config/goncho/registry path smoke fixtures, and internal/installtest Termux dry-run publication fixture | If an Android path is unavailable, commands must return typed path/readiness warnings instead of writing into unexpected shared storage or desktop-only paths. |
| 1 / 5.X | Termux gateway foreground tmux lifecycle — Gateway lifecycle commands and docs present a Termux-specific foreground/tmux model: Telegram/Discord/Slack gateways are supported from a foreground shell or tmux session, systemd/Windows service assumptions are not advertised, and doctor/status guidance names termux-wake-lock plus Android battery settings as best-effort survival aids. The implementation must preserve the same gateway command names and JSON contracts as desktop Linux. | validated | gateway | medium | operator, gateway, system | cmd/gormes gateway/doctor fixtures under synthetic Termux env | If Termux lacks tmux or termux-wake-lock, gateway startup remains possible but doctor/status emits WARN guidance. Android process death is treated as recoverable operator environment behavior, not a Gormes crash. |
| 1 / 5.X | Termux notification bridge via termux-api — Gormes has an optional Termux notification adapter that shells out to termux-notification only when Termux and the command are detected. Gateway status reports notification availability through this adapter, while non-Termux hosts and Termux hosts without Termux:API degrade to structured no-op/WARN evidence. The adapter redacts secrets, bounds title/body text, and never makes termux-api a hard dependency. | validated | gateway | small | operator, gateway, system | internal/tools/termux_notification_test.go and cmd/gormes/gateway_termux_lifecycle_test.go with fake exec/path fixtures | Missing termux-notification or missing Termux:API app returns optional_notification_unavailable evidence; Gormes continues normally without Android notifications. |
| 1 / 5.X | Termux real-device smoke evidence — Capture a dated real-device no-root Android Termux smoke record for the current release: install via repo-root install.sh release asset, run gormes version, gormes doctor —offline —json, gormes config check, initialize SQLite/Goncho state, and run a provider-backed gormes chat -q “hello from Termux” when a test credential is available. The evidence must record Android/Termux versions, device arch, install method, and any caveats without leaking credentials. | validated | docs | small | operator, system | webpages/docs/content/install/termux-smoke.md or release evidence note | If no provider credential is available, record provider-backed oneshot as skipped with credential-unavailable evidence; local install/version/doctor/config/Goncho smoke remains required. |
| 1 / 5.X | Termux remote execution guidance — Document and, where useful, add setup/status guidance for using Termux Gormes as the mobile operator/controller while SSHing to stronger machines for heavy builds, Docker, local browser automation, and GPU/local model inference. The guidance must preserve PC-like local Gormes CLI behavior while making remote execution the credible path for workstation/server workloads. | validated | docs | small | operator, system | webpages/docs/content/install/ Termux remote-execution docs | If no remote host is configured, Termux remains a local CLI/TUI/gateway runtime and doctor/docs explain which heavy workloads are out of local scope. |
| 2 / 2.A | Kernel tool loop — The kernel executes Hermes-style multi-round tool_calls through the native Go registry, defaults to the upstream Hermes 90-turn tool iteration budget, and when an explicit budget is exhausted it asks the model for one extra toolless summary instead of surfacing a raw tool iteration limit exceeded error to the channel user. | validated | gateway | small | gateway, operator, child-agent | internal/kernel/tools_test.go | If the summary request cannot be opened or returns no visible content, the kernel finalizes a bounded assistant fallback message rather than emitting a raw PhaseFailed budget error. |
| 2 / 2.A | Coding-agent delegation tooling (codex/claude-code/opencode) — Workspace-guarded delegation of coding tasks to external agent binaries (codex, claude-code, opencode) with structured request/result, mode/edit-permission/timeout controls, and git-diff capture so Gormes can compare workers and present diffs to the operator. | validated | tools | umbrella | operator, system | internal/codingagents | Without the delegation surface, Gormes operators cannot hand a coding task to codex/claude-code/opencode from within Gormes and must shell out manually; doctor and tool registry report no coding-agent workers. |
| 2 / 2.A | Coding-agent delegation: Phase 1 scaffold (internal/codingagents) — Shared internal/codingagents package providing the CodingAgent interface, CodingAgentRequest/Result, mode constants, binary availability detection, workspace guard with default deny list, git snapshot/diff helper, and prompt wrapper. No tools are registered in this slice; adapters and registry exposure land in later phases. | validated | tools | medium | operator, system | internal/codingagents | Without the scaffold, later phases cannot compile codex/claude-code/opencode adapters against a shared contract; doctor cannot probe coding-agent binaries. |
| 2 / 2.B.1 | Telegram important notification default — Port Hermes’ current Telegram notification-mode contract into the Go channel: intermediate placeholder/progress sends are silent by default via Telegram disable_notification, ordinary final sends and approval prompts still notify, and operators can opt back into legacy all-notifications behavior through Gormes-native config/env plus the Hermes env alias. | validated | gateway | small | gateway, operator | internal/channels/telegram/thread_send_test.go + internal/channels/telegram/approval_buttons_test.go + internal/config/config_test.go | Without the silent-default contract, long Telegram agent runs can trigger push notifications for placeholder/progress chatter while final answers and approval prompts are indistinguishable from intermediate noise. |
| 2 / 2.B.3 | Slack CommandRegistry parser wiring — Slack message and slash-command ingress routes through the shared gateway CommandRegistry instead of maintaining adapter-local slash parsing | validated | gateway | small | gateway, operator | internal/slack/bot_test.go | Unknown Slack slash commands return the shared gateway unknown-command behavior while recognized help/new/stop commands keep Slack-specific ack and thread reply semantics. |
| 2 / 2.B.3 | Slack gateway.Channel adapter shim — Slack Socket Mode can run under the shared gateway.Manager through a narrow gateway.Channel shim without replacing the existing Slack client or reply fixtures | validated | gateway | small | gateway, operator | internal/slack/channel_shim_test.go | If the shim cannot start or send, gateway status reports Slack channel startup/send degradation while the standalone Slack bot tests remain unchanged. |
| 2 / 2.B.3 | Slack config + cmd/gormes gateway registration — Slack config and cmd/gormes gateway registration use the shared gateway.Manager lifecycle after the Slack Channel shim is fixture-locked | validated | gateway | small | operator, gateway | cmd/gormes/gateway_slack_test.go | gormes gateway and doctor report missing Slack bot/app tokens, disabled Slack config, or Slack startup failure without affecting Telegram or Discord registration. |
| 2 / 2.B.3 | Slack env-token enabled-state preservation — Slack gateway config treats SLACK_BOT_TOKEN as enabling Slack unless config.yaml explicitly sets slack.enabled=false or platforms.slack.enabled=false; top-level Slack settings such as require_mention, strict_mention, free_response_channels, channel prompts, and reply policy must not create a disabled Slack platform that shadows the env token | validated | gateway | small | operator, gateway | internal/config/slack_env_token_test.go::TestSlackEnvTokenEnabledState_* | Gateway status reports slack_env_token_shadowed only when an explicit enabled=false config disables Slack while preserving the redacted token source for non-gateway Slack tool use. |
| 2 / 2.B.3 | Slack app manifest App Home and private-channel scopes — gormes slack manifest generates a Hermes-compatible Slack app manifest without contacting Slack: full manifests include writable App Home DMs, groups:read, assistant view/events, bot scopes, and registry-derived slash commands; --slashes-only emits only the slash-command array for manual merges; --write[=PATH] writes atomically to an operator-chosen path or $GORMES_HOME/slack-manifest.json. | validated | gateway | small | operator, gateway | cmd/gormes/slack_manifest_test.go | If the command is unavailable or cannot write the target file, operators receive bounded CLI errors with no Slack token, app token, or live workspace access. The row does not start Socket Mode or call Slack APIs. |
| 2 / 2.B.4 | WhatsApp identity resolution + self-chat guard — WhatsApp bot identity, self-chat suppression, and peer mapping stay deterministic across bridge and native runtimes | validated | gateway | small | gateway, operator | internal/channels/whatsapp/testdata/identity_contract.json | Gateway status reports unresolved WhatsApp bot identity instead of accepting self-chat loops or ambiguous peer mappings. |
| 2 / 2.B.4 | WhatsApp outbound pairing gate + raw peer mapping — WhatsApp outbound sends are gated by pairing state and map normalized gateway chat IDs back to bridge/native raw peers | validated | gateway | small | gateway, operator | internal/channels/whatsapp/send_contract_test.go | Gateway status reports unpaired or unresolved WhatsApp targets before attempting a transport send. |
| 2 / 2.B.4 | WhatsApp reconnect backoff + send retry policy — WhatsApp send failures use bounded reconnect/backoff semantics without duplicating terminal replies or hiding unresolved runtime state | validated | gateway | small | gateway, operator | internal/channels/whatsapp/reconnect_contract_test.go | Gateway status exposes reconnect pending/exhausted state and the original send target when retries cannot recover. |
| 2 / 2.B.5 | Gateway manual reset session-boundary hooks — Gormes ports Hermes’ explicit session-boundary hook behavior for channel reset commands: /new and /reset fire an old-session finalize hook before resetting, then fire a new-session reset hook after the replacement session is established, preserve hook ordering evidence, continue reset success when a hook fails, and keep expiry-finalization hooks separate from manual reset hooks. | validated | gateway | small | gateway, system, operator | internal/gateway/session_boundary_hooks_test.go | Hook failures emit redacted session_boundary_hook_failed evidence and never block the operator-visible reset acknowledgement; missing hooks degrade to no-op without changing session map clearing or active-turn refusal behavior. |
| 2 / 2.B.5 | Gateway session reset notification parity — Gormes ports Hermes’ automatic session-reset read model and notification policy for channel sessions: idle, daily, and suspended reset decisions carry a stable reason string; replacement sessions record was_auto_reset, auto_reset_reason, and reset_had_activity without being treated as explicit fresh resets; operator notifications respect SessionResetPolicy.notify and notify_exclude_platforms defaults; and API/webhook-style platforms are excluded from unsolicited reset messages. | validated | gateway | small | operator, gateway, system | internal/gateway/session_auto_reset_notify_test.go | Expired, suspended, no-activity, notification-disabled, excluded-platform, and notification-send-failed cases return auto_reset_* evidence with session ids, reason, platform, and activity booleans only; message text, prompt content, provider credentials, and raw channel payloads remain redacted. |
| 2 / 2.B.5 | Gateway slash-confirm session-boundary cleanup — Gormes ports Hermes’ session-boundary cleanup for pending slash-command confirmations: a channel-neutral pending confirmation store can register exactly one confirmable slash action per gateway session, resolve or clear it once, and successful /new or /reset clears only the target session’s pending confirmation without running the action or touching other sessions. | validated | gateway | small | gateway, operator, system | internal/gateway/slash_confirm_test.go | A nil or empty confirmation store is a no-op; reset refusal during an active turn leaves pending confirmations intact; stale, mismatched, or already-resolved confirmations fail closed with typed errors and no handler execution. |
| 2 / 2.B.5 | Hermes live-turn prompt assembly parity (channel-neutral) — Live gateway turns for every channel (Telegram, Slack, WhatsApp, BlueBubbles, Discord, Sage Goblin, future) assemble a system-prompt header that mirrors Hermes’ run_agent.py / prompt_builder.py block order before the user message: (1) SOUL.md identity, (2) tool/memory guidance, (3) USER.md / MEMORY.md durable user context, (4) skills guidance, (5) AGENTS.md / .hermes.md / project context, (6) timestamp + model + provider metadata, (7) platform/session context. Assembly happens once inside gateway.Manager at the existing BuildSessionContextPrompt call site so all channel adapters inherit it without channel-specific code. The umbrella tracks the seven-block contract; child rows deliver each block. | validated | gateway | umbrella | gateway, system | internal/gateway/live_turn_prompt_test.go | Complete child rows now inject the channel-neutral live-turn prompt header before the user message. Missing SOUL.md, project context, USER.md, MEMORY.md, metadata seams, or self-help guidance gates still degrade by eliding only that block and preserving the platform/session block. Threat-pattern scans continue to block raw prompt-injection content before provider submission. |
| 2 / 2.B.5 | Live-turn SOUL.md and project context wiring (channel-neutral) — gateway.Manager invokes internal/llm.BuildContextFilesPrompt once per inbound submit and prepends the rendered SOUL.md + project context block (HERMES.md / .hermes.md / AGENTS.md / CLAUDE.md / .cursorrules per BuildContextFilesPrompt’s existing first-match-wins precedence) to the existing BuildSessionContextPrompt block before the kernel.PlatformEvent.SessionContext field is set. The wiring lives inside gateway.Manager — never inside any channel adapter — so Telegram, Slack, WhatsApp, BlueBubbles, Discord, Sage Goblin, and any future channel inherit the block automatically. Profile dir resolution uses config.GormesHome() in production with a fakeable seam for tests; CWD resolution uses the manager’s configured working directory with a fakeable seam. ContextFilesReport evidence is logged via gateway telemetry but never sent on the channel. | validated | gateway | small | gateway, system | internal/gateway/live_turn_prompt_test.go | Missing SOUL.md or missing project context returns an empty block from BuildContextFilesPrompt — the existing platform/session block is injected unchanged and no error reaches the channel. A SOUL.md that trips the threat-pattern scan is rendered as the existing [BLOCKED: ...] marker and never reaches the provider as raw threat content. A truncated SOUL.md or AGENTS.md carries the existing [...truncated ... kept H+T of N chars ...] marker. A failed file read is logged via telemetry and the block is dropped for that turn. |
| 2 / 2.B.5 | Live-turn USER.md and MEMORY.md durable user context block (channel-neutral) — gateway.Manager assembles a durable user-context block from <GormesHome>/memory/USER.md (produced by the shipped Memory Mirror, row 3.D.5) and <GormesHome>/memory/MEMORY.md (openclaw-migrated under internal/platform/migrate/openclaw or operator-authored), threat-scans and truncates each file using the same patterns internal/llm/context_files.go uses for SOUL.md / project context, and prepends the rendered block ahead of the platform/session block but after the existing context-files block. The wiring lives inside gateway.Manager — never inside any channel adapter — so all channels inherit it. A new helper internal/llm/durable_user_context.go (or an extension under internal/llm) exposes BuildDurableUserContextPrompt(opts) (string, DurableUserContextReport) with hermetic options for the memory dir; the rendered block carries USER.md before MEMORY.md so durable user identity precedes durable session memory. | validated | gateway | small | gateway, system | internal/gateway/live_turn_prompt_test.go + internal/llm/durable_user_context_test.go | Missing USER.md and missing MEMORY.md return an empty block — captured kernel.PlatformEvent.SessionContext is byte-identical to the slice-1 (SOUL+project + platform/session) output. A USER.md or MEMORY.md that trips the threat-pattern scan is rendered with the existing [BLOCKED: marker and never reaches the provider as raw threat content. A truncated USER.md or MEMORY.md carries the existing [...truncated ... kept H+T of N chars ...] marker. A failed file read is logged via gateway telemetry and the file is dropped from the block for that turn (the other file still renders if present). |
| 2 / 2.B.5 | Live-turn timestamp + model/provider/session metadata block + self-help guidance (channel-neutral) — gateway.Manager assembles a Hermes-compatible turn-metadata block per inbound submit, mirroring run_agent.py:3770-3779: Conversation started: <Weekday>, <Month> <Day>, <Year> <HH:MM AM/PM> followed by optional Session ID: <id>, Model: <model>, Provider: <provider> lines (each rendered only when the corresponding value is non-empty). The block is appended after the slice-1 context-files block and the slice-2 USER/MEMORY block but before the platform/session block. The same site also gates an inline injection of the already-shipped internal/llm.GormesSelfHelpGuidanceForPrompt(submitText) helper so the gateway-supplied user message can trigger Gormes self-help guidance without a model call. Both pieces honor the channel-neutral assembly site at gateway.Manager — never inside any channel adapter — so all channels (Telegram, Slack, WhatsApp, BlueBubbles, Discord) inherit them automatically. | validated | gateway | small | gateway, system | internal/gateway/live_turn_prompt_test.go + internal/llm/turn_metadata_test.go | Empty session ID, empty model, or empty provider drops the corresponding line from the metadata block; an empty block (none of timestamp/session/model/provider populated) is omitted from the assembled header. Self-help guidance is suppressed when GormesSelfHelpGuidanceForPrompt returns gate=false (user prompt not a Gormes-self-help question). A clock seam returning the zero time produces the deterministic test fixture timestamp; production wiring uses time.Now via the seam default. No raw provider credentials, base URLs, or API keys are ever rendered — only model/provider names. |
| 2 / 2.B.5 | Hermes prompt-builder guidance constants port (data-only, byte-equivalent) — Port the seven unbranded Hermes prompt-builder guidance constants from ../hermes-agent/agent/prompt_builder.py to Go as exported package-level identifiers in a new file internal/llm/guidance_constants.go: MEMORY_GUIDANCE -> MemoryGuidance, SESSION_SEARCH_GUIDANCE -> SessionSearchGuidance, SKILLS_GUIDANCE -> SkillsGuidance, TOOL_USE_ENFORCEMENT_GUIDANCE -> ToolUseEnforcementGuidance, TOOL_USE_ENFORCEMENT_MODELS -> ToolUseEnforcementModels ([]string), DEVELOPER_ROLE_MODELS -> DeveloperRoleModels ([]string), WSL_ENVIRONMENT_HINT -> WSLEnvironmentHint. Each constant must be byte-equivalent to its Hermes upstream when concatenated (Python parenthesised string literals concatenate adjacent strings into one). The slice is data-only — no wiring into live-turn prompt assembly, no provider routing change, no runtime behavior change. Subsequent slices will wire each constant into assembleLiveTurnPrompt gated on the relevant condition (tools loaded, model substring match, WSL detected). | validated | tools | small | system, operator | internal/llm/guidance_constants_test.go | Constants are pure data; no runtime path is affected. If Hermes drifts upstream the byte-equivalence test fails loudly so a follow-up port row can land the new value. |
| 2 / 2.B.5 | Hermes MEMORY_GUIDANCE stale-artifact exclusion refresh — Refresh Gormes’ pure-data internal/llm.MemoryGuidance constant to byte-match current Hermes MEMORY_GUIDANCE after upstream commit 6e5489c9f adds explicit examples of ephemeral PR, issue, commit SHA, completed-work, phase, file-count, and 7-day-stale artifacts that must not be written to durable memory. The slice is data-only: no live-turn prompt wiring, gateway behavior, provider routing, memory store mutation, or channel rendering changes. | validated | tools | small | system, operator | internal/llm/guidance_constants_test.go::TestGuidanceConstants_MemoryGuidance_ByteEquivalent | If the constant drifts, TestGuidanceConstants_MemoryGuidance_ByteEquivalent fails before any runtime path changes; operators keep the previous guidance until this data-only refresh lands. |
| 2 / 2.B.5 | Live-turn metadata production wiring (cmd/gormes -> Manager seams) — cmd/gormes wires the slice-4 metadata seams (LiveTurnNow, LiveTurnActiveModel, LiveTurnActiveProvider) into the production gateway.ManagerConfig at the existing construction site cmd/gormes/gateway.go:gatewayManagerConfig. Active session ID is passed through assembleLiveTurnPrompt as a direct parameter (mirroring submitText) instead of through a closure seam, because the per-turn session ID is not in scope at seam-construction time; the LiveTurnActiveSessionID closure field is removed. After this slice lands, live Telegram/Slack/WhatsApp/BlueBubbles/Discord turns emit the Hermes-format Conversation started: <weekday>, <Month> <D>, <YYYY> <HH:MM AM/PM>\nSession ID: <id>\nModel: <model>\nProvider: <provider> block per run_agent.py:3770-3779. | validated | tools | small | operator, system | internal/gateway/live_turn_prompt_test.go + cmd/gormes/gateway_test.go + cmd/gormes/telegram_test.go | If cfg.Hermes.Model or cfg.Hermes.Provider is empty in production config, the corresponding line is omitted from the metadata block (slice-4’s BuildTurnMetadataBlock already handles empty fields). If the gateway cannot resolve a session ID at the call site, the SessionID line is omitted. The clock fallback is deterministic via time.Now. No raw provider credentials, base URLs, or API keys are rendered. |
| 2 / 2.B.5 | BlueBubbles iMessage session-context prompt guidance — Gateway session-context prompts tell the agent when the origin is BlueBubbles/iMessage and ask for short, blank-line-separated message bubbles | validated | gateway | small | gateway, system | internal/gateway/session_context_test.go | Until this lands, BlueBubbles still sends replies through the first-pass adapter but the model is not explicitly guided toward iMessage-length bubble formatting. |
| 2 / 2.B.5 | Telegram production live-turn provider payload golden — The actual gormes telegram entrypoint is exercised with fake Telegram ingress and a fake provider capture so the final provider ChatRequest for What's your name? contains Gormes SOUL identity, USER.md, MEMORY.md, AGENTS/project context, timestamp, model, provider, Telegram/session metadata, skill/tool guidance, and the user message before provider execution. | validated | gateway | small | gateway, system, operator | cmd/gormes/telegram_test.go + internal/gateway/live_turn_prompt_test.go | If a context file is missing, the captured request still carries redacted missing-context evidence and never uses post-provider string replacement to force identity. |
| 2 / 2.B.5 | Telegram /status Hermes-format closeout — /status renders Hermes-compatible field order and labels, always includes a real session title when a title exists or can be generated, quotes the triggering Telegram message, and remains a gateway command that never enters the provider/model path. | validated | gateway | small | gateway, operator | internal/gateway/status_command_test.go + internal/channels/telegram/bot_test.go | If title generation cannot run, status returns structured title_unavailable evidence instead of silently omitting the Title field or rendering a hardcoded fake title. |
| 2 / 2.B.5 | Gateway /title manual session title command — Implement Hermes-compatible /title handling in the gateway: /title shows the current title, /title <name> sanitizes and stores a manual title, manual titles are not overwritten by auto-title, invalid titles return operator guidance, and the command never reaches the provider. | validated | gateway | small | gateway, operator | internal/gateway/status_command_test.go | If the metadata store is unavailable, /title returns title_store_unavailable evidence and does not mutate transcript content. |
| 2 / 2.B.5 | Session metadata manual-title protection flag — Persist a manual-title flag on session.Metadata, expose a concrete SessionTitleStore adapter that surfaces it to PerformAutoTitle, and wire the gateway /title set path to mark titles as manual so future auto-title generation cannot overwrite operator-set names. | validated | gateway | small | gateway, system | internal/persistence/session/title_store_test.go | Legacy bbolt rows without the field decode as manual=false (zero value) so existing untitled sessions remain auto-title-eligible. If the adapter cannot read metadata, Title() returns err and PerformAutoTitle records auto_title_store_read_failed evidence rather than silently overwriting. |
| 2 / 2.B.5 | Gateway auto-title generation wiring — After an assistant turn finalizes (kernel.PhaseIdle), the gateway invokes session.PerformAutoTitle once per turn against the MetadataTitleStore adapter, using internal/llm.GenerateTitle as the provider boundary, so untitled sessions get one auto-generated title from the first user+assistant exchange while manual titles, already-titled sessions, blank generator output, and provider failures all surface evidence without retry storms or transcript mutation. | validated | gateway | medium | gateway, operator, system | internal/gateway/auto_title_wiring_test.go | If the configured TitleModelFunc is nil, PerformAutoTitle returns AutoTitleCodeProviderFailed evidence and the gateway routes it through the existing auxiliary-failure callback (4.F[1]) so operators see why no title was set. Provider errors and blank model output surface auto_title_provider_failed and auto_title_blank_result via the same channel without crashing the foreground turn. |
| 2 / 2.B.5 | Telegram reply_to_mode and reply-context parity — Telegram replies honor Hermes-style reply mode configuration, fall back cleanly if a target message was deleted, and inbound Telegram reply text can be attached to session context without leaking raw slash commands to the model. | validated | gateway | medium | gateway, operator | internal/channels/telegram/reply_mode_test.go + internal/gateway/manager_test.go | If reply metadata is unavailable, Gormes sends a normal message with reply_context_missing evidence rather than failing the turn. |
| 2 / 2.B.5 | Telegram sendChatAction typing API — Add a hermetic Bot.SendChatAction(ctx, chatID, action) method backed by the existing telegramClient.Request seam (tgbotapi.NewChatAction) so the gateway can later fire “typing” indicators during streaming phases. Non-fatal failure path: errors are returned but never panic, and bot wrapping continues operation. | validated | gateway | small | gateway, operator | internal/channels/telegram/chat_action_test.go | If the Telegram Request call fails, SendChatAction returns the error so callers can record redacted evidence; no fallback retry, no goroutine, no panic. |
| 2 / 2.B.5 | Gateway typing-action wiring during stream — While a turn is in PhaseConnecting/PhaseStreaming/PhaseReconnecting/PhaseFinalizing, the gateway fires the channel’s typing action at most every 4 seconds via a Channel-level capability check, so users see a live typing indicator instead of a static hourglass-only placeholder. Typing-action-capable channels send the first visible assistant text directly; non-typing-capable channels preserve the existing placeholder/edit path. | validated | gateway | small | gateway, operator | internal/gateway/typing_action_test.go | If the channel’s typing call fails, the gateway records redacted evidence and continues delivering coalesced placeholder/final updates. No retry storm; one failed typing-action call does not retry within the same 4s window. |
| 2 / 2.B.5 | Placeholder edit-failure fallback hardening — When the coalescer’s edit path fails (Telegram returns an error mid-stream or at finalize), the gateway falls back to a plain Send of the final text exactly once when the final is not already visible, records redacted edit_failed_fallback evidence, and never produces duplicate final messages even under concurrent flushImmediate / flushImmediateFinal races or terminal-edit failures after an already-visible preview. | validated | gateway | small | gateway, operator | internal/gateway/coalesce_failure_test.go | If both edit and Send fail, the gateway records send_final_failed evidence and the turn ends without a delivered final message rather than panicking or retrying in a hot loop. |
| 2 / 2.B.5 | Gateway stream/tool trace formatting fixture matrix — Channel-neutral stream rendering has source-backed fixtures for Hermes/Sidon text deltas, the active streaming cursor, separate tool progress, errors, final answer separation, Hermes display.tool_progress off/new mode behavior plus config.yaml global/per-platform loading, and Hermes /verbose gate/cycle/persistence behavior, with Telegram MarkdownV2 escaping and compact labels for memory/skills/cron/search/read/patch/terminal/browser actions. | validated | gateway | medium | gateway, operator | internal/gateway/render_test.go + internal/gateway/manager_test.go + internal/kernel/toolexec_test.go + internal/kernel/tools_test.go + internal/config/config_test.go + cmd/gormes/gateway_test.go | Unknown tool events render as bounded generic tool_progress evidence instead of raw provider payloads or dropped traces. |
| 2 / 2.B.5 | Telegram dynamic BotCommand menu wiring — Telegram startup registers the core command set plus enabled plugin/skill slash commands through the existing dynamic BotCommand helper, respecting Telegram’s 100-command cap and emitting hidden-count evidence. | validated | gateway | small | gateway, system, operator | internal/channels/telegram/bot_test.go + cmd/gormes/gateway_test.go + internal/skills/preprocessing_commands_test.go | If dynamic command discovery fails, the bot registers the core menu; runtime registration caps at Telegram’s 100-command platform limit so core commands remain available. |
| 2 / 2.B.5 | Active Hermes/Sidon profile context root resolver for live turns — Live-turn context discovery resolves explicit Gormes overrides first, then Gormes-owned home/profile files and Gormes workspace ancestor SOUL/USER/MEMORY files. Upstream HERMES_HOME may be inspected by migration/config tooling, but it is not a live persona or durable-memory fallback for gateway turns, preventing Mineru/Hermes profile fall-through in the Telegram dogfood bot. | validated | gateway | small | gateway, system | internal/llm/context_root_resolver_test.go + internal/gateway/live_turn_prompt_test.go | Missing Gormes-owned profile or memory files render missing-context evidence and continue the turn from the platform/session block; HERMES_HOME content is ignored unless explicitly migrated into Gormes-owned files or selected through GORMES_CONTEXT_HOME/GORMES_CONTEXT_MEMORY_DIR. |
| 2 / 2.B.5 | Durable context ordering and frozen snapshot decision fixture — Lock the Gormes/Hermes decision for USER.md and MEMORY.md ordering plus frozen-versus-per-turn durable memory semantics with source-backed provider payload fixtures, then either document the owned divergence or change ordering to match Hermes. | validated | gateway | small | gateway, system | internal/gateway/live_turn_prompt_test.go + internal/llm/durable_user_context_test.go | Until the decision is locked, parity matrix rows must mark durable context ordering as partial and avoid claiming strict Hermes prompt equivalence. |
| 2 / 2.B.5 | Live-turn model/tool guidance wiring — Wire the ported Hermes guidance constants into live-turn prompt assembly so memory guidance, session-search guidance, skills guidance, tool-use enforcement, model-family guidance, and environment hints reach provider requests in the correct role/order when their conditions apply. | validated | gateway | medium | gateway, system | internal/kernel/guidance_test.go + internal/gateway/live_turn_prompt_test.go + internal/llm/guidance_constants_test.go | If tools or skills are unavailable, the guidance block omits only those gated sections and reports no false capability. |
| 2 / 2.B.5 | Gateway active-turn policy manifest closeout — Compare the full Hermes gateway slash-command policy set against Gormes CommandDef policies and close remaining bypass, reject, drain, unavailable, and model-leak differences with a manifest fixture. | validated | gateway | small | gateway, operator | internal/gateway/active_turn_command_bypass_test.go | Unknown policies default to reject-with-guidance during active turns and never leak slash text to the provider. |
| 2 / 2.B.5 | Gateway conversational session metadata refresh — Normal conversational Telegram turns create or refresh session metadata with Hermes-compatible session ID, created time, last activity time, connected platform evidence, and title eligibility before /status renders it. | validated | gateway | small | gateway, operator | internal/gateway/session_metadata_test.go | If metadata persistence fails, /status reports session_metadata_unavailable instead of fabricating timestamps. |
| 2 / 2.B.5 | Gateway session token accounting parity — Accumulate per-session provider usage into session metadata so /status reports Hermes-compatible token totals rather than only the last usage frame. | validated | gateway | small | gateway, system, operator | internal/gateway/token_accounting_test.go | If provider usage is missing, status renders zero or unknown with usage_missing evidence and never guesses token counts. |
| 2 / 2.B.5 | Gateway startup allowlist + weak credential guard — Gormes ports Hermes gateway admission startup safety before channel runtimes connect: startup reports when no platform/global allowlist or allow-all override is configured; platform placeholder tokens such as ***, changeme, your_api_key, and placeholder disable the affected enabled platform with redacted evidence; and API-server wildcard/network hosts refuse placeholder keys while loopback-only development hosts remain allowed. | validated | gateway | medium | operator, gateway, system | cmd/gormes/gateway_admission_test.go; internal/gateway/admission_test.go | Missing allowlists produce gateway_allowlist_missing warning evidence; placeholder platform tokens produce gateway_weak_credential_disabled; network-exposed API server placeholder keys produce api_server_weak_key_refused. No token, API key, chat id, user id, or raw dotenv line is printed. |
| 2 / 2.B.5 | Telegram voice/audio inbound attachment markers — Telegram voice and audio messages produce nonblank channel-neutral submit text plus bounded attachment descriptors so downstream gateway/model paths can see media evidence instead of an empty user turn. | validated | gateway | small | gateway, operator, system | internal/channels/telegram/bot_test.go:TestBot_ToInboundEvent_VoiceMessageIsNotBlank + TestBot_ToInboundEvent_AudioMessageWithCaptionPreservesCaptionAndMarker | If Telegram supplies media without a resolvable URL, Gormes preserves the platform file ID as redacted source evidence and still emits a readable marker rather than blank text. |
| 2 / 2.B.5 | Non-editable gateway progress/commentary send fallback — Channels without placeholder/edit capabilities receive progress-safe interim or final assistant messages through the plain Send path without EditMessage calls | validated | gateway | small | gateway, system | internal/gateway/manager_test.go | Non-editable channels continue to receive final responses, but quick commentary/interim updates may be suppressed until the send-fallback fixture proves no edit path is attempted. |
| 2 / 2.B.5 | WhatsApp identifier safety predicate — WhatsApp identity code exposes one pure predicate/normalizer that accepts only safe ASCII WhatsApp peer identifiers and rejects traversal, path separators, percent-encoded separators, empty values, and Unicode lookalikes before alias graph construction | validated | gateway | small | gateway, operator, system | internal/channels/whatsapp/identity_safety_test.go::TestWhatsAppIdentifierSafetyPredicate | Unsafe WhatsApp identifiers return whatsapp_identifier_unsafe evidence from the helper instead of being silently normalized or stripped into a plausible peer ID. |
| 2 / 2.B.5 | WhatsApp unsafe sender/chat inbound evidence — WhatsApp inbound normalization in internal/channels/whatsapp/inbound.go uses the validated NormalizeSafeWhatsAppIdentifier helper to reject unsafe raw sender, chat, and reply target identifiers before Event.UserID, Event.ChatID, Reply.ChatID, or outbound pairing targets are created | validated | gateway | small | gateway, operator, system | internal/channels/whatsapp/identity_inbound_safety_test.go::TestNormalizeInboundWithIdentity_UnsafeRawIDs | Unsafe inbound WhatsApp sender/chat/reply IDs produce whatsapp_identifier_unsafe evidence and an unresolved event shape instead of a session key or pairing target. |
| 2 / 2.B.5 | WhatsApp unsafe alias endpoint inbound evidence — WhatsApp inbound alias graph expansion skips unsafe alias endpoints with explicit whatsapp_identifier_unsafe evidence after raw sender/chat safety is fixture-backed | validated | gateway | small | gateway, operator, system | internal/channels/whatsapp/identity_inbound_safety_test.go::TestNormalizeInboundWithIdentity_UnsafeAliasEndpointRejected | Unsafe alias endpoints are ignored with whatsapp_identifier_unsafe evidence instead of being added to alias graph entries or outbound pairing targets. |
| 2 / 2.B.5 | Gateway fresh-final eligibility helper — Gateway coalescer exposes a deterministic fresh-final eligibility decision for stale editable previews without changing send/edit behavior yet | validated | gateway | small | operator, gateway, system | internal/gateway/coalesce_fresh_final_test.go::TestFreshFinalEligibility | Until this helper lands, fresh-final cannot be tested with a fake clock and all channels keep the legacy edit-in-place finalization path. |
| 2 / 2.B.5 | Gateway fresh-final send/delete fallback — Gateway streaming finalization can replace an eligible old editable preview with a fresh final message, best-effort delete the old preview when supported, and fall back to the legacy final edit when fresh send fails | validated | gateway | small | operator, gateway, system | internal/gateway/coalesce_fresh_final_test.go::TestCoalescerFreshFinalSendFallback | Until this lands, the coalescer can identify stale previews but still finalizes by editing the original preview, so long-running Telegram replies keep the first-token visible timestamp. |
| 2 / 2.B.5 | Telegram fresh-final delete and config exposure — Telegram gateway streaming exposes fresh_final_after_seconds with a 60 second default and wires the Telegram adapter to best-effort delete stale preview messages via the Bot API deleteMessage call after the shared gateway coalescer successfully sends a fresh final | validated | gateway | small | operator, gateway, system | internal/channels/telegram/bot_test.go::TestBot_DeleteMessage and internal/config/config_test.go::TestLoad_TelegramFreshFinalAfterSeconds | Until this lands, the shared fresh-final coalescer remains disabled in real Telegram gateway runs and stale previews are left visible after long-running replies. |
| 2 / 2.B.5 | Telegram group bot-command mention gate helper — Telegram ingress exposes a pure mention-gate helper that treats /cmd@botname bot_command entities as direct address when group require-mention policy is enabled, rejects commands addressed to other bots, and keeps bare group slash commands gated | validated | gateway | small | gateway, operator, system | internal/channels/telegram/group_mention_test.go | Until the helper is bound into Telegram group ingress, Gormes cannot safely enable Hermes-style require_mention command gating for Telegram groups; status should report telegram_group_mention_gate_unavailable instead of silently responding to bare group commands. |
| 2 / 2.B.5 | Telegram require-mention config fields — internal/config parses Telegram require_mention and bot_username fields with disabled defaults so group mention gating remains opt-in and can be tested without constructing a Telegram bot runtime | validated | gateway | small | gateway, operator, system | internal/config/config_test.go::TestLoad_TelegramRequireMentionFields | When the fields are absent or malformed, Telegram group mention gating remains disabled with config evidence instead of changing DM, allowed-chat, or first-run discovery behavior. |
| 2 / 2.B.5 | Telegram group require-mention bot binding — internal/channels/telegram applies the validated group mention helper to synthetic group updates using parsed require_mention/bot_username settings, dropping unaddressed group text and bare slash commands while preserving DMs, allowed-chat gating, first-run discovery, and fresh-final streaming | validated | gateway | small | gateway, operator, system | internal/channels/telegram/group_mention_binding_test.go::TestBot_ToInboundEvent_GroupMentionGate_* | Telegram status reports telegram_group_message_unaddressed or telegram_group_mention_gate_unavailable instead of silently processing unaddressed group traffic. |
| 2 / 2.B.5 | Slack rich-text quotes/lists + link-unfurl ingress — Slack inbound text augmentation extracts readable rich_text quote/list/preformatted blocks and link-unfurl attachment previews while routing commands from the original Slack text only | validated | gateway | small | operator, gateway, system | internal/slack/rich_text_test.go::TestAugmentInboundTextExtractsQuotesListsAndAttachments | If Slack blocks or attachments cannot be parsed, Gormes processes the original text and records slack_rich_text_unavailable evidence instead of dropping the message. |
| 2 / 2.B.5 | Slack thread-parent context + team-scoped cache key — Slack thread replies preserve parent text and team-scoped thread-context cache keys so cron or third-party bot parents are available as reply context without self-bot echo loops | validated | gateway | small | operator, gateway, system | internal/slack/thread_context_test.go::TestThreadParentContextIncludesBotParentAndScopesCacheByTeam | If the thread parent cannot be fetched, Slack ingress continues with thread_id and message_id evidence but leaves reply_to_text empty. |
| 2 / 2.B.5 | Gateway message deduplicator bounded helper — Shared gateway exposes a pure in-memory MessageDeduplicator that caps tracked message IDs at max_size, evicts the oldest ID deterministically, and returns duplicate/evicted/disabled evidence without touching manager routing | validated | gateway | small | gateway, system | internal/gateway/message_deduplicator_test.go::TestMessageDeduplicator_MaxSizeEvictsOldest | Gateway helper reports deduplicator_disabled, duplicate_message, or deduplicator_evicted evidence instead of growing unbounded in long-running platform adapters. |
| 2 / 2.B.5 | Gateway inbound dedup key helper — Gateway exposes a pure helper that derives a dedup tracking key from InboundEvent.Platform, ChatID, ThreadID, and MessageID, returning explicit missing-ID evidence without dropping or submitting events | validated | gateway | small | gateway, system | internal/gateway/inbound_dedup_key_test.go::TestInboundDedupKey_* | Gateway status can distinguish dedup_unavailable_missing_message_id from duplicate_message before manager dispatch is wired. |
| 2 / 2.B.5 | Gateway inbound dedup manager binding — Gateway manager owns one in-memory MessageDeduplicator instance, derives tracking keys through the validated InboundDedupKey helper, drops repeated inbound submissions with duplicate_message evidence, and lets missing-ID events continue with dedup_unavailable_missing_message_id evidence | validated | gateway | small | gateway, system | internal/gateway/message_deduplicator_manager_test.go::TestGatewayInboundDedup_* | Gateway status reports dedup_unavailable for missing IDs and duplicate_message for dropped repeats instead of silently replaying duplicate platform events. |
| 2 / 2.B.5 | Email outbound Date header contract — Email outbound message construction always supplies an RFC 5322 Date header with an injectable clock when one is absent, while preserving caller-supplied Date values and leaving reply threading unchanged | validated | gateway | small | operator, gateway, system | internal/channels/email/outbound_date_test.go::TestEmailOutboundDateHeader_* | Email delivery status reports email_date_header_unavailable if the outbound builder cannot compute a Date header, instead of emitting provider-rejected messages without Date. |
| 2 / 2.B.5 | Telegram MarkdownV2 parse-mode rendering closeout — internal/channels/telegram/bot.go::Send and SendReply set msgCfg.ParseMode = tgbotapi.ModeMarkdownV2 so that the MarkdownV2-escaped output produced by internal/gateway/render.go::FormatStreamTelegram, FormatFinalTelegram, and FormatErrorTelegram renders as bold/italic/code/spoiler/strike on Telegram clients instead of literal backslashes. A parse-failure fallback resends the same body in plain text via msgCfg.ParseMode = ” when Telegram rejects the MarkdownV2 payload, mirroring Hermes’ behavior at gateway/platforms/telegram.py:998-1003. Edit messages (EditMessage) and placeholder sends (SendPlaceholder/SendReplyPlaceholder) honor the same parse-mode policy. | validated | gateway | small | gateway, operator | internal/channels/telegram/bot_test.go | If Telegram rejects MarkdownV2 (Bot API error ‘can’t parse entities’), the bot retries the same body once with parse_mode unset and emits redacted telemetry evidence. If the retry also fails, the original error is surfaced via HookOnError and the channel state machine treats the platform as failed for that turn. |
| 2 / 2.B.5 | Telegram topic mode off/help/auth/debounce closeout — Gormes ports current Hermes Telegram topic-mode command semantics: /topic help shows usage without activating topic tables, /topic off disables topic mode and clears bindings idempotently, unauthorized users cannot toggle topic mode, BotFather screenshot/capability guidance is debounced per chat, and /topic off resets the debounce counters. | validated | gateway | small | operator, gateway, system | internal/gateway/telegram_topic_mode_test.go; internal/channels/telegram/bot_test.go | Missing topic-mode storage or unsupported platform state returns telegram_topic_unavailable or telegram_topic_auth_denied evidence instead of enabling topic routing for unauthorized users or spamming repeated capability screenshots. |
| 2 / 2.B.5 | Telegram document/photo cache + batch attachment parity — Telegram document, video, and photo ingress matches Hermes’ channel-visible media behavior: supported files are cached with bounded metadata, small text documents inject content into the submit text, unsupported or unverifiable files produce explicit operator-visible evidence, and photo bursts or albums are coalesced into one gateway event before submission. | validated | gateway | medium | gateway, operator, system | internal/channels/telegram/document_cache_test.go; internal/channels/telegram/photo_batch_test.go | If Telegram file download, MIME inference, or batch flushing is unavailable, Gormes emits telegram_document_unavailable, telegram_document_too_large, telegram_document_unsupported, or telegram_photo_batch_unavailable evidence and never turns media-only updates into blank user turns. |
| 2 / 2.B.5 | Discord authenticated attachment download safety — Discord ingress downloads image, audio, and document attachments through the authenticated Discord session when possible, falls back through SSRF-gated URL fetches only when safe, and preserves bounded attachment evidence without exposing raw CDN URLs or internal fetch targets. | validated | gateway | medium | gateway, operator, system | internal/channels/discord/attachment_download_test.go | If Discord attachment bytes cannot be read or a fallback URL is unsafe, Gormes emits discord_attachment_unavailable or discord_attachment_blocked_ssrf evidence and submits any remaining text without attempting an unauthenticated unsafe fetch. |
| 2 / 2.B.5 | Slack Block Kit approval buttons + action callback — Slack exposes Hermes-compatible execution approval prompts as Block Kit buttons in the active thread and resolves once/session/always/deny callbacks exactly once through the gateway approval store with redacted channel evidence. | validated | gateway | medium | operator, gateway, system | internal/slack/approval_buttons_test.go; internal/gateway/hook_consent_test.go | If Slack interactivity or approval storage is unavailable, Gormes returns slack_approval_unavailable evidence and falls back to the existing non-interactive approval path without running the requested tool. |
| 2 / 2.B.5 | Discord thread participation persistence — Discord thread participation state survives adapter restarts through a bounded persisted tracker so Gormes can distinguish known Discord threads without relying only on in-memory forum lifecycle events. | validated | gateway | small | gateway, system | internal/channels/discord/thread_participation_test.go | If the persisted tracker cannot be loaded, Gormes starts with an empty tracker, logs redacted discord_thread_tracker_reset evidence, and never blocks Discord ingress because of a corrupt or missing state file. |
| 2 / 2.B.5 | Cross-platform image/document MEDIA delivery routing — Gateway final-response MEDIA tags route local image, document, video, and audio files to native Telegram, Discord, and Slack send paths while stripping tag syntax from operator-visible text and preserving reply/thread metadata. | validated | gateway | medium | gateway, operator, system | internal/gateway/media_delivery_test.go; internal/channels/telegram/media_send_test.go; internal/channels/discord/media_send_test.go; internal/slack/media_send_test.go | If a channel lacks a native media method or a local file is missing, Gormes sends redacted media_unavailable evidence through the existing text fallback without leaking local paths, credentials, or raw MEDIA tags. |
| 2 / 2.B.5 | Telegram inline approval buttons + callback auth — Telegram execution approvals use Hermes-compatible inline keyboard buttons for once, session, always, and deny decisions; callback queries resolve the stored gateway approval exactly once, enforce Telegram/global allowlists, and leave unrelated callback families such as model picker or update prompts untouched. | validated | gateway | medium | operator, gateway, system | internal/channels/telegram/approval_buttons_test.go; internal/gateway/hook_consent_test.go | If inline keyboards, callback routing, or approval storage is unavailable, Gormes returns telegram_approval_unavailable evidence and keeps the pending command blocked rather than silently approving or falling through to text-only execution. |
| 2 / 2.B.5 | Telegram polling conflict + webhook secret startup guard — Telegram startup matches Hermes’ safety behavior: polling mode clears stale webhooks before getUpdates, repeated 409 polling conflicts retry before becoming a typed fatal error, startup network failures are marked retryable, token-scoped locks prevent same-host double polling, and webhook mode refuses to start without an explicit secret token. | validated | gateway | medium | operator, gateway, system | internal/channels/telegram/startup_lifecycle_test.go | Startup failures surface telegram_bot_token_lock, telegram_polling_conflict, telegram_connect_error_retryable, or telegram_webhook_secret_missing evidence; Gormes never starts an unauthenticated webhook endpoint or runs two same-token polling loops on the same host. |
| 2 / 2.B.5 | Slack mention/free-response gating + strict thread-memory guard — Slack ingress honors Hermes’ require_mention, strict_mention, and free_response_channels policy: channel messages default to mention-gated, configured channels can opt into free responses, active-session thread replies continue without a fresh mention, DMs always pass, and strict mention mode does not persist mentioned threads for future automatic replies. | validated | gateway | medium | operator, gateway, system | internal/slack/mention_gate_test.go; internal/config/slack_config_test.go | Malformed Slack mention settings fail closed for require_mention, fail open only for strict_mention legacy compatibility, and emit slack_mention_policy_unavailable evidence instead of silently widening channel access. |
| 2 / 2.B.5 | Discord interaction authorization + mention safety guards — Discord native interaction surfaces use the same admission policy as message ingress and default to safe mention behavior: allowed_mentions blocks everyone/here and role pings unless explicitly opted in, slash commands honor user, role, allowed-channel, ignored-channel, and thread-parent gates, component buttons honor user-or-role allowlists, and unauthorized skill autocomplete cannot leak the installed skill catalog. | validated | gateway | medium | operator, gateway, system | internal/channels/discord/interaction_auth_test.go; internal/channels/discord/allowed_mentions_test.go | If Discord native interaction auth helpers are unavailable, Gormes hides or rejects native interactions with discord_interaction_auth_unavailable evidence and keeps plain message-command handling as the only active path. |
| 2 / 2.B.5 | Gateway processing lifecycle reactions for Telegram and Discord — Gateway processing lifecycle hooks drive channel-native reactions for supported platforms: in-progress reactions are set when a turn starts, success/failure terminal reactions are set when the turn completes, cancellation suppresses terminal success/failure, and reaction failures never break message processing or final delivery. | validated | gateway | medium | gateway, operator | internal/gateway/reaction_lifecycle_test.go; internal/channels/telegram/reactions_test.go; internal/channels/discord/reactions_test.go | Channels without reaction support, disabled reaction config, missing message IDs, or platform API failures emit reaction_unavailable evidence at debug level and continue processing without changing user-visible response delivery. |
| 2 / 2.B.5 | Telegram text batching + caption merge parity — Telegram ingress matches Hermes’ delayed text batching and caption merge behavior: rapid text updates from the same session are coalesced before gateway dispatch, different chats stay isolated, pending batch state is cleaned after flush/disconnect, and media captions merge by exact duplicate only without substring loss. | validated | gateway | medium | operator, gateway, system | internal/channels/telegram/text_batch_test.go; internal/channels/telegram/caption_merge_test.go | If the text batcher is unavailable, Gormes emits telegram_text_batch_unavailable evidence and falls back to immediate per-message dispatch without dropping messages, mixing chats, or widening group mention access. |
| 2 / 2.B.5 | Cross-platform multi-image native batching — Gateway media delivery exposes Hermes-compatible multi-image batching for platforms with native batch APIs: Telegram sends media groups in chunks of ten with animation fallback, Discord sends up to ten files per message, Slack uses files_upload_v2 file_uploads chunks of ten, email can build one multipart reply with all local images, and unsupported channels retain per-image fallback delivery. | validated | gateway | medium | gateway, operator, system | internal/gateway/media_batch_test.go; internal/channels/telegram/media_batch_test.go; internal/channels/discord/media_batch_test.go; internal/slack/media_batch_test.go; internal/channels/email/media_batch_test.go | If native batch delivery fails or a channel lacks a batch capability, Gormes falls back to existing per-file media delivery with redacted media_batch_unavailable evidence and never leaks local paths, raw MEDIA tags, or upload credentials. |
| 2 / 2.B.5 | Discord message admission + reply-mode policy — Discord message ingress applies Hermes’ channel admission policy before gateway dispatch and outbound sends honor Discord reply_to_mode: allowed, ignored, free-response, and no-thread channel lists support CSV/list/scalar config, wildcards where Hermes allows them, and parent-thread matching; require_mention defaults on for guild channels; DMs and known bot-participated threads bypass fresh mentions; bot authors obey DISCORD_ALLOW_BOTS none/mentions/all; auto-threading is skipped for replies, free-response channels, and configured no-thread channels; and reply references can be off, first chunk only, or all chunks. | validated | gateway | medium | operator, gateway, system | internal/channels/discord/admission_policy_test.go; internal/channels/discord/reply_mode_test.go | Malformed Discord admission settings fail closed for allow/ignore policy, keep require_mention enabled, and emit discord_admission_policy_unavailable evidence instead of silently widening server-channel access. |
| 2 / 2.B.5 | Webhook dynamic route reload + signed rate-limit order — Generic webhook ingress matches Hermes runtime safety around dynamic subscriptions and request admission: static routes and agent-created webhook_subscriptions.json routes are merged with static precedence and mtime-gated reloads, file removal clears dynamic routes, corrupt dynamic JSON degrades without deleting static routes, HMAC signature validation happens before rate limiting, invalid signatures never consume quota, and valid signed requests still enforce per-route rate limits. | validated | gateway | medium | operator, gateway, system | internal/channels/webhook/runtime_test.go; internal/channels/webhook/dynamic_routes_test.go | If dynamic route reload or runtime admission is unavailable, Gormes starts only the validated static webhook routes, reports webhook_dynamic_routes_unavailable or webhook_rate_limit_unavailable evidence, and refuses unsigned protected routes instead of widening ingress access. |
| 2 / 2.B.5 | Slack/Discord channel-scoped skills, prompts, and reload resync — Slack and Discord gateway ingress honor Hermes channel-scoped routing metadata: channel_skill_bindings resolve by exact channel or parent thread/forum channel, accept a single skill string or ordered list, deduplicate while preserving order, skip malformed or empty bindings, and attach the resolved auto-skill set to gateway events without mutating the global skill catalog. Discord channel_prompts resolve by exact channel or parent, exact matches override parent prompts, blank prompts are ignored, slash events, thread sessions, and retry flows preserve the prompt, and run-agent prompt assembly places context prompt, channel prompt, then global prompt. Discord /reload-skills calls adapter refresh_skill_group when present so autocomplete entries resync from the current collector state, sort alphabetically, and preserve previous entries on collector errors. | validated | gateway | medium | operator, gateway, system | internal/gateway/channel_scope_test.go; internal/channels/discord/channel_scope_test.go; internal/slack/channel_scope_test.go | Malformed channel skill bindings, blank prompts, collector errors, or adapters without refresh hooks degrade to no auto-skill/no channel prompt and stale-but-valid autocomplete entries, with channel_scope_unavailable evidence instead of widening skills globally. |
| 2 / 2.B.5 | Telegram fallback transport + polling reconnect recovery — Telegram runtime matches Hermes’ network resilience behavior for blocked or unstable Telegram API access: fallback IP config parsing rejects invalid, IPv6, whitespace-only, and leading-zero addresses; requests to api.telegram.org try the primary host first, preserve Host header and SNI, fall back only on connect timeout/connect errors, try fallback IPs in order, stick to the last working fallback, bypass fallback for non-Telegram hosts and read-timeout/non-connect errors, and raise the last connect error if all endpoints fail. Polling reconnect recovery self-schedules after retryable network startup/polling failures unless fatal state is already set, drains only the polling request pool before reconnect or conflict retry, continues if drain fails, resets retry counts on success, marks fatal after max retries, and emits a heartbeat probe after a successful reconnect. | validated | gateway | medium | operator, gateway, system | internal/channels/telegram/network_test.go; internal/channels/telegram/startup_lifecycle_test.go | If fallback parsing or reconnect orchestration is unavailable, Gormes keeps primary Telegram requests, reports telegram_network_fallback_unavailable or telegram_polling_reconnect_unavailable evidence, and never drains general-purpose request pools or rewrites non-Telegram hosts. |
| 2 / 2.B.5 | Telegram sticker vision adapter binding — Telegram sticker updates bind the completed gateway sticker cache to live adapter handling: animated/video stickers inject a deterministic placeholder from emoji/set name without vision download, static stickers look up cached descriptions by file_unique_id, download sticker bytes through a fakeable Telegram file client on cache miss, persist .webp bytes and description metadata, call an injected vision analyzer with Hermes’ sticker prompt, cache the analyzed description, and fall back to emoji/set_name description on download or vision failures without dropping the inbound submit. | validated | gateway | medium | operator, gateway, system | internal/channels/telegram/sticker_test.go | If sticker download, cache I/O, or vision analysis fails, Gormes injects a bounded sticker placeholder with redacted evidence and continues the Telegram message path instead of dropping the update or blocking the gateway. |
| 2 / 2.B.5 | Discord native slash/thread command registration parity — Discord native command registration matches Hermes’ channel-visible slash surface: hardcoded commands such as /thread and /restart delegate auth and defer behavior to their handlers; COMMAND_REGISTRY commands and plugin slash commands auto-register without overriding built-ins; argument-bearing commands proxy optional args into _run_simple_slash; /skill registers as one flat autocomplete-backed command whose payload stays under Discord’s command-size limit, dispatches selected skill names to their real slash command, and returns ephemeral guidance for unknown skills. /thread creates a Discord thread with sanitized/truncated names, falls back through a seed message when direct thread creation lacks permission, reports ephemeral success/failure, optionally dispatches the seed message into the new thread session, and native slash events preserve thread or group source context. | validated | gateway | medium | operator, gateway, system | internal/channels/discord/slash_commands_test.go; internal/channels/discord/thread_command_test.go | If Discord command registration or thread creation helpers are unavailable, Gormes keeps message-command parsing and reports discord_slash_registration_unavailable or discord_thread_create_unavailable evidence; plugin conflicts never override built-ins and failed thread creation sends only ephemeral failure text. |
| 2 / 2.B.5 | Telegram entity-only mention boundary closeout — Telegram require-mention gating must use Telegram entities only, not substring scans: mention entities in text or captions match the configured bot username case-insensitively; text_mention entities match the bot user ID for private-name mentions; malformed offsets/lengths, different usernames, different user IDs, and bare @bot substrings without mention entities are rejected. Email-like strings, hostnames, superstring usernames, underscore suffixes, URLs, and code-block text containing @bot do not address the bot unless Telegram supplied a real mention/text_mention entity. | validated | gateway | small | operator, gateway, system | internal/channels/telegram/mention_boundary_test.go | If entity boundary parsing is unavailable, Gormes keeps require-mention fail-closed for groups and reports telegram_mention_boundary_unavailable rather than processing substring-only @handles. |
| 2 / 2.B.5 | Telegram thread-aware outbound text + typing seam — Gateway outbound delivery preserves channel thread metadata for Telegram text, reply, placeholder, fresh-final, tool-progress, and typing-action sends without changing generic delivery parsing: manager hooks carry the active SessionSource thread ID into optional thread-aware channel capabilities, Telegram sends non-General text through raw Bot API sendMessage params with message_thread_id, omits message_thread_id for General topic thread 1 on text sends, and sends typed chat actions with message_thread_id when a thread is present. | validated | gateway | medium | operator, gateway, system | internal/gateway/thread_delivery_test.go; internal/channels/telegram/thread_send_test.go | Channels that do not implement thread-aware capabilities keep the existing Send/SendReply/SendPlaceholder/SendChatAction behavior; Telegram thread send parse failures keep the existing MarkdownV2 fallback path; retry and inbound forum detection remain in the parent fallback row. |
| 2 / 2.B.5 | Telegram forum thread fallback + send retry safety — Telegram outbound send/typing behavior preserves Hermes forum and retry safety: forum General-topic inbound messages without message_thread_id retain synthetic thread context 1; outbound sends to General omit message_thread_id=1; send and typing retry without message_thread_id when Telegram returns BadRequest ‘message thread not found’; non-thread BadRequest errors fail immediately; transient NetworkError sends retry with bounded attempts; TimedOut sends do not retry to avoid duplicate visible messages; RetryAfter sleeps/backoffs then retries; and once a chunk clears an invalid thread ID, later chunks in the same long response use no thread ID directly. | validated | gateway | medium | operator, gateway, system | internal/channels/telegram/thread_fallback_test.go; internal/channels/telegram/send_retry_test.go | If thread fallback or retry classification is unavailable, Gormes reports telegram_thread_fallback_unavailable or telegram_send_retry_unavailable evidence and avoids duplicate retries for timeouts rather than silently dropping all streaming/tool-progress messages. |
| 2 / 2.B.5 | Telegram DM topic reply-fallback routing — Gormes ports Hermes’ May 2026 Telegram private-DM topic routing fix for Hermes-created topic lanes: a Telegram private chat with a topic/thread id must send text through the existing thread+reply anchor when the triggering message id is available, must never send with message_thread_id alone when the reply anchor is unavailable, must keep every long final-answer page anchored to the triggering message, and must skip sendChatAction for these lanes because Telegram Bot API 10.0 rejects typing calls that only carry message_thread_id. | validated | gateway | small | operator, gateway, system | internal/gateway/thread_delivery_test.go; internal/gateway/typing_action_test.go | If a Telegram private-DM topic turn lacks a triggering message id, Gormes degrades to a plain chat send instead of routing a partial message_thread_id-only send outside the visible topic lane; typing indicators are skipped without channel-visible errors or raw Bot API logs. |
| 2 / 2.B.5 | Telegram semantic MarkdownV2 formatter + table rewrite — Telegram outbound formatting mirrors Hermes’ Markdown-to-MarkdownV2 conversion rather than only escaping plain text: fenced and inline code are preserved with escaped backslashes/backticks, bold/italic/strikethrough/spoiler/header/link/blockquote constructs convert to valid MarkdownV2 with special-character escaping, italic conversion does not span newlines or corrupt bullet lists, expandable blockquote prefixes stay intact, markdown pipe tables are rewritten into mobile-readable bullet row groups outside code blocks, plaintext fallback strips MarkdownV2 escapes without mangling snake_case identifiers, and chunk indicators are MarkdownV2-escaped. | validated | gateway | medium | operator, gateway | internal/channels/telegram/format_test.go; internal/gateway/render_telegram_format_test.go | If semantic formatting fails, Gormes falls back to existing fully escaped MarkdownV2 or plain text with telegram_format_unavailable evidence; malformed Markdown must never produce invalid entities that drop the send. |
| 2 / 2.B.5 | Telegram Markdown table row-label bullet suppression — Telegram final-message table rewriting mirrors Hermes’ row-label column handling: when a Markdown table has one more data cell than header cell, the first data cell is rendered only as the bold row heading and the remaining cells align with the headers. The row-label cell must not also appear as a duplicate bullet item under the first header, while ordinary same-width tables keep the existing first-cell heading plus bullet rows. | validated | gateway | small | operator, gateway | internal/gateway/render_telegram_format_test.go::TestFormatFinalTelegramText_TableRowLabelDoesNotDuplicateAsBullet | Without the row-label guard, Telegram users see duplicated row labels in table rewrites, such as a bold row heading followed by a redundant first bullet for the same label. |
| 2 / 2.B.5 | Telegram streaming edit Markdown safety — Telegram streamed edit delivery mirrors Hermes’ finalization split: non-final editMessageText calls send the partial draft as plain text with no MarkdownV2 parse_mode so incomplete Markdown cannot break live streaming, while final edits keep MarkdownV2 formatting and parse-error plaintext fallback. The shared gateway coalescer already passes the finalize flag through gateway.FinalizingMessageEditor; this slice binds that interface in the Telegram adapter without changing generic coalescer semantics or Telegram send/reply behavior. | validated | gateway | small | operator, gateway | internal/channels/telegram/bot_parse_mode_test.go::TestBot_EditMessageFinal_* | If the adapter cannot distinguish streaming from final edits, Gormes may send partial Markdown through Telegram’s MarkdownV2 parser and drop or delay visible streamed updates; final replies still fall back to a plain text send path. |
| 2 / 2.B.5 | Telegram guest mention allowlist bypass — Telegram group admission matches Hermes guest_mode: when an operator configures an allowed_chat_id/allowed_chats allowlist and enables guest_mode, a group or supergroup outside that allowlist may reach the gateway only when the message explicitly mentions the bot by @username, text_mention user ID, or /command@botname entity. Replies to the bot, regex mention patterns, bare slash commands, and ordinary text must not bypass the allowlist. | validated | gateway | small | operator, gateway | internal/channels/telegram/group_mention_binding_test.go::TestBot_ToInboundEvent_GuestMode* plus internal/gateway manager admission fixture | If guest_mode is disabled or bot identity is unavailable, Gormes keeps the existing strict allowlist behavior and drops non-allowlisted group traffic. |
| 2 / 2.B.5 | Gateway platform reconnect isolation + channel health limits — Gateway platform lifecycle matches Hermes’ multi-channel resilience: each enabled platform connect is bounded by HERMES/GORMES gateway platform connect timeout and one timed-out platform is queued for retry without blocking later platforms; retryable startup/connect failures enter a reconnect watcher with attempts/next_retry state, successful retry installs the adapter and clears failure state, non-retryable fatal errors leave the queue, and retryable failures stay queued with incremented attempts. Connected-platform readouts cover every built-in platform via generic token/api-key checks or bespoke checkers that return bool without panics. Long-lived HTTP platform clients share tuned keepalive limits with env overrides and malformed override fallbacks, and WhatsApp typing releases HTTP responses structurally instead of leaking sockets. | validated | gateway | medium | operator, gateway, system | internal/gateway/platform_reconnect_test.go; internal/gateway/platform_connected_checkers_test.go; internal/gateway/platform_http_client_limits_test.go | If lifecycle resilience is unavailable, Gormes reports platform_reconnect_unavailable, platform_connected_checker_missing, or platform_http_client_limits_unavailable evidence while continuing healthy channels and preserving redacted startup failure state. |
| 2 / 2.B.5 | Gateway per-platform circuit breaker + /platform pause/resume/list command — Extend the completed platform reconnect/failed-queue seam (internal/gateway/platform_reconnect.go ReconnectFailedPlatforms / PlatformFailure) with Hermes’ per-platform circuit breaker so the gateway keeps running when one platform keeps failing: after a configurable threshold of consecutive retryable failures (Hermes _PAUSE_AFTER_FAILURES=10) the platform is marked paused, kept in the failed set with a pause_reason and a far-future next_retry, surfaced as platform_state=“paused” in the runtime status read-model, and skipped by the reconnect watcher until manually resumed. Add a gateway-only /platform <pause|resume|list> [name] operator slash command (the Go port of gateway/run.py:_handle_platform_command, registered in internal/gateway/commands.go CommandRegistry as gateway_only with args_hint “<pause|resume|list> [name]” like the existing handler ports) where: /platform pause <name> pauses a queued platform (idempotent; rejects an unqueued platform), /platform resume <name> unpauses, resets the attempt counter and schedules an immediate retry (returns a clear not-paused/not-queued message otherwise), /platform list shows connected and paused platforms, bare /platform prints usage plus the list, and an unknown platform name is rejected with guidance. Auto-pause must log/return the Hermes-shaped ‘fix the issue then run /platform resume <name>’ guidance. This row does not reopen the completed reconnect-isolation row and does not change provider retry behavior. | validated | gateway | medium | operator, gateway, system | internal/gateway/platform_reconnect_test.go (extend) or internal/gateway/platform_circuit_breaker_test.go (new) | If the circuit breaker or /platform handler is unavailable, the gateway continues healthy platforms and reports platform_circuit_breaker_unavailable / gateway_platform_command_unavailable evidence rather than crashing the gateway or silently dropping the failing platform; the existing reconnect watcher behavior is preserved unchanged. |
| 2 / 2.B.5 | Gateway /model interactive provider/model picker — Gateway /model with no arguments opens an interactive two-step provider→model picker on channels that support inline keyboards (Telegram first; Discord later) and writes a per-session model/provider override that the live-turn provider payload sees on the next message. The handler shares the Hermes-compatible ⚙ Model Configuration / Select a provider: / Select a model: header text and reuses the curated provider list from the Hermes provider registry manifest. Channels without inline-keyboard support and the existing /model <name> typed-arg path keep the current static-text behavior. The picker is the gateway slash-handler port of _handle_model_command; it does not duplicate the completed gormes model Cobra CLI picker, which persists hermes.model/hermes.provider to config TOML and is governed by row Gormes model interactive provider/model picker. | validated | gateway | medium | operator, gateway, system | internal/gateway/model_picker_test.go; internal/channels/telegram/model_picker_buttons_test.go | On channels without inline keyboards the no-arg /model invocation continues to return the existing static 🤖 Model / 📡 Provider text and the picker is reported as gateway_model_picker_unavailable evidence rather than silently dropping. If the provider registry manifest returns no providers, the handler falls back to the static text and reports gateway_model_picker_empty_providers. OpenRouter live-catalog filtering is out of scope for this slice: the picker uses the curated manifest models only and records gateway_model_picker_openrouter_live_filter_pending degraded evidence; a separate row may add live-catalog filtering parity with Hermes’ fetch_openrouter_models. Picker selections apply as session-only overrides; the --global persistence path is reachable only through the typed-arg /model <name> --global path covered by a future row. |
| 2 / 2.B.5 | Gateway memory monitor pressure policy — Port Hermes gateway memory monitor behavior into Gormes as a typed pressure policy that samples process memory, reports WARN/CRITICAL evidence, and can request bounded shutdown/restart action without killing unrelated operator processes. | validated | gateway | small | - | internal/gateway memory monitor fake sampler fixtures | - |
| 2 / 2.B.11 | Discord SessionSource guild/parent/message evidence — Discord ingress preserves guild, parent channel, and triggering message IDs in the gateway SessionSource without changing existing chat/thread routing | validated | gateway | small | gateway, operator | internal/channels/discord/session_source_metadata_test.go | Gateway session context omits unavailable Discord scope IDs explicitly rather than fabricating guild/thread/message metadata. |
| 2 / 2.B.11 | Discord forum media + polish parity — Discord forum sends preserve Hermes forum-post semantics on top of the shipped Discord adapter: outbound text to a forum parent creates a starter thread post, follow-up chunks route through the created thread, file-bearing sends use create_thread(file=…) with deterministic thread names, failures are returned as SendResult warnings/errors, and forum thread metadata keeps parent_chat_id/thread_id/message_id intact for shared gateway delivery. | validated | gateway | medium | gateway, operator | internal/channels/discord/forum_media_test.go | Discord forum create/send/upload failures produce send_warning or send_failed evidence while non-forum channel sends continue through the existing shared-chassis contract. |
| 2 / 2.B.12 | Channel-neutral native runtime turn adapter — Telegram, Slack, Discord, WhatsApp, BlueBubbles, and future channels enter the same native Gormes turn adapter so provider/runtime fixes preserve Hermes channel parity instead of hard-coding Telegram behavior | validated | gateway | small | gateway, operator, system | internal/gateway/channel_neutral_turn_adapter_test.go | Provider/runtime failures are rendered through the shared external-channel safe error helper without leaking raw error text and active turn state is cleared so subsequent inbound traffic is not blocked. |
| 2 / 2.B.12 | Hermes gateway platform registry manifest — Gormes owns a source-backed gateway platform manifest that enumerates every current Hermes Platform enum value and connector file, classifies each platform as implemented, partial, row-backed, excluded, or Gormes-owned, and records command, config, credential, inbound, outbound, media, toolset, pairing, and delivery status without requiring live platform credentials. | validated | gateway | medium | gateway, operator, system | internal/gateway/platform_manifest_test.go::TestHermesGatewayPlatformManifestCoversUpstream | Unknown platform enum values, connector files, or platform-specific command/toolset surfaces fail the manifest test. Unsupported platforms remain visible as row-backed or excluded entries with source refs instead of disappearing from docs, gateway status, or future command planning. |
| 2 / 2.B.12 | MSGraph webhook platform manifest drift closeout — Gormes refreshes the gateway platform manifest drift harness so the in-repo Hermes submodule is checked, then classifies Hermes’ new msgraph_webhook platform as a row-backed webhook surface with redacted connected-state detection before any live listener implementation begins. | validated | gateway | small | gateway, operator, system | internal/gateway/platform_manifest_test.go::TestHermesGatewayPlatformManifestCoversUpstream | Until a future runtime adapter row ships, msgraph_webhook remains visible as row-backed/not configured rather than disappearing from platform drift checks or being advertised as a working listener. |
| 2 / 2.B.12 | Bundled platform plugin manifest drift guard — Gormes extends the completed gateway platform manifest drift harness so bundled Hermes platform plugins under plugins/platforms/*/plugin.yaml are discovered alongside gateway/config.py:Platform enum values and gateway/platforms/*.py connectors. The manifest must classify current plugin platforms google_chat, irc, line, and teams with source-backed row-backed or partial status, and connected-state checks must recognize their redacted synthetic config without starting SDKs, sockets, webhooks, cron delivery, or reading real env credentials. | validated | gateway | small | gateway, operator, system | internal/gateway/platform_manifest_test.go::TestHermesGatewayPlatformManifestCoversBundledPluginPlatforms | Fresh Hermes bundled platform plugins fail the manifest drift test instead of silently disappearing from Gormes status/planning. Until a live adapter row ships, plugin platforms remain row-backed or partial and status helpers report configured/unconfigured from synthetic config only. |
| 2 / 2.B.12 | Navivox stdio protocol control-plane tracer — gormes navivox serve --stdio exposes a Gormes-owned SSH stdio control-plane server for the future Flutter Navivox app. The first slice implements only the binary NVOX frame codec and fakeable server loop for hello -> server.status, ping -> pong, and unknown readable event -> structured error; it rejects bad magic, unsupported prelude versions, invalid JSON headers, oversized headers/payloads, and payload-length mismatches without starting gateway.Manager or handling chat, voice, config, tools, pairing, or selected-agent routing. | validated | gateway | small | gateway, operator, system | internal/channels/navivox/protocol_test.go and internal/channels/navivox/server_test.go | Unreadable framing errors close the stdio server with typed Go errors instead of emitting untrustworthy in-band frames; readable unsupported event types return redacted error frames with correlation_id set to the triggering message id. |
| 2 / 2.B.12 | Navivox QR pairing descriptor CLI — gormes navivox pair creates a gateway pairing-code request for the navivox platform, prints a terminal QR code, and prints a plain-text fallback navivox://pair?... descriptor containing transport=ssh, host, port, SSH user, gormes navivox serve --stdio, protocol version, device name, pairing code, and expiry. Host selection prefers an explicit --host, then Tailscale IPv4, then LAN IPv4, then loopback. gormes navivox setup-host --plan renders the recommended host preparation path with Tailscale SSH, OpenSSH server commands, and sudo-password safety without mutating the host. | validated | gateway | small | gateway, operator, system | cmd/gormes/navivox_test.go::TestNavivoxPairPrintsScannableDescriptorAndPersistsPendingPairing | If QR scanning is unavailable, the fallback URI is fully usable. If Tailscale is unavailable, the command still emits an explicit host source and recommends gormes navivox setup-host --plan. Pairing output never includes private keys, sudo passwords, or shell credentials; setup planning states that sudo passwords are prompt-only and never stored. |
| 2 / 2.B.12 | Navivox Flutter voice morph surface — The Flutter Navivox app exposes a reusable VoiceMorphSurface for voice mode with state-driven soft morph graphics, an intensity input that can later bind to mic/TTS amplitude, reduced-motion phase freezing, live semantics, and a visible speaking surface in the fake chat voice message flow. | validated | gateway | small | operator | flutter-navivox/app/test/features/voice/widgets/voice_morph_surface_test.dart | Reduced-motion settings freeze phase-driven animation without removing the state-specific visual. Intensity is clamped to 0.0..1.0 so malformed audio levels cannot distort layout or painter behavior. |
| 2 / 2.B.12 | Multimodal photo attachment passthrough — TurnAdapter.Dispatch materializes inbound gateway.Attachment{Kind:“photo”} entries into kernel.PlatformEvent.ContentParts as llm.MessageContentPart{Type:“image_url”, ImageURL:“data: | validated | gateway | small | operator, gateway, system | internal/gateway/turn_adapter_test.go (synthetic 4x4 JPEGs generated via image/jpeg.Encode — no committed binary fixtures) | When Attachment.URL is empty or the cache file is unreadable, the helper omits the image_url part entirely; the existing channel-side text marker (e.g. ‘[Telegram photo unavailable: cache read failed]’) in SubmitText remains the user/model signal. Existing image_shrink_retry handles oversized payloads on retry without adapter-side bounds. |
| 2 / 2.B.12 | Hermes-style default prompt and image-path hints for inbound photos — When the gateway constructs a kernel turn from an InboundEvent that carries one or more photo attachments, the leading native content text part preserves the user’s caption when present or becomes What do you see in this image? when the caption is empty. Each successfully-attached image also appends one [Image attached at: <local path>] hint inside that leading text part so model-callable tools that take an image-path argument can address the same image. This matches Hermes’ build_native_content_parts contract. | validated | gateway | small | operator, gateway, system | internal/gateway/turn_adapter_test.go (extend existing TurnAdapter_Dispatch_PhotoAttachment* tests) | When a cache file is unreadable, the helper omits that image_url part and does not advertise that path in native text hints; skipped-image counts are logged. Plain text projection still carries the existing channel attachment marker for memory and legacy UI compatibility. |
| 2 / 2.B.12 | Hermes gateway platform strict-fidelity source-pair expansion — Build a report-only strict-fidelity classifier for Hermes gateway platform evidence: scan the current hermes-agent/gateway/config.py:Platform enum, gateway/platforms connector/helper/docs files, bundled plugins/platforms/* platform plugins, and tui_gateway bridge files, then group them into deterministic platform-source families tied to the completed Gormes platform manifest, existing source-pair rows, planned adapter rows, explicit exclusions, or owned-divergence notes. The classifier must expand cmd/repoctl hermes-contract-inventory and source-pair evidence only; it must not import or execute Hermes Python, start live SDKs/webhooks/sockets, mutate channel runtime behavior, or claim strict channel/TUI parity without exact row/source-pair/test proof. | validated | docs | medium | operator, system | internal/repoctl/hermes_contract_inventory_gateway_platforms_test.go | Until this classifier lands, strict-fidelity reports can still show Hermes gateway platform and tui_gateway files as broad unmapped buckets, so Gormes must avoid claiming complete channel platform, platform-plugin, API-server, or TUI-gateway parity from the existing manifest alone. |
| 2 / 2.D | Cron no-agent script-only short-circuit — Cron jobs with no_agent=true execute only their configured script, never construct a kernel/agent turn, deliver trimmed successful stdout verbatim, suppress empty stdout or wakeAgent=false output, and deliver script failure/timeout alerts with run-store evidence. | validated | gateway | small | operator, gateway, system | internal/automation/cron/no_agent_test.go; internal/tools/cronjob_tool_no_agent_test.go | A no_agent job without a script fails closed with cron_no_agent_script_required evidence and does not create an agent turn. |
| 2 / 2.D | Durable operator run report for unattended jobs — Add a Gormes-owned durable OperatorRunReport artifact for unattended cron/fleet jobs. The report is produced from existing cron run, provider/runtime readiness, delivery, session, and release-ledger evidence and records job_id, run_id, profile/workspace, provider/model, delivery target, start/end/status, degraded_reason, transcript/session refs, redacted error/log summary, and recommended_next_command without running a real provider, gateway, or scheduler loop. | validated | orchestrator | small | operator, system | internal/automation/cron/operator_run_report_test.go::TestOperatorRunReportBuildsSuccessAndDegradedArtifacts | When a run is suppressed, times out, fails provider/auth readiness, or cannot deliver, the report remains written with status=degraded or failed, a stable degraded_reason, redacted detail, and a recommended repair command rather than disappearing into logs. |
| 2 / 2.D | Scheduled briefing job emits operator run report — Wire scheduled briefing cron/fleet jobs so every unattended run writes an OperatorRunReport after completion. The slice should cover local delivery, suppressed/no-agent/script-only jobs, provider-backed runs, timeout/error paths, and repeat/terminal completion evidence while preserving existing cron_runs and CRON.md mirror behavior. | validated | orchestrator | medium | operator, system | internal/automation/cron/operator_briefing_report_test.go::TestScheduledBriefingWritesOperatorRunReport | If the briefing cannot execute or cannot produce user content, the cron executor still writes a report with status=degraded or failed, run completion evidence, redacted error summary, and next repair command. |
| 2 / 2.E.1 | Durable job routing policy — Durable-job routing separates deterministic restart-survivable work from live LLM subagents while keeping Gormes Go-native subagent APIs | validated | orchestrator | small | operator, child-agent, system | internal/core/subagent/minion_policy_test.go | Until the policy lands, Gormes exposes in-memory subagents plus append-only run logs only; durable minion routing is documented as unavailable in status/doctor surfaces. |
| 2 / 2.E.1 | Durable subagent/job ledger — SQLite-first job ledger records restartable subagent and deterministic work state with claim, progress, result, error, parent-child, and cancellation fields | validated | orchestrator | medium | operator, child-agent, system | internal/core/subagent/durable_ledger_test.go | Doctor and status report append-only run logs without restart rehydration until the durable ledger can claim and resume work. |
| 2 / 2.E.3 | Durable job backpressure + timeout audit — Gormes durable jobs expose Gormes-owned max-waiting backpressure, wall-clock timeout evidence, and operator-readable queue health without importing Minions runtime code | validated | orchestrator | small | operator, system | internal/core/subagent/durable_backpressure_test.go | Doctor/status reports queue full, timeout-at, and stale waiting counts before accepting more durable work. |
| 2 / 2.E.3 | Durable worker supervisor status seam — Gormes reports durable-worker liveness, restart intent, and degraded/no-worker modes before any external supervisor controls job execution | validated | orchestrator | small | operator, system | internal/core/subagent/durable_supervisor_status_test.go | Doctor/status reports no durable worker, stale heartbeat, or supervisor-unavailable instead of silently accepting restart-survivable work. |
| 2 / 2.E.3 | Durable pause/resume intent contract — Gormes durable jobs record pause and resume as explicit operator/system control intents over the SQLite ledger without starting a Gormes-owned worker daemon | validated | orchestrator | small | operator, system | internal/core/subagent/durable_lifecycle_test.go | Doctor/status reports paused jobs, resume intent, and unsupported child-agent lifecycle control instead of silently leaving workers active. |
| 2 / 2.E.3 | Durable replay and inbox message contract — Gormes durable jobs can replay completed/failed work and store steer messages as auditable inbox data without exposing protected shell submission to child agents | validated | orchestrator | small | operator, system, child-agent | internal/core/subagent/durable_replay_inbox_test.go | Status reports replay unavailable, inbox-unread, and protected-submit denial separately so operators can distinguish observation from mutation. |
| 2 / 2.E.3 | Durable worker execution loop — Gormes durable jobs have a Go-native fake-handler execution seam that claims one waiting job from the SQLite ledger, invokes an injected handler with context.Context, records progress/result/failure evidence, and exits cleanly when no job is claimable | validated | orchestrator | small | operator, system | internal/core/subagent/durable_worker_test.go | Durable-worker status reports idle, claim_unavailable, handler_failed, or heartbeat_unavailable without starting a background daemon or shell executor. |
| 2 / 2.E.3 | Durable worker abort-slot recovery safety net — Gormes durable worker execution loop propagates timed-out jobs into handler cancellation and uses a bounded grace timer to free the worker slot with auditable recovery evidence if the handler ignores cancellation | validated | orchestrator | small | operator, system | internal/core/subagent/durable_worker_abort_test.go | Durable-worker status reports abort_signal_sent, abort_slot_recovered, handler_ignored_abort, or abort_recovery_unavailable instead of leaving capacity wedged. |
| 2 / 2.E.3 | Durable worker RSS watchdog policy helper — Gormes durable worker exposes a pure RSS watchdog policy helper that classifies disabled mode, unavailable RSS reads, threshold exceeded evidence, and stable watchdog restart reset without integrating with the worker loop yet | validated | orchestrator | small | operator, system | internal/core/subagent/durable_worker_rss_watchdog_test.go::TestDurableWorkerRSSWatchdogPolicy | Durable-worker policy reports rss_watchdog_disabled, rss_threshold_exceeded, rss_watchdog_unavailable, or stable_watchdog_restart before runtime drain wiring exists. |
| 2 / 2.E.3 | Durable worker RSS drain integration — DurableWorker integrates the validated RSS watchdog policy after job completion and on an injected periodic check, starts graceful drain, cancels in-flight handlers through existing abort-slot recovery, and records watchdog drain evidence | validated | orchestrator | small | operator, system | internal/core/subagent/durable_worker_rss_drain_test.go | Durable-worker status reports rss_drain_started or rss_handler_abort_sent instead of wedging capacity after threshold evidence. |
| 2 / 2.E.3 | Durable worker RSS doctor/status evidence — Doctor and durable-worker status output expose RSS watchdog configuration, latest threshold/read-failure/drain reason, and unavailable evidence from the durable ledger without killing the single Gormes process | validated | orchestrator | small | operator, system | internal/doctor/durable_rss_watchdog_test.go | Doctor/status reports rss_watchdog_disabled, rss_watchdog_unavailable, rss_threshold_exceeded, rss_drain_started, and stable_watchdog_restart evidence separately. |
| 2 / 2.F.1 | Gateway slash registry parity sweep (recognized-name expansion) — internal/gateway/commands.go::CommandRegistry recognizes every Hermes/Sidon command from cmd/gormes/hermes_cli_parity_test.go’s manifest, even when the handler is not yet implemented, so unknown-command replies only fire on actual non-Hermes inputs. Each newly-recognized command lands with ActiveTurnPolicy: CommandActiveTurnPolicyUnavailable and a friendly description, mirroring the existing /retry, /undo, /title, /branch, /compress pattern. Aliases (reset for new, set-home for sethome, gateway for platforms, etc.) resolve to the canonical command. Handler ports remain owned by the 49-file CLI tree port umbrella; this row only changes recognition. | validated | gateway | small | gateway, operator | internal/gateway/commands_test.go + cmd/gormes/hermes_cli_parity_test.go | When an expanded recognized command is sent and no handler exists, the bot replies ’/ |
| 2 / 2.F.1 | Gateway /commands paginated command and skill catalog — Port Hermes gateway /commands [page] as a channel-native, model-free catalog that lists available gateway commands plus enabled dynamic skill commands with pagination and usage/out-of-range guidance. | validated | gateway | small | - | Hermes gateway/run.py:_handle_commands_command combines gateway_help_lines with agent.skill_commands.get_skill_commands, uses 15-entry Telegram pages and 20-entry non-Telegram pages, clamps out-of-range page requests, and returns usage for non-numeric args. Pi contributes only the harness lesson that command catalogs should expose source/provenance from a single command inventory; it is not parity. | Skill catalog scan failures must not break the built-in command catalog; invalid page arguments return usage guidance; built-in slash dispatch and unknown-command protection continue to consume slash text without model leakage. |
| 2 / 2.F.3 | Gateway channel disconnect timeout on failed startup — Gormes mirrors Hermes’ failed-startup adapter cleanup hardening: when a channel Run exits with a startup error, Manager.Run still attempts Disconnect on partially initialized adapters, but that cleanup is bounded by HERMES_GATEWAY_ADAPTER_DISCONNECT_TIMEOUT (default 5s, negative disables) so one wedged adapter cannot block gateway shutdown or mask the original startup failure. | validated | gateway | small | gateway, operator | internal/gateway/manager_test.go | If Disconnect times out, Manager.Run logs timeout evidence and continues with the original startup failure; invalid timeout env values fall back to the 5s default. |
| 2 / 2.F.3 | Gateway shutdown capped adapter disconnect — Gormes mirrors current Hermes gateway stop/restart hardening for the normal shutdown path: when Manager.Run is cancelled, it asks Disconnect-capable channels to release resources under HERMES_GATEWAY_ADAPTER_DISCONNECT_TIMEOUT and returns without waiting forever for a wedged adapter goroutine. This extends the existing failed-startup disconnect cap to planned stop/restart shutdown without changing active-turn drain, service-manager restart, PID validation, or channel SDK internals. | validated | gateway | small | gateway, operator | internal/gateway/manager_test.go | If a shutdown Disconnect call times out, Manager.Run logs channel-scoped timeout evidence and continues the planned shutdown; channels that already honor context cancellation still stop normally. |
| 2 / 2.F.3 | Drain-timeout resume_pending recovery — Gateway restart/shutdown drain timeouts preserve resumable session identity and inject a reason-aware resume note on the next turn without overriding hard-stuck or suspended state | validated | gateway | small | gateway, operator | internal/gateway/resume_pending_test.go | Gateway status reports resume_pending, drain_timeout, and non-resumable stuck/suspended evidence instead of silently dropping in-flight session context. |
| 2 / 2.F.3 | Pairing read-model schema + atomic persistence — Gateway pairing state is persisted as a Go-native XDG read model with atomic writes, deterministic pending/approved ordering, and per-platform paired/unpaired status before approval UX | validated | gateway | small | gateway, operator | internal/gateway/pairing_store_test.go | Gateway status reports missing, corrupt, or permission-denied pairing state without starting transports or accepting unknown users. |
| 2 / 2.F.3 | Pairing approval + rate-limit semantics — Pairing approval uses Hermes-compatible code generation, expiry, pending limits, failed-approval lockout, per-user rate limits, and Telegram DM from_user fallback without starting agent sessions | validated | gateway | small | gateway, operator | internal/gateway/pairing_approval_test.go | Gateway status distinguishes rate-limited, max-pending, expired, locked-out, allowlist-denied, and unresolved-user pairing attempts. |
| 2 / 2.F.3 | Unauthorized DM pairing response contract — Unknown direct-message users receive the configured deny, pair, or ignore response without leaking authorized-session state | validated | gateway | small | gateway, operator | internal/gateway/unauthorized_dm_test.go | Gateway status and logs show denied or pending-pair users without starting agent sessions. |
| 2 / 2.F.3 | gormes gateway status read-only command — A read-only gormes gateway status command renders configured channels, pairing state, and runtime lifecycle from stores without starting transports or agent sessions | validated | gateway | small | operator | cmd/gormes/gateway_status_test.go | The command reports no-channel, missing/corrupt pairing state, missing runtime state, and failed channel lifecycle evidence without opening Telegram, Discord, Slack, memory, or provider clients. |
| 2 / 2.F.3 | Runtime status JSON + PID/process validation — Gateway runtime status validates PID/start-time identity and stale-process cleanup before callers trust gateway_state.json as the live lifecycle read model | validated | gateway | small | operator, system | internal/gateway/runtime_status_pid_test.go | Runtime status distinguishes missing state, stale PID, pid-reused mismatch, stopped process, and permission-denied process checks instead of claiming a gateway is live from old JSON. |
| 2 / 2.F.3 | Token-scoped gateway locks — Gateway credential locks are scoped by platform and credential hash so multiple profiles cannot run the same token while unrelated tokens remain independent | validated | gateway | small | operator, system | internal/gateway/token_lock_test.go | Gateway startup/status reports lock-held, stale-lock-cleared, credential-hash-mismatch, and lock-release-failed evidence without deleting unrelated platform locks. |
| 2 / 2.F.3 | Gateway /restart command + takeover markers — Gateway /restart uses registry-owned active-turn policy, deduped takeover markers, and service-manager exit semantics without looping redelivered platform updates | validated | gateway | small | operator, gateway, system | internal/gateway/restart_command_test.go | Gateway command/status reports restart_requested, takeover_marker_seen, duplicate_restart_suppressed, service_manager_unavailable only when marker store, self-restart hook, and service manager are all unavailable, and restart_timeout evidence. |
| 2 / 2.F.3 | Gateway restart notification opt-out — Gormes ports Hermes’ per-platform gateway_restart_notification opt-out for restart takeover notifications: restart command acknowledgements still send, but post-restart comeback pings are skipped for platforms whose gateway notification flag is false, and the marker is consumed so operators are not spammed on every startup. | validated | gateway | small | operator, gateway, system | internal/gateway/restart_command_test.go + internal/config/config_test.go | If the flag is absent, Gormes preserves the existing default of sending the one-shot restart notification. If the flag is false, startup records marker evidence and marks the marker consumed without sending live channel traffic. |
| 2 / 2.F.3 | Session expiry finalized-flag migration — Gateway session metadata migrates legacy memory_flushed state into expiry_finalized evidence without adding new hidden memory-flush writes | validated | gateway | small | gateway, system | internal/persistence/session/expiry_finalized_migration_test.go | Gateway status reports migrated legacy memory_flushed evidence separately from current expiry_finalized state so operators can tell old session records from new finalization writes. |
| 2 / 2.F.3 | Session expiry hook cleanup retry evidence — Gateway expiry finalization invokes hook and cached-agent cleanup exactly once per expired session, with bounded retry/give-up evidence and no memory flush task | validated | gateway | small | gateway, system | internal/gateway/session_expiry_hooks_test.go | Gateway status reports expiry_finalize_pending, expiry_finalize_failed, and expiry_finalize_gave_up with attempt counts instead of retrying hidden cleanup work forever. |
| 2 / 2.F.4 | Home channel ownership resolver fixtures — Add a channel-neutral home-channel ownership resolver for platform-only delivery targets. The resolver must prefer an explicit target chat/thread, then a Hermes-compatible per-platform home_channel.chat_id/thread/name setting bridged through Gormes config, then a discovery/pairing-owned source only when discovery is explicitly enabled for that platform. It must be callable by delivery routing without Telegram-specific branches and must preserve explicit endpoint/source routing semantics. | validated | gateway | small | operator, gateway | internal/gateway/home_channel_resolver_test.go with temp config structs and fake SessionSource records only; no live platform SDK or Hermes runtime service. | If a platform has no configured home channel and first-run discovery has not supplied one, resolution must return a structured missing_home_channel error and leave delivery at local/origin fallback; it must not guess a chat, leak secrets, or special-case Telegram. |
| 2 / 2.F.4 | Notify-to delivery routing — Persisted scheduled jobs and cronjob tool calls carry Hermes-compatible delivery routing into the Go cron executor: deliver values may be origin, local, platform, platform:chat_id, platform:chat_id:thread_id, or comma/list fanout; origin metadata is preserved when a job is created from a channel; PlanCronDeliveryForJob expands stored fields through home-channel/directory lookup before dispatch. | validated | gateway | medium | gateway, operator | internal/automation/cron/delivery_plan_test.go and internal/tools/cronjob_tool_test.go | Missing origin metadata falls back to local for origin targets, invalid delivery tokens produce target_parse_failed evidence, and missing home-channel/directory entries produce channel_directory_missing without invoking live adapters. |
| 2 / 2.F.4 | Channel directory atomic persistence + lookup — Gormes ports the Hermes channel_directory.json read model as a channel-neutral gateway component: writes are atomic under the Gormes state/home directory, entries preserve platform, id, name, type, guild, chat_id, thread_id, chat_topic, and composite chat_id:thread_id targets, lookups resolve exact IDs, exact names, Discord guild-qualified names, unambiguous case-insensitive prefixes, and type queries without consulting live adapters. | validated | gateway | medium | gateway, operator | internal/gateway/channel_directory_test.go | Missing, malformed, or ambiguous directory state returns channel_directory_missing, channel_directory_invalid, or channel_target_ambiguous evidence without falling back to adapter-specific cache side effects. |
| 2 / 2.F.4 | Channel directory refresh + stale-target invalidation — Gormes adds deterministic refresh and stale-target invalidation around the channel directory: Manager-owned source records and adapter inventory snapshots update the directory through one serialized writer, stale entries are marked or removed after adapter refresh failures according to fixture policy, and delivery lookup returns explicit stale-target evidence instead of silently dispatching to cached IDs. | validated | gateway | medium | gateway, operator | internal/gateway/channel_directory_refresh_test.go | Refresh failures preserve the last known-good directory with channel_directory_refresh_failed evidence; stale delivery targets return channel_target_stale rather than invoking an adapter with obsolete routing. |
| 2 / 2.F.4 | Manager remember-source hook — Gormes persists allowed inbound SessionSource records from the shared gateway.Manager into a channel-directory source ledger before directory refresh or delivery-target resolution depends on remembered sessions. The hook must derive entries from the same normalized InboundEvent source used for live-turn session context, skip unauthorized/discovery-rejected inbound events, preserve Telegram thread/topic and Discord parent/guild metadata when present, and expose a fakeable store seam so future channel-directory refresh can merge remembered sources without live platform SDK calls. | validated | gateway | small | gateway, operator | internal/gateway/channel_directory_source_test.go | If the remembered-source store is unavailable, Manager logs/surfaces channel_directory_source_unavailable evidence and still processes the inbound turn normally; it must not block Telegram replies, leak host paths, or mutate channel_directory.json directly. |
| 2 / 2.F.4 | Mirror + sticker cache surfaces — Gormes ports the Hermes delivery-mirror and sticker-cache helper contracts behind fixture-only gateway surfaces: cross-platform deliveries can select the correct target session without guessing between group participants, append a mirror assistant record with mirror metadata through the store seam, and cache Telegram sticker descriptions by stable file_unique_id with Hermes-compatible injection text. | validated | gateway | small | gateway, operator | internal/gateway/delivery_mirror_test.go and internal/gateway/sticker_cache_test.go | Missing sessions, ambiguous group participants, nil stores, missing cache files, or corrupt sticker cache JSON return structured miss/evidence and never block delivery or adapter processing. |
| 2 / 2.F.4 | Gateway delivery evidence in operator run report — Join gateway/cron delivery results into OperatorRunReport artifacts so unattended jobs record target platform, chat/thread identity, delivery path, delivered boolean, fallback path, and stable failure evidence for live adapter, standalone sender, and fallback sink paths. The slice must reuse existing cron delivery planning/outcome evidence and never require a live Telegram/Discord/Slack bot in tests. | validated | gateway | small | operator, system | internal/automation/cron/operator_delivery_report_test.go::TestOperatorRunReportIncludesDeliveryEvidence | If every delivery path fails, the report remains written with delivered=false, channel_degraded evidence, redacted target detail, and a recommended gateway status command. |
| 2 / 2.F.5 | Steer slash command parser + preview helper — Gateway exposes a pure /steer parser and preview helper that validates non-empty text guidance, rejects image-bearing or attachment-bearing payloads, and returns deterministic preview/evidence strings without touching active sessions | validated | gateway | small | operator, gateway | internal/gateway/steer_command_test.go::TestParseSteerCommand | Invalid steer input returns steer_usage or steer_payload_unsupported evidence before gateway dispatch can queue or inject it. |
| 2 / 2.F.5 | Steer slash command registry + queue fallback — Registry-owned active-turn steering command | validated | gateway | small | operator, gateway | internal/gateway/steer_queue_test.go::TestSteerCommandRegistry_* | Needs human review: gateway should continue returning the parser-level steer_usage/steer_payload_unsupported evidence and should not advertise queued /steer support until the Manager queue hook is selected. |
| 2 / 2.F.5 | Mid-run steer injection between tool calls — Gateway /steer guidance can be delivered into an in-flight native Gormes turn after the current tool batch, preserving provider message-role alternation by appending a clear user-guidance marker to the last tool-result message before the next provider request; no Telegram-only path, hermes-agent runtime call, or next-turn duplicate is introduced. | validated | gateway | small | operator, gateway, system | internal/kernel/tool_interrupt_test.go and internal/gateway/steer_queue_test.go | If no tool-result boundary is available, /steer keeps the existing queue/degraded fallback evidence instead of silently dropping guidance or injecting a malformed provider message. |
| 2 / 2.F.5 | Gateway-handled slash commands bypass active-session guard — Active-turn command bypass contract | validated | gateway | small | operator, gateway | internal/gateway/active_turn_command_bypass_test.go | If a command is unknown, unavailable, or marked busy_reject while a turn is active, the gateway returns the existing sanitized busy/unavailable evidence and does not enqueue the slash text into the running model turn. |
| 2 / 2.F.5 | Gateway persistent goal loop + continuation judge — Gormes ports Hermes /goal as a session-scoped gateway loop: goals persist in session metadata, support status/pause/resume/clear/done states and a max-turn budget, setting a goal queues the first turn, post-turn evaluation uses a fail-open auxiliary judge, continuation prompts are queued only when the judge says continue, user messages take priority and pause the loop for that turn, and mid-run /goal only allows safe inspection/control commands. | validated | gateway | medium | operator, gateway, system | internal/gateway/goal_loop_test.go; internal/persistence/session/goal_state_test.go | Missing session metadata, unavailable auxiliary judge, malformed judge JSON, exhausted budgets, queue failures, or active-turn unsafe goal changes return goal_* evidence and continue safely without wedging the session, losing the user message, or starting an unbounded loop. |
| 2 / 2.F.5 | Gateway/TUI /queue explicit FIFO slash parity — Port Hermes’ explicit /queue//q operator command through Gormes gateway channels and the native TUI. /queue <prompt> queues exactly one full follow-up user turn without interrupting an active run, preserves FIFO order across multiple invocations, reports queue depth, and never merges queued prompts or submits raw slash text to the model. Native TUI /queue with no args reports local queue depth; /queue <prompt> appends to the same visible queued-message buffer that drains after the active turn. | validated | gateway | medium | - | internal/gateway/queue_command_test.go; internal/tui/queue_slash_test.go | Missing arguments return Usage: /queue <prompt> in gateway and a queue-depth status in TUI; full queues return the existing busy queue-full evidence without submitting to the model. Gateway /queue <prompt> with no active turn returns queue_unavailable guidance instead of silently parking an undrainable prompt or submitting raw slash text. |
| 2 / 2.H | Goncho-backed dynamic agent registry — internal/goncho/dynamic_agents.go exposes a DynamicAgentRegistry (Create, Get, List, Bind, Unbind, Resolve) persisting agents and channel/peer bindings in new SQLite tables alongside Goncho memory. The gateway’s existing agentRouter consults the registry as an overlay on top of config.AgentsCfg/AgentBindingCfg: static config wins on AgentID conflict (enforced at Create via CreateAgentOptions.ReservedIDs — operator-defined identity in config.toml cannot be silently shadowed), but dynamic bindings supplement runtime peer matches that static config does not cover. Public Go interface stays small; persistence and conflict semantics stay hidden behind the registry boundary. | validated | memory | small | operator | internal/goncho/dynamic_agents_test.go tempdir SQLite + fake clock | Without runtime mutation, every new agent persona requires editing config.toml and reloading; in-chat spawn UX from rows 2.H.2-4 cannot exist. |
| 2 / 2.H | gormes agent spawn/list/inspect/bind/unbind CLI — cmd/gormes/agent.go gains subcommands spawn, list, inspect, bind, and unbind that drive the DynamicAgentRegistry from 2.H.1. Each subcommand accepts —json for fleet automation with leading build provenance. spawn takes a name and optional —persona text. bind matches by —channel, —peer-kind, —peer-id, and optional —thread-id. list shows runtime-spawned agents. inspect resolves a peer tuple back to the agent that would handle it (exits 0 with bound=false in —json mode for unbound peers; exits 1 with agent_not_bound on stderr in text mode). unbind is idempotent. The existing ‘gormes agent reset’ subcommand stays untouched. —skills/—model flags from the draft contract are deferred to a follow-up row when static-config equivalent semantics are nailed down. | validated | tools | small | operator | cmd/gormes/agent_spawn_test.go tempdir Goncho home | - |
| 2 / 2.H | Telegram /spawn opens forum topic bound to spawned agent — When an operator-tier user runs ‘/spawn | validated | gateway | medium | operator | internal/channels/telegram/spawn_test.go fake Telegram client + tempdir Goncho | If createForumTopic fails (rate limit, missing permission), the handler reports the API error and does not leave a stranded registry record — registry write is committed only after the topic exists. |
| 2 / 2.H | Discord /spawn opens thread bound to spawned agent — Same UX as 2.H.3 on Discord: ‘/spawn | validated | gateway | medium | operator | internal/channels/discord/spawn_test.go fake discordgo session + tempdir Goncho | - |
| 3 / 3.E.7 | Interrupted-turn memory sync suppression — Interrupted or cancelled turns cannot flush partial observations into GONCHO or external Honcho-compatible memory | validated | memory | small | system | internal/memory/interrupted_sync_test.go | Memory status reports skipped or interrupted sync attempts without promoting partial facts to recall. |
| 3 / 3.E.7 | Honcho-compatible scope/source tool schema — Honcho-compatible tool schemas expose GONCHO scope and source allowlist controls without renaming public tools | validated | memory | small | operator, system | internal/gonchotools/honcho_tools_test.go | Memory status and tool schema evidence show when user-scope or source-filtered recall is unavailable. |
| 3 / 3.E.7 | Honcho host integration compatibility fixtures — GONCHO preserves host-facing Honcho session, peer, and tool semantics needed by current OpenCode and SillyTavern integrations | validated | memory | small | operator, system | internal/goncho/host_integration_test.go | Doctor and memory status explain which Honcho-compatible host mappings are unsupported instead of silently accepting incompatible config. |
| 3 / 3.E.7 | SillyTavern persona and group-chat mapping fixtures — Goncho maps Honcho SillyTavern peer modes, session naming, enrichment modes, and group-chat participants without widening recall or leaking the internal goncho name | validated | memory | small | operator, system | internal/goncho/sillytavern_mapping_test.go | Host mapping evidence reports unsupported SillyTavern panel knobs or missing peer/session identifiers instead of silently merging personas or group characters. |
| 3 / 3.E.7 | Cross-chat deny-path fixtures — Same-chat default recall with explicit user-scope widening | validated | memory | small | operator, system | internal/memory cross-chat allow-deny recall fixtures | Memory status and operator evidence report unresolved, conflicting, or denied cross-chat identity bindings. |
| 3 / 3.E.7 | Cross-chat operator evidence — Memory and Goncho status expose operator-readable evidence for allowed, denied, and degraded cross-chat recall decisions | validated | memory | small | operator, system | internal/memory/cross_chat_operator_evidence_test.go | Status and dry-run output report unresolved user bindings, conflicting chat bindings, source allowlists, and host mapping gaps before recall widens. |
| 3 / 3.E.8 | parent_session_id lineage for compression splits — Session metadata records compression/fork lineage and can resolve the live descendant without rewriting ancestor history | validated | memory | small | operator, gateway, system | internal/persistence/session/lineage_test.go | Session mirrors and status show missing, orphaned, or looped lineage instead of silently resuming stale roots. |
| 3 / 3.E.8 | Gateway resume follows compression continuation — Gateway and CLI resume resolve a titled or root session to the newest live compression descendant before loading transcript history | validated | gateway | small | operator, gateway, system | internal/gateway/resume_continuation_test.go | Resume status reports unresolved continuation chains and falls back visibly instead of loading an ended compression root as live history. |
| 3 / 3.E.8 | Lineage-aware source-filtered search hits — Session and message search can surface parent/child lineage context for matched sessions without widening the same-chat default recall fence | validated | memory | small | operator, system | internal/memory/session_lineage_search_test.go | Search evidence reports lineage unavailable or orphaned instead of implying a complete compression chain. |
| 3 / 3.E.8 | Operator-auditable search evidence — Goncho/Honcho-compatible search surfaces operator evidence for user scope, source filters, and session lineage on every widened cross-source search hit | validated | memory | small | operator, system | cmd/gormes/goncho_search_evidence_test.go | Search output reports missing session-directory evidence, same-chat fallback, unavailable lineage, orphaned lineage, and source allowlists instead of implying cross-source recall was safe by default. |
| 3 / 3.F | Goncho context representation options — honcho_context exposes the Honcho v3 session.context representation controls while preserving current same-chat defaults | validated | memory | small | operator, system | internal/goncho/context_options_test.go | Unsupported representation options return structured unavailable evidence instead of being silently ignored. |
| 3 / 3.F | Goncho search filter grammar — Goncho search accepts a typed subset of Honcho v3 filters and rejects unsupported filter operators visibly | validated | memory | medium | operator, system | internal/goncho/filter_grammar_test.go | Unknown filters, unsupported operators, or metadata paths return a structured unsupported-filter error instead of widening search. |
| 3 / 3.F | Vector store + reconciler divergence proof — Gormes explicitly owns local SQLite semantic recall as a divergence from Honcho’s LanceDB/Turbopuffer external vector-store and sync-vector reconciler model. | validated | memory | small | operator, system | internal/memory/divergence_test.go::TestVectorStoreDivergenceRowsClassifyExternalStoresAndReconciler | No external vector API key, LanceDB path, namespace delete, or sync-vector worker is required for local semantic recall; unsupported external sync semantics are visible as excluded divergence rows. |
| 3 / 3.F | Directional peer cards and representation scopes — Peer cards and stored representations are keyed by workspace, observer, and observed peer instead of a flat workspace/peer pair | validated | memory | medium | operator, system | internal/goncho/directional_peer_card_test.go | When directional representation is unavailable, the service reports that only the default gormes observer view was used. |
| 3 / 3.F | Goncho queue status read model — Gormes exposes Honcho-style representation, summary, and dream work-unit queue status as observability, not synchronization | validated | memory | small | operator, system | internal/goncho/queue_status_test.go | If no Goncho task queue exists yet, memory status reports zero tracked Goncho work units plus the existing extractor queue status. |
| 3 / 3.F | Goncho summary context budget — Session summaries use Honcho’s short/long cadence and 40/60 context budget without double-billing last-N-turn recall | validated | memory | medium | operator, system | internal/goncho/summary_context_test.go | When summaries are unavailable or too large for the token budget, context returns recent messages plus explicit summary_absent evidence. |
| 3 / 3.F | Goncho dialectic chat contract — Goncho exposes a Honcho peer.chat-compatible request and response contract while keeping query-specific reasoning separate from prompt-time context assembly | validated | memory | small | operator, system | internal/goncho/chat_contract_test.go | Until the real dialectic tool loop and streaming transport land, honcho_chat returns deterministic content plus explicit unsupported evidence for stream=true and target-specific reasoning gaps. |
| 3 / 3.F | Goncho file upload import ingestion — Goncho can import text-like memory files as session messages without persisting original file bytes, matching Honcho’s file-upload migration model | validated | memory | medium | operator, system | internal/goncho/file_import_test.go | Until PDF extraction and a Goncho queue exist, unsupported content types fail before writes and imported messages report queue-unavailable evidence. |
| 3 / 3.F | Goncho topology design fixtures — Goncho workspace, peer, session, and observation defaults are fixture-locked before more memory behavior is added | validated | memory | small | operator, system | internal/goncho/topology_design_test.go | Unknown external participant identity falls back to a deterministic source-prefixed peer ID and records the fallback in evidence. |
| 3 / 3.F | Goncho operator diagnostics contract — Gormes exposes a Honcho-inspired Goncho doctor path that checks memory topology, queues, config, and degraded modes without requiring operators to inspect raw tables | validated | memory | medium | operator, system | cmd/gormes/goncho_doctor_test.go | Missing optional model/provider features are reported as degraded capability rows, not startup failures, unless a requested command needs them. |
| 3 / 3.F | Goncho streaming chat persistence contract — Goncho streaming chat stores the final assistant response once and never turns partial stream chunks into memory | validated | memory | small | operator, system | internal/goncho/streaming_chat_persistence_test.go | Until streaming transport exists, stream=true returns explicit unsupported evidence while non-streaming chat keeps the Honcho-compatible response contract. |
| 3 / 3.F | Goncho configuration namespace — Gormes owns a Go-native [goncho] configuration namespace that maps Honcho runtime limits and feature gates into existing config loading | validated | memory | small | operator, system | internal/config/goncho_config_test.go | Unset Goncho config uses documented defaults and reports feature-disabled evidence instead of requiring Honcho-style Python service variables. |
| 3 / 3.F | Goncho dreaming scheduler contract — Goncho models Honcho dream scheduling as local auditable work intent with cooldown, idle, threshold, and dedupe gates before any LLM dream execution | validated | memory | small | operator, system | internal/goncho/dream_scheduler_test.go | When dreaming is disabled or no scheduler table exists, Goncho context and doctor output report dream_disabled or dream_unavailable evidence rather than implying background reasoning is active. |
| 3 / 3.F | Goncho CRUD lifecycle invariants — Goncho service methods preserve Honcho’s core workspace/session/message lifecycle invariants over local SQLite: message creation assigns session-local sequence numbers with workspace/session/peer metadata, session deletion cascades session-scoped messages/conclusions/summaries while preserving cross-session peer state, and workspace deletion cascades only that workspace’s local Goncho rows. | validated | memory | small | operator, system | internal/goncho/crud_invariants_test.go | Goncho records lifecycle identity in turn metadata instead of introducing a parallel hosted Honcho schema; OpenAPI route shapes, SDK pagination, webhooks, vector embeddings, documents, and queue workers remain separate rows. |
| 3 / 3.F | Goncho empty peer-card hint contract — honcho_profile reads that return an empty Goncho peer card include a structured diagnostic hint explaining likely causes and alternative tools, while populated cards keep the existing card response shape and empty cards remain a non-error state | validated | memory | small | operator, system | internal/gonchotools/honcho_profile_hint_test.go::TestHonchoProfileEmptyHint_* | If Goncho cannot distinguish the exact cause, the hint reports peer_card_empty_unknown plus alternatives to honcho_reasoning and honcho_search instead of surfacing a cryptic empty result. |
| 3 / 3.F | Hermes memory tool over Goncho/local durable store — Expose the Hermes-visible memory tool with add, replace, and remove actions plus a Gormes read-only inspection action over memory/user targets, backed by Goncho or local durable USER.md/MEMORY.md storage, while preserving safe write responses, redaction, locks, prompt-insertion semantics, and Hermes-compatible replace content arguments. | validated | memory | medium | system, operator | internal/tools/memory_tool_test.go + internal/memory | If durable memory storage is unavailable, the tool returns memory_store_unavailable evidence and does not mutate prompt context or transcripts. |
| 3 / 3.F | Goncho memory provider lifecycle adapter — Create a native Goncho memory-provider lifecycle adapter covering initialize, prefetch, sync turn, pre-compress contribution, memory-write mirror, delegation, and shutdown evidence so Gormes matches Hermes MemoryManager behavior without a hosted Honcho dependency. | validated | memory | medium | system | internal/memory/provider_lifecycle_test.go + internal/goncho | Unavailable providers return memory_provider_unavailable evidence and leave ordinary turn execution intact. |
| 3 / 3.F | Goncho Memory V1 compatibility contract and migration harness — Freeze the first public Goncho memory compatibility contract so future V2+ memory work can improve ranking, summaries, embeddings, QMD-assisted deep search, dialectic, storage internals, and self-improving memory loops without breaking existing user memory or leaking memory across independent agents. V1 must define stable memory IDs, provenance, agent/workspace/peer/session scopes, markdown format, MCP/tool semantics, forget-versus-purge behavior, per-agent eval artifacts, and no-loss upgrade tests before additional local-first memory features build on top. | validated | memory | medium | operator, system | internal/memory/testdata/goncho_v1/; internal/goncho/memory_v1_contract_test.go; internal/gonchotools/memory_v1_mcp_test.go; cmd/gormes/goncho_memory_v1_test.go | If optional embeddings, QMD, or dialectic workers are unavailable, V1 memory remains readable, writable, migratable, and recallable through local SQLite/FTS/graph paths with no network or hosted provider dependency. |
| 3 / 3.F | GONCHO local-first markdown MCP memory requirement — GONCHO must support a local-first memory mode that answers the OpenClaw community pain point: no cloud dependency, no mandatory API key, user-readable/editable markdown memory files, MCP-compatible access from any agent framework, optional local embeddings via Ollama, and restart-persistent storage. | validated | memory | medium | operator, system | internal/goncho/local_markdown_mcp_test.go; internal/gonchotools/mcp_catalog_test.go; internal/memory/markdown_store_test.go | If Ollama or embeddings are unavailable, GONCHO still persists and serves markdown-backed lexical/SQLite recall locally without requiring network access or a hosted memory provider. |
| 3 / 3.G | Goncho keys + webhooks compatibility surface — Goncho exposes Honcho-compatible scoped-key JWT helpers and workspace-scoped webhook endpoint CRUD/test-event/signature helpers over local SQLite without binding HTTP routes or delivery workers. | validated | memory | small | operator, system | internal/goncho/keys_test.go; internal/goncho/webhooks_test.go | The slice proves the local compatibility contract only: OpenAPI route binding, hosted auth middleware, queue-backed webhook publishing, delivery retries, and HMAC HTTP delivery remain row-backed by the HTTP route and operational worker rows. |
| 3 / 3.G | Goncho webhook delivery retry worker contract — Goncho ports Honcho outbound webhook delivery as a local fake-HTTP worker contract through WebhookDeliveryWorker, WebhookDeliveryStore, WebhookHTTPClient, WebhookClock, WebhookDeliveryAttempt, and WebhookDeliveryResult types with queue-empty/test events, HMAC headers, retry/backoff/failure evidence, and workspace endpoint disablement without requiring Redis, hosted Honcho, or live network tests. | validated | memory | small | operator, system | internal/goncho/webhook_delivery_test.go | Failed or disabled endpoints produce retry/failure evidence and leave local state auditable instead of dropping events silently or blocking core Goncho writes. |
| 3 / 3.G | Goncho HTTP route parity over OpenAPI v3 — Goncho owns a source-backed OpenAPI v3 route manifest for every upstream Honcho HTTP path/method and classifies each route as service-backed or still row-backed with residual implementation evidence. | validated | memory | small | operator, system | internal/goncho/http/routes_test.go | This slice does not bind HTTP handlers. Routes without a service target remain row-backed with explicit residuals, and service-backed routes still require a later internal/apiserver adapter for auth, pagination, validation, status codes, and response envelopes. |
| 3 / 3.G | Goncho CLI command-tree parity — Gormes owns a source-backed Honcho CLI command-tree manifest that enumerates upstream honcho-cli commands, global flags, shared scope flags, command groups, JSON/exit/confirm contracts, and classifies each command or flag as implemented or still row-backed. | validated | memory | small | operator, system | cmd/gormes/goncho_cli_parity_test.go | This slice does not implement the missing Honcho CLI commands. Only cmd/gormes goncho doctor is marked implemented; every other upstream command/global flag remains row-backed with residual behavior for future CLI adapter rows. |
| 3 / 3.G | Goncho Honcho SDK compatibility e2e harness — Goncho exposes a Honcho-compatible local harness that can run the upstream Python and TypeScript SDK session/message/search/context flows against Gormes without a hosted Honcho service. | validated | memory | medium | operator, system | internal/goncho/compat_harness_test.go::TestGonchoHonchoSDKCompatibility | If a Honcho SDK flow is not supported locally, the harness reports sdk_flow_unsupported with the exact endpoint or field instead of silently diverging from Honcho. |
| 3 / 3.G | Goncho memory integration into normal agent turn — A Go-native agent turn reads scoped Goncho memory before prompt assembly, exposes honcho_* tool compatibility during the turn, and persists final user/assistant messages after provider completion without Python or hosted Honcho. | validated | memory | medium | operator, gateway, system | internal/kernel/goncho_turn_integration_test.go::TestAgentTurnUsesGonchoMemory | If Goncho recall, tool compatibility, or post-turn persistence is unavailable, the turn status reports goncho_recall_unavailable, honcho_tools_unavailable, or goncho_persist_unavailable while the agent turn remains explainable. |
| 3 / 3.H | Goncho session-end structured summary capture — When a session closes, produce a structured summary capsule (files_modified, decisions_made, open_questions, skill_outcomes, next_steps) stored in goncho_session_summaries. Triggered by session close + idle timeout hybrid. Written by a lightweight model call (cheap model, not main provider). Makes every downstream memory improvement work better by improving input quality. | validated | memory | medium | system | internal/goncho/session_summary_test.go tempdir SQLite + fake session lifecycle | Session summaries remain free-text only; structured fields missing. /continue loads raw summaries without field-level filtering. |
| 3 / 3.H | Goncho BM25 + RRF parallel retrieval fusion — Replace waterfall recall (FTS5 fallback → semantic embedding → CTE) with parallel retrieval: run FTS5 keyword search and semantic embedding search concurrently, fuse results with Reciprocal Rank Fusion. Score relevance against current user message, not just recency. | validated | memory | large | system | - | Recall falls back to existing waterfall pipeline. RRF fusion unavailable; parallel retrieval not attempted. |
| 3 / 3.H | Goncho /memory and /continue CLI commands — Add /memory CLI command for inspecting, searching, and deleting memories. Add /continue command listing recent sessions with summaries, allowing explicit session resumption with full context injection. Transparency without forcing management at startup. | validated | memory | medium | operator | - | Memory remains invisible to users. Session resumption is automatic only; no explicit session selection available. |
| 3 / 3.H | Goncho dream fact extraction and memory compression — Phase 1 of dream consolidation: scan raw turns during idle periods, extract durable conclusions, update peer cards, and compress redundant memories. Runs async, non-blocking. Uses existing dream_scheduler.go infrastructure. | validated | memory | large | system | - | Dream scheduler runs but produces no extracted conclusions. Memory compression does not occur; raw turns accumulate without distillation. |
| 3 / 3.H | Goncho skill-outcome tracking as conclusions — Track skill invocations with outcomes (success/failure, parameters, lessons learned) as Goncho conclusions. Source field references skill name. Enables surfacing ‘you solved this before’ patterns. No new schema — uses existing conclusions table with source=‘skill: | validated | memory | small | system, gateway | - | Skill outcomes are not tracked. Agent cannot surface prior skill usage or lessons learned from previous sessions. |
| 3 / 3.H | Goncho workspace isolation with explicit global scope — Strict workspace-scoped memory by default. Conclusions and memories carry a scope field (workspace vs global). Auto-detect workspace from project root (.git, go.mod, pubspec.yaml). Global-scope memories (e.g., coding preferences) cross workspace boundaries. | validated | memory | medium | system, operator | - | All memories are workspace-scoped. No global-scope sharing. Cross-project learning impossible. |
| 4 / 4.A | Provider interface + stream fixture harness — Provider-neutral request and stream event transcript harness | validated | provider | medium | system | internal/llm provider transcript fixtures | Provider status reports missing fixture coverage or unavailable adapters before kernel routing can select them. |
| 4 / 4.A | Hermes provider registry and alias manifest — Gormes owns a source-backed provider registry manifest that enumerates Hermes canonical provider IDs, aliases, models.dev mappings, transport family, auth type, aggregator flag, env vars, base-url overrides, and current implementation status before claiming provider/model/auth parity. Unsupported providers remain manifest-visible and row-backed rather than disappearing from model picker, status, docs, or runtime planning. | validated | provider | medium | operator, gateway, system | internal/llm/provider_registry_manifest_test.go::TestHermesProviderRegistryManifestCoversUpstream | Unknown provider IDs, missing aliases, stale chat —provider choices, or unclassified auth modes fail the manifest test with provider_registry_drift. The manifest must not require live models.dev, live provider credentials, browser auth, or network discovery. |
| 4 / 4.A | OpenRouter Pareto router request plugin — Gormes’ OpenAI-compatible provider request serializer ports Hermes’ OpenRouter Pareto Code router plugin boundary: when the selected OpenRouter model is exactly openrouter/pareto-code and the operator supplies a valid min-coding-score knob, the request body includes extra_body.plugins = [{"id":"pareto-router","min_coding_score": score}]; all other models, providers, unset scores, and invalid scores omit the plugin block. | validated | provider | small | operator, system | internal/llm/openrouter_pareto_test.go | Invalid or out-of-range Pareto scores are dropped before provider I/O, matching Hermes’ safe omission behavior; no live OpenRouter credentials, model catalog calls, or provider network access are required to prove the request-shaping contract. |
| 4 / 4.A | Tool-call normalization + continuation contract — Cross-provider tool-call continuation contract | validated | provider | medium | system | internal/llm cross-provider tool continuation fixtures | Provider status reports transcript or continuation fixture gaps before adapters can be selected for tool-capable turns. |
| 4 / 4.A | DeepSeek/Kimi reasoning_content echo for tool-call replay — Thinking-mode providers that require reasoning_content on assistant tool-call turns receive an echoed value during persistence and API replay | validated | provider | small | system | internal/llm/reasoning_content_echo_test.go | Provider status explains when a thinking-mode provider requires reasoning echo padding and when a stored transcript was repaired for replay. |
| 4 / 4.A | DeepSeek/Kimi cross-provider reasoning isolation — DeepSeek and Kimi replay of assistant tool-call turns injects an empty reasoning_content placeholder before promoting generic stored reasoning from another provider | validated | provider | small | system | internal/llm/reasoning_content_echo_test.go | Provider status reports cross-provider reasoning isolation as unavailable until replay fixtures prove prior-provider reasoning is not forwarded to DeepSeek/Kimi tool-call continuations. |
| 4 / 4.A | DeepSeek/Kimi all-assistant reasoning_content replay — DeepSeek, Kimi, and Moonshot OpenAI-compatible replays include provider-required reasoning_content on every assistant message while preserving Gormes’ cross-provider reasoning isolation | validated | provider | small | system | internal/llm/reasoning_content_echo_test.go | Provider status reports all-assistant reasoning_content padding as unavailable until fixtures prove plain assistant turns and tool-call turns both satisfy thinking-provider replay requirements. |
| 4 / 4.A | Moonshot/Kimi tool-schema sanitizer — Moonshot/Kimi OpenAI-compatible requests rewrite advertised tool schemas to Moonshot’s stricter flavored JSON Schema subset only when the model slug targets Kimi/Moonshot | validated | provider | small | system | internal/llm/moonshot_schema_test.go | Non-Moonshot OpenAI-compatible providers keep the generic tool schema sanitizer; Moonshot/Kimi requests get deterministic synthetic types and anyOf parent-type repair before the request leaves internal/llm. |
| 4 / 4.A | Azure OpenAI query/default_query transport contract — OpenAI-compatible Azure endpoints preserve api-version query parameters and keep GPT-5.x deployments on chat/completions with max_completion_tokens semantics instead of upgrading to Codex Responses | validated | provider | small | system, operator | internal/llm/azure_openai_transport_test.go | Provider status reports azure_query_preserved, azure_chat_completions, or azure_transport_unavailable evidence instead of silently dropping api-version or switching GPT-5.x to Responses. |
| 4 / 4.A | Azure Anthropic Messages endpoint contract — Azure Anthropic endpoints use static Azure keys as Authorization: Bearer credentials, strip trailing /v1 before Messages requests, and pass api-version through query parameters without invoking Anthropic OAuth refresh | validated | provider | small | system, operator | internal/llm/azure_anthropic_transport_test.go | Provider status reports azure_anthropic_key_missing, azure_api_version_query, or azure_oauth_bypassed evidence before any live Messages request is attempted. |
| 4 / 4.A | Azure Foundry transport probe read model — Azure Foundry endpoint probing determines OpenAI-style, Anthropic-style, or manual-required transport from deterministic inputs without reading credentials or writing config | validated | provider | small | operator, system | internal/llm/azure_foundry_probe_test.go | Probe status reports azure_transport_detected, azure_models_probe_failed, azure_anthropic_probe_failed, or azure_detect_manual_required evidence while preserving manual endpoint entry. |
| 4 / 4.A | Azure Foundry probe — path sniffing — TDD packet for a missing pure helper; create exactly two files and do not edit the already validated /models probe. First add internal/llm/azure_foundry_path_sniff_test.go in package llm with TestClassifyAzurePath table subtests named exact_anthropic, trailing_anthropic, nested_anthropic, mixed_case, bare_host_unknown, openai_path_unknown, substring_false_positives, and empty_and_malformed_unknown. Then add internal/llm/azure_foundry_path_sniff.go exposing ClassifyAzurePath(rawURL string) AzureTransport that reuses the AzureTransport type/constants from internal/llm/azure_foundry_models_probe.go. Algorithm: url.Parse; empty path or parse error => AzureTransportUnknown; lowercase and strings.TrimRight(parsed.Path, ”/”); return AzureTransportAnthropic only when the path equals “/anthropic”, ends with “/anthropic”, or contains “/anthropic/”. This slice never returns AzureTransportOpenAI and never opens HTTP, reads env/config, writes files, starts goroutines, or changes ProbeAzureFoundry. | validated | provider | small | operator, system | internal/llm/azure_foundry_path_sniff_test.go (new file)::TestClassifyAzurePath/substr_false_positive | Probe status reports azure_path_sniff_unknown when no path heuristic matches, and azure_path_sniff_evidence with detected scheme/host/path otherwise. |
| 4 / 4.A | Azure Foundry probe — /models classification + Anthropic fallback — Pure helper internal/llm/azure_foundry_models_probe.go exposes type AzureProbeResult struct{Transport AzureTransport; Models []string; Reason string; Evidence []string} and ProbeAzureFoundry(ctx, client *http.Client, base, apiKey string) (AzureProbeResult, error). Probes | validated | provider | small | operator, system | internal/llm/azure_foundry_models_probe_test.go | Probe status reports azure_models_probe_failed, azure_anthropic_probe_failed, or azure_detect_manual_required when classification cannot be made; manual api_mode entry remains available. |
| 4 / 4.A | Azure Foundry runtime env/config read model — Azure Foundry runtime resolution combines detected api_mode, deployment/model, redacted env/config evidence, and provider context metadata without contacting Azure or storing plaintext secrets | validated | provider | small | operator, system | internal/llm/azure_foundry_runtime_test.go | Provider status reports azure_foundry_base_url_missing, azure_foundry_key_missing, azure_context_known, azure_context_unknown, and azure_secret_redacted evidence before any live request. |
| 4 / 4.A | Azure Foundry CLI setup/status manual fallback — Azure Foundry CLI/status surfaces expose probe/runtime evidence and manual api_mode/deployment fallback without running a live setup wizard in tests | validated | tools | small | operator | internal/cli/azure_foundry_status_test.go | CLI status reports azure_detect_manual_required, azure_models_probe_failed, azure_context_unknown, and secret-redacted runtime evidence while preserving manual api_mode and model entry. |
| 4 / 4.A | Azure Foundry Responses-only model-family API mode — Azure Foundry runtime resolution upgrades GPT-5.x, codex, o1, o3, and o4 deployment/model names to codex_responses unless the configured api_mode is anthropic_messages, while leaving GPT-4o, GPT-4.1, Llama, Mistral, Grok, empty names, and explicit non-Anthropic configured modes on their existing mode | validated | provider | small | operator, system | internal/llm/azure_foundry_api_mode_test.go | Provider status reports azure_foundry_api_mode_inferred, azure_foundry_api_mode_preserved, or azure_foundry_api_mode_unknown evidence instead of sending Responses-only Azure deployments to /chat/completions. |
| 4 / 4.A | Bedrock provider runtime binding — Gormes closes the Bedrock provider plan by binding the already validated Converse request mapper, ConverseStream decoder, SigV4 credential seam, and stale-client retry classifier into a fakeable provider runtime: config/env choose bedrock_converse, region/auth evidence is redacted, fake signed requests route through the shared ProviderTransport harness, stream fixtures normalize through internal/llm.Event, and runtime status distinguishes unavailable credentials from provider transport failures. | validated | provider | medium | operator, system | internal/llm/bedrock_provider_binding_test.go | Missing AWS credentials, unsupported model, signing failure, stale transport recovery, and Bedrock runtime errors produce bedrock_* status evidence without live AWS calls or raw secret/body leakage. |
| 4 / 4.A | Bedrock Converse payload mapping (no AWS SDK) — Pure Bedrock Converse request mapping over the shared provider message/tool contract | validated | provider | small | system | internal/llm/bedrock_converse_mapping_test.go | Provider status reports Bedrock as unavailable until request mapping fixtures pass and credential wiring lands. |
| 4 / 4.A | Bedrock stream event decoding (SSE fixtures) — internal/llm/bedrock_stream.go decodes synthetic Bedrock ConverseStream event maps into the shared hermes.Event model without AWS SDK clients: text deltas emit EventToken, reasoningContent.text deltas emit EventReasoning, contentBlockStart/contentBlockDelta/contentBlockStop toolUse chunks accumulate one ToolCall, messageStop maps stopReason to FinishReason, and metadata.usage maps inputTokens/outputTokens to the final EventDone | validated | provider | small | system | internal/llm/bedrock_stream_test.go | Provider status reports bedrock_stream_unavailable until fixtures prove text, reasoning, tool-use, interrupt, and usage event decoding; Bedrock remains request-mapping-only before this row lands. |
| 4 / 4.A | Bedrock SigV4 + credential seam — internal/llm/bedrock_sigv4.go exposes a transport-free Bedrock credential and signing seam: ResolveBedrockAuth(env map[string]string) BedrockAuthEvidence classifies AWS_BEARER_TOKEN_BEDROCK, AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY, AWS_PROFILE, AWS_CONTAINER_CREDENTIALS_RELATIVE_URI, AWS_WEB_IDENTITY_TOKEN_FILE, or missing credentials; ResolveBedrockRegion(env) chooses AWS_REGION, AWS_DEFAULT_REGION, then us-east-1; SignBedrockRequest(req *http.Request, creds StaticAWSCredentials, now time.Time) error adds deterministic SigV4 Authorization, X-Amz-Date, and X-Amz-Content-Sha256 headers for bedrock-runtime without opening the network | validated | provider | small | operator, system | internal/llm/bedrock_sigv4_test.go | Provider status reports bedrock_credentials_missing, bedrock_profile_selected, bedrock_bearer_selected, bedrock_static_key_selected, or bedrock_sigv4_unavailable without logging secret values. |
| 4 / 4.A | Bedrock stale-client eviction + retry classification — Bedrock runtime clients evict stale transport state without hiding request or validation failures | validated | provider | small | system | internal/llm/bedrock_stale_client_test.go | Provider logs and status distinguish stale transport recovery from non-retryable Bedrock request failures. |
| 4 / 4.A | Gemini Cloud Code request/stream mapper — Gormes ports the pure OpenAI-to-Code-Assist Gemini mapper from Hermes: message content becomes Gemini contents with systemInstruction separation, tool calls and tool results preserve ID/name/JSON arguments, tool_choice becomes Gemini toolConfig, Code Assist envelopes wrap requests with model/project metadata, non-stream responses normalize back into ChatResponse, and stream events unwrap into shared text/tool/error chunks. | validated | provider | medium | system, operator | internal/llm/testdata/gemini_cloudcode/*.json | Google safety/error envelopes return structured code_assist_error evidence with status, reason, Retry-After, and quota hints; tests never require Google OAuth or network access. |
| 4 / 4.A | OpenRouter compatible-provider routing — Gormes ports Hermes OpenRouter as an OpenAI-compatible provider route, not a standalone bespoke adapter: runtime resolution honors explicit openrouter/custom/base-url choices, emits Hermes app attribution headers, preserves model suffixes such as :free and :nitro, detects OpenRouter base URLs for model metadata and pricing, and maps retryable/fatal provider errors through the shared error classifier. | validated | provider | medium | system, operator | internal/llm/testdata/openrouter/*.json | Missing keys, non-OpenRouter custom URLs, rate limits, and upstream HTML/error bodies produce sanitized provider_setup or provider_unavailable evidence; no test uses a live OPENROUTER_API_KEY. |
| 4 / 4.A | OpenRouter Grok prompt-cache affinity header — OpenRouter-routed Grok models carry Hermes’ current xAI prompt-cache affinity contract: when a Gormes turn has a session id and the model slug starts with x-ai/grok- or xai/grok-, the OpenRouter HTTP request sends x-grok-conv-id equal to that session id. Non-Grok OpenRouter models, Grok requests without a session id, and non-OpenRouter routes must not receive the header. | validated | provider | small | system, operator | internal/llm/openrouter_compatible_test.go::TestOpenRouterGrokPromptCacheAffinityHeader | Without the header, Grok-on-OpenRouter turns can lose xAI backend affinity and prompt-cache hit rate across session turns even though the generic OpenRouter request succeeds. |
| 4 / 4.A | Google Code Assist project/quota resolver — Gormes ports the Google Code Assist control-plane helpers that Gemini Cloud Code depends on: build Google API headers from a token provider, discover/load Code Assist tier and managed project, run onboarding with free-vs-paid project requirements, retrieve quota buckets for /gquota-style status, resolve configured/env/managed project context, and classify VPC-SC/quota/Retry-After errors safely. | validated | provider | medium | system, operator | internal/llm/testdata/google_code_assist/*.json | Missing token, paid-tier project requirement, VPC-SC denial, quota exhaustion, and HTTP failures return code_assist_* evidence with sanitized response text and Retry-After; fake token providers keep unit tests hermetic. |
| 4 / 4.A | Codex Responses pure conversion harness — OpenAI Responses request/response conversion for Codex-compatible providers without live OAuth | validated | provider | small | system | internal/llm/codex_responses_adapter_test.go | Provider status reports Codex unavailable until Responses conversion fixtures pass and auth wiring is configured. |
| 4 / 4.A | Codex Responses assistant content role types — Codex Responses payload conversion emits role-correct text content parts: input_text for user messages and output_text for assistant replay messages | validated | provider | small | system | internal/llm/codex_responses_role_content_test.go | Codex provider status reports codex_responses_role_content_unavailable if assistant list-content replay would still send input_text to the Responses API. |
| 4 / 4.A | Codex Responses HTTP client binding — provider=openai-codex on the Go HTTP client routes OpenStream through the Codex Responses transport at /v1/responses instead of the generic chat-completions endpoint, then normalizes the provider JSON response back into Hermes stream events. | validated | provider | small | operator, system | internal/llm/provider_transcript_test.go::TestProviderTranscriptHarness_OpenAICodexUsesResponsesAPI | Codex OAuth refresh, account selection, and stale-token relogin remain blocked on the token-vault/auth rows; this binding only uses the configured endpoint and bearer token already supplied to NewHTTPClientWithProvider. |
| 4 / 4.A | Codex OAuth state + stale-token relogin — Codex OAuth state is Gormes-owned and stale refresh failures force explicit relogin | validated | provider | small | operator, system | internal/llm/codex_oauth_state_test.go | Auth status explains missing, stale, imported, or relogin-required Codex credentials without touching ~/.codex. |
| 4 / 4.A | Codex stream repair + tool-call leak sanitizer — Codex Responses streams repair empty output and reject leaked function-call text before parent history is updated | validated | provider | small | system | internal/llm/codex_stream_repair_test.go | Provider logs explain repaired empty output, leaked tool-call text, and unsupported Codex stream items. |
| 4 / 4.A | Cross-provider reasoning-tag sanitization — internal/llm owns one provider-neutral reasoning-tag sanitizer that strips model-emitted reasoning tags from stored assistant text, visible operator text, and resume recap text without mutating raw stream/transcript evidence | validated | provider | small | operator, system | internal/llm/reasoning_tag_sanitizer_test.go | Provider status reports reasoning_tag_sanitizer_unavailable when the shared sanitizer is not wired; raw stream fixtures remain available for audit even when rendered or stored text is sanitized. |
| 4 / 4.A | Tool-call argument repair + schema sanitizer — Provider tool-call arguments are repaired or rejected against available tool schemas before execution | validated | provider | small | system, child-agent | internal/llm/tool_call_argument_repair_test.go | Tool execution status reports schema-repair failures before a malformed provider call reaches the executor. |
| 4 / 4.A | OpenAI-compatible developer-role API-boundary swap — OpenAI-compatible request serialization maps internal system guidance to provider-expected developer role for GPT-5/Codex model families at the API boundary without mutating stored internal messages or non-matching provider payloads. | validated | provider | small | system | internal/llm/openai_compatible_role_test.go | Unknown model families keep system role and emit no role-swap claim; provider errors remain redacted. |
| 4 / 4.A | xAI Grok provider adapter — Gormes can route tool-call turns through xAI Grok API with native request/response mapping, streaming, and error classification | validated | provider | medium | system | internal/llm/grok_adapter_test.go | Provider status reports grok_unavailable until the adapter has request fixtures, stream decoding, and tool-call normalization. |
| 4 / 4.A | LM Studio provider adapter — Gormes can route turns through LM Studio local inference server with OpenAI-compatible request/response mapping | validated | provider | small | system | internal/llm/lmstudio_adapter_test.go | Provider status reports lmstudio_unavailable until the adapter has request fixtures and local-server discovery. |
| 4 / 4.A | Vision-unsupported provider retry (strip-images-and-resend) — When a provider returns a 4xx error whose body matches the vision-rejection phrase set, the provider stack strips all image_url / input_image / image content parts from the in-flight messages, marks the session as vision-unsupported (so future turns in the same session skip image attachment), and retries the same turn once. 5xx and timeout errors are NOT classified as vision-unsupported — those route to the existing transient-retry path. Phrase matching is best-effort English; expand the phrase set when new provider wordings are observed. | validated | provider | medium | operator, gateway, system | internal/llm/vision_unsupported_retry_test.go using fake HTTP transports that return vision-rejection bodies | When the same session’s vision-supported flag flips to false, subsequent image_url attachments are stripped pre-flight (before the provider call) so the session doesn’t keep paying the round-trip cost; the channel surfaces the resulting text-only behavior in operator logs at INFO level. |
| 4 / 4.A | Gormes Router config and route registry read model — Add an opt-in router config/read-model that exposes local routes for the user’s primary provider, user-owned provider free-tier candidates, optional local OpenAI-compatible endpoints, and custom OpenAI-compatible providers. It must parse routes, aliases, inbound API-key settings, fallback classes, and redacted status evidence without starting the HTTP server. | validated | provider | medium | operator, system | internal/provider/router/config_test.go | - |
| 4 / 4.A | Gormes Router setup wizard and provider-picker boundary — Add gormes setup router as a local-gateway setup path that enables the OpenAI-compatible Gormes Router without treating it as a normal upstream provider. Setup must preserve gormes setup provider for upstream provider credentials, offer configured/healthy route aliases and fallback choices, generate or reference an inbound local API key safely, and prevent accidental self-routing/recursion. | validated | provider | medium | operator, system | cmd/gormes/setup_router_test.go | If Router is treated as a normal provider, users can accidentally configure Gormes to call its own local endpoint or assume Router itself supplies free model capacity instead of routing to configured user-owned providers. |
| 4 / 4.A | Gormes Router OpenAI-compatible models/chat endpoint — Expose the first local Gormes Router HTTP service with GET /v1/models, POST /v1/chat/completions non-streaming, and local inbound API-key authentication over the route registry. The endpoint must use fakeable provider clients and Gormes request/response mapping rather than a blind reverse proxy. | validated | provider | medium | operator, system | internal/provider/router/server_test.go | - |
| 4 / 4.A | Gormes Router streaming SSE and fallback safety — Add stream:true SSE support and safe fallback routing. The router may retry/fallback on 429, 408, timeout, and 5xx before output starts; it must not fallback after emitting partial streamed output and must not fallback on auth, policy, or malformed-request errors. | validated | provider | medium | operator, system | internal/provider/router/stream_fallback_test.go | - |
| 4 / 4.A | Gormes Router health/status counters and redacted logs — Add router health checks, quota/status counters, and redacted operational logging for the local router. Status must expose provider/model route health, attempt/success/failure/fallback counters, quota/usage signals when available, and last error class without exposing raw secrets or raw upstream bodies. | validated | provider | medium | operator, system | internal/provider/router/status_test.go | - |
| 4 / 4.A | CLIProxyAPI-compatible upstream route adapter — After the Gormes Router MVP exists, optionally allow a CLIProxyAPI server to be configured as a normal OpenAI-compatible upstream base URL. This must not import CLIProxyAPI runtime code, management APIs, OAuth automation, or multi-account pooling; it only treats CLIProxyAPI as a user-configured upstream endpoint. | validated | provider | medium | operator, system | internal/provider/router/cliproxy_upstream_test.go | - |
| 4 / 4.B | ContextEngine interface + status tool contract — Stable context engine status and compression boundary | validated | provider | medium | operator, system | internal/llm/testdata/context_status and internal/kernel context-engine replay fixtures | Context status reports disabled compression, cooldowns, unknown tools, token-budget pressure, and replay gaps. |
| 4 / 4.B | Compression token-budget trigger + summary sizing — Go context compression budget state recalculates threshold, tail, and summary token budgets whenever the active model context window changes | validated | provider | small | operator, system | internal/llm/context_compressor_budget_test.go | Context status reports compression disabled or unavailable instead of using stale budget values from a previous model window. |
| 4 / 4.B | Aux compression headroom for system and tool schemas — Compression feasibility lowers the raw-message threshold below the auxiliary model context window by reserving deterministic system-prompt, tool-schema, flush-instruction, and safety headroom | validated | provider | small | operator, system | internal/llm/context_compressor_headroom_test.go | Context status reports headroom-clamped compression thresholds instead of allowing the first compression or flush-memory request to overflow the auxiliary model. |
| 4 / 4.B | Aux compression provider-aware context cap — Auxiliary compression feasibility resolves the auxiliary model context window with the active provider identity before applying headroom and threshold budgets | validated | provider | small | operator, system | internal/llm/context_compressor_provider_cap_test.go | Context status reports provider-cap source, raw-model fallback, or missing auxiliary context evidence instead of budgeting compression against the wrong raw OpenAI-compatible window. |
| 4 / 4.B | Tool-result pruning + protected head/tail summary — Gormes freezes the pure context-compression pruning pass before kernel mutation: protect system and first-turn head messages, choose the recent tail by token budget with at least three messages, keep assistant tool_calls paired with their tool results, prune old oversized tool result content without cutting tool-call arguments or JSON payloads, and emit summary-prefix-compatible replacement messages. | validated | provider | medium | operator, system | internal/llm/context_compressor_pruning_test.go | Context status reports pruning_skipped, prune_budget_unavailable, or invalid_tool_pair evidence instead of silently truncating JSON arguments, dropping required tool results, or mutating live history. |
| 4 / 4.B | Aux compression single-prompt threshold reconciliation — Auxiliary compression budgeting follows Hermes 5006b220 by treating the summarizer request as raw messages plus one small user instruction, not as a system-prompt-plus-tool-schema memory-flush request | validated | provider | small | operator, system | internal/llm/context_compressor_single_prompt_test.go | Context status reports whether a threshold used legacy_headroom, single_prompt_aux, provider_cap, or unavailable evidence so operators can see why compression starts early or late. |
| 4 / 4.B | Compression protected-tail multimodal length estimator — Context-compression tail/protect-boundary code uses one pure content-length estimator that counts plain strings by rune length, list/map text blocks by their text field length, bare string list items by their own length, unknown list items by fmt.Sprint length, and image-only blocks as zero text chars; it must never call .get-style assumptions on non-map content and must not mutate message history | validated | provider | small | operator, system | internal/llm/context_compressor_content_test.go | Context status reports compression_length_estimator_unavailable and falls back to conservative tail preservation instead of crashing on mixed multimodal content. |
| 4 / 4.B | Context compressor image-token budget charge — Context-compression budget accounting charges a flat image token equivalent for image_url, input_image, and image content parts while keeping raw base64 payload length out of text-char estimates and preserving the existing text-only estimator boundary | validated | provider | small | operator, system | internal/llm/context_compressor_image_budget_test.go::TestCompressionContentBudgetLength_ImageParts | Context status reports image_budget_estimator_unavailable and uses conservative compression thresholds instead of treating image-heavy turns as near-empty. |
| 4 / 4.B | Context references stable-handle store — Gormes parses Hermes-compatible @ context references and allocates deterministic pending handles through a Go-native context-reference store without expanding files, running git, fetching URLs, or mutating kernel message history | validated | provider | small | operator, system | internal/llm/context_references_test.go; internal/persistence/transcript/refs_test.go | Reference handles remain pending until a later context-injection row attaches, blocks, or expands source content; handle allocation requires no provider, filesystem, git, URL, or kernel side effect. |
| 4 / 4.B | Manual compression feedback renderer + focus parser — Gormes splits the stale manual-compression placeholder into a pure helper row: parse /compress commands into an optional focus topic, render Hermes-compatible manual-compression feedback from before/after message counts and token estimates, report no-op compression without a success banner, explain when fewer messages estimate as more tokens, and surface session-split evidence without mutating live history. | validated | provider | small | operator, system | internal/llm/manual_compression_feedback_test.go | Manual compression unavailable, no-op compression, token-estimate increase, and session-split sync cases return manual_compression_* evidence with counts and approximate token totals only; raw prompt text, summaries, tool results, and provider payloads are never logged by the renderer. |
| 4 / 4.B | ContextEngine compression-boundary callback vocabulary — internal/llm defines a compression-boundary callback vocabulary on ContextEngine with stable lineage evidence and status fields, without binding kernel compression execution yet | validated | provider | small | operator, system | internal/llm/context_engine_boundary_test.go::TestContextEngineCompressionBoundaryVocabulary | Context status reports compression_boundary_unavailable or last_boundary_missing evidence until kernel compression execution binds the callback. |
| 4 / 4.B | Kernel compression-boundary callback binding — Kernel compression execution calls the validated ContextEngine boundary callback exactly once after a successful compression result is accepted into transcript lineage | validated | provider | small | operator, system | internal/kernel/compression_boundary_test.go::TestKernelCompressionBoundary_* | Until kernel compression execution is wired, context status keeps reporting last_boundary_missing instead of implying context-engine caches were reset. |
| 4 / 4.B | ContextEngine session-end hook on reset — Kernel session reset and session-rotation boundaries notify the active ContextEngine with the outgoing session id and transcript before clearing local state, then run the existing reset cleanup, mirroring Hermes commit_memory_session calling context_compressor.on_session_end without tearing providers down. | validated | provider | small | operator, system | internal/kernel/contextengine_test.go::TestKernel_ResetSession_NotifiesContextEngineSessionEndBeforeReset | If no ContextEngine is configured, reset behavior remains unchanged. If the ContextEngine hook fails, reset remains best-effort and still clears local state like Hermes’ failure-tolerant lifecycle hook. |
| 4 / 4.B | Gormes-owned session tree navigator over lineage and labels — Add a native /tree session navigator that projects Gormes’ existing session lineage, fork, compression, and title metadata into an in-place tree view with search/filter modes and operator labels. Selecting a prior user turn should restore that prompt for editing when safe; selecting non-user entries should switch the visible leaf or report why the stored transcript cannot be replayed. The implementation must use Gormes session stores and lineage tables, not Pi JSONL files. | validated | tui | medium | operator, system | internal/tui/tree_selector_test.go | - |
| 4 / 4.C | Default agent identity / SOUL.md loader — Native prompt assembly loads the default agent identity from SOUL.md or a built-in fallback, matching Hermes DEFAULT_AGENT_IDENTITY behavior | validated | provider | small | operator, system | internal/llm/identity_loader_test.go | If SOUL.md is missing, the built-in fallback identity is used and records soul_md_fallback evidence |
| 4 / 4.C | Context-file discovery + injection scan — Native prompt assembly exposes a pure context-file discovery helper that mirrors Hermes project-context precedence: load SOUL.md from the Hermes/Gormes profile unless skipped, then load exactly one project context source in order .hermes.md/HERMES.md walking up to git root, AGENTS.md/agents.md in cwd, CLAUDE.md/claude.md in cwd, then .cursorrules plus sorted .cursor/rules/*.mdc in cwd. Each loaded source is scanned for Hermes-compatible injection/invisible-character patterns and head/tail truncated to the context-file budget before being rendered into a deterministic prompt block. | validated | provider | medium | system, operator | internal/llm/context_files_test.go::TestContextFiles* | If profile or project context files are absent, unreadable, blocked by the injection scan, or larger than the budget, the helper returns deterministic empty/blocked/truncated evidence instead of reading live secrets, widening filesystem scope, or falling back to Python Hermes runtime services. |
| 4 / 4.C | Progressive subdirectory hint tracker — Gormes ports Hermes’ progressive subdirectory hint discovery as a pure prompt-context helper: after tool calls that mention paths or terminal workdirs, discover AGENTS.md/agents.md, CLAUDE.md/claude.md, or .cursorrules from newly visited directories and bounded ancestors, scan/truncate the hint content with the same safety policy as startup context files, format it as appendable tool-result context, and never mutate the system prompt or prompt-cache key. | validated | provider | small | system, operator | internal/llm/subdirectory_hints_test.go | Unreadable directories, permission errors, malformed shell command strings, URLs, git remotes, already-loaded directories, empty hint files, and over-budget hint files return subdirectory_hint_* evidence or no-op results without blocking the tool result or widening filesystem access. |
| 4 / 4.C | Model-specific role and tool-use guidance — Pure native prompt/provider helper selects the API-facing system role and model guidance without calling any provider: gpt-5 and codex-family models emit developer-role system content at the adapter boundary, other models keep system; tool-use enforcement guidance is injected only when valid tool names exist and config enables always/auto/list matching; Google guidance is injected for gemini/gemma families, OpenAI/Codex guidance for gpt/codex families; the internal Gormes transcript keeps system-role semantics so later adapters own provider-specific role translation. | validated | provider | small | operator, system | internal/llm/model_guidance_test.go | Malformed tool_use_enforcement config falls back to Hermes auto mode and records tool_use_enforcement_defaulted evidence; missing tools suppress the guidance block instead of adding model-visible promises about unavailable tools. |
| 4 / 4.C | Toolset-aware skills prompt snapshot — Native prompt assembly builds a deterministic Hermes-compatible skills index snapshot filtered by available tools/toolsets: SKILL.md and DESCRIPTION.md metadata are reduced into a manifest keyed by relative path, mtime, and size; an in-process LRU cache is keyed by local skill root, external roots, sorted available tools, sorted available toolsets, platform hint, and disabled skill names; an optional disk snapshot is reused only when manifest version and file metadata match; metadata.hermes.fallback_for_toolsets, requires_toolsets, fallback_for_tools, and requires_tools hide or show skills exactly like Hermes; local active skills win over external duplicates; output categories and skill names are sorted and deduped. | validated | skills | medium | operator, system | internal/llm/skills_prompt_test.go | If the snapshot is missing, stale, malformed, or unwritable, Gormes falls back to a full active-skill scan and records skills_prompt_snapshot_miss or skills_prompt_snapshot_write_failed evidence without injecting disabled, unsupported, or unavailable skills. |
| 4 / 4.C | Memory guidance constant + injection — Native prompt assembly injects the MEMORY_GUIDANCE block from Hermes agent/prompt_builder.py when memory tools are available | validated | provider | small | operator, system | internal/llm/memory_guidance_test.go | If memory guidance is unavailable, the prompt omits the block and records memory_guidance_suppressed evidence |
| 4 / 4.C | Session search guidance constant + injection — Native prompt assembly injects the SESSION_SEARCH_GUIDANCE block from Hermes agent/prompt_builder.py when session search is enabled | validated | provider | small | operator, system | internal/llm/session_search_guidance_test.go | If session search guidance is unavailable, the prompt omits the block and records session_search_guidance_suppressed evidence |
| 4 / 4.C | Gormes self-help skill/docs prompt guidance — Native prompt assembly exposes a pure guidance block that tells the model, when the user asks about configuring, setting up, troubleshooting, or using gormes/Gormes itself, to consult Gormes-owned docs/skills before answering; the block must use gormes/Gormes product wording, must not emit deprecated Gormes Agent or upstream Hermes Agent product wording, and must be gated so unrelated user prompts do not receive extra self-help text. | validated | provider | small | system, operator | internal/llm/self_help_guidance_test.go::TestGormesSelfHelpGuidance* | If a future Gormes self-help skill is unavailable, prompt assembly still points to the local Astro/Starlight docs surface and emits self-help-unavailable evidence instead of silently falling back to deprecated Gormes Agent or upstream Hermes Agent wording. |
| 4 / 4.C | [SYSTEM:→[IMPORTANT: meta-instruction prefix rename for Azure content filter compatibility — Single rename of the bracketed meta-instruction prefix used for skill-invocation and cron-heartbeat prompts: change every occurrence of the literal string “[SYSTEM:” to “[IMPORTANT:” in (a) internal/skills/commands.go (currently at line 94 inside the BuildSkill block string template) and (b) internal/automation/cron/heartbeat.go’s CronHeartbeatPrefix constant. The replacement is byte-for-byte over a 7→10 character literal; semantic meaning to the model is unchanged. Update the two existing assertions in internal/skills/preprocessing_commands_test.go (string literal [SYSTEM: The user has invoked) and the two assertions in internal/automation/cron/heartbeat_test.go (HasPrefix and contained “[SYSTEM:”) to match the new prefix. No public Go API renames, no struct shape changes, no provider/transport edits, no schema migrations. | validated | provider | small | system | internal/automation/cron/heartbeat_test.go | Cron-heartbeat and skill-invocation prompts continue to assemble in the same shape; only the leading bracketed marker token differs. Azure OpenAI content-filter (Default/DefaultV2) no longer rejects with HTTP 400 ‘prompt-injection attempt’ when these markers appear at message head. |
| 4 / 4.C | Native full prompt assembly — Native prompt assembly combines all validated blocks into a single Hermes-compatible system prompt | validated | provider | medium | operator, gateway, child-agent, system | internal/llm/prompt_assembly_test.go | If any block is missing, assembly continues with the remaining blocks and records missing_block evidence; the prompt never falls back to Python Hermes runtime |
| 4 / 4.C | Ephemeral prefill messages file injection — Port Hermes’ ephemeral prefill-message behavior into the native Go turn path. Gormes loads a JSON array of message objects from an explicit prefill file setting, resolves relative paths from the Gormes runtime home while preserving Hermes-compatible env/config aliases, validates the payload without logging content, and injects copies of those messages into provider-bound API messages immediately after the effective system prompt and before conversation history/user input. Prefill messages are API-call-time only: they must not be written to session history, channel transcripts, memory/Goncho stores, debug logs, or visible TUI/gateway output. | validated | provider | medium | operator, gateway, system | internal/llm/prefill_messages_test.go + internal/gateway/prefill_messages_test.go + internal/kernel/prefill_messages_test.go | Missing file, unreadable file, malformed JSON, non-array JSON, unsupported role/content shape, or oversized payload returns empty prefill plus typed prefill_messages_unavailable evidence with the redacted path and reason. Gateway and TUI turns continue without prefill instead of failing startup or sending placeholder text to the provider. |
| 4 / 4.D | Provider-enforced context-length resolver — Displayed and budgeted context windows prefer provider-enforced limits over raw models.dev metadata | validated | provider | small | operator, system | internal/llm/model_context_resolver_test.go | Model status reports whether the context length came from provider-specific caps, models.dev fallback, or an unknown model. |
| 4 / 4.D | Model pricing/capability registry fixtures — Read-only model metadata exposes deterministic pricing, capability flags, provider family, and raw context facts before routing consumes them | validated | provider | small | operator, system | internal/llm/model_registry_test.go | Model status distinguishes unknown pricing, unknown capability, and stale embedded registry data instead of inventing defaults. |
| 4 / 4.D | Ollama Cloud models.dev suffix normalization — Gormes normalizes Ollama Cloud model IDs from models.dev before merging static metadata with live provider/model picker data, stripping Hermes-compatible :cloud and -cloud suffixes while preserving unsuffixed IDs and deduplicating clean live IDs. | validated | provider | small | operator, system | internal/llm/ollama_cloud_models_test.go | Model picker/status reports ollama_cloud_model_normalized or ollama_cloud_model_metadata_unavailable evidence instead of showing duplicate suffixed/unsuffixed Ollama Cloud entries or selecting a suffixed ID that the live API rejects. |
| 4 / 4.D | Model catalog cache + preferred-provider live merge — Gormes ports Hermes model-catalog freshness behavior behind injectable fetch/cache seams: manifest validation rejects malformed or future-version catalogs, fetch writes an XDG-scoped cache and uses in-process reuse, network failures fall back to disk or stale cache, disabled config short-circuits fetches, override URLs win, Vercel AI Gateway pricing is translated into prompt/completion/cache fields, free Moonshot/Kimi AI Gateway models can be promoted into recommended lists, and preferred providers such as opencode-go/opencode-zen merge fresh models.dev IDs before curated extras without changing OpenRouter/Nous fallback behavior. | validated | provider | medium | operator, system | internal/llm/model_catalog_test.go; cmd/gormes/model_catalog_test.go | Fetch failures, disabled catalog config, malformed manifests, stale cache, invalid override URL, and missing credentials return model_catalog_* evidence while preserving the embedded provider registry and never requiring live models.dev or provider credentials in unit tests. |
| 4 / 4.D | Routing policy and fallback selector — Smart model routing is a pure selector over explicit overrides, provider availability, fallback policy, and model metadata before any provider call switches models | validated | provider | small | operator, system | internal/llm/model_routing_test.go | Routing status reports metadata gaps, unavailable providers, and disabled fallback routes before changing a turn’s model. |
| 4 / 4.D | Per-turn model selection — Kernel accepts a per-turn model override, sends it on exactly one hermes.ChatRequest, exposes the active model in RenderFrame during that turn, and falls back to the resident config model on the next turn | validated | provider | small | operator, gateway, system | internal/kernel/per_turn_model_test.go | If no per-turn model is supplied or the override is blank, the kernel continues using cfg.Model and status frames make the resident model explicit. |
| 4 / 4.D | Per-turn reasoning effort propagation — Kernel and provider request models carry an optional reasoning effort for exactly one turn or session without mutating resident model/provider configuration | validated | provider | small | operator, gateway, system | internal/kernel/per_turn_reasoning_test.go and internal/llm/reasoning_effort_request_test.go | Status frames and provider request evidence distinguish unsupported reasoning effort, default reasoning, disabled reasoning, and explicit per-turn/session overrides. |
| 4 / 4.D | Provider-default model resolution at config load — When hermes.provider is set and hermes.model is empty or still equals the upstream Hermes-fleet placeholder ‘hermes-agent’, Gormes resolves the effective model from the provider’s catalog instead of sending ‘hermes-agent’ to the provider. Resolution mirrors Hermes’ get_default_model_for_provider(provider) — first entry in the provider’s best-known model list. For openai-codex, config load stays offline: prefer the operator’s Codex CLI default model, then the local Codex model cache ordered by picker priority, then the curated DEFAULT_CODEX_MODELS fallback from Hermes. | validated | provider | small | operator, system | internal/llm/provider_default_model_test.go; internal/config/provider_default_model_test.go; cmd/gormes/gateway_default_model_test.go; cmd/gormes/doctor_default_model_test.go | When no local Codex model preference or cache is available, fall back to the curated catalog and surface the fallback choice in gormes doctor —offline evidence so operators see why a particular default was selected. |
| 4 / 4.D | OpenAI Codex Spark catalog and context parity — Gormes mirrors Hermes OpenAI Codex model catalog behavior for gpt-5.3-codex-spark: curated Codex fallbacks include Spark, local Codex model-cache selection keeps Codex CLI-only entries even when supported_in_api is false, model picker suggestions expose Spark for openai-codex, and provider-aware context resolution reports Spark’s 128k Codex OAuth window instead of substring-matching gpt-5.3-codex to 272k. | validated | provider | small | operator, system | internal/llm/provider_default_model_test.go; internal/llm/model_catalog_test.go; internal/llm/model_context_resolver_test.go; internal/llm/model_registry_test.go | If Spark is absent from the operator’s local Codex cache or account entitlement, Gormes still lists it only as a curated/offline Codex candidate and leaves runtime provider rejection to the existing provider error path; no live Codex API call or entitlement probe is introduced. |
| 4 / 4.D | Image input mode resolver + vision_analyze text fallback — Manager.submitPinned consults DecideImageInputMode before calling imageContentPartsFromAttachments. When mode resolves to ‘native’ (vision-capable model + no auxiliary.vision override), behavior is unchanged. When mode resolves to ‘text’ (text-only or unknown model OR explicit auxiliary.vision config OR explicit agent.image_input_mode=text), Gormes does not emit image_url parts; this bounded row prepends a typed vision_pre_analysis_unavailable degraded marker to SubmitText until a concrete vision_analyze backend is wired. The active model’s vision capability is read from internal/llm model metadata (internal/llm/model_registry.go). | validated | gateway | medium | operator, gateway, system | internal/gateway/manager_image_mode_test.go, internal/gateway/manager_photo_passthrough_test.go, internal/config/image_input_mode_test.go, and internal/llm/image_routing_test.go | When mode=text is selected but no vision_analyze backend is configured, the channel reports a typed degraded marker (vision_pre_analysis_unavailable) and falls back to the existing attachment-marker text rather than silently dropping the image or sending image_url to a non-vision provider that will reject it. |
| 4 / 4.E | Trajectory writer + redaction gates — Gormes ports Hermes’ opt-in trajectory writer as a native JSONL append boundary over transcript/kernel snapshots: scratchpad tags are converted to | validated | provider | small | operator, system | internal/persistence/transcript/trajectory_writer_test.go; internal/audit/trajectory_writer_test.go | Trajectory disabled, invalid output path, append failure, malformed snapshot data, and redaction failures return trajectory_disabled, trajectory_write_failed, or trajectory_redaction_failed evidence without blocking the turn or writing raw secrets. |
| 4 / 4.E | Trajectory compressor + compressed-evidence lineage — internal/persistence/transcript owns the pure Hermes trajectory compression decision and metrics algorithm, while internal/audit owns redacted compressed-lineage evidence that hashes summary text instead of storing it | validated | provider | small | operator, system | internal/persistence/transcript/trajectory_compressor_test.go | This slice does not call a live summarizer or process directories; callers must supply summary text and can still record lineage without leaking summary contents into audit JSONL. |
| 4 / 4.E | Self-monitoring telemetry — Gormes bridges Hermes turn/provider/tool telemetry and Honcho telemetry/reasoning traces into local redacted telemetry, audit, and insights evidence through SelfMonitoringBridge, TelemetryEventMatrix, ReasoningTraceRecord, TelemetrySink, AuditSink, and InsightsRecorder interfaces without changing the local usage.jsonl schema until compatibility tests pass. | validated | provider | medium | operator, system | internal/telemetry/self_monitoring_test.go; internal/goncho/telemetry_test.go | Telemetry emission failures, unavailable metrics exporters, and unsupported hosted Honcho tracing fields produce nonfatal local evidence instead of blocking turns, memory writes, or queue processing. |
| 4 / 4.F | Title prompt and truncation contract — Native title generation exposes a pure request/response boundary that builds Hermes-compatible title prompts from bounded session history, truncates candidate titles deterministically, returns empty-title fallback evidence for empty history or blank model output, and surfaces provider failures through a typed nonfatal error result without writing session metadata | validated | provider | small | operator, system | internal/llm/title_generator_test.go::TestTitle* | Title status reports auto_title_skipped, title_provider_failed, or title_blank_result evidence instead of silently leaving NULL titles with no operator-visible cause. |
| 4 / 4.F | Title auxiliary failure visibility — CLI and gateway title-generation callers route nonfatal title_provider_failed evidence through the same operator-visible auxiliary-failure channel used for other background provider failures, while swallowing callback errors so title generation cannot crash the foreground turn | validated | gateway | small | operator, gateway, system | internal/kernel/title_failure_test.go | If the failure callback is unavailable or itself fails, title generation records callback_failed evidence and preserves the foreground turn instead of panicking or hiding the provider failure. |
| 4 / 4.F | Auto-naming sessions — Session auto-naming persists exactly one generated title for eligible untitled sessions after the native title helper succeeds, respects manual title overrides, preserves historical transcript content, and records skip/failure evidence without retry storms | validated | provider | small | operator, system | internal/persistence/session/auto_title_test.go | Untitled sessions remain searchable with auto_title_skipped or title_provider_failed evidence instead of being repeatedly retried without user-visible status. |
| 4 / 4.G | Token vault — Gormes owns an XDG-scoped, Hermes-compatible credential file vault that resolves declared relative credential paths without path traversal, missing-file leaks, or cross-profile bleed. | validated | provider | small | operator, system | internal/config/token_vault_test.go | Missing, absolute, traversal, symlink-escaped, or unreadable credential files return structured redacted evidence and do not mount or expose host paths outside the active profile. |
| 4 / 4.G | Anthropic OAuth/keychain credential discovery — Gormes exposes a native Anthropic auth-state helper that discovers Claude Code OAuth credentials from an injectable macOS keychain seam before JSON files, preserves corrupt JSON as a recoverable backup, and classifies stale OAuth refresh failures as relogin-required without leaking secrets. | validated | provider | small | operator, system | internal/config/anthropic_auth_state_test.go | Auth status reports keychain unavailable, corrupt auth backup, or relogin-required without deleting credentials. |
| 4 / 4.G | Multi-account auth — Gormes exposes a provider-neutral multi-account credential-pool helper that can load persisted credential records, select an available credential by Hermes-compatible strategy, mark an active credential exhausted with redacted evidence, and rotate without reading live provider tokens or contacting provider/keychain services. | validated | provider | small | operator, system | internal/config/credential_pool_test.go | Empty pools, exhausted credentials, invalid strategy names, corrupt records, and unsafe credential payloads return structured redacted evidence while preserving the persisted pool for operator recovery. |
| 4 / 4.G | Credential non-ASCII sanitizer + one-shot warning — Gormes ports Hermes’ credential copy-paste sanitizer before provider headers, dotenv imports, and auth status read models use credential bytes: credential-looking keys such as API_KEY, TOKEN, AUTH_TOKEN, BOT_TOKEN, and credential-pool access/refresh tokens strip non-ASCII runes, report the stripped code points once per key with re-copy guidance, preserve non-credential Unicode values, preserve ASCII control bytes, and never log raw credential material. | validated | provider | small | operator, system | internal/config/credential_sanitizer_test.go; internal/config/dotenv_test.go; internal/config/credential_pool_test.go | Non-ASCII credential values produce credential_non_ascii_stripped evidence with key name, code points, warning-once status, and redacted preview only; malformed dotenv lines, unset env refs, and non-credential Unicode text keep existing degraded behavior without secret leaks. |
| 4 / 4.G | Google OAuth flow + refresh seam — Google Workspace OAuth setup stores pending PKCE state, accepts either a raw code or localhost redirect URL, preserves only actually granted scopes, and reports partial-scope tokens without forcing refresh invalid_scope failures | validated | provider | small | operator, system | internal/config/google_oauth_state_test.go | Auth status reports oauth_pending_missing, oauth_state_mismatch, token_partial_scope, token_corrupt, or refresh_failed evidence instead of deleting credentials or retrying with over-broad scopes. |
| 4 / 4.G | MiniMax OAuth provider registry and default auth routing — Gormes classifies Hermes’ minimax-oauth provider across the native provider manifest and CLI auth routing before any live MiniMax OAuth browser flow is ported: minimax-oauth and its aliases resolve as an Anthropic Messages OAuth provider, model picker/provider inventory can display it, and gormes auth add minimax-oauth defaults to the OAuth path with row-backed unavailable evidence instead of misclassifying it as an API-key provider. | validated | provider | small | operator, system | internal/llm/provider_registry_manifest_test.go; cmd/gormes/auth_oauth_command_test.go | Until the MiniMax OAuth PKCE flow is ported, auth add returns redacted provider-oauth-planned evidence; no browser, token endpoint, provider network, or live credential store is touched. |
| 4 / 4.G | GitHub Copilot token exchange + Responses mode selector — Gormes ports Hermes Copilot credential and request-mode behavior behind native auth/transport seams: resolve COPILOT_GITHUB_TOKEN, GH_TOKEN, GITHUB_TOKEN, then optional gh CLI token in order; reject classic ghp_ PATs while accepting OAuth/fine-grained/app token shapes; exchange raw GitHub tokens for Copilot API JWTs with fingerprinted cache and raw-token fallback on exchange failure; build Copilot headers with Openai-Intent, x-initiator, and vision flags; and select Responses API mode for current GPT-5 Copilot model families while keeping GPT-5-mini and non-GPT models on chat mode. | validated | provider | medium | operator, system | internal/config/copilot_auth_test.go; internal/llm/copilot_mode_test.go | Missing tokens, classic PATs, gh CLI failures, Copilot exchange network errors, empty exchanged tokens, expired cache entries, and unknown model-mode decisions return copilot_auth_* or copilot_mode_* evidence without logging raw GitHub/Copilot tokens or shelling out in tests. |
| 4 / 4.H | Provider-side resilience — Provider resilience umbrella over retry, cache, rate, and budget behavior | validated | provider | large | system | internal/llm and internal/kernel provider resilience fixtures | Provider and kernel status expose retry schedule, Retry-After hints, cache disabled paths, rate guards, and budget telemetry gaps. |
| 4 / 4.H | Classified provider-error taxonomy — Structured provider error classification contract | validated | provider | small | system | internal/llm provider error-classification fixture table | Provider status and logs expose auth, rate-limit, context, retryable, and non-retryable classes instead of raw opaque errors. |
| 4 / 4.H | Generic provider timeout message classifier — Provider errors with timeout-shaped plain messages classify as timeout retryable/fallback failures even when the concrete Go error type is generic, matching Hermes’ generic exception timeout handling for local provider shims. | validated | provider | small | system | internal/llm/errors_test.go::TestClassifyProviderError_GenericTimeoutMessages | Plain provider-shim timeout messages remain unknown and can be reported as empty/opaque model failures instead of retrying or falling back deterministically. |
| 4 / 4.H | Provider image-too-large error classification — Provider error classification distinguishes image_too_large failures from generic context overflow and retryable errors so multimodal callers can trigger bounded image-shrink recovery without retry loops | validated | provider | small | operator, system | internal/llm/errors_image_test.go::TestClassifyProviderError_ImageTooLarge | Provider status reports image_too_large_unclassified and refuses automatic image retry instead of retrying the same oversized payload or misreporting a text context overflow. |
| 4 / 4.H | Unsupported temperature retry + Codex no-temperature guard — Provider calls that actually send temperature retry once without it on unsupported-parameter errors, while Codex Responses payload conversion never emits temperature | validated | provider | small | system | internal/llm/unsupported_temperature_retry_test.go | Provider status records temperature-stripped retry evidence instead of treating unsupported temperature as an opaque fatal 400. |
| 4 / 4.H | Codex Responses temperature guard after flush removal — Codex Responses payload conversion keeps omitting temperature while removing obsolete memory-flush fixture names, source references, and docs language after Hermes deleted the memory-flush path | validated | provider | small | system | internal/llm/codex_responses_temperature_test.go | Provider status and docs explain no-temperature behavior as a Codex Responses transport rule independent of session-boundary memory behavior. |
| 4 / 4.H | Generic unsupported-parameter retry + max_tokens guard — OpenAI-compatible provider calls detect unsupported request parameters by name and retry max_tokens as max_completion_tokens only when the first payload actually sent max_tokens | validated | provider | small | system | internal/llm/unsupported_parameter_retry_test.go | Provider status records parameter-stripped retry evidence for supported reactive retries instead of surfacing opaque 400s or sending max_completion_tokens when no max_tokens was requested. |
| 4 / 4.H | Streaming interrupt retry suppression — Kernel stream cancellation and /stop-style events abort retry loops before any fresh provider stream is opened | validated | provider | small | operator, system | internal/kernel/stream_interrupt_retry_test.go | Render/status frames report interrupted retry suppression instead of reconnecting after the operator has cancelled a running turn. |
| 4 / 4.H | Provider stream-drop retry diagnostics — Kernel reconnect attempts for provider stream drops emit exactly one provider-named status frame per drop and a structured warning log with retry attempt, provider, endpoint, error kind, and error type evidence. | validated | provider | small | operator, system | internal/kernel/stream_drop_diagnostics_test.go | Operators see a generic reconnecting frame and must inspect unstructured logs to identify which provider or stream error class caused a retry. |
| 4 / 4.H | Provider stream-drop timing and upstream diagnostics — Kernel reconnect attempts for provider stream drops include Hermes’ current deep diagnostics in the structured warning log: compact error-chain text, upstream HTTP status/headers, streamed byte and chunk counts, elapsed attempt duration, and time-to-first-byte when available. The user-visible reconnect frame stays a single compact provider-named line with an elapsed suffix only when timing evidence exists. | validated | provider | small | operator, system | internal/kernel/stream_drop_diagnostics_test.go | Operators can see that a provider stream dropped, but cannot tell whether it failed before first byte, died mid-stream, came from a specific upstream edge/provider, or was wrapped by an inner transport error. |
| 4 / 4.H | Provider timeout config fail-closed helper — Provider timeout lookup handles missing or failed config loading by returning explicit unset evidence, and parses provider request/stale timeout overrides without panicking or applying stale defaults | validated | provider | small | operator, system | internal/llm/provider_timeout_config_test.go | Provider status reports timeout_config_unavailable, timeout_config_invalid, or timeout_unset evidence instead of crashing startup or silently applying a stale provider timeout. |
| 4 / 4.H | Prompt-cache capability guard — Gormes applies Hermes prompt-cache markers only when provider, endpoint, API mode, and model policy allow them: native Anthropic uses native layout, OpenRouter Claude uses envelope layout, third-party Anthropic Claude gateways cache conservatively, Qwen on opencode/opencode-go/Alibaba gets envelope markers, and OpenAI-wire custom providers without an allow rule strip cache_control visibly. | validated | provider | medium | operator, system | internal/llm/prompt_cache_policy_test.go | Provider status reports prompt_cache_supported, prompt_cache_stripped, prompt_cache_provider_unknown, or prompt_cache_policy_unavailable instead of leaking unsupported cache_control fields into strict providers. |
| 4 / 4.H | Provider account usage read model + renderer — Gormes exposes a Hermes-compatible provider account-usage read model with internal/llm.AccountUsageSnapshot and AccountUsageWindow, fakeable Codex/Anthropic/OpenRouter fetchers, and a shared redacted renderer that later CLI/status and gateway /usage command rows can bind without rediscovering provider payload semantics. | validated | provider | medium | operator, gateway, system | internal/llm/account_usage_test.go | Missing credentials, unsupported providers, upstream 401/429/5xx responses, malformed usage payloads, and unavailable account-usage endpoints produce typed unavailable/unsupported/degraded evidence instead of blocking normal provider turns or command dispatch. |
| 4 / 4.H | Gateway /usage command binding over provider account usage — Gormes binds Hermes CLI and gateway /usage behavior to the provider account-usage read model, preserving running-agent versus cached-agent precedence, transcript usage fallback, account-limit rendering, and redacted unavailable output without adding provider-client state. | validated | gateway | medium | operator, gateway, system | internal/gateway/usage_command_test.go; cmd/gormes/usage_command_test.go | No active agent, missing cached usage, unsupported providers, and provider-account failures render explicit unavailable/unsupported evidence instead of panics, raw tokens, or live network requirements in command tests. |
| 4 / 4.H | Provider rate guard + budget telemetry — Provider rate guards distinguish true account quota exhaustion from upstream-capacity 429s and expose budget telemetry without sleeping or blocking unrelated models in unit tests | validated | provider | small | system | internal/llm/provider_rate_guard_test.go | Provider status reports rate_guard_unavailable, nous_rate_limited, nous_upstream_capacity, budget_header_missing, and reset-window evidence instead of silently tripping a global breaker. |
| 4 / 4.H | Provider rate guard — x-ratelimit header classification — TDD packet for a missing pure helper that matches Hermes 192e7eb2 reset-window semantics; create exactly two files and do not wire a provider breaker. STEP 1: cd into the repo root and run ls internal/llm/provider_rate_guard*.go — both files must be absent before the worker writes them; if Classify429 already exists, the worker should run the focused test and update this row. STEP 2: write internal/llm/provider_rate_guard_classification_test.go in package llm with one TestClassify429 function holding a t.Run-driven table with named subtests genuine_quota_1h_reset, short_reset_upstream_capacity, healthy_remaining_upstream_capacity, missing_headers_insufficient, unknown_headers_ignored, malformed_values_ignored, and three_buckets_with_remaining_one_missing_returns_upstream_capacity. Each subtest builds an http.Header via h := http.Header{}; h.Set(…); h.Set(…). STEP 3: write internal/llm/provider_rate_guard.go exposing type RateLimitClass string with constants RateLimitGenuineQuota=“genuine_quota”, RateLimitUpstreamCapacity=“upstream_capacity”, RateLimitInsufficientEvidence=“insufficient_evidence” and func Classify429(headers http.Header) RateLimitClass. Algorithm: iterate the four Hermes Nous bucket tags (“requests”, “requests-1h”, “tokens”, “tokens-1h”) via the paired x-ratelimit-remaining-{tag} and x-ratelimit-reset-{tag} headers; use headers.Get, strings.TrimSpace, strconv.Atoi for remaining, strconv.ParseFloat for reset seconds. A bucket is exhausted only if remaining<=0 AND reset>=60 seconds (matches Hermes _MIN_RESET_FOR_BREAKER_SECONDS). If any bucket is exhausted, return RateLimitGenuineQuota. If at least one bucket parsed successfully and none are exhausted, return RateLimitUpstreamCapacity. Otherwise return RateLimitInsufficientEvidence. STEP 4: no shared breaker state, no time.Now, no sleeps, no retry policy, and no provider routing changes; imports are limited to net/http, strconv, and strings (plus testing in the test file). | validated | provider | small | system | internal/llm/provider_rate_guard_classification_test.go (new file)::TestClassify429/genuine_quota_1h_reset | Provider status reports rate_guard_classified as one of {genuine_quota, upstream_capacity, insufficient_evidence}; reset-window evidence is parsed for the decision but detailed budget telemetry waits for the dependent row. |
| 4 / 4.H | Provider rate guard — degraded-state + last-known-good evidence — TDD packet for a missing pure state-transition helper. The prerequisite (RateLimitClass + Classify429 in internal/llm/provider_rate_guard.go) is shipped on main as of commit a1d7d928; do NOT stub or duplicate those constants. STEP 1: write internal/llm/provider_rate_guard_degraded_test.go in package llm (importing only testing and time). Define t0 and t1 once at the top of TestApplyClassification: t0 := time.Date(2026,4,26,12,0,0,0,time.UTC); t1 := t0.Add(5*time.Minute). Add six t.Run subtests in this order — preserves_last_known_when_insufficient, treats_empty_class_as_insufficient, fresh_genuine_quota_clears_unavailable, fresh_upstream_capacity_clears_unavailable, input_immutability_via_struct_value, transitions_back_to_available_on_fresh_evidence — each calling ApplyClassification with explicit GuardState literals and asserting the returned struct field-by-field with t.Fatalf on mismatch. STEP 2: write internal/llm/provider_rate_guard_degraded.go in package llm (importing only time). Define type GuardState struct { LastKnownClass RateLimitClass; LastKnownAt time.Time; Unavailable bool } and func ApplyClassification(state GuardState, now time.Time, class RateLimitClass) GuardState. Algorithm: if class == RateLimitInsufficientEvidence OR class == RateLimitClass(""), return GuardState{LastKnownClass: state.LastKnownClass, LastKnownAt: state.LastKnownAt, Unavailable: true}. Otherwise return GuardState{LastKnownClass: class, LastKnownAt: now, Unavailable: false}. The helper takes its argument by value, never mutates it, never reads time.Now, never spawns a goroutine, and never touches retry/breaker code. | validated | provider | small | system | internal/llm/provider_rate_guard_degraded_test.go (new file)::TestApplyClassification/preserves_last_known_when_insufficient | Provider status reports last_known_good=present|absent, plus rate_guard_unavailable or budget_header_missing when classification is unsafe; no retry amplification. |
| 4 / 4.H | Hermes fast-mode request override serializer — Gormes ports the provider-bound portion of Hermes fast mode: a pure internal/llm resolver classifies eligible OpenAI flagship and Claude Opus 4.6 models, returns typed request overrides, and the native provider transports serialize only supported overrides (service_tier=priority for OpenAI-compatible/Codex Responses payloads, speed=fast plus the Anthropic fast-mode beta for eligible native Anthropic requests) without wiring the /fast command or live config persistence in this slice. | validated | provider | small | operator, gateway, system | internal/llm/fast_mode_request_overrides_test.go | Unsupported models return no override evidence, explicit unsupported speed/service_tier values are omitted from provider payloads, and callers can leave /fast unavailable until the command/config binding row lands. |
| 4 / 4.I | Python-free normal agent turn e2e harness — A normal CLI/API/gateway agent turn can execute entirely in Go from input through prompt/context assembly, fake provider tool call, Go tool execution, Goncho memory recall/persistence, and final response/audit evidence, with no Python bridge or hosted Honcho dependency. | validated | provider | medium | operator, gateway, system | internal/support/e2e/normal_turn_test.go::TestPythonFreeNormalAgentTurn | The e2e harness reports python_bridge_used, provider_step_missing, tool_loop_missing, goncho_step_missing, or audit_evidence_missing instead of passing a partial Go turn as complete. |
| 4 / 4.I | Provider-tool-memory golden transcript suite — Golden transcript fixtures capture provider request parts, tool-call continuation, Goncho memory evidence, final assistant message, and audit/status fields for representative Hermes-compatible turns without live credentials. | validated | provider | medium | operator, system | internal/support/e2e/testdata/normal_turn/*.json | Transcript replay reports fixture_missing, provider_part_mismatch, tool_continuation_mismatch, or memory_evidence_mismatch with the fixture name and field path. |
| 4 / 4.I | Hermes and Honcho feature parity map to Go implementation plan — A planner pass maps Hermes agent loop, provider routing, prompt assembly, tool continuation, memory plugin, skills/plugins, API, CLI, operator commands, and Honcho memory/server/SDK concepts into Go package targets, implementation strategy, proof gates, subsystem classifications, dependency order, and progress.json anchors. | validated | docs | small | operator, system | docs/content/building-gormes/architecture_plan/hermes-honcho-go-runtime-plan.md | Unmapped upstream behavior is reported as parity_unmapped with upstream path, Gormes package, and proposed row instead of being hidden in prose. |
| 4 / 4.I | Upstream source coverage ledger for Hermes/Honcho mapping completeness — A planner-facing ledger enumerates every feature-bearing Hermes and Honcho source class, maps each class to a feature-map anchor, Go package target, progress anchor, and coverage status, and defines the drift rule for deciding whether the feature map is complete. | validated | docs | small | operator, system | docs/upstream_coverage_test.go::TestUpstreamCoverageLedgerMatchesSourceClasses | If an upstream source class is missing from the ledger or lacks a feature-map/progress anchor, parity auditors report parity_unmapped and send the work to gormes-planner before builder selection. |
| 4 / 4.I | Swarm feature-level parity audit for Hermes/Honcho map — A five-lane sub-agent parity audit checks the Hermes/Honcho feature map beyond top-level source classes, records missing or vague feature-level surfaces, and maps every finding to a Go package target and progress action without implementing runtime code. | validated | docs | small | operator, system | docs/content/building-gormes/architecture_plan/swarm-feature-parity-audit.md | If any swarm lane reports missing or vague behavior, Gormes reports source_class_mapped_but_feature_level_incomplete and records the gap in Swarm Feature Parity Audit instead of claiming full feature parity. |
| 4 / 4.I | Hermes/Honcho Go runtime plan second-wave reconciliation — A second-wave sub-agent reconciliation turns the raw Hermes/Honcho swarm findings into one canonical Go runtime implementation plan, classifying every subsystem as owned, mapped-by-symbol, mapped-by-contract, excluded, or still row-backed, with no subsystem-level unknown/gap remaining. | validated | docs | small | operator, system | docs/content/building-gormes/architecture_plan/hermes-honcho-go-runtime-plan.md | If a future parity pass finds an unclassified subsystem, Gormes reports unknown_gap_in_runtime_plan and requires gormes-planner to update the runtime plan, feature map, swarm audit, upstream ledger, progress rows, and nested coverage test before builder selection. |
| 4 / 4.I | Nested feature-level coverage test matrix for swarm gaps — Docs tests verify selected nested upstream feature classes from the swarm audit, including Hermes provider transports/tools/plugins/skills/ACP/MCP and Honcho SDK/MCP/CLI/OpenAPI/deploy/test fixtures, so broad top-level source-class globs cannot hide unmapped behavior. | validated | docs | medium | operator, system | docs/upstream_coverage_test.go::TestNestedUpstreamFeatureCoverage | If a nested upstream path is not represented in the feature map, swarm audit, or progress row, the test reports nested_feature_unmapped with the upstream path and expected ledger evidence. |
| 4 / 4.I | Hermes website docs mirror coverage gate — Gormes docs keep the upstream Hermes documentation mirror source-backed from the active nested Hermes repo: docs tests discover ./hermes-agent/website/docs before legacy sibling paths, compare every active public-safe Hermes Markdown/category docs file against webpages/docs/content/upstream-hermes, and report missing, extra, or unsanitized pages with exact paths so operator docs drift cannot hide behind a skipped mirror test. Existing Gormes public-docs safety policy excludes high-risk upstream godmode/red-team skill pages from the published mirror and catalog. | validated | docs | small | operator, system | webpages/docs/docs_test.go::TestMirroredDocsCoverage | Docs validation reports hermes_docs_source_missing, hermes_docs_mirror_missing, hermes_docs_mirror_extra, hermes_docs_docusaurus_syntax, or hermes_docs_link_unresolved evidence instead of silently skipping when Hermes is checked out under ./hermes-agent; high-risk upstream skill docs remain excluded by the public-docs safety gate. |
| 4 / 4.I | Gormes setup/channel/provider docs webpage parity gate — The public Gormes docs site gives operators one coherent setup path from webpage entrypoint to first successful model turn and optional messaging gateway: the docs home, Getting Started, First Run, Provider Setup, Gateway Operations, Telegram Bot, CLI reference, config reference, environment reference, and provider reference pages must agree with the live gormes --help, gormes setup --help, gormes model --help, and gormes gateway --help command surface; they must link to active upstream Hermes provider/messaging docs under upstream-hermes/ for parity context while clearly separating Gormes-ready commands from row-backed or fixture-only channels/providers. | validated | docs | small | operator, gateway, system | webpages/docs/landing_page_docs_test.go::TestGormesOperatorSetupChannelProviderDocs | Docs tests report gormes_docs_setup_path_missing, gormes_docs_provider_claim_unverified, gormes_docs_channel_claim_unverified, gormes_docs_help_mismatch, or gormes_docs_upstream_context_missing instead of letting the webpage advertise stale setup, channel, or provider behavior. |
| 4 / 4.I | Native runtime provider gateway binding — Gormes gateway constructs a native Go runtime/provider binding from Hermes-compatible config when no explicit endpoint is configured, so live Telegram and other channel turns do not default to a dead localhost backend while explicit OpenAI-compatible endpoints remain supported. The binding must flow through shared gateway/runtime seams rather than Telegram-specific conditionals. | validated | gateway | medium | gateway, operator, system | internal/runtime/native_provider_gateway_binding_test.go | If native provider credentials or model routing are incomplete, gateway status and channel replies report provider_config_missing/native_runtime_unavailable evidence. They must not silently dial http://127.0.0.1:8642, require a Python/Hermes runtime service, or hide the configured provider/model behind secret-bearing logs. |
| 4 / 4.I | Hermes compatibility namespace retirement boundary — Gormes splits the current internal/llm parity staging package into durable Gormes-owned runtime packages and a narrow Hermes compatibility namespace after provider/model/tool-call/context fixtures are stable. Hermes-specific import, drift, command/config, provider/platform inventory, and deprecated-shim evidence moves to internal/compat/hermes; provider transport/routing, model metadata, tool-call parsing/repair, account usage, prompt/context/compression helpers, and normal-turn runtime contracts move to Gormes-owned packages. internal/llm is allowed only as a temporary import shim during the migration and must be removed or reduced to no public runtime ownership before release readiness. | validated | provider | large | operator, gateway, system | internal/compat/hermes/namespace_contract_test.go::TestHermesCompatibilityBoundaryOwnsOnlyCompatibilitySurfaces | If a caller still imports internal/llm for Gormes-owned runtime behavior after the migration, package-boundary tests report hermes_namespace_runtime_leak with the importing package and the target replacement package. Hermes compatibility docs and drift manifests remain available under internal/compat/hermes so removing the staging namespace does not hide upstream parity evidence. |
| 4 / 4.I | Hermes agent runtime strict-fidelity source-pair expansion — Expand source-pair and progress mappings for unmapped Hermes agent runtime files before treating them as runtime implementation gaps. The pass must classify transports, LSP helpers, context/compression helpers, prompt caching, retry/rate diagnostics, conversation loop helpers, tool dispatch helpers, and safety/redaction helpers into existing Gormes provider/runtime/tool rows or new builder rows. | validated | docs | medium | operator, system | internal/fidelity agent runtime strict-fidelity mapping fixture | Until this strict-fidelity bucket is classified, Gormes must continue treating the matching Hermes source/docs/tests as unmapped blockers and avoid claiming complete Hermes parity for this surface. |
| 4 / 4.J | Shell blocklist + filesystem scoping + permission approval — Shell blocklist (36+ patterns), filesystem scoping (folder-level read/write), permission approval UX (inline y/n/always) | validated | tools | medium | operator, gateway, child-agent | internal/tools/permissions_test.go | Commands matching blocklist patterns are rejected before execution; filesystem operations outside scoped directories are rejected; unapproved operations prompt inline y/n/always |
| 4 / 4.K | Resilient provider chain dispatch — DeepSeek → OpenAI → Anthropic → Grok → Ollama resilient routing with chain failure detection | validated | provider | small | operator, system | internal/llm/fallback_chain_test.go | Single provider failure is surfaced but the chain continues; complete chain failure reports all attempted providers |
| 4 / 4.K | Hermes fallback activation + classifier carve-outs — Gormes extends the existing provider fallback chain from generic dispatch into Hermes’ current agent-loop behavior: fallback_model accepts a single object or ordered list, ignores missing provider/model entries without panics, normalizes supported fallback providers through the provider registry, activates fallback after empty-response retry exhaustion, updates compressor/model context after activation, and treats malformed provider JSON as retryable transport corruption rather than a local validation error. | validated | provider | medium | operator, system | internal/llm/fallback_activation_test.go; internal/kernel/provider_fallback_test.go | Fallback status reports fallback_config_invalid, fallback_unavailable, fallback_activated, fallback_jsondecode_retryable, or compressor_fallback_updated evidence without exposing provider credentials or silently retrying a non-retryable local validation error. |
| 4 / 4.K | Fallback entry api_key_env credential alias — Gormes preserves Hermes’ current fallback-entry credential alias behavior: fallback_model/fallback_providers entries may carry key_env or api_key_env, the selected environment variable is resolved only at fallback activation, unset variables become an absent explicit API key rather than an empty string, and explicit base_url/api_key metadata travels with the fallback route without leaking credentials in operator evidence. | validated | provider | small | operator, system | internal/llm/fallback_activation_test.go; internal/kernel/provider_fallback_test.go | Missing or unset credential env vars leave the fallback route selectable but pass no explicit API key to the fallback client factory; provider setup may then use its normal defaults or report credential_unavailable without logging secret values. |
| 4 / 4.L | Plan gate hook in agent turn loop — Before tool execution, the agent loop invokes a plan-gate safety check. Unsafe plans are refused with explanation. Safe plans proceed. This mirrors MOSAIC (2025) plan->check->act/refuse pattern. | validated | orchestrator | medium | operator, system | - | - |
| 4 / 4.L | Tool gate pre-execution validation — Each individual tool invocation is checked against intent alignment before execution. This mirrors IntentGuard’s two-gate architecture: plan gate (strategic) + tool gate (tactical). | validated | tools | medium | operator, system | - | - |
| 4 / 4.L | Refusal-as-action in ReAct cycle — The agent loop supports ‘refuse’ as a first-class action in the ReAct cycle. When safety gates reject a planned action, the agent can refuse and explain why, rather than silently failing or hallucinating a different action. | validated | orchestrator | small | operator | - | - |
| 4 / 4.L | Safety loop end-to-end integration — Integration test proving plan-gate + tool-gate + refusal work together in a complete agent turn. Covers: safe turn passes, unsafe plan blocked, tool drift blocked, multi-step chain with mixed safe/unsafe steps. | validated | orchestrator | small | operator, system | - | - |
| 4 / 4.M | Circuit breaker per provider and API key — Each provider connection gets an independent circuit breaker tracking consecutive failures. After threshold (default 5), breaker trips to OPEN and all calls fast-fail for cooldown period (default 30s). Half-open state allows single probe request. | validated | provider | small | system | - | - |
| 4 / 4.M | P95 latency-aware failover — Provider selection considers P95 latency in addition to health status. Degraded-but-not-dead providers get reduced traffic weight rather than full exclusion. Rolling window tracks last N requests. | validated | provider | small | system | - | - |
| 4 / 4.M | Capability-based model tier routing — Route simple queries to cheap models and complex queries to capable models based on a fast classifier. Avoids sending ‘hello’ to Claude Opus and avoids sending multi-file refactors to a 7B model. | validated | provider | medium | operator | - | - |
| 5 / 5.A | Tool registry inventory + schema parity harness — Operation and tool descriptor parity before handler ports | validated | tools | medium | operator, gateway, child-agent, system | internal/tools upstream schema parity manifest fixtures | Doctor reports disabled tools, missing dependencies, schema drift, and unavailable provider-specific paths. |
| 5 / 5.A | Tool-call JSON-string array/object coercion parity — Gormes revisits the completed tool-call repair boundary so provider-emitted stringified JSON arrays/objects are coerced according to the advertised tool schema before validation, while malformed JSON-array-looking strings degrade with explicit repair evidence instead of reaching tool execution silently. | validated | provider | small | system, child-agent | internal/llm/tool_call_argument_repair_test.go | When a schema-array argument is a bad JSON-array-looking string, Gormes keeps the call recoverable only as a single-element list if the item schema permits it and returns typed repair evidence otherwise; malformed required/object values still fail before execution. |
| 5 / 5.A | Tool parity manifest refresh for Hermes b35d692f — Embedded upstream tool parity fixtures reflect Hermes b35d692f tool names, toolsets, schemas, and platform restrictions before handler rows rely on them | validated | tools | small | operator, gateway, system | internal/tools/parity_b35d692f_test.go | Tool parity doctor reports stale donor commit, missing discord/discord_admin rows, stale cron context_from schema, or mismatched platform toolsets before runtime ports begin. |
| 5 / 5.A | Tool parity manifest refresh for Hermes ea86714 computer_use — Refresh the embedded upstream tool parity manifest from Hermes b35d692f to Hermes ea86714cc for descriptor-only drift: add the new computer_use toolset/tool schema, capture its macOS/cua-driver degraded dependency metadata, and reconcile the already-shipped Kanban worker tool descriptors so the manifest no longer hides active upstream core tools. | validated | tools | small | operator, gateway, child-agent, system | internal/tools/parity_ea86714_test.go; internal/tools/testdata/upstream_tool_parity_manifest.json | Tool parity doctor reports stale_source_commit, missing computer_use, missing kanban_* descriptors, or missing cua-driver provider-path evidence before any runtime handler or toolset claim can proceed. |
| 5 / 5.A | Tool parity manifest refresh for Hermes 524cbabd patch schema — Refresh the embedded upstream tool parity manifest from Hermes ea86714cc to Hermes 524cbabd8 for descriptor-only patch-tool schema drift: PATCH_SCHEMA keeps required=[mode] with no anyOf/oneOf, but its top-level and property descriptions now advertise per-mode required parameters so strict providers do not omit replace-mode old_string/new_string or patch-mode patch content. | validated | tools | small | operator, gateway, child-agent, system | internal/tools/parity_524cbabd_test.go; internal/tools/testdata/upstream_tool_parity_manifest.json | Tool parity doctor reports stale_source_commit or patch schema drift before provider/tool rows rely on the embedded manifest; this slice records upstream descriptor truth without enabling unsupported Gormes patch-mode execution. |
| 5 / 5.A | Microsoft Graph auth/client helper parity — Port Hermes’ Microsoft Graph app-only auth and reusable REST client helpers into a fake-transport Go helper package before any Graph-backed model tool or skill script depends on them. The slice covers env credential normalization, cached token fetch, retry-after handling, pagination, streaming downloads, and redacted degraded errors without requiring live Azure credentials. | validated | tools | medium | operator, system | internal/tools/msgraph_client_test.go | Graph-backed tools or skills must report msgraph_not_configured, msgraph_token_unavailable, or msgraph_request_unavailable evidence instead of advertising live Microsoft Graph access when tenant/client/secret or the network path is unavailable. |
| 5 / 5.A | Discord tool split + platform-scoped toolsets — Gormes exposes Discord read/participate and Discord admin capabilities as separate descriptors/toolsets with platform-scoped availability | validated | tools | small | operator, gateway, system | internal/tools/discord_toolset_test.go | Tool status drops Discord schemas when token, intents, allowlist, or platform scope make actions unavailable instead of advertising impossible admin calls. |
| 5 / 5.A | Discord tool limit coercion helper — Discord tool argument normalization coerces string/float/integer limit inputs for search_members and fetch_messages, applies Hermes-compatible defaults, and clamps model-visible values before any REST handler is ported | validated | tools | small | operator, gateway, system | internal/tools/discord_limit_test.go | Invalid Discord limit arguments fall back to action defaults with discord_limit_defaulted evidence instead of surfacing a type panic or sending unbounded API queries. |
| 5 / 5.A | Home Assistant HASS_TOKEN platform-toolset carveout — Platform toolset resolution keeps homeassistant enabled for cron and CLI default selections when HASS_TOKEN is present while leaving MOA/RL and missing-token Home Assistant default-off behavior unchanged | validated | tools | small | operator, gateway, system | internal/cli/toolset_config_test.go::TestPlatformToolsetStatusKeepsHomeAssistantWhenHASSTokenSet | When HASS_TOKEN is absent, homeassistant remains default-off and toolset status reports homeassistant_token_missing rather than advertising HA control tools. |
| 5 / 5.A | Home Assistant tool handlers + service safety validation — Gormes ports the Hermes Home Assistant control tool handlers behind a Go-native injectable HTTP client: ha_list_entities, ha_get_state, ha_list_services, and ha_call_service validate entity/domain/service inputs, preserve the upstream summary/result envelopes, require HASS_TOKEN availability, and fail closed on path traversal, SSRF-style names, blocked service domains, and malformed JSON payloads. | validated | tools | medium | operator, gateway, system | internal/tools/homeassistant_tool_test.go | Missing HASS_TOKEN, missing HASS_URL, invalid entity/service identifiers, blocked domains, malformed service JSON, or client failures return redacted homeassistant_tool_unavailable/homeassistant_validation_failed evidence instead of registering broken device-control handlers or leaking tokens. |
| 5 / 5.A | Pure core tools first — Umbrella sequencing guard for the first native tool-calling tranche: prioritize schema-backed, credential-free or fake-store tools (Clarify, Todo, Debug helpers, local format/read-only helpers) before browser, network, sandbox, and write-capable tools so the registry, descriptor, debug, and degraded-evidence contracts stabilize without external services. | validated | docs | umbrella | - | - | - |
| 5 / 5.A | Stateful tool migration queue — Gormes defines the migration queue and execution guard for stateful Hermes tools before exposing write-capable tools to the native loop: file, session, checkpoint, and process tools declare state domains, XDG roots, rollback/audit behavior, concurrency policy, and degraded evidence; the first implementation is a registry/read-model contract that lets builders add one stateful tool at a time without bypassing path isolation. | validated | tools | medium | operator | internal/tools/stateful_migration_queue_test.go | Stateful tools without a validated queue entry return tool_state_contract_missing, tool_path_denied, tool_rollback_unavailable, or tool_concurrency_blocked evidence instead of mutating files, sessions, checkpoints, or processes. |
| 5 / 5.A | Terminal process watch notification throttle contract — Terminal/process tools rate-limit watch-pattern notifications, suppress matches after process exit, and make notify_on_complete mutually exclusive with watch_patterns before any live process runner is exposed | validated | tools | small | operator, child-agent, system | internal/tools/process_notifications_test.go | Tool status reports watch-pattern disabled, promoted-to-notify-on-complete, or global overflow evidence instead of flooding operators with duplicate delayed process notifications. |
| 5 / 5.A | Tool output budget persisted artifact pointer — Native tool execution bounds large tool results by persisting full output as a session artifact and returning a short text pointer to the model/channel, preserving Hermes operator readability and channel safety. | validated | tools | small | gateway, operator, system | internal/tools/result_budget_test.go | If artifact persistence fails, the result is still bounded and includes a safe warning without exposing raw oversized payloads to external channels. |
| 5 / 5.A | Tool descriptor layer (OperationSpec) — Every tool in the registry carries a declarative descriptor (OperationSpec) that generates model schemas, CLI commands, gateway slash commands, doctor checks, and audit taxonomy from one source | validated | tools | medium | operator, gateway, child-agent, system | internal/tools/operation_spec_test.go | If descriptors are missing, doctor reports tool_descriptor_incomplete and the tool is hidden from gateway/child-agent callers until the descriptor is present. |
| 5 / 5.A | Hermes tool tail strict-fidelity source-pair expansion — Classify remaining unmapped Hermes tools into covered Gormes tool rows, focused builder rows, or explicit exclusions. The pass must cover web/search providers, voice/TTS/STT tools, video/image tools, environment backends, tool result storage, process/zombie guards, URL and website policy helpers, and x_search auth behavior without hiding them behind the existing broad 61-tool row. | validated | docs | medium | operator, system | internal/tools strict-fidelity tail mapping fixtures | Until this strict-fidelity bucket is classified, Gormes must continue treating the matching Hermes source/docs/tests as unmapped blockers and avoid claiming complete Hermes parity for this surface. |
| 5 / 5.B | Environment interface + file sync contract — Gormes ports Hermes sandbox environment and file-sync contracts into a Go Environment interface with path mapping, upload/download, timeout, cleanup, and parser-family inventory fixtures before backend-specific Docker/SSH/Modal/Daytona/Singularity execution lands. | validated | tools | medium | operator, child-agent, system | internal/tools/environment_contract_test.go; internal/llm/tool_call_parser_manifest_test.go | Unavailable or unsupported environment backends return environment_backend_unavailable or parser_family_row_backed evidence without shelling out, starting containers, or dropping file-sync intent. |
| 5 / 5.B | Terminal snapshot source stdout suppression guard — The future Go terminal-environment snapshot wrapper suppresses stdout and stderr while loading persisted shell environment snapshots so macOS bash declare -x output cannot leak environment variables into terminal tool responses | validated | tools | small | operator, child-agent, system | internal/tools/environment_snapshot_test.go::TestEnvironmentSnapshotSource_* | Terminal status reports environment_snapshot_unavailable or snapshot_source_suppressed evidence rather than returning raw declare -x lines to the model. |
| 5 / 5.B | Terminal deleted-cwd recovery guard — Residual terminal/local environment deleted-cwd parity after the completed foreground terminal recovery row: future background process spawn must reject deleted cwd before subprocess creation, and future persistent-shell state must reset with explicit evidence instead of panicking. | validated | tools | medium | operator, child-agent, system | internal/tools/terminal_cwd_recovery_test.go | Deleted or inaccessible cwd returns terminal_cwd_recovered, terminal_cwd_deleted, or background_cwd_unavailable evidence without leaking host paths outside the workspace root or silently running in an unrelated directory. |
| 5 / 5.B | Raw tool-call parser fixture matrix — Gormes owns a source-backed manifest and fake-output fixture matrix for every Hermes root environments/tool_call_parsers/*.py parser before claiming raw model-output tool-call parity. | validated | provider | small | system | internal/llm/tool_call_parser_manifest_test.go | Unknown parser files or unsupported parser families remain row-backed with explicit evidence instead of silently falling back to generic parsing. |
| 5 / 5.B | Docker execution backend (container lifecycle + mount policy) — Gormes executes agent tools inside Docker containers through the existing DockerContainerKey helper and Environment interface. The backend handles: image selection/resolution from config or Hermes-compatible defaults, mount policy (allowlisted host paths mapped read-only, workspace mapped read-write, blocked dangerous mounts), env passthrough from config with allowlist filtering, container lifecycle with timeout cleanup, and stdout/stderr capture for tool output. | validated | tools | medium | operator, system | internal/tools/docker_exec_test.go | Missing Docker socket, image pull failure, or container timeout produce structured errors with mount_policy_blocked, image_pull_failed, or container_timeout reasons. Gateway status reports Docker backend availability without exposing raw Docker socket paths. |
| 5 / 5.B | Docker backend top-level container reuse semantics — Pure helper internal/tools/docker_container_key.go exposes type DockerContainerRequest struct{TaskID string; IsSubagent bool; IsRollout bool} and DockerContainerKey(req DockerContainerRequest) string. The helper trims TaskID, returns “default” for top-level requests with empty TaskID, returns the trimmed TaskID for top-level explicit task IDs, and returns the trimmed TaskID for subagent or rollout requests. If IsSubagent or IsRollout is true and TaskID is empty, it returns "" so callers must generate an isolated task ID before creating a Docker environment. No Docker CLI calls, no filesystem reads, no cleanup, no env config. | validated | tools | small | operator, child-agent, system | internal/tools/docker_container_key_test.go | Doctor/status reports docker_task_scope_missing when an isolated subagent or rollout request lacks a generated task_id instead of silently falling back to the shared default container. |
| 5 / 5.B | Singularity command/preflight contract — Port the Hermes Singularity/Apptainer sandbox command contract as a pure Go builder: executable resolution prefers apptainer over singularity, version preflight records bounded evidence, instance start command construction carries containment, no-home, writable tmpfs or overlay, readonly binds, memory, CPU, image, and instance id, exec wraps bash with optional login shell, and cleanup plans instance stop with the upstream timeout. Keep live Singularity execution out of unit tests. | validated | tools | small | - | - | - |
| 5 / 5.B | Sandbox Policy Explain — Port OpenClaw’s sandbox explain: gormes sandbox explain shows the effective trust class, tool allowlist, filesystem scope, and network policy for any agent context. Make sandbox policy operator-visible, reinforcing the ‘visible degraded mode’ contract. | validated | tools | small | operator | internal/tools/sandbox_explain_test.go | Unparseable sandbox config, missing Docker daemon, or policy resolution failure reports sandbox_explain_unavailable with root cause and available fallback modes. |
| 5 / 5.C | Browser action contract + event transcript — Gormes freezes the native browser tool contract before binding Chromedp or Rod: action schema, page-state transcript events, screenshot/result envelope, console/content-none guards, private-URL safety handoff, oversized artifact pointer behavior, and unavailable-backend errors are represented as pure Go types and fixtures. | validated | tools | medium | operator, child-agent, system | internal/tools/browser_contract_test.go | Browser status returns browser_backend_unavailable, browser_action_invalid, browser_result_truncated, or private_url_local_sidecar evidence instead of starting a browser, contacting cloud providers, or dumping screenshots/transcripts into model context. |
| 5 / 5.C | go-browser-harness Chromedp action backend — Bind the Go-native gormes.browser.action.v1 contract to a live chromedp/CDP backend in ../go-browser-harness without Python: resolve the operator’s remote-debugging endpoint, open browser_navigate in a new tab, preserve BU_NAME task namespaces, support snapshot/click/type/scroll/back/press/console/get_images/vision/cdp/dialog actions, return ActionResult JSON with @e refs plus bounded screenshot/artifact references, and surface typed unavailable evidence when Chrome/CDP is not reachable. | validated | tools | medium | operator, child-agent, system | ../go-browser-harness/pkg/harness/action_test.go | Missing Chrome remote-debugging endpoint, stale CDP target, invalid @e refs, action timeouts, screenshot failures, and CDP command failures return typed Go harness evidence such as go_browser_harness_backend_unavailable or go_browser_harness_action_invalid without falling back to Python, launching an unmanaged browser, or leaking cookies/profile state. |
| 5 / 5.C | Browser provider bridge + Firecrawl fallback — Fakeable cloud browser provider bridge implements Hermes’ CloudBrowserProvider shape for Browserbase and Firecrawl without starting a browser: provider registration is gated by explicit config/env credentials, CreateSession returns session_name, provider_session_id, bb_session_id compatibility evidence, cdp_url, and feature flags; Browserbase 402 responses retry deterministically by stripping keepAlive then proxies before failing; Firecrawl honors configured API URL and browser TTL; CloseSession and EmergencyCleanup are idempotent and redact API keys, project IDs, session URLs, and response bodies in errors. | validated | tools | medium | operator, child-agent, system | internal/tools/browser_provider_bridge_test.go | Unavailable credentials, provider create failures, Browserbase fallback retries, Firecrawl API failures, and cleanup failures return typed evidence such as browser_provider_unconfigured, browserbase_plan_fallback, browser_provider_create_failed, browser_provider_cleanup_failed, and browser_provider_error_redacted without leaking secrets or attempting live navigation. |
| 5 / 5.C | Camofox REST browser mode and managed identity bridge — Gormes ports Hermes’ Camofox browser mode as a fakeable REST backend seam without starting a browser: CAMOFOX_URL enables the Camofox path only when BROWSER_CDP_URL is blank, managed persistence derives stable profile-scoped user_id and task-scoped session_key values with Hermes’ uuid5 namespace contract, first-session creation POSTs /tabs with userId/sessionKey/url through an injected HTTP client, soft cleanup drops local state without DELETE when managed persistence is enabled, and hard close DELETEs /sessions/{user_id} with bounded redacted evidence. | validated | tools | small | operator, child-agent, system | internal/tools/browser_camofox_bridge_test.go | Missing CAMOFOX_URL, explicit BROWSER_CDP_URL override, malformed tab-create responses, and close failures return typed browser_provider evidence without live browser startup, DNS, Camofox calls, raw profile paths, cookies, or unbounded response bodies. |
| 5 / 5.C | Browser Use cloud + Go browser harness bridge — Fakeable Browser Use provider and Go browser harness command bridge implements Hermes’ BrowserUseProvider session shape and browser operator workflow without starting a live browser: config detects direct BROWSER_USE_API_KEY or managed gateway state, CreateSession records id, cdpUrl/connectUrl, liveUrl, feature flags, timeout/proxy/profile inputs, and external_call_id with redacted evidence; Stop/Cleanup PATCHes action=stop; the default command runner builds argv as go-browser-harness —action-json gormes.browser.action.v1 plus a sanitized BU_NAME environment, while browser-harness -c remains explicit legacy compatibility only; harness stdout/stderr and screenshot/text outputs normalize into BrowserResultEnvelope with artifact budgeting. | validated | tools | medium | operator, child-agent, system | internal/tools/browser_use_harness_bridge_test.go | Unavailable Browser Use credentials, missing go-browser-harness binary, session create failures, stale CDP/connectUrl resolution, cleanup failures, and command-runner failures return typed evidence such as browser_use_unconfigured, browser_harness_unavailable, browser_use_session_create_failed, browser_use_cleanup_failed, and browser_harness_command_failed without leaking API keys, profile IDs, cookies, CDP URLs, or unbounded provider response bodies. |
| 5 / 5.C | Go browser-harness Hermes browser_* tool wrappers — Gormes registers Hermes-visible browser_* tools backed by the Go BrowserHarnessBridge: browser_navigate emits go-browser-harness —action-json with schema_version gormes.browser.action.v1, new_tab=true, and the target URL; browser_snapshot returns text plus @e refs; browser_click/browser_type/browser_scroll/browser_back/browser_press/browser_console/browser_get_images/browser_vision/browser_cdp/browser_dialog generate argv-only Go action JSON, use sanitized BU_NAME task namespaces, preserve BrowserResultEnvelope artifact budgeting, and degrade with browser_harness_* evidence when the external harness is unavailable. Python browser-harness -c snippets remain explicit legacy compatibility only. | validated | tools | medium | operator, child-agent, system | internal/tools/browser_harness_tools_test.go | Missing go-browser-harness binary, stale CDP attachment, bad @e refs, command failures, and oversized output return typed browser_harness_unavailable/browser_harness_command_failed evidence through bounded BrowserHarnessToolResponse JSON. Unit tests use fake process runners only and never start Chrome, Browser Use, Browserbase, Firecrawl, Camofox, Python browser-harness, or go-browser-harness. |
| 5 / 5.C | Go-native Hermes web_search/web_extract tool wrappers — Gormes registers Hermes-visible web_search and web_extract tools backed by a fakeable WebHTTPClient. ResolveWebBackend maps Firecrawl direct/self-hosted config and managed Firecrawl gateway tokens into a common resolution shape. web_search posts to /v2/search and normalizes Firecrawl-style results into {success,data.web[]}. web_extract posts /v2/scrape once per public URL, blocks private/internal URLs with private_url_blocked evidence before any HTTP call, and normalizes markdown/title/sourceURL into {results[]}. Missing web config keeps both tools registered and returns web_provider_unavailable JSON instead of removing descriptors from the default registry. | validated | tools | small | operator, gateway, child-agent, system | internal/tools/web_tools_test.go + cmd/gormes/registry_test.go | Missing Firecrawl config returns web_provider_unavailable while preserving descriptors. Provider HTTP failures return web_provider_request_failed without leaking bearer tokens. Invalid URLs return web_invalid_arguments. Private, loopback, LAN, or internal hosts are blocked with private_url_blocked before the injected HTTP client is called. |
| 5 / 5.C | Go-native Hermes web backend matrix and config resolver — Gormes resolves Hermes web.backend and web.use_gateway configuration into typed Go web backend state, preserves Hermes fallback order across Firecrawl, Parallel, Tavily, and Exa credentials, advertises the public web_search.limit parameter, and routes web_search/web_extract through fakeable HTTP adapters for Firecrawl, Exa, Parallel, and Tavily without live provider credentials in tests. | validated | tools | medium | operator, gateway, child-agent, system | internal/tools/web_tools_test.go + internal/config/config_test.go + cmd/gormes/registry_test.go | Missing or whitespace-only provider credentials keep web tools registered but resolve to web_provider_unavailable; provider HTTP failures remain per-tool JSON failures and redact bearer/API-key material. Private/local URL blocking from the first web slice still runs before provider extract calls. |
| 5 / 5.C | Go-native Hermes web extract safety policy and summarizer — Gormes ports the remaining registered Hermes web_extract safety behavior around the existing fakeable web provider adapters: URLs containing known secret/API-key token prefixes short-circuit before any HTTP request, Hermes security.website_blocklist config blocks requested URLs before fetch and redirected final URLs after fetch, base64 data-image payloads are scrubbed from JSON output, and long extracted pages can be summarized through an injected WebContentProcessor with the production registry wiring it to the active Hermes client. | validated | tools | medium | operator, gateway, child-agent, system | internal/tools/web_tools_test.go + internal/config/config_test.go + cmd/gormes/registry_test.go | Website policy blocks return a per-result blocked_by_policy object without contacting providers. Secret-bearing URLs return a top-level failure before provider calls. If summarization is unavailable, disabled, empty, or fails, Gormes keeps bounded raw content rather than failing the web_extract call. Content over the processing input cap returns a focused too-large message. |
| 5 / 5.C | Goscrapling local extraction for web_extract — Gormes keeps the public Hermes-compatible web_extract tool name and integrates Go-native local/static extraction through the public github.com/TrebuchetDynamics/goscrapling v0.1.0 module release. When css_selector is set, web_extract fetches public http(s) URLs locally through the existing fakeable WebHTTPClient/direct document path, applies the same private/internal URL, secret URL, redirect, and website-policy guards, parses the response with goscrapling, and returns only matching element text in the existing {results[]} envelope. When the automatic DuckDuckGo/free backend handles web_extract, Gormes now tries goscrapling static URL extraction first for normal root URLs and document paths, using DuckDuckGo Instant Answer only as fallback. | validated | tools | small | operator, gateway, child-agent, system | internal/tools/web_tools_test.go::TestGoscraplingDependencyUsesPublicV010Release + TestWebExtractToolUsesGoscraplingCSSSelectorForStaticPages + TestWebExtractToolUsesGoscraplingForDuckDuckGoRootURL | Selector requests that match no elements or empty content return per-result invalid-arguments evidence. Fetch/parse failures return per-result request-failed evidence. DuckDuckGo/free static extraction falls back to Instant Answer when local fetch cannot produce content. Dynamic JavaScript pages still use browser tools/CDP; no new public goscrapling tool name is exposed. |
| 5 / 5.C | Go-native Hermes web_crawl tool adapter — Gormes exposes a Go-native web_crawl tool for the Hermes web_crawl_tool helper surface, backed by the existing fakeable WebHTTPClient and web backend resolver. Firecrawl crawl posts to /v2/crawl with Hermes’ fixed 20-page markdown crawl payload and ignores instructions because Firecrawl crawl has no prompt parameter. Tavily crawl posts to /crawl with url, limit, extract_depth, optional instructions, and the API key in the JSON body. Both paths normalize crawled pages into {results:[{url,title,content,error}]}, apply private/internal URL blocking before provider calls, enforce security.website_blocklist before crawl and against final crawled URLs, scrub base64 data images from output, and pass long page content through the existing WebContentProcessor when enabled. | validated | tools | medium | operator, gateway, child-agent, system | internal/tools/web_tools_test.go + cmd/gormes/registry_test.go | Missing or unsupported crawl backend returns a bounded success:false error explaining that web_crawl requires Firecrawl or Tavily and suggesting web_search plus web_extract as fallback. Secret-bearing crawl URLs are blocked before any HTTP request as a Gormes-owned hardening for making Hermes’ unregistered helper model-visible. Provider HTTP failures return a redacted crawl error without leaking bearer tokens. |
| 5 / 5.C | Go-native Hermes web managed gateway status and live smoke closure — Gormes closes the remaining native web-tool parity gaps after web_search/web_extract/web_crawl: Firecrawl managed-gateway routing can resolve a Nous OAuth access token from the native Gormes auth store, refresh expiring tokens with Hermes’ OAuth refresh-token grant, and prefer the managed route when web.use_gateway is true; the web backend resolver exposes a redacted status read model showing backend, direct/managed/unavailable route, credential source, gateway mode, tool names, and required env/auth hints; gormes doctor --offline renders that status without leaking tokens; and opt-in live smoke coverage can exercise real web_search/web_extract plus optional web_crawl only when an operator explicitly enables it with credentials. | validated | tools | medium | operator, gateway, child-agent, system | internal/tools/web_tools_test.go + internal/tools/web_live_smoke_test.go + cmd/gormes/registry_test.go + cmd/gormes/doctor_custom_provider_test.go | Missing direct provider credentials or managed gateway auth keeps web tool descriptors registered and reports web_provider_unavailable in tool output plus WARN status in doctor. If a Nous access-token refresh fails, the resolver falls back to the cached access token like Hermes’ managed_tool_gateway helper. Invalid shared gateway schemes are rejected before a managed URL is promoted. Auth-store access tokens are never included in status, doctor output, test failures, or tool results. Live smoke tests are skipped unless GORMES_LIVE_WEB_SMOKE=1 is set; missing credentials fail only that explicitly requested smoke run. |
| 5 / 5.C | Brave Search + DDGS web search provider parity — Gormes web backend matrix and config resolver (5.C[7]) supports Brave Search API and DDGS as first-class search providers. Brave uses the Brave Search API with a configurable API key and optional free-tier constraints; DDGS uses the duckduckgo_search Python package pattern adapted to Go. Both providers integrate through the existing internal/tools/web_tools.go backend interface so web_search tool wrappers select them transparently by name. | validated | tools | medium | operator, system | internal/tools/web_tools_test.go | Missing or invalid API keys for Brave/DDGS degrade to provider_disabled in the web backend matrix; web_search falls back to the next configured provider. Gateway status reports disabled search providers without exposing keys. |
| 5 / 5.C | Browser artifact and console render contract — Browser tool results expose bounded screenshot paths, DOM snapshots, console logs, page errors, and artifact metadata in a channel-neutral envelope; renderers show safe previews without raw bytes, base64 blobs, private URLs, CDP secrets, or unbounded provider output. | validated | tools | medium | system, gateway, operator | internal/tools/browser_artifact_test.go + internal/gateway/render_test.go | Oversized or missing artifacts render browser_artifact_unavailable or browser_artifact_truncated evidence and keep the turn readable. |
| 5 / 5.C | Browser console expression CDP result shaping — Gormes browser_console(expression=…) mirrors Hermes’ current CDP-supervisor fast-path result contract: Runtime.evaluate runs through the existing Go CDP backend without spawning a legacy subprocess, primitive/object/JSON-string results are returned as structured data with success/result/result_type/method evidence, and JavaScript exceptions surface as success=false tool data instead of retrying or hiding the error. | validated | tools | small | operator, child-agent, system | internal/tools/browser_use_harness_bridge_test.go | If no CDP backend is configured or the transport fails, the existing browser_harness_* unavailable/failed evidence is preserved; JS-side exceptions remain ordinary browser_console result data and do not become transport failures. |
| 5 / 5.C | Telegram browser artifact rendering — Telegram rendering for browser results is mobile-readable and Hermes/Sidon-compatible: screenshot/artifact pointers, DOM excerpts, console errors, and browser progress traces are MarkdownV2-safe, bounded, reply-threaded, and separate from final answers. | validated | gateway | small | gateway, system, operator | internal/gateway/telegram_browser_render_test.go + internal/tools/browser_artifact_test.go | If Telegram media delivery is unavailable, Gormes sends a text artifact pointer with browser_artifact_text_fallback evidence. |
| 5 / 5.C | Browser hybrid private-URL local sidecar routing — Pure routing helper internal/tools/browser_hybrid_routing.go exposes type BrowserRoute struct{SessionKey string; ForceLocal bool; Reason string}, IsPrivateBrowserHost(host string) bool, and RouteBrowserNavigation(taskID, rawURL string, cloudConfigured, autoLocalForPrivateURLs, cdpOverride, camofoxMode bool) BrowserRoute. When cloudConfigured && autoLocalForPrivateURLs && !cdpOverride && !camofoxMode and rawURL’s host is localhost, loopback, RFC1918 IPv4, IPv6 loopback, IPv4 link-local, or suffix .local/.lan/.internal, return SessionKey=“ | validated | tools | small | operator, system | internal/tools/browser_hybrid_routing_test.go | Until browser runtime exists, browser status can report hybrid_routing_unavailable; once wired, private URLs must never be sent to cloud providers when auto-local routing is enabled. |
| 5 / 5.C | Browser SSRF quoted-false guard — Browser and URL safety helpers coerce quoted false-like config values ("false", 'false', 0, no, off) to disabled booleans before private/local URL SSRF guards decide whether cloud navigation is allowed | validated | tools | small | operator, system | internal/tools/browser_ssrf_guard_test.go | Browser safety status reports ssrf_guard_config_invalid or private_url_blocked instead of treating quoted false as truthy and sending private URLs to a cloud/browser provider. |
| 5 / 5.C | Go browser harness binary repo + integration lane (placeholder) — Reserves the integration slot for the future Go browser harness binary repository so Gormes can drop in the runtime backend without re-doing the contract. The Gormes side already ships internal/tools/browser_harness_tools.go (12 browser_* tool wrappers) and internal/tools/browser_contract.go (BrowserAction validation, SSRF guard) that exec a configured Command via BrowserHarnessBridge.Run. This umbrella row tracks: (1) the future sibling Go repo that hosts the binary, (2) the JSON action contract version pinned at gormes.browser.action.v1, (3) the integration test that proves the contract still validates after the binary materializes, and (4) the install/discovery path Gormes uses to find the binary (PATH lookup vs vendored path). | validated | tools | umbrella | gateway, system | internal/tools/browser_harness_tools_test.go + internal/tools/browser_contract_test.go | Until the binary exists, every browser_* tool execution returns ErrBrowserHarnessUnavailable evidence (already implemented and tested by browser_harness_tools_test.go). The umbrella does not change runtime behavior; it documents the integration surface so the future repo lands cleanly. |
| 5 / 5.C | Browser session inactivity cleanup thread — Gormes mirrors Hermes’ browser session inactivity cleanup: a background goroutine reaps sessions that have been idle longer than BROWSER_SESSION_INACTIVITY_TIMEOUT (default 300s, overridable via GORMES_BROWSER_INACTIVITY_TIMEOUT env var). Cleanup closes the harness process for timed-out sessions and records redacted inactivity_cleanup evidence. The goroutine starts once at manager/service init and sleeps between reap cycles (default 60s interval) using a clock seam so tests stay hermetic. | validated | tools | small | system | internal/tools/browser_inactivity_cleanup_test.go | If the cleanup goroutine cannot start or the reap encounters a locked process, it records redacted evidence and continues the next cycle. A nil or absent browser backend does not prevent the goroutine from starting — it simply has nothing to reap. |
| 5 / 5.C | Goscrapling browser-backed extraction gate for web_extract — Extend the existing Hermes-compatible web_extract tool with an explicit operator-selected goscrapling browser extraction backend for dynamic pages. The model-visible tool name and schema stay unchanged; Gormes owns backend selection, URL/secret/website-policy checks, result budgeting, unavailable evidence, and channel rendering. goscrapling owns the BrowserFetcher response contract. Browser extraction returns the existing {results[]} envelope with structured extraction evidence (engine: goscrapling, mode: browser, status/content-type/wait evidence) and falls back or degrades without launching a browser in tests. | validated | tools | medium | operator, gateway, child-agent, system | internal/tools/web_tools_test.go::TestWebExtractToolUsesGoscraplingBrowserFetcher | If the goscrapling browser backend is not configured, the browser runtime is unavailable, a wait condition fails, or JavaScript rendering times out, web_extract returns per-result typed degraded evidence and keeps the existing static goscrapling/CDP/provider routes intact. No new model-visible goscrapling tool name is exposed. |
| 5 / 5.C | Goscrapling local crawler fixture adapter seam for web_crawl — Narrow the blocked goscrapling crawler plan into a builder-ready first seam: keep the public web_crawl descriptor/schema stable, add an injected local crawler adapter behind explicit web.backend=goscrapling_crawler selection, and prove the adapter can return normalized crawl pages with bounded crawl evidence using hermetic fake crawler sessions only. This slice must not bump goscrapling, use a local replace, fetch live websites, or claim robots/cache/checkpoint/session-adapter parity; when those primitives are absent or the adapter is not injected, Gormes must keep typed degraded unavailable evidence instead of falling back to Firecrawl/Tavily. | validated | tools | small | operator, gateway, child-agent, system | internal/tools/web_tools_test.go::TestWebCrawlToolUsesGoscraplingLocalCrawlerFixtureAdapter | Without an injected local crawler adapter, or when the adapter reports robots/cache/checkpoint/session-adapter capabilities unavailable, web_crawl returns typed degraded web_provider_unavailable/local-crawler evidence with backend goscrapling_crawler. Policy, private/internal URL, redirected-final URL, secret URL, max-page, duplicate/offsite, cancellation, and crawler runtime errors must be bounded and redacted; Firecrawl/Tavily fallback is forbidden for explicit local-crawler selection. |
| 5 / 5.C | web_crawl explicit local-crawler unavailable evidence — Add the repo-local compatibility gate for operators who explicitly select the future local goscrapling crawler backend for web_crawl before the crawler dependency row is unblocked. The resolver should recognize local crawler spellings such as goscrapling_crawler / goscrapling-crawler as a crawl-intent backend, keep it unavailable unless a later adapter is registered, and make web_crawl return typed redacted unavailable evidence without falling through to Firecrawl/Tavily, making live HTTP calls, changing the model-visible schema, or implementing goscrapling spider primitives inside Gormes. | validated | tools | small | operator, gateway, child-agent, system | internal/tools/web_tools_test.go::TestWebCrawlExplicitGoscraplingCrawlerUnavailableDoesNotFallback | When web.backend explicitly names the local crawler before the parent goscrapling crawler dependency is released, web_crawl returns success:false with evidence:web_provider_unavailable (or an equally typed unavailable code), backend:goscrapling_crawler, and a redacted operator-actionable message. Auto backend selection keeps existing Firecrawl/Tavily behavior; unsupported local crawler selection must not silently fall through to any remote provider. |
| 5 / 5.D | Image input mode router + native content parts — internal/llm exposes a pure image input routing helper that resolves agent.image_input_mode auto/native/text from model vision capability and auxiliary vision override, then builds native provider content parts with text plus data-url image_url entries without invoking a live provider | validated | provider | small | operator, system | internal/llm/image_routing_test.go::TestImageInputRouting_* | Multimodal status reports image_input_text_fallback, image_input_native_forced, image_input_native_unavailable, or image_input_auxiliary_vision_override instead of silently dropping images. |
| 5 / 5.D | vision_analyze native multimodal tool-result path — Gormes exposes a model-callable vision_analyze tool that can load a local image path or file:// URL into a multimodal tool-result envelope, carries that envelope through kernel role=tool messages as content parts, and serializes those parts for Codex Responses and OpenAI-compatible providers without stringifying or dropping image bytes. The slice falls back to typed JSON errors for missing/invalid images and does not call a live auxiliary vision LLM. | validated | tools | medium | operator, child-agent, system | internal/tools/vision_analyze_test.go, internal/kernel/toolexec_multimodal_test.go, internal/llm/codex_responses_multimodal_tool_result_test.go | Missing image paths, directories, unsupported schemes, and non-image payloads return redacted vision_analyze_* JSON errors; provider transports that cannot carry multimodal tool-result arrays keep the text summary rather than leaking binary bytes or blocking the tool loop. |
| 5 / 5.D | Image-too-large shrink retry helper — internal/llm exposes a pure image retry planner that, after a provider image_too_large classification, returns a single bounded retry plan with smaller image content parts and explicit no-retry evidence when shrinking is unavailable | validated | provider | small | operator, system | internal/llm/image_shrink_retry_test.go::TestImageShrinkRetry_* | Provider status reports image_shrink_unavailable, image_shrink_no_images, image_shrink_limit_reached, or image_shrink_failed instead of retrying the same oversized request. |
| 5 / 5.D | Image generation result contract — Image-generation tool results use a stable local artifact envelope with provider name, model, prompt hash, media type, output path, and redacted error evidence before any live image model call is wired | validated | tools | small | operator, system | internal/tools/image_generation_result_test.go | Image generation returns image_generation_unavailable or image_generation_provider_error envelopes instead of embedding raw binary data or provider secrets in transcripts. |
| 5 / 5.D | Image generation provider registry + plugin dispatch — Gormes ports Hermes image-generation provider registration and dispatch before live image model parity claims: providers register behind a Go-native interface, names are non-empty and sorted, duplicate names replace prior providers deterministically, image_gen.provider config wins when registered, FAL remains the preferred fallback when multiple providers are present and no config is set, missing configured providers degrade with provider_not_registered evidence, and the image_generation tool force-refreshes plugin discovery once before returning a missing-provider error. | validated | tools | small | operator, system | internal/tools/image_generation_provider_test.go; internal/plugins/image_gen_provider_test.go | Missing plugins, invalid provider registrations, absent configured providers, unavailable providers, provider errors, and artifact write failures return image_generation_* or provider_not_registered evidence without exposing prompts, API keys, or raw binary data. |
| 5 / 5.D | FAL image generation queue REST binding — The default FAL image-generation provider performs a lazy first-use REST queue call against https://queue.fal.run using Hermes’ image_generation_tool.py payload semantics and fal.ai’s current queue contract: POST model payload with Authorization: Key, poll status until COMPLETED, then retrieve the response URL and return the first image URL through the existing Gormes ImageGenResult envelope. | validated | tools | small | operator, system | internal/tools/image_generation_test.go::TestFALGenImageProviderQueueRESTFlow | Missing FAL_API_KEY, submit/status/response HTTP failures, non-COMPLETED terminal statuses, malformed queue JSON, missing response_url, and empty image result arrays return image_gen_provider_unavailable or image_gen_api_error evidence without leaking API keys, prompts, queue URLs with credentials, or raw provider bodies. |
| 5 / 5.D | Native video_analyze tool contract — Gormes exposes a video_analyze tool that accepts a local video path or URL and a prompt, routes the call to a vision-capable multimodal provider (Gemini today, others as the registry grows), and returns analysis text plus optional structured metadata. Routing is gated by the existing Image input mode router/provider-vision-capability check; non-vision providers return a typed unsupported_video error rather than a fake reply. Mirrors Hermes v0.13.0 PR #19301. | validated | tools | medium | operator, child-agent | internal/tools/video_analyze_test.go | When no vision-capable provider is configured, the tool returns unsupported_video evidence with the configured providers listed; it never uploads the video file or attempts a non-vision text fallback. |
| 5 / 5.E | Voice mode environment detector + audio provider seam — Gormes ports Hermes’ voice-mode environment detection into a Go-native, injectable audio-provider seam: SSH, container, WSL, Termux, missing PortAudio/sounddevice-equivalent probes, no-device, and provider-unavailable cases produce hard warnings or informational notices matching Hermes’ voice-mode availability rules before any microphone capture or playback is attempted. | validated | tools | small | operator, gateway, system | internal/tools/voice_mode_env_test.go | Headless, SSH, container, WSL-without-PulseAudio, missing audio dependencies, missing Termux:API app, no-device, and provider-probe failures return voice_mode_audio_not_available evidence with bounded warnings/notices instead of panicking, importing optional audio libraries at startup, or blocking STT/TTS tool registration. |
| 5 / 5.E | Transcription tool contract — Native STT/transcription tool helper validates local audio input and provider selection before gateway media hooks call it: files must exist, be regular files, use supported audio suffixes, and stay under configured max bytes; explicit provider selection among local, local_command, groq, openai, mistral, and xai never silently falls back; auto mode chooses Hermes order local, groq, openai, mistral, xai from injected availability; model defaults and overrides are normalized per provider; tool results return transcript/provider/model/language on success or typed redacted error evidence on failure. | validated | tools | medium | operator, gateway, system | internal/tools/transcription_tool_test.go | Disabled STT, missing files, directories, unsupported formats, oversized audio, missing provider credentials, local-command failures, and provider API failures return evidence codes such as stt_disabled, audio_not_found, audio_not_file, unsupported_audio_format, audio_too_large, stt_provider_unavailable, and stt_api_error with secret/redaction guards. |
| 5 / 5.E | Telegram voice/audio STT ingress hook — Telegram voice/audio messages resolve Telegram file metadata through fakeable GetFile/DownloadFile seams, run an injected audio transcriber before gateway submission, prepend a Hermes-style transcript marker to the user turn when transcription succeeds, and preserve bounded attachment/error evidence when download or STT degrades. | validated | gateway | small | gateway, operator, system | internal/channels/telegram/bot_test.go:TestBot_ToInboundEvent_VoiceMessageIncludesTranscriptWhenTranscriberConfigured + TestBot_ToInboundEvent_VoiceMessageFallsBackWhenDownloadFails | If Telegram file resolution, download, or transcription fails, Gormes keeps the original voice/audio marker, records a sanitized attachment error such as telegram getFile failed or telegram download failed, and does not leak file_id, bot token, direct Telegram file URL, command output, or API credentials. |
| 5 / 5.E | TTS tool contract + media delivery seam — Gormes ports Hermes text_to_speech as a native Go tool/result contract and channel-neutral MEDIA delivery seam: text_to_speech validates text/output path/provider choice, returns success/file_path/media_tag or typed redacted errors, and gateway final-response delivery strips MEDIA tags while sending local audio through platform voice/audio senders such as Telegram sendVoice/sendAudio. | validated | tools | medium | operator, gateway, system | internal/tools/tts_tool_test.go; internal/gateway/media_delivery_test.go; internal/channels/telegram/bot_test.go | Missing TTS provider/config, unsupported output path, synthesis failure, missing media file, and platform voice-send failure return typed redacted evidence such as tts_provider_unavailable, unsupported_audio_format, tts_api_error, tts_output_missing, and media delivery errors without exposing API keys, provider URLs with credentials, or local command output. Overlong text is truncated to the Hermes-compatible cap and marked with truncated=true in the result envelope. |
| 5 / 5.E | MiniMax TTS v1 text_to_speech raw-audio compatibility — Gormes ports current Hermes MiniMax TTS provider drift: default MiniMax synthesis uses https://api.minimax.chat/v1/text_to_speech, model speech-01, voice female-shaonv, a flat {model,text,voice_id} payload, and accepts raw audio responses while keeping the legacy JSON/hex response parser as fallback compatibility. | validated | tools | small | operator, gateway, system | internal/tools/tts_minimax_test.go | MiniMax endpoint, HTTP status, content-type, and response-shape failures return typed redacted tts_api_error evidence without leaking API keys, request text, or provider URLs containing credentials. |
| 5 / 5.E | TTS provider matrix + dotenv/command-provider resolution — Gormes ports the Hermes TTS provider/config matrix behind fakeable synthesis seams: built-in providers cannot be shadowed by command providers, tts.providers.<name> wins over legacy per-provider blocks, command providers render input/output placeholders with quoted context and bounded timeout cleanup, provider API keys resolve from env or Gormes dotenv state, global and per-provider speed values are normalized/clamped, provider-specific max_text_length caps are enforced, local KittenTTS/Piper dependencies degrade lazily, and MiniMax endpoint/body compatibility remains delegated to the dedicated MiniMax row. | validated | tools | medium | operator, gateway, system | internal/tools/tts_provider_matrix_test.go | Missing optional SDKs, missing dotenv/env API keys, invalid command-provider config, command timeouts, command failures, overlong text, unsupported output formats, and provider import failures return redacted tts_* evidence without disabling unrelated providers or leaking command output, prompts, paths outside temp roots, or API keys. |
| 5 / 5.E | Voice record-key config binding for native TUI — Gormes native TUI and voice mode honor a Hermes-compatible voice.record_key setting end to end: config parse and setup surfaces preserve the key, slash/status output reports it, and the key resolver uses the configured shortcut instead of a hardcoded default while malformed, reserved, or unsupported modifier combinations fall back with evidence. | validated | tools | small | operator, gateway, system | internal/tools/voice_mode_test.go; internal/tui/hermes_keybindings_test.go | Malformed or reserved record_key values return voice_record_key_invalid or voice_record_key_reserved evidence and fall back to the default key without disabling STT/TTS or leaking config file contents. |
| 5 / 5.E | Telegram voice STT HTTP-provider fallback — When the local whisper/whisper-cli binary is absent from PATH, Gormes’ Telegram channel falls back to the HTTP STT providers already shipped in the binary (internal/tools/transcription_providers.go) — currently OpenAI Whisper, plumbed via env vars GORMES_STT_OPENAI_KEY / OPENAI_API_KEY / VOICE_TOOLS_OPENAI_KEY. Local-CLI shim retains priority when both are available (matches Hermes’ free-local default). When neither is configured, the channel preserves the prior attachment-marker-only fallback behavior. | validated | gateway | small | operator, gateway, system | internal/channels/telegram/transcriber_resolver_test.go and cmd/gormes/telegram_transcriber_test.go | Resolver returns nil when neither local binary nor HTTP provider env vars are set; the channel surfaces the existing ‘audio unavailable’ attachment marker without leaking provider configuration details. |
| 5 / 5.E | Pure-Go STT exploration — Track the architectural question: how should Gormes ship local STT given the Go ecosystem currently has no production-quality pure-Go (CGO_ENABLED=0) STT library? Document the four mutually-exclusive choices (status quo / cgo build-tag / WASI productionization / wait+monitor) with their tradeoffs so future planners do not re-research the same dead ends. This row is exploratory and decision-tracking — not builder-selectable until a stakeholder commits to one of the four paths. | validated | provider | large | operator, system | (no fixture — research/decision row) | Without resolution, Gormes’ local STT story stays at: shell out to local whisper/whisper-cli (separate install) OR cloud HTTP fallback (Groq free tier wired 2026-05-09, OpenAI Whisper, etc.). The ‘free, in-binary, no install, local STT’ intersection remains empty. |
| 5 / 5.E | wazero WASI smoke harness — Establish a hermetic Go test harness that loads a trivial WASI hello-world module via wazero and reads its stdout. Pins a specific wazero release in go.mod, proves the runtime instantiates inside Gormes’ CGO_ENABLED=0 build, and creates the internal/wasi package as the home for all subsequent WASI work (Whisper, future audio modules). | validated | provider | small | operator, system | internal/wasi/smoke_test.go and internal/wasi/testdata/hello.wasm | If wazero fails to instantiate the module, the harness reports a typed degraded marker (wasi_runtime_unavailable) so callers can fall back without crashing. |
| 5 / 5.E | whisper.cpp WASI module discovery — Vendor a known-good whisper.cpp WASI/WASM build under internal/wasi/whisper/testdata/, instantiate it via wazero, query its WASI exports, and prove it responds to —help (or equivalent argv probe). This row does not yet transcribe audio — it proves the binary loads and is callable from Go. | validated | provider | small | operator, system | internal/wasi/whisper/discovery_test.go and internal/wasi/whisper/testdata/whisper.wasm | If the whisper.wasm binary fails to instantiate (incompatible WASI version, missing imports), the discovery test reports a typed degraded marker naming the missing import so the next row can address it. |
| 5 / 5.E | Pure-Go Whisper transcribe one WAV — Transcribe a small fixture WAV file end-to-end through wazero+whisper.wasm, returning a non-empty transcript string. Define and implement the public Transcriber API (NewTranscriber/TranscribeWAV/Close) named in the stakeholder plan, with WASI filesystem mounting for /models and Go-side 16 kHz mono PCM WAV decoding that matches the active whisper-wasi prototype. This is the first row that actually performs inference. | validated | provider | medium | operator, system | internal/wasi/whisper/transcribe_test.go, internal/wasi/whisper/testdata/{whisper.wasm,jfk.wav}, and a checksum-verified ggml-tiny.en.bin cache populated by the Whisper tiny.en model cache fetcher row | If model file is missing or corrupted, NewTranscriber returns a typed error (model_unavailable) without panicking. If TranscribeWAV’s WASI invocation hangs or errors, returns a typed error (wasi_inference_failed) with the underlying wazero diagnostic redacted of any test-fixture paths. |
| 5 / 5.E | Whisper tiny.en model cache fetcher — Provide a small helper that resolves ggml-tiny.en.bin for WASI Whisper tests by verifying an existing cache file or downloading it from a pinned HTTPS URL into a caller-selected cache directory. The helper records the model filename, URL, expected byte size, and SHA-256; downloads write to a temporary partial file, verify bytes before rename, and never place the model under git-tracked testdata or embed it in the runtime binary. | validated | provider | small | operator, system | internal/wasi/whisper/model_cache_test.go with httptest-backed downloads; no live Hugging Face request in tests | Missing cache directories, bad HTTP status, short reads, size mismatch, checksum mismatch, and rename failures return typed model_cache_* errors with URL/path details bounded to test cache paths and without writing corrupted final model files. |
| 5 / 5.E | Wire Pure-Go Whisper into Telegram resolver — Extend cmd/gormes/telegram_transcriber.go’s resolveTelegramAudioTranscriber with the in-binary WASI Whisper as a third candidate, slotted between local CLI (free, fastest) and HTTP cloud fallback (Groq/OpenAI). Final priority: local CLI → in-binary WASI Whisper → HTTP cloud → nil. The WASI candidate is constructed only when GORMES_WASI_WHISPER_MODEL points to an existing model file; it gracefully returns nil when the env var is unset or the file is missing. | validated | gateway | small | operator, gateway, system | cmd/gormes/telegram_transcriber_test.go (extended) | If GORMES_WASI_WHISPER_MODEL is unset or points at a missing file, the WASI candidate constructor returns nil and the resolver falls through to HTTP cloud — same UX as today. |
| 5 / 5.E | WASI Whisper ffmpeg preprocess + fixed-window chunker — Introduce a reusable internal/wasi/whisper/audio preprocessing package that accepts inbound audio bytes, preserves already-compatible 16kHz mono PCM WAV input, converts non-WAV formats through an injectable ffmpeg-style converter into 16kHz mono PCM WAV, returns typed audio_preprocess_unavailable evidence when conversion is unavailable, and exposes fixed-window PCM chunking for offline Whisper transcription. This is a library slice only; Telegram resolver binding and pure-Go codec replacement remain separate rows. | validated | provider | small | operator, system | internal/wasi/whisper/audio/preprocess_test.go and internal/wasi/whisper/audio/chunker_test.go | When ffmpeg is absent or conversion fails, Preprocess returns a typed error with code audio_preprocess_unavailable; callers can surface an actionable fallback instead of panicking or silently dropping the voice message. |
| 5 / 5.E | Audio preprocessing and chunking pipeline — Bind the completed internal/wasi/whisper/audio preprocessing and fixed-window chunker into the in-binary WASI Telegram transcriber. The adapter must run inbound Telegram audio bytes through the shared preprocessor, split long PCM into bounded chunks, transcribe each chunk through the existing WASI TranscribeWAV seam using temp WAV files, stitch non-empty chunk transcripts in order, and surface typed audio_preprocess_unavailable evidence when ffmpeg/conversion is unavailable. Go-native codec replacement and benchmarks remain separate rows. | validated | provider | small | operator, system | cmd/gormes/telegram_transcriber_test.go plus internal/wasi/whisper/audio encoder/chunker fixtures | When ffmpeg is not on PATH, the preprocessor returns a typed degraded marker (audio_preprocess_unavailable) and the channel surfaces ‘audio decoding requires ffmpeg’ attachment-marker fallback rather than panicking. This degraded state is operator-actionable. |
| 5 / 5.E | Whisper benchmark harness + perf budget — Build the first WASI Whisper benchmark harness around the shipped Row C Transcriber, using the checksum-verified ggml-tiny.en cache path and the checked-in JFK WAV fixture to measure model load time, inference time, realtime factor, heap footprint, transcript length, and binary-size delta. The benchmark reports Go benchmark metrics, benchmarks.json carries the STT result, and the README rollup surfaces the tiny.en realtime factor so operators can see whether in-binary STT should stay opt-in or become the default resolver path. Base/small model benchmarking remains a follow-up only after their model artifacts are pinned like tiny.en. | validated | provider | small | operator, system | internal/wasi/whisper/benchmarks_test.go and benchmarks.json | If the tiny.en model file is unavailable and cannot be fetched into the configured cache, the benchmark skips with model-cache evidence instead of calling cloud STT or failing unrelated tests. |
| 5 / 5.E | Go-native OGG/Opus decoder decision — Research and decide whether to use a pure-Go OGG/Opus decoder (e.g. ogg/opus bindings) vs keeping the ffmpeg shim for Telegram voice messages. Produce a decision document listing tradeoffs: binary size impact, platform compatibility (Termux, Windows, locked-down Linux), latency vs ffmpeg, and maintenance burden. The row produces a decision, not code. | validated | docs | small | operator, system | - | - |
| 5 / 5.E | Go-native OGG/Opus decoder implementation — Implement the decision document’s chosen build-tag approach: gate the ffmpeg converter behind a !noffmpeg build tag, provide a typed-error stub for noffmpeg builds, and expose a thin DecodeOGGToPCM wrapper in internal/tools/. The default ffmpeg path is preserved; no pure-Go decoder is implemented. | validated | docs | small | operator, system | internal/tools/audio_decode_test.go | Default builds convert audio via ffmpeg (requires ffmpeg on PATH). Noffmpeg builds return audio_decode_ffmpeg_missing evidence and the channel degrades to attachment markers. |
| 5 / 5.E | Pure-Go TTS decision research — Research and record the architecture decision for local TTS: (a) pure-Go TTS via a Go speech synthesis library, (b) embed a small WASM TTS engine, or (c) keep the existing command-provider pattern with shell-out to edge-tts/piper. Document tradeoffs: binary size, voice quality, latency, platform compatibility. This row produces a decision document; no runtime code. | validated | docs | small | operator, system | - | - |
| 5 / 5.E | Shared speech artifact cache for Go-owned TTS — Create a shared Go speech artifact cache used by current STT and future Go-owned TTS backends. The cache verifies filename safety, byte size, SHA-256, existing cache hits, HTTP downloads, and atomic partial-file cleanup without requiring Python, Node, shell commands, CGO, live providers, or committed model artifacts. | validated | tools | small | operator, system | internal/speech/artifact/cache_test.go | Invalid metadata, empty cache dir, HTTP failures, status errors, size mismatch, checksum mismatch, write failures, and non-regular files return typed cache errors with redacted paths/URLs; failed downloads leave no final artifact and no .partial residue. |
| 5 / 5.E | Go-owned local TTS runtime seam + fixture fallback — Implement the first Go-owned local TTS backend behind the existing text_to_speech/TTSProvider contract as a fakeable runtime seam plus a deliberately low-quality pure-Go fixture/formant provider. This clears the runtime-source blocker without pretending Piper/ONNX/WASI neural quality is solved: the provider must synthesize a valid WAV through Go code with no Python, Node, shell command, CGO, native ONNX Runtime, browser/Emscripten runtime, or cloud dependency. Neural Piper/WASI work remains a later source-backed upgrade behind the same seam. | validated | tools | medium | operator, gateway, system | internal/tools/tts_go_native_provider_test.go; internal/speech/tts/fixture_test.go | Missing fixture/runtime support, unsupported text, disabled build tags, synthesis errors, and benchmark budget overruns return typed tts_provider_unavailable or tts_api_error evidence without changing gateway delivery, leaking temp paths, or silently shelling out to Python/Node/platform commands. Provider=auto may still use existing cloud/command compatibility fallbacks; explicitly selecting the Go-owned provider must not fall through silently. |
| 5 / 5.F | Skills hub search result types + in-memory registry provider — internal/skills declares HubSearchResult{Name,Description,Source,InstallID,Score}, HubRegistryProvider interface{ Snapshot(ctx) ([]HubSearchResult, error) }, and an in-memory fake provider used by tests; no Search() function, gateway dispatch, or active-store mutation is added in this slice | validated | skills | small | operator, system | internal/skills/hub_search_types_test.go | The fake provider returns sentinel errors (registry_unavailable, registry_rate_limited) so later slices can table-test degraded evidence without changing the type surface. |
| 5 / 5.F | Skills hub search read-model function over registry providers — internal/skills exposes Search(ctx, query, providers []HubRegistryProvider, opts) (HubSearchResponse, error) returning sorted results plus typed degraded evidence (HubSearchEvidenceEmptyQuery, HubSearchEvidenceRegistryUnavailable, HubSearchEvidenceRateLimited, HubSearchEvidenceNoResults) without touching the active/inactive skill store or any HTTP client | validated | skills | small | operator, gateway, system | internal/skills/hub_search_test.go | Search returns HubSearchResponse{Results,Evidence} where Evidence is a typed enum value; callers must not assume an empty Results slice means failure. |
| 5 / 5.F | Skill registries — Gormes ports Hermes’ skills-hub search registry infrastructure as read-only HubRegistryProvider implementations for GitHub Contents API taps, skills.sh search, Hermes centralized index, Well-Known endpoint, ClawHub catalog, Claude Code marketplace, and LobeHub agent marketplace. Every provider implements Snapshot(ctx) ([]HubSearchResult, error) with typed degraded evidence (registry_unavailable, registry_rate_limited, registry_malformed) and never mutates active/inactive skill stores. | validated | skills | small | operator, gateway, system | internal/skills/hub_registry_sources_test.go | Every provider returns typed HubSearchEvidence values for unavailable, rate-limited, or malformed registry responses. Callers can render actionable retry guidance without mutating the active skill store. |
| 5 / 5.F | Skills hub direct URL candidate parser — internal/skills exposes a pure ParseURLSkillCandidate(rawURL string, skillMD []byte) (URLSkillCandidate, error) helper that mirrors Hermes UrlSource.fetch metadata without network or store writes: HTTPS SKILL.md URLs only, source=url, trust=community, files={SKILL.md}, resolved name from valid frontmatter or URL slug, awaiting_name evidence when neither produces a safe install name, and no path traversal in name/category candidates | validated | skills | small | operator, system | internal/skills/url_candidate_test.go | The helper returns evidence values url_skill_invalid_url, url_skill_missing_name, url_skill_invalid_name, or url_skill_invalid_frontmatter; callers can render actionable retry guidance without writing to the active skill store. |
| 5 / 5.F | Skills hub direct URL install name/category guard — The gormes skills install <https://.../SKILL.md> flow uses URLSkillCandidate evidence to require an explicit --name on noninteractive URL installs when no safe name is resolved, rejects invalid name overrides, optionally accepts a category on interactive/CLI surfaces, and stages the downloaded SKILL.md through quarantine/scan before activation | validated | skills | small | operator, system | internal/cli/skills_url_install_test.go | Noninteractive URL installs fail closed with url_skill_missing_name and retry guidance; scan/quarantine failures leave the active skill store unchanged and preserve candidate evidence for operator review. |
| 5 / 5.F | Skill preprocessing + dynamic slash commands — Skill content preprocessing and skill-backed slash commands are deterministic, disabled-skill aware, and prompt-safe | validated | skills | small | operator, gateway, system | internal/skills/preprocessing_commands_test.go | Skill status reports disabled, missing-prerequisite, or preprocessing-failed skills without injecting them into prompts. |
| 5 / 5.F | [IMPORTANT:] prompt prefix for cron and skill commands — internal/automation/cron.CronHeartbeatPrefix and internal/skills.BuildSkillSlashCommandMessage emit [IMPORTANT: instead of [SYSTEM: so Azure OpenAI Default/DefaultV2 content filters do not reject Gormes prompts as prompt-injection (HTTP 400) — same semantic meta-instruction, different bracketed marker; tests update in lockstep so the byte-match assertions still cover drift | validated | skills | small | operator, system | internal/automation/cron/heartbeat_test.go | Operator-visible prompt text changes from [SYSTEM: ...] to [IMPORTANT: ...]; behavior is otherwise identical, including the [SILENT] suppression contract and the skill body trimming. |
| 5 / 5.F | Skills list — enabled/disabled status column + —enabled-only filter — internal/skills/list.go exposes type SkillStatus string (“enabled”, “disabled”), extends SkillRow with a Status field and adds ListOptions{Source string; EnabledOnly bool}. ListInstalledSkills(opts ListOptions, disabled map[string]struct{}) []SkillRow returns every installed skill annotated with Status from the disabled set; when opts.EnabledOnly is true, disabled rows are filtered out. The CLI surface (gormes skills list —source | validated | skills | small | operator, system | internal/skills/list_test.go | Status column makes disabled skills visible without forcing the operator to read config; —enabled-only matches the upstream “what will load” introspection question. |
| 5 / 5.F | Update bundled skills across active and named profiles — Gormes skill update/sync paths seed reviewed bundled skills into the active profile and every named profile, target explicit profile roots, preserve user-modified skills, and return a deterministic per-profile summary without writing to unrelated homes. | validated | skills | small | operator, system | internal/skills/profile_sync_test.go; cmd/gormes/skills_sync_test.go | Profile-root discovery, sync conflicts, and write failures return skill_profile_sync_unavailable, skill_profile_conflict, or skill_profile_write_failed evidence with profile names only; no skill body or private path content is logged. |
| 5 / 5.F | Bundled Airtable productivity skill contract — Gormes can ship the upstream Airtable skill as reviewed SKILL.md content with optional credential discovery, prompt-safe cookbook text, and visible unavailable evidence without requiring Airtable credentials during startup | validated | skills | small | operator, system | internal/skills/airtable_skill_test.go | Skill catalog marks Airtable unavailable or missing AIRTABLE_API_KEY/AIRTABLE_PAT evidence instead of omitting the skill or injecting unusable instructions into prompts. |
| 5 / 5.F | Bundled TouchDesigner MCP skill catalog contract — Gormes treats upstream TouchDesigner MCP as reviewed bundled creative skill content, not an optional-skill install, while exposing missing TouchDesigner/MCP prerequisites as visible availability evidence and never starting a TouchDesigner or MCP process during skill catalog tests | validated | skills | small | operator, system | internal/skills/touchdesigner_skill_test.go::TestTouchDesignerSkill_* | Skill catalog marks touchdesigner-mcp as bundled but unavailable with touchdesigner_mcp_missing evidence when the MCP server or TouchDesigner app is absent; prompt injection excludes unavailable skills. |
| 5 / 5.F | Gateway/TUI dynamic skill slash invocation parity — Hermes-compatible /skill-name args invocations work through native TUI and manager-backed gateway channels by resolving enabled SKILL.md commands before unknown-command guidance, expanding to the reviewed skill invocation message, and never submitting the raw slash text to the model. | validated | skills | small | - | Hermes agent/skill_commands.py resolve_skill_command_key and build_skill_invocation_message; Gormes gateway/TUI fixtures invoke /skill-name args and assert expanded content plus no raw slash leakage. | Unknown or scan-failed slash commands remain consumed with visible unknown/unavailable guidance; built-in slash commands keep precedence; legacy ParseInboundText callers still receive empty EventUnknown bodies so slash text is not accidentally submitted. |
| 5 / 5.F | TUI/gateway reload-skills command refresh binding — Port the model-free /reload-skills / /reload_skills control path enough for Gormes dynamic skill invocation: native TUI reloads its in-memory skill slash registry from the configured skills root, and the gateway refreshes adapter-owned skill autocomplete/cache state while reporting the current enabled skill count without submitting slash text to the model. | validated | skills | small | - | Hermes gateway/run.py:_handle_reload_skills_command rescans skills and refreshes adapter skill groups; Hermes ui-tui reload-skills calls skills.reload then commands.catalog to refresh local command catalog. Gormes Runtime scans active SKILL.md on demand, so this slice focuses on TUI registry refresh and gateway adapter cache refresh/count evidence rather than full Hermes added/removed pending-note parity. | If skill runtime or reload adapter state is unavailable, the command remains consumed with visible degraded evidence; skill scan failures do not submit slash text to the model or break existing commands. |
| 5 / 5.G | Goncho MCP tool catalog — internal/gonchotools exposes a source-backed catalog for every upstream Honcho MCP tool name, classifying each row as mapped, partially mapped with field-level degradation, or unsupported with rationale. | validated | tools | small | operator, system | internal/gonchotools/honcho_mcp_catalog_test.go | Unsupported and partial Honcho MCP tools remain visible as catalog rows with source path, input contract, unsupported input names, and reasons instead of silently disappearing from parity planning. |
| 5 / 5.G | MCP server config/env resolver — Gormes parses Hermes-compatible mcp_servers config into safe stdio/HTTP server definitions with env validation, timeout defaults, header redaction, and Honcho MCP self-hosted URL support before any live MCP connection starts | validated | tools | small | operator, system | internal/tools/mcp_config_test.go | MCP status reports disabled, invalid transport, invalid env, missing SDK, or secret-redacted config errors instead of launching partial servers. |
| 5 / 5.G | MCP stdio transport + tool/list discovery — internal/tools/mcp_stdio.go exposes a stdio MCP client (NewStdioClient(def MCPServerDefinition, opts StdioClientOpts) (*StdioClient, error); (*StdioClient).Initialize(ctx) error; (*StdioClient).ListTools(ctx) ([]MCPRawTool, error); (*StdioClient).Close() error) that speaks JSON-RPC over a stdio.ReadWriteCloser injected from tests — deterministic over an in-process fake server with no real subprocess | validated | tools | small | operator, system | internal/tools/mcp_stdio_test.go | Initialize/ListTools failures return typed errors (ErrConnectTimeout, ErrInitializeFailed, ErrInvalidJSONRPCResponse) and Close is idempotent; no global stderr/stdout writes, no os.StartProcess in tests. |
| 5 / 5.G | MCP HTTP transport + tool/list discovery — internal/tools/mcp_http.go exposes an HTTP MCP client (NewHTTPClient(def MCPServerDefinition, opts HTTPClientOpts) (*HTTPClient, error); (*HTTPClient).Initialize(ctx) error; (*HTTPClient).ListTools(ctx) ([]MCPRawTool, error); (*HTTPClient).Close() error) that speaks JSON-RPC over an injectable http.RoundTripper — deterministic over httptest.Server in unit tests with no real network egress | validated | tools | small | operator, system | internal/tools/mcp_http_test.go | Same typed errors as the stdio client (ErrConnectTimeout, ErrInitializeFailed, ErrInvalidJSONRPCResponse) plus ErrAuthRequired (HTTP 401) so the OAuth follow-up row can build on it; no panic on response close even if the server hangs up mid-stream. |
| 5 / 5.G | MCP schema normalization + structured-content adapter — internal/tools/mcp_normalize.go converts MCPRawTool slices and tools/call results into native tool descriptors and model-facing strings — sanitizes tool names, validates InputSchema is a JSON-Schema object, flattens structured content vs text content into a single string, isolates server stderr to a per-server log path, and emits a tools/list_changed-style refresh hook so dynamic discovery updates replace stale descriptors deterministically | validated | tools | small | operator, system | internal/tools/mcp_normalize_test.go | Invalid InputSchema produces a SchemaRejected status (no descriptor registered); structured content with unknown content kinds renders the text fallback; stderr writes are buffered into a bounded ring buffer (8 KiB tail) and flushed to the configured per-server log file or discarded when no path is set. |
| 5 / 5.G | MCP OAuth state store + noninteractive auth errors — internal/tools adds a pure in-memory MCPOAuthStore with methods Get(server string) (MCPOAuthToken, bool), Set(server string, tok MCPOAuthToken) error, Clear(server string), and StatusFor(server string, now time.Time) MCPOAuthStatus where MCPOAuthToken has fields {AccessToken, RefreshToken, Scope, Issuer string; ExpiresAt time.Time}, MCPOAuthStatus has fields {Server, State, Evidence string} with State in {‘absent’,‘valid’,‘expired’,‘noninteractive_required’}; the store never logs or returns AccessToken/RefreshToken in its String()/Status output | validated | tools | small | operator, system | internal/tools/mcp_oauth_store_test.go | Status output reports state and evidence labels (absent, valid, expired, noninteractive_required) instead of leaking secret material; redaction is enforced in the store boundary, not at call sites. |
| 5 / 5.G | MCP OAuth refresh + 401 session-expired recovery — internal/tools adds a pure function RefreshMCPOAuth(ctx context.Context, store *MCPOAuthStore, server string, refresher MCPRefresher, now time.Time) (MCPOAuthRefreshResult, error) where MCPRefresher is an interface with one method Refresh(ctx, refreshToken string) (newTokens MCPOAuthToken, err error); the function refreshes expired tokens, handles 401-equivalent SessionExpired errors by clearing the stored token, and never blocks on user input | validated | tools | small | operator, system | internal/tools/mcp_oauth_refresh_test.go | Refresh result reports Outcome in {‘refreshed’,‘still_valid’,‘token_cleared’,‘noninteractive_required’,‘refresher_unavailable’} so callers can decide whether to retry the underlying MCP call without inspecting transport errors. |
| 5 / 5.G | Managed tool gateway bridge — Managed tool gateway bridges expose discovered remote tools through the same MCP descriptor/call contract as local fake servers without requiring live external services in tests | validated | tools | medium | operator, system | internal/tools/managed_tool_gateway_test.go | Managed gateway status reports gateway_unavailable, auth_required, schema_rejected, and tool_call_failed evidence without registering half-discovered tools. |
| 5 / 5.G | MCP circuit breaker cooldown + reconnect reset — MCP tool calls maintain Hermes-compatible per-server recovery state: repeated call failures trip a circuit breaker, calls before cooldown short-circuit without touching the session, the first call after cooldown is a half-open probe, probe success closes the breaker, probe failure reopens it, and a successful OAuth reconnect clears stale breaker counts even if the immediate post-reconnect retry still fails. | validated | tools | medium | operator, system | internal/tools/mcp_circuit_breaker_test.go; internal/tools/mcp_reconnect_test.go | Tool call results report mcp_server_unreachable, mcp_breaker_open, mcp_half_open_failed, mcp_reconnect_required, or mcp_reconnect_reset evidence instead of hanging, hammering a broken server, or leaving a recovered server permanently disabled. |
| 5 / 5.G | MCP stdio orphan cleanup after cron ticks — Cron and MCP stdio tooling track orphaned stdio server PIDs after cancellation/timeout and sweep only orphaned children after a cron tick joins, without killing active MCP sessions from parallel work | validated | tools | small | operator, system | internal/tools/mcp_orphan_cleanup_test.go | MCP status reports mcp_orphan_reaped, mcp_orphan_reap_failed, or mcp_active_pid_preserved instead of leaking detached stdio subprocesses or killing active sessions. |
| 5 / 5.G | Gormes-native MCP host runtime boundary — Gormes exposes a native MCP/tool host boundary with explicit tool declarations, filtering, audit evidence, and channel/runtime-safe execution without adopting a non-Hermes config surface. | validated | tools | medium | gateway, operator, system | internal/tools/mcp_host_boundary_test.go | Unavailable MCP servers/tools produce structured unavailable/unauthorized evidence while core Hermes-parity tools and channel commands continue to work. |
| 5 / 5.G | MCP channels_list tool — Gormes exposes the Hermes MCP channels_list tool through the MCP server, reading from the existing internal/gateway ChannelDirectoryStore Platforms map to return a per-platform channel list. The tool correctly accesses the platforms key from the directory store (ChannelDirectoryStore.Platforms is the platforms map directly, mirroring the Hermes fix in 292f46836 which corrected channels_list to iterate directory.get(“platforms”, {}) instead of directory.items()). Returns channel id, name, chat_id, and enabled status per channel, with optional platform name filter. | validated | tools | small | system, operator | internal/tools/mcp_channels_list_test.go | Unavailable channel directory or missing platforms data produces structured mcp_channels_list_empty or mcp_channels_list_error evidence without crashing the MCP server. |
| 5 / 5.H | ACP server side — Gormes maps Hermes ACP adapter entry/auth/session/tools/permissions/events into a Go-native manifest and stdio/server protocol fixture before editor integrations are advertised. | validated | tools | medium | operator, system | internal/protocols/acp/server_manifest_test.go | Unsupported ACP provider detection, missing auth, and permission prompt paths return explicit acp_row_backed evidence instead of silently registering an incomplete editor bridge. |
| 5 / 5.H | ACP Client Bridge Mode — Complete the ACP integration with client bridge mode: gormes acp client connects to the Go-native ACP server (5.H server side is validated) with session key/label resolution, reset-session capability, require-existing guard, provenance modes (off/meta/meta+receipt), and —no-prefix-cwd flag. Match OpenClaw’s ACP bridge surface. | validated | tools | medium | operator, system | internal/protocols/acp/client_test.go | Unsupported ACP provider, missing auth, session key not found, or permission prompt timeout returns explicit acp_client_row_backed evidence with available fallback modes. |
| 5 / 5.H | ACP JSON-RPC stdio session/prompt closeout — Gormes replaces ACP manifest/client-only parity with a live Go-native ACP stdio JSON-RPC server that supports initialize/image capability, provider authentication, new/load/resume/cancel, prompt streaming/final closeout, history/title/usage events, slash command interception, queued/steer prompt behavior, CWD translation, permission approvals, and clean stdout framing. | validated | tools | large | operator, system | internal/protocols/acp/jsonrpc_server_test.go; internal/protocols/acp/session_runtime_test.go | Unsupported providers, missing auth, invalid sessions, permission timeout/rejection, queued prompt conflicts, malformed multimodal blocks, or stdio framing errors return explicit acp_jsonrpc_* evidence without shelling out to Hermes Python or leaking protocol messages onto stdout. |
| 5 / 5.H | ACP stdio benign ping/probe suppression — Gormes ports Hermes’ ACP stdio startup hygiene: the ACP entrypoint keeps stdout reserved for JSON-RPC frames, routes incidental logs to stderr, treats client probe methods such as bare ping, health, and healthcheck JSON-RPC requests as benign liveness checks, suppresses noisy background-task tracebacks for those probes, and still surfaces real ACP startup/session/tool errors with redacted evidence. | validated | tools | small | operator, system | internal/protocols/acp/stdio_ping_suppression_test.go | Benign probe suppression emits acp_benign_probe_suppressed counters on stderr/status only; malformed JSON-RPC, auth/session/tool failures, and non-probe unknown methods remain visible as acp_stdio_error evidence without contaminating stdout frames or leaking prompt/tool payloads. |
| 5 / 5.H | ACP session CWD propagation into prompt runners — Gormes ports Hermes’ ACP task-cwd propagation fix by carrying the translated ACP session CWD into every RuntimePromptRequest handed to the prompt runner, including new, loaded/resumed, and queued prompt turns. Foreground terminal/file tools can then default to the editor task workspace, while explicit tool workdir arguments remain tool-local and continue to override the session CWD. | validated | tools | small | operator, system | internal/protocols/acp/session_runtime_test.go | Missing or blank ACP session cwd leaves RuntimePromptRequest.CWD empty rather than inventing a process cwd; the runner can still use its own safe default, and no live terminal command or provider call is required for this slice. |
| 5 / 5.H | ACP setup-browser bootstrap parity — gormes acp --setup-browser ports Hermes’ ACP browser-tool bootstrap behavior with platform-specific command planning, dry-run/report output, and browser harness dependency checks while keeping actual installs explicit and operator-approved. | validated | tools | small | - | cmd/gormes acp setup-browser dry-run fixtures | - |
| 5 / 5.I | Plugin SDK — Gormes loads plugin manifests, capability metadata, version constraints, and disabled-state evidence without executing plugin runtime code | validated | tools | small | operator, system | internal/plugins/manifest_test.go | Plugin status reports malformed manifests, unsupported capability kinds, missing credentials, and disabled execution before any tool or dashboard route is registered. |
| 5 / 5.I | Dashboard theme/plugin extension status contract — Native dashboard status exposes theme and UI/backend-plugin extension inventory as disabled, unavailable, or loaded without importing Hermes’ React/Vite runtime | validated | tools | small | operator, system | internal/apiserver/dashboard_extensions_test.go | Dashboard status explains that theme/plugin extension runtime is disabled or unavailable instead of advertising Hermes React dashboard extension support. |
| 5 / 5.I | Dashboard page-scoped plugin slot inventory — Dashboard plugin metadata preserves Hermes page-scoped slot names with colons and exposes them through the native disabled plugin inventory without claiming React/Vite runtime support | validated | tools | small | operator, system | internal/plugins/page_scoped_slots_test.go | Dashboard plugin status reports page-scoped slots as inert metadata while the native React/Vite slot runtime remains disabled or unavailable. |
| 5 / 5.I | Hermes plugin CLI lifecycle parity — gormes plugins ports the Hermes plugin lifecycle command surface over Go-native plugin roots and config: list, install, update, remove, enable, and disable sanitize plugin names, resolve Git/URL/file identifiers safely, copy example files, prompt for required env without overwriting existing values, gate project plugins behind explicit enablement, and never execute plugin runtime code during lifecycle operations. | validated | tools | medium | operator, system | cmd/gormes/plugins_command_test.go; internal/plugins/lifecycle_test.go | Invalid plugin identifiers, manifest-name traversal, missing plugins, git failures, malformed manifests, disabled project-plugin roots, and missing required env produce redacted plugin_cli_* evidence and cleanup partial installs without deleting paths outside the plugin root. |
| 5 / 5.I | Teams pipeline plugin CLI metadata + disabled runtime inventory — Gormes records Hermes’ new teams_pipeline first-party plugin as inert metadata: the manifest loads as a disabled bundled plugin, ctx.register_cli_command(name="teams-pipeline") is exposed as a disabled cli_command capability, the Teams/Graph runtime is reported unavailable until explicit runtime rows exist, and metadata discovery never imports plugin Python, builds Graph clients, reads live MSGRAPH/TEAMS credentials, or sends Teams delivery. | validated | tools | small | operator, system | internal/plugins/teams_pipeline_plugin_test.go | Operators can see that the Teams pipeline plugin and its teams-pipeline CLI surface exist, but every capability carries teams_pipeline_runtime_unavailable / graph_credentials_required / teams_delivery_target_required evidence instead of pretending Graph subscriptions, meeting transcript fetch, Notion sync, or Teams summary delivery work in Go. |
| 5 / 5.I | Goncho Honcho plugin session config + async write compatibility — Goncho preserves Hermes Honcho plugin session/config behavior over local-first memory: Honcho baseUrl without an API key resolves to a local/self-hosted sentinel when the URL is plausible, obvious non-URL literals fail closed, host-level config overrides root settings, writeFrequency supports async/turn/session/integer modes, session names prefer manual override then title then session id/dirname/global workspace, async writes queue and flush deterministically, and pinPeerName can opt a single-user multi-platform deployment into one configured peer while the default keeps multi-user platform IDs isolated. | validated | memory | medium | operator, gateway, system | internal/goncho/plugin_session_config_test.go; internal/goncho/async_write_test.go | Malformed baseUrl, invalid writeFrequency, async flush failures, missing session id/title, and unsafe pinPeerName combinations return goncho_config_* or goncho_async_* evidence without contacting hosted Honcho, merging unrelated users, or dropping queued memory writes silently. |
| 5 / 5.I | First-party Spotify plugin fixture — First-party plugin manifests and tool packages load through the plugin SDK without reverting to built-in tool registration | validated | tools | small | operator, system | internal/plugins/spotify_plugin_test.go | Plugin status reports missing environment or auth setup without registering broken prompt-visible tools. |
| 5 / 5.I | First-party Google Meet plugin metadata fixture — First-party Google Meet plugin manifests, tool metadata, realtime/node capability flags, and safety constraints load as disabled plugin inventory through the Plugin SDK without importing Python, starting Chrome, joining meetings, or registering built-in Go tools | validated | tools | small | operator, gateway, system | internal/plugins/google_meet_plugin_test.go::TestGoogleMeetPluginMetadata | Plugin status reports google_meet_runtime_unavailable, google_meet_realtime_unconfigured, node_auth_required, or browser_profile_required instead of exposing meeting tools that cannot run. |
| 5 / 5.I | Hindsight memory setup blank-input preservation — Future Gormes Hindsight memory-plugin setup preserves existing mode, endpoint, model, timeout, idle_timeout, and custom config values when the operator presses Enter through prompts, and never overwrites existing API-key references with blank input | validated | tools | small | operator, system | internal/plugins/memory_hindsight_config_test.go::TestHindsightSetupConfigPatch_* | Until the external Hindsight plugin exists, plugin status reports hindsight_memory_plugin_unavailable instead of pretending the internal Goncho service is a Hindsight-compatible provider. |
| 5 / 5.I | Agent Hooks Registry — Port OpenClaw’s hook registry as gormes hooks with list/enable/disable/check/info subcommands. Hooks are inspectable at runtime from gateway config (HOOK.yaml/BOOT.md). Support enable/disable without restart. | validated | tools | medium | operator | internal/plugins/hooks_test.go | Hook config parse failure, missing hook implementation, or runtime error reports per-hook status with degraded hook skipped. |
| 5 / 5.I | Plugin Marketplace + Doctor — Port OpenClaw’s plugin management surface: marketplace discovery (ClawHub-compatible), plugin doctor (load issue reporting), plugin inspect (manifest details), and third-party plugin sandboxing (WASM/subprocess isolation). | validated | tools | large | operator | internal/plugins/marketplace_test.go | Marketplace unreachable, plugin load failure, or sandbox crash reports per-plugin status with degraded plugin skipped. |
| 5 / 5.I | Extension Lifecycle Hook System — Port the first Go-native kernel extension chain from agent-zero’s lifecycle hook model: register extensions for 8+ kernel lifecycle points (agent_init, monologue_start/end, message_loop_start/end, before_main_llm_call, prompt_before/after, response/reasoning stream chunks, tool_before/after, context_deleted) and execute each hook in registration order with per-extension timeout and panic isolation. This slice provides the kernel API and test fixture only; command/UI listing stays a later plugin CLI binding. | validated | tools | medium | operator, system | internal/kernel/extensions_test.go | Extension load failure, timeout, or panic reports per-extension status with degraded extension skipped. No single extension failure blocks the agent turn. |
| 5 / 5.I | Plugin lifecycle hook: transform_llm_output — Gormes plugins can register a transform_llm_output lifecycle hook that receives the raw LLM output (text + tool_call envelope) before it reaches the conversation, and may reshape, redact, or filter the content. Hook is invoked synchronously per assistant turn, ordered by registration, and any hook returning a non-string error short-circuits with the original output preserved (no silent drops). Mirrors Hermes v0.13.0 PR #21235. | validated | orchestrator | small | operator, system | internal/plugin/transform_llm_output_test.go | If the hook chain panics or exceeds a deadline, the original LLM output is delivered unchanged with hook_failure evidence, not a swallowed message. |
| 5 / 5.I | Hermes plugin catalog strict-fidelity classifier — Build a report-only Hermes plugin catalog classifier for cmd/repoctl hermes-contract-inventory: scan the current hermes-agent/plugins/** tree, group exact plugin files into first-party families (model providers, memory providers, browser/web search, platform adapters, Google Meet, Spotify, Teams pipeline, image/video generation, dashboard/observability), and attach each family to an existing progress row, planned child row, source-pair entry, explicit exclusion, or owned-divergence note. The classifier is evidence-only: it must not import or execute Hermes plugin runtime code, mutate provider/channel behavior, or claim strict coverage without row/source-pair/test proof. | validated | docs | medium | operator, system | internal/repoctl/hermes_contract_inventory_plugin_catalog_test.go | Until this classifier lands, strict-fidelity reports can only show plugin files as an undifferentiated unmapped bucket, so Gormes must avoid claiming complete Hermes plugin/provider/channel/memory parity. |
| 5 / 5.J | Gateway approval FIFO queue resolver — Gormes exposes a thread-safe gateway approval queue that mirrors Hermes tools.approval: pending approval entries are stored per session, HasBlockingApproval reports waiting entries, ResolveGatewayApproval resolves the oldest FIFO entry while ResolveAllGatewayApprovals resolves every pending entry for a session, and ClearGatewayApprovalSession cancels all queued entries on session boundary without leaking stale approvals to the next run. | validated | gateway | small | gateway, operator, system | internal/gateway/approval_queue_test.go::TestApprovalQueue* | Empty session keys, invalid approval choices, and missing pending entries return typed no-op or not-pending evidence; the helper performs no channel sends, sleeps, goroutines, stdin prompts, shell execution, or live network calls. |
| 5 / 5.J | Hardline command pattern table + DetectHardline function — internal/tools declares HardlinePattern{Regex, Description}, a HardlinePatterns []HardlinePattern table seeded from Hermes HARDLINE_PATTERNS, and DetectHardline(cmd string) (matched bool, description string) that compiles patterns once via sync.Once and is pure (no I/O, no globals besides the compiled list) | validated | tools | small | operator, system | internal/tools/dangerous_hardline_test.go | DetectHardline returns false for any input on a corrupt regex (test-only seam) instead of panicking; production patterns are validated at init. |
| 5 / 5.J | Recoverable dangerous patterns + blocked-result schema — internal/tools declares DangerousPatterns []HardlinePattern (recoverable rules from Hermes DANGEROUS_PATTERNS), DetectDangerous(cmd) (matched, description), and BlockedResult{Approved=false, Hardline bool, Description, Operator, Command, Evidence map[string]string} produced by GuardCommand(cmd, mode) which calls DetectHardline first then DetectDangerous; pure, no execute_code wiring in this slice | validated | tools | small | operator, gateway, child-agent, system | internal/tools/dangerous_command_test.go | GuardCommand returns BlockedResult{Hardline=true} unconditionally for hardline matches; recoverable dangerous matches return BlockedResult{Hardline=false, ApprovalRequired=true} which yolo-mode callers can ignore in a later slice. |
| 5 / 5.J | Approval mode config normalization — Pure approval-mode parser normalizes config-loaded values before dangerous-command prompting is wired: bool false maps to off to preserve YAML 1.1 bare off, bool true maps to manual, strings trim/lowercase with empty and unknown values behaving as manual, and only manual, smart, and off are returned to GuardCommand callers; hardline blocks remain non-bypassable and recoverable dangerous commands use the normalized mode without starting prompts or auxiliary LLM review in this slice. | validated | tools | small | operator, system | internal/tools/approval_mode_test.go | Invalid, empty, unsupported, or non-scalar approval config records approval_mode_defaulted evidence and falls back to manual; a bool false/off value bypasses only recoverable approvals and never bypasses hardline blocks. |
| 5 / 5.J | Gateway hook auto-accept strict parser — Gateway hook consent resolves CLI/env/config auto-accept values through a strict bool/string parser so quoted false-like strings and non-bool scalars cannot silently authorize executable hooks | validated | gateway | small | operator, system | internal/gateway/hook_consent_test.go | Ambiguous or invalid hook auto-accept configuration leaves executable hooks in explicit-consent-required mode and reports hook_auto_accept_invalid instead of running handlers automatically. |
| 5 / 5.J | delegate_task batch JSON-string task recovery — Gormes’ public delegate_task tool wrapper accepts Hermes-compatible batch task payloads: native tasks arrays and JSON-string-encoded task arrays both fan out through the existing bounded SpawnBatch runtime, malformed JSON strings fail with clear guidance, and non-object task entries are rejected before any child is spawned. | validated | orchestrator | small | operator, child-agent | internal/core/subagent/delegate_tool_test.go | Until this lands, the Go batch runtime exists but model/tool-call payloads using Hermes’ tasks batch shape fail at the public delegate_task wrapper or force the model to collapse parallel work into one goal. |
| 5 / 5.J | Subagent dangerous-command non-interactive approval policy — Delegated subagent workers resolve dangerous-command approvals non-interactively with safe auto-deny by default and explicit audited auto-approve opt-in | validated | tools | small | operator, child-agent, system | internal/core/subagent/dangerous_approval_test.go::TestSubagentDangerousCommand_* | Subagent child tool execution returns approval_denied_noninteractive before invoking the executor for hardline commands and for recoverable dangerous commands unless the child config explicitly sets approval mode off. |
| 5 / 5.J | Concurrent tool approval callback propagation — Concurrent tool workers inherit only an explicit approval callback and otherwise fail closed with noninteractive denial, so approval prompts never fall back to stdin while a CLI/TUI turn owns the terminal | validated | tools | small | operator, child-agent, system | internal/tools/approval_callback_test.go::TestApprovalCallbackPropagation_* | Tool execution reports approval_callback_missing, approval_denied_noninteractive, or approval_callback_error instead of blocking on stdin or approving dangerous commands implicitly. |
| 5 / 5.J | Background review toolset restriction — Background review workers run with an explicit allowlist limited to memory and skills toolsets, deny executable/file/network toolsets by default, and surface restriction evidence in review telemetry | validated | tools | small | operator, child-agent, system | internal/core/subagent/background_review_toolset_test.go::TestBackgroundReviewToolsetRestriction | Background review status reports background_review_toolset_restricted or background_review_toolset_unavailable instead of silently granting full tool access. |
| 5 / 5.J | Cron dangerous-command approval mode — Cron-fired Gormes turns apply Hermes’ approvals.cron_mode contract to terminal tool execution: cron mode defaults to deny, recoverable dangerous commands return a noninteractive cron_mode block message without prompting or submitting a gateway approval, explicit approve/off/allow/yes permits recoverable dangerous commands, and hardline commands remain blocked even in approve mode. | validated | tools | small | system | internal/tools/terminal_tool_test.go::TestTerminalToolCronApprovalMode* | A dangerous cron terminal command in deny mode returns stable blocked tool JSON naming cron_mode and the dangerous pattern description; it never waits for stdin, gateway /approve, or a provider retry. Missing or invalid config falls back to deny. |
| 5 / 5.J | Cron approval mode config normalizer — Gormes ports Hermes’ cron-specific approval mode parser as a pure helper: cron approvals default to deny, and only approve/off/allow/yes opt cron jobs into recoverable dangerous-command auto-approval; ambiguous, empty, unsupported, or non-scalar values stay deny with stable degraded evidence. | validated | tools | small | operator, system | internal/tools/approval_mode_test.go::TestNormalizeCronApprovalMode* | Invalid cron approval config reports cron_approval_mode_defaulted evidence and remains deny; approve-mode never bypasses hardline command blocks. |
| 5 / 5.J | Tirith external security finding ingestion — Port the Hermes Tirith external security finding ingestion path: load findings from a JSON file or env-sourced source, classify by severity, and expose a security guard decision that gateway/cron/CLI callers can query before executing dangerous commands. | validated | docs | small | operator, system | internal/security/tirith_test.go | Missing or corrupt Tirith source returns tirith_unavailable evidence and falls back to the existing config-level allowlist rather than denying all commands. |
| 5 / 5.J | Unified security guard decision composer — Compose Tirith findings, path-based allowlists, URL safety rules, and website policies into one security guard decision that gateway/cron/CLI can call before executing any tool. The composer resolves conflicts deterministically (deny wins over allow, policy overrides Tirith) and always returns typed evidence explaining the decision. | validated | docs | small | operator, system | internal/security/guard_test.go | If any policy source is unavailable, the composer continues with the remaining sources and reports guard_partial_evidence. |
| 5 / 5.J | Shell blocklist (36+ dangerous patterns) — The shared tool executor blocks 36+ dangerous shell patterns before execution, with category-specific evidence and override policies | validated | tools | medium | operator, gateway, child-agent, system | internal/tools/shell_blocklist_test.go | If the blocklist is incomplete, doctor reports shell_blocklist_partial with missing pattern count; blocked commands still fail closed. |
| 5 / 5.J | Filesystem scoping (folder-level read/write restrictions) — File tools enforce folder-level read/write scope restrictions so agents cannot access paths outside configured boundaries | validated | tools | medium | operator, gateway, child-agent | internal/tools/filesystem_scope_test.go | If scoping is unconfigured, doctor reports filesystem_scope_unconfigured and file tools run with cwd-only restriction as fallback. |
| 5 / 5.J | Permission approval UX (inline y/n/always) — Dangerous actions trigger an inline approval prompt (y/n/always) with clear command preview, risk category, and persistent preference storage | validated | tools | large | operator, gateway | internal/tools/approval_ux_test.go | If approval UX is unavailable (non-interactive mode), dangerous commands fail closed with approval_ui_unavailable evidence instead of defaulting to allow. |
| 5 / 5.J | Trust-class enforcement in shared tool executor — The shared tool executor rejects tool calls from disallowed trust classes before a handler runs, preventing gateway/child-agent callers from exercising operator-local tools | validated | tools | small | operator, gateway, child-agent, system | internal/tools/trust_class_test.go | If trust-class enforcement is bypassed or misconfigured, doctor reports trust_class_enforcement_gap and the tool is hidden from disallowed callers. |
| 5 / 5.J | Secrets Runtime Controls — Port OpenClaw’s secrets runtime control surface: secrets apply for deploying previously generated plans, secrets audit to detect plaintext secrets/unresolved refs/precedence drift, secrets configure for interactive provider setup with SecretRef mapping and preflight validation, and secrets reload to re-resolve secret references and atomically swap the runtime snapshot. | validated | tools | medium | operator | internal/tools/secrets_test.go; cmd/gormes/secrets_command_test.go | Unresolved SecretRef, missing provider, or reload failure reports secrets_unavailable with exact ref path rather than silently falling back or leaking plaintext secrets. |
| 5 / 5.J | Security Audit Command — Port OpenClaw’s security audit: gormes security audit —deep —fix —json. Deep mode includes live gateway probe checks. Fix mode applies safe remediations and file-permission fixes. JSON mode produces machine-readable output. Audit categories: gateway auth status, state integrity, channel security warnings, shell blocklist coverage, filesystem scoping, credential redaction. | validated | tools | medium | operator | internal/tools/security_audit_test.go | Unauditable surfaces, missing probes, or unfixable issues report per-category status with severity level and recommended action rather than blocking the entire audit. |
| 5 / 5.J | Email allowlist pre-dispatch loop guard — Email ingress drops non-allowlisted senders before thread-context construction or gateway dispatch, while an empty allowlist permits all senders and allowlisted senders proceed with the existing reply-thread contract, preventing mail loops and unauthorized model submits. | validated | gateway | small | gateway, operator, system | internal/channels/email/allowlist_test.go | Dropped senders produce email_sender_denied evidence with redacted sender/domain fields and no provider call, gateway event, thread read, or outbound reply. |
| 5 / 5.J | Auth state TOCTOU close + redaction default-on parity — Gormes auth.json read-modify-write paths use atomic file replacement (open, write to sibling tempfile, fsync, rename) with no observable TOCTOU window between read and write. MCP OAuth credential persistence follows the same atomic-replace contract. The redaction default is restored to ON; opt-out is via explicit redaction.enabled: false in config. Mirrors Hermes v0.13.0 PRs #21193 (redaction default-on flip), #21194 (auth.json TOCTOU), #21241 (MCP OAuth TOCTOU). Reverts the v0.12.0 default-off flip (#16794). | validated | tools | small | operator, system | internal/auth/atomic_write_test.go | Without atomic replace, a concurrent read can observe a partially-written auth.json and crash the session; with the atomic-replace contract the read either sees the prior valid state or the new valid state, never a partial merge. Without redaction default-on, log capture, debug bundles, and trajectory writes leak credentials by default. |
| 5 / 5.J | Gateway allowed_chats/channels/rooms whitelist parity — Gormes ports Hermes’ allowed_chats/allowed_channels/allowed_rooms whitelist config for gateway platforms: Telegram, Slack, Discord, Mattermost, Matrix, and DingTalk. Config is loaded from config.toml per-platform (e.g., telegram.allowed_chats, slack.allowed_channels) with env-var fallback as escape hatch. Inbound messages from non-whitelisted chat/channel/room IDs are silently dropped before reaching the kernel, with status/doctor reporting whitelist active count and skipped-message evidence. Mirrors Hermes 69d025e4a (Telegram/Mattermost/Matrix/DingTalk) and cd3ef685c (Slack), extending to Discord which already has the feature in both repos. | validated | gateway | medium | gateway, operator, system | internal/gateway/whitelist_test.go | Empty or unconfigured whitelist means all chats/channels/rooms are allowed (preserving existing behavior). A misconfigured whitelist (regex parse failure, empty string after trim) is reported via gateway status/doctor as whitelist_parse_error without dropping all messages. |
| 5 / 5.L | Atomic file write helper with temp+rename pattern — Implement a reusable atomic file write helper that writes to a temp file in the same directory and renames into place, matching Hermes’ atomic write semantics. The helper validates the target directory, handles partial-write cleanup, and never overwrites the original until the temp write succeeds. | validated | tools | small | operator, system | internal/tools/atomic_write_test.go | On rename failure the temp file is left for operator recovery and the error includes both paths. |
| 5 / 5.L | File tool atomic checkpoint integration — Integrate the atomic write helper into file_tools operations that write user files: write_file, edit_file, patch, and template rendering use temp+rename so crashes during writes cannot leave partial or corrupted user files. | validated | tools | small | operator, system | internal/tools/file_tools_atomic_test.go | If the atomic helper fails, the tool returns explicit atomic_write_failed evidence with the temp file path so the operator can recover content. |
| 5 / 5.L | Checkpoints CLI (status/list/prune/clear/clear-legacy) — Gormes exposes a gormes checkpoints CLI commandtree mirroring Hermes’ hermes_cli/checkpoints.py (faa13e49f): bare gormes checkpoints → status, gormes checkpoints status [--limit N] → total size + project count + per-project breakdown with workdir/commits/last-touch/state, gormes checkpoints list [--limit N] → alias for status, gormes checkpoints prune [--retention-days N] [--max-size-mb N] [--keep-orphans] → delete orphan/stale checkpoints and GC the store with a confirmation prompt, gormes checkpoints clear [-f|--force] → delete the entire checkpoint base (asks confirmation unless -f), gormes checkpoints clear-legacy [-f|--force] → delete only legacy archive directories. All commands consume the existing internal/tools.CheckpointManager read model (store_status, prune, clear, clear_legacy) and require no live agent, provider, network, or gateway. Output formatting mirrors Hermes: bytes in human-readable units (B/KB/MB/GB/TB), timestamps in relative age (Xs/m/h/d ago), orphan/live state flags, and per-project columnar layout. | validated | tools | medium | operator, system | cmd/gormes/checkpoints_test.go | Missing checkpoint root or empty store produces No checkpoint store at <path> instead of a panic or raw os.Stat error. Unreadable shadow repos or missing workdir markers produce orphan/live evidence in status output without aborting. |
| 5 / 5.L | Checkpoint shadow-repo GC policy — Native checkpoint manager prunes orphan and stale shadow repositories at startup using a deterministic policy before any write-capable file tools depend on rollback state | validated | tools | small | operator, child-agent, system | internal/tools/checkpoint_manager_test.go | Checkpoint status reports shadow_gc_unavailable, orphan_shadow_repo, or stale_shadow_repo evidence instead of silently leaving rollback directories to accumulate. |
| 5 / 5.L | File read dedup cache invalidation and wrapper guard — Native file-read helpers keep dedup status out of returned file content, invalidate read caches on writes/patches, and detect small wrapper calls as guarded reads | validated | tools | small | operator, child-agent, system | internal/tools/file_read_guard_test.go | File tool status reports read_dedup_cache_disabled or guard_unavailable instead of appending dedup metadata into user-visible file content. |
| 5 / 5.L | File read repeated-stub BLOCKED escalation — Native file-read safety detects repeated model-visible read_file dedup/status stubs and escalates them as BLOCKED evidence before the text can be returned or persisted as normal file content. | validated | tools | small | operator, child-agent, system | internal/tools/file_read_blocked_stub_test.go::TestFileReadRepeatedStubBlocked_* | Repeated stubs return read_dedup_stub_blocked or blocked_result evidence, while legitimate file bytes and first-seen read status remain separate from returned content. |
| 5 / 5.L | Native file task tool surface — Gormes exposes Hermes’ model-visible file task tools in the default registry: read_file with path-required schema, 1-indexed offset/limit pagination, LINE_NUM|CONTENT output, total_lines metadata, duplicate-read status stubs, continuation hints, and workspace-root safety; search_files for content/name search; write_file and patch for root-confined edits; and a foreground-only terminal tool with dangerous-command guardrails. | validated | tools | small | operator, child-agent, system | internal/tools/file_read_tool_test.go + internal/tools/terminal_tool_test.go + cmd/gormes/registry_test.go | Invalid, missing, outside-root, binary, oversized, or unreadable files return structured JSON errors without leaking file content from outside the configured workspace root. Terminal background/PTY gaps return explicit unsupported/compatibility evidence instead of hanging. |
| 5 / 5.L | V4A patch mode for native patch tool — Gormes extends the existing root-confined native patch tool with Hermes’ mode=patch V4A multi-file patch path: parse *** Begin Patch blocks, validate all referenced paths and existing-file state before mutation, then apply Add File, Update File, and Delete File operations atomically enough that validation failures leave the workspace unchanged. The first slice intentionally keeps move operations, fuzzy matching strategies, lint hooks, and checkpoint rollback linkage as follow-up file-ops work. | validated | tools | small | operator, child-agent, system | internal/tools/file_read_tool_test.go::TestPatchToolV4A* | Malformed V4A patches, outside-root paths, stale existing-file state, missing update hunks, and unsupported move operations return structured errors without partially applying any file changes. |
| 5 / 5.L | V4A move operation for native patch tool — Gormes completes the safe V4A move slice for the native patch tool: *** Move File: old/path -> new/path validates both source and destination inside the workspace root, requires source file-state evidence from the current task, refuses to overwrite existing destination files, applies the rename only after every operation in the patch validates, records the moved destination state, and reports the source-to-destination move in patch result evidence. | validated | tools | small | operator, child-agent, system | internal/tools/file_read_tool_test.go::TestPatchToolV4AMove* | Missing sources, stale source state, outside-root destinations, and existing destination files return structured patch_validation_failed or file-state errors without moving, deleting, or overwriting any file. |
| 5 / 5.L | Symlink-preserving atomic writer helper — Gormes adds a shared atomic replace helper that matches Hermes’ symlink-safe config/state writes: regular files are atomically replaced, existing symlinks remain symlinks while their real targets receive the new bytes, broken symlinks create the target without replacing the link, and existing target permissions are preserved unless the caller explicitly supplies first-write permissions. | validated | tools | medium | operator, system | internal/tools/atomic_replace_test.go; internal/config/writer_test.go; cmd/gormes/secrets_command_test.go; internal/persistence/session/index_mirror_test.go | Permission-denied, symlink-loop, outside-root, temp-file, chmod, and rename failures return typed atomic_write_failed or atomic_write_symlink_escape evidence without deleting the original target, replacing the symlink, or leaking private absolute home paths. |
| 5 / 5.L | File write/patch staleness registry + cwd tracking — Gormes write_file and patch match Hermes’ stateful file-edit safety: every read records a per-root path token, hash/mtime/size evidence, and live cwd/task context; write_file and patch reject stale edits when the file changed since the last read token; relative paths use the active terminal cwd/task cwd at operation time; successful writes update the registry; missing files, first writes, and external deletions return deterministic evidence without corrupting content. | validated | tools | medium | operator, child-agent, system | internal/tools/file_staleness_test.go; internal/tools/file_state_registry_test.go | Stale edits return file_stale, file_deleted, file_state_missing, or file_state_cwd_mismatch evidence with redacted paths and no mutation; successful edits update registry state so a later patch does not falsely fail on the agent’s own write. |
| 5 / 5.L | Terminal cwd config bridge — gormes config set terminal.cwd <path> persists the Hermes-compatible terminal working-directory setting, config.Load reads [terminal].cwd, and the default terminal tool starts foreground commands from that configured workspace while terminal.cwd = "." remains the launch-directory placeholder | validated | tools | small | operator, gateway, system | cmd/gormes/config_command_test.go::TestConfigCommand_SetTerminalCWDWritesTOMLAndLoads + cmd/gormes/registry_test.go::TestBuildDefaultRegistryPassesTerminalCWDToTerminalTool + internal/tools/terminal_tool_test.go::TestTerminalToolDefaultWorkdirExpandsTilde | Missing or placeholder terminal.cwd falls back to the process launch directory; missing explicit directories return the terminal tool’s existing resolve-working-directory error instead of silently running elsewhere. |
| 5 / 5.L | Terminal deleted-cwd recovery — Gormes foreground terminal execution recovers when the configured/session working directory has been deleted: before launching bash it resolves the nearest existing ancestor, falls back to the system temp directory only when no ancestor exists, records bounded recovery evidence, and avoids wedging later terminal calls on the same stale cwd. | validated | tools | small | operator, child-agent, system | internal/tools/terminal_tool_test.go::TestTerminalToolRecoversWhenConfiguredWorkdirDeleted | Missing default/session cwd returns terminal_cwd_recovered evidence and runs from a real directory; explicit invalid per-call workdir requests still return resolve-working-directory errors instead of silently running elsewhere. |
| 5 / 5.L | search_files hidden-root and context-line parsing drift — Native search_files preserves Hermes search semantics for hidden roots and context output: when the requested root is hidden, content and file-name search include it; context lines parse the rightmost -<line>- delimiter so hyphenated numeric filenames are not truncated. | validated | tools | small | operator, child-agent, system | internal/tools/file_search_drift_test.go | Malformed context output returns search_context_parse_failed evidence and hidden-root traversal remains root-confined; outside-root hidden paths still fail closed without leaking content. |
| 5 / 5.L | Structured lint delta for native write/patch tools — Gormes write_file and patch preserve Hermes’ post-edit structured lint feedback for JSON, YAML, and TOML files: successful writes still land the requested bytes, but the tool result includes lint evidence showing clean output, malformed post-write content, or pre-existing parse failures that were not newly introduced by the edit. | validated | tools | small | operator, child-agent, system | internal/tools/file_lint_delta_test.go | Unsupported extensions omit lint evidence for this slice; malformed structured files return bounded parser errors without rolling back the edit, shelling out to external linters, reading files outside the workspace root, or leaking absolute home paths. |
| 5 / 5.L | Python syntax lint delta for native write/patch tools — Gormes write_file and patch preserve Hermes’ post-edit Python syntax lint feedback for .py files: successful writes still land the requested bytes, but the tool result includes lint evidence for clean Python, syntax errors introduced by the edit, and pre-existing syntax errors that were not introduced by the edit. | validated | tools | small | operator, child-agent, system | internal/tools/file_lint_delta_test.go | Python syntax lint uses a bounded syntax-only parser path and never executes the edited module. If a parser binary is unavailable in production, the write still succeeds and omits lint evidence rather than blocking file edits or invoking terminal/execute_code. |
| 5 / 5.L | Shell lint delta for native write/patch tools — Gormes write_file and patch preserve Hermes’ external shell-linter post-edit feedback for .js, .ts, .go, and .rs files: successful edits still land the requested bytes, but tool results include bounded lint evidence when the matching command reports errors and skipped evidence when the command is unavailable. | validated | tools | small | operator, child-agent, system | internal/tools/file_lint_delta_test.go | Missing node, npx, go, or rustfmt returns skipped lint evidence without blocking the write/patch, shelling through terminal, reading outside the workspace root, or leaking absolute home paths. |
| 5 / 5.L | Patch replace no-match did-you-mean hint — Gormes native patch replace mode mirrors Hermes’ model-visible recovery help when old_string is not found: a missing match returns a bounded Did you mean one of these sections? hint with nearby line-numbered snippets, plus generic read/search guidance when no useful nearby text exists. The slice changes only the error payload for failed replace-mode matches; successful exact replacements, V4A patches, lint evidence, stale-state protection, and file mutation semantics stay unchanged. | validated | tools | small | operator, child-agent, system | internal/tools/file_read_tool_test.go::TestPatchToolReplaceNoMatchIncludesDidYouMeanHint | If the target file is unreadable, binary, stale, outside the workspace, or has ambiguous duplicate matches, the existing typed error path remains unchanged and no speculative file content is leaked. Similar-section snippets are capped and include only lines already present in the addressed workspace file. |
| 5 / 5.L | Core fuzzy replace strategies for native patch tool — Gormes native patch replace mode mirrors Hermes’ core fuzzy replacement chain for common model-authored old_string drift: exact matches still win first, then line-trimmed, whitespace-normalized, indentation-flexible, escape-normalized, and trimmed-boundary matches can apply one unique replacement or all replacements when replace_all=true. The slice is limited to replace-mode matching and keeps V4A patch parsing, file-state checks, lint evidence, binary/path guards, and no-match hints on their existing paths. | validated | tools | small | operator, child-agent, system | internal/tools/file_read_tool_test.go::TestPatchToolFuzzyReplace | If no core fuzzy strategy finds a unique match, the existing bounded no-match hint remains visible. Ambiguous fuzzy matches without replace_all=true, stale file-state failures, binary files, unreadable files, and V4A validation failures return typed errors without mutating files. |
| 5 / 5.L | Unicode-normalized fuzzy replace for native patch tool — Gormes native patch replace mode mirrors Hermes’ unicode-normalized fuzzy replacement strategy: smart quotes, apostrophes, en/em dashes, ellipses, and non-breaking spaces in either file content or model-supplied old_string normalize to their ASCII equivalents for matching while replacements still apply to the original byte positions. The slice is limited to replace-mode matching and preserves exact/core fuzzy precedence, replace_all ambiguity rules, file-state checks, lint evidence, binary/path guards, V4A patch behavior, and no-match hints. | validated | tools | small | operator, child-agent, system | internal/tools/file_read_tool_test.go::TestPatchToolFuzzyReplaceUnicodeSmartQuotes/TestPatchToolFuzzyReplaceUnicodeDashAndEllipsis/TestPatchToolFuzzyReplaceUnicodeAmbiguousRequiresReplaceAll | If unicode normalization does not find a unique match, the existing bounded no-match hint remains visible. Ambiguous unicode-normalized matches without replace_all=true, stale file-state failures, binary files, unreadable files, and V4A validation failures return typed errors without mutating files. |
| 5 / 5.L | Block-anchor fuzzy replace for native patch tool — Gormes native patch replace mode mirrors Hermes’ block-anchor fuzzy replacement strategy after exact, core fuzzy, and unicode-normalized strategies fail: same first and last lines anchor a candidate block, normalized middle content must meet Hermes’ 0.50 threshold for a single candidate or 0.70 threshold for multiple candidates, and replacements apply only to original byte positions. The slice is limited to replace-mode matching and preserves exact/core/unicode precedence, replace_all ambiguity rules, read-before-edit file-state checks, lint evidence, binary/path guards, V4A patch behavior, and no-match hints. | validated | tools | small | operator, child-agent, system | internal/tools/file_read_tool_test.go::TestPatchToolFuzzyReplaceBlockAnchor | If block-anchor matching does not find a unique safe candidate, the existing bounded no-match hint remains visible. Ambiguous block-anchor matches without replace_all=true, stale file-state failures, binary files, unreadable files, V4A validation failures, and low-similarity middle sections return typed errors without mutating files. |
| 5 / 5.L | V4A fuzzy hunk matching for native patch tool — Gormes native patch mode=patch mirrors Hermes V4A update-hunk matching: update hunks use the same fuzzy replacement strategy chain as replace mode instead of exact strings.Count, and @@ context hint @@ may recover a hunk by searching only the nearby window around the hint. The slice preserves validate-before-mutate semantics, stale file-state checks, root/binary guards, add/delete/move behavior, lint evidence, and replace-mode behavior. | validated | tools | small | operator, child-agent, system | internal/tools/file_read_tool_test.go::TestPatchToolV4AFuzzyHunk | If no unique fuzzy hunk match exists, V4A patch validation fails without mutating any file. Ambiguous matches, stale file-state failures, binary files, outside-root paths, and lint failures continue to return typed evidence through the existing patch payload. |
| 5 / 5.L | Context-aware fuzzy replace for native patch tool — Gormes native patch replace mode mirrors Hermes’ final context-aware fuzzy replacement strategy after exact, core fuzzy, unicode-normalized, and block-anchor strategies fail: candidate blocks of the same line count match when at least 50% of pattern lines have per-line similarity of 0.80 or higher, then the normal uniqueness/replace_all rules decide whether the replacement can apply. The slice is limited to replace-mode matching and preserves file-state guards, path/binary guards, lint evidence, V4A patch behavior, rollback, and no-match hint behavior. | validated | tools | small | operator, child-agent, system | internal/tools/file_read_tool_test.go::TestPatchToolFuzzyReplaceContextAware | If no context-aware candidate exists, or multiple candidates exist without replace_all, patch returns the existing no-match or ambiguity evidence without mutating the file. Lower-similarity blocks remain no-match rather than speculative edits. |
| 5 / 5.L | V4A patch apply rollback for native patch tool — Gormes native patch mode=patch snapshots the workspace after all V4A operations validate and before any filesystem mutation, then restores that snapshot if an apply-time write/delete/move step fails after earlier actions changed files. The slice preserves validate-before-mutate semantics, successful add/update/delete/move payloads, stale file-state checks, root/binary guards, fuzzy hunk matching, lint evidence, and replace-mode behavior. | validated | tools | small | operator, child-agent, system | internal/tools/file_read_tool_test.go::TestPatchToolV4ARollback | If snapshot capture fails before mutation, patch returns a typed apply failure without changing files. If restore fails after an apply error, the payload includes rollback_error and the partial modified/created/deleted evidence so the operator can recover. |
| 5 / 5.L | Patch replace post-write verification — Gormes native patch replace mode mirrors Hermes’ post-write verification: after a successful replace write, the tool re-reads the target file and confirms the on-disk bytes match the intended content before returning status ok or updating file-state evidence. Silent write/no-persist failures and verify-read errors return bounded typed errors and do not record the patch as successful. | validated | tools | small | operator, child-agent, system | internal/tools/file_read_tool_test.go::TestPatchToolReplacePostWriteVerification | If the write call succeeds but verification cannot re-read the file or the bytes differ, the patch result returns a post-write verification error with the relative path only; raw file contents, absolute home paths, provider data, and stale file-state updates are not emitted. |
| 5 / 5.L | Hermes LSP write-time semantic diagnostics — After write_file or patch, Gormes runs a language-server diagnostic pass equivalent to Hermes’ write-time LSP surface, shifts baseline ranges through edits, and returns new semantic errors to the agent without blocking unsupported languages. | validated | tools | medium | - | internal/tools LSP diagnostic fake-server fixtures | - |
| 5 / 5.L | Per-file mutation queue for native write edit and patch tools — Serialize concurrent file mutations that target the same canonical path across native write, edit, patch, and custom file-task tools while preserving parallel execution for independent files. The queue must resolve symlink aliases for existing files, use cleaned absolute paths for new files, cover the full read-modify-write window, and compose with the existing file staleness registry and atomic writer helpers. | validated | tools | small | operator, system | internal/tools/file_mutation_queue_test.go | - |
| 5 / 5.M | Multi-model coordination — Gormes ports Hermes mixture_of_agents as a gated tool over the native provider router: reference models run in bounded parallel with retry/failure isolation, at least one successful reference is required before aggregation, the aggregator prompt includes all successful references in deterministic order, debug evidence records counts/models/errors without trace spam, and OpenRouter requirements degrade safely when credentials/routing are unavailable. | validated | tools | medium | operator | internal/tools/mixture_of_agents_test.go | Missing OpenRouter routing, insufficient successful references, empty model outputs, or aggregator failure returns moa_unavailable, moa_insufficient_references, or moa_aggregator_failed evidence without launching live provider calls in tests. |
| 5 / 5.M | Hermes Kanban durable board core — Gormes implements the first Hermes Kanban vertical slice over native Gormes-owned state: a local SQLite kanban.db under GORMES_HOME/GORMES_KANBAN_* only, idempotent schema creation, tasks with assignees/status/workspace/dependency links, parent-completion child promotion, cycle rejection, atomic ready-task claim, block/unblock readiness recomputation, JSON CLI output, and parity manifest evidence for implemented core commands versus residual Kanban surfaces. | validated | orchestrator | medium | operator, child-agent, system | internal/kanban/store_test.go; cmd/gormes/kanban_command_test.go; internal/cli/command_registry_policy_test.go; cmd/gormes/hermes_cli_parity_test.go | Missing or invalid task ids, unsupported workspace kinds/status filters, cycle attempts, and non-ready claims return visible errors or claimed=false evidence without touching ~/.hermes, reading other agents’ config, spawning workers, or widening into dashboard/gateway state. |
| 5 / 5.M | Hermes Kanban dispatcher and worker spawn loop — Gormes ports the Hermes Kanban dispatcher core as a native gateway-managed loop with a fakeable worker-spawner seam: each tick reclaims stale claims, selects ready assigned tasks, claims through the store, records spawn attempts and spawn failures, passes GORMES_* worker context, respects max-spawn caps across successful and failed attempts, and blocks tasks behind a circuit breaker after repeated failures. Real OS process spawning, PID/crash detection, max-runtime termination, worker log rotation, and production config wiring remain a separate row. | validated | orchestrator | large | operator, gateway, child-agent, system | internal/kanban/dispatcher_test.go; internal/gateway/kanban_dispatcher_test.go | Missing profile binaries, spawn failures, exceeded runtime caps, stale worker heartbeats, and locked databases report dispatcher_unavailable, worker_spawn_failed, worker_timed_out, or task_circuit_open evidence without infinite retry loops. |
| 5 / 5.M | Hermes Kanban production worker process binding — Gormes binds the fakeable Kanban dispatcher spawner to a production worker launcher that resolves Gormes profiles, resolves the default worker executable with PATH-first gormes lookup and current-executable fallback for stripped cron/systemd PATHs, builds the native gormes worker argv/env with Kanban context pins and the kanban-worker skill, redirects stdout/stderr to per-task logs with bounded rotation, records worker PID/run metadata, detects crashed worker PIDs, enforces per-task max-runtime caps through injected process controls, and reports worker_spawn_failed, worker_crashed, worker_timed_out, or task_circuit_open evidence without reading live Hermes config. | validated | orchestrator | medium | operator, gateway, child-agent, system | internal/kanban/process_spawner_test.go; internal/kanban/worker_lifecycle_test.go; internal/gateway/kanban_dispatcher_test.go | Explicitly configured missing gormes binaries, invalid profile names, unwritable workspaces/log paths, stale PIDs, process-kill failures, and max-runtime expiry return typed evidence and release or block the task according to the dispatcher failure policy; default worker spawns fall back to the current executable when PATH lacks gormes and never spawn Hermes Python. |
| 5 / 5.M | Hermes Kanban worker tools and prompt gating — Gormes exposes Hermes-compatible worker tools for Kanban tasks: kanban_show, kanban_complete, kanban_block, kanban_heartbeat, kanban_comment, kanban_create, and kanban_link are only visible inside a Kanban worker task or explicit profile toolset, enforce task ownership for destructive operations, and carry structured parent summaries/metadata to children. | validated | tools | medium | child-agent, operator, system | internal/kanbantools/kanban_tools_test.go; cmd/gormes/registry_kanban_test.go | Outside a Kanban worker context the tools are hidden or return kanban_context_missing; attempts to complete, block, or heartbeat a foreign task return kanban_task_ownership_denied without mutating the board. Kanban comment cross-task handoff and author hardening are owned by the completed follow-up child row. |
| 5 / 5.M | Kanban orchestrator board-routing tools — Refresh the completed Kanban worker-tool surface for current Hermes: orchestrator profiles with the kanban toolset see kanban_list and kanban_unblock in addition to lifecycle tools, dispatcher-spawned task workers never see board-routing tools even if their profile has the kanban toolset, and model-facing Kanban tool args parse string booleans for include_archived and triage instead of treating non-empty strings as truthy. | validated | tools | small | child-agent, operator, system | internal/kanbantools/kanban_tools_test.go; cmd/gormes/registry_kanban_test.go | If board-routing tools leak into worker schemas, a scoped child agent can enumerate or unblock board work outside its assigned task; if string booleans are not parsed explicitly, model-supplied "false" can archive-list or triage tasks unexpectedly. |
| 5 / 5.M | Kanban comment author hardening and cross-task handoff policy — Gormes’ kanban_comment tool matches current Hermes worker behavior: callers cannot supply or expose an author override, comments are attributed from the worker profile with a worker fallback, and workers may comment on other tasks as a deliberate handoff channel while complete, block, and heartbeat remain scoped to the worker task. | validated | tools | small | child-agent, operator, system | internal/kanbantools/kanban_tools_test.go | If this regresses, a child agent can forge authoritative-looking Kanban comment authors through args.author, or Gormes can block the Hermes cross-task handoff channel. |
| 5 / 5.M | Hermes Kanban slash/gateway/dashboard surfaces — Gormes ports the operator surfaces around Kanban: /kanban routes in TUI/gateway use the same parser/output as gormes kanban, gateway status exposes dispatcher state and nudge capability, and the dashboard shows live Kanban tasks, lanes, filters, worker runs, and dispatcher nudges over authenticated Gormes dashboard routes. | validated | gateway | large | operator, gateway, system | internal/apiserver/dashboard_kanban_test.go; internal/gateway/kanban_command.go; internal/kanbantools/kanban_tools_test.go | When the dispatcher or dashboard stream is unavailable, operators see kanban_dispatcher_unavailable or kanban_dashboard_unavailable evidence; /kanban remains recognized and unavailable rather than leaking to the model. |
| 5 / 5.M | Native TUI /kanban slash command binding over gormes kanban — The native Bubble Tea TUI treats /kanban as a local operator command, not prompt text: idle and busy /kanban init/list/show/create/complete invocations dispatch through the same gormes kanban Cobra command tree used by the CLI, clear the editor, and surface bounded command output or redacted error evidence in the status line. | validated | gateway | small | operator, gateway, system | internal/tui/slash_kanban_test.go; cmd/gormes/tui_kanban_slash_test.go | If the Kanban store or command runner is unavailable, the TUI consumes /kanban with kanban: ... evidence instead of forwarding the slash text to the model or silently dropping the command. |
| 5 / 5.M | Gateway /kanban shared command-runner binding — Gateway /kanban ... messages are consumed as operator commands and delegated through an injected Kanban slash runner backed by the same gormes kanban Cobra command tree used by the CLI/TUI. The gateway sends the runner output as a native reply, surfaces runner failures as bounded kanban error evidence, preserves active-turn bypass so the slash text never reaches the model, and truncates long output with Gormes-branded terminal guidance. | validated | gateway | small | operator, gateway, system | internal/gateway/kanban_command_test.go; cmd/gormes/gateway_kanban_slash_test.go | If the runner is unavailable or returns an error, the gateway consumes /kanban and sends redacted kanban error evidence instead of falling back to hand-coded mutation logic or submitting slash text to the active model turn. |
| 5 / 5.M | Kanban slash help and usage-error UX — Gormes ports Hermes’ current /kanban chat-command help behavior over the shared Cobra runner: bare /kanban plus help, --help, -h, and ? return a curated short help block; /kanban <subcommand> -h returns captured subcommand help; parse/usage errors return bounded /kanban-branded guidance instead of raw Cobra/argparse-style noise; valid command failures such as missing task IDs still surface as command errors. | validated | gateway | small | operator, gateway, system | cmd/gormes/tui_kanban_slash_test.go; internal/gateway/kanban_command_test.go | If the slash runner cannot classify a usage failure, it should still consume the slash text with bounded /kanban usage evidence and must not submit the text to the model, mutate Kanban state, or leak home-directory paths. |
| 5 / 5.M | Kanban dashboard dispatch quick path — The authenticated Gormes dashboard exposes a Kanban dispatch quick path that mirrors Hermes’ Kanban dashboard plugin POST /dispatch: operators can trigger one immediate dispatcher pass from the dashboard instead of waiting for the periodic gateway tick, receive the typed dispatch result with build attribution, and get explicit degraded evidence when the dispatcher seam is unavailable or fails. | validated | gateway | small | operator, gateway, system | internal/apiserver/dashboard_kanban_test.go:TestDashboardKanban_DispatchQuickPath | If the dashboard has no Kanban dispatcher seam or the pass fails, the endpoint returns kanban_dispatcher_unavailable or kanban_dispatch_failed without opening a Kanban database, starting a gateway loop, leaking worker env, or submitting slash text to the model. |
| 5 / 5.M | Kanban dashboard task run history endpoint — The authenticated Gormes dashboard exposes per-task Kanban run history matching Hermes’ dashboard drawer and kanban runs <task-id> read model: GET /api/kanban/tasks/{id}/runs requires the active dashboard session token, validates the task exists, returns build attribution plus task_id and ordered runs, emits runs: [] for tasks with no attempts, and reports typed store/not-found evidence without reading live Hermes state or mutating tasks. | validated | gateway | small | operator, gateway, system | internal/apiserver/dashboard_kanban_test.go:TestDashboardKanban_TaskRuns | Missing Kanban storage, missing task IDs, or run ledger read failures return kanban_store_unavailable, kanban_task_not_found, or kanban_runs_error without opening Hermes config, starting a dispatcher, spawning workers, or leaking local paths. |
| 5 / 5.M | Kanban dispatcher status in gateway /status — Gormes exposes the gateway-owned Kanban dispatcher state in the operator-facing /status reply. The status command reads the existing runtime status store and renders dispatcher state, last tick, spawned count, failed spawn count, auto-block count, and degraded last_error evidence without reading Hermes config or touching Kanban databases. | validated | gateway | small | operator, gateway, system | internal/gateway/status_command_test.go:TestStatusCommandIncludesKanbanDispatcherStatus; internal/gateway/status_command_test.go:TestStatusCommandOmitsKanbanDispatcherWhenRuntimeStatusUnreadable | If the runtime status store is unavailable or unreadable, /status omits the Kanban section rather than failing the whole status command; dispatcher loop errors already surface as degraded KanbanDispatcherStatus.LastError. |
| 5 / 5.M | Kanban multi-board isolation — Kanban dispatcher enforces board-scoped isolation: workers spawned for board A cannot see or mutate board B’s tasks. The SQLite store uses per-board database files or namespaced tables, and the dispatcher validates the board name before spawning. | validated | orchestrator | small | operator, system | - | - |
| 5 / 5.M | Kanban workspace context injection — Kanban worker spawning injects the board’s workspace directory as the worker’s working directory and loads the workspace’s AGENTS.md/CLAUDE.md context, mirroring Hermes workspace-path isolation. | validated | orchestrator | small | operator, system | - | - |
| 5 / 5.M | Kanban run history persistence — Kanban run history records spawn attempts, successes, failures, and completion evidence per task so operators and the dispatcher can inspect past runs and detect spin-loop failures. | validated | orchestrator | small | operator, system | - | - |
| 5 / 5.M | Kanban notification delivery parity — Kanban worker completion triggers notification delivery to the board owner’s configured channel (Telegram/Discord/Slack) with task summary and run evidence. | validated | orchestrator | small | operator, system | - | - |
| 5 / 5.M | Kanban chat board DB pin — At chat/TUI startup, Gormes pins the current Gormes Kanban board database path into GORMES_KANBAN_DB when the operator has not already supplied an explicit GORMES_KANBAN_DB. This mirrors Hermes’ chat-time HERMES_KANBAN_BOARD pin while preserving Gormes-owned board state: in-process kanban_* tools, /kanban slash commands, and shelled gormes kanban ... calls in the same chat see the same selected board even if another terminal switches the global current-board pointer later. | validated | orchestrator | small | operator, child-agent, gateway, system | cmd/gormes/chat_command_test.go::TestChatCommandPinsCurrentKanbanBoardDBForTools | If the current-board file is unreadable or invalid, chat startup continues with existing Kanban resolution and does not block agent startup. Explicit GORMES_KANBAN_DB remains authoritative. The slice never reads HERMES_KANBAN_BOARD, HERMES_KANBAN_DB, ~/.hermes, or Hermes profile state. |
| 5 / 5.M | Kanban schema migration duplicate-column race guard — Gormes Kanban schema migrations tolerate the same stale-snapshot race Hermes fixed in _migrate_add_optional_columns: if a connection sees an optional column missing but another concurrent opener adds it before this connection executes ALTER TABLE, Gormes treats SQLite duplicate-column errors as a successful idempotent migration while still surfacing real schema failures. | validated | orchestrator | small | operator, gateway, system | internal/kanban/store_test.go::TestKanbanStoreAddColumnIfMissingIgnoresDuplicateColumnRace | Duplicate-column migration races return nil and keep gateway/CLI Kanban open usable; non-duplicate ALTER TABLE failures still return typed migrate kanban table.column errors without deleting tasks, rewriting rows, changing board routing, or reading Hermes home/env state. |
| 5 / 5.M | Kanban notify subscription store and CLI — Gormes ports Hermes Kanban notification subscription persistence and operator commands: the native Kanban store records gateway subscriptions by task/platform/chat/thread with a last_event_id cursor, exposes unseen event lookup plus explicit cursor advancement, and gormes kanban notify-subscribe, notify-list, and notify-unsubscribe provide text and JSON operator evidence over the selected Gormes board. | validated | orchestrator | small | operator, gateway, system | internal/kanban/notify_test.go; cmd/gormes/kanban_notify_test.go | Unknown task IDs, missing required platform/chat values, absent subscriptions, and missing board state return bounded CLI/store errors without starting gateway dispatchers, sending notifications, reading Hermes env/home paths, following watch loops, or mutating task status. |
| 5 / 5.M | Kanban notify delivery engine blocked retention — Gormes ports Hermes’ Kanban notification delivery semantics over the native subscription store: a fakeable delivery engine polls unseen task events for notify subscriptions, sends one bounded message per completed/blocked/gave_up/crashed/worker_timed_out event to the subscribed platform/chat/thread target, advances the cursor after successful sends, removes subscriptions only for completed/gave_up/crashed/worker_timed_out, and deliberately keeps blocked subscriptions active so unblock->block cycles notify again. | validated | gateway | small | operator, gateway, system | internal/kanban/notifier_test.go::TestKanbanNotifierBlockedEventDoesNotUnsubscribe | Missing tasks, unknown platform adapters, send failures, absent subscriptions, and closed contexts return bounded delivery evidence without deleting task events, dropping blocked subscriptions, starting a live gateway, reading Hermes env/home paths, or following watch/tail loops. |
| 5 / 5.M | Kanban stats command and board summary — Gormes ports Hermes kanban stats: the native Kanban store exposes a board summary with non-archived counts by status, nested counts by assignee/status, oldest ready task age in seconds, and the current stats timestamp; gormes kanban stats renders the same operator text and --json emits a build-attributed machine-readable document. | validated | orchestrator | small | operator, gateway, system | internal/kanban/stats_test.go; cmd/gormes/kanban_stats_test.go | Empty boards return empty maps and null oldest_ready_age_seconds; archived tasks are excluded; store/query errors return normal command errors without reading Hermes homes, starting dispatchers, following watch loops, or mutating task state. |
| 5 / 5.M | Kanban corrupt timestamp age hardening — Gormes ports Hermes’ Kanban task-age corruption guard: a task row whose created_at, started_at, completed_at, claim_expires, or heartbeat_at field contains corrupt non-integer SQLite data must not crash board stats, CLI task listing, dashboard Kanban task routes, or dispatcher scans. Corrupt timestamp fields degrade to zero/nil time evidence and oldest-ready age is null when no valid ready timestamp exists. | validated | orchestrator | small | operator, gateway, system | internal/kanban/stats_test.go::TestBoardStatsSkipsCorruptReadyTimestamps; internal/kanban/store_test.go::TestKanbanTaskScansCorruptTimestampsAsZero | Corrupt timestamp fields such as ‘%s’, ‘abc’, empty strings, float-like strings, or NULL values are treated as unavailable for derived ages instead of aborting the whole board/dashboard response. Other task fields and valid timestamp rows remain readable. |
| 5 / 5.M | Kanban named-board workspace and log roots — Gormes preserves Hermes named-board filesystem isolation for worker scratch directories and logs: the default board keeps legacy <root>/kanban/workspaces and <root>/kanban/logs, while a named board whose DB is <root>/kanban/boards/<slug>/kanban.db uses <root>/kanban/boards/<slug>/workspaces and <root>/kanban/boards/<slug>/logs without adding an extra nested kanban/ directory. | validated | orchestrator | small | operator, child-agent, system | internal/kanban/dispatcher_test.go::TestKanbanDispatcherNamedBoardWorkspaceRoot and internal/kanban/process_spawner_test.go::TestKanbanProcessSpawnerNamedBoardLogRoot | Malformed or direct-pinned database paths keep the existing local fallback rooted near the DB and never read Hermes env vars or home directories; worker launch still returns typed spawn/workspace errors instead of deleting board data. |
| 5 / 5.M | Kanban current-board task command routing — Gormes task-level kanban commands route through the Gormes-owned current-board pointer written by gormes kanban boards switch, so create/list/show/claim/complete/block/link operate on the selected board database instead of always opening the default kanban.db. The slice preserves GORMES_KANBAN_DB as the explicit DB pin and does not read Hermes HERMES_KANBAN_BOARD or ~/.hermes state. | validated | orchestrator | small | operator, child-agent, system | cmd/gormes/kanban_command_test.go::TestKanbanTaskCommandsUseCurrentBoard and TestKanbanCommandPreservesExplicitDBPin | If the current-board file points at a missing board, task commands fail with board-not-found evidence rather than silently creating an empty board or falling back to default. |
| 5 / 5.M | Kanban task run history command — Gormes ports Hermes’ kanban runs <task-id> operator read model over the existing native run ledger: the command lists the task’s run attempts in start order, supports --json with build provenance and an empty runs: [] array for tasks with no attempts, emits compact human output with outcome/elapsed/summary/error hints, and routes through the selected Gormes current board without reading Hermes env or home state. | validated | orchestrator | small | operator, gateway, system | cmd/gormes/kanban_runs_test.go | Unknown task IDs, tasks with no runs, empty metadata, and unfinished run timestamps return parseable CLI evidence without starting workers, reading worker log files, or crossing board databases. |
| 5 / 5.M | Kanban boards list/show task-count read model — Gormes ports the read-only Hermes kanban boards list/show/current operator view over the native board registry: board lists include the implicit default board, the selected Gormes current-board marker, per-status task counts, total task counts, JSON build provenance, and Hermes-compatible aliases (ls, current) without reading HERMES_KANBAN_* live env or ~/.hermes state. | validated | orchestrator | small | operator, child-agent, system | cmd/gormes/kanban_boards_test.go; cmd/gormes/kanban_boards_list_json_test.go | Missing board databases show empty counts without creating databases, and missing named boards return visible not-found evidence instead of silently creating a board or falling back to default. |
| 5 / 5.M | Kanban global —board task command override — Gormes ports Hermes’ global kanban --board <slug> override for task-level commands: create/list/show/runs/claim/complete/block/link operate on the named Gormes board for one invocation without mutating the persisted current-board pointer, while explicit GORMES_KANBAN_DB continues to pin the database path. | validated | orchestrator | small | operator, child-agent, system | cmd/gormes/kanban_command_test.go::TestKanbanCommandBoardFlagRoutesWithoutSwitchingCurrent | Missing or invalid board slugs return bounded CLI errors and do not create a board directory or fall back to the default board. |
| 5 / 5.M | Kanban GC terminal event and worker-log retention — Gormes ports Hermes kanban gc retention as a safe native store and CLI slice: old task_events are deleted only for terminal done/archived tasks, worker log files older than the selected retention are pruned only from the current Gormes board log root, and active task state, board databases, workspaces, directories, and Hermes home paths are never deleted. | validated | orchestrator | small | operator, child-agent, system | internal/kanban/store_test.go::TestKanbanGC_*; cmd/gormes/kanban_command_test.go::TestKanbanGCCommand_* | Missing log directories return zero deletions, invalid retention values return bounded CLI errors, and filesystem cleanup failures surface kanban_gc_unavailable evidence without deleting active task events or crossing board roots. |
| 5 / 5.M | Kanban worker log read command — Gormes ports Hermes kanban log <task-id> [--tail N] as a read-only native operator command: it reads the selected Gormes board’s worker log for the task, optionally returns only the last N bytes with partial-line trimming, prints missing-log evidence without creating files, and never reads HERMES_HOME, Hermes profiles, live worker processes, or other boards. | validated | orchestrator | small | operator, child-agent, system | internal/kanban/store_test.go::TestReadWorkerLog*; cmd/gormes/kanban_log_test.go | Missing logs return a bounded no log for <task-id> error on stderr; unreadable logs return redacted read-worker-log evidence without mutating task state, worker logs, board state, or Hermes-owned paths. |
| 5 / 5.M | Kanban task event tail command — Gormes ports Hermes kanban tail <task-id> [--interval seconds] as a read-only native operator command: it follows the selected Gormes board’s task event stream, prints existing and newly observed task_events as [timestamp] kind payload, clamps the poll interval to Hermes’ 100ms minimum, exits cleanly on cancellation, and never starts workers, reads Hermes home state, or tails worker log files. | validated | orchestrator | small | operator, child-agent, system | cmd/gormes/kanban_tail_test.go::TestKanbanTailCommand* | Missing boards or unreadable event stores return bounded CLI/store errors before entering the follow loop; cancellation exits without mutating task state, worker logs, board state, or Hermes-owned paths. |
| 5 / 5.M | Kanban worker heartbeat, reclaim, and zombie detection — Kanban workers emit a heartbeat at a configurable interval; the dispatcher reclaims tasks whose worker heartbeat is stale beyond the threshold, marks the worker as zombie, and re-enqueues the task with retry budget honored. Auto-block on incomplete worker exit, darwin zombie detection, and a unified failure counter across spawn/timeout/crash outcomes are part of the same contract. Mirrors Hermes v0.13.0 PRs #21183, #21214, #20188, #20410. | validated | orchestrator | medium | operator, system | internal/kanban/worker_heartbeat_test.go | Without heartbeat reclaim, a hung worker holds its task forever and the dispatcher cannot reassign; status reports show worker=stale and task=held with last-seen timestamp instead of pretending the work is in progress. |
| 5 / 5.M | Hermes Kanban specify triage parity — Gormes ports Hermes’ specify triage feature: gormes kanban create —triage can park rough ideas in triage, and gormes kanban specify | validated | gateway | small | operator, gateway | internal/kanban/specify_test.go; cmd/gormes/kanban_command_test.go | When the auxiliary LLM is unavailable or returns unparseable output, /kanban specify returns a degraded result with the raw error redacted and does not modify the task. |
| 5 / 5.N | Todo — Gormes ports Hermes todo_tool as a small stateful native tool: items validate id/content/status, duplicate IDs keep the last entry while preserving list order, replace vs merge semantics match Hermes, only pending/in_progress items are injected into prompts, summary counts include pending/in_progress/completed/cancelled, and storage is deterministic under an injected per-session root. | validated | tools | small | operator | internal/tools/todo_tool_test.go | Malformed tool args, missing store, corrupt JSON, or state-root denial returns todo_invalid_args, todo_store_unavailable, or todo_store_corrupt evidence without touching file-tools or global session state; Hermes-compatible item normalization defaults invalid status to pending and empty content to (no description). |
| 5 / 5.N | Clarify — Gormes ports Hermes clarify as a schema-validated, interruptible user-reply tool: required question text, up to four trimmed choices, platform-added Other behavior, callback/resume routing for gateway and TUI, deterministic unavailable output in non-interactive cron/oneshot contexts, and one-shot resume-token cleanup after the next user reply. | validated | tools | medium | operator, gateway, child-agent, system | internal/tools/clarify_tool_test.go; internal/gateway/clarify_resume_test.go | Clarify returns clarify_invalid_args, clarify_unavailable, clarify_timeout, or clarify_route_missing evidence instead of blocking cron/oneshot turns, reading stdin from a noninteractive context, or leaking a pending route into the wrong session. |
| 5 / 5.N | Session search tool schema and argument validation — internal/persistence/sessionsearchtool defines a session_search Tool descriptor with Hermes-style recall guidance, JSON schema, timeout, and argument validator for query, role_filter, scope, sources, mode, limit, and current_session_id without registering the tool globally or reading memory | validated | tools | small | operator, child-agent, system | internal/persistence/sessionsearchtool/session_search_tool_schema_test.go::TestSessionSearchToolSchema_* | Invalid input returns session_search_invalid_args evidence instead of widening recall or falling back to global search. |
| 5 / 5.N | Session search tool execution wrapper — internal/persistence/sessionsearchtool session_search executes over existing internal/memory SearchSessions/SearchMessages APIs, preserving same-chat defaults, explicit user/source widening, Hermes role_filter turn narrowing, hidden tool-source isolation by default, lineage-root exclusion in recent mode, and Goncho/Honcho-compatible evidence without changing ranking or persistence | validated | tools | small | operator, child-agent, system | internal/persistence/sessionsearchtool/session_search_tool_execution_test.go::TestSessionSearchToolExecution_* + internal/memory/session_catalog_test.go | Tool result reports session_search_unavailable, source_filter_denied, lineage_root_excluded, or session_search_invalid_args evidence instead of widening recall silently. |
| 5 / 5.N | Session shutdown memory transcript handoff — Session-end memory handoff receives the completed transcript/message slice explicitly, while skip_memory, interrupted turns, and expiry-only cleanup still prevent hidden GONCHO or Honcho-compatible memory writes | validated | memory | small | operator, gateway, system | internal/memory/shutdown_handoff_test.go::TestShutdownMemoryHandoff_* | Memory shutdown status reports shutdown_messages_unavailable, shutdown_memory_skipped, or shutdown_memory_interrupted instead of calling a no-arg provider that silently loses transcript context. |
| 5 / 5.N | Debug helpers — Gormes ports Hermes DebugSession as shared tool debug infrastructure: tool-specific env vars enable a per-tool session ID, log entries remain in memory until explicit save, save writes deterministic JSON under an injected debug log directory, disabled debug mode is a no-op, get_session_info returns enabled/session/path/count evidence, and sensitive arguments are redacted before persistence. | validated | tools | small | operator, system | internal/tools/debug_helpers_test.go | When debug mode is disabled or the log directory is unavailable, tool calls return debug_disabled or debug_log_unavailable evidence without hidden writes or tool execution failure. |
| 5 / 5.N | Debug share paste sweep scheduler contract — Gormes debug-share planning owns a persisted paste-deletion queue and an hourly scheduler hook that sweeps expired paste.rs entries with bounded evidence while retaining opportunistic CLI-only cleanup as a fallback | validated | tools | small | operator, system | internal/cli/debug_paste_sweep_test.go::TestDebugPasteSweep_* | If the scheduler is not running, debug-share status reports paste_sweep_scheduler_unavailable and the CLI fallback can still sweep pending entries on the next debug invocation. |
| 5 / 5.N | Doctor GitHub CLI auth fallback — gormes doctor reports GitHub readiness as pass/usable when no GITHUB_TOKEN/GH_TOKEN is configured but gh auth status --json authenticated exits successfully, matching active Hermes’ doctor contract; missing gh, unauthenticated gh, timeouts, or command failures degrade with redacted evidence and never require network in tests. | validated | tools | small | operator, system | cmd/gormes/doctor_github_auth_test.go; internal/doctor/github_auth_test.go | Doctor reports github_auth_unavailable, github_cli_missing, github_cli_unauthenticated, github_cli_timeout, or github_cli_status_failed evidence without logging tokens, gh stdout/stderr bodies, or profile paths. |
| 5 / 5.N | Planner audit blank-subphase control-plane bucket — internal/plannerloop.subphaseFromTask normalizes empty/non-conforming task labels to a sentinel ControlPlaneSubphaseID = ‘control-plane/backend’ so SummarizeAutoloopAudit toxic_subphases and hot_subphases never use the empty string as a subphase_id | validated | orchestrator | small | operator, system | internal/plannerloop/autoloop_audit_test.go | When a worker fails before claiming a row (no Task value), the audit attributes the event to ControlPlaneSubphaseID instead of ” so reports don’t collapse multiple unrelated incidents into a blank bucket. |
| 5 / 5.N | Autoloop recent-failure detail excerpts — Planner audit includes bounded worker_failed detail excerpts in Recent failed tasks so control-plane/backend failures are distinguishable without reopening the raw ledger | validated | orchestrator | small | operator, system | internal/plannerloop/autoloop_audit_test.go::TestSummarizeAutoloopAuditIncludesRecentFailureDetail | If no detail fields exist on a failed ledger event, the audit continues to show status and task only instead of fabricating a cause. |
| 5 / 5.N | Backend usage-limit stdin health bypass — Builder-loop treats backend usage-limit/stdin-wait exits that produce no worker diff as backend infrastructure degradation, emits run-level backend evidence, and avoids charging the selected feature row as a worker_error quarantine candidate | validated | orchestrator | small | system | internal/builderloop/run_health_test.go::TestRunOnce_BackendUsageLimitDoesNotQuarantineRow | When every configured backend is usage-limited or waiting for stdin, the loop emits backend_degraded/backend_waiting_for_stdin evidence and leaves feature-row health unchanged so the planner sees an infrastructure outage instead of a toxic implementation row. |
| 5 / 5.N | Cronjob tool API + schedule parser parity — Inventory umbrella for Hermes cronjob tool parity: the single upstream cronjob(action=…) tool spans schedule parsing, prompt/script safety, create/list/update/pause/resume/remove/run actions, context_from chaining, and multi-target delivery. Gormes implements these as dependency-ordered native Go rows below; this umbrella is not a builder slice and remains planned only as a source-backed parent. | validated | tools | umbrella | - | - | - |
| 5 / 5.N | Cron schedule parser + repeat state fixtures — internal/automation/cron adds a pure Hermes-compatible schedule parser and repeat-state read model before any public cronjob tool handler is exposed. ParseCronSchedule(input string, now time.Time) returns a typed ParsedSchedule for one-shot durations (30m, 2h, 1d), recurring intervals (every 30m, every 2h), 5-field cron expressions, and ISO timestamps. CronNextRunDecision(parsed, lastRunUnix, repeatCompleted, now) reports whether a one-shot is still recoverable inside the 120s grace window, whether recurring jobs should fast-forward stale next-run times, and whether finite repeat counts are exhausted. | validated | tools | small | operator, system | internal/automation/cron/schedule_parser_test.go | Invalid schedules and exhausted repeat counters return typed unavailable evidence; the scheduler keeps skipping only the bad job instead of stopping the whole cron loop. |
| 5 / 5.N | Cron recurring next-run failure preservation — internal/automation/cron adds a pure run-completion state helper and executor post-run hook that updates LastRunUnix, LastStatus, and RepeatCompleted after every run while preserving recurring cron/interval jobs as active (Paused=false) when next-run computation is unavailable or returns no future time; one-shot or finite-repeat exhaustion may become terminal, but recurring compute failures must produce typed evidence instead of being confused with user pause/completion | validated | tools | small | operator, system | internal/automation/cron/run_completion_test.go | When the next-run decision cannot be computed for a recurring schedule, the job remains unpaused with LastStatus=error and run-store/evidence text describing cron_next_run_unavailable; the scheduler may skip the bad tick but must not silently disable the schedule. |
| 5 / 5.N | Cron prompt/script safety + pre-run script contract — internal/automation/cron exposes pure safety helpers for cron creation/update: ScanPromptForCronThreat(prompt string) (CronSafetyFinding, bool) blocks Hermes critical prompt-injection/exfiltration patterns plus invisible Unicode controls, and ValidatePreRunScriptPath(script string, scriptsRoot string) returns a clean relative script path only when it stays under the injected scriptsRoot. No script execution or scheduler wiring occurs in this slice. | validated | tools | small | operator, system | internal/automation/cron/prompt_script_safety_test.go | Cron create/update returns blocked_prompt, invisible_unicode, script_absolute, script_home_relative, or script_traversal evidence before persistence instead of silently storing unsafe jobs. |
| 5 / 5.N | Cron GitHub auth-header scanner parity — Gormes refreshes the completed cron prompt scanner and cronjob tool scanner to match current Hermes cron auth-header behavior: bundled GitHub API examples that pass $GITHUB_TOKEN in Authorization headers to api.github.com, including single- or double-quoted URLs, are allowed, while Authorization headers carrying secret variables to arbitrary hosts and URL/form secret exfiltration remain blocked with stable evidence. | validated | tools | small | operator, system | internal/automation/cron/prompt_script_safety_test.go::TestScanPromptForCronThreat_GitHubAuthHeaderAllowlist and internal/tools/cronjob_tool_test.go::TestCronjobTool_GitHubAuthHeaderScannerParity | If the scanner drifts, safe built-in GitHub cron skills are blocked on every tick or unsafe Authorization-header exfiltration to non-GitHub hosts is accepted. |
| 5 / 5.N | Cronjob tool action envelope over native store — internal/tools exposes a cronjob action adapter over the native internal/automation/cron Store without starting the scheduler: action=create validates schedule, prompt/script safety, name uniqueness, optional repeat/model/toolsets/workdir fields, then persists a cron.Job; list returns redacted job summaries; update preserves unspecified fields and supports empty-array/string clearing for skills, enabled_toolsets, workdir, script, and context_from; pause/resume flip Paused; remove deletes by ID; run returns a RunNow request object for the caller instead of executing immediately. | validated | tools | medium | operator, gateway, system | internal/tools/cronjob_tool_test.go | The tool envelope returns JSON-string error fields for missing job IDs, invalid schedule, blocked prompt/script, duplicate name, store-disabled, and run-now unsupported rather than panicking or starting hidden cron turns. |
| 5 / 5.N | Cron run resource release contract — internal/automation/cron exposes a typed per-run resource-release ledger helper that records spawned subprocess PIDs (via an injected killer interface), io.Closer values for SQLite session DBs, and outbound HTTP RoundTripper idle-closables. The helper guarantees deterministic close ordering, idempotent re-entry, partial-failure continuation, and explicit evidence vocabulary so a later Executor binding can release every per-run resource exactly once. The helper does not start a kernel turn, hit a provider, manage MCP stdio runtimes, or change the Scheduler goroutine lifecycle. | validated | gateway | small | operator, system | internal/automation/cron/run_release_test.go::TestRunReleaseLedger_* | Cron run completion records cron_release_subprocess_killed, cron_release_session_db_closed, cron_release_http_idle_closed, cron_release_http_idle_closed_failed, or cron_release_skipped_no_resource evidence so operator-facing health surfaces show whether ephemeral resources were reaped, instead of silently leaking fds across ticks. |
| 5 / 5.N | Cron run resource release executor binding — internal/automation/cron Executor.Run pushes per-run *sql.DB session stores, tool subprocess handles, and outbound RoundTripper idle-closables into the validated Cron run resource release ledger before returning, so the next Scheduler tick observes no leaked fds; success, kernel-error, ctx-cancel, and timeout paths all release through the same ledger exactly once. | validated | gateway | small | operator, system | internal/automation/cron/executor_release_test.go::TestExecutorRunReleasesPerRunResources_* | Cron run completion ledger reports cron_release_executor_skipped, cron_release_executor_panicked, or cron_release_executor_partial when the executor binding cannot guarantee per-run release, instead of silently leaking handles across ticks. |
| 5 / 5.N | Cron context_from output chaining — Cron jobs can reference prior job outputs as bounded prompt context without waiting for same-tick jobs or reading outside the cron output store | validated | tools | small | operator, system | internal/automation/cron/context_from_test.go | Cron execution skips missing, unreadable, invalid, or not-yet-run context sources with visible evidence while preserving the base prompt. |
| 5 / 5.N | Cron multi-target delivery + media/live-adapter fallback — Cron delivery planning supports comma-separated deliver targets, origin/local/platform target normalization, MEDIA tag extraction from final assistant text, and live-adapter-first delivery fallback through the existing cron.DeliverySink abstraction without changing the Phase 2.D run audit store | validated | gateway | small | operator, gateway, system | internal/automation/cron/delivery_plan_test.go | Cron delivery evidence reports target_parse_failed, channel_directory_missing, media_ignored, live_adapter_unavailable, and fallback_sink_used without dropping the run audit row. |
| 5 / 5.N | Cron deliver=all routing intent expansion — Gormes ports Hermes’ reserved cron delivery routing token all: at run/plan time, deliver=all expands to every currently known home-channel target from the injected channel directory, composes with origin, local, explicit targets, and comma-separated lists, and deduplicates by normalized platform/chat/thread without sending live network messages in tests. | validated | gateway | small | operator, gateway, system | internal/automation/cron/delivery_plan_test.go::TestCronDeliveryPlan_RoutingIntentAll | If no channel directory with home targets is available, all yields channel_directory_missing evidence and does not invent delivery targets, inspect live config, or fall back to local silently. |
| 5 / 5.N | Plugin standalone sender cron delivery fallback — Gormes ports Hermes’ plugin-platform standalone sender fallback at the cron delivery boundary: when a non-local delivery target has no live gateway adapter, an injected standalone sender registry may deliver through an ephemeral per-platform sender before the legacy text-only DeliverySink fallback runs. The seam is fakeable, per target, preserves live-adapter-first behavior, reports standalone_sender_used / standalone_sender_failed evidence, and never opens live IRC, Teams, Google Chat, Telegram, Discord, Slack, or other network clients in unit tests. | validated | gateway | small | operator, gateway, system | internal/automation/cron/delivery_plan_test.go::TestCronDeliveryPlan_StandaloneSenderFallback | If no standalone sender is registered for a target, existing live_adapter_unavailable plus fallback_sink_used evidence is preserved. If a standalone sender fails, cron records standalone_sender_failed and then falls back through DeliverySink so the run audit remains recoverable. |
| 5 / 5.N | Goncho serialized write queue + relation candidates — Goncho serializes memory/conclusion writes and records pending relation candidates for possible conflicts or supersession without blocking the originating memory write. | validated | memory | medium | operator, system | internal/goncho/write_queue_relation_test.go | If candidate search or relation insertion fails, the memory write still succeeds with degraded evidence; queue-full returns a retryable typed error. |
| 5 / 5.N | Blocker Policy Integration — Port the sages-openclaw fleet blocker protocol into Gormes: classify blockages by type (access|infra|dependency|decision|bug|unknown), record evidence in structured format, auto-pivot to unblocked work, and surface active blockers in gormes status output. | validated | tools | small | operator | internal/tools/blocker_test.go | Unknown blocker type, missing evidence, or inability to pivot reports blocker_unclassified in status rather than crashing or silently stalling. |
| 5 / 5.N | OpenClaw SecretRef core resolver — Port OpenClaw’s additive SecretRef core into Gormes config: support typed {source,provider,id} refs, implicit default env provider, configured file providers, strict validation, fail-closed missing secret behavior, and redacted resolution evidence without logging secret values. | validated | provider | small | operator, gateway, system | internal/config/secretref_test.go | Unsupported exec refs, missing env values, unconfigured providers, insecure file paths, invalid ids, unreadable files, and non-string file values return redacted SecretRef evidence instead of falling back to plaintext or continuing silently. |
| 5 / 5.N | Cross-agent config isolation — Enforce the 2026-05-03 hard rule that normal Gormes runtime, doctor, gateway, onboard, setup, config, status, and MCP login paths never read or write other agents’ config/state paths. gormes migrate hermes and gormes migrate openclaw are the only commands allowed to read Hermes/OpenClaw/Clawdbot/Moltbot source paths, and those commands must copy supported values into Gormes-native config/dotenv/state so there is no live post-migration dependency. | validated | tools | medium | operator, gateway, system | internal/config/config_test.go::TestLoad_IgnoresHermesConfigYAML; cmd/gormes/gateway_test.go::TestGatewayVerbosePersistsGormesDisplayPlatformOnly; cmd/gormes/mcp_login_command_test.go::TestMCPLoginDefaultIgnoresHermesConfig; internal/platform/migrate/hermes/manifest_test.go::TestHermesMigrationManifest*; internal/platform/migrate/openclaw/manifest_test.go::TestOpenClawMigrationManifest* | Foreign config residue is ignored by normal commands and reported only through explicit migration guidance where relevant. Missing native MCP/server/provider config degrades to Gormes-native unknown/unconfigured evidence instead of falling back to HERMES_HOME, ~/.hermes, ~/.openclaw, ~/.clawdbot, or ~/.moltbot. |
| 5 / 5.N | SecretRef runtime snapshot activation — Wire Gormes SecretRefs into startup/reload activation: resolve only effectively active credential surfaces into an in-memory runtime snapshot, fail fast on active unresolved refs, keep last-known-good snapshot on reload failure, and ensure gateway/provider/channel hot paths never re-resolve SecretRefs per request. | validated | gateway | medium | operator, gateway, system | internal/runtime/secretref_runtime_test.go | Inactive SecretRefs report ignored-inactive-surface diagnostics; active unresolved refs block activation with redacted evidence and do not fall back to stale plaintext config. |
| 5 / 5.N | OpenClaw security audit —deep —fix — Port the operator-facing OpenClaw security audit command surface into Gormes: gormes security audit and gormes security audit --deep --fix inspect config, SecretRefs, filesystem permissions, gateway exposure/auth, tool sandbox/blocklist policy, plugin trust, and channel credentials, then apply only safe deterministic fixes with exact finding counts. | validated | tools | medium | operator, gateway, system | internal/tools/security_audit_test.go; cmd/gormes/security_command_test.go | Deep probes that require unavailable SecretRefs, live gateway access, or exec providers degrade to explicit findings instead of skipping silently or logging secrets. |
| 5 / 5.N | ACP bridge doctor/status evidence — Close the remaining operator-visible ACP bridge evidence gap after the completed 5.H ACP client/server rows: gormes doctor --offline must report local ACP stdio server readiness, local client bridge readiness, and explicit remote-endpoint unsupported/unavailable evidence without claiming a live remote bridge exists. | validated | gateway | small | operator, gateway | internal/protocols/acp/bridge_status_test.go; cmd/gormes/doctor_acp_test.go | Remote ACP endpoints report acp_bridge_unavailable / unsupported_remote_acp_endpoint with endpoint and auth source absent or redacted; local Gormes operation continues and the local ACP client/server surfaces remain visible. |
| 5 / 5.N | Gateway probe auth/capability HTTP closeout — Close the remaining Hermes API-server gateway probe gap after the completed TCP discover/probe slice: expose and probe Hermes-compatible /health, /health/detailed, and authenticated /v1/capabilities HTTP endpoints with redacted evidence for unavailable, unauthenticated, unsupported-capability, and malformed gateways. | validated | gateway | small | operator, gateway | cmd/gormes/gateway_probe_http_test.go | HTTP probe failures show endpoint, status code, and auth source classification without leaking bearer tokens, gateway passwords, or SecretRef values. |
| 5 / 5.N | Safety-critical panic and swallowed-error closeout — Close the safety code-quality gaps called out by source audit: remove or contain MustRegister panics from runtime/library paths, surface kernel FinalizeAssistantTurn store failures as structured evidence, and replace silent gateway typing-action panic recovery with redacted diagnostics. | validated | tools | medium | operator, gateway, system | internal/kernel/finalize_store_test.go | Registration collisions, finalize store errors, and typing-action sink panics become visible non-secret evidence without crashing the gateway or silently losing state. |
| 5 / 5.N | Session Health Monitoring — Port Link’s session-health-monitor patterns: track session file sizes with 500KB/2MB tier alerts, monitor heartbeat freshness with 45min/90min tiers, and expose via gormes health command with structured JSON output. | validated | tools | small | operator | internal/tools/health_test.go | Missing session files, stale heartbeat, or unreadable metrics report health_unavailable with specific path/error details. |
| 5 / 5.N | Evidence-Before-Claims Quality Gate — Port Link’s evidence-before-claims pattern: doctor output and build results must include exact counts (pass/fail/skip), not summary claims. Every status line with a count must derive from an actual computation, not a hardcoded narrative. | validated | tools | small | operator | internal/doctor/evidence_test.go | Uncomputable counts report count_unavailable with the reason (missing data, corrupt store) rather than fabricating a number. |
| 5 / 5.N | Git Delivery Contract Enforcement — Enforce the fleet-wide git delivery contract: split commits by concern, commit after each validated slice, push to origin, report hash/branch/push confirmation. gormes-builder skill must include post-commit validation. | validated | tools | small | operator | internal/tools/git_delivery_test.go | Unpushed commits, dirty working tree, or missing remote report git_delivery_incomplete rather than silently failing. |
| 5 / 5.N | QMD Hybrid Search — Port the fleet’s shared QMD hybrid search (BM25 + vector) as gormes search. Index all markdown docs in the workspace for offline keyword + semantic retrieval, with BM25-only fallback when no embedding model is configured. | validated | tools | medium | operator | internal/tools/qmd_test.go | Missing embedding model, corrupt index, or unreadable workspace reports search_unavailable with root cause. Falls back to BM25-only search when vector model unavailable. |
| 5 / 5.N | Session Rollover Automation — Port the fleet’s session rollover rule (1500KB threshold -> write handoff summary -> fresh session). gormes session rollover exports current session, writes 5-line handoff summary, starts fresh session. Auto-rollover at configurable threshold. | validated | tools | small | operator, system | internal/persistence/session/rollover_test.go | Session file too large to export cleanly, corrupt session state, or rollover failure reports session_rollover_failed with specific path/error and keeps original session intact. |
| 5 / 5.N | System Events, Heartbeat, and Presence — Port OpenClaw’s system event surface: gormes system event enqueues a system event and optionally triggers a heartbeat; gormes system heartbeat shows and controls heartbeat state; gormes system presence lists system presence entries. Events are written to the audit ledger (JSONL) and surfaced in gormes status. | validated | tools | medium | operator, system | internal/tools/system_events_test.go | Missing audit ledger, event queue full, or heartbeat disabled reports system_unavailable with ledger path/error details. |
| 5 / 5.N | Gateway Discover and Probe — Port OpenClaw’s gateway network discovery: gormes gateway discover finds local gateways via Bonjour/mDNS; gormes gateway probe shows gateway reachability + discovery + health + status summary; gormes gateway usage-cost fetches usage cost summary from session logs. | validated | tools | small | operator | internal/tools/gateway_discover_test.go + cmd/gormes/gateway_discover_test.go | No gateways discovered, probe timeout, or usage data unavailable reports per-endpoint status with failure reason. |
| 5 / 5.N | Channels Capabilities Introspection — Port OpenClaw’s channels capabilities as a Go-native, read-only operator command: gormes channels capabilities and gormes channels capabilities --channel <name> render channel capability metadata from Gormes’ source-backed platform manifest plus configured-channel state. The command must show support features, intents, scopes, format limitations, and configured/not-configured status without opening live channel clients or requiring credentials. | validated | tools | small | operator | internal/channels/capabilities_test.go | Unconfigured channel, missing adapter, unknown channel, or capability query failure reports visible per-channel degraded evidence rather than crashing or probing live SDKs. |
| 5 / 5.N | Teams configured-state in channel capabilities — gormes channels capabilities --channel teams reports Teams as a source-backed plugin platform and shows configured/not-configured Teams state from the same redacted [teams] config evidence used by gormes gateway status, without opening the Teams live transport or exposing TEAMS_CLIENT_SECRET. This closes the stale completed channel-capability row after Hermes added the Teams platform plugin and Gormes shipped the fakeable Teams seam. | validated | tools | small | operator | cmd/gormes/channels_capabilities_test.go | Teams is absent from the Hermes gateway Platform enum but present as a plugin-backed platform. The capability manifest must include it as a channel without breaking enum/connector drift checks; Teams with enabled = true but missing credentials still appears as configured with redacted missing-credential detail, while absent Teams config remains not_configured and no live Microsoft SDK, Bot Framework listener, or credential probe is used. |
| 5 / 5.N | Prompt Fragment Include System — Port agent-zero prompt fragment system: prompts stored as fragments with {{include filename.md}} / {{include “filename.md”}} directives, priority search order (agent profile > user > plugin > default), {{include original}} chains through hierarchy, variables substituted at render time, and render-time cache invalidation keyed by fragment file mtime. | validated | tools | medium | operator, system | internal/llm/prompt_fragments_test.go | Missing fragment, circular include, or render failure reports prompt_fragment_error with chain trace. |
| 5 / 5.N | Multi-agent gateway runtime activation — Wire the validated AgentRouter into live gateway turn handling: inbound messages resolve to agent-specific session keys, workspace/agentDir prompt seams, per-turn model overrides, route evidence in SessionContext and /status, and a resident-kernel reset when the route switches agent session keys so local history does not bleed across agents. | validated | gateway | small | operator, gateway, system | internal/gateway/manager_multi_agent_test.go | Missing or unmatched bindings fall back to the configured default agent; missing workspace/agentDir paths omit context files while preserving the routed session key. Per-agent auth/profile stores and tool-policy enforcement are not claimed by this row and remain in the dedicated follow-up row. |
| 5 / 5.N | Multi-agent auth and tool-policy runtime isolation — Finish OpenClaw-style multi-agent isolation beyond route-scoped turns: construct per-agent auth/profile/model registry snapshots, enforce agents.list[].skills and agents.list[].tools allow/deny policy, and provide independent kernel/runtime factories where local history, tool state, and credentials cannot cross agent boundaries. | validated | gateway | medium | operator, gateway, system | internal/gateway/manager_multi_agent_isolation_test.go | A broken agent auth/profile/tool-policy snapshot disables only that agent route and reports redacted route evidence; other agents and the default route continue operating. |
| 5 / 5.N | Per-agent channel bot tokens (Telegram/Discord/Slack) — Enable per-agent bot tokens for channel platforms so each agent can bind to its own Telegram bot, Discord bot, or Slack app. Config follows OpenClaw’s channels. | validated | gateway | medium | operator, gateway, system | internal/gateway/manager_multi_agent_token_test.go | If per-account tokens are not configured, the gateway falls back to the existing global platform token and legacy single-agent routing. Missing account-specific tokens report channel_account_token_missing evidence without blocking other accounts or the default route. |
| 5 / 5.N | Cron env-ref expansion + parallel run state serialization — Gormes ports Hermes cron scheduler safety drift: cron job execution expands ${VAR} references from loaded config/env snapshots before worker execution and serializes concurrent job state updates so parallel runs cannot lose status, attempt, or output transitions. | validated | gateway | small | operator, gateway, system | internal/automation/cron/scheduler_env_test.go; internal/automation/cron/run_state_concurrency_test.go | Missing env refs resolve to cron_env_ref_unresolved evidence and concurrent state-write conflicts retry or report cron_state_write_conflict without corrupting the job/run store. |
| 5 / 5.N | Cron origin delivery isolation from session identity — Gormes matches Hermes’ fresh cron safety fix: a stored cron delivery origin remains routing metadata only. Cron agent turns must not seed live gateway/session identity into the kernel prompt or tool context from job.Origin, while final output delivery still resolves origin through the stored delivery origin. | validated | gateway | small | operator, gateway, system | internal/automation/cron/executor_test.go:TestCronExecutorDoesNotUseDeliveryOriginAsSessionIdentity | If a cron job carries malformed origin metadata, delivery planning records channel_directory_missing or target_parse_failed evidence; kernel submission stays cron-scoped and does not inherit the malformed origin as a sender identity. |
| 5 / 5.N | Cron script/workdir/inactivity execution binding — Gormes binds the already-modeled cron script and workdir fields into the native cron executor: pre-run scripts execute once per tick through injected runners, stdout/stderr/timeout evidence is folded into the prompt or alert envelope, workdir jobs run with TERMINAL_CWD and sequential partitioning, env/config state is restored after each run, inactivity timeout wraps provider stream activity, Codex 401 refresh uses the cron/gateway execution path, and cron state files keep owner-only permissions. | validated | gateway | medium | operator, gateway, system | internal/automation/cron/script_workdir_execution_test.go; internal/automation/cron/inactivity_timeout_test.go | Script failures, missing workdirs, invalid permissions, idle timeouts, Codex auth refresh failures, and env restoration errors return redacted cron_execution_* evidence without leaking env values, running live shells, or corrupting job/run state. |
| 5 / 5.N | Cron no-agent script-only watchdog mode — Gormes ports Hermes cron no_agent jobs as script-only scheduled automations: creation/update/list surfaces persist the mode, script is required, prompt is optional and ignored, successful script stdout can be delivered verbatim, empty stdout and wakeAgent=false stay silent, and failures/timeouts emit alert evidence without constructing an agent turn. | validated | gateway | medium | operator, gateway, system | internal/automation/cron/no_agent_test.go; internal/tools/cronjob_tool_no_agent_test.go | Invalid no-agent jobs return cron_no_agent_script_required or cron_no_agent_invalid_mode evidence; script failures return redacted cron_no_agent_script_failed/timeout evidence without leaking secrets or falling back to an agent prompt. |
| 5 / 5.N | Cron partial legacy job read-model normalization — Gormes ports Hermes’ refreshed cron read-safe job normalization: partially written or legacy cron job records with missing/null id, name, prompt, schedule, schedule display, or state fields must not crash or render blank operator/admin/tool surfaces. Store reads normalize ID from the bucket key, keep prompt empty when absent, derive a stable name from prompt/skills/script/id, show ? for unknown schedules, and keep pause/enabled state deterministic for cronjob tool and API-server job listings. | validated | gateway | small | operator, gateway, system | internal/automation/cron/store_test.go; internal/tools/cronjob_tool_test.go; internal/apiserver/cron_admin_read_test.go | Malformed cron records remain redacted and readable as partial records; invalid schedules keep next_run_unix at 0 and schedule ? instead of blocking list/get, tool output, or pause/resume responses. |
| 5 / 5.N | Cron dashboard partial-record page — Gormes replaces the placeholder Cron dashboard route with a native React page over /v1/admin/cron/jobs that matches Hermes’ refreshed partial-record resilience: loading/error/empty states are explicit, partial legacy records render stable title/schedule/state fallbacks, pause/resume decisions use a shared getJobState helper instead of raw state, and pause/resume/trigger buttons call the existing cron admin endpoints without starting schedulers, providers, or live channels. | validated | gateway | small | operator, gateway | web/tests/run-tests.mjs:CronDashboardPage | If the cron API is unreachable, the page shows bounded error evidence and a retry action; partial records still render as readable operator rows with ? schedule and scheduled/paused/disabled state instead of blank cards or JavaScript exceptions. |
| 5 / 5.N | Navivox host setup apply with transient sudo — gormes navivox setup-host --apply prepares a Linux host for Navivox by detecting OpenSSH server and Tailscale readiness, prompting for sudo password only through a masked transient prompt when required, installing/enabling OpenSSH server and Tailscale through explicit distro-aware steps, running tailscale up --ssh or equivalent Tailscale SSH enablement with operator confirmation, and discarding the sudo password immediately after the command. The existing --plan output remains the non-mutating default evidence surface. | validated | gateway | medium | operator, system | cmd/gormes/navivox_host_setup_test.go | Unsupported OS, missing package manager, missing systemd, failed sudo, failed Tailscale auth, or declined confirmation return typed setup evidence and leave the host unchanged beyond steps already completed. The command never writes sudo passwords, private SSH keys, or Tailscale auth keys into config, progress data, logs, or pairing URIs. |
| 5 / 5.N | Gateway auto-resume on restart — Gormes gateway auto-recovers interrupted sessions on restart: when the gateway process restarts, any in-progress sessions that were interrupted by the shutdown are detected and resumed (not orphaned), with the existing session context, conversation history, and tool state preserved. Auto-resume respects channel-specific reply semantics and fires the same session-boundary hooks as a normal continuation. | validated | gateway | small | gateway, operator | internal/gateway/auto_resume_test.go | Interrupted sessions that cannot be auto-resumed (missing kernel state, expired, or manually reset) are marked as terminated with auto_resume_failed evidence and do not block gateway startup. Gateway status reports orphaned session count and auto-resume outcome per channel. |
| 5 / 5.N | Hermes x_search tool and auth surface — Expose Hermes’ first-class x_search tool in Gormes with a descriptor, OAuth/API-key auth status, query/result envelope, rate-limit/degraded errors, and registry/toolset visibility without requiring live X credentials in tests. | validated | tools | medium | - | internal/tools x_search fake transport fixtures | - |
| 5 / 5.N | Goncho durable recall trace IR + fused ranking pipeline — Introduce an internal Goncho recall pipeline where RecallEngine.Run is the only caller-facing entrypoint, package-local generate/score/select phases produce a durable RecallTrace, and Honcho-compatible search/context projections can only be built from that trace. RecallCandidate owns facts/content/provenance, RecallScore owns scoring components and selection evidence, and RecallTrace owns replay/debug/eval state including TraceID, PipelineVersion, RecallScoringConfig, CreatedAt, selected/rejected candidates, and code-first warnings for every degraded path. | validated | memory | medium | operator, system | internal/goncho/testdata/recall_trace/*.golden.json | Semantic, graph, FTS, scope, stale-index, and token-budget fallbacks must be recorded in RecallTrace.Warnings instead of silently widening or dropping recall; external Honcho-compatible response shapes stay unchanged. |
| 5 / 5.N | Goncho recall diagnostics CLI over RecallTrace — Add a narrow gormes goncho recall-diagnostics --trace <trace.json> operator command that consumes an existing durable RecallTrace JSON artifact and explains selected/rejected ranking outcomes, score components, warning codes, and the no-projection-without-trace invariant. The command must not run retrieval, touch Goncho persistence, change Honcho-compatible public API responses, or add replay/benchmark/UI scope. | validated | memory | small | operator, system | cmd/gormes/goncho_recall_diagnostics_test.go | Missing or invalid trace files fail with exit code 1 and typed CLI guidance; traces with warning codes render status=degraded while preserving the warning code/stage/severity evidence. No DB/backend/provider dependency is required. |
| 5 / 5.N | Goncho replayable retrieval traces — Add a narrow replay layer over existing durable RecallTrace artifacts plus gormes goncho recall-replay --trace <trace.json> text/JSON output. The replay must expose an ordered, deterministic retrieval timeline for query, scored candidates, warning codes, selected/rejected candidates, and trace-only projection readiness without running retrieval, touching Goncho persistence, changing Honcho-compatible public APIs, or adding benchmark/UI/backend scope. | validated | memory | small | operator, system | cmd/gormes/goncho_recall_replay_test.go | Missing or invalid trace files fail with exit code 1 and clear CLI guidance; traces with degraded paths replay every warning as a code-first event containing code/stage/severity evidence. The replay command requires no database, provider, embedding model, graph index, hosted Honcho service, or network access. |
| 5 / 5.N | Goncho proof matrix and fixture harness — Add a focused proof matrix that defines and tests what Goncho works means across local storage lifecycle, retrieval/context behavior, durable RecallTrace evidence, Honcho-compatible tool contracts, Memory V1 governance/tombstones, and existing operator surfaces. This row must add hermetic tests only; it must not add a proof CLI, replay UI, benchmark corpus, learning-loop behavior, new retrieval algorithms, live provider calls, hosted Honcho dependencies, or public Honcho API changes. | validated | memory | small | operator, system | internal/goncho/proof_matrix_test.go | The proof matrix uses temp SQLite stores, golden RecallTrace fixtures, local tool execution, and zero-state doctor fixtures. Any proof failure is a normal test failure; no live provider, network, hosted Honcho, external vector database, or Hermes learning-loop dependency is required. |
| 5 / 5.N | Morning degraded-status summary over latest run report — Add a read-only operator summary surface that renders the latest OperatorRunReport into text and JSON for morning review. The summary must show whether the unattended run succeeded, degraded, or failed; include job/run identity, delivery status, provider/auth readiness, redacted error details, and a recommended next command; and integrate with existing gormes status-style output without mutating cron or gateway state. | validated | orchestrator | small | operator, system | cmd/gormes/status_operator_report_test.go::TestStatusRendersLatestOperatorRunReport | If no report exists or the latest report cannot be decoded, the command returns status=operator_report_unavailable with the path/reason redacted and points operators to the scheduler/doctor command rather than failing with raw filesystem errors. |
| 5 / 5.N | Provider/auth readiness preflight for unattended jobs — Add a pure provider/auth readiness preflight for unattended cron/fleet jobs that resolves provider, model, endpoint, credential-pool availability, and native-runtime binding before execution. It returns redacted readiness evidence for OperatorRunReport writers and blocks the run early with recommended setup/auth commands when provider credentials or endpoints are missing. | validated | provider | small | operator, system | internal/runtime/operator_preflight_test.go::TestOperatorPreflightClassifiesProviderAuthReadiness | When provider, model, endpoint, or credential resolution is incomplete, the preflight returns provider_auth_unready evidence and a recommended command such as gormes setup provider, gormes auth status, or gormes config check —json without opening a provider client or mutating config. |
| 5 / 5.N | Goncho golden transcript e2e harness — Add one deterministic end-to-end Goncho memory transcript that runs through the real kernel turn path with temp SQLite and a deterministic fake provider: turn one captures a user memory, deterministic local extraction writes it into Goncho, turn two recalls it through a durable RecallTrace projection, and the final provider answer proves the injected context. This patch must not add diagnostics CLI, replay UI, benchmark corpus, scoped grants, agent-as-tool, resumable checkpoints, learning-loop behavior, public Honcho API changes, live providers, network calls, or external DB/backends. | validated | memory | small | operator, system | internal/support/e2e/goncho_memory_turn_test.go | The e2e harness uses only temp SQLite, temp GORMES_HOME, deterministic fake provider output, deterministic Goncho capture, and a golden JSON fixture. Missing recall context or negative-control leakage makes the fake provider return sentinel failure text and the test fails. No live provider, network, hosted Honcho, external vector DB, benchmark corpus, replay UI, or Hermes learning-loop dependency is required. |
| 5 / 5.N | Goncho retrieval benchmark corpus — Add a narrow internal retrieval benchmark corpus for Goncho that consumes existing durable RecallTrace artifacts and deterministic fixture traces to produce repeatable R@5/R@10, context-hit, token-budget, latency-shape, warning-code, and per-case evidence. This row must not add a benchmark CLI, replay UI, new retrieval algorithm, live provider/network path, external database/backend, hosted Honcho dependency, or public Honcho API change. | validated | memory | small | operator, system | internal/goncho/recall_benchmark_test.go | Benchmark cases evaluate only supplied RecallTrace data and deterministic latency metadata. Missing trace IDs or missing relevant memory IDs emit stable code-first warnings in the report instead of silently passing. The corpus uses golden fixtures and no live provider, network, hosted Honcho, external vector DB, or storage backend. |
| 5 / 5.N | Hermes send_message tool list and target contract — Bring Gormes’ existing send_message tool descriptor/handler up to the narrow Hermes contract that can be proven without live channel sends: the schema must expose action with send|list, optional target, and optional message; action=list must return a typed list/unavailable envelope from an injected channel-directory provider; action=send must reject missing target/message with Hermes-style tool-error JSON, parse platform[:chat[:thread]] targets through the shared gateway delivery-target parser, and call an injected sender only after validation. This row must not start gateway services, contact Telegram/Discord/Slack, or implement media delivery. | validated | tools | small | operator, gateway, child-agent, system | internal/tools/sendmessage/send_message_test.go::TestSendMessageToolListAndValidatedSendContract | When no directory or sender is injected, send_message returns typed send_message_directory_unavailable or send_message_backend_unavailable evidence instead of silently succeeding. Unknown/ambiguous human channel names should tell the model to call send_message(action="list") before sending, preserving Hermes guidance without live network access. |
| 5 / 5.N | Image generation managed-gateway provider binding — Bind the existing image_generate runner/provider registry to the existing ManagedGatewayBridge with hermetic fake HTTP MCP gateway fixtures, so a configured managed image provider can generate the standard redacted image artifact envelope without live FAL/API credentials. | validated | tools | small | operator, system | internal/tools/managed_tool_gateway_test.go fake HTTP MCP gateway plus internal/tools/imagegen/generation_test.go artifact-envelope fixtures | Auth-required, gateway-unavailable, schema-rejected, tool-call-failed, and circuit-breaker outcomes map to stable image-generation degraded evidence; bearer tokens, prompts, and raw gateway errors stay redacted. |
| 5 / 5.N | OSV malware advisory check for MCP package launch — Port Hermes’ tools/osv_check.py behavior into a hermetic Go safety helper used before MCP extension package launch: infer npm/PyPI package names from npx/uvx/pipx commands, query an injected OSV client for MAL-* advisories only, block confirmed malware with redacted operator evidence, and fail open on network/parse/unrecognized-command errors without live OSV access in tests. | validated | tools | small | operator, system | internal/tools/mcp/osv_malware_check_test.go with fake OSV responses for npm scoped packages, npm version parsing, PyPI extras/version parsing, MAL-only filtering, network fail-open, and non-npx/uvx skip cases | Known MAL-* advisories return a typed blocked/degraded evidence envelope; non-malware CVEs, unknown commands, malformed args, OSV network errors, and parse failures allow launch with explicit fail-open evidence and no package token leakage beyond the inferred package name. |
| 5 / 5.N | Hermes toolset distribution manifest and deterministic sampler — Port Hermes’ toolset_distributions.py contract as a hermetic Go manifest and deterministic sampler: expose the named distribution definitions, descriptions, and percentage weights; validate referenced toolsets through the existing Gormes toolset catalog; sample each toolset independently from an injectable RNG; and guarantee the highest-probability valid toolset is selected when all rolls miss. This row does not run batch/datagen jobs or change operator toolset config persistence. | validated | tools | small | operator, system | internal/platform/cli/toolsets/distribution_test.go with deterministic RNG fixtures for default, image_gen, safe, terminal_only, unknown distribution, invalid toolset skip, and highest-probability fallback cases | Unknown distributions return typed unavailable/error evidence; invalid distribution entries are skipped with validation evidence; if every random roll misses, the sampler chooses the highest-probability valid toolset so callers never receive an empty enabled-toolset set when a distribution has valid entries. |
| 5 / 5.O | Hermes CLI command-tree parity manifest — cmd/gormes owns a source-backed Hermes CLI compatibility manifest that enumerates every upstream top-level command, nested subcommand, global/root flag, slash command, gateway command handler, dynamic plugin-provided command, and Gormes-owned addition, then classifies each path as implemented, row-backed, owned, excluded, or not-yet-applicable before any handler work claims CLI parity. | validated | tools | small | operator, gateway, system | cmd/gormes/hermes_cli_parity_test.go::TestHermesCLIParityManifest | Unknown or unclassified Hermes command paths, including dynamic plugin command registrations, fail the parity test; unsupported-but-known commands must carry row-backed or owned evidence instead of disappearing from help, docs, or migration plans. |
| 5 / 5.O | Hermes CLI nested parser inventory refresh — Refresh the Gormes Hermes CLI compatibility manifest and tests from current Hermes argparse parser groups so every nested top-level command, alias, and removed/stale parser path is classified distinctly from gateway message handlers and dynamic plugin commands. Unsupported commands still need row-backed, excluded/deprecated, or Gormes-owned evidence; no current Hermes parser command may be absent merely because Gormes has not implemented it yet. | validated | tools | medium | operator, gateway, system | cmd/gormes/hermes_cli_parity_test.go::TestHermesCLIParityManifestNestedParserInventoryMatchesHermes | Nested parser drift fails the manifest test with a command-path diff. Stale paths such as gateway reset, cron enable, webhook serve, mcp call, mcp auth, profile set, auth login, and auth refresh are either excluded/stale with evidence or removed from active parser-command assertions instead of treated as implementation targets. |
| 5 / 5.O | Hermes auth command-tree manifest refresh — Refresh the Gormes Hermes CLI compatibility manifest so provider auth command paths match current non-deprecated Hermes behavior: top-level hermes login is classified as removed/deprecated guidance, top-level hermes logout --provider nous|openai-codex|spotify remains a provider-auth shortcut, hermes auth without a subcommand opens interactive credential-pool management, and nested auth commands are add, list, remove, reset, status, logout, and spotify rather than the stale auth login or auth refresh paths. | validated | tools | small | operator, system | cmd/gormes/hermes_cli_parity_test.go::TestHermesCLIParityManifestProviderAuthCommandsMatchHermes | Unknown, removed, or stale auth command paths are explicit manifest entries with auth_command_removed, auth_command_stale, or row_backed evidence instead of silently suggesting unsupported auth login or auth refresh commands. |
| 5 / 5.O | Hermes auth credential-pool command surface — Implement the non-OAuth gormes auth command surface over the existing native credential pool: bare auth renders an interactive-safe status/readout without prompting in tests; auth add <provider> --type api-key|api_key [--label --api-key] stores a manual credential for registry/openrouter-style providers with provider base URL metadata; auth list [provider] renders redacted pool rows with exhaustion status; auth remove <provider> <index|id|label> deletes one credential; auth reset <provider> clears exhaustion status; auth status <provider> renders credential-pool auth state; and auth logout <provider> clears stored provider auth without leaking secrets. OAuth browser/device-code flows and source-specific cleanup/suppression stay in the provider OAuth adapter row. | validated | tools | medium | operator, system | cmd/gormes/auth_command_test.go | Empty API keys, missing inference base URLs, corrupt auth.json, empty pools, invalid remove targets, unsupported OAuth requests, and logout/status of an absent provider return auth_api_key_missing, credential_pool_corrupt, credential_pool_empty, credential_not_found, provider OAuth adapters are planned, or logged-out evidence with all token fields redacted. |
| 5 / 5.O | Hermes auth OAuth provider adapters — Implement non-deprecated provider login through gormes auth add <provider> --type oauth with injectable provider login seams for Hermes OAuth-capable inference providers: anthropic uses Hermes PKCE credentials, nous uses device-code state and mirrors providers.nous plus credential_pool.nous, openai-codex uses Codex device-code tokens and clears device_code suppression on relink, google-gemini-cli stores Google Code Assist OAuth credentials, and qwen-oauth imports/refreshes local Qwen CLI credentials into a pool entry. Each adapter returns the Hermes source label, base URL, expiry/refresh metadata, and redacted status evidence without requiring live credentials in tests. | validated | provider | medium | operator, system | cmd/gormes/auth_oauth_command_test.go | OAuth provider login failures return provider-specific evidence such as anthropic_oauth_failed, nous_device_code_failed, nous_provider_mirror_missing, codex_device_code_failed, google_gemini_oauth_failed, qwen_cli_auth_missing, qwen_cli_refresh_failed, auth_suppression_cleared, and auth_secret_redacted while preserving any existing auth store unless a fake successful login is committed. |
| 5 / 5.O | Hermes auth Spotify service-provider subcommand — Implement gormes auth spotify [login|status|logout] as the service-provider auth companion to the first-party Spotify plugin: login runs an injectable PKCE callback/token exchange using client_id, redirect_uri, scope, no-browser, and timeout inputs; status reads providers.spotify from the auth store and reports logged-in, expiry, scope, redirect_uri, and API base URL with secrets redacted; logout clears only Spotify provider auth. The command gates Spotify tool/plugin availability through the same providers.spotify auth key Hermes uses. | validated | tools | medium | operator, system | cmd/gormes/auth_spotify_command_test.go | Missing client ID, redirect mismatch, callback timeout, token exchange failure, partial/corrupt auth state, and absent Spotify credentials return spotify_client_id_missing, spotify_redirect_invalid, spotify_callback_timeout, spotify_token_exchange_failed, spotify_auth_corrupt, or spotify_logged_out evidence without leaking authorization codes, access tokens, or refresh tokens. |
| 5 / 5.O | CLI banner/output formatting helpers — Pure CLI banner and output formatting helpers match upstream deterministic text behavior without terminal or command-registry coupling | validated | tools | small | operator, system | internal/cli/banner_output_test.go | CLI command rows continue to render minimal fallback output until banner/version/toolset formatting helpers are fixture-backed. |
| 5 / 5.O | CLI deterministic tip selector — internal/cli adds a fixed exported corpus Tips []string of at least 30 non-empty entries (each unique, none containing newlines) and a pure function TipFor(seed int64) string that returns Tips[(seed mod len(Tips))]; tip text is owned by Gormes and does not need to mirror upstream tip strings | validated | tools | small | operator | internal/cli/tips_test.go | Callers that want a non-deterministic random tip can inject time.Now().UnixNano() as the seed; tests always inject a fixed seed so output is deterministic. |
| 5 / 5.O | CLI OpenClaw residue detection and hint text — internal/cli exposes pure DetectOpenClawResidue(home string) bool and OpenClawResidueHint(commandName string) string helpers that detect only an existing ~/.openclaw directory and return Gormes-specific cleanup guidance without reading or writing config files | validated | tools | small | operator, system | internal/cli/openclaw_residue_test.go::TestOpenClawResidue* | If HOME cannot be inspected, the helper returns false and never blocks CLI startup; startup binding can decide later whether to persist a seen flag. |
| 5 / 5.O | CLI onboarding seen-state map helpers — internal/cli exposes pure OnboardingSeen and MarkOnboardingSeen helpers over an in-memory map shape compatible with config onboarding.seen without reading or writing real config files | validated | tools | small | operator, system | internal/cli/onboarding_state_test.go::TestOnboardingSeen* | Malformed onboarding config maps are treated as unseen and never block CLI startup; command wiring can persist the corrected map in a later row. |
| 5 / 5.O | CLI contextual first-touch onboarding hint renderers — internal/cli exposes pure constants and renderers for Hermes-compatible contextual onboarding hints: BusyInputPromptFlag = busy_input_prompt, ToolProgressPromptFlag = tool_progress_prompt, BusyInputHint(surface, mode string) string for interrupt/queue/steer modes, and ToolProgressHint(surface string) string for long-running tool progress. CLI text is plain ASCII and gateway text may use channel-friendly wording, but both preserve the operator contract: explain what just happened, name /busy or /verbose follow-up commands, and state that the tip only shows once. | validated | tools | small | operator, gateway, system | internal/cli/onboarding_hints_test.go | Unknown busy-input modes fall back to interrupt wording; unknown surfaces return CLI/plain text. The helpers do not read or write config and do not decide whether a hint has already been seen. |
| 5 / 5.O | CLI bracketed-paste wrapper sanitizer — internal/cli exposes StripLeakedBracketedPasteWrappers(text string) string, a pure sanitizer that removes canonical ESC [200~/[201~ wrappers, visible caret-escape wrappers, degraded boundary [200~/[201~ wrappers, and boundary 00~/01~ fragments while preserving non-wrapper substrings inside ordinary text | validated | tools | small | operator, system | internal/cli/paste_sanitizer_test.go | If a terminal leaks bracketed-paste markers into a CLI buffer, Gormes strips only recognized boundary wrappers before command/path detection; ambiguous inline text is preserved rather than over-sanitized. |
| 5 / 5.O | CLI slow bracketed-paste diagnostic threshold — internal/cli exposes a pure slow-paste diagnostic helper that emits structured evidence only when bracketed-paste handling exceeds a 500ms threshold and never logs pasted content bytes | validated | tools | small | operator, system | internal/cli/paste_diagnostic_test.go::TestSlowBracketedPasteDiagnostic_* | CLI diagnostics report paste_handler_slow with duration_ms and threshold_ms only; prompt content, clipboard content, and image paths are redacted. |
| 5 / 5.O | CLI terminal control-response sanitizer — Gormes ports Hermes’ defensive CLI input sanitizer for leaked terminal responses: pure internal/cli helpers strip cursor-position DSR/CPR responses and SGR mouse reports in ESC-prefixed, visible caret-escape, and narrowly bounded bare forms before command/path parsing, while preserving ordinary text, ANSI color SGR sequences, and literal angle-bracket content. | validated | tools | small | operator, system | internal/cli/terminal_response_sanitizer_test.go | When resize storms, multiplexers, or mouse tracking leak terminal reports into the input buffer, Gormes returns terminal_response_stripped evidence without logging input text, command arguments, file paths, or terminal coordinates. |
| 5 / 5.O | CLI submitted user-message preview formatter — Gormes ports Hermes’ submitted user-message scrollback preview as a pure internal/cli formatter: single-line submissions render the normal bullet line, multiline submissions render a bounded first-lines/last-lines preview, hidden middle content is replaced by a deterministic count, invalid preview config values fall back to safe defaults, tail lines can be disabled with last_lines=0, and display markup/control text is escaped before rendering. | validated | tools | small | operator, system | internal/cli/user_message_preview_test.go | Empty input, single-line input, malformed config, and overlong multiline input return user_message_preview_* evidence with counts only; raw prompt bodies, secrets, file contents, images, or pasted payloads are not logged by the helper. |
| 5 / 5.O | CLI webhook URL normalizer — internal/cli adds a pure function NormalizeWebhookURL(raw string) (string, error) that trims whitespace, requires scheme http or https, strips trailing slashes from the path, lowercases the host, rejects userinfo (raw URLs containing ’@’ before the host) with a sentinel error, and returns the canonicalized URL string; no network call is made | validated | tools | small | operator | internal/cli/webhook_test.go | Callers handle the typed error classes (ErrWebhookURLEmpty, ErrWebhookURLBadScheme, ErrWebhookURLUserInfoForbidden, ErrWebhookURLParseFailed) instead of treating any non-empty string as a valid webhook target. |
| 5 / 5.O | CLI dump support-summary helper — internal/cli adds a pure function RenderDumpSummary(in DumpInput) string where DumpInput has fields {Version, OS, Arch, ProfileName string; Toolsets []string; SecretsLikeKeys []string} that returns a deterministic multi-line summary; the function never reads files, clock, env, or network and redacts every entry of SecretsLikeKeys (replaces literal occurrences with ‘[redacted]’) in the output text | validated | tools | small | operator | internal/cli/dump_test.go | Empty Toolsets renders ‘toolsets: (none)’; missing fields render ‘unknown’ instead of leaking errors. |
| 5 / 5.O | PTY bridge protocol adapter — Dashboard/TUI PTY sessions expose bounded read, write, resize, close, and unavailable-state behavior through a testable adapter | validated | tools | small | operator | internal/cli/pty_bridge_test.go | Dashboard or CLI status reports PTY unavailable instead of falling back to unsafe shell execution. |
| 5 / 5.O | CLI command registry parity + active-turn busy policy — CLI slash/command registry parity declares active-turn policy for every recognized command before handlers can mutate runtime state | validated | tools | medium | operator, gateway, system | internal/cli/command_registry_policy_test.go | Unknown or not-yet-ported commands return visible unavailable/busy evidence instead of entering the kernel as ordinary prompt text. |
| 5 / 5.O | Gateway /reasoning command parser — Pure parser internal/gateway/reasoning_command.go exposes type ReasoningAction int (ReasoningActionShow, ReasoningActionSet, ReasoningActionReset), type ReasoningEffort string (high|low|medium|""), type ReasoningCommand struct{Action ReasoningAction; Effort ReasoningEffort; Global bool}, and ParseReasoningCommand(args []string) (ReasoningCommand, error). Empty args returns Action=Show. “high|low|medium” returns Action=Set with that Effort. Trailing “—global” sets Global=true. “reset” alone returns Action=Reset, Global=false. “reset —global” returns an error matching ErrResetGlobalUnsupported. Unknown effort returns ErrInvalidEffort. No state, no clock, no I/O. | validated | tools | small | operator, gateway | internal/gateway/reasoning_command_test.go | Parser surfaces typed errors (ErrInvalidEffort, ErrResetGlobalUnsupported) so the dispatcher can render the upstream warning class without re-parsing. |
| 5 / 5.O | Gateway /reasoning apply + dispatch — internal/gateway/reasoning_command.go also exposes type SessionReasoningState struct{Effort ReasoningEffort; Source string} where Source is “session”|“global”|“unset”, type ReasoningReply struct{Effort ReasoningEffort; Scope string; PersistFailed bool}, and ApplyReasoningCommand(state SessionReasoningState, cmd ReasoningCommand, persistGlobal func(ReasoningEffort) error) (SessionReasoningState, ReasoningReply). Action=Show returns current state unchanged. Action=Set with Global=false mutates session-only. Action=Set with Global=true calls persistGlobal; on failure falls back to session-only and sets PersistFailed=true. Action=Reset clears the session override. Internal/gateway/manager.go wires “/reasoning” through the existing command registry to ParseReasoningCommand + ApplyReasoningCommand for the calling session. | validated | tools | small | operator, gateway | internal/gateway/reasoning_command_test.go | ReasoningReply.PersistFailed surfaces a global-save failure without changing session state; Source distinguishes session vs global vs unset. |
| 5 / 5.O | Busy command guard for compression and long CLI actions — Long-running CLI commands set busy input state and reject overlapping user input until the command exits | validated | tools | small | operator | internal/cli/busy_command_test.go | CLI/TUI status reports command-busy state instead of accepting overlapping input that can corrupt turn state. |
| 5 / 5.O | Gormes agent template reset command — gormes agent reset [--target <dir>] [--dry-run] [--force] seeds Gormes-owned agent context templates adapted from Hermes’ default SOUL/persona behavior. By default it creates missing SOUL.md, AGENTS.md, IDENTITY.md, TOOLS.md, memory/USER.md, and memory/MEMORY.md under the target root, skips existing files without overwriting, reports deterministic create/skip actions, and uses config.GormesHome() when —target is omitted. Gateway and standalone Telegram runtime startup apply the same non-force template writer to the configured terminal/default-agent workspace before live-turn prompt discovery, so a clean runtime workspace gets usable identity, project, operational, and durable memory files without an extra manual reset. | validated | tools | small | operator, system | cmd/gormes/agent_reset_test.go | Existing files are skipped unless —force is set; —dry-run reports would_create/would_overwrite/would_skip actions without creating directories or files; an empty target returns agent_template_target_required; runtime startup fails fast on template-write failure, reload keeps the last good gateway config, and errors surface the relative template path without leaking unrelated filesystem content. |
| 5 / 5.O | Hermes py2many parity mapping report — Create a repo-local, non-runtime py2many mapping pass that runs py2many --go against the Hermes Python source tree into ignored temporary artifacts, captures success/failure, generated symbol names, imports, and unsupported syntax by source file, and converts discoveries into source-backed progress.json rows for missing Gormes parity contracts. Generated Go is evidence only and must never be imported into production Gormes packages. | validated | docs | medium | operator, system | docs/content/building-gormes/architecture_plan/py2many-hermes-map.md | If py2many cannot be installed or the Go backend fails for most files, the report records tool_unavailable or backend_unsupported evidence with commands attempted, still emits an rg-based Hermes source-class inventory, and does not commit generated Go. |
| 5 / 5.O | Hermes source-pair manifest and Phase 0 refresh mode — Create a rerunnable Phase 0 parity guard that maps high-risk Hermes source files to their Gormes targets, validates that covered rows have Go targets and tests, fails when the manifest is stale against the active Hermes checkout, safely fast-forwards the local Hermes checkout on request, syncs source-pair SHAs, and emits generated source-pair and py2many reports without importing generated py2many output into production Gormes packages. | validated | docs | medium | operator, system | docs/content/building-gormes/architecture_plan/hermes-source-pairs.json + docs/content/building-gormes/architecture_plan/hermes-source-pairs.md | scripts/hermes-parity-refresh.sh --check --no-fetch validates the pinned local checkout when remote access is unavailable. Plain --check fetches origin/main metadata and exits nonzero when upstream Hermes has advanced. --update refuses dirty or non-fast-forward Hermes checkouts before moving the detached checkout, syncing manifest SHAs, regenerating reports, and validating progress metadata. |
| 5 / 5.O | Gormes auth bare interactive credential-pool readout — gormes auth invoked with no subcommand renders the credential-pool table for every registered provider with current marker, label, source, auth_type, and exhaustion status using only the existing internal/config credential-pool readers; in non-TTY contexts the bare invocation prints the same table plus a hint and exits 0 instead of entering an interactive add/remove/reset/strategy menu. | validated | tools | small | operator, system | cmd/gormes/auth_bare_command_test.go | Empty pool prints (no credentials configured) per provider section, corrupt auth.json returns credential_pool_corrupt evidence without panicking, optional AWS/Bedrock STS identity probe returns aws_identity_unavailable when the AWS SDK or env credentials are absent. |
| 5 / 5.O | Gormes auth status per-provider aggregator — gormes auth status <provider> renders an aggregated read model over the existing token vault, multi-account credential pool, and per-provider OAuth state rows: it prints <provider>: logged in plus indented metadata (auth_type, client_id, redirect_uri, scope list, expires_at_ms, api_base_url, account label) when any source is present, falls back to <provider>: logged out (<typed-reason>) otherwise, and never reads or echoes raw access/refresh tokens. | validated | tools | small | operator, system | cmd/gormes/auth_status_command_test.go | Unknown providers return auth_provider_unknown, partial OAuth state returns auth_status_partial with available metadata only, expired tokens return auth_status_expired with expiry timestamp, and read errors return auth_status_unavailable without leaking secrets. |
| 5 / 5.O | Gormes auth add openai-codex strict isolation contract — gormes auth add openai-codex runs only the device-code OAuth flow against the Gormes-owned Codex auth.json under the credential pool; it never imports tokens from ~/.codex/auth.json by default, never reads Codex CLI / VS Code refresh tokens by default, and prints an explicit (Hermes will keep working independently with its own session) envelope so the Codex CLI / VS Code extension cannot rotate Gormes tokens. When the explicit flag —emergency-import-from-codex-cli | validated | provider | small | operator, system | cmd/gormes/auth_add_codex_strict_test.go | Device-code start failures return codex_device_code_failed; mid-flow callback errors return codex_callback_failed; an unlabeled attempt to reuse an existing ~/.codex/auth.json returns codex_external_import_blocked with the operator-facing redirect (use 'gormes auth add openai-codex --emergency-import-from-codex-cli <path>' if you accept the refresh-token race envelope); the explicit emergency-import path returns codex_emergency_import_jwt_expired when the imported access token is past its expiry. |
| 5 / 5.O | Gormes auth add bedrock open-question planning note — Preserve Hermes parity for the Bedrock auth gap: gormes auth add bedrock refuses credential-pool mutation for API-key or OAuth-style inputs and returns bedrock_use_aws_sdk_chain guidance, while bare gormes auth remains the redacted AWS credential-chain status surface. | validated | tools | small | operator, system | cmd/gormes/auth_command_test.go::TestGormesAuthAddBedrockRefusesCredentialPoolMutation | No credential-pool entry is written for Bedrock; operators receive bedrock_use_aws_sdk_chain evidence that points to AWS env/profile/SSO/role credential-chain setup and bare gormes auth Bedrock identity status without printing tokens, URLs, ARNs, or host paths. |
| 5 / 5.O | Gormes profile command binding — Implements the current runtime-ready gormes profile operator surface over the existing CLI profile name validator (CLI profile name validator row), root resolver (CLI profile root resolver row), and active-profile store (CLI active-profile store row). list enumerates known profiles with an active marker, use <name> is the canonical sticky active-profile switch, set <name> remains a compatibility alias, create <name> [--clone-all] creates named profile roots with redacted output, show renders the active profile and redacted root, and info <name> reads optional distribution.yaml metadata. Hermes lifecycle and distribution mutators (delete, alias, rename, export, import, install, update) stay registered as deterministic row-backed unavailable commands until their full behavior ships. | validated | tools | small | operator, system | cmd/gormes/profile_command_test.go | Invalid profile name returns profile_name_invalid typed evidence and the active-profile store is left untouched. An unwritable profile root returns profile_root_unwritable. A corrupt active-profile store returns active_profile_corrupt. A partial failure (e.g. store updated but root resolver swap fails) returns profile_set_partial_failure with the prior active profile name in the evidence so operators can recover. |
| 5 / 5.O | Gormes profile distribution metadata readout — Port Hermes’ new shareable profile-distribution metadata surface without implementing remote git install/update in this slice: Gormes reads distribution.yaml from resolved profile roots, applies Hermes manifest defaults, surfaces distribution name/version/source/env requirements through gormes profile list, gormes profile show, and gormes profile info <name>, and treats plain profiles as non-distributions without failing. | validated | tools | small | operator, system | internal/cli/profile_distribution_test.go; cmd/gormes/profile_command_test.go | If a profile has malformed distribution metadata, Gormes returns typed parse evidence for that profile without mutating profile roots, cloning git repos, reading live Hermes homes, or leaking raw profile root paths in list/show output. |
| 5 / 5.O | Gormes profile create clone-all infrastructure exclusion — Port Hermes’ hardened profile create --clone-all default-profile copy policy: Gormes creates a named profile from the default Gormes profile, preserves real profile data, strips runtime process files, excludes default-root infrastructure directories, and excludes regenerable bytecode/socket/temp files at any depth. | validated | tools | small | operator, system | internal/cli/profile_create_test.go; cmd/gormes/profile_command_test.go | Invalid, reserved, or existing profile names fail before copying; missing default profile roots return typed create evidence; stdout/stderr and JSON output render only redacted profile roots and never print raw home paths, secrets, repo checkout paths, or copied file contents. |
| 5 / 5.O | Model and profile selector seam (Cobra + gateway) — Defines type ModelSelector interface { Select(ctx context.Context, kind SelectionKind) (Selection, error) } and type ProfileSelector interface { Select(ctx context.Context) (Profile, error) } in internal/cli/selector.go. Default implementations consume the existing rows: Hermes config.yaml model/provider runtime bridge for live config, TUI launch model override + static alias resolver for alias resolution, and the three profile helpers (CLI profile name validator, CLI profile root resolver, CLI active-profile store). The seam returns a (provider, model, account) triple for model selection and a profile descriptor for profile selection; consumers decide what to do with the result. The seam is the canonical entry point for both the Cobra gormes model / gormes profile commands and the gateway slash commands /model / /profile. | validated | tools | small | operator, system | internal/cli/selector_test.go | selector_no_match typed evidence is returned when no provider/model alias resolves; selector_helper_unavailable when one of the helper rows is not yet wired; selector_alias_resolution_failed when the static alias resolver returns an error. |
| 5 / 5.O | Gormes top-level logout provider shortcut — gormes logout --provider {nous|openai-codex|spotify} is a top-level shortcut that dispatches to the same logout handler as gormes auth logout, narrowly limited to those three provider choices, and clears only the named provider’s stored auth state with redacted output. It must remain parallel to gormes auth logout rather than aliasing or shadowing it. | validated | tools | small | operator, system | cmd/gormes/logout_top_level_test.go | Unsupported --provider choices return auth_logout_provider_unsupported with the explicit nous|openai-codex|spotify allow-list; missing provider state returns auth_state_absent and exits 0; corrupt auth store returns credential_pool_corrupt without partial deletes. |
| 5 / 5.O | Top-level logout configured-provider fallback — gormes logout without --provider falls back to the current configured provider when it is a top-level Hermes logout target (nous or openai-codex), invokes the same redacted logout helper as the explicit shortcut, and resets hermes.provider to auto after a successful or idempotent logout. Explicit --provider {nous|openai-codex|spotify} keeps the existing allow-list and also resets hermes.provider when it matched the target. | validated | tools | small | operator, system | cmd/gormes/logout_top_level_test.go | If no explicit provider and no configured fallback exists, the command exits 0 with redacted absent-state evidence instead of treating an empty provider as an unsupported provider error. |
| 5 / 5.O | Gormes removed top-level login guidance — gormes login is not registered as a top-level Cobra command. Operators who type the removed command receive deterministic, secret-safe guidance to gormes auth add <provider> --type oauth without executing provider auth, opening a browser, reading provider argument values, or mutating credential state. | validated | tools | small | operator | cmd/gormes/root_help_test.go::TestRemovedTopLevelEntrypointsReturnReplacementGuidance; internal/cli/typo_suggestions_test.go::TestTypoSuggestionGuidesRemovedLoginCommand | Removed-command guidance is intentionally narrow: it names the canonical gormes auth add <provider> --type oauth path and never echoes provider, portal, inference, client-id, scope, or token-looking argument values. Existing gormes auth add remains the only provider-auth command. |
| 5 / 5.O | Gormes model interactive provider/model picker — gormes model runs a selection-only interactive provider+model picker over the Hermes provider registry manifest and the Model and profile selector seam (Cobra + gateway) interface: it requires a TTY, shows the current provider/model, presents a provider menu, prompts for the model identifier, and persists only hermes.model and hermes.provider to the active Gormes config TOML. Provider auth is the responsibility of gormes auth add (including the explicit --emergency-import-from-codex-cli override) — gormes model never offers OAuth, device-code, or vendor-CLI import flows. | validated | tools | medium | operator, system | cmd/gormes/model_picker_test.go | Non-TTY invocation returns model_picker_requires_tty without launching a browser or reading the model catalog; provider/model menu cancellation leaves config untouched; missing provider credentials emit selection-only guidance pointing at gormes auth add <provider> without running OAuth, device-code, vendor-CLI import, or mutating auth.json. |
| 5 / 5.O | Gormes setup minimal sectioned wizard slice — gormes setup [section] [--non-interactive] [--quick] preserves the original sectioned setup foundations: section=provider writes provider endpoint/model to config.toml and the API key to the Gormes dotenv file; section=model dispatches to the shared model picker (same path as gormes model); section=workspace and section=bindings return bounded operator guidance/defaults without mutating unsupported nested config; unknown sections return setup_section_unsupported and the operator-facing recommended command. --non-interactive uses defaults/env and never prompts. Later setup rows supersede the original no-section, agent, tts, terminal, gateway, tools, reset, and reconfigure behavior. | validated | tools | small | operator, system | cmd/gormes/setup_minimal_test.go | Unknown sections return setup_section_unsupported with the canonical section list (provider|model|agent|workspace|bindings|tts|terminal|gateway|tools); non-TTY provider/model setup without —non-interactive returns setup_requires_tty with a deterministic noninteractive path. |
| 5 / 5.O | Gormes setup top-level chooser menu — gormes setup with no section in an interactive TTY renders a Gormes-owned top-level setup chooser before running any work. The menu presents Quick Setup, Full Setup, Model & Provider, Terminal Backend, Messaging Platforms (Gateway), Tools, Agent Settings, and Exit with a Quick Setup default. Selection routes through the current setup section handlers, and Exit returns success without mutating config. Non-TTY invocations keep deterministic text guidance and never launch an interactive menu. Current upstream Hermes returning-user entry-mode behavior is tracked separately by Hermes setup entry-mode and reset semantics. | validated | tools | small | operator, system | cmd/gormes/setup_minimal_test.go | If the terminal menu helper receives an invalid selection, gormes setup exits with setup_menu_invalid_selection evidence and leaves config untouched. Non-TTY and —non-interactive invocations print canonical section commands instead of launching the chooser. |
| 5 / 5.O | Gormes setup full-wizard shell and branded summary — gormes setup Full Setup and the compatibility --reset/--reconfigure flags run a sectioned full-wizard shell, then render a Gormes-branded completion summary. The summary names Gormes config/state through GORMES_HOME/~/.gormes, config.toml, the Gormes dotenv file, Gormes data directories, and gormes ... follow-up commands. Interactive Full Setup then offers a Gormes-branded chat launch prompt. It must never print ~/.hermes, config.yaml, hermes setup, hermes gateway, Launch hermes chat now?, or an upstream install dir in Gormes output. | validated | tools | small | operator, system | cmd/gormes/setup_minimal_test.go | If the full wizard runs in compatibility/non-interactive mode, it renders each section with keep-current/default behavior and still prints the Gormes completion summary without entering live provider/gateway/backend flows. |
| 5 / 5.O | Gormes setup model step uses the dynamic provider-tracked model picker — The gormes setup Inference Provider section must present, for the operator’s selected/active provider, the same dynamic per-provider callable-model list the gormes model and gateway/native-TUI /model pickers already use (internal/llm.ListPickerProviders), instead of the legacy bare free-text prompt with at most five static suggestions. Two coupled defects: (1) the model prompt provider must equal the provider just selected/active in the section — the transcript witness shows Active provider “OpenAI Codex” but the prompt reads “Model for openrouter [gpt-5.5]”, because runSetupInferenceProviderSection does not carry the chosen/active provider into runSetupActiveProviderModelPicker (it resolves to the provider-catalog default, openrouter); (2) cmd/gormes/model.go:promptModelChoice is fed defaultModelCatalogSuggestions(provider) = hermes.ProviderModelCatalogSuggestions(provider, nil) — the nil disables the live/dynamic catalog, so the operator never sees the provider’s actual model set. The fix wires the existing ListPickerProviders-backed picker (the port already consumed by internal/gateway/model_picker.go and internal/tui/slash_model.go) into the setup model step with provider continuity, while preserving q/cancel, Enter-keeps-current, and gormes setup model --non-interactive default behavior. This supersedes the deliberately-static promptModelChoice seam introduced by the completed row at progress.json:19063; it must not fork a second picker or change the already-complete gateway/gormes model/TUI picker behavior. | validated | tools | medium | - | cmd/gormes/setup_model_picker_test.go | If the dynamic per-provider catalog is unavailable (offline, no creds, empty provider list), the setup model step falls back to the current static suggestions + free-text entry rather than blocking setup, and reports the degraded source instead of silently showing the wrong provider. Non-interactive setup keeps its existing default-without-prompt behavior. |
| 5 / 5.O | Hermes setup entry-mode and reset semantics — Align gormes setup entry-mode semantics with current Hermes where upstream owns behavior. For an existing install, bare gormes setup and gormes setup --reconfigure run the full setup wizard directly without the Gormes-owned top-level chooser; --quick runs only the missing-items quick path. For a fresh install, bare setup, --reconfigure, and --quick enter the first-time quick-vs-full choice. --reset rewrites default config before any noninteractive/no-TTY guidance. --non-interactive or no TTY must not prompt. Gormes-owned provider, workspace, bindings, and onboard surfaces remain documented extensions rather than Hermes section parity. | validated | tools | medium | operator, system | cmd/gormes/setup_entry_mode_test.go | If an entry mode cannot proceed interactively, the command prints deterministic setup guidance with the relevant section commands and returns without reading stdin, launching a provider/gateway/backend, or mutating credentials. Reset failures surface the config path and preserve the previous file when the default write cannot complete. |
| 5 / 5.O | Gormes setup tools checklist command binding — gormes setup tools and the top-level Tools choice render Hermes-compatible tool/provider checklists over the existing Go toolset helpers. The command shows configurable toolsets for the CLI platform, preselects current defaults minus default-off rows, persists selected toolsets through the platform toolset config helper, preserves MCP server names/no_mcp semantics, and then offers provider/API-key configuration rows for selected tools without making live vendor calls. | validated | tools | medium | operator, system | cmd/gormes/setup_tools_test.go | Unavailable provider-key setup returns setup_tools_provider_row_backed with the provider/toolset key. Restricted platform toolsets are hidden or disabled with restricted_toolset evidence instead of being persisted for CLI. |
| 5 / 5.O | Gormes setup gateway platform checklist command binding — gormes setup gateway and the top-level Messaging Platforms choice render a Hermes-compatible platform checklist, preselect already-configured platforms from gateway status/config helpers, and dispatch selected platforms to existing or row-backed setup handlers. The first pass covers Telegram, Discord, and Slack with typed row-backed evidence for live token/webhook/service setup; it must not start the gateway or require live platform credentials in tests. | validated | gateway | small | operator, gateway, system | cmd/gormes/setup_gateway_test.go | Missing platform setup handlers return setup_gateway_platform_row_backed with the platform id and recommended command. Unsupported plugin platforms remain visible through the platform registry manifest when present but disabled until their dedicated rows land. |
| 5 / 5.O | Bubble Tea Messaging Platforms setup: Telegram-first Hermes fidelity — gormes setup gateway becomes the Bubble Tea-only Messaging Platforms setup surface for TTY users and the source of channel setup truth for first-run setup. The first executable slice ships Telegram end to end: token capture and validation, allowlist/pairing/open access policy, structured home_channel config, redacted review-before-write, --plan, non-TTY guidance, GORMES_* plus Hermes-compatible env aliases, explicit Hermes migration mapping, channel-scoped offline doctor evidence, and one consolidated gateway lifecycle recommendation after selected flows complete. Runtime paths stay Gormes-owned and normal runtime never reads Hermes config or dotenv files. | validated | gateway | medium | operator, gateway, system | cmd/gormes/setup_gateway_bubbletea_test.go; cmd/gormes/setup_gateway_test.go; internal/gateway/channel_setup_test.go; internal/config/config_test.go; internal/config/writer_test.go; internal/platform/migrate/hermes/manifest_test.go; internal/platform/migrate/hermes/writer_test.go | Non-TTY setup returns setup_gateway_requires_tty or renders --plan without raw key handling; token-only Telegram config is partial until an access policy is chosen; missing home channel is warning evidence, not unconfigured; legacy env/config aliases remain read-compatible but setup writes Gormes-owned names and redacts all secrets. |
| 5 / 5.O | Gormes setup terminal TTS and agent-settings section bindings — gormes setup terminal, gormes setup tts, gormes setup agent, and the corresponding full-wizard sections render Hermes-compatible prompt choices and persist only supported Gormes config/env fields. Terminal offers local, docker, modal, ssh, daytona, singularity/apptainer, and keep-current; TTS offers Edge, ElevenLabs, OpenAI, xAI, MiniMax, Mistral Voxtral, Gemini, NeuTTS, and keep-current; agent settings prompts max iterations, tool-progress mode, compression threshold, and session reset policy. Supported runtime settings feed the actual Gormes runtime. Unsupported backend/provider choices return typed row-backed evidence. | validated | tools | medium | operator, system | cmd/gormes/setup_minimal_test.go | Unsupported terminal/TTS providers return setup_terminal_backend_row_backed or setup_tts_provider_row_backed. Invalid numeric agent inputs keep the current value with setup_agent_value_ignored evidence instead of corrupting config. Invalid tool-progress choices are rejected by the bounded selector and keep the current display.tool_progress value. |
| 5 / 5.O | Gormes uninstall dry-run command contract — gormes uninstall [--dry-run] [--keep-config] [--keep-credentials] [--yes] enumerates every artifact Gormes wrote (XDG config dir, credential pool, gateway state, session logs, cron registry, MCP OAuth state) and emits a deterministic dry-run manifest by default; the destructive path requires both --yes and an absent --dry-run to actually delete files. The dry-run output is byte-stable across runs (sorted paths, no clock/uuid leakage) so operators can diff it before acting. | validated | tools | small | operator, system | cmd/gormes/uninstall_dryrun_test.go | Missing artifacts are reported as (absent) rather than errors; permission errors during dry-run enumeration are reported as path_unreadable with the bare path; destructive runs without --yes are rejected with uninstall_confirmation_required and exit code 2; partial deletes are forbidden — the destructive path is all-or-skip per artifact group. |
| 5 / 5.O | Gormes mcp login interface seam + noninteractive default — gormes mcp login <name> is registered as a Cobra command that delegates to a MCPLoginFlow interface defined in internal/tools/mcp_login.go with signature type MCPLoginFlow interface { Login(ctx context.Context, server MCPServer) (*MCPSession, error) }. The default implementation noninteractiveLoginFlow returns noninteractive_required typed evidence with operator guidance text (pointing at gormes mcp remove + gormes mcp add and the future browser-flow row) and never opens a browser, callback server, or live token-exchange request. Tests inject fake successful, failed, and noninteractive flows to prove the seam supports a future browser implementation. Existing rows 15458 (MCP OAuth state store) and 15522 (MCP OAuth refresh) supply the OAuth state store and refresh seams; this row only ports the command + interface seam. | validated | tools | medium | operator, system | internal/tools/mcp_login_test.go | Default flow returns noninteractive_required typed evidence; injected flows can additionally return mcp_login_state_store_unwritable when the OAuth state store is read-only and mcp_login_flow_failed when an injected flow returns an error. Unknown MCP server name returns mcp_server_unknown with the configured server list. Non-OAuth auth modes (header/stdio) return mcp_auth_not_oauth with the operator-facing redirect (use 'gormes mcp remove' + 'gormes mcp add'). |
| 5 / 5.O | Gormes mcp login browser callback flow — Implements MCPLoginFlow.Login over a localhost callback server (ephemeral http.Server bound to 127.0.0.1 with an OS-assigned port), a browser launch hook (default os.exec opens the system browser; tests inject a recording fake), and a code-for-token exchange transport (tests inject a fake http.RoundTripper so no live token endpoint is contacted). On success, the resulting MCP session is stored through row 15458’s interface; on failure, the OAuth state store is left byte-identical. Donor pattern (pattern-only, not copied verbatim): references/go-agent-os/goclaw/internal/oauth/openai.go localhost-callback shape; donor pattern (Hermes-side): hermes-agent/hermes_cli/auth.py:_make_spotify_callback_handler. | validated | tools | medium | operator, system | internal/tools/mcp_login_browser_test.go | Port collision returns mcp_login_port_collision; callback timeout returns mcp_login_callback_timeout; token exchange error returns mcp_login_token_exchange_failed; redirect-URI mismatch returns mcp_login_redirect_uri_mismatch. None of these failure modes panic, hang, or leak access tokens / refresh tokens / authorization codes into stdout/stderr. |
| 5 / 5.O | Hermes fallback provider chain CLI commands — Gormes must restore current Hermes’ fallback top-level command and nested list/ls, add, remove/rm, and clear provider-chain commands. The command reads and writes a Gormes-native TOML fallback chain compatible with Hermes’ top-level fallback_providers list and legacy fallback_model shape, preserves the current primary model/provider when adding a fallback through the existing model picker, and redacts all credential-adjacent state by never reading provider tokens. | validated | tools | small | operator, system | cmd/gormes/fallback_test.go | If the picker is unavailable or stdin is non-TTY, gormes fallback add fails with the same typed model-picker error used by gormes model; list/remove/clear still operate on the local config file and do not contact providers. |
| 5 / 5.O | Provider endpoint/API-key root flags + runtime resolution — cmd/gormes accepts —endpoint, —api-key, —model, and —provider as invocation-only overrides for oneshot and TUI startup; flag values win over env/config for the current process, —api-key is never persisted, and all status/error evidence redacts the secret value. | validated | tools | small | operator, system | cmd/gormes/provider_flag_resolution_test.go | If endpoint/model/provider flags are incomplete or ambiguous, startup returns an exit-code-2 operator error before opening a provider client; no config file is modified. |
| 5 / 5.O | Gormes profile skills chat invocation shim — Gormes accepts the active Hermes worker invocation shape gormes -p <profile> --skills <skill> chat -q <prompt> as a native compatibility shim: profile selection resolves Gormes-owned profile roots before config loading, repeated or comma-separated --skills values become an explicit runtime skill allowlist for the one turn, and chat -q dispatches through the existing oneshot path without reading HERMES_HOME, shelling out to Hermes, or persisting invocation-only flags. | validated | tools | small | operator, system, child-agent | cmd/gormes/chat_command_test.go | Invalid profile names, missing profile root resolution, missing selected skills, and provider setup failures return typed Gormes evidence before opening provider clients; raw skill paths, private profile roots, and provider secrets are not printed. |
| 5 / 5.O | Hermes config.yaml Telegram compatibility bridge — Historical bridge row superseded by the Cross-agent config isolation contract: normal Gormes runtime must not read Hermes config.yaml; Telegram compatibility is preserved through gormes migrate hermes manifest/writer mapping and native env aliases for Hermes dotenv keys. | validated | tools | small | operator, system, gateway | internal/config/config_test.go::TestLoad_IgnoresHermesConfigYAML; internal/config/config_test.go::TestLoad_TelegramHermesEnvAliases; internal/platform/migrate/hermes/manifest_test.go::TestHermesMigrationManifest_DotenvKeysClassifiedAndRedacted; internal/platform/migrate/hermes/writer_test.go::TestHermesConfigWriter_AppliesImportableConfig | Foreign Hermes config residue is ignored by normal runtime paths; operators use gormes migrate hermes --dry-run and --yes to copy supported Telegram values into Gormes-native config and dotenv files. |
| 5 / 5.O | Gormes config command surface — cmd/gormes exposes gormes config show, path, env-path, and set <key> <value> over the native XDG config surfaces: non-secret dotpaths write config.toml, secret aliases such as api_key and _API_KEY/_TOKEN write the Gormes .env file, and show output redacts every secret. | validated | tools | medium | operator, system | cmd/gormes/config_command_test.go; internal/config/writer_test.go | Malformed keys, unknown top-level sections, and invalid boolean/int values return typed operator errors without partially writing either file. |
| 5 / 5.O | Gormes config set comment-preserving TOML writes — Gormes preserves operator-authored comments and readable Unicode in config.toml when gormes config set mutates non-secret values, matching Hermes’ current round-trip config mutation behavior while keeping Gormes’ native TOML/XDG schema and atomic write guarantees. | validated | tools | small | operator, system | internal/config/writer_test.go::TestConfigWriter_WriteTOMLValuePreservesCommentsAndUnicode | Malformed TOML, unknown sections, or invalid typed values still fail before mutation; successful writes update only the selected key and preserve unrelated comments/values. |
| 5 / 5.O | Gormes config edit/check/native schema-migrate closeout — cmd/gormes completes Hermes config-command parity for the native Gormes config surface by adding gormes config edit, gormes config check, and gormes config migrate over XDG TOML/dotenv files, while keeping gormes migrate hermes and gormes migrate openclaw as the only cross-product import commands. | validated | tools | small | operator, system | cmd/gormes/config_closeout_test.go; internal/config/check_test.go | Missing editor binaries, malformed config files, future _config_version values, and invalid dotenv entries produce typed operator evidence without mutating files unless the migrate subcommand can write atomically. |
| 5 / 5.O | Hermes config migration dry-run manifest — internal/platform/migrate/hermes builds a deterministic dry-run manifest for gormes migrate hermes by reading an explicit source directory, $HERMES_HOME, or ~/.hermes, parsing config.yaml and .env, classifying every supported key as importable, skipped, conflict, or archived, and printing a report without writing Gormes state. | validated | tools | small | operator, system | internal/platform/migrate/hermes/manifest_test.go; cmd/gormes/migrate_hermes_test.go | Missing Hermes homes, invalid YAML, unreadable .env files, and newer unknown schema versions produce report items instead of panics or partial imports. |
| 5 / 5.O | Hermes config migration writer — gormes migrate hermes --yes applies the validated Hermes migration manifest into Gormes config.toml and .env with backups, conflict handling, redaction, and an import report; session and memory imports remain archived/skipped unless their target rows are complete. | validated | tools | medium | operator, system | internal/platform/migrate/hermes/writer_test.go; cmd/gormes/migrate_hermes_writer_test.go | Conflicting existing Gormes keys are skipped by default and reported with —overwrite guidance; no partial write is allowed if backup creation fails. |
| 5 / 5.O | OpenClaw migration dry-run manifest — internal/platform/migrate/openclaw builds a deterministic dry-run manifest for gormes migrate openclaw by reading ~/.openclaw, ~/.clawdbot, ~/.moltbot, or —source, mapping OpenClaw config, .env, memory, skills, MCP, channels, model providers, TTS, tools, approvals, session reset, and archive-only surfaces into Gormes destinations without writing state. | validated | tools | medium | operator, system | internal/platform/migrate/openclaw/manifest_test.go; cmd/gormes/migrate_openclaw_test.go | Unknown OpenClaw keys, SecretRef file/exec sources, running OpenClaw processes, and platform token conflicts are reported as skipped/conflict/warning items instead of being dropped. |
| 5 / 5.O | OpenClaw migration writer and cleanup command — gormes migrate openclaw --yes applies the validated OpenClaw manifest into Gormes config, dotenv, memories, skills, and archive report directories with backups and conflict policies; gormes migrate openclaw cleanup archives leftover OpenClaw directories after warning about running processes. The Hermes-compatible spellings gormes claw cleanup and alias gormes claw clean delegate to the same cleanup engine, preserve JSON build provenance, and do not shell out to Hermes or OpenClaw Python. | validated | tools | medium | operator, system | internal/platform/migrate/openclaw/apply_test.go; cmd/gormes/migrate_openclaw_apply_test.go; cmd/gormes/hermes_cli_parity_test.go | Running OpenClaw or active Gormes gateway channels produce warnings and require —yes before token-bearing migrations; cleanup refuses noninteractive destructive renames without —yes. |
| 5 / 5.O | CLI profile name validator — internal/cli adds a pure function ValidateProfileName(name string) error and an exported sentinel error set: ErrProfileNameEmpty, ErrProfileNameTooLong, ErrProfileNameInvalidChars, ErrProfileNameReserved; the function accepts names matching ^[a-z0-9][a-z0-9_-]{0,63}$, treats ‘default’ as valid (special alias), and rejects the reserved subcommand names {‘create’,‘delete’,‘list’,‘use’,‘export’,‘import’,‘show’} | validated | tools | small | operator, system | internal/cli/profile_name_test.go | Callers report a typed sentinel error class instead of free-form text so the CLI can render uniform error messages later without re-parsing strings. |
| 5 / 5.O | CLI profile root resolver — internal/cli adds a pure function ResolveProfileRoot(name string, gormesXDGConfigHome string) (string, error) that returns gormesXDGConfigHome+‘/gormes’ for name==‘default’ and gormesXDGConfigHome+‘/gormes/profiles/‘+name for any other valid profile name; it rejects invalid names by returning the same sentinel errors as ValidateProfileName and never touches the filesystem | validated | tools | small | operator, system | internal/cli/profile_root_test.go | Path resolution returns sentinel errors for invalid names; callers handle filesystem access (mkdir, stat) outside this helper. |
| 5 / 5.O | CLI active-profile store — internal/cli adds an active-profile read/write store with ReadActiveProfile(rootFile string) (string, error) returning ErrActiveProfileUnset when missing, WriteActiveProfile(rootFile, name string) error that calls ValidateProfileName before writing, and ClearActiveProfile(rootFile string) error; both write paths use a temp-file + rename pattern so partial writes never produce a half-written file | validated | tools | small | operator, system | internal/cli/active_profile_test.go | Read returns ErrActiveProfileUnset for missing files; write rejects invalid names via the validator’s sentinel errors; clear is idempotent (no error when the file does not exist). |
| 5 / 5.O | Scripted chat query model/provider resolver — Gormes accepts gormes chat -q <prompt> plus root --model, --provider, GORMES_INFERENCE_MODEL, and GORMES_INFERENCE_PROVIDER overrides with Hermes-compatible ambiguity errors before any agent execution starts. Removed root -z/--oneshot invocations return guidance to gormes chat -q instead of executing. | validated | tools | small | operator, system | cmd/gormes/oneshot_flags_test.go | CLI parse/status output returns exit code 2 and an actionable stderr error when —provider is set without an explicit model, rather than silently pairing it with a stale configured model. |
| 5 / 5.O | Oneshot final-output writer boundary — One-shot mode runs one native Gormes kernel turn over a fake provider and writes only final assistant content plus one trailing newline to stdout | validated | tools | small | operator, system | cmd/gormes/oneshot_output_test.go | CLI output reports provider/client setup failures on stderr with nonzero exit codes while banners, render frames, tool previews, logs, and session IDs never enter stdout. |
| 5 / 5.O | Oneshot noninteractive safety and clarify policy — One-shot mode resolves clarify-style prompts and dangerous-command/hook approvals deterministically without hanging or silently widening Gormes safety defaults | validated | tools | small | operator, system | cmd/gormes/oneshot_safety_test.go | One-shot status/audit evidence reports noninteractive assumption, clarify_unavailable, dangerous_command_blocked, or explicit approval-bypass state instead of waiting for TUI input. |
| 5 / 5.O | Platform toolset config persistence + MCP sentinel — CLI setup/tools configuration persists per-platform toolset choices without preserving platform-default supersets, losing MCP server names, or leaking restricted Discord toolsets | validated | tools | small | operator, gateway | internal/cli/toolset_config_test.go | CLI config status reports ignored default supersets, invalid numeric keys, no_mcp suppression, and restricted toolsets instead of silently enabling extra tools. |
| 5 / 5.O | Platform toolset mixed composite runtime expansion — When a persisted platform_toolsets entry mixes a Hermes platform-default composite such as hermes-cli with explicit configurable opt-ins such as spotify, Gormes expands the composite into its native runtime toolsets while preserving the explicit opt-in and without resurrecting default-off or platform-restricted toolsets. | validated | tools | small | operator, gateway | internal/cli/toolset_config_test.go | Unknown composite names remain passthrough/MCP candidates with existing restricted-toolset evidence; explicit empty platform selections still stay empty; platform-default supersets are still stripped before persistence. |
| 5 / 5.O | Effective toolset picker dedupes bundled plugin keys — CLI toolset selection merges built-in toolsets with plugin-provided toolsets without duplicate keys, preserving the built-in label and description when bundled plugins such as Spotify share a key | validated | tools | small | operator, gateway | internal/cli/effective_toolsets_test.go | CLI setup status reports duplicate plugin/built-in toolset keys and keeps one effective option instead of presenting duplicate picker rows. |
| 5 / 5.O | WhatsApp top-level pairing wizard shell — Top-level gormes whatsapp preserves Hermes’ public setup entrypoint by rendering a Gormes-owned WhatsApp pairing wizard shell for the Baileys WhatsApp Web bridge, including mode, persisted enabled/mode/allowlist env state, dependency readiness, scan instructions, session path, bridge event streaming, Gormes-owned paired-success next steps, plus a --plan preflight for config/env/session/log paths and bridge command shape without reading private WhatsApp session state | validated | tools | small | operator, gateway, system | cmd/gormes/whatsapp_command_test.go | If the Baileys bridge bundle or npm dependencies are unavailable, gormes whatsapp prints the Gormes blocker and exits non-zero; gormes whatsapp --plan still renders the expected bridge command and config paths without starting Node. |
| 5 / 5.O | WhatsApp live Baileys QR pairing wizard — Installed Gormes bundles or resolves the Hermes-compatible Baileys bridge so gormes whatsapp can install bridge npm dependencies, run node bridge.js --pair-only --session $GORMES_HOME/whatsapp/session, stream QR/reconnect/paired logs, verify creds.json before paired-success output, and keep all paths under GORMES_HOME without requiring Python or a Hermes checkout | validated | tools | medium | operator, gateway, system | cmd/gormes/whatsapp_command_test.go | If Node, npm, the bridge bundle, or pairing credentials are unavailable, the command reports the exact missing dependency or incomplete pairing evidence and never prints a paired-success claim. |
| 5 / 5.O | Gateway management CLI read-model closeout — Gateway management CLI exposes read-only status, pairing, runtime-validation, and channel-availability evidence over existing Gormes stores before mutating start/stop/restart commands widen the surface | validated | tools | small | operator, gateway, system | cmd/gormes/gateway_status_test.go | Gateway CLI reports missing runtime state, invalid PID/process evidence, missing pairing store, and disabled channels via the existing native runtime/pairing read models instead of inventing a second management state model. |
| 5 / 5.O | Gateway mutating-subcommand unavailability stub — Gateway lifecycle CLI provides local safe stop and restart commands over validated runtime PID evidence: stop interrupts only the validated live gateway, restart first tries a fakeable systemd user-service restart on Linux, then falls back to the validated runtime record by restarting a live managed gateway or starting a fresh gateway when no live managed runtime exists, while unsupported start/install/uninstall subcommands surface a stable unavailable error with a pointer to the existing internal/cli/service_restart.go helper instead of silently failing or shelling out through untested paths. | validated | tools | small | operator, gateway, system | cmd/gormes/gateway_mutating_unavailable_test.go | gormes gateway stop is idempotent when runtime status is missing, stale, or pid-reused; it signals only a PID validated as the live Gormes gateway and refuses to stop while active_agents is non-zero. gormes gateway restart reports service-manager restart evidence when systemd succeeds, bounds service-manager restart calls with the requested timeout, falls back to the runtime record when the service manager is unavailable, starts a fresh gateway when the runtime record is missing or stale, and refuses active-agent or unsafe PID-validation restarts. Unsupported start/install/uninstall subcommands print a deterministic gateway: <subcommand> is not available; use the systemd/service_restart helper message and exit with a stable non-zero code. |
| 5 / 5.O | Windows gateway Scheduled Task lifecycle commands — On Windows, gormes gateway install, start, restart, and uninstall use the native Scheduled Task service path instead of the generic unavailable stub: install registers the login task and starts it inline, start/restart delegate to the Scheduled Task runner, and non-Windows platforms keep deterministic unavailable guidance for start/install/uninstall while gateway restart uses the Go-native service-manager/runtime restart path. | validated | tools | small | operator, gateway, system | cmd/gormes/gateway_mutating_unavailable_test.go::TestGatewayWindowsScheduledTaskLifecycleCommands | Windows lifecycle commands return scheduled_task_unavailable evidence when schtasks cannot be invoked; non-Windows start/install/uninstall continue returning the stable gateway: <subcommand> is not available; use the service_restart helper message without touching runtime stores, while non-Windows restart reports service-manager or recorded-runtime restart evidence. |
| 5 / 5.O | Windows detached gateway Ctrl+C boundary — Gormes preserves the current Hermes Windows gateway control boundary: foreground gormes gateway remains interruptible with Ctrl+C, while Windows Scheduled Task launches mark the child gateway as detached and the runtime absorbs stray console-control interrupts instead of treating sibling CLI broadcasts as operator stops. | validated | tools | small | operator, gateway, system | cmd/gormes/gateway_mutating_unavailable_test.go | If the detached marker is missing, a Windows Scheduled Task gateway can exit on an unrelated console Ctrl+C broadcast; if foreground handling is over-broadened, operators lose the documented Ctrl+C stop path. |
| 5 / 5.O | Service RestartSec parser helper — Service-management helpers parse systemd RestartUSec/RestartSec evidence into a bounded restart delay without invoking live service managers | validated | tools | small | operator, system | internal/cli/service_restart_parse_test.go | CLI service status reports restart_delay_defaulted, restart_delay_malformed, restart_delay_infinite, or service_manager_unavailable evidence instead of hiding parser failures. |
| 5 / 5.O | Service restart active-status poller — Update and service-management flows poll fake active status through the RestartSec cooldown window before reporting restarted, timeout, or crashed evidence | validated | tools | small | operator, system | internal/cli/service_restart_poll_test.go | CLI update/status output reports service restart timeout, retry, missing service manager, or crashed-after-restart evidence instead of claiming a restarted gateway that immediately failed. |
| 5 / 5.O | Hermes sessions CLI MRU browse/delete ergonomics — gormes sessions matches Hermes operator ergonomics over the native session store: -c/continue resolves the most recently active session by last_active before started_at and is not limited to the newest started 20 rows; delete accepts unique ID prefixes and reports not found without mutating; delete/prune confirmation handles EOF/non-TTY as cancelled; browse offers an interactive picker with title-over-preview display and a numbered fallback when curses/TUI selection is unavailable; and unquoted multi-word session names are coalesced before subcommands. | validated | tools | medium | operator, system | cmd/gormes/sessions_command_test.go; internal/persistence/session/mru_test.go | Empty stores, ambiguous/missing prefixes, DB search errors, non-TTY confirmation EOF, curses/browser unavailable, invalid fallback selections, and keyboard interrupts return sessions_cli_* evidence without deleting sessions or leaving DB handles open. |
| 5 / 5.O | Backup/update opt-in and exclusion policy — CLI backup/update policy defaults pre-update backups off unless explicitly requested, honors —no-backup over —backup, and excludes checkpoints plus SQLite WAL/SHM/journal sidecars from backup manifests | validated | tools | small | operator, system | internal/cli/backup_policy_test.go::TestBackupPolicy_* | Update status reports backup_skipped_default, backup_forced, backup_disabled_by_flag, or backup_manifest_excluded_paths instead of silently archiving large or unsafe runtime files. |
| 5 / 5.O | Self-update command lifecycle safety — gormes update becomes a native, source-checkout-safe update command over the existing installer/runtime helpers: it detects managed checkout state, autostashes dirty local changes, switches back to the configured update branch when safe, prefers fast-forward pull with reset fallback evidence, restores or preserves stash state deterministically, mirrors output to an update log while ignoring SIGHUP on POSIX, restarts or reloads a live gateway only through validated service/runtime seams, and warns or kills stale dashboard processes before updated assets are served. | validated | tools | large | operator, system | cmd/gormes/update_command_test.go; internal/cli/update_lifecycle_test.go | Update output reports update_not_managed_checkout, update_autostash_preserved, update_branch_switch_failed, update_network_error, update_auth_error, update_gateway_restart_unavailable, update_gateway_restart_timeout, update_hangup_log_unavailable, or update_stale_dashboard_detected without deleting user changes or claiming a restarted service that failed. |
| 5 / 5.O | Gormes update release planner and dry-run contract — gormes update must distinguish normal release-installed operators from managed source checkouts before it mutates anything. For release installs, the command plans against trusted GitHub Releases: detect install layout, current version/build, OS/arch artifact name, channel policy (stable by default, development explicit), latest release metadata, snapshot path, components to update, blocked reasons, and whether an update is available. gormes update --dry-run prints the exact non-mutating plan; gormes update --check performs no mutation and exits 0 when current, 10 when an update is available, and nonzero for check errors. Existing source-checkout behavior from Self-update command lifecycle safety remains the managed/dev path and must not be silently used for release installs. | validated | tools | medium | operator, system | cmd/gormes/update_release_plan_test.go; internal/cli/update_release_plan_test.go | Unknown install layout, unsupported OS/arch, missing release metadata, channel mismatch, dirty unmanaged source checkout, and network/API lookup failures produce typed plan blockers without changing binaries, assets, skills, services, config, credentials, sessions, memory, or source checkouts. |
| 5 / 5.O | Gormes update verified binary swap and rollback — For release installs, gormes update must turn a validated release plan into a binary-only mutation: create a pre-update snapshot, download the selected GitHub release artifact through an injectable downloader, verify OS/arch, checksum manifest, and signature/provenance when available, smoke-test the staged binary with gormes version --json, atomically swap the managed and published binary paths, and roll back automatically on any post-snapshot failure. gormes update --rollback <snapshot-id> restores a previous binary snapshot. --force may bypass downgrade or unmanaged-session warnings but must never bypass checksum, signature, provenance, platform, or smoke-test failures. | validated | tools | large | operator, system | cmd/gormes/update_release_binary_test.go; internal/cli/update_release_binary_test.go | Download, checksum, signature/provenance, platform mismatch, smoke-test, permission, atomic-rename, and rollback failures emit typed evidence, preserve the snapshot ID, and never report update success while the active binary is old or partially replaced. |
| 5 / 5.O | Gormes update bundled assets and skills sync — After release binary update is safe, release-installed gormes update must read a verified release manifest and apply only manifest-enumerated bundled assets and bundled skills. The manifest is part of the trusted release chain: strict schema, path, digest, and duplicate-entry validation must complete before mutation; local web builds and runtime directory scans are not authoritative for release installs. Update snapshots every file it may mutate, updates all known profiles with digest-based three-way bundled skill sync, preserves user-modified active skills, writes deterministic conflict copies for new bundled versions, removes only unmodified removed-from-manifest bundled skills, marks modified removed skills as orphaned, records a release install ledger/report, and supports rollback of update-mutated files with post-update-edit conflict preservation. User config, credentials, sessions, memory databases, operator-created files, service drain/restart behavior, and public component-selection flags are out of scope for this slice. | validated | tools | large | operator, system | cmd/gormes/update_release_assets_test.go; internal/cli/update_release_assets_test.go; internal/skills/update_sync_test.go | Missing or invalid release manifest, asset checksum mismatch, unwritable asset directory, skill conflict, or rollback failure emits typed evidence and preserves user-modified content; update may finish with a degraded sync report only when the binary remains valid and user state was not clobbered. |
| 5 / 5.O | Gormes update managed service drain and restart — gormes update must coordinate release updates with live Gormes processes after binary/assets/skills sync is safe: acquire a global update lock, detect managed gateway/background services, drain and stop managed services before mutation, refuse or prompt for unmanaged active sessions unless --force is given, restart managed services after update, health-check them, and restore prior service state after rollback when possible. The existing source-checkout gateway restart evidence remains valid, but release updates need a broader lock/drain/restore wrapper that prevents two updates or live services from mutating shared assets concurrently. | validated | tools | large | operator, system | cmd/gormes/update_release_services_test.go; internal/cli/update_service_coordination_test.go | Lock contention, drain timeout, stop failure, unmanaged active sessions, restart failure, health-check timeout, and rollback service-restore failure produce typed evidence with snapshot/manual recovery paths; --force may bypass unmanaged-session warnings but not lock integrity, snapshot, binary verification, or service health evidence. |
| 5 / 5.O | doctorCustomEndpointReadiness check function — cmd/gormes adds a pure function doctorCustomEndpointReadiness(cfg config.Config) doctor.CheckResult that returns Name=‘Custom endpoint’, Status=Pass when Hermes.Endpoint and Hermes.APIKey and Hermes.Model are all non-empty, Status=Warn when any one is missing (with itemized evidence), and Status=Fail when Endpoint is set but Model is empty; doctorCmd RunE invokes this function after the existing Goncho/Slack checks; —offline still skips network probes elsewhere | validated | tools | small | operator, system | cmd/gormes/doctor_custom_provider_test.go | When endpoint is set but credentials or model are missing, the check emits Status=Warn with item-level notes (api_key=missing, model=missing) instead of exiting non-zero, so operators see precisely which field needs attention. |
| 5 / 5.O | gormes doctor actionable issues summary and —fix auto-remediation — gormes doctor must end every run with the Hermes parity affordance it currently lacks: an aggregated, actionable issues summary plus a gormes doctor --fix auto-remediation path. Today internal/doctor/doctor.go is a flat CheckResult reporter and cmd/gormes/doctor.go prints checks then stops (the transcript ends at [SKIP] gateway/discord: disabled with no summary); cmd/gormes/doctor.go has --offline but no --fix. Port Hermes hermes_cli/doctor.py:run_doctor end-of-report behavior: after all checks, collect the same explicit actionable-issue funnel Hermes keeps in issues + manual_issues: FAIL checks plus fixable or explicitly actionable WARN checks become a numbered Found N issue(s) to address: list whose count N is computed from those findings (not narrated), while optional/degraded WARNs (logged-out optional auth providers, disabled gateway channels, GitHub rate-limit hints, local directory/profile setup hints, Skills Hub initialization) remain section-visible but do not inflate N. Each issue line carries the Gormes-owned remediation hint, followed by a Tip: run "gormes doctor --fix" to auto-fix what's possible. line when any actionable issue is auto-fixable. Add a --fix flag that auto-remediates at least the source-backed fixable classes — config schema/version migrate (Hermes doctor.py:693/722) and broken/missing published-command symlink repair (doctor.py:979/1003) — then re-runs the affected checks and reports what was fixed vs still-manual. Owned divergence: all paths and wording are Gormes (~/.gormes, gormes setup, gormes config migrate), never ~/.hermes/hermes setup; the issues count must be evidence-derived consistent with the existing ‘doctor counts must be computed’ contract. --offline continues to skip network checks and --fix performs no network remediation under --offline. This row does not rework unrelated doctor checks or add new diagnostic sections (Security Advisories / Directory Structure / Skills Hub are separate parity rows). | validated | tools | medium | - | cmd/gormes/doctor_fix_test.go | If a fixable class cannot be auto-remediated (no write permission, ambiguous state, or --offline), --fix leaves it in the manual issues list with the Gormes remediation hint and a typed reason rather than failing the whole command or claiming a fix that did not happen. With zero issues, doctor prints a clean ‘no issues’ line and no --fix tip. |
| 5 / 5.O | gormes doctor ◆ Section grouping + upstream section ordering (UX parity) — gormes doctor must present its diagnostics with the same organized UX as upstream hermes doctor: labeled ◆ <Section> headers (cyan+bold-equivalent) printed in the upstream order, grouping the EXISTING gormes CheckResults — NOT adding new diagnostic data. Today internal/doctor/doctor.go:179 CheckResult.Format() emits a flat [PASS]/[WARN]/[SKIP] <name>: <summary> stream and cmd/gormes/doctor.go’s doctorReporter funnels each result through Format() with no section grouping; hermes_cli/doctor.py:297 run_doctor instead renders print(); print(color("◆ <Name>", CYAN, BOLD)) per section in the fixed order Security Advisories, Configuration Files, Auth Providers, Directory Structure, External Tools, API Connectivity, Tool Availability, Skills Hub, Memory Provider, Profiles, then the (already-shipped) Found N issue(s) to address: end block. Introduce a section taxonomy + a section-aware reporter path that maps each current gormes check to its upstream section and prints a blank-line-then-◆ <Section> header before that section’s checks (header only when the section has ≥1 check). The shipped Found N issue(s) summary + --fix block must still render AFTER the sections, byte-equivalent. gormes doctor --json output ({build,failed,target,checks:[…]}) must be unchanged (sections are a human-mode presentation concern only). All wording stays Gormes-owned (gormes setup, gormes config migrate, ~/.gormes), never hermes setup/~/.hermes. Owned divergence: gormes-only checks with no 1:1 Hermes section — build identity, TUI, Termux runtime, Goncho config (→ Memory Provider), Custom endpoint (→ Configuration Files/API Connectivity), gateway/telegram|discord|Slack (→ API Connectivity) — are placed under the nearest upstream section or a single named Gormes-owned section, and that placement is documented as intentional ownership, never silently dropped. This row does NOT add new section CONTENT (live Security Advisories scan, full Directory Structure tree, Skills Hub init + GITHUB_TOKEN note, Auth Providers enumeration, per-profile Profiles) — those are the separate follow-up umbrella row. | validated | tools | medium | - | cmd/gormes/doctor_section_test.go | A check whose name maps to no known section falls under a single explicit Gormes-owned catch-all section header rather than being dropped or panicking; a section with zero checks prints no header (no empty ◆ lines). If section grouping cannot be resolved the reporter falls back to the existing flat Format() stream so doctor never fails solely due to presentation. |
| 5 / 5.O | gormes doctor section-content parity (Security Advisories / Directory Structure / Skills Hub / Auth Providers / Profiles) — Umbrella inventory parent for the upstream hermes doctor section CONTENT that gormes doctor does not yet emit, tracked separately from the section-grouping UX slice. Upstream hermes_cli/doctor.py@55c9f3206 run_doctor renders, inside its ◆ sections, diagnostic data gormes currently lacks: a live Security Advisories scan with remediation (doctor.py:350), a Directory Structure listing of SOUL.md/MEMORY.md/USER.md/state.db sessions (:812), Skills Hub directory init state + a GITHUB_TOKEN rate-limit note (:1637), an Auth Providers enumeration (Nous Portal / Gemini OAuth / MiniMax / codex CLI) (:749), and per-profile Profiles (gateway running + model per profile) (:1768). Each is its own builder-ready behavior with distinct source seams; this parent only inventories them so they are not lost. Child rows must be split before implementation — do not implement this parent as one slice. | validated | tools | umbrella | - | n/a (inventory parent; child rows carry fixtures) | Parent row is inventory-only and non-selectable; each child behavior carries its own degraded_mode when split. |
| 5 / 5.O | gormes doctor ◆ Directory Structure section content — Add the missing ◆ Directory Structure diagnostic CONTENT to gormes doctor (parity with hermes_cli/doctor.py@55c9f3206:812). The ◆-section grouping/header UX already ships (internal/doctor.sectionForCheck maps a check named “Directory Structure” → SectionDirectoryStructure), but no check feeds it so the section never renders. Add a new internal/doctor check that inspects config.GormesHome() and emits ONE CheckResult{Name:“Directory Structure”} whose Items (ItemInfo PASS/WARN) report: the Gormes home dir exists; the Gormes-owned subdir layout (NOT Hermes’ cron/sessions/logs/skills/memories) — the actual ~/.gormes layout: sessions/, memory/, skills/, cron/, subagents/, tools/, hooks/ (missing → WARN “will be created on first use”); and the agent-template starter files SOUL.md, memory/MEMORY.md, memory/USER.md whose canonical paths come from internal/core/agenttemplate/parity_manifest.go (reuse that manifest as the source of truth — do NOT hardcode a parallel path list). SOUL.md present with non-template content → PASS “persona configured”; present but empty/template-only → PASS with note “edit it to customize personality”; missing → WARN “run gormes setup”. MEMORY.md/USER.md: present → PASS with char count; not yet created → a non-actionable PASS item with note “not yet created (written on first memory)” (Gormes Status has no INFO; upstream check_info maps to a PASS ItemInfo with an explanatory note — do NOT add a new Status enum value and do NOT make ‘not yet created’ a WARN, which would falsely inflate the Found N issue(s) count). Wire via reporter.Add in cmd/gormes/doctor.go so it auto-groups under the existing ◆ Directory Structure header; --offline unaffected (pure local FS, no network/secrets); hermetic via temp GORMES_HOME. Owned divergence: all paths/wording Gormes (~/.gormes, memory/ not memories/, gormes setup, gormes config edit) — never ~/.hermes/hermes. Do NOT add other section content (Skills Hub/Auth Providers/Profiles/Security Advisories are sibling rows) and do not change existing checks or the full wizard. | validated | tools | small | - | internal/doctor/doctor_directory_test.go | If config.GormesHome() does not exist yet, the home item is WARN “will be created on first use” and subdir/file items report missing without erroring (fresh-install is not a doctor failure). A check that cannot stat a path degrades that item to WARN with a typed note rather than panicking or failing the whole run. The section header only renders because the check is emitted (consistent with the shipped ◆-grouping empty-section rule). |
| 5 / 5.O | gormes doctor ◆ Skills Hub section content — Add ◆ Skills Hub diagnostic content to gormes doctor (parity with hermes_cli/doctor.py@55c9f3206:1637): a check that inspects ~/.gormes/skills/.hub (exists?), .hub/lock.json installed-skill count (corrupt → WARN), .hub/quarantine pending count (WARN if >0), and a GITHUB_TOKEN/GH_TOKEN rate-limit note (token configured → PASS; gh CLI authenticated → PASS; neither → WARN ‘60 req/hr — set in ~/.gormes/.env’). Emit one CheckResult{Name:“Skills Hub”} so it groups under the shipped ◆ Skills Hub header. Owned divergence: ~/.gormes paths, Gormes skills-hub layout, never ~/.hermes/hermes skills. Partial overlap with the existing ‘GitHub auth’ check — reuse its token-resolution logic, do not duplicate it. Hermetic temp GORMES_HOME; --offline skips only the gh-CLI network probe. | validated | tools | small | - | internal/doctor/doctor_skills_hub_test.go | Missing .hub dir → WARN ‘not initialized (run: gormes skills list)’; corrupt lock.json → WARN, not a panic; gh probe under —offline → SKIP that sub-item. |
| 5 / 5.O | gormes doctor ◆ Auth Providers section content — Add ◆ Auth Providers diagnostic content to gormes doctor (parity with hermes_cli/doctor.py@55c9f3206:749): a per-provider auth-status enumeration. Upstream lists Nous Portal / OpenAI Codex / Gemini OAuth / MiniMax OAuth login state. Gormes owns a different provider set (openai-codex credential pool, nous, custom endpoint) — emit one CheckResult{Name:“Auth Providers”} enumerating each Gormes-supported provider’s configured/authenticated state, reusing the existing credential-pool / SecretRef-runtime / auth-status seams (do NOT infer readiness from hermes.api_key alone; credential-pool providers like openai-codex must show configured). Owned divergence: Gormes provider taxonomy + ~/.gormes paths; redact all tokens/JWTs. Partial overlap with existing SecretRef runtime / Custom endpoint / provider-health checks — this row adds the missing per-provider list, it does not replace them. | validated | provider | medium | - | internal/doctor/doctor_auth_providers_test.go | A provider whose auth status cannot be resolved → WARN ‘not logged in’ with redacted reason, never a token leak or panic; —offline reports last-known/local state without a network probe. |
| 5 / 5.O | gormes doctor ◆ Profiles section content — Add Gormes-owned ◆ Profiles diagnostic content to gormes doctor (parity intent: hermes_cli/doctor.py@55c9f3206:1768 ◆ Profiles), now CONTRACT-MAPPED and SHIPPED. Emit ONE CheckResult{Name:“Profiles”} with ItemInfo (PASS/WARN only): a summary item N profile(s) found (active: <name|default>); one item for default plus each valid named profile from the Gormes profile seam (cmd/gormes/profile.go profileCommandSeams.ListKnownProfiles() + ReadActiveProfileName() + ResolveProfileRoot + ReadDistributionManifest, or the underlying cli.* funcs — reuse as source of truth, do NOT create a parallel profile inventory). Each profile item shows the name, active marker, resolved root existence (missing → WARN), effective local provider/model from that profile’s own config.toml/config.yaml plus provider-default model resolution (no current shell env/flags), recorded gateway_state.json lifecycle state if present (no PID validation or live process probe), and optional cli.ProfileDistributionManifest summary (absent → NON-actionable PASS note ‘no distribution manifest’, NEVER WARN). Default-only → a single clean PASS item ‘default profile only’. Wire via reporter.Add in cmd/gormes/doctor.go so it groups under the existing ◆ Profiles header, and map "Profiles" → SectionProfiles in internal/doctor/doctor_section.go. Pure local FS — no network, identical under --offline; hermetic via temp GORMES_HOME. OWNED DIVERGENCE: Gormes profiles are ~/.gormes/profiles/<name> subdirs with profile-local config, recorded gateway state, optional distribution manifest, and an active marker — NOT Hermes’ separate ~/.hermes-<name> homes, NOT shell-wrapper aliases / _get_wrapper_dir, and NOT orphan-alias detection. Gormes-owned wording only (~/.gormes/profiles, gormes profile create, gormes profile list) — never ~/.hermes-<name>/hermes/wrapper-alias. Do NOT add other section content (Security Advisories is the remaining sibling) or change existing checks/the full wizard. | validated | tools | small | - | internal/doctor/doctor_profiles_test.go | If the profile-list seam is unavailable or errors, emit a single WARN item with a typed reason (no panic, no fabricated profile list). A profile whose root cannot be resolved, whose local config is missing/invalid, whose provider/model is missing, or whose recorded gateway_state.json is unreadable → WARN for that item only. Default-only / zero named profiles → one clean PASS ‘default profile only’ (NOT a WARN — Gormes always has a usable default). Manifest-absent and gateway-state absent are non-actionable PASS notes, never WARN. |
| 5 / 5.O | gormes doctor ◆ Security Advisories section content — Add ◆ Security Advisories diagnostic content to gormes doctor (parity intent hermes_cli/doctor.py@55c9f3206:350). CONTRACT CORRECTED + SHIPPED 2026-05-15 (gormes-tdd-slice): the row was mis-authored as LARGE / needs-interface-designer / port-a-Python-advisory-DB. The real upstream hermes_cli/security_advisories.py@55c9f3206 is ONE static Advisory dataclass entry (shai-hulud-2026-05 / mistralai 2.4.6) + detect_compromised (a Python importlib.metadata package scan) + filter_unacked + full_remediation_text + get_acked_ids/ack_advisory (config.yaml security.acked_advisories list). doctor.py:350 ◆ Security Advisories RUNS FIRST: fresh (unacked) hits→check_fail(title,‘(pkg==ver)’) + indented remediation block + funnel into the manual-issues summary (‘Resolve security advisory hermes doctor --ack <id>’); acked-but-still-present→check_warn informational; none→check_ok(‘No active security advisories’). Therefore slice_size=small, NO gormes-interface-designer prereq — the boundary is a direct struct port plus the two SHIPPED sibling deep-module patterns internal/doctor/doctor_directory.go & doctor_profiles.go (injected inventory; internal/doctor stays decoupled from internal/security). SHARP OWNED DIVERGENCE: Gormes is a single Go binary with NO Python venv / importlib.metadata, so detect_compromised’s Python-package scan CANNOT be faithfully reproduced and MUST NOT be fabricated. The honest port = a Gormes-owned advisory catalog (Go struct: ID/Title/Summary/URL/Compromised[]{Package,Versions}/Remediation/Published/Severity) + an INJECTABLE detector seam; the default detector finds nothing in a pure-Go runtime → the section is a clean PASS ‘No active security advisories’ (exactly Hermes’ ‘silent otherwise’ — this is the faithful behavior, NOT a stub). Ack store is Gormes-owned UNDER ~/.gormes (file-based in internal/security, dir-injected like CheckDirectoryStructure(home); NOT a change to internal/config SecurityCfg, NOT the Python config.yaml; missing store → all unacked, never panic). FULL subsystem this slice (user-chosen, zero follow-up rows): internal/security advisory catalog + DetectCompromised(detector) + FilterUnacked + FullRemediationText + ~/.gormes ack store + AckAdvisory; internal/doctor CheckSecurityAdvisories emitting ONE CheckResult{Name:“Security Advisories”} (fresh→FAIL+remediation actionable/IN CollectDoctorIssues; acked-but-present→WARN informational; none→non-actionable clean PASS NOT inflating Found-N — mirror the directory/profiles non-actionable-PASS rule); doctor_section.go sectionForCheck case "Security Advisories": return SectionSecurityAdvisories (renders FIRST via OrderedDoctorSections); cmd/gormes/doctor.go reporter.Add(doctorSecurityAdvisoriesStatus()) + a cobra --ack <id> flag persisting via the internal/security store. —offline-safe (pure local FS, no network); hermetic temp GORMES_HOME; Gormes-owned scaffolding wording (gormes doctor --ack <id>, hermes/pip uninstall is upstream advisory CONTENT carried verbatim as catalog data, distinct from Gormes-owned funnel wording). | validated | tools | small | - | internal/security/advisories_test.go | No catalog hits in a pure-Go runtime (the owned-divergence default) → clean non-actionable PASS ‘No active security advisories’ (NOT a SKIP and NOT a WARN — exactly Hermes’ check_ok ‘silent otherwise’ path); ack store file missing/unreadable → treat all hits as unacked, never panic; a detector seam error → one WARN item with a typed reason, never a fabricated Python-package scan. |
| 5 / 5.O | gormes setup gormes setup <section> must present the same boxed section chrome as upstream hermes setup <section> and as the gormes FULL wizard, instead of the ad-hoc plain cli.PrintHeader/cli.ClearScreen text each section currently prints. Upstream hermes_cli/main.py:1709 cmd_setup → hermes_cli/setup.py:3132 run_setup_wizard wraps every section run in a boxed header (setup.py:3199): a 59-wide box ┌───┐ / │ ⚕ Hermes Setup — {label:<34s} │ / └───┘, then runs the section, then a print_success(f"{label} configuration complete!") footer. The gormes full wizard already has parity chrome (cmd/gormes/setup.go:755-760 boxed │ Gormes Agent Setup Wizard │, ◆ <Section> headers, boxed ✓ Setup Complete! :1146), but runSetupSection (cmd/gormes/setup.go:528) dispatches gormes setup <section> straight into each runSetup│ Gormes Setup — <Label> │ header (reusing the existing 59-wide box shape used by printSetupWizardHeader / the shipped doctor RenderDoctorHeader — do NOT add a third chrome variant) printed before the section func runs, and a uniform <Label> configuration complete! success footer printed after a section completes without error, for every entry in setupSections (provider|model|agent|workspace|bindings|tts|terminal|gateway|tools). Section logic/prompts are unchanged — this is presentation only. Owned divergence: the section→label map is gormes-owned (provider→Provider, model→Model, agent→Agent Settings, workspace→Workspace, bindings→Channel Bindings, tts→Text-to-Speech, terminal→Terminal Backend, gateway→Messaging Gateway, tools→Tools) and documented as intentional, not silently derived. Non-interactive / no-TTY section paths keep working (header may still print; no new prompts; errSetupRequiresTTY/cancel paths must not emit a false ‘configuration complete!’). All wording Gormes-owned (gormes setup, ~/.gormes), never hermes setup/~/.hermes. Out of scope: full-wizard chrome (already covered) and the divergent legacy internal/cli/setup_wizard.go ⚕ Gormes Setup Wizard/✅ Setup complete! path (separate deferred follow-up note). | validated | tools | small | - | cmd/gormes/setup_section_test.go | If a section returns errSetupRequiresTTY, is cancelled, or errors, the uniform <Label> configuration complete! footer is NOT printed (no false success); the boxed header may still print since it precedes the section. An unknown section keeps the existing setupSectionUnsupported behavior. Non-interactive sections print the header then their existing non-interactive output, no prompts. |
| 5 / 5.O | Profile Control Center v2 umbrella — single root config and active services — Inventory-only, fixture-backed parent for the Gormes-owned Profile Control Center v2 direction. The target config surface is one root $GORMES_HOME/config.toml at config_version = 2, stable [profiles.<id>] service records, global [credentials.<id>] records referenced by profiles, no v2 active_profile/default_profile field, and screen-reader-first gormes setup profiles control-center flows. Runtime/profile data may still live under profile directories, but canonical v2 config is rooted once; implementation must happen only through child rows for schema, migration, read-model, TUI, provider/channel credentials, Navivox routing, docs, and fleet supervision. | validated | tools | umbrella | operator, system | docs/content/building-gormes/architecture_plan/profile-config-v2.md#profile-control-center + internal/config/profile_config_v2.go:DefaultConfigDocumentV2 | Until child rows ship, legacy profile/config paths remain compatibility inputs. This parent must not write runtime code, prompt for live credentials, select one global active/default profile, or treat per-profile config.toml files as the v2 canonical destination. Operators should see v2 as planned direction with child rows carrying executable behavior. |
| 5 / 5.O | gormes setup profiles — section scaffold + per-profile workspace list — Add a NEW Gormes-owned profiles section to gormes setup (child 1/3 of the setup-profiles umbrella). Wire it through the SHIPPED runSetupSection boxed-header/footer chrome (cmd/gormes/setup.go:528 — add “profiles” to setupSections :36 and setupSectionLabels :43 as “profiles”->“Profiles”; add a dispatch case to runSetupProfilesSection; do NOT add a third chrome variant — reuse the shipped │ Gormes Setup — <Label> │ / <Label> configuration complete! wrapper exactly like the other sections). runSetupProfilesSection (interactive, TTY): list known profiles with the active marker (REUSE defaultProfileCommandSeams().ListKnownProfiles + ReadActiveProfileName — do NOT re-enumerate ~/.gormes/profiles by hand); offer to create a profile (REUSE CreateProfile seam — do NOT reimplement profile creation); let the operator select a profile; prompt for ONE OR MORE workspace directories (repeatable prompt / comma-or-newline list); persist them as a workspace LIST into the SELECTED profile’s OWN config.toml via the real internal/config/writer.go TOML round-trip (default profile → config.GormesHome() config path; named profile → filepath.Join(config.GormesHome(),“profiles”,Profiles configuration complete! footer on cancel/refuse/error (the shipped chrome already suppresses the footer when err!=nil or output cancelled — keep that contract). —offline-safe (pure local FS, no network). Hermetic via temp GORMES_HOME. OWNED DIVERGENCE: Hermes has no setup profiles section — Gormes-owned wording only (gormes setup profiles, gormes profile create, hermes/ | validated | tools | small | - | cmd/gormes/setup_profiles_test.go | No TTY / non-interactive → errSetupRequiresTTY or a deterministic non-interactive message, NO false success footer. Profile-list seam error → surface a typed error, never a panic or a fabricated profile list. Selecting the default profile writes config.GormesHome() config; a named profile writes profiles/ |
| 5 / 5.O | gormes setup profiles — per-profile channels (telegram/whatsapp/discord/slack) — Child 2/3 of the setup-profiles umbrella. BUILDER-READY (refinement TODOs RESOLVED 2026-05-16; child 1 SHIPPED commit fb23ff105). Extend runSetupProfilesInteractive (cmd/gormes/setup.go) so AFTER the shipped per-profile workspace-list step, for the SELECTED profile, prompt for a comma-separated list of messaging channels; validate each against the known set {telegram,whatsapp,discord,slack} (skip/reject unknown with a Gormes-owned message — never fabricate a channel); persist the validated list as a TOML ARRAY into the SELECTED profile’s OWN config.toml (default profile → config.GormesHome()/config.toml; named → pseams.ResolveProfileRoot(name)/config.toml) via config.WriteTOMLValue(path, “agents.defaults.channels”, input) — the SAME real internal/config round-trip child 1 used for agents.defaults.workspaces; blank input KEEPS the existing list (no destructive clear); round-trips back via config.Load into cfg.Agents.Defaults.Channels. Add AgentDefaultsCfg.Channels []string (internal/config/agents.go, toml channels) and an agents.defaults.channels coerce case in internal/config/writer.go coerceTOMLValue → parseEnvCSV string array — EXACTLY SYMMETRIC with the shipped Workspaces field + agents.defaults.workspaces case (do NOT invent a different schema). REUSE BOUNDARY (decided this pass): runSetupBindingsSection is ADVISORY-ONLY (prints [[bindings]] guidance + ‘Or open your editor’, no persistence) so it is NOT reused for persistence; child 2 extends the child-1 interactive flow and uses the same config.WriteTOMLValue round-trip. OWNED DIVERGENCE: Hermes has no per-profile multi-channel concept; agents.defaults.channels is WHICH channels the profile uses, distinct from [[bindings]] routing and per-channel credential cfg (TelegramCfg/DiscordCfg/SlackCfg tokens, whatsapp QR/session via gormes whatsapp / internal/gateway/channel_setup.go) — per-channel credential/token/QR/whatsapp-pairing setup is OUT OF SCOPE and MUST NOT be reimplemented or faked here. Non-interactive/no-TTY unchanged (errSetupRequiresTTY, NO false Profiles configuration complete!); —offline-safe (pure local FS); hermetic temp GORMES_HOME; Gormes-owned wording (never ~/.hermes/hermes/wrapper); Goncho.Workspace untouched. OUT OF SCOPE: child 3 (navivox-default). SHIPPED 2026-05-16 (gormes-tdd-slice). | validated | tools | small | - | cmd/gormes/setup_profiles_test.go | No TTY / non-interactive → errSetupRequiresTTY, NO false completion footer (same shipped child-1 chrome rule). Unknown channel name → skipped with a Gormes-owned ‘unknown channel’ notice, never persisted or fabricated. Blank channel input → keep the existing agents.defaults.channels list (no destructive clear). Per-channel credentials/tokens/whatsapp-pairing are NOT prompted here (out of scope) — only the channel-name list is recorded, so the step is fully hermetic and identical under —offline. |
| 5 / 5.O | Navivox multi-server profile routing config model — Replace the superseded navivox-default plan with a v2 Navivox config model that can manage many profiles and many Navivox servers from the single root config. [navivox.servers.<id>] declares bind/transport/allowed profile ids/capabilities, while [profiles.<id>.channels.navivox] opts a profile into one or more servers and voice profiles. Navivox must not prompt for or persist one startup-selected/default profile; routing/profile selection is an in-app/server capability over the configured profile fleet. | validated | gateway | medium | operator, gateway, system | internal/config/profile_config_v2_test.go:TestNavivoxProfileRoutingBuildsServerScopedProfileFleetSafely | If a server references a missing or disabled profile, Navivox reports navivox_profile_unavailable for that route and keeps other configured profiles/servers available. |
| 5 / 5.O | Custom provider model-switch credential preservation — internal/cli adds a pure function ResolveCustomProviderSecret(ref CustomProviderRef, env map[string]string) (CustomProviderResolution, error) where CustomProviderRef has fields {Name string, BaseURL string, APIKey string, KeyEnv string} and CustomProviderResolution has fields {EffectiveSecret string, PersistAsRef string, Evidence string}; the function reads env-template ${VAR} from APIKey via env, prefers KeyEnv when APIKey is empty, and never returns plaintext in PersistAsRef when the input was a reference | validated | tools | small | operator, system | internal/cli/custom_provider_secret_test.go | Resolution returns Evidence=‘credential_missing’, ‘secret_ref_preserved’, ‘plaintext_provided’, or ‘env_var_unset’ so callers can distinguish persistable references from resolved secrets without writing plaintext to config. |
| 5 / 5.O | Custom provider model-switch key_env write guard — internal/cli exposes a pure model-switch patch helper that accepts an in-memory custom provider ref plus a target model and returns the config patch/evidence for default_model changes while preserving original credential storage: providers that relied on key_env and had no inline api_key/api_key_ref must not gain an api_key entry, while providers that already had inline plaintext or ${VAR} api_key may keep that existing value without writing resolved plaintext | validated | tools | small | operator, system | internal/cli/custom_provider_model_switch_test.go::TestCustomProviderModelSwitchPatch_* | Model-switch planning returns credential_write_skipped_key_env, credential_ref_preserved, plaintext_preserved, or credential_missing evidence so setup/status surfaces can explain why api_key was not written. The credential-preservation prerequisite and backend health bypass are now validated, so this row should run as a pure internal/cli patch-helper fixture. |
| 5 / 5.O | CLI log redactor for known secret shapes — internal/cli/log_redact.go exposes RedactLine(line []byte) ([]byte, int) where the int is the number of redactions applied. Matches and replaces with “[REDACTED]”: (1) Bearer XXX in any header line, (2) api_key=VALUE or x-api-key: VALUE, (3) Telegram bot tokens NN:XXXXXXXX (digits + colon + >=20 alnum/_/-), (4) Slack xoxb-/xoxp-/xoxs- tokens, (5) OpenAI sk-* keys longer than 16 chars. Returns input unchanged with count=0 if no match. Pure: only regexp + bytes packages from stdlib. | validated | tools | small | operator, system | internal/cli/log_redact_test.go | Redactor counts replacements per line so the snapshot caller can attach a per-section Redacted field without re-scanning. |
| 5 / 5.O | CLI log snapshot reader using shared redactor — internal/cli/log_snapshot.go exposes type LogClass string with constants LogClassMain=“main” and LogClassToolAudit=“tool_audit”, type LogSnapshotRoots struct{LogPath, ToolAuditPath string}, type SnapshotOpts struct{HeadBytes, TailBytes int64} (zero or negative HeadBytes/TailBytes default to 641024 and 161024 respectively), type LogSection struct{Class LogClass; Path string; Head, Tail []byte; Missing, Truncated bool; Redacted int; Unreadable string} and type Snapshot struct{Sections []LogSection}. SnapshotLogs(roots LogSnapshotRoots, opts SnapshotOpts) Snapshot returns Sections in order [LogClassMain, LogClassToolAudit] and ALWAYS includes both sections even when both files are absent. For each path: if os.Stat fails with os.IsNotExist, set Missing=true and skip read. If os.ReadFile fails for any other reason, set Unreadable to err.Error() and continue. If file size <= HeadBytes+TailBytes, set Head to the entire file content, Tail to nil, Truncated=false. If file size > HeadBytes+TailBytes, set Head to the first HeadBytes bytes, Tail to the last TailBytes bytes, Truncated=true. Then split each of Head and Tail by ‘\n’ (bytes.Split), pass each non-empty line through RedactLine, sum the returned counts into LogSection.Redacted, and write the redacted lines back joined by ‘\n’ into Head/Tail. The function never returns an error and never panics. | validated | tools | small | operator, system | internal/cli/log_snapshot_test.go | Per-class results carry Missing, Truncated, Redacted, Unreadable so doctor/status can render evidence without failing. |
| 5 / 5.O | Hermes config.yaml model/provider runtime bridge — Gormes reads Hermes profile config.yaml model.default and model.provider into runtime inference defaults so gateway/CLI turns preserve Hermes provider selection before higher-precedence Gormes config, env, or flags override it. | validated | tools | small | operator, gateway, system | internal/config/config_test.go::TestLoad_HermesConfigYAMLModelProviderParity | If model.provider is absent, provider auto-detect remains explicit; if a higher-precedence Gormes env/flag override is set, it wins without mutating Hermes config.yaml. |
| 5 / 5.O | Interactive Onboarding — Keep the first-run readiness status and deterministic wizard plan as an internal onboarding helper: model/provider selection -> auth setup -> gateway channel configuration -> browser/CDP checks -> skill discovery -> dashboard launch. Public CLI entrypoints are gormes setup for guided setup and gormes doctor --offline --target <target> --json for machine-readable readiness; top-level gormes onboard is not registered. | validated | tools | medium | operator | cmd/gormes/skills_onboard_test.go::TestOnboardExplainsRuntimeSkillsAndLearningState,TestOnboardShowsConfiguredProviderDetails,TestOnboardWizardNonInteractiveShowsOrderedPlanAndSkipWarnings,TestOnboardWizardPrefillsConfiguredProviderAndAuth; internal/cli/onboard_test.go::TestOnboardPlanWalksFirstRunWizardStepsWithSkipWarnings,TestOnboardPlanPrefillsConfiguredRuntime; future full wizard prompts remain row-backed | The internal onboarding helper reports first-run status, runtime skills root, bundled/local skill counts, and partial learning-loop state with next-step commands. Public setup/doctor surfaces report missing provider credentials, gateway config gaps, or browser unavailability per step and keep skip warnings explicit. |
| 5 / 5.O | Internal onboarding interactive action runner — The internal onboarding wizard seam turns the existing deterministic first-run plan into an action runner for tests and setup integration. It renders the same model -> provider -> auth -> gateway -> browser/CDP -> skills -> dashboard steps, shows each step’s configured/missing status and skip warning before any action, and lets the operator run, skip, or review each step through injected seams. Public CLI must not register top-level gormes onboard; selected actions delegate through fakeable command seams to existing setup/model/auth/gateway/browser/skills/dashboard surfaces. | validated | tools | medium | operator, system | cmd/gormes/onboard_wizard_test.go; internal/cli/onboard_test.go::TestOnboardPlan* | Non-TTY or --non-interactive invocations keep the current deterministic plan output and do not prompt. If an action seam is unavailable, the wizard returns onboard_action_row_backed with the step id and recommended command while preserving the remaining plan. |
| 5 / 5.O | CLI setup/onboard/help text fidelity matrix — Add a deterministic operator-visible text matrix for gormes setup, setup entry modes, setup sections, internal onboarding helper output, removed gormes onboard guidance, provider/model setup guidance, root help, unknown-command suggestions, and non-TTY fallbacks. The matrix must keep Hermes-compatible setup behavior where upstream owns the flow, preserve Gormes-owned first-run readiness wording where it intentionally differs, and prevent regressions such as parent setup arguments leaking into delegated model/provider commands, arrow-key escape bytes being treated as selections, Hermes paths appearing in Gormes summaries, or interactive-only navigation copy appearing in line-input fallback mode. | validated | tools | medium | operator, system | cmd/gormes/setup_text_fidelity_test.go::TestSetupTextFidelity_* + cmd/gormes/onboard_wizard_test.go | When a setup/onboarding-helper action is not wired yet, output must include a row-backed command name and actionable next step instead of panic text, raw Cobra errors, or silent no-ops. |
| 5 / 5.O | Hermes CLI alias and suggestion fidelity matrix — Add deterministic fixtures for Hermes-visible command alias, prefix expansion, quick-command alias, and unknown/ambiguous-command guidance across the native CLI, gateway slash dispatcher, and native TUI slash handler. The matrix must prove aliases canonicalize before dispatch and hooks, exact commands win over longer prefix matches, unique prefixes preserve arguments, ambiguous prefixes produce bounded suggestions, quick-command aliases preserve user arguments without recursion loops, and unknown commands show actionable help instead of falling through to the model or silently executing. Gormes-owned typo redirects such as gormes migrate ooenclaw remain explicit suggestions, not silent Hermes aliases; gormes login is removed-command guidance to gormes auth add <provider> --type oauth rather than an alias or command. | validated | tools | small | operator, system | cmd/gormes/command_alias_fidelity_test.go::TestHermesCommandAliasFidelity_* + internal/cli/command_alias_test.go + internal/gateway/commands_test.go::TestGatewayCommandAliasFidelity_* | Unknown command, unsupported alias target, ambiguous prefix, and quick-command alias cycle paths emit deterministic guidance with nonzero/handled status as appropriate; no path submits the raw slash/top-level command to the agent kernel as ordinary prompt text. |
| 5 / 5.O | Logs Command — Port openclaw logs as gormes logs: tail gateway file logs, support follow mode (-f), filter by level, and stream via RPC if gateway is remote. | validated | tools | small | operator | internal/cli/logs_test.go | Missing log file, permission denied, or remote gateway unreachable reports logs_unavailable with root cause. |
| 5 / 5.O | Gateway planned stop marker + WSL systemd PATH parity — Gateway stop/service control distinguishes intentional operator stops from unexpected service deaths with a planned-stop marker, and generated systemd units preserve WSL interop PATH entries for Windows command bridges only when running under WSL. | validated | gateway | small | operator, gateway, system | cmd/gormes/gateway_mutating_unavailable_test.go; internal/gateway/planned_stop_test.go; internal/cli/service_restart_test.go | Gateway status reports planned_stop_marker_unavailable, planned_stop_marker_stale, or wsl_interop_path_unavailable instead of treating every SIGTERM as a crash-loop trigger or stripping Windows interop commands from WSL service units. |
| 5 / 5.O | Gateway stale-code self-check uses git HEAD SHA — Gormes gateway stale-code detection compares the gateway boot commit against the current git HEAD SHA instead of source-file mtimes, so agent edits to tracked files do not trigger stale-code restart guidance while a real git pull/checkout that advances HEAD still does. | validated | gateway | small | operator, gateway, system | internal/gateway/stale_code_test.go; cmd/gormes/gateway_status_test.go | Non-git installs, unreadable git metadata, missing packed refs, or disappearing worktrees return stale_code_git_unavailable evidence and skip restart advice instead of crashing or falling back to mtime false positives. |
| 5 / 5.O | Agent lifecycle hooks (agent:start, agent:step, agent:end) — Gormes gateway hook system fires agent:start, agent:step, and agent:end HookPoints around kernel turn processing, mirroring Hermes’ gateway/hooks.py agent lifecycle event types at lines 9-17 (event definitions) and lines 35-210 (HookRegistry class with emit/emit_collect, wildcard matching, async/sync handler support, and error isolation). agent:start fires when the kernel begins processing a message (Hermes dispatch: gateway/run.py lines 6734-6741, context keys: platform, user_id, session_id, message); agent:step fires after each tool-calling turn iteration (Hermes dispatch: gateway/run.py lines 13585-13614 via step_callback bridge, context keys: platform, user_id, session_id, iteration, tool_names); agent:end fires when the kernel completes processing (Hermes dispatch: gateway/run.py lines 6876-6880, context keys: platform, user_id, session_id, message, response). Hook errors during agent lifecycle events are logged but do not abort the turn, matching Hermes’ try/except-per-handler pattern at hooks.py lines 174-181. | validated | gateway | small | system | - | - |
| 5 / 5.O | Nous OAuth device code + refresh token + agent key provisioning — Gormes ports Hermes’ Nous OAuth device code login, refresh token rotation, and agent key minting pipeline. The credential_pool.go already holds the persistence schema (NousOAuthCredentials struct, SaveNousOAuthCredentials), but no actual OAuth handshake or token refresh logic exists. This row adds: (1) browser-based device code login flow matching Hermes’ _nous_device_code_login, (2) refresh token rotation via X-Nous-Refresh-Token header matching _refresh_access_token, (3) short-lived (24h) agent API key minting from portal matching _mint_agent_key, and (4) runtime credential resolution orchestrating refresh→mint with retry on stale access tokens matching resolve_nous_runtime_credentials. | validated | provider | medium | system | internal/llm/nous_oauth_test.go | When the Nous OAuth endpoint is unreachable or the refresh token is invalid, operators see a classified OAuth error (device_code_expired, refresh_token_revoked, agent_key_minting_failed) and are guided to re-run auth add nous. |
| 5 / 5.O | Hermes send command stdin/file payload parity — gormes send preserves Hermes hermes send behavior for stdin/file payload decoding, binary/invalid-text rejection, newline preservation, session targeting, dry/no-agent modes, and TUI resume safety without leaking raw control sequences into terminal output. | validated | orchestrator | small | operator, system | cmd/gormes send command tests against Hermes send_cmd fixtures | When no standalone platform delivery backend is configured, gormes send fails closed with send_backend_unavailable instead of pretending a message was sent; --dry-run remains available for scripted payload validation without provider, gateway, or TUI startup. |
| 5 / 5.O | Hermes session recap command surface — Port Hermes’ session recap command as a Gormes-native read-only session summarizer over local session/transcript storage, preserving output modes, missing-session diagnostics, and provider-free degraded behavior. | validated | orchestrator | small | - | cmd/gormes session recap fixtures | - |
| 5 / 5.O | Profile workspace allow-list enforcement policy — Make agents.defaults.workspaces the Gormes-owned profile workspace allow-list, not just setup metadata. With an empty list, the default project workspace is the operator home. With a non-empty list, model-facing project read/write access is restricted to the normalized listed roots. Runtime internals may access the active profile root (GORMES_HOME) for config, auth, sessions, memory, skills, logs, cron, and gateway state, but model-facing tools must not treat the whole profile root as a project workspace. Model-facing profile edits are limited to explicit profile-owned content: identity files (SOUL.md, IDENTITY.md when present) and the active profile skills/ directory. Profile-local home/ is subprocess HOME/runtime state, not a broad project workspace. Sibling profiles, arbitrary operator-home paths, .env, auth.json, session/memory databases, logs, and other runtime state are denied as project paths. File tools, local/project execute_code, and coding-agent delegation must share one resolver. Local terminal must use a tested sandbox-capable backend for allow-listed roots or fail closed; merely setting cwd is not accepted as confinement. | validated | tools | medium | operator, system | Temp GORMES_HOME with a named profile containing agents.defaults.workspaces = [", plus active-profile SOUL.md/IDENTITY.md/skills fixtures, profile secret/runtime-state fixtures, sibling-profile fixtures, and outside-root fixtures. | If agents.defaults.workspaces is empty, the project workspace policy defaults to the operator home for compatibility. If the list is non-empty and the local terminal backend cannot provide real confinement, terminal commands fail closed with profile_workspace_scope_violation instead of pretending cwd is a sandbox; path-aware tools still enforce the project allow-list plus explicit profile-owned editable content. |
| 5 / 5.O | Profile-local subprocess HOME parity — Port Hermes’ profile-local subprocess HOME semantics for Gormes local shell execution. New Gormes profiles create a home/ directory under the profile root, and local shell tools that spawn subprocesses use that directory as HOME when the active GORMES_HOME/home exists. This matches Hermes’ git/ssh/gh/npm credential isolation without changing the process working directory, without changing the separate profile workspace allow-list policy, and without shipping wrapper aliases in this slice. | validated | tools | medium | operator, system | Temp GORMES_HOME with profiles/worker/home plus terminal and execute_code commands that print $HOME. | If the active profile has no home/ directory, subprocess env falls back to the operator HOME exactly as today. Remote/container backends and row-backed wrapper alias commands remain unchanged. |
| 5 / 5.O | Long-term plan: profile fleet supervisor and single control-plane gateway — Define Gormes’ long-term profile-fleet runtime so operators get one control surface for all named profiles while preserving Hermes-compatible profile state separation. The near-term per-profile gateway services remain a compatibility bridge; the target is a fleet supervisor that can enumerate configured profiles, start/stop/restart profile-scoped workers or a proven profile-scoped in-process equivalent, validate token ownership, surface per-profile health, and coordinate update/restart-all flows without sharing config, auth, sessions, memory, tool state, or kernels across profiles. | validated | orchestrator | large | operator, gateway, system | internal/gateway/fleet_supervisor_test.go; cmd/gormes/gateway_fleet_test.go | If fleet supervision is unavailable, Gormes must keep the Hermes-compatible per-profile service/process bridge and report exact per-profile service state instead of collapsing profiles into the default GORMES_HOME. |
| 5 / 5.O | CLI module contract registry and manifest gate — Add an importable Gormes CLI contract registry outside cmd/gormes that declares approved module ownership for visible Cobra command roots, setup sections, and slash commands; the live cmd/gormes tree must derive an exact command-path manifest from this registry and fail tests when any command, setup section, or slash command lacks a feature-module owner. | validated | tools | small | operator, gateway, system | internal/cli/gormescli/contract_test.go and cmd/gormes/cli_contract_manifest_test.go | Unknown or unowned command/setup/slash surfaces fail closed in tests with bounded ownership errors instead of drifting inside cmd/gormes without a module contract. |
| 5 / 5.O | cmd/gormes profile command package extraction — Move the profile command surface from cmd/gormes into an importable feature-owned gormescli profile module while preserving every existing command, flag, JSON output, setup interaction, exit-code intent, and profile test fixture. | validated | tools | small | operator, system | internal/cli/gormescli/modules/profiles/profile_command_test.go plus existing cmd/gormes profile command tests | If the extracted module cannot receive binary build provenance, cmd/gormes injects it through module options; row-backed lifecycle commands still return an ExitCode-capable error without importing cmd/gormes. |
| 5 / 5.O | cmd/gormes setup section registry extraction — Replace the global cmd/gormes setup section list with an importable gormescli setup registry validated by the module contract gate while preserving section order, boxed chrome, and existing setup section behavior. | validated | tools | small | operator, system | internal/cli/gormescli/setup_registry_test.go and cmd/gormes/cli_contract_manifest_test.go | Setup sections without a valid feature-module owner fail the contract test before they can drift inside cmd/gormes. |
| 5 / 5.O | cmd/gormes provider usage command package extraction — Move the gormes usage provider account-usage command from cmd/gormes into an importable providers module while preserving flags, JSON shape, provider inference, bounded HTTP timeout, credential redaction, and gateway helper compatibility. | validated | provider | small | operator, gateway, system | internal/cli/gormescli/modules/providers/usage_command_test.go plus cmd/gormes/usage_command_test.go | If account usage is unsupported or credentials are absent, the provider module returns the existing unavailable snapshot without live TUI startup or credential leakage. |
| 5 / 5.O | cmd/gormes provider command surface package extraction — Move provider-owned command construction for auth, logout, model, fallback, usage, and insights from cmd/gormes into an importable providers module while preserving public command spelling, flags, JSON shapes, exit codes, credential redaction, provider setup ownership, and existing root-command behavior through thin cmd/gormes adapters. | validated | provider | medium | operator, gateway, system | internal/cli/gormescli/modules/providers/*_command_test.go plus existing cmd/gormes auth/logout/model/fallback/usage fixtures | Provider command construction is module-owned; deeper auth/OAuth runtime and shared model prompt helpers remain explicit cmd/gormes seams until separate runtime/TUI rows move them. |
| 5 / 5.O | cmd/gormes gateway row-backed command package extraction — Move row-backed gateway-adjacent command trees for webhook, hooks, and pairing into an importable gateway module while preserving command spelling, aliases, destructive flags, row-backed JSON shape, build provenance, and exit-code behavior. | validated | gateway | small | operator, gateway | internal/cli/gormescli/modules/gateway/rowbacked_test.go plus cmd/gormes Hermes row-backed parity tests | Commands remain row-backed with structured unavailable JSON and exit code 2 until the underlying gateway management rows ship. |
| 5 / 5.O | cmd/gormes channels capabilities command package extraction — Move gormes channels capabilities command construction and rendering into an importable channels module while preserving configured-state redaction, unknown-channel exit code, human output, JSON shape, and build provenance. | validated | gateway | small | operator, gateway | internal/cli/gormescli/modules/channels/capabilities_test.go plus cmd/gormes/channels_capabilities_test.go | Unknown channels still return exit code 1 with typed unknown_channel evidence; configured details are supplied through cmd/gormes seams until channel runtime modules own each platform’s config summary. |
| 5 / 5.O | cmd/gormes live gateway command package extraction — Move live gateway, dashboard, and agent command construction into the importable gateway module while preserving gateway lifecycle behavior, dashboard startup behavior, agent registry JSON shapes, command spelling, flags, and exit-code behavior. | validated | gateway | medium | operator, gateway | internal/cli/gormescli/modules/gateway live/dashboard/agent tests plus cmd/gormes gateway/dashboard/agent focused tests | Live gateway manager, dashboard HTTP server, and dynamic-agent registry runtimes remain in cmd/gormes seams; only command construction and typed option parsing moved. |
| 5 / 5.O | cmd/gormes channel service command package extraction — Move slack, whatsapp, and telegram top-level service command construction into the importable channels module while preserving Slack manifest behavior, WhatsApp pairing flags/output, Telegram runtime startup behavior, command spelling, flags, JSON shapes, and exit-code behavior. | validated | gateway | medium | operator, gateway | internal/cli/gormescli/modules/channels slack/whatsapp/telegram tests plus cmd/gormes Slack/WhatsApp/Telegram focused tests | Slack manifest generation, WhatsApp pairing runtime, and Telegram long-poll runtime remain in cmd/gormes seams; only command construction and typed option parsing moved. |
| 5 / 5.O | cmd/gormes root command assembly extraction — Move Cobra root command assembly, root flags, root help text, and canonical top-level command ordering into internal/cli/gormescli while leaving cmd/gormes/main.go responsible for process entry, panic dump, exit-code mapping, ldflags/build values, and runtime seam factories. | validated | tools | medium | operator | internal/cli/gormescli/root_test.go plus cmd/gormes root/help/parity tests | cmd/gormes still owns runtime-specific rootRuntime defaults and command factory injection until deeper runtime modules exist. |
| 5 / 5.O | Root config.toml v2 profile service schema — Add typed internal/config support for the v2 root profile schema in $GORMES_HOME/config.toml: config_version = 2, [profiles.<id>] map entries, seed [profiles.main] enabled=true name="" on fresh install/migrate, no active_profile or default_profile config keys, stable id validation for <id>, mutable display name, per-profile workspaces/settings/providers/channels references, and a global [credentials.<id>] registry whose secret values are represented only by SecretRef/env/file/exec references. All enabled=true profiles are active service declarations. | validated | tools | medium | operator, system | internal/config/profile_config_v2_test.go | If v2 schema is absent, config loading keeps the legacy v1 fallback read path and reports profile_config_v2_unavailable readiness evidence without writing per-profile canonical config. |
| 5 / 5.O | Legacy profile config v2 migration planner — Add an internal/config migration planner/apply path that converts legacy Gormes profile state into the v2 single-root schema with a previewable diff. Inputs include top-level v1 [hermes], [telegram], [discord], [slack], [agents.defaults], legacy $GORMES_HOME/active_profile, and existing $GORMES_HOME/profiles/<id>/config.toml files. Output is one $GORMES_HOME/config.toml with [profiles.<id>], [credentials.<id>], per-profile provider/channel references, and migration notes; profile directories remain runtime data homes only. | validated | tools | medium | operator, system | internal/config/profile_migration_v2_test.go | If migration cannot classify a legacy secret or profile file, the planner leaves it unmigrated with typed manual_action_required evidence and never deletes the source. |
| 5 / 5.O | Profile Control Center read model — Move profile setup state projection out of cmd/gormes into internal/cli/gormescli/modules/profiles as a pure read model for a Profile Control Center. The model enumerates all enabled and disabled v2 profiles, shows profile id, display name status, Runtime/Readiness/Activity lanes, workspaces, provider readiness, channel readiness, credential ownership/share warnings, legacy migration warnings, and action availability from a typed catalog. It must not require a live gateway, provider, channel token, or Navivox server. | validated | tools | small | operator, system | internal/cli/gormescli/modules/profiles/control_center_model_test.go | If v2 config is missing, the read model surfaces legacy_config_detected/migration_available rows instead of pretending the old per-profile config shape is current. |
| 5 / 5.O | Profile Control Center TUI shell and draft apply flow — Replace the current narrow gormes setup profiles flow with a screen-reader-first Profile Control Center TUI over the read model. First screen lists every profile service with status lanes; detail view exposes workspaces, providers, channels, credentials, and migration state; edits are staged as a draft, summarized as a redacted diff, and only written on explicit Apply. Add-profile creates a staged stable id plus display name; naming the seed main profile is a staged edit. Slice excludes live start/stop/reset/restart, arbitrary command launchers, and secret echoing. | validated | tui | medium | operator, system | internal/cli/gormescli/modules/profiles/control_center_tui_test.go | If terminal capabilities are limited, the TUI falls back to deterministic list/input screens with the same read-model labels and draft/apply semantics. |
| 5 / 5.O | Per-profile provider credential readiness — Add provider setup/readiness for v2 profiles: each [profiles.<id>.providers.<provider>] block references an explicit credential id, default model, optional endpoint/base URL, and allowed model list; each [credentials.<id>] provider credential records kind/provider/owner_profile/secret_ref. Setup must never ask for provider credentials globally when editing a profile, must not silently share credentials across profiles, and must surface provider catalog/model-picker readiness so OpenRouter and other aggregators show their available model set instead of a truncated static list. | validated | provider | medium | operator, gateway, system | internal/provider/profile_provider_config_test.go | When a provider catalog cannot be fetched or a credential is missing, readiness returns provider_models_unavailable or provider_credential_missing and keeps manual model entry available without live token use. |
| 5 / 5.O | Per-profile channel credential readiness and allow-lists — Add channel setup/readiness for v2 profiles: each [profiles.<id>.channels.<channel>] block references an explicit credential id, enabled flag, channel-specific allow-lists, mention/thread/tool-progress settings, and optional runtime labels; each channel credential in [credentials.<id>] records kind/channel/owner_profile/secret_ref. Telegram, Slack, Discord, WhatsApp, and future channel adapters must resolve credentials through the profile/channel binding rather than top-level global token fields, while legacy top-level channel config remains a fallback/migration input. | validated | gateway | medium | operator, gateway, system | internal/gateway/profile_channel_config_test.go | When a profile channel lacks a credential, the gateway skips only that profile-channel binding with channel_credential_missing evidence and leaves other profile services available. |
| 5 / 5.O | Gormes setup providers plural alias — gormes setup providers is accepted as a plural operator alias for the existing gormes setup provider section. The alias must route through the same provider setup section, including the boxed setup header, non-interactive GORMES_ENDPOINT/GORMES_API_KEY flow, secret redaction, config writes, and existing unsupported-section behavior for genuinely unknown sections. | validated | tools | small | operator, system | cmd/gormes/setup_minimal_test.go::TestSetupProvidersAliasRunsProviderSection | Without the alias, operators who naturally type gormes setup providers get setup_section_unsupported: providers even though the provider setup flow exists and the help text repeatedly references provider configuration. |
| 5 / 5.O | Root tools command config-backed toolset toggles — Replace the row-backed unavailable root gormes tools stubs with a narrow Hermes-compatible operator CLI for tools list, tools enable <name...>, and tools disable <name...> on the CLI platform toolset selection. The command must reuse the existing setup/TUI toolset config machinery, persist platform_toolsets.cli without dropping unrelated config, print deterministic enabled/disabled/session-reset evidence, and keep gateway/channel /tools slash handling classified separately until a dedicated gateway row ports that surface. | validated | tools | small | operator, system | cmd/gormes/tools_command_test.go::TestToolsCommandListShowsRuntimeToolsets; cmd/gormes/tools_command_test.go::TestToolsCommandEnableDisablePersistsCLISelection | If the tool config cannot be loaded or a requested toolset/MCP server is unknown, the command returns bounded operator-facing evidence and must not call providers, mutate live sessions, or silently enable arbitrary tool names. Gateway/channel /tools remains unavailable until separately row-backed. |
| 5 / 5.P | OCI image — Gormes ships an OCI image contract that mirrors upstream Docker entrypoint/config volume operational behavior while proving the final image contains the Go binary and no required Python runtime path. | validated | docs | small | operator, system | docs/install/oci_image_test.go | Container smoke tests run offline and report missing binary, missing config volume, or Python-runtime dependency evidence without contacting registries or providers. |
| 5 / 5.P | Homebrew — Gormes ports Hermes Homebrew/release artifact expectations into a Go-native formula fixture with version, checksum, binary install layout, and doctor smoke contract. | validated | docs | small | operator, system | docs/install/homebrew_formula_test.go | Formula validation reports missing artifact, checksum, binary layout, or doctor smoke evidence instead of publishing an untestable tap update. |
| 5 / 5.P | Nix flake package and NixOS module contract — Gormes ports Hermes’ Nix/flake packaging surface into a Go-native fixture: packaging/nix/flake.nix exposes a static buildGoModule package for linux/darwin systems, a dev shell and formatter, and a minimal nixosModules.default service contract for gormes gateway/doctor operation without Python, uv2nix, Node, or hosted Honcho service dependencies. | validated | docs | small | operator, system | webpages/docs/install/nix_flake_test.go::TestNixFlakeContract | If live Nix is unavailable, fixture tests still catch stale Hermes Python flake inputs, invalid manual GOOS/GOARCH mappings, missing service options, or hidden hosted-Honcho dependencies before operators rely on the package. |
| 5 / 5.P | Unix installer (install.sh) source-backed update flow — The Unix installer mirrors Hermes’ source-backed user install experience for Gormes: curl-pipe runs clone or update a managed checkout, build the Go binary, publish the command, verify it, and rerun as an update flow without Python/Node runtime installs. | validated | docs | small | operator, system | webpages/landing/legacy/go-renderer/internal/site/install_unix_test.go | If the managed checkout already exists, install.sh updates it in place with git fetch/checkout/pull and reports the checkout as the install source instead of silently downloading a release artifact or cloning a temporary tree. |
| 5 / 5.P | Unix installer root/FHS layout policy — The Unix installer has an explicit tested policy for root Linux installs, legacy user installs, Termux, and explicit —dir/—bin-dir overrides | validated | docs | small | operator, system | webpages/landing/legacy/go-renderer/internal/site/install_unix_test.go | Installer help and dry-run output explain when Gormes uses user-scoped layout, FHS root layout, or refuses a root-owned path instead of silently publishing an unreachable binary. |
| 5 / 5.P | Windows installer (install.ps1 + install.cmd) parity — Gormes exposes a native Windows install path with install.ps1 plus install.cmd: the script supports one-line and inspect-first PowerShell entrypoints, managed checkout/update, user PATH publication, managed Go fallback, dry-run planning, gateway restart policy, offline verification, and public docs that describe the Windows-specific behavior without copying Hermes’ Python/uv/Node installer claims. | validated | docs | small | operator, system | webpages/landing/legacy/go-renderer/internal/site/install_pwsh_test.go; docs/windows_native_guide_test.go | If the PowerShell installer or docs drift, Windows operators either follow stale WSL-only guidance, miss the native PATH/update controls, or believe install.ps1 runs setup automatically when it only verifies the offline doctor. |
| 5 / 5.P | Installer script serving and MIME validation — Wire Unix install.sh plus Windows install.ps1 and install.cmd into the Go renderer site server with correct Content-Type headers, cache-control, and static export. Tests verify the Unix script is loaded from the repo root, Windows scripts are embedded, and every installer is served/static-exported with safe MIME and cache headers. | validated | docs | small | operator, system | webpages/landing/legacy/go-renderer/internal/site/assets_test.go | - |
| 5 / 5.P | Install isolation: GORMES_BIN_DIR is an authoritative sandbox boundary — When GORMES_BIN_DIR (or --bin-dir) is set to a directory the operator intends as a sandbox, install.sh must publish the gormes command only into that directory and must NOT also rewrite an existing gormes binary discovered elsewhere on PATH (typically ~/.local/bin/gormes). The current behavior emits updating active PATH command /home/<user>/.local/bin/gormes and replaces a production symlink with one pointing at the sandbox, which dangles when the sandbox dir is reaped. Sandbox passes must be observably side-effect-free outside the sandbox prefix. | validated | tools | small | operator, system | internal/installtest/iso_bin_dir_test.go | Without this fix, any operator running install.sh with a sandbox bin dir (e.g., for testing, in a container, in CI) silently mutates the host’s production gormes symlink. The gormes-install skill flags this as iso-bin-hijack on every sandbox pass. |
| 5 / 5.P | Install isolation: skip shell-rc PATH write when bin dir is under /tmp — When the operator declares a sandbox boundary by setting GORMES_BIN_DIR or GORMES_PREFIX, install.sh must not write export PATH=... lines into the operator’s shell rc files (~/.bashrc, ~/.profile, ~/.zshrc, ~/.zprofile, fish config). The same sandbox_bin_dir_set helper that gates the iso-bin-hijack fix is reused. ensure_path_in_shell_config still exports the sandbox bin dir into the current install run’s PATH so downstream verification works, but persistent rc-file edits are skipped. print_install_plan_body and print_verbose_plan surface the decision as edit_shell_rc_files: skipped|yes. | validated | tools | small | operator, system | internal/installtest/iso_shellrc_test.go | Without this fix, sandbox install passes leave permanent dangling PATH entries in the operator’s login shell config. The gormes-install skill flags this as iso-shellrc-leak on every sandbox pass. |
| 5 / 5.P | Install isolation: skip system service install when sandbox bin dir is set — When the operator declares a sandbox boundary by setting GORMES_BIN_DIR or GORMES_PREFIX, install.sh must not write to the production user systemd unit dir (install_system_service: skipped|yes. | validated | tools | small | operator, system | internal/installtest/iso_systemd_dir_test.go | Without this fix, every sandboxed install pass on a Linux host with systemd-user available silently rewrites the operator’s production gormes-gateway.service to point ExecStart at /tmp/gormes-install-test/ |
| 5 / 5.P | Install: prefer pre-built release binary over source build by default — install.sh defaults to fetching the pre-built release tarball from GitHub Releases (gormes-install_method: binary-fetch|source-build (<reason>). Source-build is used when: —local is set, —from-source / GORMES_INSTALL_FROM_SOURCE=1, —branch is non-default (release binaries only published from main), or the host platform (uname -s/-m) has no published asset slug. On binary-fetch failure (network, missing asset, SHA-256 mismatch, missing tools), install.sh falls back to source-build with a clear log line so installs still succeed in restricted environments. | validated | tools | small | operator, system | internal/installtest/install_method_test.go; internal/installtest/local_cli_e2e_test.go; internal/installtest/public_install_command_test.go | Without this fix, every fresh release install downloads a Go toolchain (~150MB), clones the source repo, and runs go build — typically 2-5 minutes even on a fast machine, and impossible at all on locked-down hosts without a Go compiler or git. With the fix, the same fresh install completes in ~2 seconds via signed binary fetch. |
| 5 / 5.P | Install: Termux publishes a real $PREFIX/bin binary, not an $HOME-targeting symlink — On Termux/Android the installer publishes $PREFIX/bin/gormes as a real, directly-executable file, never a symlink whose target resolves under the app-private writable $HOME (/data/data/com.termux/files/home/.gormes/bin/gormes). The pre-fix failure was in install.sh:publish_built_binary: the symlink-preferred path could make $PREFIX/bin/gormes resolve to $HOME/.gormes/bin/gormes, causing Android 10+ exec restrictions to reject the post-publish verify and roll back the command. The fixed path copies the real binary into $PREFIX/bin whenever is_termux is true, while keeping the release build, android-arm64 asset, managed_bin_dir bookkeeping, and non-Termux symlink behavior unchanged. | validated | tools | small | - | internal/installtest/termux_publish_test.go | On non-Termux hosts the existing symlink-preferred publish path is unchanged. If the Termux copy cannot be written to $PREFIX/bin, install.sh fails with the existing not-writable guidance rather than silently falling back to a broken $HOME symlink. |
| 5 / 5.P | Termux exec argv path-alias sanitizer — On Termux/Android, Gormes strips the executable path that termux-exec injects into argv even when the injected argument is reported through /data/data/com.termux/... and os.Executable() reports the same binary through Android’s /data/user/0/com.termux/... alias. gormes version, gormes --version, and installer publish verification must not route the injected path as an unknown Cobra subcommand. | validated | tools | small | operator, system | cmd/gormes/termux_exec_args_test.go::TestSanitizeTermuxExecArgsWithExe | Without the alias match, affected Termux devices can still receive unknown command "/data/data/com.termux/files/usr/bin/gormes" for "gormes" during gormes version or post-publish verification even when the android-arm64 binary and $PREFIX/bin copy are otherwise valid. |
| 5 / 5.P | Termux binary-fetch publish verification source fallback — When the release-binary path succeeds through download, checksum, extract, and copy publication but the published command fails gormes version on Termux, install.sh treats that as a binary-fetch runtime failure and falls back to source-build once. This recovers the latest-release transcript where the v0.2.20 android-arm64 binary was copied into $PREFIX/bin/gormes but failed verification under termux-exec argv injection, leaving a rolled-back command. | validated | tools | small | operator, system | internal/installtest/termux_publish_recovery_test.go::TestTermuxBinaryFetchPublishVerificationFallsBackToSourceBuild | If the source-build fallback cannot run because git, Go, or network access is unavailable, the installer exits with the existing source-prerequisite failure instead of silently accepting a non-runnable Termux release binary. Non-Termux publish verification behavior stays fail-fast. |
| 5 / 5.Q | TUI gateway tool-progress mode normalizer — Pure helper internal/tuigateway/tool_progress_mode.go exposes NormalizeToolProgressMode(raw any) string returning one of {off, new, all, verbose}. Mirrors upstream tui_gateway/server.py:_load_tool_progress_mode (line 661). Boolean false → off, boolean true → all, string is lowercased and trimmed; any value outside the recognised set defaults to all. No config file reads, no session state. | validated | gateway | small | operator, gateway | internal/tuigateway/tool_progress_mode_test.go | TUI gateway falls back to all-progress until this normalizer is fixture-backed. |
| 5 / 5.Q | TUI gateway completion path normalizer — Pure helper internal/tuigateway/completion_path.go exposes NormalizeCompletionPath(in string, isWindows bool) string. Mirrors upstream tui_gateway/server.py:_normalize_completion_path (line 428): expands a leading ~ to the operator’s home dir (caller injects the home value via a small stubbable hook), and on non-Windows callers also rewrites Windows-style C:/foo into /mnt/c/foo. No filesystem stat, no os.UserHomeDir inside the helper — caller threads a HomeFn or homeDir string. | validated | gateway | small | operator, gateway | internal/tuigateway/completion_path_test.go | Remote-TUI completion menus echo the raw path until this normalizer is fixture-backed. |
| 5 / 5.Q | TUI gateway tool summary formatter — Pure helper internal/tuigateway/tool_summary.go exposes FormatToolSummary(name string, resultJSON []byte, durationSeconds *float64) (string, bool). Mirrors upstream tui_gateway/server.py:_tool_summary (line 996): for web_search returns ‘Did N searches’ (or ‘Did 1 search’); for web_extract returns ‘Extracted N pages’; for any other tool returns ‘Completed’ if duration is set, else ("", false). The duration suffix ’ in 1.2s’ is appended whenever durationSeconds is non-nil. Caller decides whether to emit; this helper never logs or writes. | validated | gateway | small | operator, gateway | internal/tuigateway/tool_summary_test.go | TUI tool-complete bubbles fall back to the raw tool name until this formatter is fixture-backed. |
| 5 / 5.Q | TUI gateway image/personality/platform-event helpers — Pure TUI gateway media, personality, and platform-event helpers expose stable structs and validation without remote transport | validated | gateway | small | operator, gateway, system | internal/tuigateway/media_personality_event_test.go | Remote TUI image/personality/platform-event features report unavailable until helper validation is fixture-backed. |
| 5 / 5.Q | TUI gateway config health null-section probe — Gormes ports Hermes’ TUI gateway config-health probe as a pure Go helper over decoded config maps: bare YAML/null top-level sections are reported with actionable empty-section guidance, display.personality with null or empty agent.personalities reports that the personality overlay will be skipped, and agent/session construction paths tolerate nil agent/display/personality maps without panics or unintended overlay activation. | validated | gateway | small | operator, gateway, system | internal/tuigateway/config_health_test.go | Null config sections, null personality maps, invalid personality choices, and unavailable config-health probes return tui_config_health_warning or tui_personality_skipped evidence; the helper must not mutate config, clear history, read live config files, or print raw system prompts. |
| 5 / 5.Q | TUI mouse tracking config + slash toggle — Mouse/wheel tracking is config-backed, runtime-toggleable, and emits terminal enable/disable state without restarting the TUI | validated | gateway | small | operator, system | internal/tui/mouse_tracking_test.go | TUI status reports mouse tracking unavailable or disabled instead of leaving terminal mouse mode stale. |
| 5 / 5.Q | Native TUI bundle independence check — Gormes TUI startup and install/update status stay Go-native and never depend on Hermes’ Node/Ink dist bundle freshness checks | validated | gateway | small | operator, system | cmd/gormes/tui_bundle_independence_test.go | TUI and doctor/status output report native Go TUI availability instead of asking operators to run npm install/build or repair packages/hermes-ink/dist/ink-bundle.js. |
| 5 / 5.Q | TUI launch model override + static alias resolver — Gormes TUI honors top-level —model/—provider and GORMES_INFERENCE_* overrides at startup without network model lookup or oneshot-only coupling | validated | gateway | small | operator, system | cmd/gormes/tui_model_override_test.go | Startup returns an actionable model/provider ambiguity error instead of silently using stale configured defaults or performing network catalog lookup. |
| 5 / 5.Q | TUI prompt-submit auto-title eligibility helper — Pure helper internal/tui/auto_title.go exposes type AutoTitleInput struct{SessionKey string; FallbackSessionID string; Status string; UserText string; AssistantText string; Interrupted bool; HistoryCount int}, type AutoTitleRequest struct{SessionID string; UserText string; AssistantText string; HistoryCount int}, and BuildAutoTitleRequest(in AutoTitleInput) (AutoTitleRequest, bool). It returns ok=true only when Status==“complete”, Interrupted is false, strings.TrimSpace(UserText) and strings.TrimSpace(AssistantText) are non-empty, and the resolved session ID is non-empty. Resolution prefers strings.TrimSpace(SessionKey) and falls back to strings.TrimSpace(FallbackSessionID). The returned request preserves the original UserText/AssistantText bytes and HistoryCount. No title generation, provider call, DB write, goroutine, clock lookup, or TUI transport change in this slice. | validated | gateway | small | operator, gateway, system | internal/tui/auto_title_test.go::TestBuildAutoTitleRequest | TUI/session status can report auto_title_skipped with reason interrupted, empty_prompt, empty_response, non_complete, or missing_session before a later row wires title generation. |
| 5 / 5.Q | TUI TerminalNativeSelectionHelp constant + help-string fixture — internal/tui declares an exported string constant TerminalNativeSelectionHelp = ‘Selection: use your terminal’s native selection (Shift-drag in most terminals; iTerm Cmd-drag, tmux copy-mode). Gormes does not advertise an in-app copy hotkey.’ and a pure helper SelectionHelpLine() that returns it; one fixture asserts the constant exists, mentions ‘terminal’ but not ‘Cmd+C’/‘Ctrl+C’/‘Ctrl-Shift-C’/‘OSC 52’/‘clipboard hotkey’/‘Ink’, and another asserts no advertised copy shortcut leaks anywhere else in the package | validated | gateway | small | operator | internal/tui/selection_help_test.go | If a future row adds a real Go-native copy mode, it must replace this constant rather than extend it; until then, the help-string fixture prevents accidental advertising of unimplemented Ink shortcuts. |
| 5 / 5.Q | Native TUI slash-command dispatch table — internal/tui exposes a generic slash-command registry — a single dispatch helper checks editor input on Enter, routes any registered slash to a handler that returns (handled bool, statusMessage string, cmd tea.Cmd), and short-circuits the normal kernel.Submit path; the existing /mouse handler migrates onto the registry, and /save registers as an explicit no-op stub that emits save not yet implemented so a follow-up row can wire the export call | validated | tools | small | operator, system | internal/tui/slash_dispatch_test.go | At this prerequisite layer, unregistered slashes still fall through to the caller; the dependent Native TUI Hermes slash dispatch behavioral matrix row owns Hermes-known/unknown slash non-leak behavior. Registered slashes that return handled=false continue submission so a buggy handler can never silently drop a turn. |
| 5 / 5.Q | Native TUI /save canonical session export — Native Bubble Tea TUI replaces the /save no-op stub registered by Native TUI slash-command dispatch table with a handler that calls an injected SessionExportFunc(ctx, sessionID) (path string, err error) which delegates to internal/persistence/transcript.ExportMarkdown over the persisted session DB and renders the resulting file path through StatusMessage — never via the kernel submit path | validated | gateway | small | operator, system | internal/tui/slash_save_test.go | TUI StatusMessage reports save: no conversation, save: no active session, save: store unavailable, or save: write failed: <err> instead of sending /save to the model or writing partial UI-only transcripts; failed writes remove partial files via os.Remove before reporting. |
| 5 / 5.Q | Native TUI /save XDG export helper — cmd/gormes exposes a small helper that builds a tui.SessionExportFunc over config.MemoryDBPath and transcript.ExportMarkdown, writing markdown exports under $XDG_DATA_HOME/gormes/sessions/exports and never under HERMES_HOME or the process working directory | validated | gateway | small | operator, system | cmd/gormes/tui_save_export_test.go::TestTUISaveExportHelper_* | The helper returns store/export/write errors to the TUI save handler so partial-file cleanup can report save failure without falling back to ambiguous paths. |
| 5 / 5.Q | Native TUI /save local runtime binding — cmd/gormes local native TUI startup passes the validated XDG SessionExportFunc through tui.Options{SessionExport: …} while remote TUI startup remains unchanged | validated | gateway | small | operator, system | cmd/gormes/tui_save_binding_test.go::TestTUISaveBinding_* | Local TUI /save reports save: store unavailable or save: write failed: <err> through the existing slash handler instead of exposing an unwired handler; remote TUI startup remains unaffected. |
| 5 / 5.Q | Native TUI /branch session fork + transcript target switch — Native Bubble Tea TUI adds a /branch [name] slash handler using an injected SessionBranchFunc(ctx context.Context, req BranchRequest) (BranchResult, error). BranchRequest carries ParentSessionID, Title, and HistoryCount; BranchResult carries SessionID, ParentSessionID, Title, and TranscriptCopied. The handler refuses empty conversation history, never submits /branch to the kernel, calls the injected function with the current frame SessionID, updates the model’s active SessionID to the returned branch SessionID, clears per-session pending UI state, and ensures subsequent turns persist under the branch session rather than the parent. The backing session/transcript helper copies persisted turns from parent to child and writes session.Metadata with ParentSessionID=parent and LineageKind=session.LineageKindFork; ResolveLineageTip must continue ignoring fork children. | validated | gateway | medium | operator, system | internal/tui/slash_branch_test.go and internal/persistence/session/fork_test.go | TUI StatusMessage reports branch: no conversation, branch: no active session, branch: store unavailable, or branch: fork failed: <err>; failures leave the parent session active and do not copy partial turns. |
| 5 / 5.Q | Native TUI /branch local runtime resident-session binding — Complete the local-runtime half of the native /branch [name] port: cmd/gormes injects a production SessionBranchFunc for local Bubble Tea startup that forks the parent transcript into a fresh child session, persists fork metadata, switches the kernel’s resident session through Kernel.ResumeSession, and replays the visible frame history (falling back to the copied transcript when the frame does not carry history). This closes the gap between the TUI-local branch SessionID and the kernel-owned resident session so following turns run under the branch, not the parent. | validated | tui | small | operator, system | internal/tui/slash_branch_test.go::TestSlashBranch_HappyPathSwitchesSessionIDAndDoesNotSubmit + cmd/gormes/tui_branch_slash_test.go::TestTUIBranchSlashAdapterForksTranscriptAndResumesKernelSession + cmd/gormes/tui_branch_slash_test.go::TestTUIBranchSlashBindingLocalModelReceivesSessionBranchAdapter | If the local branch adapter is missing, /branch returns branch: store unavailable; if sessions.db is locked/corrupt or the Goncho memory database is missing, the handler consumes the slash text and reports branch: fork failed: <error> while leaving the parent active. A nil resume seam reports kernel resume unavailable; remote/plain TUI constructors keep SessionBranch nil and never open local state. |
| 5 / 5.Q | Native TUI resident session-switch replay helper — Consolidate the shared local-runtime mechanics used by native TUI /resume and /branch: resolving command-specific policy remains in each slash adapter, while transcript-to-Hermes replay, visible-history cloning, nil-resume degradation, root-context fallback, and Kernel.ResumeSession invocation live behind one package-local cmd/gormes resident session-switch helper. This preserves the public slash contracts while reducing the persistence/replay interface burden before additional session-switch commands build on the same seam. | validated | tui | small | operator, system | cmd/gormes/tui_sessions_slash_test.go::TestTUIResumeSlashBindingLocalModelReceivesSessionResumeAdapter + cmd/gormes/tui_branch_slash_test.go::TestTUIBranchSlashAdapterForksTranscriptAndResumesKernelSession + cmd/gormes/tui_branch_slash_test.go::TestTUIBranchSlashAdapterFallsBackToCopiedTranscriptWhenVisibleHistoryEmpty | The helper preserves the existing typed degraded evidence: missing kernel resume reports kernel resume unavailable; missing or unreadable transcript rows surface the underlying transcript/session DB error; /resume still validates empty id/prefix before resume availability; /branch still validates resume availability before metadata/id/fork work. Remote/plain TUI constructors keep their session switch seams nil. |
| 5 / 5.Q | TUI running-agent placeholder surfaces interrupt + queued slash actions — internal/tui Model.editor.Placeholder switches between an idle placeholder (Type a message and hit Enter…) and an in-flight placeholder that lists discoverable busy-time actions — at minimum msg=interrupt · Ctrl+C cancel, plus any slash command registered with BusyAvailable=true on the SlashRegistry — so operators can discover the cancel + interrupt UX without reading docs | validated | tools | small | operator, system | internal/tui/running_placeholder_test.go | If the registry exposes no busy-available slashes, the in-flight placeholder degrades to msg=interrupt · Ctrl+C cancel only (Gormes’s current minimum-viable busy actions). |
| 5 / 5.Q | Native TUI conversation viewport tail helper — internal/tui exposes a pure conversation viewport helper that clips RenderFrame.History to the visible tail under width/height budgets, emits a deterministic omitted-history sentinel, and always preserves DraftText and LastError inputs | validated | tools | small | operator, system | internal/tui/viewport_history_test.go::TestConversationViewportTail_* | If height is tiny or width is narrow, the helper renders the latest visible turn plus compact draft/error/sentinel evidence instead of panicking or allocating the full history body. |
| 5 / 5.Q | Native TUI queued-message edit helper — internal/tui owns a pure queued-message buffer helper that can enqueue, dequeue, select an item for edit, replace edited text, cancel edit without mutation, delete the edited item, and compute a stable three-row visible window before any Bubble Tea keybinding or kernel submission wiring lands | validated | tools | small | operator, system | internal/tui/queued_messages_test.go::TestQueuedMessages_* | Until runtime binding lands, the helper is unused and the TUI continues to accept one foreground message at a time; no queued message should be silently dropped. |
| 5 / 5.Q | Native TUI renderConv viewport budget binding — internal/tui renderConv uses the validated conversation viewport helper with the caller-provided frame height/width budget so each Bubble Tea frame renders only the visible conversation tail plus draft/error status | validated | tools | small | operator, system | internal/tui/viewport_render_test.go::TestRenderConvViewportBinding_* | renderConv falls back to the helper compact latest-turn output when the terminal is too small instead of rebuilding or returning an unbounded conversation string. |
| 5 / 5.Q | Native TUI Hermes skin token renderer — internal/tui exposes a HermesSkin value model and renderer tokens that mirror active Hermes Ink theme/skin defaults for the interactive TUI: prompt symbol, Hermes-style response label shape, status bar colors, input-rule color, placeholder style, and width-aware minimal-chrome thresholds. The response label is an owned Gormes product-identity divergence (⚕ Gormes) while preserving the upstream chrome shape. Legacy prompt_toolkit skin refs remain fallback evidence for older color-key compatibility only. This is a pure helper slice; it does not change Bubble Tea runtime layout yet. | validated | tools | small | operator, system | internal/tui/hermes_skin_test.go::TestHermesSkin_* | The TUI reports explicit degraded/unavailable evidence instead of silently dropping input, hiding status, or leaking slash text to the model. |
| 5 / 5.Q | Native TUI Hermes status bar renderer — internal/tui renders a Hermes-compatible single-line footer/status bar from the latest RenderFrame plus local timing state: short model name, context used/total, context percentage with threshold styles, session duration, per-prompt elapsed timer, width-aware collapse at <52 and <76 columns, and no wrapping or duplicated ghost rows. | validated | tools | small | operator, system | internal/tui/hermes_status_bar_test.go::TestHermesStatusBar_* | The TUI reports explicit degraded/unavailable evidence instead of silently dropping input, hiding status, or leaking slash text to the model. |
| 5 / 5.Q | Native TUI Hermes bottom-pinned chrome layout — Gormes local Bubble Tea TUI replaces the dashboard/sidebar chrome with Hermes bottom-pinned chat contract: transcript/output above, an activity/status hint row only when there is useful non-idle evidence, single-line Hermes Ink-style status rule, unboxed prompt line with Hermes prompt symbol that expands only for multiline drafts, optional rows below the prompt, Gormes-branded response label inside the Hermes response-box shape, and no Telemetry/Soul side pane or standalone full-width input rules. The local full-screen Bubble Tea renderer should use the terminal alternate screen like current Hermes Ink unless an explicit inline mode is added, while preserving Go-native RenderFrame consumption and ensuring completed assistant text is not rendered both as transcript history and live draft. | validated | tools | medium | operator, system | internal/tui/hermes_chrome_test.go::TestHermesChrome_* | The TUI reports explicit degraded/unavailable evidence instead of silently dropping input, hiding status, or leaking slash text to the model. |
| 5 / 5.Q | Native TUI Hermes input keybinding semantics — Gormes TUI key handling matches the active Hermes Ink behavior for normal input and active turns: Enter submits/reroutes by state, newline chords insert newlines, Up/Down browse history or queued messages only at line boundaries, Ctrl+C cancels modal/active turn or clears idle draft before exiting on empty input, Ctrl+D/action-D exits only when input is empty, Ctrl+L/action-L forces a clean redraw, and running-agent text honors busy_input_mode interrupt/queue/steer semantics. Legacy prompt_toolkit refs remain fallback evidence only for behavior not represented in ui-tui. | validated | tools | medium | operator, system | internal/tui/hermes_keybindings_test.go::TestHermesKeybindings_* | The TUI reports explicit degraded/unavailable evidence instead of silently dropping input, hiding status, or leaking slash text to the model. |
| 5 / 5.Q | Native TUI Shift+Enter newline CSI-u parity — Gormes TUI treats Shift+Enter byte sequences emitted by Kitty keyboard protocol / CSI-u and xterm modifyOtherKeys terminals as the same newline chord as Alt+Enter, while plain Enter continues to submit by state. Bubble Tea currently surfaces those distinct Shift+Enter sequences as unknown CSI messages instead of a KeyShiftEnter key, so Gormes normalizes only the three source-backed Shift+Enter sequences before they can be dropped by the editor. | validated | tools | small | operator, system | internal/tui/hermes_keybindings_test.go::TestHermesKeybindings_*ShiftEnter* | Terminals that collapse Shift+Enter to plain Enter keep the existing Alt+Enter and Ctrl+J newline fallbacks; malformed or unrelated CSI input is ignored instead of submitting or mutating the draft. |
| 5 / 5.Q | Native TUI clipboard, OSC52, and terminal setup parity — Gormes ports the active Hermes Ink terminal integration helpers as pure Go-native services behind injectable process/env/filesystem seams: clipboard read fallbacks, macOS clipboard write, OSC52 query/decode with tmux passthrough, VS Code-family keybinding setup with conflict detection/backups, truecolor environment resolution, and terminal parity hints. It preserves the current terminal-native selection help until real copy support is explicitly wired. | validated | tools | medium | operator, system | internal/tui/clipboard_test.go; internal/tui/terminal_setup_test.go | Missing clipboard commands, unsupported OSC52, binary-looking/empty clipboard payloads, SSH terminal setup refusal, keybinding conflicts, backup/write failures, and NO_COLOR/truecolor constraints return tui_terminal_* evidence rather than advertising unavailable copy/setup behavior. |
| 5 / 5.Q | Native TUI image/file drop + paste collapse ingress — Gormes native TUI ports Hermes Ink composer ingress behavior before provider wiring: pasted/dropped local paths are classified without confusing real slash commands, image files attach through an image.attach gateway seam with remainder text preserved, large pasted text collapses through a paste.collapse seam with readable paste labels, clipboard/OSC52 text fallback is used only for usable text, malformed or unterminated bracketed paste is flushed back to normal input, and external-editor/copy commands expand paste placeholders or copy assistant output without leaking reasoning scratchpads. | validated | tools | medium | operator, gateway, system | internal/tui/composer_ingress_test.go; internal/tuigateway/ingress_rpc_test.go | Missing files, directories, unsupported image suffixes, rejected file URIs, unusable/binary clipboard payloads, paste-collapse failures, gateway image-attach failures, active modal/editor conflicts, and invalid copy indices return tui_ingress_* evidence without submitting slash-looking paths as commands or leaking collapsed paste file paths. |
| 5 / 5.Q | Native TUI Hermes slash completion helpers — internal/tui exposes pure completion and autosuggest helpers backed by internal/cli.CommandRegistry so the local TUI can complete Hermes-compatible slash commands, aliases, subcommands, and unavailable-but-recognized commands without submitting unknown slash text to the model. Runtime completion-menu binding remains a dependent visual row. | validated | tools | small | operator, system | internal/tui/slash_completion_test.go::TestHermesSlashCompletion_* | The TUI reports explicit degraded/unavailable evidence instead of silently dropping input, hiding status, or leaking slash text to the model. |
| 5 / 5.Q | Native TUI absolute path completion routing — Native Bubble Tea TUI exposes a pure completion-request classifier that matches current Hermes Ink behavior: leading absolute paths and trailing absolute path tokens route to path completion with the correct replace offset, while real slash commands continue routing to slash completion and picker-owned /model and /provider inputs do not invoke generic completion. | validated | tools | small | operator, system | internal/tui/completion_request_test.go | Ambiguous completion inputs return no generic completion request instead of submitting slash-looking path text to the model or replacing the wrong prompt span. |
| 5 / 5.Q | Native TUI Hermes slash dispatch behavioral matrix — Add a deterministic Hermes Ink slash-dispatch matrix for the native Bubble Tea TUI so typed slash commands are classified before normal prompt submission. Registered local commands such as /save, /branch, /browser, and /mouse keep their Go-native handlers; Hermes-known but not locally handled commands such as /model, /provider, /skills, /details, /tools, /history, /status, /title, /rollback, and /queue return explicit command-unavailable or gateway-required evidence instead of entering kernel.Submit; unknown and ambiguous slash prefixes are consumed with bounded guidance; and future gateway-backed handlers have a single table that mirrors active Hermes createSlashHandler route kinds without running Node or Python. | validated | tools | small | operator, system | internal/tui/slash_dispatch_behavior_test.go::TestHermesSlashDispatchBehavior_* | If a Hermes-known slash command cannot run in the local Go TUI yet, the editor is cleared and the status line reports unavailable/gateway-required evidence; no slash command text is submitted to the model as ordinary prompt content. |
| 5 / 5.Q | Native TUI /quit local exit binding — Gormes native TUI treats /quit and /exit as local exit commands matching current Hermes Ink: slash dispatch consumes the editor, schedules Bubble Tea quit, never calls the submitter/kernel, and never falls through to gateway or unavailable-command evidence. | validated | tools | small | operator, system | internal/tui/slash_dispatch_behavior_test.go::TestHermesSlashDispatchBehavior_QuitExitsLocally | If the TUI cannot exit locally, the slash text is still consumed with explicit evidence rather than being submitted to the provider; the correct implementation path is local tea.Quit, not a gateway request. |
| 5 / 5.Q | Native TUI Hermes tool progress + modal panel renderers — internal/tui adds Hermes-compatible renderer primitives for tool progress and interactive modal panels: live spinner text with elapsed timer, stacked tool-completion scrollback lines for all/new modes, dangerous-command approval panels with once/session/always/deny/view choices, clarify question panels with numbered options and Other, and sudo/secret panels with countdown/hint text. This row renders state injected by tests; live tool/approval/clarify wiring remains dependent work. | validated | tools | medium | operator, system | internal/tui/hermes_panels_test.go::TestHermesPanels_* | The TUI reports explicit degraded/unavailable evidence instead of silently dropping input, hiding status, or leaking slash text to the model. |
| 5 / 5.Q | Native TUI Ink behavioral transcript golden matrix — Add deterministic text snapshots for the active Hermes Ink full-screen transcript/composer behavior as a single Gormes-visible sequence: user rows, assistant rows, tool-trail/thinking rows, tool-result boxes, queued-message rows, sticky prompt/status rule, live streaming assistant draft, and final assistant history. The matrix must prove Gormes’ Bubble Tea renderer preserves visible ordering and duplication rules while keeping Gormes-owned branding differences explicit. | validated | tools | medium | operator, system | internal/tui/hermes_behavioral_fidelity_test.go::TestHermesBehavioralFidelity_* | If the matrix cannot express a Hermes Ink behavior yet, the fixture records an explicit unsupported_behavior evidence string instead of silently accepting legacy prompt_toolkit output. |
| 5 / 5.Q | Native TUI markdown soft-wrap boundary trim — Gormes native TUI prose and markdown wrapping trims exactly one whitespace character at each soft-wrap boundary, matching Hermes Ink’s wrap-trim behavior for rendered markdown text while preserving intentional extra spacing and source-line indentation. | validated | tools | small | operator, system | internal/tui/markdown_test.go::TestRenderMarkdownSoftWrapTrim_* | Without the trim-aware wrapper, narrow terminal renders show stray leading spaces on continuation lines or collapse intentional indentation, making Gormes visually drift from active Hermes Ink markdown output. |
| 5 / 5.Q | Channel/TUI iteration-limit finalization transcript fixture — Add an end-to-end visible transcript fixture for Hermes-style max-tool-iteration exhaustion across kernel, gateway, and local TUI renderers. When the tool loop reaches the budget, Gormes must ask the provider for a single no-tools summary, deliver one final assistant message, clear live tool-progress/thinking indicators, and never expose raw loop-control errors such as tool iteration limit exceeded, duplicate final answers, or a stuck hourglass/progress row. | validated | gateway | medium | gateway, operator, system | internal/gateway/iteration_finalization_test.go::TestIterationLimitFinalization_* + internal/tui/hermes_behavioral_fidelity_test.go::TestHermesBehavioralFidelity_FinalAssistantNotDuplicated | If the no-tools summary request fails, the visible final transcript uses a bounded degraded summary-failed message with provider error classification, not a raw loop panic or repeated progress bubble. |
| 5 / 5.Q | SSE streaming to Bubble Tea TUI — Remote Bubble Tea clients can consume native Gormes turn events over SSE without the Python tui_gateway process | validated | gateway | medium | operator, gateway, system | internal/tuigateway/sse_stream_test.go | TUI status reports remote streaming unavailable while local Bubble Tea mode continues to work. |
| 5 / 5.Q | TUI websocket attach transport — Gormes ports the current Hermes Ink attach-mode contract as a native Go remote-TUI transport: a configured websocket URL connects without spawning a local kernel/provider process, sends JSON-RPC session.create, prompt.submit, session.interrupt, and terminal.resize envelopes, consumes event frames into native RenderFrame updates, optionally mirrors event frames to a sidecar websocket, and redacts query/user-info credentials from attach failure diagnostics. | validated | gateway | medium | operator, gateway, system | internal/tuigateway/websocket_attach_test.go; cmd/gormes/remote_tui_test.go | Unavailable websocket attach URLs return remote streaming unavailable evidence with redacted URLs while local Bubble Tea and existing —remote SSE mode continue to work. |
| 5 / 5.Q | OpenAI-compatible chat-completions API server — OpenAI-compatible chat.completions HTTP surface over the native Gormes turn loop | validated | gateway | medium | operator, gateway | internal/apiserver/chat_completions_test.go | API health and error envelopes report auth, body-size, content-normalization, and streaming failures without starting hidden sessions. |
| 5 / 5.Q | API server multimodal content preservation — Native API server request normalization preserves Hermes’ multimodal payload semantics instead of flattening images away: text-only arrays collapse to text, image_url and input_image parts survive into the native turn/message content-part model for chat.completions and responses, data:image URLs are accepted, non-image data URLs and file/input_file parts fail with OpenAI-compatible 400 errors, and visible-payload checks treat image-only messages as non-empty. | validated | gateway | medium | operator, gateway, system | internal/apiserver/multimodal_content_test.go; internal/kernel/multimodal_turn_test.go | Unsupported parts return unsupported_content_type or invalid_image_url envelopes before a turn starts; supported image parts are preserved for providers that can consume native image input, while providers without image support return provider-visible image_input_unavailable evidence rather than silently dropping the image. |
| 5 / 5.Q | Responses API store + run event stream — Stateful OpenAI Responses and runs APIs over the same native session chain as chat completions | validated | gateway | medium | operator, gateway | internal/apiserver/responses_runs_test.go | API status reports response-store disabled, LRU eviction, orphaned runs, and previous_response_id misses. |
| 5 / 5.Q | API server disconnect snapshot persistence — Streaming disconnects and server cancellations persist incomplete Responses snapshots when store=true | validated | gateway | small | operator, gateway | internal/apiserver/disconnect_snapshot_test.go | Stored response status distinguishes incomplete disconnect snapshots from failed or completed responses. |
| 5 / 5.Q | Gateway proxy mode forwarding contract — Gateway adapters can forward turns to a remote OpenAI-compatible Gormes API server while preserving session IDs and safe history filtering | validated | gateway | small | gateway, operator | internal/gateway/proxy_mode_test.go | Gateway status reports proxy unreachable, stale generation, or missing proxy credentials without dropping local audit records. |
| 5 / 5.Q | Gateway proxy replay assistant metadata preservation — Gormes proxy-mode replay preserves safe assistant metadata that provider request builders need for multi-turn fidelity: existing reasoning and reasoning_content echoes survive safe history filtering, empty reasoning_content remains a meaningful sentinel, cache/content-part context survives, and unsafe tool-call/tool-result history is still excluded before forwarding to the remote OpenAI-compatible server. | validated | gateway | small | gateway, operator | internal/gateway/proxy_mode_test.go::TestProxySubmitter_PreservesAssistantReplayMetadata | Unsupported provider serializers may still strip cache_control according to provider capability policy; proxy safe-history now preserves safe replay metadata before provider-specific request shaping while continuing to strip unsafe tool/tool-call history. |
| 5 / 5.Q | Dashboard API client contract — Dashboard-facing API helpers consume native chat, Responses, model, provider, OAuth, session, and tool-progress endpoints without importing the upstream React app | validated | gateway | small | operator | internal/apiserver/dashboard_contract_test.go | Dashboard status reports missing native endpoints or disabled panels instead of assuming the upstream Node/React server exists. |
| 5 / 5.Q | Dashboard PTY chat sidecar contract — Dashboard chat sidecar bridges PTY bytes and structured tool events without merging terminal transport into API server state | validated | gateway | small | operator | internal/apiserver/dashboard_pty_test.go | Dashboard status reports PTY or sidecar event publication unavailable while preserving normal API chat. |
| 5 / 5.Q | API server detailed health snapshot contract — Native API server defines a pure detailed-health snapshot model that reports provider, response-store, run-event stream, gateway, and cron availability/degradation without binding HTTP routes or cron mutations | validated | gateway | small | operator, gateway, system | internal/apiserver/detailed_health_snapshot_test.go | The snapshot reports cron_unavailable, response_store_disabled, run_events_unavailable, gateway_unavailable, or provider_unconfigured evidence instead of a flat OK/failed bit. |
| 5 / 5.Q | API server detailed health endpoint — Native API server binds the validated DetailedHealthSnapshot value model to unauthenticated GET /health/detailed and /v1/health/detailed routes while preserving existing flat /health and /v1/health behavior | validated | gateway | small | operator, gateway, system | internal/apiserver/detailed_health_endpoint_test.go | HTTP detailed health returns per-section degraded evidence through stable JSON without requiring an API key, while flat /health remains backward-compatible. |
| 5 / 5.Q | API server cron admin read-only endpoints — Native API server exposes authenticated read-only cron admin endpoints for list/get/run-history over the existing cron store and run audit without creating a second scheduler control plane | validated | gateway | small | operator, gateway, system | internal/apiserver/cron_admin_read_test.go | HTTP cron admin reports cron_store_unavailable, cron_job_missing, or cron_runs_unavailable evidence using the shared API error envelope. |
| 5 / 5.Q | API server cron admin mutating endpoints — Native API server exposes authenticated cron admin create/update/delete/pause/resume/trigger endpoints that reuse the native cron action envelope, schedule parser, safety policy, and delivery plan | validated | gateway | small | operator, gateway, system | internal/apiserver/cron_admin_mutation_test.go | HTTP cron admin reports validation_failed, unsafe_script_rejected, cron_job_missing, trigger_delivery_unavailable, or cron_store_unavailable using the shared API error envelope. |
| 5 / 5.Q | API server legacy jobs routes + default toolset — Native API server keeps Hermes’ platform-facing job/toolset contract while using Gormes’ cron/admin internals: /api/jobs and /api/jobs/{id} compatibility routes map to the validated cron admin list/create/get/update/delete/pause/resume/run behavior with the same auth/body/error guardrails, and platform api_server resolves the default hermes-api-server toolset unless platform_toolsets.api_server overrides it. | validated | gateway | medium | operator, gateway, system | internal/apiserver/jobs_compat_test.go; internal/cli/api_server_toolset_test.go | Cron module/store unavailability returns api_jobs_unavailable or cron_store_unavailable through the shared API error envelope; invalid IDs, missing names, oversized prompts, invalid repeat counts, auth failures, and unsupported toolsets return typed evidence without starting a scheduler or provider turn. |
| 5 / 5.Q | Provider client lazy-init for TUI cold-start budget — Provider HTTP client construction (OpenAI, Anthropic, Bedrock helpers, Firecrawl-equivalent web client) is lazy: clients are only instantiated when a code path actually selects that provider, not at package init or config load. A checked-in benchmark exercises the gormes binary cold-start path (process exec → first scripted-chat frame, in gormes chat -q) and asserts a documented budget; the budget rationale cites Hermes v0.12.0 PR #17046 (lazy OpenAI/Anthropic/Firecrawl) within the broader ~57% cold-start cut. | validated | provider | medium | operator | internal/runtime/coldstart_bench_test.go | Without lazy provider construction, cold-start cost grows with installed-provider count and runs OAuth/network probes for unselected providers; the benchmark surfaces the regression but does not block startup. |
| 5 / 5.Q | Native TUI /model slash command binding over the existing model picker — The native Bubble Tea TUI treats /model (and the /m prefix) as a local operator command, not prompt text: dispatching it opens the already-implemented ModelPicker overlay (internal/tui/model_picker.go RenderModelPicker/UpdateModelPicker — a TUI-LOCAL overlay, unlike the kernel-driven Approval/Clarify/Secret panels, so it needs its own Model overlay state + update.go key routing + view.go render slot), clears the editor, never calls Submitter; confirming applies an IN-SESSION model switch; cancel returns unchanged. BLOCKED: builder-pass 2026-05-15 established there is NO in-session model-switch seam in the local kernel path — PlatformEventKind is {Submit,Cancel,Quit,ResetSession,Steer} with no model override; kernel.go SetModel is construction-only; the completed 5.O picker is config-TOML-persist only; SessionModelOverride is gateway-server-only and not wired to the local Bubble Tea kernel. This row therefore depends on the new ‘Kernel in-session model-switch seam for the native TUI’ prerequisite. The picker render/key engine already exists and MUST be reused, not reimplemented; the missing piece is the apply seam plus a model-catalog -> internal/tui data seam. | validated | tui | large | - | internal/tui/slash_model_test.go; cmd/gormes/tui_model_slash_test.go | If the model catalog is unavailable, /model is consumed with model: ... status evidence instead of forwarding the slash text to the model or silently dropping it; the picker is not opened with an empty/invalid catalog. |
| 5 / 5.Q | Kernel in-session model-switch seam for the native TUI — Add a Design-B kernel in-session model-switch: a new PlatformEventKind PlatformEventSetModel whose PlatformEvent carries the existing Model string plus a new Provider string, processed exactly like PlatformEventResetSession — a synchronous ack chan error, valid only from PhaseIdle/PhaseFailed (reject mid-turn with a typed error analogous to ErrResetDuringTurn), and the resident mutation performed on the Run loop’s own goroutine to preserve the single-owner invariant. The handler sets a resident session override (k.sessionModel/k.sessionProvider); selectTurnModel (kernel.go:1431) precedence becomes per-event PlatformEvent.Model > resident session override > k.cfg.Model; when the chosen provider differs from the resident client’s, a new k.client is resolved through the EXISTING FallbackClientFactory(ctx, llm.ModelRoute) machinery (kernel.go:37) rather than a bespoke client builder. Session history is NOT cleared (unlike ResetSession). Catalog seam (2) needs NO new code: the downstream /model picker is populated from the existing internal/llm.ListPickerProviders() (internal/tui already imports internal/llm), so this row is kernel-only. Interface design is COMPLETE (gormes-interface-designer 2026-05-15: Design B chosen; Design A ‘TUI per-submit sticky’ rejected — fixed construction-time client cannot cross-provider; Design C ‘out-of-band SetSessionModel method’ rejected — data race vs the single-owner event loop). | validated | tui | medium | - | - | - |
| 5 / 5.Q | Kernel cross-provider client swap for in-session model switch — Extend the shipped PlatformEventSetModel handler so that when the requested provider (k.sessionProvider, already captured by the prerequisite row) differs from the resident k.client’s provider, a new k.client is resolved on the Run-loop goroutine through the EXISTING FallbackClientFactory(ctx, llm.ModelRoute) machinery (internal/kernel/kernel.go:37) — NOT a bespoke client builder. Requires a pure provider+model -> llm.ModelRoute resolver (ModelRoute needs BaseURL/APIMode/KeyEnv); source it from the existing provider catalog (internal/llm ListPickerProviders / provider config) rather than inventing a new catalog. Same-provider switches keep the resident client untouched (already shipped). History is still NOT cleared. The gateway SessionModelOverride stays untouched. | validated | tui | medium | - | - | - |
| 5 / 5.Q | Native TUI slash handler-port coverage — Inventory-only, fixture-backed parent for native Bubble Tea TUI slash commands that Gormes recognizes but does not yet handle locally. NewDefaultSlashRegistry handles the shipped local commands (/help, /clear, /new, /compact, /mouse, /scroll, /save, /branch, /copy, /browser, /kanban, /model//m, /quit//exit). Remaining slash tokens route through slashFallbackResult to cli.ResolveCommandAlias and CommandRegistry, where recognized names are consumed with native-TUI or gateway unavailable evidence and unknown/ambiguous names are consumed with guidance so command text never reaches the model. Hermes ui-tui first tries local SLASH_COMMANDS, then gateway slash.exec/command.dispatch; Gormes keeps unported local handler ports as child rows instead of silently proxying every command through this umbrella. | validated | tui | umbrella | operator, system | internal/tui/slash_dispatch_behavior_test.go::TestHermesSlashDispatchBehavior_KnownUnhandledCommandsNeverSubmit + internal/tui/slash_completion_test.go::TestHermesSlashCompletion_UnavailableCommandsStillComplete | Recognized but unported native-TUI commands render explicit recognized but unavailable in the native TUI or requires gateway support in the native TUI evidence; unknown and ambiguous slash tokens render guidance. The fallback must consume slash text rather than submit it as model prompt content, and every real local port must land as its own child row with focused fixtures. |
| 5 / 5.Q | Native TUI shipped slash command registry availability metadata — Align the shared CLI CommandRegistry with native Bubble Tea slash handlers already shipped in NewDefaultSlashRegistry so slash completions, active-turn busy evaluation, and fallback evidence all report /branch, /fork, /save, /copy, /mouse, /scroll, /quit, and /exit as available local TUI commands instead of recognized-but-unavailable commands. | validated | tui | small | operator, system | internal/tui/slash_completion_test.go::TestHermesSlashCompletion_LocalNativeHandlersAreAvailable | If registry metadata drifts from shipped native handlers, completion menus dim working commands, busy guards reject local commands with unavailable evidence, and future slash child-row selection starts from stale inventory. |
| 5 / 5.Q | Native TUI Terminal.app truecolor and ANSI sanitizer parity — Port Hermes Ink TUI Terminal.app/truecolor and ANSI sanitizer behavior into the native Gormes TUI so renderer output keeps cursor/source-of-truth stability, strips malformed CSI safely, and preserves readable color behavior across modern terminals. | validated | tui | small | - | internal/tui Terminal.app/ANSI sanitizer fixtures | - |
| 5 / 5.Q | Hermes ui-tui strict-fidelity action matrix — Map the unmapped Hermes ui-tui source and test surface into Gormes-native TUI rows, owned-divergence notes, or explicit exclusions. The matrix must cover command dispatch, viewport/history stores, RPC/gateway client events, terminal modes, clipboard/OSC52, provider/model UI, approval actions, and state isolation before the strict-fidelity report can stop treating ui-tui as an undifferentiated blocker bucket. | validated | docs | large | operator, system | internal/tui hermes-ui-tui strict-fidelity matrix fixtures | Until this strict-fidelity bucket is classified, Gormes must continue treating the matching Hermes source/docs/tests as unmapped blockers and avoid claiming complete Hermes parity for this surface. |
| 5 / 5.Q | Hermes web dashboard strict-fidelity contract map — Build a report-only Hermes web dashboard strict-fidelity classifier for cmd/repoctl hermes-contract-inventory: scan the current hermes-agent/web/src dashboard tree, group exact files into deterministic surface families (terminal chat/PTY, sessions, profiles, plugins and page-scoped slots, OAuth/provider panels, model picker, cron/admin/jobs, i18n, themes, and gateway client event shapes), and attach each family to an existing Gormes API/TUI gateway row, planned child row, source-pair entry, explicit exclusion, or owned-divergence note. The classifier is evidence-only: it must not import or execute React/TypeScript dashboard code, mutate internal/apiserver or internal/tuigateway behavior, or claim strict dashboard parity without row/source-pair/test proof. | validated | docs | medium | operator, system | internal/repoctl/hermes_contract_inventory_dashboard_test.go | Until this classifier lands, strict-fidelity reports can only show Hermes web/src dashboard files as an undifferentiated unmapped bucket, so Gormes must avoid claiming complete dashboard/API/TUI gateway parity. |
| 5 / 5.Q | Native TUI /help slash command binding — Bind /help in the native Bubble Tea TUI as a local operator command. It must be consumed before kernel submit, clear the editor, never fall through to the recognized-but-unavailable fallback, advertise the shipped native TUI slash commands, and explain that remaining recognized commands are consumed with visible unavailable evidence rather than being sent to the model. This is the first small local help port; Hermes’ richer panel rendering remains source evidence for future panel-style help, not a blocker for this status-row slice. | validated | tui | small | - | internal/tui/slash_dispatch_test.go TestSlashRegistry_HelpHandlerShowsNativeInventory / TestSlashRegistry_HelpAdvertisedWhileBusy | - |
| 5 / 5.Q | Native TUI /redraw local repaint binding — Bind Hermes ui-tui /redraw to the native Bubble Tea local repaint path so terminal-drift recovery is handled locally, never submitted to the model, and never routed through unavailable slash fallback. The slash command reuses the same visible-frame clearing seam as Ctrl+L and preserves kernel/session state for the next render frame. | validated | tui | small | operator, system | internal/tui/slash_dispatch_behavior_test.go::TestHermesSlashDispatchBehavior_RedrawClearsVisibleFrameLocally | If /redraw remains fallback-only, operators can recover terminal drift only via Ctrl+L while the slash command renders recognized-but-unavailable evidence despite Hermes exposing a local redraw slash command. |
| 5 / 5.Q | Native TUI /statusbar chrome mode binding — Bind Hermes ui-tui /statusbar//sb to the native Bubble Tea chrome so operators can toggle the model/context status rule locally with on, off, top, bottom, or toggle. The handler must not submit slash text to the model; it mutates only the TUI status-bar mode and uses the chrome assembler to place or hide the existing Hermes-compatible status rule. | validated | tui | small | operator, system | internal/tui/slash_statusbar_test.go::TestStatusBarSlashTogglesChromePlacementWithoutSubmitting | If /statusbar is not ported, Gormes still consumes it with recognized-unavailable evidence, but operators cannot hide or reposition the native status rule without restarting or editing config. |
| 5 / 5.Q | Native TUI /details detail-section visibility binding — Bind Hermes ui-tui /details to the native Bubble Tea detail-visibility seam so operators can locally inspect or set global detail mode (hidden, collapsed, expanded, cycle/toggle) and per-section overrides for thinking, tools, subagents, and activity. The handler must not submit slash text to the model; it updates native TUI detail state and the transcript renderer must honor that state for thinking and tool-progress sections while preserving existing transcript, draft, error, and status chrome behavior. | validated | tui | small | operator, system | internal/tui/slash_details_test.go::TestDetailsSlashControlsThinkingAndToolVisibilityWithoutSubmitting | If /details is not ported, Gormes consumes it with recognized-unavailable evidence and operators cannot locally hide or expand thinking/tool progress visibility during a native TUI turn. |
| 5 / 5.Q | Native TUI /indicator busy-indicator style binding — Bind Hermes ui-tui /indicator to the native Bubble Tea busy-indicator seam so operators can inspect the current indicator style and hot-swap the running-turn glyph family among ascii, emoji, kaomoji, and unicode without submitting slash text to the model. The native hint renderer must honor the selected style immediately while preserving the existing phase/session/status-message hint text and slash fallback behavior. | validated | tui | small | operator, system | internal/tui/slash_indicator_test.go::TestIndicatorSlashControlsBusyHintWithoutSubmitting | If /indicator is not ported, Gormes consumes it with recognized-unavailable evidence and operators cannot locally switch the running-turn busy indicator style in the native TUI. |
| 5 / 5.Q | Native TUI /history current transcript page binding — Port Hermes ui-tui’s local /history behavior into the native Bubble Tea TUI without proxying through a detached CLI worker. /history [preview] must consume slash text locally, read the current in-memory kernel transcript, filter to user and assistant messages, render a dismissible History page with clipped bodies and tool-call placeholders, and report no conversation yet when no user/assistant turns exist. The page seam must stay local to the TUI and must not mutate kernel/session state. | validated | tui | small | operator, system | internal/tui/slash_history_test.go::TestHistorySlashRendersCurrentTranscriptPageWithoutSubmitting | Empty or non-conversation transcripts consume /history with no conversation yet; the transient page is local, read-only, and dismissible with Escape/Ctrl-C without submitting prompt text. |
| 5 / 5.Q | Native TUI /status current frame page binding — Port Hermes ui-tui’s /status behavior into the native Bubble Tea TUI as a local read-only status page. Hermes calls session.status and renders a Status transcript page; Gormes native TUI must consume /status locally, avoid the slash fallback, use the current kernel RenderFrame as the authoritative live status snapshot, render the page through the transient page seam, and return no active session when there is no current session. | validated | tui | small | operator, system | internal/tui/slash_status_test.go::TestStatusSlashRendersCurrentFramePageWithoutSubmitting | Without an active session, /status consumes the slash text and reports no active session; with a session, the page remains local/read-only and never depends on live gateway credentials. |
| 5 / 5.Q | Native TUI /logs gateway tail page binding — Port Hermes ui-tui’s /logs [limit] behavior into the native Bubble Tea TUI as a local read-only gateway log page. Hermes calls ctx.gateway.gw.getLogTail(Math.min(80, Math.max(1, parseInt(arg, 10) || 20))); Gormes must consume /logs locally, clamp the requested tail limit the same way, render non-empty output through the transient page seam as Logs, and report no gateway logs when the tail is empty or no local adapter is wired. | validated | tui | small | operator, system | internal/tui/slash_logs_test.go::TestLogsSlashRendersGatewayTailPageWithoutSubmitting | Plain or remote TUI instances without a local log-tail adapter consume /logs and report no gateway logs; local cmd/gormes startup injects a bounded gateway/file-fallback adapter without adding HTTP or config-path I/O to internal/tui. |
| 5 / 5.Q | Native TUI /title session-title binding — Port Hermes ui-tui /title local handler into the native Bubble Tea TUI. /title with an active session queries the current title; /title <name> persists an operator-chosen session title through an injected adapter; the handler consumes command text locally and never falls through to model submission or generic unavailable evidence. Local TUI startup wires the production adapter over session metadata so manual titles set title_manually_set=true; remote/plain TUI constructors leave the seam nil with explicit degraded evidence. | validated | tui | small | operator, system | internal/tui/slash_title_test.go::TestTitleSlashGetsAndSetsSessionTitleWithoutSubmitting + cmd/gormes/tui_title_slash_test.go::TestTUITitleSlashBindingLocalModelPersistsManualTitle | No active session returns no active session; a nil SessionTitle adapter returns title: session title unavailable; adapter errors are consumed as visible title evidence. Remote TUI remains unwired rather than opening session DB handles inside internal/tui. |
| 5 / 5.Q | Native TUI /sessions and /resume picker page binding — Port the Hermes ui-tui local resume/session picker entry point into the native Bubble Tea TUI. /sessions and /resume are consumed locally, open a dismissible Sessions transient page over an injected recent-session directory seam, and never fall through to model submission or generic unavailable evidence. The TUI owns rendering and degraded messages; cmd/gormes owns the Goncho memory database adapter. This slice intentionally opens the picker/list page only; selecting and switching the active kernel session remains future work. | validated | tui | small | operator, system | internal/tui/slash_sessions_test.go::TestSessionsSlashOpensPickerPageWithoutSubmitting + cmd/gormes/tui_sessions_slash_test.go::TestTUISessionsSlashBindingLocalModelReceivesSessionDirectory | A nil SessionDirectory adapter returns sessions: directory unavailable; an empty directory returns no sessions found; adapter errors are consumed as visible sessions: <error> evidence. Remote/plain TUI constructors leave the seam nil and never open local SQLite state. |
| 5 / 5.Q | Native TUI /resume session switch binding — Extend the native /sessions//resume picker port with an explicit /resume <id-or-prefix> switch path. The native Bubble Tea TUI resolves the operator-supplied session id or unique prefix through an injected SessionResumeFunc, asks the kernel-owned resident-session seam to switch only while idle/failed, replays ordered transcript history into the visible frame, clears stale picker state, and never submits the slash text as model prompt content. Bare /resume remains the local picker page from the previous child row. | validated | tui | small | operator, system | internal/tui/slash_sessions_test.go::TestResumeSlashWithSessionIDSwitchesVisibleSessionAndHistoryWithoutSubmitting + internal/kernel/resume_session_test.go::TestKernelResumeSessionSwitchesResidentSessionAndHistory + internal/persistence/transcript/markdown_test.go::TestLoadMessagesReturnsOrderedHermesMessages + cmd/gormes/tui_sessions_slash_test.go::TestTUIResumeSlashBindingLocalModelReceivesSessionResumeAdapter | A nil SessionResume adapter returns resume: session switch unavailable; active/streaming turns return interrupt-first guidance; missing, ambiguous, database, transcript, or kernel errors are consumed as visible resume: <error> evidence. Remote/plain TUI constructors leave the seam nil and never open local SQLite state. |
| 5 / 5.Q | Native TUI /usage local frame usage page binding — Port the Hermes ui-tui /usage entry point into the native Bubble Tea TUI as a local frame-usage page. /usage is consumed locally, reads the current kernel.RenderFrame telemetry/context evidence, opens a dismissible Usage transient page, and never falls through to model submission or generic unavailable evidence. This slice intentionally covers local session/runtime token evidence only; provider account limits/cost windows remain a future async adapter row so internal/tui stays free of HTTP, credentials, and provider account API policy. | validated | tui | small | operator, system | internal/tui/slash_usage_test.go::TestUsageSlashOpensFrameUsagePageWithoutSubmitting | When the current frame has no token telemetry, /usage consumes the command and returns no API calls yet without opening a page. Missing model/session fields render as unknown; negative counters clamp to zero. Provider account usage is omitted rather than fetched from internal/tui. |
| 5 / 5.Q | Native TUI /usage provider account usage adapter binding — Extend the native Bubble Tea TUI /usage command with a fakeable, asynchronous provider account-usage adapter. The command still opens local frame-backed session usage immediately, never submits slash text to the kernel, and appends provider account windows/details only through an injected AccountUsageFunc so internal/tui never owns config paths, credentials, HTTP clients, or provider-specific account policy. | validated | tui | small | operator, system | internal/tui/slash_usage_test.go::TestUsageSlashFetchesAccountUsageAsynchronously | When no local frame token telemetry exists, /usage keeps Hermes-style no API calls yet behavior and does not fetch provider account usage. When no account adapter is wired, /usage remains local-only. Adapter errors are rendered inside the Usage page as provider-unavailable evidence and update the status line without reopening a closed page. |
| 5 / 5.Q | Native TUI /clear and /new reset-session binding — Bind /clear and its /new alias in the native Bubble Tea TUI as local reset-session commands. They must be consumed before kernel submit, clear the editor, call the injected kernel ResetSession seam only while idle/failed, clear visible transcript/error/session state after a successful reset, and surface explicit degraded evidence when reset is unavailable or rejected. This slice ports the observable local reset behavior without claiming Hermes’ richer confirmation overlay or title-setting support. | validated | tui | small | - | internal/tui/slash_reset_test.go; cmd/gormes/tui_reset_slash_test.go | - |
| 5 / 5.Q | Native TUI /compact transcript toggle binding — Bind /compact [on|off|toggle] in the native Bubble Tea TUI as a local transcript view toggle. It must be consumed before kernel submit, update the visible transcript renderer immediately, return Hermes-style compact on/off or usage evidence, remain safe while a turn is running, and keep tiny-terminal auto-compact behavior unchanged. This slice intentionally does not persist display.compact/tui_compact through gateway config; that belongs to a later config-sync row. | validated | tui | small | - | internal/tui/slash_compact_test.go | - |
| 5 / 5.Q | Native TUI /skills read-only hub binding — Record the native Bubble Tea TUI /skills binding that consumes /skills input locally through the shared gateway skill-hub read model, keeps it available while a turn is in flight, and prevents /skills text from reaching the model while preserving read-only degraded evidence for mutating skill actions. | validated | tui | small | operator, system | internal/tui/slash_dispatch_behavior_test.go::TestHermesSlashDispatchBehavior_SkillsSlashRunsLocallyWhileBusy + internal/gateway/skills_command_test.go | Mutating /skills install/remove/update actions remain read-only/degraded with skills_manage_unavailable evidence until a dedicated skill-store mutation row lands; search/browse/list/inspect stay hermetic and never require provider or network credentials. |
| 5 / 5.Q | Native TUI /tools enable-disable binding — Port Hermes ui-tui’s local /tools enable|disable <name...> command into the native Bubble Tea TUI through an injected, fakeable tools-configuration adapter that renders usage for missing names, changed/unknown/missing-server evidence from the adapter response, and visible session-reset evidence when tool configuration changes require a reset. Other /tools subcommands remain recognized/unavailable or gateway-delegated until separately row-backed. | validated | tui | medium | operator, system | internal/tui/slash_tools_test.go::TestToolsSlashEnableDisableAdapter | When the tools adapter is nil, /tools enable|disable must be consumed with visible tools: configuration unavailable evidence and must not submit text to the model; default tests must use fakes and must not require live MCP servers, provider credentials, or network access. |
| 5 / 5.Q | Native TUI /voice status and toggle binding — Port Hermes ui-tui’s /voice [status|on|off|tts] command into the native Bubble Tea TUI through an injected, fakeable voice-toggle adapter that renders mode/TTS/record-key requirements, updates the TUI voice record-key state when the adapter response includes record_key, and preserves fallback Ctrl+B display without clobbering a cached configured binding when the response omits it. | validated | tui | medium | operator, system | internal/tui/slash_voice_test.go::TestVoiceSlashStatusUpdatesRecordKey | When the voice adapter is nil or returns unavailable requirements, /voice must render visible STT/TTS setup evidence and must not start live microphone capture, invoke Android/ADB, require provider credentials, or submit slash text to the model. |
| 5 / 5.Q | Native TUI /skin get-set binding — Port Hermes ui-tui’s /skin [name] command into the native Bubble Tea TUI through an injected, fakeable skin-config adapter that reads the current skin when no argument is provided, persists a requested skin when provided, and hot-swaps the native TUI skin state only after the adapter accepts the value. | validated | tui | small | operator, system | internal/tui/slash_skin_test.go::TestSkinSlashGetSetAdapter | When the skin adapter is nil or rejects a value, /skin must render visible unavailable/invalid evidence and must not mutate the current TUI skin, write config directly from internal/tui, or submit slash text to the model. |
| 5 / 5.Q | Gormes JSONL RPC mode over agent runtime events — Expose a local gormes JSONL RPC run mode for language-agnostic embedding. The protocol should accept prompt, steer, follow_up, abort, get_state, get_messages, session stats, model/thinking controls where existing runtime seams support them, and stream agent/tool/queue/compaction events as newline-delimited JSON with strict LF framing. It should reuse Gormes kernel/API-server event models and must not require a web server, Pi subprocess, or live provider in tests. | validated | gateway | medium | operator, system | cmd/gormes/rpc_mode_test.go | - |
| 5 / 5.Q | Native TUI voice record key uses voice toggle adapter — Close the remaining native-TUI voice-key routing gap without claiming live microphone parity: when the configured Hermes-compatible voice.record_key is pressed, the Bubble Tea model must invoke the same injected VoiceToggleFunc used by /voice on|off|tts|status, update visible status/detail rows from that adapter, and never submit the keypress text to the model. If no adapter is wired or the adapter reports unavailable audio, the UI must show typed unavailable evidence instead of the current hard-coded key-only message. This slice does not start microphone capture, STT streaming, or TTS playback. | validated | tui | small | operator, system | internal/tui/hermes_keybindings_test.go::TestHermesKeybindings_VoiceRecordTeaKeyUsesVoiceToggleAdapter | Native TUI voice capture remains degraded until a speech runtime row wires mic/STT/TTS. The record key should still route through the voice adapter so status, record-key display, and unavailable-audio details are one source of truth rather than a separate hard-coded voice recording toggle unavailable path. |
| 5 / 5.R | Execution-mode resolver + config precedence — Gormes exposes a pure execute-code mode resolver that returns Hermes-compatible project or strict from config value and default in that precedence order; unknown, empty, or nil-like values fall back to the default with redacted warning evidence, and the token set is frozen before cwd/interpreter/runtime wiring lands. | validated | tools | small | operator, system | internal/tools/execute_code_mode_test.go | Unknown, empty, or nil-like mode values produce execute_code_mode_invalid evidence and fall back to the configured default without constructing a sandbox, opening files, or running code. |
| 5 / 5.R | Strict-mode CWD + interpreter parity — Strict-mode code execution preserves the current Gormes security-first sandbox contract: isolated tmpdir CWD, canonical interpreter resolved via LocalCodeSandbox.resolveRuntime, the existing 5.K blocked-result envelope (status, error, filesystem_access, network_access) is byte-identical, and relative imports plus filesystem access outside tmpdir continue to fail. Unit tests drive resolution via injected env only, not the real host. The contract is pinned before project-mode lands so the strict-to-project diff is measurable. | validated | tools | small | system, operator | internal/tools/execute_code_strict_mode_test.go | If strict-mode resolution fails (no interpreter found), the 5.K blocked-result envelope returns execute_code_unavailable instead of silently falling back to a permissive mode. |
| 5 / 5.R | Project-mode CWD + active venv detection — Gormes adds a small project-mode execute-code sandbox contract parallel to the completed strict-mode contract: shell snippets run from the session working directory resolved from TERMINAL_CWD or the process cwd, active virtualenv/conda Python detection is frozen as a pure resolver for future Python-runtime decisions, and filesystem/network guard blocking plus the 5.K blocked-result envelope remain unchanged. This slice must not wire project mode as the default or expose Python execution. | validated | tools | small | operator, system | internal/tools/execute_code_project_mode_test.go | If TERMINAL_CWD is unset or invalid, project mode falls back to the process cwd and then to the staging tmpdir; if VIRTUAL_ENV/CONDA_PREFIX have no usable Python, the resolver falls back to the supplied system interpreter path without running or exposing Python. |
| 5 / 5.R | Default mode selection + config cut-over — Gormes cuts execute_code over to config-driven project/strict mode selection without exposing Python execution: [code_execution].mode in config.toml (or code_execution.mode in config.yaml fallback) selects project or strict via the existing resolver, the built-in default is Gormes-owned strict for the current shell-only sandbox, project mode remains opt-in, invalid config falls back to strict with redacted evidence, and the 5.K blocked-result envelope stays byte-identical across modes. | validated | tools | small | operator, system | internal/tools/execute_code_mode_cutover_test.go; internal/config/config_test.go; cmd/gormes/registry_test.go | Invalid or empty code_execution.mode values select strict mode with execute_code_mode_invalid evidence; no tool execution, cwd selection, interpreter probing, or live config mutation is needed to report the degraded state. |
| 5 / 5.S | 5-type loop detector — Hard loop, failing loop, text repetition, no-action, same-tool detection with configurable thresholds | validated | orchestrator | medium | operator, system | internal/core/agent/loop_detector_test.go | Loop events are emitted with type and evidence; agent can break loops or escalate to operator |
| 5 / 5.T | go-browser-harness doctor subcommand — go-browser-harness doctor subcommand with endpoint health checks, env reporting, redacted diagnostics | validated | tools | small | operator, system | go-browser-harness/pkg/harness/doctor_test.go | Doctor prints what it can without CDP; missing endpoint is reported clearly |
| 5 / 5.U | Pre-execution command classification — Classify every tool command as safe (whitelist), unsafe (blacklist), or uncertain (needs sandbox snapshot) before execution. Safe commands run directly. Unsafe commands are blocked. Uncertain commands trigger snapshot/rollback wrapper. | validated | tools | medium | system | - | - |
| 5 / 5.U | Transactional tool execution with snapshot/rollback — Wrap uncertain in-process tool calls with a bounded workspace filesystem snapshot before execution; successful calls commit by discarding the snapshot, while failed calls restore modified, deleted, and newly-created files before the executor returns. | validated | tools | medium | system | - | - |
| 5 / 5.U | Sandbox isolation depth selection — Operator can select sandbox isolation depth: process-level (fast, weaker isolation), container-level (Docker/gVisor, balanced), or VM-level (Firecracker, strongest isolation). Default is process-level with transactional rollback. | validated | tools | medium | operator | - | - |
| 5 / 5.V | Event bus core: pub/sub interface + in-process implementation — A unified pub/sub event bus decoupling all subsystems (TUI, gateway channels, agent loop, tools, memory). Events are typed, timestamped, and carry provenance. Core implementation is in-process with topic-based routing. No external dependency (no Kafka/Redis required for default). | validated | orchestrator | medium | system | - | - |
| 5 / 5.V | Gateway channel adapters publish to event bus — Inventory parent for binding gateway channel adapters to the shared event bus one channel at a time. Child slices publish channel-neutral message events with provenance while keeping channel-specific translation inside adapters; this parent must not be assigned directly. | validated | gateway | umbrella | system | - | - |
| 5 / 5.V | Gateway outbound sends publish message-sent events — Gateway manager successful outbound sends publish channel-neutral gateway.message.sent events through an injected EventDispatcher. Existing channel Send/SendReply delivery remains the source of truth; the bus event is post-delivery provenance for TUI, dashboard, audit, and future outbound subscribers, and event publication must not block user-visible sends. | validated | gateway | small | system, gateway | internal/gateway/event_dispatch_manager_test.go | If the EventDispatcher is nil, unavailable, or fails to publish, the channel send remains successful and the manager records only local debug evidence; no second event bus is started. |
| 5 / 5.V | Weixin gateway event-bus adapter — Weixin inbound events can be mirrored onto the shared event bus through a Go-native adapter that publishes the channel-neutral gateway.message.received topic with Weixin provenance, parsed command kind/body, chat type, chat/user/message identifiers, and trace_id without changing Weixin long-poll bootstrap, context-token reply continuity, content deduplication, media handling, or normal inbox submit delivery. | validated | gateway | small | system, gateway | internal/channels/weixin/bus_adapter_test.go | If the dispatcher or event bus is unavailable, the adapter returns weixin_event_bus_unavailable evidence to the caller and does not block the existing Weixin inbox submit path. |
| 5 / 5.V | WeCom gateway event-bus adapter — WeCom inbound events can be mirrored onto the shared event bus through a Go-native adapter that publishes the channel-neutral gateway.message.received topic with WeCom provenance, parsed command kind/body, chat type, chat/user/message identifiers, and trace_id without changing WeCom websocket/callback bootstrap, reply-vs-push routing, or normal inbox submit delivery. | validated | gateway | small | system, gateway | internal/channels/wecom/bus_adapter_test.go | If the dispatcher or event bus is unavailable, the adapter returns wecom_event_bus_unavailable evidence to the caller and does not block the existing WeCom inbox submit path. |
| 5 / 5.V | Telegram gateway event-bus adapter — Telegram inbound events can be mirrored onto the shared event bus through a Go-native adapter that publishes the channel-neutral gateway.message.received topic with Telegram provenance, parsed command kind/body, chat/user/message/thread identifiers, and trace_id without changing Telegram polling or gateway turn execution. | validated | gateway | small | system, gateway | internal/channels/telegram/bus_adapter_test.go | If the dispatcher or event bus is unavailable, the adapter returns telegram_event_bus_unavailable evidence to the caller and does not block the existing inbox submit path. |
| 5 / 5.V | Discord gateway event-bus adapter — Discord inbound events can be mirrored onto the shared event bus through a Go-native adapter that publishes the channel-neutral gateway.message.received topic with Discord provenance, parsed command kind/body, guild/channel/thread/message/user identifiers, and trace_id without changing Discord gateway session handling or normal inbox submit delivery. | validated | gateway | small | system, gateway | internal/channels/discord/bus_adapter_test.go | If the dispatcher or event bus is unavailable, the adapter returns discord_event_bus_unavailable evidence to the caller and does not block the existing Discord inbox submit path. |
| 5 / 5.V | Slack gateway event-bus adapter — Slack inbound events can be mirrored onto the shared event bus through a Go-native adapter that publishes the channel-neutral gateway.message.received topic with Slack provenance, parsed command kind/body, channel/thread/message/team/user identifiers, and trace_id without changing Slack Socket Mode, channel-shim inbox delivery, or kernel turn execution. | validated | gateway | small | system, gateway | internal/slack/bus_adapter_test.go | If the dispatcher or event bus is unavailable, the adapter returns slack_event_bus_unavailable evidence to the caller and does not block the existing Slack inbox submit path. |
| 5 / 5.V | WhatsApp gateway event-bus adapter — WhatsApp inbound events can be mirrored onto the shared event bus through a Go-native adapter that publishes the channel-neutral gateway.message.received topic with WhatsApp provenance, parsed command kind/body, canonical chat/user identifiers, chat kind, message id, and trace_id without changing WhatsApp bridge/native runtime startup, QR pairing, self-chat guards, or normal inbox submit delivery. | validated | gateway | small | system, gateway | internal/channels/whatsapp/bus_adapter_test.go | If the dispatcher or event bus is unavailable, the adapter returns whatsapp_event_bus_unavailable evidence to the caller and does not block the existing WhatsApp inbox submit path. |
| 5 / 5.V | Agent turn and tool execution events on bus — Agent turns (start, thought, action, observation, complete, error) and tool executions (start, output/progress, complete, error) are published as structured events on the bus through small Go emitters/wrappers. This unblocks the P1 full-flow gateway integration proof while preserving Hermes’ callback-shaped tool/status surface for TUI, gateway, dashboard, and audit subscribers. | validated | orchestrator | medium | system | - | - |
| 5 / 5.V | Event bus integration test: full message flow — End-to-end test proving a Telegram message flows through: channel adapter → event bus → agent turn processing → tool execution → reply event → channel adapter → delivery. Validates decoupling: TUI subscriber also receives all events without modifying agent or gateway code. | validated | gateway | small | operator, system | - | - |
| 5 / 5.W | Hermes i18n static-message port — Gormes ports Hermes’ lightweight i18n system for static user-facing messages: approval prompts, gateway slash command replies, and restart-drain notices. Agent-generated output, log lines, error tracebacks, tool outputs, and slash-command descriptions remain English-only. Language resolution: explicit lang arg → GORMES_LANGUAGE env var → display.language config key → en fallback. Supported languages: en, zh, ja, de, es, fr, tr, uk. Missing catalog keys fall back to English; broken catalogs return key paths without crashing. Catalog files live at locales/ | validated | orchestrator | medium | operator, gateway, system | - | - |
| 5 / 5.W | Hermes i18n expanded locale catalog parity — Gormes updates the completed static-message i18n port to match current Hermes’ expanded locale catalog: supported languages are en, zh, zh-hant, ja, de, es, fr, tr, uk, af, ko, it, ga, pt, ru, and hu; aliases normalize the same current Hermes regional/common names; and every bundled locale file has the same key set with English fallback behavior unchanged. | validated | orchestrator | small | operator, gateway, system | - | - |
| 6 / 6.A | Hermes background review fork lifecycle — After turns that trigger memory or skill review, Gormes can launch a quiet background review fork that inherits the active runtime credentials, runs only memory and skills toolsets, writes to shared stores without mutating parent history, emits one attributed self-improvement summary, and always cleans up approval callbacks and memory providers | validated | orchestrator | medium | operator, child-agent, system | internal/llm/background_review_test.go | Learning-loop status reports background_review_unavailable, background_review_runtime_unavailable, background_review_denied_toolset, or background_review_cleanup_failed instead of running a full-tool child agent silently. |
| 6 / 6.A | Deterministic learning-loop trigger signals — Add a pure deterministic trigger-signal scorer for the learning loop before any live LLM scoring: derive memory-review and skill-review decisions from transcript/user-turn counts, tool-iteration counts, retry/error markers, edit/patch counts, and optional explicit operator feedback. The scorer must emit explainable reason codes and redacted feature counts that can drive the existing background review fork without storing raw prompt text, launching a provider call, promoting skills, or mutating memory by itself. | validated | orchestrator | small | operator, child-agent, system | internal/llm/learning_loop_signal_test.go | Without this slice, Gormes can run the background review fork only from coarse interval counters and cannot explain why a turn should trigger memory/skill review before later extractor, scorer, or promotion logic runs. |
| 6 / 6.B | LLM-assisted pattern distillation — Skill distillation review prompts survey existing skills first, prefer class-level generalization over near-duplicate creation, reject transient environment failures and negative tool claims as durable skill constraints, and preserve a clear no-op escape path before any generated draft can be promoted | validated | skills | small | operator, system | internal/llm/background_review_test.go::TestBackgroundReviewFork_SkillPromptRejectsTransientEnvironmentFailures | Skill review status reports review_prompt_unavailable, overlap_detected, no_skill_to_save, or draft_requires_review instead of auto-promoting narrow one-off skills. |
| 6 / 6.C | SKILL.md frontmatter validation guard — Skill storage exposes a Gormes-owned frontmatter validator that reports malformed SKILL.md files before parser/store promotion or prompt injection can use them | validated | skills | small | operator, system | internal/skills/frontmatter_validation_test.go | Skill status reports skill_frontmatter_invalid with validation codes and excludes the skill from prompt injection instead of silently accepting malformed YAML/frontmatter. |
| 6 / 6.C | Hermes creative skill metadata compatibility — Skill storage fixtures preserve Hermes creative SKILL.md routing metadata for claude-design, popular-web-designs, and design-md as distinct reviewed skills, including short descriptions, version/author/license fields, hermes tags, and related_skills, without collapsing them into one generic design skill | validated | skills | small | operator, system | internal/skills/creative_design_metadata_test.go | Skill status reports creative_skill_metadata_incomplete or creative_skill_route_ambiguous when design skills cannot be parsed or distinguished, and excludes invalid/unreviewed creative drafts from prompt injection. |
| 6 / 6.C | Portable SKILL.md format — Reviewed skill-as-code storage format | validated | skills | medium | operator, system | internal/skills SKILL.md metadata, provenance, and review-state fixtures | Skill status excludes unreviewed or invalid drafts from prompt injection and records resolver or metadata failures. |
| 6 / 6.C | Hermes v0.14 optional skill catalog refresh — Refresh the Gormes skill catalog and metadata compatibility checks against Hermes v0.14 optional skills, including devops/pinggy-tunnel, research/darwinian-evolver, research/osint-investigation, and the updated Notion skill, without blindly copying unsupported Python scripts into runtime packages. | validated | skills | small | - | internal/skills optional skill catalog fixtures | - |
| 6 / 6.C | Hermes skill catalog strict-fidelity classifier — Build a report-only Hermes skill catalog strict-fidelity classifier for cmd/repoctl hermes-contract-inventory: scan the current hermes-agent/skills and hermes-agent/optional-skills trees, group exact SKILL.md, DESCRIPTION.md, and supporting-file examples into deterministic surface families (bundled catalog metadata, optional catalog metadata, category descriptions, prerequisites/readiness metadata, triggers/tags/related skills, support assets, sync/reset boundaries, and Python/script-only examples), and attach each family to an existing Gormes skill-store row, planned child row, source-pair entry, explicit exclusion, or owned-divergence note. The classifier is evidence-only: it must not copy bundled skill content into Gormes, execute support scripts, mutate internal/skills behavior, or claim strict skill-catalog parity without row/source-pair/test proof. | validated | docs | medium | operator, system | internal/repoctl/hermes_contract_inventory_skills_test.go | Until this classifier lands, strict-fidelity reports can only show Hermes bundled/optional skill files as broad unmapped buckets, so Gormes must avoid claiming complete skill catalog, optional-skill, or support-file parity. |
| 6 / 6.D | Hybrid lexical + semantic lookup — Reviewed skills are selected by a deterministic lexical plus semantic scorer while disabled and unreviewed drafts remain excluded from prompt injection | validated | skills | small | operator, system | internal/skills/hybrid_lookup_test.go | Skill status reports semantic lookup unavailable or review-gated instead of silently injecting low-confidence or unreviewed skills. |
| 6 / 6.D | Source-aware retrieval damping fixtures — Skill and memory retrieval can prefer curated/reviewed sources over bulky chat or raw imports while preserving high-detail temporal lookup and explaining every source boost or exclusion | validated | skills | small | operator, system | internal/skills/source_aware_retrieval_test.go | Retrieval status reports source_boost_unavailable, source_excluded, source_dampened, or temporal_bypass evidence instead of silently letting transcript bulk swamp curated knowledge. |
| 6 / 6.D | Delta-bounded skill and memory maintenance passes — Learning-loop maintenance accepts changed source IDs, per-source sync-anchor evidence, and stale semantic-refresh hints so skill/memory extraction, scoring, and embedding refresh can no-op or process only changed records instead of rescanning the whole local corpus | validated | skills | small | operator, system | internal/skills/delta_maintenance_test.go | Maintenance status reports delta_noop, delta_sources_missing, source_anchor_missing, source_anchor_global_fallback, stale_refresh_empty, or full_scan_fallback evidence instead of silently doing whole-corpus work. |
| 6 / 6.D | Code Cathedral II code-context retrieval fixtures — Learning-loop retrieval can consume code-context evidence with parent-scope and call-edge explanations without importing an external TypeScript indexer | validated | skills | small | operator, system | internal/skills/code_context_retrieval_test.go | Skill retrieval reports code-context unavailable rather than pretending call-graph or parent-scope evidence exists. |
| 6 / 6.E | Hermes curator auxiliary model routing slot — Gormes treats curator review as a first-class auxiliary model task: auxiliary.curator.{provider,model,base_url,api_key,timeout,extra_body} is readable/writable through native config/model surfaces, fully set provider+model overrides win over the main model, auto or partial overrides fall back to the main provider/model without leaking stray auxiliary credentials, and legacy curator.auxiliary.{provider,model,base_url,api_key} remains readable with deprecation evidence | validated | provider | small | operator, system | internal/config/curator_auxiliary_test.go + internal/llm/curator_auxiliary_test.go | Curator model status reports curator_auxiliary_auto_main, curator_auxiliary_partial_fallback, curator_auxiliary_legacy_config, curator_auxiliary_secret_stripped, or curator_auxiliary_slot_missing instead of silently using the wrong model or leaking slot credentials. |
| 6 / 6.E | Hermes curator state transitions and run reports — Gormes maintains Hermes-compatible curator state for agent-created skills: first run defers and seeds state, configured interval/paused gates control later runs, active/stale/archived transitions use last activity, pinned and non-agent-created skills are never auto-touched, dry-run skips mutations, every run records recoverable report/backup evidence, removed skills are classified as consolidated or pruned, and cron job skill links are rewritten or restored when skill names change | validated | skills | medium | operator, system | internal/skills/curator_test.go | Curator status reports curator_disabled, curator_paused, curator_no_candidates, curator_dry_run_report_only, curator_backup_failed, curator_report_write_failed, curator_classification_uncertain, or curator_cron_rewrite_failed instead of hiding skipped or partial maintenance. |
| 6 / 6.E | Hermes curator rename summary notice — Gormes surfaces Hermes’ current curator rename-map behavior anywhere operators see the last curator run: native curator runs append a bounded multi-line archived-skill map showing consolidated old -> umbrella and pruned stale skills, gormes curator status indents multi-line summaries as one field, and gormes update prints the most recent multi-line curator summary exactly once per curator run before stamping it shown. | validated | skills | small | operator, system | internal/skills/curator_test.go + cmd/gormes/curator_test.go + cmd/gormes/update_command_test.go | Curator summary rendering reports no rename map for no-op runs, caps very large archive maps with an omitted-count line, preserves JSON status output, and never lets curator state-load or summary-stamp errors fail the update command. |
| 6 / 6.E | Hermes review prompt transient-environment guard — Gormes’ native curator review prompt must preserve Hermes’ current class-first learning guard: background review may capture durable reusable fixes, but must not create or harden skills from transient setup failures, post-migration path mismatches, missing binaries, unconfigured credentials, negative tool capability claims, resolved session-specific errors, or one-off task narratives. | validated | skills | small | operator, system | internal/skills/curator_test.go::TestCuratorPromptTransientEnvironmentGuard | Without this guard, a temporary local setup failure such as a missing browser dependency can become a durable self-cited skill constraint that keeps Gormes refusing a working tool after the environment is repaired. |
| 6 / 6.E | Skill effectiveness scoring — Add the first deterministic skill-effectiveness layer on top of existing usage counters: persist append-only skill selection/outcome records, accept explicit operator feedback as auditable evidence, and expose a pure aggregate scorer that can explain positive, neutral, stale, and negative skill signals without automatically promoting, demoting, editing, or archiving skills. | validated | skills | medium | operator, gateway, child-agent, system | internal/skills/effectiveness_test.go with fake clock, temp skill root, deterministic outcome records, and no model/provider calls | Without this slice, Gormes can count skill use/view/patch activity and run curator lifecycle transitions, but it cannot connect selected skills to turn outcomes or operator feedback before future promotion/demotion logic. |
| 6 / 6.F | Hermes skill_manage support-file and curator intent actions — Gormes extends the native skill_manage tool to match Hermes’ current mutating skill contract: create/edit/patch/delete still operate on SKILL.md, patch can target supporting files, write_file/remove_file manage bounded supporting files, delete accepts curator intent through absorbed_into, pinned skills reject every write action with an unpin hint, optional agent-created guard scans can roll back blocked writes, and background-review-origin creates are the only creates marked agent-created for later curator maintenance | validated | tools | medium | operator, child-agent, system | internal/tools/skill_manager_test.go + internal/skills/usage_test.go + internal/core/subagent/skill_provenance_test.go | skill_manage returns skill_manage_unsupported_action, skill_manage_invalid_support_path, skill_manage_pinned_refusal, skill_manage_absorbed_target_missing, skill_manage_guard_blocked, or skill_manage_background_origin_unavailable instead of silently mutating the wrong file or bypassing curator intent. |
| 6 / 6.F | Hermes curator command surface — gormes curator replaces the current unavailable placeholder with Hermes-compatible status, run, pause, resume, pin, unpin, backup, rollback, and restore commands over the native curator state/report engine | validated | skills | small | operator, system | cmd/gormes/curator_test.go + internal/cli/command_registry_policy_test.go | The CLI reports curator_unavailable, curator_disabled, curator_no_agent_created_skills, curator_backup_unavailable, or curator_rollback_cancelled with actionable hints instead of silently doing nothing. |
| 6 / 6.F | Hermes curator archive/list/prune CLI catch-up — Gormes extends the already-shipped native gormes curator surface to match the current Hermes curator maintenance commands added after the original command-surface row: archive <skill> manually archives one unpinned agent-created skill, list-archived lists recoverable archived skill names, and prune --days N [--dry-run|-y] previews or bulk-archives unpinned agent-created skills idle for at least N days. The commands must use the native skills usage/archive state and keep bundled, hub-installed, pinned, recent, and already-archived skills out of destructive paths. | validated | skills | small | operator, system | cmd/gormes/curator_test.go + cmd/gormes/hermes_cli_parity_test.go + internal/cli/command_registry_policy_test.go | The CLI reports curator_archive_refused_pinned, curator_archive_not_agent_created, curator_archive_missing, curator_prune_invalid_days, curator_prune_empty, or curator_prune_cancelled instead of silently mutating the wrong skill tree. |
| 6 / 6.F | TUI + Telegram browsing — Complete the remaining Hermes-compatible skills.manage browsing path for native TUI and Telegram operators: keep the existing /skills list and inspect behavior, add source-backed browse/search/read-only management routing through the gateway command surface, render bounded channel-safe skill summaries, and return explicit row-backed unavailable evidence for mutating actions such as install, edit, disable, or review. | validated | gateway | medium | operator, gateway, system | internal/gateway/skills_command_test.go; internal/channels/telegram/bot_test.go; internal/tui/slash_dispatch_behavior_test.go | Without this slice, installed skill list/inspect works locally but Hermes-style browse/search from TUI or Telegram remains partial and operators may fall back to ad-hoc prompts or raw CLI access. |
| 6 / 6.F | Native skills list/view tool surface — Gormes registers Hermes-compatible read-only skills_list and skill_view native tools by default. skills_list returns available skills, categories, count, source/trust/status metadata, and a skill_view hint. skill_view loads SKILL.md content, frontmatter metadata, linked references/templates/assets/scripts, and safe linked-file reads while rejecting path traversal without leaking file contents. | validated | tools | small | operator, system | internal/tools/skills_tools_test.go + cmd/gormes/registry_test.go | Missing skills or linked files return bounded JSON errors with available skills/files hints. Invalid traversal requests are rejected before filesystem reads. |
| 6 / 6.F | TUI and gateway direct URL skill install binding — Bind the already-shipped direct-URL skill install policy to the channel-local /skills install <https://.../SKILL.md> --name <safe-name> path for native TUI and gateway surfaces. The slice must reuse the existing skills.PerformURLInstall quarantine/scan/store contract, keep non-interactive surfaces prompt-free, require explicit safe names when URL/frontmatter cannot provide one, and return channel-safe install or degraded evidence without submitting /skills install text to the model. This ports Hermes’ chat slash install behavior while adapting Pi’s package-source safety lessons (explicit source, scope, and review warning) to a Go-native skills-only slice; broader package resources, GitHub hub sources, update, uninstall, and skill editing remain out of scope. | validated | skills | medium | operator, gateway, system | internal/gateway/skills_command_test.go::TestHandleSkillsCommandInstallDirectURL; internal/tui/slash_dispatch_behavior_test.go::TestHermesSlashDispatchBehavior_SkillsInstallRunsLocally | Missing URL install dependencies, unsafe —name/—category values, missing-name evidence, fetch failures, quarantine/scan failures, and store write failures return redacted skills_install_* / url_skill_* text and leave the active skill store unchanged. Telegram replies stay plain text and TUI status messages stay local. |
| 6 / 6.F | Hermes/Pi external skill directories config binding — Port the Hermes skills.external_dirs contract into Gormes’ Go-native skills runtime and shape it with Pi’s multi-location skills discovery lesson. Gormes config should accept a list of extra skill roots, expand ~ and environment variables, resolve relative paths against Gormes home/profile state rather than the process cwd, skip missing or duplicate roots with typed evidence, preserve local active-skill precedence, and thread the resolved roots into prompt-building, gormes skills list, TUI/gateway /skills list|inspect, and future skill_manage mutation boundaries without reading live Hermes/Pi homes. | validated | skills | medium | operator, gateway, system | internal/config/config_test.go::TestSkillsExternalDirs; internal/skills/list_test.go::TestListInstalledSkillsExternalDirs; internal/llm/skills_prompt_test.go::TestSkillsPromptExternalDirsOrderingAndStatusFiltering; internal/gateway/skills_command_test.go::TestHandleSkillsCommandExternalDirs | Invalid or missing external skill roots are skipped with skills_external_dir_skipped evidence; unreadable roots fail only the affected skills command or prompt build, not provider startup. Duplicate roots and the local active skills root are ignored so external directories cannot shadow local active skills unexpectedly. |
| 6 / 6.F | Pi-style prompt template TUI expansion seam — Add a Gormes-owned prompt-template catalog and native TUI slash expansion path inspired by Pi prompt templates while preserving Hermes slash-command and skill-invocation precedence. Markdown templates discovered from Gormes-controlled prompt roots expand /name args... into editable prompt text, support frontmatter description and argument-hint, perform deterministic positional substitution, and never execute code, install packages, read live Pi homes, or submit expanded text to the model until the operator confirms with a normal Enter. | validated | skills | medium | operator, system | internal/tui/prompttemplates/catalog_test.go::TestPromptTemplateDiscoveryAndExpansion; internal/tui/prompt_template_slash_test.go::TestPromptTemplateSlashExpansionSeedsEditorWithoutSubmit | Missing prompt roots, unreadable files, malformed frontmatter, unsafe filenames, templates that collide with built-in slash commands, and invalid argument placeholders are skipped or rendered as prompt_template_* evidence without leaking absolute paths or submitting slash text to the model. |
| 6 / 6.F | Prompt template CLI explicit roots and opt-out binding — Extend the Pi-style prompt-template donor slice with CLI startup controls for the native TUI: --prompt-template <path> may be repeated with Markdown files or non-recursive directories, and --no-prompt-templates disables default and explicit prompt-template discovery for that TUI session. The flags must affect only local native TUI prompt-template expansion, not oneshot prompts, skills, gateway/Telegram slash commands, or Pi package/extension loading. | validated | skills | small | operator, system | cmd/gormes/prompt_templates_test.go::TestTUIWiresPromptTemplateFlags; internal/tui/prompttemplates/catalog_test.go::TestPromptTemplateDiscoveryAndExpansion | Missing or unreadable explicit paths produce prompt_template_skipped evidence in the catalog and do not fail TUI startup; —no-prompt-templates returns an empty catalog even when explicit paths are supplied. |
| 6 / 6.G | 6 typed memory categories with confidence scoring — Identity, preference, goal, habit, episode, reflection with confidence/durability scoring and conflict resolution | validated | memory | large | operator, system | internal/goncho/typed_memory_test.go | Memory falls back to session-based storage; typed memory unavailable but session history preserved |
| 6 / 6.H | SKILL.md metadata.when/loaded/placement schema — YAML frontmatter supports metadata.when (conditional activation), metadata.loaded (auto-load), metadata.placement (system/onscreen/admin); hierarchical routing | validated | skills | small | operator, system | internal/skills/metadata_test.go | Skills load without metadata placement; all skills treated as system scope |
| 6 / 6.I | Regex-based auto-link extraction + brain-first lookup — Markdown links, wikilinks, qualified wikilinks auto-extracted; typed inference; brain-first 5-step lookup | validated | memory | large | operator, system | internal/goncho/auto_link_test.go | Links not auto-extracted; lookup skips local DB/graph and goes directly to LLM/external API |
| 6 / 6.J | Memory operations as agent-callable tools — Expose memory operations as tools the agent can call directly: store_memory, retrieve_memory, update_memory, summarize_memories, forget_memory. The agent decides what to remember, not a heuristic pipeline. This is the core AgeMem (2026) pattern. | validated | memory | large | operator, system | internal/goncho/memory_tools_test.go; internal/tools/memory_tool_descriptors_test.go | - |
| 6 / 6.J | Agent-controlled memory retention with importance scoring — The agent assigns importance scores to memories at store time and can adjust them later. Retention policy uses importance × recency decay curve. Low-importance old memories are candidates for summarization or forgetting. The agent periodically reviews and prunes. | validated | memory | medium | operator | - | - |
| 6 / 6.J | Cross-session memory continuity — Memories persist across sessions with continuity guarantees. When a new session starts, the agent retrieves relevant past memories automatically (not all memories — relevance-filtered). Agent detects contradictions between old and new information and resolves them. | validated | memory | medium | operator | - | - |
| 6 / 6.K | Prompt evaluation harness — A harness that evaluates agent prompt/behavior variants against a set of test scenarios. Produces quantitative scores (task success, tool selection accuracy, response quality) for each variant. Enables data-driven prompt optimization without LLM-as-judge circularity. | validated | orchestrator | large | operator | - | - |
| 6 / 6.K | Iterative prompt mutation and scoring loop — Given a base prompt and evaluation harness, automatically generate prompt variants (mutations), score them, and select the best. Iterate until convergence or budget. Mutations target: tool selection heuristics, response style, task decomposition strategies. | validated | orchestrator | medium | operator | - | - |
| 6 / 6.K | Behavioral pattern extraction from session logs — Mine session logs and tool execution audits for behavioral patterns: which tool sequences succeed vs fail, which reasoning patterns precede good outcomes, which response styles correlate with user satisfaction. Patterns feed into the self-evolution loop as candidate mutations. | validated | orchestrator | medium | operator | - | - |
| 6 / 6.L | Skill code execution runtime — Skills are not just markdown instructions — they contain executable code that can be run in a sandboxed environment. This mirrors Voyager’s code-as-action pattern: skills are validated, sandboxed, and can be composed by the agent at runtime. | validated | skills | large | operator, system | - | - |
| 6 / 6.L | Skill dependency resolution and composition — Skills can declare dependencies on other skills. The runtime resolves the dependency graph before execution. The agent can compose skills by chaining: output of Skill A feeds into input of Skill B. Dependencies are validated at load time. | validated | skills | medium | operator | - | - |
| 6 / 6.L | Agent personalities + enhanced display config — Gormes gains Hermes-parity config surface for agent personalities (12 built-in personas with custom system prompts), display preferences (show_reasoning, streaming, bell_on_complete, compact, cleanup_progress, busy_input_mode, background_process_notifications), and agent runtime settings (max_turns, reasoning_effort, gateway_timeout, api_max_retries, verbose). Personality prompts are injected into BuildSystemPrompt. | validated | orchestrator | small | operator, system | internal/config/config.go | When no personality is configured, the agent uses the default identity prompt without personality injection. |
| 6 / 6.L | Session auto-reset + STT config parity — Gormes gains Hermes-parity session reset (auto-clear sessions on inactivity/daily boundary) and STT config surface. Session reset checks the configured policy before processing each message and auto-resets stale sessions. STT config exposes enabled/provider/local model/openai model in config.toml. | validated | orchestrator | small | operator, system | internal/gateway/manager.go | When session_reset policy is ‘none’, sessions persist indefinitely (legacy behavior). When STT is not configured, transcription defaults to local provider with base model. |
| 7 / 7.A | Signal transport/bootstrap layer — Fakeable Signal bridge bootstrap binds the existing Signal Bot seam to a signal-cli HTTP JSON-RPC/SSE client without live signal-cli: config requires SIGNAL_HTTP_URL and SIGNAL_ACCOUNT or explicit equivalents, acquires a platform lock keyed by account, health-checks /api/v1/check before starting, opens an SSE events stream with the account URL-encoded, treats comments as liveness, parses data envelopes into normalized inbound events, reconnects with jitter/backoff and stale-stream health evidence, sends JSON-RPC requests through an injected client, fetches attachments with getAttachment params {account,id}, and routes outbound direct/group sends with typing-stop and timestamp message IDs. | validated | gateway | medium | gateway, operator, system | internal/channels/signal/bootstrap_test.go | Missing config, failed health checks, platform-lock conflicts, SSE disconnects, invalid envelopes, RPC failures, attachment decode failures, and send failures return typed evidence such as signal_config_missing, signal_health_failed, signal_lock_busy, signal_sse_reconnect, signal_envelope_invalid, signal_rpc_failed, signal_attachment_unavailable, and signal_send_failed while redacting phone/account identifiers. |
| 7 / 7.A | Signal markdown bodyRanges + attachment rate scheduler — Signal outbound delivery preserves Hermes formatting and rate-limit behavior before live signal-cli transport: Markdown text is converted to plain text plus Signal bodyRanges for bold, italic, strikethrough, monospace, code blocks, and headings; snake_case, paths, bullet markers, and cross-line false positives are left untouched; offsets and lengths are computed in UTF-16 code units. Attachment sends pass through a token-bucket scheduler with capacity 50, default retry_after 4s feedback, FIFO acquire ordering under concurrency, estimate_wait reporting, retry-after calibration of refill rate per token, token reset on throttling feedback, and resettable singleton access for tests/runtime reuse. | validated | gateway | medium | gateway, operator, system | internal/channels/signal/format_test.go; internal/channels/signal/rate_limit_test.go | If markdown parsing or attachment scheduling is unavailable, Signal outbound falls back to plain text and conservative serialized attachment sends with signal_format_unavailable or signal_attachment_rate_limit_unavailable evidence; malformed Markdown never corrupts bodyRanges or blocks text sends. |
| 7 / 7.C | Matrix shared-chassis bot seam — Gormes adds a Matrix-specific seam over internal/channels/threadtext before live Matrix auth/sync: normalize room messages into gateway events with canonical thread roots, resolve reply targets for flat vs thread mode, model placeholder/edit/reaction hooks as fakeable callbacks, and preserve mention/free-room/DM gating inputs without importing a Matrix SDK. | validated | gateway | medium | gateway, operator | internal/channels/matrix/seam_test.go | Matrix status reports matrix_transport_unavailable while seam fixtures still prove routing, thread, and hook contracts; no live homeserver is required. |
| 7 / 7.C | Matrix self/bridge sender drop helper — Matrix adapter exposes pure sender-classification helpers that drop bot self-events case-insensitively, fail closed when the bot user_id is unresolved, and treat appservice/system bridge senders with empty or leading-underscore localparts as non-user identities before pairing or gateway dispatch | validated | gateway | small | gateway, operator, system | internal/channels/matrix/sender_filter_test.go | Matrix status reports matrix_sender_filter_unavailable while the helper is missing; future Matrix runtime must drop self/bridge/system events instead of offering pairing codes or feeding echo-loop messages to the agent. |
| 7 / 7.C | Mattermost shared-chassis bot seam — Gormes adds a Mattermost-specific seam over internal/channels/threadtext before REST/websocket transport: parse posted-event payloads into gateway events, ignore self/system/duplicate posts, preserve root_id as canonical thread_id, model reply_mode=thread vs off for outbound root_id decisions, and keep upload/edit/status hooks fakeable. | validated | gateway | medium | gateway, operator | internal/channels/mattermost/seam_test.go | Mattermost status reports mattermost_transport_unavailable while seam fixtures prove event parsing, dedup, mention gating, and reply-target behavior without REST or websocket sessions. |
| 7 / 7.C | Matrix real client/bootstrap layer — Gormes adds a fakeable Matrix client/bootstrap boundary after the shared-chassis seam: config resolves homeserver/access-token or user/password auth, whoami/login learns the bot user_id, initial sync records joined rooms and next_batch, event handlers are registered for room messages/reactions/invites, sync loop restart/auth-stop behavior is represented as a pure runner contract, and media/E2EE hooks remain injectable seams rather than live SDK side effects. | validated | gateway | medium | gateway, operator, system | internal/channels/matrix/bootstrap_test.go | Matrix status reports matrix_config_missing, matrix_auth_failed, matrix_sync_unavailable, matrix_e2ee_unavailable, or matrix_transport_unavailable without starting a live homeserver connection in unit tests. |
| 7 / 7.C | Matrix E2EE device-id crypto-store binding — Matrix E2EE bootstrap binds the configured runtime device_id into the crypto store before account writes so Olm/megolm identity lookups stay device-scoped | validated | gateway | small | gateway, operator | internal/channels/matrix/crypto_store_test.go | Matrix status reports E2EE unavailable or stale device identity instead of starting encrypted sync with a blank or mismatched device_id. |
| 7 / 7.C | Mattermost REST/WS bootstrap layer — Gormes adds a fakeable Mattermost REST/websocket bootstrap boundary after the shared-chassis seam: config resolves MATTERMOST_URL/TOKEN, auth probes users/me, REST helpers sanitize response errors, websocket posted events feed the seam parser, reconnect policy is represented without sleeps, channel/user lookup is injectable, and file upload/edit/send paths are shaped with fake HTTP responses before any live Mattermost transport is enabled. | validated | gateway | medium | gateway, operator, system | internal/channels/mattermost/bootstrap_test.go | Mattermost status reports mattermost_config_missing, mattermost_auth_failed, mattermost_ws_unavailable, mattermost_upload_failed, or mattermost_transport_unavailable without opening real REST or websocket connections in unit tests. |
| 7 / 7.E | BlueBubbles iMessage bubble formatting parity — BlueBubbles outbound iMessage sends are non-editable, markdown-stripped, paragraph-split bubbles without pagination suffixes | validated | gateway | small | gateway, system | internal/channels/bluebubbles/bot_test.go | BlueBubbles remains a usable first-pass adapter, but long replies may still arrive as one stripped text send until paragraph splitting and suffix-free chunking are fixture-locked. |
| 7 / 7.E | Feishu transport/bootstrap layer — Gormes adds a fakeable Feishu/Lark transport bootstrap boundary before live SDK binding: config resolves app credentials, connection_mode selects webhook vs websocket, webhook URL verification and signature checks are pure helpers, websocket event handlers register message/reaction/card/customized processors, inbound events queue until the adapter loop is ready, and rich-text/card send failures return typed SendResult evidence with redacted tokens. | validated | gateway | medium | gateway, operator, system | internal/channels/feishu/bootstrap_test.go | Feishu status reports feishu_config_missing, feishu_signature_invalid, feishu_loop_not_ready, feishu_ws_unavailable, or feishu_send_failed without opening live websocket or webhook network calls in unit tests. |
| 7 / 7.E | Feishu native update prompt cards — Gormes ports Hermes’ Feishu gateway update-prompt card behavior before live SDK binding: detached update prompts render as native interactive Yes/No cards, store prompt state by prompt_id, resolve exactly once, persist the selected answer through an injected writer, and return a resolved-state card without falling back to plain-text prompt replies. | validated | gateway | small | gateway, operator | internal/channels/feishu/bootstrap_test.go::TestFeishuUpdatePrompt | If the card cannot be posted or resolved, Feishu update prompts degrade to typed feishu_update_prompt_unavailable evidence without reading live homes, opening Feishu network connections, or leaking prompt response paths. |
| 7 / 7.E | Feishu drive-comment rule + pairing seam — Gormes ports Hermes Feishu drive-comment rule resolution before comment replies can invoke an agent: config loads comments policy and per-document rules, exact document rules beat wiki rules, wiki rules beat wildcard/top defaults when supplied, each enabled/policy/allow_from field falls back independently, explicit empty allow_from does not fall through, invalid policies default to pairing, and pairing add/remove/list uses an atomic temp-root store. | validated | gateway | small | gateway, operator, system | internal/channels/feishu/comment_rules_test.go | Missing or malformed rules files return Feishu comment defaults with feishu_comment_rules_defaulted evidence; denied users return feishu_comment_denied without comment API side effects. |
| 7 / 7.E | Feishu drive-comment reply workflow — Gormes freezes Hermes Feishu drive-comment reply workflow behind fake clients before live SDK binding: parse drive.notice.comment_add_v1, accept only add_comment/add_reply events, reject self/wrong-receiver/empty-to-open-id/disabled-or-denied users without side effects, sanitize comment text with ampersand-first HTML escaping, optionally reverse-lookup wiki tokens before applying wildcard/top comment rules, fetch document metadata and comment batches through a fake Drive client, add an OK reaction before agent invocation, build whole-document versus local-thread prompt envelopes, and fall back from local reply_to_comment to whole-document add_whole_comment on Feishu code 1069302 with evidence. | validated | gateway | medium | gateway, operator, system | internal/channels/feishu/comment_workflow_test.go | Disabled rules, denied users, unresolvable wiki tokens, missing document metadata, reaction failure, batch-comment failure, or reply fallback are surfaced as typed Feishu comment workflow evidence; tests never invoke the live Feishu SDK, Drive API, or model runtime. |
| 7 / 7.E | DingTalk real SDK binding — Bind the Gormes DingTalk channel to a real DingTalk Stream Mode SDK (replacing the current stub/fake). Implement credential loading (AppKey/AppSecret from config.toml), receive loop via the SDK’s callback, send lifecycle, and reconnection with the existing retry seam. | validated | docs | small | operator, system | internal/channels/dingtalk/client_test.go | If the SDK is unavailable or credentials are missing, the channel reports dingtalk_sdk_unavailable evidence and the gateway skips only this channel. |
| 7 / 7.E | Yuanbao protocol envelope + markdown fixtures — Gormes parses Yuanbao websocket/protobuf-style envelopes and Markdown message fragments into gateway-neutral events using fixture data only | validated | gateway | small | gateway, system | internal/channels/yuanbao/proto_test.go | Yuanbao adapter status reports protocol_unavailable or markdown_parse_failed evidence instead of starting a live session with unparsed payloads. |
| 7 / 7.E | Yuanbao media/sticker attachment normalization — Yuanbao image, file, voice, and sticker payloads normalize into gateway attachments with bounded metadata and explicit unavailable evidence before live downloads are enabled | validated | gateway | small | gateway, system | internal/channels/yuanbao/media_test.go | Unsupported or oversized Yuanbao media produces yuanbao_media_unavailable evidence while preserving the text portion of the inbound event. |
| 7 / 7.E | Yuanbao gateway runtime + toolset registration — Yuanbao runtime registration exposes a disabled-by-default gateway platform and toolset surface over the existing shared gateway manager without widening default enabled tools | validated | gateway | medium | operator, gateway, system | cmd/gormes/yuanbao_gateway_test.go | Config/status reports yuanbao_disabled, credentials_missing, or yuanbao_runtime_unavailable instead of starting an unauthenticated live session. |
| 7 / 7.E | Microsoft Teams adapter plugin seam — Gormes ports Hermes’ Microsoft Teams platform as a disabled-by-default plugin-backed channel seam before live Bot Framework binding: config resolves TEAMS_CLIENT_ID, TEAMS_CLIENT_SECRET, TEAMS_TENANT_ID, and TEAMS_PORT from env or explicit platform config with redacted status; plugin registration exposes teams with TEAMS_ALLOWED_USERS, TEAMS_ALLOW_ALL_USERS, max message length 28000, and setup guidance; fake runtime tests normalize personal/group/channel activities, prefer AAD object IDs, filter self messages, deduplicate activity IDs, strip <at>...</at> bot mentions, cache image attachments as PHOTO events, and send text/typing/adaptive approval cards through fake app clients. | validated | gateway | medium | gateway, operator, system | internal/channels/teams/runtime_test.go; cmd/gormes/teams_gateway_test.go | Missing SDK/aiohttp equivalents, missing credentials, invalid ports, unauthorized approval clicks, app-not-initialized sends, attachment-cache failures, and connect/start failures return teams_* evidence without starting a live HTTP server, exposing client secrets, or widening default channel toolsets. |
| 7 / 7.E | QQ Bot transport/bootstrap layer — Fakeable Official QQ Bot transport bootstrap binds the existing QQ Bot shared-chassis seam to token, gateway, websocket, and REST clients without live QQ: config resolves app_id/client_secret from explicit values or QQ_APP_ID/QQ_CLIENT_SECRET, applies markdown support and DM/group allowlist policies, acquires an app-id platform lock, uses an HTTP redirect guard for token/media requests, fetches token and gateway URL through injected clients, consumes websocket READY/RESUMED/dispatch/heartbeat frames with sequence tracking, builds REST message bodies for markdown/plain/truncated replies, waits for reconnect before sends, and returns retryable send evidence on timeout or close. | validated | gateway | medium | gateway, operator, system | internal/channels/qqbot/bootstrap_test.go | Missing dependencies, missing credentials, token failure, gateway failure, websocket close codes, reconnect timeouts, send failures, voice STT unavailability, and unsafe voice/media URLs return typed evidence such as qq_missing_dependency, qq_missing_credentials, qq_token_failed, qq_gateway_unavailable, qq_ws_closed, qq_send_retryable, qq_voice_stt_unavailable, and qq_ssrf_blocked with secrets redacted. |
| 7 / 7.E | Google Chat shared-chassis platform adapter seam — Gormes exposes a Google Chat platform adapter seam parallel to the completed Microsoft Teams channel seam: plugin metadata, a minimal Pub/Sub/CloudEvents message normalizer, and a fakeable delivery boundary live in internal/channels/googlechat. Real Pub/Sub, Chat REST, OAuth, and attachment transport wiring remain follow-up rows. | validated | gateway | small | operator, gateway | internal/channels/googlechat/runtime_test.go | Without the seam, Google Chat events have no in-Gormes route; with the seam but no transport, dispatch returns googlechat_transport_not_configured evidence instead of dropping events silently. |
| 7 / 7.E | Google Chat relay sender-type self-filter — Gormes extends the completed Google Chat channel seam to normalize Hermes relay-flat and native Chat API Pub/Sub envelopes, preserving sender_type from relay payloads and dropping BOT-originated messages before gateway dispatch. Missing or unsupported sender_type values default to HUMAN for backward compatibility. | validated | gateway | small | gateway, operator | internal/channels/googlechat/runtime_test.go::TestGoogleChatRelayFlatSenderTypeSelfFilter | Malformed relay/native envelopes return no inbound event and never dispatch a synthetic user message. Unknown relay sender_type values are treated as HUMAN rather than failing existing human relay traffic. |
| 7 / 7.E | Google Chat standalone cron sender — Gormes ports the Google Chat text-only standalone cron sender boundary: for cron targets whose platform is google_chat and that have no live gateway adapter, a package-local standalone sender validates Google Chat space/user and thread resource names, obtains an injected bearer token, posts a text-only Chat REST message with optional thread name through an injected poster, returns typed errors for invalid resource names or failed token/post operations, and reports unsupported platforms as cron.ErrStandaloneSenderUnavailable so the generic cron fallback remains intact. | validated | gateway | small | gateway, operator | internal/channels/googlechat/standalone_test.go | Nil token/poster seams, invalid target resource names, missing tokens, or post failures return user-safe errors without opening live Google clients, reading service-account files, printing tokens, or skipping the existing cron DeliverySink fallback. |
| 7 / 7.E | Google Chat install dependency hint refresh — Gormes refreshes the Google Chat platform metadata install hint from the stale Hermes plugin extra to the current public operator dependency command: pip install google-cloud-pubsub google-api-python-client google-auth google-auth-oauthlib. This is metadata-only and does not add live Pub/Sub, OAuth, attachment, gateway registration, or transport behavior. | validated | gateway | small | gateway, operator | internal/channels/googlechat/runtime_test.go::TestGoogleChatPluginMetadataMatchesHermesDocsInstallHint | If Google Chat transport remains unwired, delivery still returns googlechat_transport_not_configured; the refreshed install hint only changes operator guidance and never reads credentials or starts Google clients. |
| 7 / 7.E | SimpleX Chat platform plugin parity — Port Hermes’ bundled SimpleX platform plugin into a native Gormes channel behind the shared gateway.Channel contract. The builder must use a fakeable WebSocket transport to prove daemon configuration, disabled/missing-url/dependency-degraded status, opaque contact/group IDs, allowlist and allow-all admission, DM pairing prompts, correlation-ID echo filtering, inbound newChatItem/newChatItems normalization, outbound @[]/#[group] command formatting, home-channel/standalone delivery evidence, and bounded reconnect/health degradation without requiring a live SimpleX account or daemon. | validated | gateway | medium | operator, gateway | internal/channels/simplex/simplex_test.go | Until this adapter lands, Gormes must keep SimpleX visible as row-backed in the platform manifest/status checks and report missing WebSocket URL, dependency, daemon, allowlist, and delivery failures as degraded evidence rather than claiming SimpleX channel parity. |
| 8 / 8.A | TD engineering blog scaffolded and live — TrebuchetDynamics has a publicly reachable engineering blog with a working Atom/RSS feed, an /about page that names the org and the methodology, and a deploy pipeline so a markdown commit becomes a published post without manual intervention. Hosting choice is owner’s call (Astro/Hugo/Eleventy + Cloudflare/Vercel/GitHub Pages); the row is done when a stranger can subscribe to a feed and read one published post. | validated | docs | small | operator | webpages/blog/ (or chosen blog repo path) | Without a publication outlet, every loop commit is invisible in the reputation market; the strategy described in success-plan.md cannot start. |
| 8 / 8.A | TD social presence connected to blog feed — After the TrebuchetDynamics engineering blog feed is live and the operator selects a social account, provide a reproducible feed-to-social automation with a dry-run preview, idempotency guard, and documented publish method so blog posts can be announced without click-only console configuration. | fixture_ready | docs | small | operator | scripts/blog-feed-social/publish.mjs --dry-run with a repo-local or freshly built feed fixture | Without distribution, even good writeups die without an audience; this row creates the minimum viable distribution channel. |
| 8 / 8.B | README rewrite to methodology-first positioning — README.md leads with the autonomous-porting methodology and the sharp v1.0 differentiator (in <50 words), with Hermes appearing as the parity oracle in paragraph 2 — not as the lede. Existing operator-facing sections (install, quick start, status) are preserved; only the framing changes. The README explicitly links to success-plan.md and the agentic-porting-kit repo (or a placeholder section if that kit has not yet been extracted). | validated | docs | small | operator | README.md | Without the rewrite, the public face of the repo positions Gormes as “Hermes in Go” — a clone framing that gives reputation credit to Hermes, not TrebuchetDynamics. |
| 8 / 8.B | README release and benchmark metadata sync — README.md and public benchmark mirrors stay synchronized with the current release metadata and freshly measured optimized Linux binary: the README latest-release stanza, development-head wording, release-summary sentence, benchmark size, and benchmark date are derived from version/release metadata plus benchmarks.json, while repoctl benchmark recording updates every checked-in public benchmark mirror from one source of truth. | validated | docs | small | operator, system | internal/repoctl/readme_test.go::TestUpdateReadmeSyncsReleaseAndBenchmarkMetadata and internal/repoctl/bench_test.go::TestRecordBenchmarkCopiesBenchmarksToPublicMirrors | If release or benchmark metadata drifts, public trust surfaces can advertise an old release, stale size evidence, or a >30 MB benchmark even after the release pipeline and current optimized build satisfy the v1 size contract. |
| 8 / 8.B | gormes.ai landing page positioning audit — The gormes.ai landing page hero matches the success-plan.md North Star and README: autonomous-porting methodology + the ratified 30-skill single-binary differentiator appear before Hermes compatibility. Hermes parity stays as supporting evidence. Until the separate TD engineering blog row publishes a real URL, the landing page must not add a dead blog link; it should instead preserve docs, GitHub, install, roadmap, and TrebuchetDynamics links. | validated | docs | small | operator | webpages/landing/src/ | A landing page misaligned with the README erodes the methodology framing every time a reader bounces between the two. |
| 8 / 8.B | Gormes market comparison positioning brief — A canonical strategy note captures the buyer-language lesson from the OpenClaw/Hermes/hosted-agent comparison without treating social-post claims as evidence. It defines the comparison axes Gormes should answer publicly, records the approved one-line frame, and forbids citing unverified popularity, token-ranking, release-count, or CVE claims without primary-source verification. | validated | docs | small | operator | docs/content/building-gormes/strategy/market-positioning.md | Without this brief, future README/landing/docs passes can drift back into clone framing, unverifiable competitor claims, or methodology-heavy copy that hides the runtime wedge. |
| 8 / 8.B | Public comparison matrix: Gormes vs Hermes, OpenClaw, hosted agents — Publish an operator-facing comparison matrix that explains Gormes beside Hermes, OpenClaw, and hosted agent services on the axes buyers already use: integrations/channels, learning loop, operational stack, ownership/control, security/trust posture, and first-run proof. The page must be evidence-based: Gormes claims cite local docs/tests/artifacts, competitor claims are phrased as category tradeoffs unless primary sources are verified, and the copy avoids popularity, token-ranking, release-count, and CVE claims sourced only from social posts. | validated | docs | small | operator | webpages/docs/content/why-gormes.md::comparison matrix | Without a canonical comparison, external posts define the category for Gormes and force the README or landing page to answer clone, chaos, or hosted-SaaS framing ad hoc. |
| 8 / 8.B | Channel capability matrix with stable/fixture/planned labels — Turn channel breadth into proof instead of a raw count: publish a channel capability matrix that separates runtime-ready, fixture-backed, and planned adapters, links each stable top-channel setup path, and states that gateway readiness comes from gormes channels capabilities plus gormes gateway status, not from roadmap row count. | validated | docs | small | operator | webpages/docs/docs_test.go::TestChannelCapabilityMatrixDocs | Without labeled channel proof, Gormes either undersells existing gateway breadth or overclaims long-tail adapters as runtime-ready support. |
| 8 / 8.B | Learning-loop proof demo for skills, memory, and curator — Make the Hermes-compatible learning loop visible as an operator proof: document or demo the path from task evidence to skill creation/improvement, memory/recall persistence, curator status, and improved repeated-task behavior. The proof must use local fixtures or deterministic screenshots/output, avoid live-model hype, and link the existing skills, curator, and memory command surfaces. | validated | docs | small | operator | webpages/docs/content/building-gormes/core-systems/learning-loop.md::operator proof | If the learning loop stays buried in architecture docs, users will believe Gormes only has the no-stack runtime wedge and will continue assigning the self-improvement story exclusively to Hermes. |
| 8 / 8.B | No-stack first-run proof path from install to offline doctor — Harden the public first-run proof around the strongest Gormes wedge: install or source-build the single binary, verify gormes version, run gormes doctor --offline, open the offline TUI, then add provider credentials only after local readiness passes. README, docs, and landing copy must keep the distinction between local no-stack software and hosted zero-infrastructure SaaS. | validated | docs | small | operator, system | webpages/docs/docs_test.go::TestFirstRunProofPathDocumentsOfflineDoctorBeforeCredentials; webpages/landing/tests/home.spec.mjs::homepage sells the short buyer-focused landing | Without a crisp first-run proof, hosted competitors can define the self-hosting debate as Docker/VPS/weekend maintenance even when Gormes does not require Python, venvs, Docker, or a remote server for local use. |
| 8 / 8.B | Canonical config.toml v2 profile schema docs — Document the accepted v2 profile config shape as a planning contract: single $GORMES_HOME/config.toml, config_version = 2, seed [profiles.main] enabled=true name="", stable profile ids separate from display names, all enabled profiles active, no active_profile/default_profile, global credential registry with owner metadata, per-profile provider/channel credential references, Navivox multi-server routing, legacy fallback/migration rules, and Profile Control Center UX boundaries. | validated | docs | small | operator, system | docs/content/building-gormes/architecture_plan/profile-config-v2.md | Until runtime rows ship, the docs are explicitly marked as the v2 target architecture and do not claim current shipped config behavior. |
| 8 / 8.C | Engineering writeup #1: autonomous Hermes-porting loop — A 1500–2500 word engineering writeup published on the TD blog explaining how the autonomous loop ships 1–2 Hermes-parity features per hour with TDD discipline. Includes: an architecture diagram of the loop, a real diff walkthrough of 3+ recent loop-shipped commits (e.g., Telegram thread fallback, browser cleanup, skill validator), the cost-per-feature numbers, and the validation-gate flow. Writes for a reader who has not seen Gormes before. | draft | docs | medium | operator | webpages/blog/posts/autonomous-hermes-porting-loop.md | Without writeup #1, the methodology is invisible to anyone outside the repo; the autonomous loop is a curiosity, not a credential. |
| 8 / 8.C | Engineering writeup #1 cost telemetry evidence packet — Create a local, no-credential cost telemetry evidence packet for Engineering Writeup #1. The packet must snapshot the current builder-loop cost-report output, development-goal iteration log coverage, elapsed telemetry window, known 7-day/30-day cost fields or explicit unknown_no_cost_data evidence, and the exact remaining evidence needed before the public post can claim cost-per-feature numbers. It must not publish externally, estimate missing costs, or treat unknown cost as $0.00. | validated | docs | small | operator, system | docs/content/building-gormes/strategy/writeups/autonomous-hermes-porting-loop-cost-evidence.md | If the builder loop has no opencode JSONL cost events or less than one week of measured telemetry, the packet must report unknown or insufficient data with command output and keep the parent writeup blocked. |
| 8 / 8.C | Engineering writeup #1 local publication review packet — Create a local, no-credential review packet for Engineering Writeup #1 that lets Juan review the autonomous Hermes-porting loop draft before any public posting. The packet must point to the draft post, summarize the three diff walkthroughs and validation-gate evidence already in the draft, list exact missing final-acceptance inputs (one week of cost telemetry, publication date/platform, social submission target), and preserve the secret/publication boundary so no live social credentials or external posting are required. | validated | docs | small | operator, system | docs/content/building-gormes/strategy/writeups/autonomous-hermes-porting-loop-review-packet.md | If measured cost telemetry, publication date, platform, or social account evidence is missing, the packet must show those as explicit pending operator inputs rather than inventing estimates or publishing live. |
| 8 / 8.C | Hermes v0.14 release feature-to-module pairing ledger — Map Hermes v0.14 release-level user-visible features to the Gormes module taxonomy, mark which surfaces are covered or row-backed, and add source-backed planned rows for newly visible behavior that is not already explicit in module roadmaps. | validated | docs | small | - | docs/content/building-gormes/architecture_plan/hermes-v0.14-module-pairings.md | - |
| 8 / 8.C | Hermes contract inventory gate — Build a report-only Hermes contract inventory gate that scans the current in-repo Hermes checkout, inventories source files, upstream docs pages, upstream tests, CLI/tool/provider/channel/session/memory/skill/learning-loop candidates, joins that evidence to progress.json rows and hermes-source-pairs.json, and emits machine-readable plus human-readable gap reports without failing CI by default. The gate must treat agent continuity as first-class: sessions, Memory/Goncho/Honcho compatibility, workspace/peer/profile identity boundaries, context retrieval and prompt budget, summaries/conclusions/search, skill templates and skills UX, skill precedence/sync/update/reset, learning-loop curator behavior, candidate memory/skill updates, feedback/outcome scoring, audit trail, mutation safety, prompt/context/memory/skill insertion ordering, and profile-scoped isolation. The gate proves whether a given Hermes SHA has every behavior/architecture contract classified as covered, partial, planned, excluded, or owned_divergence before Gormes claims full pairing. | validated | docs | medium | operator, system | internal/repoctl/hermes_contract_inventory_test.go; webpages/docs/content/building-gormes/architecture_plan/hermes-contract-inventory.md | Without this inventory gate, Hermes pairing remains a hand-maintained release-note/source-file map and can silently drift from rolling upstream HEAD; Gormes must continue describing parity as source-backed but not exhaustive, especially for agent-continuity behavior that spans sessions, memory, skills, learning-loop updates, prompt insertion, and profile isolation. |
| 8 / 8.C | Strict-fidelity upstream test-suite classifier — Classify the strict-fidelity test blockers by upstream suite, exact Hermes source under test, current Gormes progress row or explicit exclusion. The row must turn the 1,206 unmapped upstream test files from one giant blocker count into deterministic report groups that builders can chase without creating a side backlog. | validated | docs | medium | operator, system | internal/repoctl/hermes_contract_inventory_test.go:TestWriteHermesContractInventoryWritesJSONAndMarkdown; webpages/docs/content/building-gormes/architecture_plan/hermes-contract-inventory.md | Until this strict-fidelity bucket is classified, Gormes must continue treating the matching Hermes source/docs/tests as unmapped blockers and avoid claiming complete Hermes parity for this surface. |
| 8 / 8.C | Hermes integrations claim audit + source-backed plugin/skill parity map — Turn the sanitized Reddit/WebAfterAI Hermes integrations post into a source-backed parity map without accepting marketing shorthand as fact: classify each named integration as first-party bundled skill, bundled plugin/backend, gateway/platform/tool, optional skill, indirect web/browser/MCP/scraping workflow, Gormes-owned candidate, or unsupported/excluded claim. The audit must explicitly handle cases where a workflow is achievable through generic web scraping, browser automation, MCP, or Firecrawl-style extraction without being a direct Hermes plugin or tool, and it must not create implementation rows for Reddit, Stripe, InsForge, Graphiti/Zep, or Fireflies unless exact current Hermes source appears. | validated | docs | small | operator, system | webpages/docs/content/building-gormes/architecture_plan/hermes-integrations-claim-audit.md | Until the claims are source-classified, public roadmap and parity work can overstate Hermes/Gormes integration breadth by treating scraped workflows, optional skills, and unsupported social-post claims as native plugins. |
| 8 / 8.D | Sharp v1.0 differentiator decision — A short decision document records the sharp v1.0 differentiator. The decision unblocks every downstream messaging row. | validated | docs | small | operator | docs/content/building-gormes/strategy/v1-differentiator.md | Without a written, dated differentiator, the README and landing page cannot be rewritten coherently. |
| 8 / 8.D | Single-binary cross-platform release pipeline — A reproducible CI pipeline produces signed single-binary releases for linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, windows/amd64, and android/arm64 (Termux) on every git tag. Each binary is ≤30 MB, includes version + build metadata, and has its install size and SHA recorded in the release notes. Mirrors install.sh / install.ps1 to fetch the right asset. | validated | tools | umbrella | operator, system | .github/workflows/release.yml | Without a release pipeline, the v1.0 differentiator (“single binary on X platforms”) cannot be substantiated; users still build from source. |
| 8 / 8.D | Release binary version/provenance smoke guard — The release workflow proves each matrix-built archive contains a Go binary with inspectable build metadata, and every host-executable matrix leg runs gormes version --json before packaging so the injected semver tag, git commit, dirty bit, and build date are checked from the actual binary output. | validated | tools | small | operator, system | webpages/docs/install/release_workflow_test.go::TestReleaseWorkflowSmokeChecksBinaryVersionMetadata | Without a binary smoke guard, the workflow can inject ldflags and publish archives while never proving the produced executable reports the tag, commit, dirty bit, and build date that release notes and fleet automation rely on. |
| 8 / 8.D | CI and installer Go toolchain floor sync — Every CI/release workflow and source-build installer path follows the Go toolchain floor declared in go.mod instead of pinning stale Go 1.25 text. GitHub Actions setup-go steps use go-version-file: go.mod so the toolchain directive selects Go 1.26, while install.sh, install.ps1, and public source-build docs require and install Go 1.26+. | validated | tools | small | operator, system | docs/install/go_toolchain_workflow_test.go::TestGitHubActionsUseGoModToolchain and internal/installtest/go_toolchain_floor_test.go::TestInstallersRequireCurrentGoToolchain | If the workflow or installer floor drifts below go.mod, development CI, release builds, and source-build fallback can fail with a toolchain mismatch even though Docker and local development already use Go 1.26. |
| 8 / 8.D | Release prep guide target matrix sync — The v0.1.0 release-prep guide stays in lockstep with the current release workflow target matrix and trust artifacts: it must name linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, windows/amd64, windows/arm64, and android/arm64 Termux archives, plus SHA-256 sidecars, SPDX SBOMs, and GitHub SBOM/build-provenance attestations. The guide must not fall back to the stale generic Linux/macOS/Windows summary once the workflow and installer rows prove a seven-target release. | validated | docs | small | operator, system | docs/install/release_workflow_test.go::TestReleasePrepGuideTargetMatrixMatchesWorkflow and webpages/docs/install/release_workflow_test.go::TestReleasePrepGuideTargetMatrixMatchesWorkflow | If release documentation drifts from the workflow matrix, operators can approve a tag believing fewer or different artifacts will be produced. The fixture fails before release-prep docs can understate or omit target-specific archives and trust artifacts. |
| 8 / 8.D | Windows install.ps1 release binary fetch selector — The native Windows installer defaults to fetching the latest prebuilt windows-amd64 or windows-arm64 release archive from GitHub Releases on main, verifies the .sha256 sidecar, extracts gormes.exe into the managed build-bin path, and then reuses the existing publish, PATH, verification, ledger, and gateway-restart flow. It falls back to the managed source build only when -FromSource or GORMES_INSTALL_FROM_SOURCE is set, the branch is not main, the host architecture has no published asset, or runtime release download/verification fails. | validated | tools | small | operator, system | internal/installtest/windows_installer_release_test.go::TestWindowsInstallerReleaseFirstContract | Without the Windows release-fetch selector, the v1 Windows-without-Python promise is still source-build shaped: fresh Windows installs need Git plus Go even though CI publishes Windows release archives. |
| 8 / 8.D | OCI image PR build and arm64 smoke workflow — Gormes verifies OCI image changes before merge by running path-scoped PR and development Docker builds for linux/amd64 and linux/arm64, loading each image locally, and smoking —help, dashboard —help, and doctor —offline through the image entrypoint. The final image must stay Python/Node/Playwright-free, run as a nonroot user, and keep the config-volume bootstrap path executable. | validated | tools | small | operator, system | webpages/docs/install/oci_image_test.go::TestOCIWorkflowBuildsAndSmokesAMD64AndARM64 | Without OCI smoke coverage, Dockerfile or entrypoint regressions can merge to development/main and surface only after an operator pulls the image. PR/development runs must build and smoke images only; they must not push registry tags or upload release artifacts. |
| 8 / 8.D | Release build-date provenance injection — The release workflow injects a UTC build timestamp into every prebuilt binary, and gormes version --json reports it as build_date beside the semver tag, Hermes-style date alias, git commit, dirty bit, Go runtime, OS, and arch. Developer/source builds may fall back to Go VCS build-info time or unknown, but tagged release artifacts must carry the workflow-injected timestamp. | validated | tools | small | operator, system | cmd/gormes/version_test.go::TestVersionCommand_JSONIncludesBuildDateField; webpages/docs/install/release_workflow_test.go::TestReleaseWorkflowInjectsBuildDateProvenance | Without build_date, operators can verify the release tag and source commit but cannot tell when a downloaded binary was minted; the parent release pipeline acceptance remains only partially satisfied. |
| 8 / 8.D | Landing release metadata date-alias sync — The public landing release metadata mirrors the operator-facing semver tag together with the Hermes-style date alias from cmd/gormes/version.go. sync-assets.mjs must parse VersionDateAlias into webpages/landing/src/data/release.json, and the landing release label must display the paired alias so public install surfaces, release metadata, and gormes version --json agree. | validated | docs | small | operator, system | webpages/docs/install/release_metadata_test.go::TestLandingReleaseMetadataCarriesDateAlias | Without date_alias in landing release metadata, public install/trust surfaces show only vgormes version --json and release documentation claim a paired Hermes-style vYYYY.M.D alias. |
| 8 / 8.D | GitHub release title date-alias binding — Tagged GitHub Releases display the same semver plus Hermes-style date alias that gormes version --json and landing release metadata report. The release workflow must parse VersionDateAlias from cmd/gormes/version.go while building release notes, include the paired alias in the release-note heading, and pass a custom softprops/action-gh-release name so the GitHub release title is not only the tag name. | validated | tools | small | operator, system | webpages/docs/install/release_workflow_test.go::TestReleaseWorkflowReleaseTitleCarriesDateAlias | Without this binding, public release pages still show only the semver tag even though Gormes’ binary identity and landing metadata claim a paired vYYYY.M.D alias. |
| 8 / 8.D | Release notes artifact size table — The release workflow records each release archive’s install size beside its SHA-256 in generated GitHub release notes. The publish job must compute the byte count from the downloaded tar.gz artifacts, render a Size column in the artifacts table before publication, and keep SBOM and provenance wording intact. | validated | tools | small | operator, system | webpages/docs/install/release_workflow_test.go::TestReleaseWorkflowReleaseNotesIncludeArchiveSize | Without artifact sizes in release notes, operators cannot verify the v1 single-binary size promise from the public release page even though the workflow enforces the cap internally. |
| 8 / 8.D | Release SBOM attestation binding — The release workflow binds each generated SPDX SBOM to its matching release archive through GitHub artifact attestations. After the matrix build creates dist/gormes-${version}-${goos}-${goarch}.tar.gz and dist/gormes-${version}-${goos}-${goarch}.sbom.json, CI must run the current GitHub SBOM attestation action with the tarball as subject-path and the SPDX file as sbom-path before upload. | validated | tools | small | operator, system | webpages/docs/install/release_workflow_test.go::TestReleaseWorkflowAttestsSBOMsForArchives | Without an SBOM attestation binding, operators receive SBOM files and build-provenance attestations but cannot verify that the published SPDX document is cryptographically associated with the release archive. |
| 8 / 8.D | Release build provenance attest action contract — The release workflow binds every matrix-built tar.gz release archive to GitHub build-provenance artifact attestations using the current artifact-attestation action shape: actions/attest@v4 with the matrix-specific archive as subject-path. The attestation must run after the archive, checksum, size gate, and SBOM are produced, before the archive is uploaded from the build job, while preserving SBOM attestation, SHA-256 sidecars, release-note size/SHA output, and installer asset publication. | validated | tools | small | operator, system | webpages/docs/install/release_workflow_test.go::TestReleaseWorkflowAttestsBuildProvenanceForArchives | Without a focused build-provenance attestation contract, the release flow can drift to an older action or attest the wrong subject while release notes continue to claim GitHub build provenance exists. |
| 8 / 8.D | Release notes SBOM attestation wording — The generated GitHub release notes must describe the two attestation streams that the release workflow actually publishes: SPDX SBOM attestations and build-provenance attestations for every matrix tar.gz archive. The release page must not imply that only build provenance is attested once the workflow has both Attest SBOM and Attest build provenance steps. | validated | tools | small | operator, system | webpages/docs/install/release_workflow_test.go::TestReleaseWorkflowReleaseNotesNameSBOMAttestations | If release notes omit SBOM attestation wording, operators can see SBOM files and build-provenance text but miss that SBOM documents are also bound to the matching archives in GitHub’s attestation store. |
| 8 / 8.D | Release archive 30 MB size gate — The release workflow enforces the v1 differentiator’s single 30 MB binary promise before release artifacts are uploaded: every built archive is checked against a 31,457,280-byte cap after packaging and checksum generation, and any oversize archive fails the build with a clear target-specific message. | validated | tools | small | operator, system | webpages/docs/install/release_workflow_test.go::TestReleaseWorkflowEnforcesMaxArchiveSize | If an archive exceeds the cap, the release matrix fails before upload or publication; operators keep source-build installs rather than receiving an oversized prebuilt binary. |
| 8 / 8.D | Termux android/arm64 release artifact and installer selector — The release pipeline and installer treat Termux android/arm64 as a first-class prebuilt binary target: GitHub Actions builds GOOS=android/GOARCH=arm64 with the same static-archive, SHA-256, SBOM, and provenance path as the other release targets, and install.sh maps Termux arm64/aarch64 hosts to the android-arm64 asset while leaving non-Termux Linux arm64 on linux-arm64. | validated | tools | small | operator, system | webpages/docs/install/release_workflow_test.go::TestReleaseWorkflowContract and internal/installtest/install_method_test.go::TestInstall_DryRunTermuxArm64_PrefersAndroidReleaseAsset | Unsupported Termux architectures fall back to source-build with an explicit no-published-asset reason; non-Termux Linux hosts continue selecting linux-* assets. |
| 8 / 8.D | Gormes-owned chat TUI divergence ratification — Scope the Hermes-in-Go core rule to runtime/protocol parity and explicitly declare the interactive gormes chat presentation a Gormes-owned surface. Replace the Hermes-pinned presentation assertions in hermes_behavioral_fidelity_test.go (EmptyTranscriptIntro exact-string mandate, ❯/┊/─── glyph locks, and the you:/⚕ Gormes/role-label forbiddances) with Gormes-owned golden assertions, preserving the genuinely behavioral guarantees (no duplicate final assistant, tool output not absorbed into prose, transcript ordering). | validated | tui | small | - | - | - |
| 8 / 8.D | Gormes-owned session-aware welcome panel — Replace the static empty-transcript intro (view.go conversationEmptyIntro; banner.go bannerLogo/bannerCaduceus/bannerWelcome are currently only test-referenced) with a bordered, session-aware welcome panel that keeps the Gormes caduceus/title identity but adds live context reachable from kernel.RenderFrame — model, provider/runtime (ProviderStatus.Provider/Runtime), cwd, session id — plus a best-effort version from runtime/debug.ReadBuildInfo (omitted on devel) and a ‘getting started’ slash-tips block, rendered on the first frame before any turn. Panel border/title/accent/dim colors MUST derive from the active HermesSkin (HermesSkinColors.Banner*) so all built-in skins keep theming the panel; ccx-go’s single hardcoded orange palette is a layout/composition donor only. Degrade gracefully under HermesSkin.UseMinimalChrome (terminal width < 64) to a compact non-bordered form. Tool count is NOT in kernel.RenderFrame nor importable from internal/tui — deferred to the ‘Gormes welcome panel version/tool-count wiring’ follow-up row, not silently dropped. | validated | tui | medium | - | - | - |
| 8 / 8.D | Gormes-owned semantic chat style system — Introduce a Gormes-owned internal/tui/styles.go exposing a small flat set of named semantic transcript-role styles (user, assistant, tool name, tool output, error, prompt, separator, status), each derived from the active HermesSkin (HermesSkinColors) rather than a new hardcoded palette, and route the transcript role/chrome rendering in view.go through it so user/assistant/tool/error messages are visually differentiated and scannable, replacing scattered ad-hoc lipgloss.NewStyle calls in the role/chrome path. markdown.go (the working hand-rolled body renderer with its own element styles) is out of scope and must not be rewritten; ccx-go’s flat styles.go is a naming/composition donor only, not a palette to copy. | validated | tui | medium | - | - | - |
| 8 / 8.D | Gormes-owned streaming feedback uplift — On the existing kernel.RenderFrame/coalescer streaming model (view.go conversationViewportTail; do NOT replace the coalescer), reuse thinking.go RenderThinking (not reimplemented) and wire it into the live per-turn frame path so an active-but-quiet turn (no tool trace, draft, or error yet) always shows a distinct thinking indicator, suppressed the moment any concrete signal exists so transcript order is never disturbed; and collapse long tool output in conversationToolResultBlock to first N lines + a ’[+K more lines]’ summary (ccx-go RenderToolOutputInline pattern), leaving short output intact. Coalescer semantics unchanged; FinalAssistantNotDuplicated and tool-output-not-absorbed preserved. Wiring the richer RenderToolTrail status icons (⏳/✅/❌) and RenderSpinnerFrame animation cadence into the live path is split out to the ‘Gormes streaming tool-trail status + spinner cadence wiring’ follow-up row (needs an update.go tick loop), not silently dropped. | validated | tui | medium | - | - | - |
| 8 / 8.D | Gormes streaming tool-trail status + spinner cadence wiring — Layer the richer reused renderers onto the R3 streaming feedback: wire thinking.go RenderToolTrail (tree + per-tool ⏳/✅/❌ status icons + duration) into the live kernel.RenderFrame path so in-turn tool start vs done vs error are visibly distinct beyond the flat tooltrace line, and drive RenderSpinnerFrame as an animated activity cue via an update.go tick command. Reuse the existing thinking.go/spinner.go renderers (do not reimplement); do NOT replace the gateway coalescer; preserve FinalAssistantNotDuplicated, tool-output-not-absorbed, transcript ordering, and the R3 thinking-indicator/collapse behavior. | validated | tui | medium | - | - | - |
| 8 / 8.D | Gormes welcome panel version/tool-count wiring — Thread the operator-facing release version (cmd/gormes main.Version) and the agent tool count into the chat TUI so the session-aware welcome panel can show both. Add an optional WelcomeContext seam on the Model (set by cmd/gormes at startup, zero-value safe) that the welcome panel reads; when unset the panel keeps the R1 best-effort/omit behavior. This is the deliberately deferred cross-package half of the welcome panel — kept out of R1 because main.Version is unimportable from internal/tui and tool count is absent from kernel.RenderFrame. | validated | tui | small | - | - | - |
| 8 / 8.D | Termux latest-installer follow-up release publication — Publish the follow-up public release that carries the already-committed Termux installer recovery fix. The release must follow the required development -> main PR -> tag lane, must not tag from development, and must prove the public latest installer is no longer the affected v0.2.20 artifact before docs or operators call Termux latest install repaired. | validated | tools | small | operator, system | internal/installtest/termux_publish_recovery_test.go | Public v0.2.22 is now the latest GitHub release; it carries the Termux executable-argument recovery, install.sh and install.ps1 assets, and the android-arm64 archive plus checksum. |
| 8 / 8.D | Removal of public v0.2.20 Termux latest-install caveats from README, landing, install docs, and troubleshooting docs — After a follow-up public release containing the Termux installer recovery exists, update README, landing release metadata/copy, install docs, troubleshooting docs, and their tests so current public surfaces no longer describe v0.2.20 as the latest affected Termux install while historical changelog/TODO evidence remains factual. | validated | docs | small | operator, system | webpages/docs/docs_test.go::Termux v0.2.20 caveat tests; webpages/docs/install/termux_guide_test.go::TestInstallDocsWarnTermuxV020LatestReleaseCaveat; webpages/landing/tests/home.spec.mjs::homepage sells the short buyer-focused landing | If this cleanup runs before a follow-up release exists, public docs falsely claim the latest Termux installer is repaired; if it never runs after release, public docs continue telling users the latest installer is broken. |
| 8 / 8.D | Gormes-owned TUI queued-message widget and busy delivery modes — Adapt Pi’s visible steering/follow-up queue pattern into the native Gormes Bubble Tea chat TUI without changing Hermes-compatible slash command semantics. While a turn is active, plain Enter should honor the configured busy-input mode, queued or steering drafts should be visible in the bottom-pinned chrome, queued entries should drain after the kernel returns idle, and the UI must keep Alt/Shift+Enter newline behavior intact. | validated | tui | medium | operator, system | internal/tui/queued_messages_test.go | - |
| 8 / 8.D | Gormes-owned TUI extension status widget and footer seam — Introduce a small Go-native TUI extension context that lets trusted in-process Gormes extensions add or clear footer status entries, widgets above or below the editor, and working-indicator text/frames. The seam should be typed, width-safe, scoped to the active session, and degrade to no-op evidence in non-interactive modes; it must not execute TypeScript or import Pi packages. | validated | tui | medium | operator, system | internal/tui/status_bar_ext_test.go | - |
| 8 / 8.E | Agentic-porting-kit extraction spec — A short strategy spec defines the public agentic-porting-kit repository boundary before extraction begins: repo shape, skill renames, Gormes-to-generic terminology rules, standalone example fixture, README promise, license requirement, and acceptance tests for loading the kit without cloning Gormes. | validated | docs | small | operator | webpages/docs/content/building-gormes/strategy/agentic-porting-kit.md | Without a written extraction boundary, a builder can copy Gormes-local process into a public repo that still depends on Gormes paths and cannot prove the methodology is portable. |
| 8 / 8.E | Agentic-porting-kit local standalone fixture — Add a repo-local, copy-ready standalone Python-greeter-to-Go example, minimal progress schema, and validation script that prove the agentic-porting-kit example can validate and test without Gormes tooling, GitHub repository creation, provider credentials, or network access. | validated | skills | small | operator | examples/agentic-porting-kit/python-greeter-to-go/progress.json + scripts/agentic-porting-kit/validate-local-example.sh | Without the local fixture, the blocked public repo scaffold must design schema/example/script details during an external GitHub operation instead of copying a locally validated package. |
| 8 / 8.E | Agentic-porting-kit local porting skill skeletons — Add six repo-local, copy-ready porting-* skill skeletons and validation checks that prove the future agentic-porting-kit scaffold has loadable generic skill names, source/target implementation terminology, PORTING_PROGRESS_PATH guidance, and no Gormes-only paths before any external repository is created. | validated | skills | small | operator | examples/agentic-porting-kit/skills/porting-*/SKILL.md + scripts/agentic-porting-kit/validate-local-example.sh | Without local skill skeletons, the blocked public repo scaffold must generalize six skills during a GitHub create/push operation instead of copying a locally validated loadable set. |
| 8 / 8.E | Agentic-porting-kit local README and license fixture — Add copy-ready local README.md and LICENSE fixtures for the future agentic-porting-kit public repository, and extend the no-auth local validator so it proves the README promise, Codex and Claude Code loading guidance, public quick-start paths, schema adaptation guidance, Gormes proof language, license section, and MIT-shaped license before any external repository is created. | validated | docs | small | operator | examples/agentic-porting-kit/README.md + examples/agentic-porting-kit/LICENSE + scripts/agentic-porting-kit/validate-local-example.sh | Without local README/LICENSE fixtures, the blocked public repo scaffold still has to draft operator-facing copy and license content during the external GitHub create/push operation. |
| 8 / 8.E | Agentic-porting-kit local public layout assembly gate — Add a copy-ready public scripts/validate-example.sh and a local validate-public-layout.sh assembler that builds the future agentic-porting-kit repository shape in a temporary directory, maps the Gormes-local fixture paths to public repo paths, and runs the public validator without GitHub access. | validated | tools | small | operator | scripts/agentic-porting-kit/validate-public-layout.sh assembles a temp public repo and runs examples/agentic-porting-kit/scripts/validate-example.sh | Without a local public-layout gate, path and script-shape mistakes in the future public repo scaffold would only be discovered during the blocked GitHub create/push operation. |
| 8 / 8.E | Agentic-porting-kit public repo scaffold — Create the public TrebuchetDynamics/agentic-porting-kit repository from the extraction spec with README, LICENSE, progress schema, validation script, six renamed porting skills, and a tiny Python-greeter-to-Go example. The copied skills must load in a fresh Codex or Claude Code session without depending on the Gormes checkout. | draft | skills | medium | operator | TrebuchetDynamics/agentic-porting-kit:examples/python-greeter-to-go/progress.json | Without the public scaffold, the methodology remains inspectable only inside Gormes and cannot be cited or reused by other teams. |
| 8 / 8.F | Loop $/iteration cost metric in status file — The autonomous builder loop records a cost-per-iteration estimate in its status file (loop-health.env or a sibling file), broken down by backend (codexu vs opencode/cost and tokens fields per run; for codexu, use a documented fallback estimate. A --cost-report subcommand on the loop wrapper prints the rolling 7-day and 30-day spend. | validated | tools | small | operator, system | internal/loopcost/cost_test.go | Without cost telemetry, the operating principle ”$/feature shipped” cannot be enforced; the loop runs at indefinite cost. |
| 8 / 8.F | Stop git-tracking duplicate landing progress mirrors (build-time generate) — The canonical backlog webpages/docs/content/building-gormes/architecture_plan/progress.json (5.2 MB) is duplicated VERBATIM into two git-tracked landing mirrors, so every progress edit is a ~3-file multi-MB diff (~10.4 MB pure duplication tracked). Make both mirrors build-time generated, not committed. CONFIRMED generation today: (1) webpages/landing/src/data/progress.json is COPIED by webpages/landing/scripts/sync-assets.mjs:101-103 from the canonical file; (2) webpages/landing/legacy/go-renderer/internal/site/data/progress.json is consumed via //go:embed data/progress.json in webpages/landing/legacy/go-renderer/internal/site/progress.go:13 (so it MUST exist as a file in that package dir at go build/go test time — cannot simply gitignore without a regenerate-before-build step or repointing the embed). Approach: (a) add both mirror paths to .gitignore and git rm --cached them; (b) ensure go run ./cmd/progress write (and/or sync-assets.mjs) regenerates BOTH from the canonical file; (c) handle the go:embed constraint so go test ./webpages/... -count=1 and the landing build still pass from a clean checkout — either repoint the embed at a generated path produced before compilation, or have the test/build harness regenerate the embedded file first (the row’s builder must pick the smallest safe option and document it). Do NOT change the canonical file format or the rendered site output. Owned Gormes infra cleanup (not Hermes parity). SHIPPED 2026-05-16 (gormes-tdd-slice): src mirror was DEAD (no Astro consumer) → removed from progressctl + sync-assets.mjs, untracked via webpages/landing/.gitignore (negation→explicit ignore, benchmarks.json preserved); legacy go:embed mirror kept but written SLIM (135 KB status/name-only via slimProgress, 5.2 MB→135 KB, ~38×) so go:embed stays clean-build-valid and rendered output is byte-equivalent. | validated | tools | medium | system | internal/buildscripts_test.go | If a mirror cannot be regenerated, the build/test must fail loudly with a clear ‘run go run ./cmd/progress write’ message — never silently embed a stale or empty mirror. |
| 8 / 8.F | Compact completed-row shipped-evidence notes to a one-line pointer — 1001 of 1020 progress rows are status=complete and carry long multi-paragraph SHIPPED-evidence note prose that is redundant with git history (the commit holds the detail). Add a Gormes-owned compaction that rewrites a COMPLETED row’s verbose note to a one-line pointer SHIPPED <YYYY-MM-DD> <shortSHA|see git log> — <one-line behavior>, PRESERVING name/status/contract_status/provenance/acceptance/contract/source_refs/write_scope (only the prose note is compacted — nothing else is lost; not_ready_when must forbid touching any other field). Prefer extending cmd/progress (e.g. a go run ./cmd/progress compact maintenance subcommand) over a new binary; the compaction must be idempotent, must NOT change go run ./cmd/progress validate semantics or the progress-row contract schema, and must be fully reversible by git. Scope decision: SHIP the compaction helper + an ONGOING rule (future completions write a one-line note) and a GUARDED opt-in one-time sweep of existing complete rows (the bulk sweep is a large mechanical diff — keep it a separate explicit invocation, not automatic, so it can land as its own commit). Materially reduces byte size without losing provenance (git still holds full evidence). Owned Gormes infra. | validated | tools | medium | system | internal/progress/compact_test.go | If a completed row’s note cannot be safely parsed for a one-line summary, leave it unchanged (never lossily truncate mid-sentence or drop a non-note field). |
| 8 / 8.F | Module-split the progress backlog (per-subsystem files, parity-aligned) — Umbrella (user-requested 2026-05-16, grill-corrected). Split the canonical backlog into a feature-module layout so agents and humans work against small, navigable feature files while preserving one logical backlog behind internal/progress.Load. The physical module names are the plain feature areas approved in the grill session: browser, builder, channels, cli, config, cross-cutting, doctor, docs, fleet, gateway, goncho, install, kanban, landing, learning-loop, memory, navivox, planner, profiles, progress, providers, release, runtime, sessions, skills, stt, tools, tts, tui. execution_owner remains work ownership; module becomes the physical home. The previous broad buckets (commands, setup-config-install, gateway-channels, providers-auth, memory-sessions-skills, orchestrator) are transitional only and must not be valid final C5 modules. | validated | tools | umbrella | system | - | - |
| 8 / 8.F | Backlog split C1: lossless multi-file loader/writer behind the single-file API — Child 1 of the module-split umbrella — the smallest NON-behavior-changing first step. In internal/progress, add the ability to load AND write a split layout (a directory of per-module files, or index + per-module files) BEHIND the existing single-file public API: internal/progress.Load(path) (progress.go:245) must transparently accept EITHER the monolithic progress.json OR the split layout and return the identical in-memory model; add a round-trip pair (e.g. go run ./cmd/progress split / ... merge, or internal Split()/Merge()) that is BYTE-STABLE through the existing stable marshal (internal/progress/progress_marshal.go) — merge(split(x)) == x and validate output identical. Do NOT move any real rows, do NOT change any consumer (cmd/progress, plannerloop, builderloop, status, docs/landing generators), do NOT change validate semantics. This is purely a back-compat shim + a lossless round-trip proven by tests, so a later child can flip the on-disk layout with zero behavior change. Owned Gormes infra. | validated | tools | small | system | internal/progress/split_test.go | If a split layout is malformed/partial, Load must return a typed error (never a silently partial backlog) and the monolithic path must keep working unchanged. |
| 8 / 8.F | Backlog split C2: docs/landing generators read the split layout — Make the docs/landing generators source the canonical backlog through C1’s dual-layout Load so they work identically whether the backlog is the monolithic progress.json OR a split directory. Today internal/progressctl.Write/Validate/Compact call progress.Load(progressPaths(root).progressJSON) (a fixed FILE path); add split-layout resolution (when a split directory exists at the canonical location, or an opt-in path, load it via the C1 transparent Load) and prove every generated artifact — the building-gormes markered docs (_index.md, agent-queue.md, next-slices.md, blocked-slices.md, contract-readiness.md, umbrella-cleanup.md, progress-schema.md), the slim go:embed legacy mirror, and the validate JSON — is BYTE-IDENTICAL regardless of which physical layout backs the source. No row is moved and the monolith stays the default; this is purely teaching the generators to read either layout. Owned Gormes infra. | validated | tools | small | system | internal/progressctl/progressctl_test.go | If the split layout is absent or malformed, generators fall back to the monolithic progress.json unchanged (C1 ErrMalformedSplit surfaces; never emit a partially-generated doc set). |
| 8 / 8.F | Backlog split C3: migrate remaining backlog consumers and the write path to the split layout — Make every remaining backlog consumer AND the write-back path split-layout-safe without changing internal/progress.Load/SaveProgress public signatures. Readers (cmd/progress validate/write/compact, cmd/gormes/status.go, internal/architectureplanneragent) are already transparent via C1’s dual-layout Load — verify and add coverage. The meaty part is the WRITE path: internal/builderloop/health_writer.go and internal/plannerloop persist via progress.SaveProgress, which writes a single monolithic file; when the canonical source is a split layout a SaveProgress-equivalent must round-trip back into the split layout (reuse C1 WriteSplit/Split) so health/verdict/status writes do not silently collapse the split into a monolith. Add a split-aware save (e.g. SaveProgress detects the existing layout, or a SaveSplit sibling the writers call) proven by a write→reload round-trip that preserves the split. No row moved; monolith remains default. Owned Gormes infra. | validated | tools | medium | system | internal/progress/split_test.go | If the layout cannot be determined, fall back to monolithic SaveProgress (never write a partial split; never leave the backlog half-monolith half-split). |
| 8 / 8.F | Backlog split C5a: optional per-row module key + deterministic derivation + backfill — C1’s split is keyed by PHASE (index.json keys = phase IDs). A true per-MODULE physical split needs a deterministic module(row) mapping, but rows carry no module tag — only the coarse optional execution_owner enum {docs,gateway,memory,provider,tools,skills,orchestrator,tui,goncho}, which is too coarse for the agreed fine module set (profiles/doctor/tts/stt/navivox/learning_loop/kanban have no dedicated owner). Add an OPTIONAL module string field to the Item schema (json:“module,omitempty”), a deterministic Module(it, phaseID, subphaseID) derivation that defaults from execution_owner + phase/subphase when module is absent (mapping recorded in the umbrella’s agreed taxonomy), and a one-time validator-neutral, marshal-stable backfill — mirroring the additive ROW B/C1 pattern (must NOT change go run ./cmd/progress validate semantics, the row-contract schema requiredness, row identity, or stable marshal ordering; fully git-reversible). This is the prerequisite that lets per-module physical files exist; until it ships the C1 phase-keyed split is the canonical physical layout. Owned Gormes infra. | validated | tools | small | system | internal/progress/module_test.go | If module is absent or unrecognised, Module() returns the deterministic default from execution_owner/phase (never an error, never a silently mis-bucketed row). |
| 8 / 8.F | Backlog split C4: AGENTS.md + gormes-* skills source-order updated to the split layout — Update AGENTS.md (the ‘progress.json is the only backlog’ rule and Shared Progress Representation section, ~lines 6/89/96/100) and every gormes-* skill’s Source Order / progress-contract wording to describe the split (and, post-C5, per-module) on-disk layout while EXPLICITLY preserving the single LOGICAL backlog contract: physically multiple files, one logical backlog, all agents/tools still go through internal/progress.Load (never hand-parse member files). Doc/skill-only; no runtime code. Must keep the symlinked skill loader views (.agents/.claude/.codex) consistent with the canonical docs/development-skills sources. Owned Gormes infra. | validated | docs | small | system | no runtime fixture (doc/skill copy change); skill-shape validated by quick_validate.py | If wording is ambiguous about logical-vs-physical, keep the stricter ‘single logical backlog via internal/progress.Load’ statement so no agent starts hand-parsing member files. |
| 8 / 8.F | Backlog split C5b: module-keyed split layout behind the existing API — Resolve the umbrella’s deferred phase-vs-module physical-key decision (RESOLVED: module-keyed — see umbrella note C5 RESOLUTION) by generalizing the C1 split layout to support a keyBy mode (phase | module) BEHIND the existing internal/progress.Load/SaveProgress API, with NO on-disk flip (pure additive, mirrors the C1/C5a additive precedent). Today internal/progress/split.go keys the split by phase: splitIndex={Meta, Phases []string} + phases/progress split command, and lossless Merge all still hold. No on-disk canonical flip in this slice; the monolith stays canonical. Owned Gormes infra. | validated | tools | medium | system | internal/progress/split_test.go | If a split layout’s declared keying is unknown/malformed, Load returns ErrMalformedSplit (never a silently partial or mis-keyed backlog); the phase-keyed and monolith paths keep working unchanged. |
| 8 / 8.F | Backlog split C5c: migrate webpages/docs raw progress.json readers to internal/progress.Load — webpages/docs Go tests currently os.ReadFile the canonical backlog and JSON-parse it as a single monolith: webpages/docs/phase5_docs_test.go:34 os.ReadFile("content/building-gormes/architecture_plan/progress.json") and webpages/docs/upstream_coverage_test.go:973 os.ReadFile(filepath.Join("content","building-gormes","architecture_plan","progress.json")); webpages/docs/docs_test.go’s content-walk must be audited for the same path. These run under go test ./webpages/docs — a gate on every commit/CI — and would fail the instant the canonical path becomes a module-keyed split DIRECTORY (C5). Convert each raw read to internal/progress.Load (or a tiny split-aware test helper that calls progress.Load then re-marshals through the stable marshaller) so the gate stays green whether the canonical path is a monolithic file OR a module-keyed split directory. Preserve every existing assertion; do not weaken. Also assert explicitly that the served/dist progress.json (.github/workflows/deploy-gormes-docs.yml:47 test -f dist/building-gormes/architecture_plan/progress.json) is a generated Hugo mirror — NOT the canonical — and is therefore unaffected by the flip. Owned Gormes infra; prerequisite for C5. | validated | docs | small | system | webpages/docs/phase5_docs_test.go ; webpages/docs/upstream_coverage_test.go ; webpages/docs/docs_test.go ; internal/progress.Load dual-layout (C1) + WriteSplitBy module-keyed (C5b) as the in-test split-directory fixture seam. | If a webpages/docs test genuinely needs raw bytes rather than the parsed model, it must obtain them via progress.Load + stable re-marshal, never by assuming a single on-disk file. |
| 8 / 8.F | Backlog split C5d: migrate gormes-* skill discovery commands off raw jq of the canonical progress.json — The gormes-planner, gormes-builder, and gormes-provider-parity SKILL.md discovery snippets run jq ... docs/content/building-gormes/architecture_plan/progress.json directly against the canonical backlog. jq on a directory fails, so every future planner/builder/provider-parity pass — and the autonomous fleet that drives them — breaks the moment the canonical path becomes a module-keyed split DIRECTORY (C5). Replace the raw-jq discovery snippets with a split-safe equivalent: prefer go run ./cmd/progress (a read-only list/emit) or jq over go run ./cmd/progress’s merged emit, so discovery returns the same row inventory whether the canonical is a monolithic file or a split directory. Canonical skill sources are docs/development-skills/cmd/progress lacks a discovery-shaped read-only emit, this row MAY add a pure, non-default read-only subcommand (mirror the ROW B/C1 separate-explicit-action precedent) — record any cmd/progress write-scope addition in the row note. Owned Gormes infra; prerequisite for C5. | validated | docs | small | system | docs/development-skills/{gormes-planner,gormes-builder,gormes-provider-parity}/SKILL.md ; quick_validate + find -L .agents/skills .claude/skills .codex/skills -maxdepth 2 -name SKILL.md symlink-shape gate. | If cmd/progress cannot emit the needed shape, the skill snippet must go run ./cmd/progress to a temp merged monolith and jq THAT — never jq the canonical path directly. |
| 8 / 8.F | Backlog split C5e: make non-Go raw progress.json consumers (fleet scripts + CI path globs) split-directory-safe — Two non-Go consumer classes still assume the canonical backlog is a single file and would break when C5 makes it a module-keyed split DIRECTORY (the canonical is ONE file, reachable as both docs/… via the docs→webpages/docs symlink and webpages/docs/…). (1) Fleet/cron shell scripts that jq "$PROGRESS_JSON" and require_file "$PROGRESS_JSON": scripts/gormes-architecture-planner-tasks-manager.sh (797,831,1039,1294,1326), scripts/documentation-improver.sh (251,491,516), scripts/landingpage-improver.sh (455,479) — [ -f dir ] and jq dir both fail, breaking the autonomous fleet’s TASKS.md / docs / landing generation. (2) GitHub Actions path-trigger globs paths: webpages/docs/content/building-gormes/architecture_plan/progress.json in .github/workflows/www-mirror-sync.yml (6,16) and .github/workflows/deploy-gormes-www.yml (9) — a glob ending in /progress.json does NOT match changed member files progress.json/index.json or progress.json/modules/*.json, so deploy + mirror-sync would SILENTLY stop firing on backlog changes (a silent pipeline break, worse than a loud test failure). Make both split-safe: shell scripts read the backlog via go run ./cmd/progress (emit/merge to a temp monolith) and gate existence with a split-aware check instead of raw jq/require_file -f; CI globs add …/progress.json/** while keeping the file glob for back-compat. deploy-gormes-docs.yml:47 test -f dist/.../progress.json is the generated Hugo artifact (not the canonical) — out of scope, note it explicitly as verified-unaffected. Owned Gormes infra; prerequisite for C5. | validated | tools | medium | system | scripts/gormes-architecture-planner-tasks-manager.sh ; scripts/documentation-improver.sh ; scripts/landingpage-improver.sh ; .github/workflows/www-mirror-sync.yml ; .github/workflows/deploy-gormes-www.yml ; internal/buildscripts_test.go / scripts orchestrator bats harness if they cover these scripts. | If cmd/progress cannot emit a merged monolith for a script, the script must fail loudly with a split-aware message — never silently jq an empty or directory path. |
| 8 / 8.F | Backlog split C5f: replace coarse module buckets with the approved feature taxonomy — Replace the current broad module constants with the approved fine feature taxonomy and make module names a closed enum for validation. The final allowed modules are: browser, builder, channels, cli, config, cross-cutting, doctor, docs, fleet, gateway, goncho, install, kanban, landing, learning-loop, memory, navivox, planner, profiles, progress, providers, release, runtime, sessions, skills, stt, tools, tts, tui. Old values such as commands, setup-config-install, gateway-channels, providers-auth, memory-sessions-skills, and orchestrator are migration inputs only, not valid final modules. Keep progress.Module as a compatibility fallback, but C5 must use explicit valid modules rather than owner-derived coarse buckets. | validated | tools | small | system | internal/progress/module.go ; internal/progress/module_test.go ; internal/progress/validate.go | Before C5, old module values may be recognized only by migration/audit tooling; after validation hardening they must fail with a row location and allowed-module list. |
| 8 / 8.F | Backlog split C5g: explicitly classify every row into a valid feature module — Backfill an explicit module field on every backlog row using the C5f feature taxonomy, with an automated first pass and a planner-audited correction pass. The migration may change only Item.Module. It must map old broad buckets to feature modules by row content, not by blind one-to-one rename: gateway-channels becomes channels, gateway, tts, stt, navivox, or browser as appropriate; memory-sessions-skills becomes memory, sessions, or skills; orchestrator becomes planner, builder, progress, fleet, kanban, or learning-loop. C5 remains blocked until every row has a valid explicit module and no row is unclassified. | validated | tools | medium | system | docs/content/building-gormes/architecture_plan/module-assignment-audit.md ; docs/content/building-gormes/architecture_plan/progress.json | If a row is too vague to classify, leave it out of the backfill and fail the audit; a planner must sharpen that row instead of guessing. |
| 8 / 8.F | Backlog split C5h: add module-scoped progress commands for planner and builder selection — Expose module filters as views over the single logical backlog, starting with one-module selection. Add a read-only progress command surface such as go run ./cmd/progress list --module providers that returns the same row inventory from a monolithic file or a module-keyed split directory. Planner and builder skills should name the module boundary before row selection; module filters must never create independent queues. | validated | tools | small | system | cmd/progress list --module providers ; docs/development-skills/gormes-{planner,builder,tdd-slice}/SKILL.md | If multi-module filtering is not ready, support exactly one module and fail clearly on comma-separated input; never silently broaden to the whole backlog. |
| 8 / 8.F | Backlog split C5i: render per-module roadmap pages before the physical split — Generate per-module roadmap pages from the single logical backlog before C5 flips the on-disk layout. Keep the unified roadmap canonical, but add module pages grouped by original phase/subphase with compact priority/status counts. The pages must expose each row’s module in normal review so misfiled rows are visible before the physical split. | validated | docs | small | system | docs/content/building-gormes/modules/ ; internal/progress/render.go | If generated module pages would duplicate too much content, render compact row summaries with links back to the unified roadmap; do not create hand-authored side backlog files. |
| 8 / 8.F | Backlog split C5: single atomic operator-gated flip to the module-keyed split directory — The actual on-disk flip, ONE single atomic operator-gated commit. Replace the single canonical FILE docs/content/building-gormes/architecture_plan/progress.json with a feature-module-keyed split DIRECTORY at that exact path. The physical member files must use the approved feature taxonomy from C5f, not the earlier broad transitional buckets. Every backlog consumer stays transparent via internal/progress.Load/SaveProgress; the format remains atomic-all-modules (index.json skeleton + every modules/ | validated | tools | medium | system | internal/progress/split_test.go module-keyed byte-stable round-trip (C5b) is the format basis; the flip itself is verified by merge(Load(dir)) == pre-flip monolith bytes + the full repo/docs/skill/fleet/CI gates green. | If any raw (non-progress.Load) reader of the canonical path remains (C5c/C5d/C5e not all shipped), or the fleet is mid-commit, or merge(materialized split) is not byte-identical to the pre-flip monolith — abort. Never commit a half-monolith/half-split, lossy, or orphaned-reader state. |
| 8 / 8.F | OpenCode part-cost telemetry adapter for builder loop — Teach the builder-loop cost telemetry parser to accept the current OpenCode JSONL step-finish schema where spend is emitted at part.cost, token counts are emitted at part.tokens.input and part.tokens.output, and timestamps may be numeric milliseconds. Preserve the older usage.cost schema and keep missing-cost output explicit. The shell cost-report path must return cost_status=ok for a local fixture containing part.cost, without using live providers or external credentials. | validated | tools | small | system | internal/loopcost/cost_test.go current OpenCode part-cost fixture; internal/codexubuilderloopscript_test.go shell cost-report fixture | If neither usage.cost nor part.cost is present, keep reporting unknown_no_cost_data and never present missing telemetry as $0.00. |
| 8 / 8.F | Progress next-work read-only selector — Expose a pure read-only go run ./cmd/progress next-work command that hides builder-loop candidate-selection mechanics behind a small operator interface. When at least one unblocked builder-ready row exists, it must report decision=build plus the top candidate identity and selection reason using the same ranking as internal/builderloop.NormalizeCandidates. When no candidate exists, it must report decision=plan with an explicit planner action instead of forcing agents to hand-parse generated markdown or create side queues. The command must work identically from monolithic and split progress layouts and must not mutate the canonical backlog. | validated | tools | small | system | internal/progressctl/progressctl_test.go next-work fixtures; cmd/progress/main_test.go CLI fixture | If candidate selection cannot load or validate progress, return the existing progress load/validation error; if zero candidates exist, return exit 0 with decision=plan and no side effects. |
| 8 / 8.F | Progress next-work repo-scope filter — Extend the read-only go run ./cmd/progress next-work selector with a repo-scoped option so development-goal runs locked to this repository can avoid claiming rows whose write_scope escapes the repo root, such as sibling Navivox Flutter app work. The option must keep using internal/builderloop.NormalizeCandidates for ranking, then filter by normalized write_scope containment under the selected --repo-root. When all builder-ready rows are outside the repo, it must exit 0 with decision=plan and a clear split/repair action instead of selecting cross-root work or creating a side queue. | validated | tools | small | system | internal/progressctl/progressctl_test.go repo-only next-work fixtures; cmd/progress/main_test.go CLI fixture | If no in-repo candidate remains after filtering, return decision=plan with explicit scope evidence; do not mutate progress data and do not treat cross-root candidates as buildable. |
| 8 / 8.F | Internal topology guard for package consolidation — Add a small topology guard that reports non-hidden internal top-level directory count, cmd/gormes direct internal import count, and active-row forbidden legacy roots without changing runtime behavior. The guard must support row-scoped old_roots/new_roots/owner_module/source_refs entries so each later package move can start with a meaningful RED proof instead of a brittle whole-repo directory allowlist. | validated | orchestrator | small | system | internal/internal_topology_test.go | metrics-only guard with no package move or runtime behavior change |
| 8 / 8.F | Internal CLI surface package rehome — Move the completed command-surface assembly from internal/cli/gormescli to internal/cli/gormescli while preserving the live Cobra command manifest, setup registry, slash-command ownership, root command ordering, and feature command constructors. This is the first Hermes-aligned step toward internal/cli/surface and internal/cli/commands/*. | validated | tools | medium | system | internal/internal_topology_test.go plus cmd/gormes/cli_contract_manifest_test.go | behavior-preserving import-path move only; no command spelling, flag, JSON, gateway, channel, tool, provider, or persistence behavior changes |
| 8 / 8.F | Internal tool compact helper package rehome — Move the terminal/execute_code output compaction helper from internal/toolcompact to internal/tools/compact while preserving reducer outputs, evidence metadata, terminal and execute_code compaction behavior, and cmd/gormes registry wiring. This is the first behavior-preserving Tool Adapter Enclave package move. | validated | tools | small | - | internal/tools/compact/compact_test.go plus cmd/gormes/registry_test.go and internal/tools terminal/execute_code compaction tests after the move. | If the rehome is incomplete, builds fail on the old import path or topology validation reports stale internal/toolcompact references; no runtime compatibility shim is allowed. |
| 8 / 8.F | Internal tool trace helper package rehome — Move the gateway/TUI/channel tool-progress rendering helper from internal/tooltrace to internal/tools/trace while preserving Hermes-style progress previews, block deduplication, tool_progress=new suppression, and channel/TUI render callers. This is the next behavior-preserving Tool Adapter Enclave package move after compact. | validated | tools | small | - | internal/tools/trace/tooltrace_test.go and internal/tools/trace/tooltrace_preview_test.go after the move, plus gateway/TUI/channel render focused tests. | If the rehome is incomplete, builds fail on the old import path or topology validation reports stale internal/tooltrace references; no runtime compatibility shim is allowed. |
| 8 / 8.F | Internal session search tool package rehome — Move the model-facing session_search adapter from internal/persistence/sessionsearchtool to internal/tools/sessionsearch while preserving descriptor text, JSON schema, argument normalization, memory/session catalog execution, degraded evidence, and cmd/gormes registry wiring. This is the next behavior-preserving Tool Adapter Enclave package move after compact and trace. | validated | tools | small | - | internal/tools/sessionsearch/session_search_tool_schema_test.go and internal/tools/sessionsearch/session_search_tool_execution_test.go after the move, plus cmd/gormes registry tests. | If the rehome is incomplete, builds fail on the old import path or topology validation reports stale internal/persistence/sessionsearchtool references; no runtime compatibility shim is allowed. |
| 8 / 8.F | Progress Control Plane staged deepening program — Track the staged architecture program that deepens the Progress Control Plane while preserving progress.json as the single Logical Backlog: workitem classification first, Progress Workspace layout/path seam second, Generated Artifact Plan third, and Progress Projections fourth. | validated | tools | umbrella | operator, system | internal/progress/workitem/workitem_test.go | Without the staged program, progress surfaces can drift independently and agents may select different next rows from the same Logical Backlog. |
| 8 / 8.F | Progress workitem row classification seam — Introduce a deep internal/progress/workitem module that indexes every Progress Row once, classifies it as assignable, blocked, umbrella, missing-proof, complete, needs-human, quarantined, or deferred, and exposes one Assignable Row Order consumed by progress next-work, RenderNextSlices, RenderAgentQueue, and builderloop.NormalizeCandidates. | validated | tools | medium | operator, system | internal/progress/workitem/workitem_test.go | If row classification stays split across render.go and builderloop/candidates.go, generated next-work views can disagree with the CLI selector about which Progress Row is assignable. |
| 8 / 8.F | Progress Workspace layout and path seam — Introduce a Progress Workspace module that owns canonical backlog path resolution, monolith-vs-module-split layout adapters, load/save/emit helpers, and generated artifact paths. Retire progress.split from canonical resolution now that the canonical progress.json path is already materialized as a module-keyed split directory; keep split export as explicit maintenance behavior only if still useful. | validated | tools | medium | operator, system | internal/progress/workspace/workspace_test.go | When layout handling leaks, callers must know whether progress.json is a file or directory and duplicate temp re-encode recipes to stay split-safe. |
| 8 / 8.F | Progress write generated artifact plan — Refactor progress write so it first builds a Generated Artifact Plan describing marker updates, module roadmap pages, and site mirrors, then executes that plan. Add a small progress write —dry-run adapter if it falls out naturally, listing artifact kinds and paths without writing. | validated | tools | medium | operator, system | internal/progress/ctl/artifacts_test.go | Without an artifact plan, progressctl.Write mixes output inventory, rendering, filesystem writes, and operator messages, making dirty-worktree impact hard to inspect before generation. |
| 8 / 8.F | Progress projections for active handoff shipped evidence and health — Add typed Progress Projections over the same Logical Backlog so callers can request active handoff, shipped evidence, and row health views without understanding every progress.Item field. This slice must not physically move shipped evidence, notes, provenance, health, or blockers into separate stores. | validated | tools | medium | operator, system | internal/progress/projections_test.go | If every caller consumes progress.Item directly, schema knowledge spreads and future row-field changes require broad edits across validators, renderers, and control-plane tools. |
| 8 / 8.G | Built-with-Gormes page scaffold — A page at gormes.ai/built-with lists real deployments or self-hosted uses of Gormes from a repo-owned entry template. The initial page may contain only TrebuchetDynamics’ own operator deployment, but it must label that truthfully, avoid fabricated users, and document the PR-based submission process plus the required entry fields inline. | validated | docs | small | operator | webpages/landing/src/data/builtWith.js + webpages/landing/src/pages/built-with.astro + webpages/landing/tests/home.spec.mjs | Without the page, even genuine outside users have no place to land their name; reputation compounds through visibility. |
| 8 / 8.G | Upstream Hermes user-stories static mirror — The static Gormes docs mirror for upstream Hermes user stories must expose the current source-backed story count, category rollup, and fresh representative entries from hermes-agent/website/src/data/userStories.json instead of collapsing the page to an omitted interactive component. The mirror is intentionally a static summary, not a copied React grid or a duplicate quote database. | validated | docs | small | operator, system | webpages/docs/upstream_user_stories_test.go::TestUpstreamHermesUserStoriesStaticMirror | Without the static summary, Gormes’ upstream archive hides the community-proof surface that Hermes publishes publicly, so docs/public parity looks stale even when the upstream submodule is current. |
| 9 / 9.A | Agent middleware chain framework — Gormes gains a declarative middleware chain framework for the agent runtime (internal/llm + internal/agent), inspired by DeerFlow’s 19-middleware ordered chain with @Next/@Prev positioning and RuntimeFeatures-style toggle flags. Each middleware implements a single concern: thread-data setup, sandbox lifecycle, tool error handling, loop detection, memory injection, title generation, token usage tracking, clarification interception. Middlewares are assembled into an ordered chain by a factory that accepts a RuntimeFeatures struct where each feature = enabled (use built-in), disabled, or a custom Middleware instance. The middleware ordering is deterministic, and the chain is inspectable (dump to string for debugging). | validated | orchestrator | medium | operator, system | internal/core/agent/middleware_test.go | Without the middleware chain, the agent runtime uses hardcoded turn lifecycle without inspection, ordering, or feature toggles. Individual agents can still run but cannot customize their behavior pipeline. |
| 9 / 9.B | Sandbox provider interface and virtual path security — Gormes gains a sandbox provider abstraction layer over the existing tool execution environment (internal/tools/, internal/tools/environment_*.go, internal/tools/filesystem_scope.go), inspired by DeerFlow’s SandboxProvider/Sandbox interface pair with virtual path security. The SandboxProvider interface defines acquire/get/release/shutdown lifecycle. A LocalSandboxProvider implementation provides filesystem-level isolation with per-thread virtual path mapping. The virtual path system uses a /mnt/user-data/{workspace,uploads,outputs} prefix that resolves to host paths with path-traversal rejection, path-family enforcement (read-only vs read-write), and output masking (host paths never leak into agent return values). Existing internal/tools/IsolationLevel (process/container/vm) and FilesystemScope continue to work — the sandbox provider wraps them rather than replacing them. | validated | orchestrator | medium | operator, system | - | - |
| 9 / 9.C | Agent personalities + enhanced display config — Gormes gains Hermes-parity config surface for agent personalities (12 built-in personas with custom system prompts), display preferences (show_reasoning, streaming, bell_on_complete, compact, cleanup_progress, busy_input_mode, background_process_notifications), and agent runtime settings (max_turns, reasoning_effort, gateway_timeout, api_max_retries, verbose). Personality prompts are injected into BuildSystemPrompt. | validated | orchestrator | medium | operator, system | internal/config/config.go | When no personality is configured, the agent uses the default identity prompt without personality injection. |
| 9 / 9.D | Transcribe audio tool registration + local whisper provider — Gormes registers the existing transcribe_audio tool in the default tool registry so STT works by default with no API key. A LocalSTTProvider wraps the WASI whisper runtime (internal/wasi/whisper/) into the TranscriptionProvider interface with auto-downloading tiny.en model (~77MB from HuggingFace). Cloud STT providers (OpenAI, Groq, Mistral, XAI) are registered alongside and activate when their API keys are present. | validated | orchestrator | small | operator, system | internal/tools/transcription_providers_local.go | When the local whisper model is unavailable (first-run download failure or disk full), the transcribe_audio tool reports stt_provider_unavailable instead of crashing. Cloud providers (OpenAI, Groq, Mistral, XAI) still work if their API keys are configured. |
| 9 / 9.E | Remove SSH Navivox stdio path — Delete the SSH stdio Navivox transport — gormes navivox serve|pair|setup-host CLI surface, the wire-protocol Go package (codec/frames/server/status), the Flutter dartssh2 transport and ssh_navivox_channel client, and the dartssh2 dependency. Preserve the HTTP/WS path (internal/channels/navivox/channel.go and flutter-navivox/app/lib/core/gateway/*). Move PlatformName = "navivox" from protocol.go into the surviving channel package so deleting protocol.go does not break the gateway import. | validated | gateway | medium | - | internal/channels/navivox/channel_test.go | - |
| 9 / 9.E | Remove Flutter Navivox fake-server mode and wire protocol — Delete the Flutter SSH wire-protocol artifacts that survived the first cut: lib/core/protocol/navivox_frame.dart, lib/core/protocol/frame_reader.dart, lib/core/transport/byte_transport.dart, lib/core/transport/in_memory_transport.dart, lib/core/channel/fake_navivox_channel.dart. Rework lib/core/channel/navivox_channel_provider.dart so activeNavivoxChannelProvider returns the gateway channel directly (no fake/gateway switcher), and delete NavivoxChannelMode/NavivoxChannelSwitcher. Update enterFakeServerMode callers in the router to use a real local gateway against a test fixture instead. | validated | gateway | small | - | flutter-navivox/app/test/router/app_router_test.dart | - |
| 9 / 9.E | Remove Flutter SSH keys feature — Delete the Flutter SSH keys feature in its entirety: lib/features/keys/ (data, services, screens), the lib/features/keys/services/openssh_key_validator.dart already on the SSH deletion shortlist, and the entire test/features/keys/ directory. Remove the Keys route from the router and any sidebar/menu entries. Drop drift, drift_dev, and sqlite3 from pubspec.yaml (sole consumers are KeyDatabase/DriftKeyStore). Drop generated key_database.g.dart. After this slice, Flutter Navivox has no dart:io-only or native-FFI-only dependencies and can be web-targeted (the original blocker the operator flagged on 2026-05-14). | validated | gateway | small | - | flutter-navivox/app/test/router/app_router_test.dart | - |
| 9 / 9.E | Navivox VPN host enumeration helper — An owned internal/network/vpnhost package enumerates active VPN interfaces — Tailscale (via tailscale ip -4/-6), WireGuard (via ip -j link show type wireguard), and tun-class OpenVPN/IPSec devices — returning {iface, kind, ipv4, ipv6} tuples ordered Tailscale → WireGuard → other VPN. The navivox HTTP gateway channel (mandatory-VPN bind, connect command) consumes it. The package is built fresh — no SSH-pair code remains by this point. | validated | gateway | small | - | internal/network/vpnhost/vpnhost_test.go | - |
| 9 / 9.E | Navivox HTTP gateway mandatory-VPN bind — When [navivox] exposure_mode is tailscale, wireguard, or vpn, config validation and gateway startup refuse to start unless bind_host matches an active VPN interface IP returned by internal/network/vpnhost. Loopback (127.0.0.1) remains valid only under exposure_mode=local; 0.0.0.0/:: remains gated behind exposure_mode=public + public_confirmed=true. The exposure_mode allowlist is extended to add wireguard and vpn alongside existing local/tailscale/public. | validated | gateway | small | - | internal/config/navivox_config_test.go | - |
| 9 / 9.E | Navivox HTTP gateway connect command — A gormes navivox connect subcommand prints the connect URL(s) for the live navivox HTTP channel — one per active VPN interface returned by internal/network/vpnhost — with --json shape {build, host, host_source, port, token_required, base_url, healthz_url, websocket_url}. Loopback URL is included only when exposure_mode=local. The command never prints the static_token value; it only reports token_required: true/false. A new top-level gormes navivox cobra command is reintroduced (deleted with the SSH path) and re-registered in cmd/gormes/main.go. | validated | gateway | small | - | cmd/gormes/navivox_connect_info_test.go | - |
| 9 / 9.E | Navivox setup QR image pairing handoff — gormes setup gateway for Navivox writes a secret-safe PNG QR handoff after configuring the HTTP/WebSocket channel. The QR payload is a navivox://connect descriptor containing base_url, websocket_url, auth_mode, exposure_mode, token_required, and rest_token only when token auth is selected. The raw token is stored in .env and never printed to stdout/stderr; the PNG is written under $GORMES_HOME/navivox/pairing.png with owner-only permissions because it embeds the token and URL. | validated | gateway | small | operator, gateway, system | cmd/gormes/setup_gateway_test.go::TestSetupGatewayNavivoxLocalModeWritesSafeConfig and cmd/gormes/setup_navivox_test.go::TestNavivoxSetupPairingURIIncludesRESTTokenAndURLs | If QR generation fails, setup exits with typed setup evidence instead of printing token material. If the operator cannot scan QR, the existing token-redacted gormes navivox connect command and manual token entry remain available. |
| 9 / 9.F | Navivox HTTP/WS documentation refresh — Refresh the stale Navivox planning docs so every operator and builder surface describes the current HTTP/WebSocket gateway channel, not the deleted SSH stdio transport. The pass updates the decision record, PRD, architecture, routes, testing plan, and generated CLI/docs references to say that gormes navivox connect, /healthz, /v1/navivox/status, /v1/navivox/turn, and /v1/navivox/stream are the supported path; gormes navivox serve|pair|setup-host, NVOX binary frames, dartssh2, SSH keys, and fake-server mode are historical/deleted only. | validated | docs | small | operator, gateway, system | docs/content/cli/navivox.md | If docs remain stale, builders may reintroduce the deleted SSH/stdin protocol or fake-server client; runtime behavior stays HTTP/WS but operator setup guidance is contradictory. |
| 9 / 9.F | Navivox connect-and-talk first screen — Make the first useful Navivox screen a connected chat/voice test surface: after the operator pastes or scans gormes navivox connect, the app connects to the HTTP/WS gateway, shows gateway readiness, opens chat as the primary surface, and lets the operator send text or a device-transcribed voice turn to Gormes without configuring telephony. The slice should prove the Dograh-style Web Call insight through a local fake HTTP gateway and one real internal/channels/navivox fixture. | validated | gateway | medium | operator, gateway, system | flutter-navivox/app/test/features/setup/navivox_connect_and_talk_test.dart | When the gateway is not reachable, the app shows a bounded setup/connect error with gormes navivox connect guidance and keeps text-only fallback available; it never suggests telephony as the prerequisite for a first agent turn. |
| 9 / 9.F | Navivox profile contact summary API — Expose a safe server-authoritative Navivox profile contact read model before visual chat polish. Gormes returns one contact per server_id + profile_id, where the profile is an isolated Gormes home/config/secrets/sessions/memory/skills/runtime state, not a live OpenClaw-style multi-agent router. Provide an HTTP snapshot plus WebSocket updates with display name, deterministic avatar seed, sanitized latest preview, health, compact workspace-root counts, attention badges, mic availability, and active-turn state. Flutter renders/caches this summary but keeps local-only metadata such as pins, aliases, command word, calibration, and trusted-server state out of the API. No raw workspace paths, secrets, tokens, config documents, or session payloads appear in contact rows. | validated | gateway | medium | operator, gateway, system | internal/channels/navivox/profile_contacts_test.go | If profile contact updates cannot stream, Navivox keeps the last HTTP snapshot visible with a stale/reconnecting badge and refreshes on reconnect; offline or unauthorized profiles remain visible with disabled send/mic affordances and recovery actions. |
| 9 / 9.F | Navivox API capability contract — Expose a first-class authenticated /v1/navivox/capabilities document for Navivox clients. The document must list supported Navivox endpoints, health aliases, event kinds, auth mode, profile-management actions, attachment limits, voice/STT/TTS support, canonical stream/deprecation rules, and must keep dashboard /api/profiles, raw local paths, tokens, and provider credentials out of the mobile contract. | validated | gateway | small | operator, gateway, system | internal/channels/navivox/capabilities_test.go | If the capability document is unavailable, Navivox clients must keep profile creation/import, attachment upload, and voice affordances disabled or in unavailable-sheet mode instead of guessing from dashboard or OpenAI-style endpoints. |
| 9 / 9.F | Navivox continuous voice command mode — Implement the first continuous voice mode as a local Navivox app mode pointed at one active trusted server + profile contact. The row mic starts device STT when mic permission and per-server local trust exist; transcripts auto-send as voice-marked user bubbles with a short visible Sending... grace window. The command word defaults to navi, is locally configurable and optionally calibrated, is recognized only at utterance start while voice mode is open, and drives local commands before text reaches Gormes: direct navi <profile>, navi cancel, navi stop, navi settings, navi help, plus a five-second command mode when the word is spoken alone. navi cancel follows the voice-turn lifecycle: discard before server commit, request cancel/stop when supported after commit, and never claim side effects were undone. | validated | gateway | medium | operator, gateway, system | flutter-navivox/app/test/features/voice/continuous_voice_command_mode_test.dart | When device STT, mic permission, server trust, or gateway cancellation is unavailable, typed chat remains usable; mic affordances show disabled reasons, command mode stays local, and post-commit cancellation degrades to stopped/cannot-undo evidence instead of deleting history. |
| 9 / 9.F | Navivox Telegram-inspired chat polish — After the connect-and-talk loop and profile contact summary API work, make Navivox feel like a polished Telegram-inspired operator client without changing the Gormes HTTP/WS backend. Render a flat profile-contact list with deterministic avatar, display name, small server label, sanitized latest preview, timestamp, health, attention badges, workspace counts, and mic availability; render the profile chat screen with grouped Telegram-style bubbles, compact timestamps, local send/queued/streaming/done/error ticks, a pinned redacted server/profile/trust banner, always-reachable composer, and a global continuous-voice bar when active. Use Telegram-like draggable sheets for profile/server/action/tool detail flows. Evaluate v_chat_bubbles as the bubble renderer (VBubbleStyle.telegram, VCustomBubble for ToolCallCard, performance config for long transcripts) but fall back to local widgets if the package fails accessibility, theming, performance, or dependency review. This row is visual/interaction polish only: no TDLib, MTProto, Firebase chat backend, Telegram login, telephony, campaigns, or call-center scope. | validated | gateway | medium | operator, gateway | ../navivox-app/test/features/chat/profile_contact_list_test.dart + ../navivox-app/test/features/chat/transcript_bubble_test.dart + ../navivox-app/test/features/chat/transcript_thread_test.dart + ../navivox-app/test/shared/app_shell_test.dart | If v_chat_bubbles is unsuitable, Navivox keeps the current simple adapter and implements only local Telegram-like preview tiles, grouped bubbles, status ticks, and sheets. Text chat, tool cards, and voice transcript fallback remain usable. |
| 9 / 9.F | Navivox natural-language profile seed backend API — Add the in-repo Gormes profile seed backend: a CLI/API path that accepts a short natural-language seed such as screen inbound leads, triage support calls, or work on mineru repo, returns an editable validated profile draft, and can apply that draft through the existing server-authoritative Gormes profile/config seams. The generator may call a provider when configured, but must always degrade to deterministic local templates with generation_source=template; workspace paths are suggestions only until the operator explicitly confirms them and Gormes validates/canonicalizes them server-side. Navivox may request the backend over the authenticated HTTP channel, but this row does not edit the sibling Flutter app. | validated | orchestrator | medium | operator, gateway, system | cmd/gormes/profile_seed_test.go | Without a model/provider, seed creation uses a deterministic local template and marks generation_source=template; invalid or risky seeds are rejected with redacted typed evidence and do not mutate profile config or workspace roots. |
| 9 / 9.F | Navivox natural-language profile seed Flutter UI — Add the sibling Navivox Flutter profile seed UI that calls the Gormes backend profile-seed API, offers Create from seed in the chat/profile flow, renders the returned editable draft fields, requires explicit workspace path entry or confirmation, applies only through the backend, and then shows the new profile as a contact. The Flutter app must not write TOML or infer/grant workspace roots on its own. | validated | gateway | medium | operator, gateway, system | ../navivox-app/test/features/profiles/profile_seed_flow_test.dart | Without a model/provider, seed creation uses a deterministic local template and marks generation_source=template; invalid or risky seeds are rejected with redacted typed evidence and do not mutate profile config or workspace roots. |
| 9 / 9.F | Navivox structured tool event cards backend API — Gormes gateway and Navivox backend emit structured tool progress through gateway.ToolProgressSender as tool_call_started, tool_call_updated, and tool_call_finished events with stable IDs, redacted metadata, run-record evidence, and no assistant-prose leakage; existing non-Navivox text progress remains unchanged. | validated | gateway | medium | operator, gateway, system | internal/gateway/tool_progress_structured_test.go; internal/channels/navivox/tool_events_test.go; internal/persistence/session/navivox_run_record_test.go | If a channel lacks structured tool progress, gateway falls back to existing bounded text progress; if Navivox receives malformed tool metadata, it renders redacted error evidence inside a tool card instead of assistant text. |
| 9 / 9.F | Navivox structured tool event cards Flutter UI — The sibling Navivox Flutter app consumes the Gormes backend structured tool-progress contract, upserts one durable ToolCallCard per tool_call_id for started/updated/finished states, renders redacted artifact rows, and never converts tool events into assistant prose. | validated | gateway | medium | operator, gateway, system | ../navivox-app/test/core/channel/gateway_navivox_channel_test.dart; ../navivox-app/test/features/chat/ | If a channel lacks structured tool progress, gateway falls back to existing bounded text progress; if Navivox receives malformed tool metadata, it renders redacted error evidence inside a tool card instead of assistant text. |
| 9 / 9.F | Navivox safe config admin backend API — Implement the Gormes-owned Navivox config admin backend seam: authenticated schema, redacted config.get, diff, validate, apply, and reload-or-pending-restart results over the existing HTTP/WS channel. Gormes remains the source of truth for validation and writes; the backend returns typed field errors, secret status/source evidence, redacted before/after data, and reload evidence without exposing raw secret values or requiring sibling Flutter edits. | validated | gateway | medium | operator, gateway, system | internal/channels/navivox/config_admin_test.go | When config validation fails, Navivox shows typed field errors and keeps the last-good server config active; secret values are never returned, logged, or echoed, only status/source/redacted evidence. |
| 9 / 9.F | Navivox safe config admin Flutter UI — Render the Navivox config admin backend contract in the sibling Flutter app: schema-driven controls, redacted current values, diff/validate/apply confirmation, secret set/rotate/delete/test actions, and reload-or-pending-restart status. Flutter consumes backend schema and actions only; it never edits config.toml, .env, or raw secret values directly. | validated | gateway | medium | operator, gateway, system | ../navivox-app/test/features/config/config_screen_test.dart | When config validation fails, Navivox shows typed field errors and keeps the last-good server config active; secret values are never returned, logged, or echoed, only status/source/redacted evidence. |
| 9 / 9.F | Navivox voice run records backend API — Define the in-repo Gormes backend run-record seam for Navivox turns: durable text transcript, optional voice transcript metadata, device/server STT evidence, TTS output metadata, typed tool timeline entries, provider usage/cost when available, attachment/artifact refs, terminal status, and a redacted inspection read model. The backend API/gateway work must stay inside this repository and use existing session/store/apiserver surfaces where possible; sibling Flutter rendering is a separate dependent row. | validated | memory | medium | operator, gateway, system | internal/persistence/session/navivox_run_record_test.go | If audio bytes, STT, TTS, or usage data are unavailable, the run record stores explicit unavailable evidence and preserves the text transcript/tool timeline instead of dropping the run or faking costs. |
| 9 / 9.F | Navivox voice run records Flutter inspection UI — Render the sibling Flutter Navivox inspection surface for backend run records after the Gormes run-record API lands. The app should fetch or receive redacted run records, show text and voice transcript evidence, STT/TTS metadata, tool timeline cards, attachment/artifact refs, terminal status, and provider usage/cost with explicit unknown states. This row is intentionally cross-root and must not be selected during repo-only Gormes iterations. | validated | gateway | medium | operator, gateway, system | ../navivox-app/test/features/chat/navivox_run_records_test.dart | If audio bytes, STT, TTS, or usage data are unavailable, the run record stores explicit unavailable evidence and preserves the text transcript/tool timeline instead of dropping the run or faking costs. |
| 9 / 9.F | Navivox per-profile BYO voice profiles backend API — Add the Gormes-owned backend seam for per-profile Navivox voice profile metadata: typed config fields, provider-matrix validation, redacted credential status, authenticated HTTP read/validate responses, and voice-turn run-record fallback evidence. | validated | provider | medium | operator, gateway, system | internal/channels/navivox/voice_profiles_test.go; internal/config/profile_voice_profile_test.go | When a requested STT/TTS provider is unavailable, Gormes records provider_unavailable evidence, falls back according to the profile policy, and keeps text chat usable; secrets remain redacted/write-only. |
| 9 / 9.F | Navivox per-profile BYO voice profiles Flutter UI — Add the sibling Navivox Flutter profile/config controls that consume the backend voice-profile contract without writing config files or storing raw secrets. | validated | gateway | medium | - | ../navivox-app/test/features/profiles/; ../navivox-app/test/features/config/ | - |
| 9 / 9.G | PicoClaw-derived channel media and identity regression matrix — Add a fake-adapter regression matrix, sourced from current PicoClaw channel reports, that proves Gormes preserves sender identity, allowlist decisions, durable rich-media envelopes, voice transcript text, PDF/document attachment metadata, Feishu-style tool-progress notifications, and same-agent final delivery semantics through the channel-neutral gateway path. | validated | gateway | medium | operator, gateway, system | internal/gateway/picoclaw_channel_regression_test.go | Unsupported channel/media combinations return redacted typed evidence, unknown senders fail closed, and final answers are sent through the existing fresh-final or coalescer fallback without editing placeholder/tool-feedback messages into user-visible finals. |
| 9 / 9.G | PicoClaw-derived session ledger read-model regression matrix — Add a session-ledger read-model regression matrix that proves Gormes stores and renders multiple user messages in a turn, per-message timestamps, sender attribution, durable attachment references, and non-destructive reset metadata without collapsing history to session.updated or deleting older channel history. | validated | memory | small | operator, gateway, system | internal/persistence/session/picoclaw_ledger_regression_test.go | Legacy session records without per-message timestamps render explicit unknown timestamp evidence and keep original order; reset operations mark boundaries without deleting prior transcript rows. |
| 9 / 9.G | PicoClaw-derived provider stream and auth regression matrix — Add a provider regression matrix from PicoClaw reports that replays fake OpenRouter reasoning-model chunks, Codex Responses output_item.done events, 401/auth failures, local LM Studio/OpenAI-compatible model routing, and retryable LLM-call failures through Gormes provider seams with no live credentials. | validated | provider | medium | operator, system | internal/llm/picoclaw_provider_regression_test.go | Provider failures surface classified, action-oriented diagnostics with provider/model/auth source evidence while raw stream frames remain available for audit and never leak reasoning text into assistant-visible content. |
| 9 / 9.G | PicoClaw-derived tool path safety regression pack — Add a workspace path-safety regression pack that proves Gormes command/file tools keep relative paths relative to the active workspace, reject root enumeration such as find /, preserve virtual-path masking, and apply command guards to the parsed command body before execution. | validated | tools | small | operator, system | internal/tools/picoclaw_path_safety_regression_test.go | Unsafe or ambiguous paths are blocked with typed redacted evidence and no filesystem mutation; virtual host paths are masked back to virtual paths before returning tool output. |
| 9 / 9.G | MCP Streamable HTTP session lifecycle compatibility — Extend the MCP HTTP client compatibility fixture to the current Streamable HTTP contract: initialize captures Mcp-Session-Id, all subsequent POST/GET/DELETE requests replay that header, SSE responses are accepted from the single MCP endpoint, 404 with a session header triggers a new initialization path, and legacy HTTP+SSE /sse endpoint events with sessionId are classified as backwards-compatibility input rather than silently dropping the session. | validated | tools | small | operator, system | internal/tools/mcp_streamable_http_session_test.go | Servers that require a session ID but omit or reject it produce mcp_session_required or mcp_session_expired evidence; unsupported legacy SSE endpoint shapes remain visible compatibility failures instead of empty-session POSTs. |
| 9 / 9.G | Dynamic agent identity inheritance regression matrix — Add a dynamic-agent identity regression matrix that proves spawned or delegated agents keep explicit parent linkage while receiving their own SOUL/persona, tool policy, AGENTS.md scope, and memory/search scope, so child agents do not silently inherit the root agent role as their own identity. | validated | orchestrator | small | operator, child-agent, system | internal/core/subagent/picoclaw_identity_regression_test.go | If a child identity cannot be resolved, delegation refuses with child_identity_unresolved evidence and leaves the parent turn untouched rather than running as an ambiguous root persona. |
Authoring Rule
Section titled “Authoring Rule”New priority progress rows should add contract metadata when the row is used as an implementation handoff. The fields are optional only for historical rows and inventory buckets.
Required handoff shape:
{ "name": "Short executable slice", "status": "planned", "contract_status": "draft", "contract": "The upstream behavior being preserved", "slice_size": "small", "execution_owner": "gateway", "trust_class": ["operator"], "degraded_mode": "How partial capability becomes visible", "fixture": "The replayable local fixture proving compatibility", "source_refs": ["docs/content/upstream-hermes/source-study.md"], "blocked_by": ["optional dependency"], "unblocks": ["optional downstream slice"], "ready_when": ["The dependency or handoff condition is true"], "acceptance": ["fixture or behavior that proves this row is done"]}Canonical Progress Source
Section titled “Canonical Progress Source”There is one docs-side progress source:
docs/content/building-gormes/architecture_plan/progress.jsonDo not reintroduce docs/data/progress.json. The website can keep an embedded
copy under www.gormes.ai/internal/site/data/progress.json, but that file is a
generated site asset, not a planning source.