AGENTS.md
apps/desktop/AGENTS.mdAGENTS.md
Quality
44/100
Scores the file, not the repository.Length
1,708 words
13 headings · 0 code blocksRepository
225k
— · pushed 0 days agoLast changed
3 days ago
First indexed 3 days ago.1# Desktop Engineering Guide23How to build Hermes Desktop well. This is a judgment guide, not an inventory —4it teaches the invariants and the reasoning behind them so a change fits the app5even as files move. Read it with the repository `AGENTS.md` (root rules still6apply) and [`DESIGN.md`](./DESIGN.md) for the visual and interaction contract.78When a rule here and the code disagree, trust the code and fix whichever is9wrong — but never break an invariant to make a change easier.1011## What this app is1213Desktop is its own native chat surface. It is not the browser dashboard and it14does not embed the TUI. Three parties, each authoritative for one thing:1516- **Electron** owns the machine: process lifecycle, native filesystem/git/17 windows, install/update, and a narrow, typed capability bridge.18- **The renderer** owns the experience: navigation, presentation, and ephemeral19 interaction state.20- **The agent backend** owns the work: sessions, tools, model calls, streaming.2122Keep the seams clean. The renderer never reaches for Node or Electron directly;23native power arrives through a deliberate capability, not a general escape hatch.24Agent behavior lives behind the gateway, never reimplemented in React. When a25change blurs a seam, that is the smell — fix the seam, don't widen it.2627## Decide state by authority2829The first question for any piece of state is *who is allowed to be right about30it*, not where it is convenient to store it. Put state with its authority:3132- The **backend** is authoritative for anything another Hermes surface can also33 change. Treat the renderer's copy as a cache of that truth.34- **Electron** is authoritative for machine and runtime facts.35- The **renderer** owns only what is purely about this window's presentation.3637From that, everything else follows: shared renderer state lives in small stores38owned by the feature that owns the concern; request-shaped server data that wants39invalidation lives in the query layer; short-lived interaction detail stays in40the component; hot coordination that must not paint stays in a ref. Reach for the41narrowest home that still lets the state be correct. A new global store is a42claim that many distant surfaces need it — earn that claim.4344Persisted state must declare its scope in its own key: is this global, or does it45belong to a connection, a profile, a stored session, a project, or a window?46Getting the scope wrong is how one profile's setting bleeds into another.4748## Identity is not incidental4950Sessions have more than one identity, and conflating them is a recurring source51of "session not found" and vanishing history. Reason about which identity a52surface needs: durable navigation and anything the user pins or persists key off53the stable/durable identity; live streaming keys off the runtime identity; state54that must outlive compression keys off the lineage root. Keep the mapping between55them explicit and translate at the boundary rather than passing the wrong id56inward.5758## Server truth is cached, not owned5960The renderer paints from a cache of backend truth, so it must reconcile, not61assume:6263- **Merge, don't clobber.** A refresh is new information layered over what you64 already know, not a replacement that can drop live or pinned rows.65- **Be optimistic, then honest.** Direct manipulation should paint immediately66 from a snapshot; a failed write rolls back visibly and an authoritative67 refresh gets the last word.68- **Guard against the past.** Async results can arrive out of order; a stale69 response must never overwrite newer intent. Generation counters and request70 tokens exist for this.71- **Isolate the foreground.** Only the surface the user is looking at may publish72 into the shared view; background work updates its own cache quietly.73- **Coalesce noise, flush signal.** Batch high-frequency cosmetic updates, but74 let terminal transitions (a turn finishing, needing input, failing) reach the75 user immediately.76- **Preserve reference identity on no-ops.** Handing React a fresh array that77 contains the same data re-renders expensive trees for nothing.7879## Switching context is a re-home, not a reboot8081Changing profile, connection, or mode is a workspace switch, not a cold start.82The shell and whatever the user was doing stay put; only the gateway-bound view83is cleared and repopulated, and the previous context must not leak into the next84one. Reserve the full-screen boot/connecting experience for a genuinely unusable85backend.8687There are three distinct switch shapes, and conflating them is the classic bug:8889- A **connection/mode apply** (local ↔ remote ↔ cloud) is the soft re-home:90 shell mounted, gateway-bound stores explicitly wiped, then reconnect. Query91 invalidation alone cannot evict live session stores — wipe them.92- A **runtime home change** (switching the underlying `HERMES_HOME` profile) is93 a hard re-home: the window legitimately reloads and state resets by remount.94- A **live profile swap** in the same window activates another profile's socket95 while background profiles keep streaming; lists merge rather than wipe, and96 only an explicit user selection starts a fresh foreground draft.9798Treating a soft switch as hard flickers the app; treating a hard one as soft99strands stale rows. After any swap, the active socket, active profile, and100connection atoms must agree, or REST and filesystem calls route to the wrong101backend.102103## Cross everything as an observable ladder104105Desktop lives at the seams: versions, profiles, local vs remote vs cloud,106partially installed runtimes, stale caches, older backends. The durable technique107for all of it is the same — an ordered ladder of candidates:1081091. Precedence is written down, in one place, as data or a pure function.1102. A candidate is trusted only after it is validated at the right boundary.111 Existence is not proof; probe what you're about to rely on.1123. A failed *read* falls to the next rung; a failed *authoritative write*113 surfaces or rolls back rather than silently retargeting.1144. A missing capability and a transient failure are different: the first may115 enable a compatibility path or a disabled state; the second should retry.1165. Retries are bounded and end in a real recovery affordance — never an infinite117 spinner or a hot loop.1186. One resolver owns each policy so every caller gets the same answer. Scatter is119 how two call sites drift apart.120121This is the shape of backend discovery, command/version fallbacks, connection and122auth resolution, workspace-cwd selection, capability detection, and preview123normalization alike. Learn the shape, not a snapshot of the current rungs.124125Two auth-flavored corollaries worth naming because they are easy to get wrong:126127- **One-time credentials are never reused.** An OAuth gateway connection mints a128 fresh WebSocket ticket on every dial and never falls back to the cached URL.129 Only a confirmed 401/403 (or an explicitly tagged auth rejection) means130 reauthentication; timeout, network, malformed-response, and server failures131 remain connectivity errors. Only long-lived token/local auth may reuse a132 cached URL as a lower rung.133- **A connection test must exercise the leg you'll actually use.** An HTTP134 status probe passing while the WebSocket/auth leg fails is a false positive135 that ships as "it said connected but nothing works."136137## Compatibility without carrying the past forever138139Desktop and its runtime update on separate clocks, so a change can meet an older140backend. Keep those users working: preserve the current feature, keep the141fallback narrow and tied to an identified older runtime, and cover it with a142test. A fallback that quietly degrades the feature it's meant to protect is worse143than the crash it replaced.144145## Keep the waist narrow, grow at the edges146147The root contribution rubric governs here too. New capability should arrive at148the smallest surface that solves it: extend what exists, add a feature locally,149lean on an existing seam — before you invent a framework. The shell's internal150registries are composition seams, not a public plugin ABI; do not build a151universal extension system, a manifest, or a plugin adapter for a single152consumer. Design a shared contract only once more than one real consumer proves153its shape. "Plugin" means several unrelated things across Hermes — do not assume154one surface's extension model runs in another.155156## Respect the person using it157158Design and engineering meet at intent. The user's attention and context are159sacred:160161- Never navigate, move focus, or open a surface because something *happened* in162 the background. Offer; don't hijack.163- The states around loading are distinct experiences — empty, loading,164 reconnecting, degraded/stale, and exhausted-recovery each deserve their own165 honest copy and their own way out.166- Keyboard ownership follows focus. The focused surface wins its keys; one167 cancel gesture does exactly one thing.168- Expensive, stateful surfaces (terminals, live tools) stay alive when hidden.169 Visibility is not lifecycle.170171## Make it feel instant172173Performance is a feature the user feels, especially in drag, resize, scroll,174typing, streaming, and terminals. The principles are timeless even as the code175changes: keep hot-path state local or narrowly derived; don't subscribe heavy176trees to per-frame updates; coalesce pointer work; avoid reading layout right177after writing style; and don't mount expensive content mid-gesture. Prove speed178against realistic content — a fast empty demo proves nothing about a long179transcript. If motion is masking latency, remove the motion, don't tune it.180181## Testing as a habit of proof182183Test the behavior that would actually break a user, not a snapshot of today's184data. Favor invariants over frozen values. Exercise the real path for anything185at a seam — resolver precedence and its failure rungs, identity and scope186boundaries, optimistic rollback and stale-response ordering, and both sides of a187local/remote adapter with its profile routing intact. Match how the suite is188actually run rather than inventing a command; when in doubt, read the scripts.189190## The taste test before you hand off191192- Does every piece of state live with its authority, at the narrowest scope?193- Would a background event ever steal the foreground or the user's focus?194- Does each resolver have one home, a validated ladder, and a bounded, recoverable195 end?196- Do local, remote, and profile routing still agree?197- Does async failure leave a usable UI and a way forward?198- Do hot interactions stay cheap under realistic load?199- Does the change pass the [`DESIGN.md`](./DESIGN.md) checklist and update all200 locales?201202If any answer is "not sure," that's the part to go verify.203
Also in NousResearch/hermes-agent
Diff this repo’s formatsOne repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| NousResearch/hermes-agentAGENTS.md · 225k | AGENTS.md | setupbuildtestlint-format+11 | 84/100 | 3 days ago |
Similar configs
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| n8n-io/n8npackages/@n8n/agents/AGENTS.md · 199k | AGENTS.md | buildteststylearch+3 | 100/100 | 3 days ago | |
| aaif-goose/gooseAGENTS.md · 52k | AGENTS.md | setupbuildtestlint-format+6 | 100/100 | 3 days ago | |
| SkeneTechnologies/skene-cookbookAGENTS.md · 51 | AGENTS.md | setupbuildtestlint-format+7 | 100/100 | 2 days ago | |
| duckduckgo/content-scope-scriptsspecial-pages/AGENTS.md · 70 | AGENTS.md | buildteststylearch+3 | 100/100 | 3 days ago | |
| mui/material-uiAGENTS.md · 99k | AGENTS.md | setupbuildtestlint-format+9 | 100/100 | 3 days ago | |
| OnlyTerp/prompt-cache-skillsAGENTS.md · 112 | AGENTS.md | setupbuildtestlint-format+5 | 100/100 | 3 days ago | |
| trick77/agents-md-syncAGENTS.md · 2 | AGENTS.md | setupbuildteststyle+5 | 100/100 | 3 days ago | |
| vllm-project/vllmAGENTS.md · 88k | AGENTS.md | setuptestlint-formatstyle+5 | 100/100 | 3 days ago |
