Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 169 additions & 0 deletions .agents/skills/lat-md/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
---
name: lat-md
description: >-
Writing and maintaining lat.md documentation files — structured markdown that
describes a project's architecture, design decisions, and test specs. Use when
creating, editing, or reviewing files in the lat.md/ directory.
---

# lat.md Authoring Guide

This skill covers the syntax, structure rules, and conventions for writing `lat.md/` files. Load it whenever you need to create or edit sections in the `lat.md/` directory.

## What belongs in lat.md

`lat.md/` files describe **what** the project does and **why** — domain concepts, key design decisions, business logic, and test specifications. They do NOT duplicate source code. Think of each section as an anchor that source code references back to.

Good candidates for sections:
- Architecture decisions and their rationale
- Domain concepts and business rules
- API contracts and protocols
- Test specifications (what is tested and why)
- Non-obvious constraints or invariants

Bad candidates:
- Step-by-step code walkthroughs (the code itself is the walkthrough)
- Auto-generated API docs (use tools for that)
- Temporary notes or TODOs

## Section structure

Every section **must** have a leading paragraph — at least one sentence immediately after the heading, before any child headings or other block content.

The first paragraph must be ≤250 characters (excluding `[[wiki link]]` content). This paragraph is the section's identity — it appears in search results, command output, and RAG context.

```markdown
# Good Section

Brief overview of what this section documents and why it matters.

More detail can go in subsequent paragraphs, code blocks, or lists.

## Child heading

Details about this child topic.
```

```markdown
# Bad Section

## Child heading

This is invalid — "Bad Section" has no leading paragraph.
```

`lat check` enforces this rule.

## Section IDs

Sections are addressed by file path and heading chain:

- **Full form**: `lat.md/path/to/file#Heading#SubHeading`
- **Short form**: `file#Heading#SubHeading` (when the file stem is unique)

Examples: `lat.md/tests/search#RAG Replay Tests`, `cli#init`, `parser#Wiki Links`.

## Wiki links

Cross-reference other sections or source code with `[[target]]` or `[[target|alias]]`.

### Section links

```markdown
See [[cli#init]] for setup details.
The parser validates [[parser#Wiki Links|wiki link syntax]].
```

### Source code links

Reference functions, classes, constants, and methods in source files:

```markdown
[[src/config.ts#getConfigDir]] — function
[[src/server.ts#App#listen]] — class method
[[lib/utils.py#parse_args]] — Python function
[[src/lib.rs#Greeter#greet]] — Rust impl method
[[src/app.go#Greeter#Greet]] — Go method
[[src/app.h#Greeter]] — C struct
```

`lat check` validates that all targets exist.

## Code refs

Tie source code back to `lat.md/` sections with `@lat:` comments:

```typescript
// @lat: [[cli#init]]
export function init() { ... }
```

```python
# @lat: [[cli#init]]
def init():
...
```

Supported comment styles: `//` (JS/TS/Rust/Go/C) and `#` (Python).

Place one `@lat:` comment per section, at the relevant code — not at the top of the file.

## Test specs

Describe tests as sections in `lat.md/` files. Add frontmatter to require that every leaf section has a matching `@lat:` comment in test code:

```markdown
---
lat:
require-code-mention: true
---
# Tests

Authentication test specifications.

## User login

Verify credential validation and error handling.

### Rejects expired tokens

Tokens past their expiry timestamp are rejected with 401, even if otherwise valid.

### Handles missing password

Login request without a password field returns 400 with a descriptive error.
```

Each test references its spec:

```python
# @lat: [[tests#User login#Rejects expired tokens]]
def test_rejects_expired_tokens():
...
```

Rules:
- Every leaf section under `require-code-mention: true` must be referenced by exactly one `@lat:` comment
- Every section MUST have a description — at least one sentence explaining what the test verifies and why
- `lat check` flags unreferenced specs and dangling code refs

## Frontmatter

Optional YAML frontmatter at the top of `lat.md/` files:

```yaml
---
lat:
require-code-mention: true
---
```

Currently the only supported field is `require-code-mention` for test spec enforcement.

## Validation

Always run `lat check` after editing `lat.md/` files. It validates:
- All wiki links point to existing sections or source code symbols
- All `@lat:` code refs point to existing sections
- Every section has a leading paragraph (≤250 chars)
- All `require-code-mention` leaf sections are referenced in code
24 changes: 24 additions & 0 deletions .codex/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "lat hook claude UserPromptSubmit"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "lat hook claude Stop"
}
]
}
]
}
}
105 changes: 105 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Before starting work

- Run `lat search` to find sections relevant to your task. Read them to understand the design intent before writing code.
- Run `lat expand` on user prompts to expand any `[[refs]]` — this resolves section names to file locations and provides context.

# Post-task checklist (REQUIRED — do not skip)

After EVERY task, before responding to the user:

- [ ] Update `lat.md/` if you added or changed any functionality, architecture, tests, or behavior
- [ ] Run `lat check` — all wiki links and code refs must pass
- [ ] Do not skip these steps. Do not consider your task done until both are complete.

---

# What is lat.md?

This project uses [lat.md](https://www.npmjs.com/package/lat.md) to maintain a structured knowledge graph of its architecture, design decisions, and test specs in the `lat.md/` directory. It is a set of cross-linked markdown files that describe **what** this project does and **why** — the domain concepts, key design decisions, business logic, and test specifications. Use it to ground your work in the actual architecture rather than guessing.

# Commands

```bash
lat locate "Section Name" # find a section by name (exact, fuzzy)
lat refs "file#Section" # find what references a section
lat search "natural language" # semantic search across all sections
lat expand "user prompt text" # expand [[refs]] to resolved locations
lat check # validate all links and code refs
```

Run `lat --help` when in doubt about available commands or options.

If `lat search` fails because no API key is configured, explain to the user that semantic search requires a key provided via `LAT_LLM_KEY` (direct value), `LAT_LLM_KEY_FILE` (path to key file), or `LAT_LLM_KEY_HELPER` (command that prints the key). Supported key prefixes: `sk-...` (OpenAI) or `vck_...` (Vercel). If the user doesn't want to set it up, use `lat locate` for direct lookups instead.

# Syntax primer

- **Section ids**: `lat.md/path/to/file#Heading#SubHeading` — full form uses project-root-relative path (e.g. `lat.md/tests/search#RAG Replay Tests`). Short form uses bare file name when unique (e.g. `search#RAG Replay Tests`, `cli#search#Indexing`).
- **Wiki links**: `[[target]]` or `[[target|alias]]` — cross-references between sections. Can also reference source code: `[[src/foo.ts#myFunction]]`.
- **Source code links**: Wiki links in `lat.md/` files can reference functions, classes, constants, and methods in TypeScript/JavaScript/Python/Rust/Go/C files. Use the full path: `[[src/config.ts#getConfigDir]]`, `[[src/server.ts#App#listen]]` (class method), `[[lib/utils.py#parse_args]]`, `[[src/lib.rs#Greeter#greet]]` (Rust impl method), `[[src/app.go#Greeter#Greet]]` (Go method), `[[src/app.h#Greeter]]` (C struct). `lat check` validates these exist.
- **Code refs**: `// @lat: [[section-id]]` (JS/TS/Rust/Go/C) or `# @lat: [[section-id]]` (Python) — ties source code to concepts

# Test specs

Key tests can be described as sections in `lat.md/` files (e.g. `tests.md`). Add frontmatter to require that every leaf section is referenced by a `// @lat:` or `# @lat:` comment in test code:

```markdown
---
lat:
require-code-mention: true
---
# Tests

Authentication and authorization test specifications.

## User login

Verify credential validation and error handling for the login endpoint.

### Rejects expired tokens
Tokens past their expiry timestamp are rejected with 401, even if otherwise valid.

### Handles missing password
Login request without a password field returns 400 with a descriptive error.
```

Every section MUST have a description — at least one sentence explaining what the test verifies and why. Empty sections with just a heading are not acceptable. (This is a specific case of the general leading paragraph rule below.)

Each test in code should reference its spec with exactly one comment placed next to the relevant test — not at the top of the file:

```python
# @lat: [[tests#User login#Rejects expired tokens]]
def test_rejects_expired_tokens():
...

# @lat: [[tests#User login#Handles missing password]]
def test_handles_missing_password():
...
```

Do not duplicate refs. One `@lat:` comment per spec section, placed at the test that covers it. `lat check` will flag any spec section not covered by a code reference, and any code reference pointing to a nonexistent section.

# Section structure

Every section in `lat.md/` **must** have a leading paragraph — at least one sentence immediately after the heading, before any child headings or other block content. The first paragraph must be ≤250 characters (excluding `[[wiki link]]` content). This paragraph serves as the section's overview and is used in search results, command output, and RAG context — keeping it concise guarantees the section's essence is always captured.

```markdown
# Good Section

Brief overview of what this section documents and why it matters.

More detail can go in subsequent paragraphs, code blocks, or lists.

## Child heading

Details about this child topic.
```

```markdown
# Bad Section

## Child heading

Details about this child topic.
```

The second example is invalid because `Bad Section` has no leading paragraph. `lat check` validates this rule and reports errors for missing or overly long leading paragraphs.
15 changes: 15 additions & 0 deletions lat.md/context-folder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Linked working folder

A conversation can be bound to a working folder (issue #27) — a desktop-only binding that scopes the agent's work. It is sent to the agent per message as a system message, and persisted per session so re-opening a conversation restores its folder.

## Desktop-only persistence

The folder isn't part of hermes-agent's session schema, so it lives in a desktop-owned table in the active profile's `state.db`, keyed by `session_id`.

[[src/main/session-context-folder-store.ts]] holds `desktop_session_context_folders` (mirroring [[src/main/session-continuation-store.ts]]): [[src/main/session-context-folder-store.ts#setSessionContextFolder]] upserts or, for a null folder, deletes the row; [[src/main/session-context-folder-store.ts#getSessionContextFolder]] reads it. The row is dropped with the rest of a session's data in [[src/main/sessions.ts#deleteSessionRows]] so a deleted session leaves no orphan binding.

## Restore and save in the chat

The chat loads the stored folder when resuming a session and saves it whenever it changes, once the conversation has a gateway session id.

In [[src/renderer/src/screens/Chat/Chat.tsx#Chat]] a load effect fetches the folder for `initialSessionId` on mount; a save effect writes `contextFolder` via `setSessionContextFolder` on every change. The save is gated on a "loaded" ref so the initial null can't overwrite a resumed session's stored folder before the load resolves. A brand-new chat saves once its session id resolves after the first message, binding the pre-selected folder to the new session.
3 changes: 3 additions & 0 deletions lat.md/lat.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ This directory defines the high-level concepts, business logic, and architecture

- [[chat-commands]] — how typed slash commands are routed through the gateway's `slash.exec`/`command.dispatch` pipeline instead of being sent as prompt text.
- [[model-context]] — the per-model context-window override that drives the context gauge and the agent's auto-compaction.
- [[model-selection]] — the session-scoped in-chat model override that switches the model (and provider) for one conversation without touching the global default.
- [[web-preview]] — the in-app split-screen webview and the `partition`-based gate that lets only it load remote HTTPS while staying sandboxed.
- [[code-blocks]] — collapsible long code blocks, and why expansion state is keyed on source position to survive react-markdown's streaming remounts.
- [[window-chrome]] — the browser-style title bar where open-conversation tabs sit on top of the window drag region, clickable while empty space still drags.
- [[sidebar-navigation]] — the recent-sessions list under the Chat nav item, capped at five with a "Show more" button that opens the full session list in a modal.
- [[context-folder]] — the per-session linked working folder, persisted in a desktop-owned state.db table so a re-opened conversation restores its folder.
29 changes: 29 additions & 0 deletions lat.md/model-selection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Session model override

The in-chat (bottom) model picker selects a model for the **current conversation only** — it never rewrites `config.yaml`, so the Settings global default is preserved (#688), and carries the full model identity so cross-provider switches route correctly.

The override is held in renderer state on each `<Chat>` run ([[src/renderer/src/screens/Chat/Chat.tsx]]), persisted by session id, and sent with every message; it is cleared when the conversation is cleared/reset and is absent on a fresh chat, so new conversations start on the global default. This is distinct from the persisted [[model-context]] default that non-chat surfaces read.

## Full identity, not just the model name

The override is a `SessionModelOverride` (`{provider, model, baseUrl}`), not a bare model string — because switching across providers must change routing, not only the `model` field.

The picker builds it via [[src/renderer/src/screens/Chat/hooks/useModelConfig.ts#effectiveOverrideBaseUrl]], the same baseUrl rule `selectModel` applies (keep the URL only for `custom`/`ollama-cloud`; clear it for named providers that have a canonical base URL), so the session pick and a persisted save can't drift. It is threaded renderer → preload IPC → main `sendMessage` as `modelOverride`.

## Desktop-only persistence

The selected model/provider is saved in a desktop-owned table keyed by session id, without storing API keys.

[[src/main/session-model-override-store.ts]] holds `desktop_session_model_overrides` with `provider`, `model`, and `base_url` only. [[src/renderer/src/screens/Chat/Chat.tsx#Chat]] restores the saved value for a resumed session, applies it to the local picker with `persist:false`, and saves later changes once a gateway session id exists. Deleting a session removes the row through [[src/main/sessions.ts#deleteSessionRows]].

## Text-only legacy fallback routes via CLI

Text-only legacy turns can use the CLI fallback when a session override changes provider or base URL away from `config.yaml`.

The upstream desktop model applies the session switch on the active gateway session with `/model <model> --provider <provider>`, then attaches media and submits on that same session. Hermes Desktop's dashboard transport follows that path; [[src/main/hermes.ts#shouldForceCliForSessionOverride]] keeps the CLI escape hatch only for text-only legacy fallback, where it can pass `-m <model>` and `--provider` without dropping attachments. Same-provider model swaps stay on the gateway/API path, where the new `model` string is sufficient. Remote (SSH) mode has no local CLI transport, so it remains limited to the model string.

## Attachment turns stay on session transport

Attachment turns must not be forced through the CLI override fallback because the CLI path cannot carry multimodal input.

[[src/main/hermes.ts#sendMessageViaCli]] can inline text-file attachments but ignores images, while the gateway/API path preserves image parts and path refs through [[src/main/hermes.ts#buildUserContent]]. When a session override is active and the user sends attachments, [[src/main/hermes.ts#shouldForceCliForSessionOverride]] leaves the turn eligible for the dashboard/gateway or API transport instead of silently dropping media.
17 changes: 17 additions & 0 deletions lat.md/sidebar-navigation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Sidebar recent sessions

The sidebar has no standalone "Sessions" nav item — the recent-chats list lives directly under the **Chat** nav item (ChatGPT-style), and the full session list opens in a modal via "Show more".

[[src/renderer/src/screens/Layout/Layout.tsx#Layout]] special-cases the `chat` entry of `NAV_ITEMS` to render the Chat button, a collapse chevron (state persisted under `hermes.sidebar.sessionsExpanded`), and [[src/renderer/src/screens/Layout/SidebarRecentSessions.tsx]] beneath it. There is no `sessions` view in the `View` union.

## Inline list and "Show more"

The inline list shows at most `RECENT_SESSIONS_LIMIT` (5) most-recent sessions; a plus-icon "Show more" button appears only when the profile has more than that.

[[src/renderer/src/screens/Layout/SidebarRecentSessions.tsx]] fetches one row over the limit (from the `sessions.json` cache, then a `state.db` sync) so a single query decides whether to render the button — it slices to 5 for display and sets `hasMore` from the raw length. Loading rows use a rotating lucide Loader icon; clicking "Show more" calls `onShowMore`, which opens the full-list modal.

## Full-list modal

"Show more" (and the Cmd/Ctrl+K menu action) open an 80%×80% modal that reuses the existing Sessions screen rather than a separate route.

The modal in [[src/renderer/src/screens/Layout/Layout.tsx#Layout]] renders [[src/renderer/src/screens/Sessions/Sessions.tsx]] inside a `.sessions-modal` over the shared `.models-modal-overlay` backdrop. Resuming a session or starting a new chat from the modal closes it; Esc and a backdrop click also close it. Because the Sessions screen owns its own fetching gated on `visible`, it loads only while the modal is open.
Loading
Loading