Merge branch 'main' into bb/gui
This commit is contained in:
@@ -1204,9 +1204,25 @@ display:
|
||||
runtime_footer: # Gateway: append a runtime-context footer to final replies
|
||||
enabled: false
|
||||
fields: ["model", "context_pct", "cwd"]
|
||||
file_mutation_verifier: true # Append an advisory footer when write_file/patch calls failed this turn
|
||||
language: en # UI language for static messages (approval prompts, some gateway replies). en | zh | ja | de | es | fr | tr | uk
|
||||
```
|
||||
|
||||
### File-mutation verifier
|
||||
|
||||
When `display.file_mutation_verifier` is `true` (default), Hermes appends a one-line advisory to the assistant's final response whenever a `write_file` or `patch` call failed during the turn and was never superseded by a successful write to the same path. This catches the "batch of parallel patches, half silently fail, model summarises success" class of over-claim without requiring you to manually run `git status` after every edit.
|
||||
|
||||
Example footer:
|
||||
|
||||
```
|
||||
⚠️ File-mutation verifier: 3 file(s) were NOT modified this turn despite any wording above that may suggest otherwise. Run `git status` or `read_file` to confirm.
|
||||
• concepts/automatic-organization.md — [patch] Could not find match for old_string
|
||||
• concepts/lora.md — [patch] Could not find match for old_string
|
||||
• concepts/rag-pipeline.md — [patch] Could not find match for old_string
|
||||
```
|
||||
|
||||
Set `file_mutation_verifier: false` (or `HERMES_FILE_MUTATION_VERIFIER=0`) to suppress the footer. The verifier only fires when real failures are outstanding at turn end — a model that retries a failed patch and succeeds within the same turn will not trigger it for that file.
|
||||
|
||||
### UI language for static messages
|
||||
|
||||
The `display.language` setting translates a small set of static user-facing messages — the CLI approval prompt, a handful of gateway slash-command replies (e.g. restart-drain notices, "approval expired", "goal cleared"). It does **not** translate agent responses, log lines, tool output, error tracebacks, or slash-command descriptions — those stay in English. If you want the agent itself to reply in another language, just tell it in your prompt or system message.
|
||||
@@ -1514,6 +1530,9 @@ browser:
|
||||
dialog_timeout_s: 300 # Safety auto-dismiss under must_respond (seconds)
|
||||
camofox:
|
||||
managed_persistence: false # When true, Camofox sessions persist cookies/logins across restarts
|
||||
user_id: "" # Optional externally managed Camofox userId
|
||||
session_key: "" # Optional session key sent when Hermes creates a tab
|
||||
adopt_existing_tab: false # Reuse an existing tab for this identity before creating one
|
||||
```
|
||||
|
||||
**Dialog policies:**
|
||||
|
||||
@@ -235,6 +235,52 @@ If step 5 logs you out, the Camofox server isn't honoring the stable `userId`. D
|
||||
|
||||
Hermes derives the stable `userId` from the profile-scoped directory `~/.hermes/browser_auth/camofox/` (or the equivalent under `$HERMES_HOME` for non-default profiles). The actual browser profile data lives on the Camofox server side, keyed by that `userId`. To fully reset a persistent profile, clear it on the Camofox server and remove the corresponding Hermes profile's state directory.
|
||||
|
||||
#### Externally managed Camofox sessions
|
||||
|
||||
When another app drives the visible Camofox browser (a desktop assistant, a custom integration, another agent), configure Hermes to operate inside that same identity instead of spawning its own isolated profile.
|
||||
|
||||
Three knobs control the behavior:
|
||||
|
||||
| Setting | Env var | Effect |
|
||||
|---------|---------|--------|
|
||||
| `browser.camofox.user_id` | `CAMOFOX_USER_ID` | Camofox `userId` Hermes uses when creating tabs. Setting this opts the session into "externally managed" mode. |
|
||||
| `browser.camofox.session_key` | `CAMOFOX_SESSION_KEY` | `sessionKey` (a.k.a. `listItemId`) sent on tab creation. Used to match an existing tab during adoption. Defaults to a per-task value if unset. |
|
||||
| `browser.camofox.adopt_existing_tab` | `CAMOFOX_ADOPT_EXISTING_TAB` | When true, Hermes calls `GET /tabs?userId=<user_id>` on first use and reuses an existing tab before creating a new one. |
|
||||
|
||||
Env vars take precedence over `config.yaml`. Either form works:
|
||||
|
||||
```yaml
|
||||
browser:
|
||||
camofox:
|
||||
user_id: shared-camofox
|
||||
session_key: visible-tab
|
||||
adopt_existing_tab: true
|
||||
```
|
||||
|
||||
```bash
|
||||
CAMOFOX_USER_ID=shared-camofox
|
||||
CAMOFOX_SESSION_KEY=visible-tab
|
||||
CAMOFOX_ADOPT_EXISTING_TAB=true
|
||||
```
|
||||
|
||||
**What changes when `user_id` is set:**
|
||||
|
||||
- Hermes skips destructive cleanup at task end (same as `managed_persistence: true`). The other app's tab/cookies/profile survive.
|
||||
- Hermes does **not** call `DELETE /sessions/<user_id>` — that endpoint wipes all user data, so it would nuke the external app's session if it fired.
|
||||
|
||||
**How tab adoption works (when `adopt_existing_tab: true`):**
|
||||
|
||||
1. On the first browser tool call after a process start, Hermes issues `GET /tabs?userId=<user_id>` (5-second timeout).
|
||||
2. If any tab in the response has `listItemId == session_key`, Hermes adopts the most recently created one in that group.
|
||||
3. Otherwise, Hermes adopts the most recently created tab for the user (any `listItemId`).
|
||||
4. If no tabs exist or the request fails, Hermes falls back to creating a new tab on the next operation.
|
||||
|
||||
Adoption only fires until `tab_id` is populated for the session. If the external app closes the adopted tab mid-run, the next browser tool call will surface a Camofox error — Hermes does not re-poll for a fresh tab on every call.
|
||||
|
||||
**Picking `session_key`:** if you want Hermes to reliably attach to a *specific* existing tab, set `session_key` to the `listItemId` the external app used when creating it. If you leave `session_key` unset and only set `user_id`, Hermes generates a per-task `session_key` (`task_<id>`) — Hermes will share cookies and the profile with the external app, but will open its own tab alongside instead of reusing one.
|
||||
|
||||
**Concurrency note:** the external app and Hermes can drive the same Camofox `userId` simultaneously, but Camofox does not coordinate per-tab focus between clients. Coordinate ownership at the application layer (e.g. the external app pauses while Hermes runs).
|
||||
|
||||
#### VNC live view
|
||||
|
||||
When Camofox runs in headed mode (with a visible browser window), it exposes a VNC port in its health check response. Hermes automatically discovers this and includes the VNC URL in navigation responses, so the agent can share a link for you to watch the browser live.
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
---
|
||||
sidebar_position: 16
|
||||
title: "LSP — Semantic Diagnostics"
|
||||
description: "Real language servers (pyright, gopls, rust-analyzer, …) wired into the post-write lint check used by write_file and patch."
|
||||
---
|
||||
|
||||
# Language Server Protocol (LSP)
|
||||
|
||||
Hermes runs full language servers — pyright, gopls, rust-analyzer,
|
||||
typescript-language-server, clangd, and ~20 more — as background
|
||||
subprocesses and feeds their semantic diagnostics into the post-write
|
||||
lint check used by `write_file` and `patch`. When the agent edits a
|
||||
file, it sees exactly the errors that edit introduced — not just
|
||||
syntax errors, but **type errors, undefined names, missing imports,
|
||||
and project-wide semantic issues** the language server detects.
|
||||
|
||||
This is the same architecture top-tier coding agents use. Hermes
|
||||
ships it self-contained: no editor host required, no plugins to
|
||||
install, no separate daemon to manage.
|
||||
|
||||
## When LSP runs
|
||||
|
||||
LSP is gated on **git workspace detection**. When the agent's working
|
||||
directory (or the file being edited) is inside a git worktree, LSP
|
||||
runs against that workspace. When neither is in a git repo, LSP
|
||||
stays dormant — useful for messaging gateways where the cwd is the
|
||||
user's home directory and there's no project to diagnose.
|
||||
|
||||
The check is layered: in-process syntax check first (microseconds),
|
||||
then LSP diagnostics second when syntax is clean. A flaky or missing
|
||||
language server can never break a write — every LSP failure path
|
||||
falls back silently to the syntax-only result.
|
||||
|
||||
Concretely, on every successful `write_file` or `patch`:
|
||||
|
||||
1. Hermes captures a baseline of current diagnostics for the file.
|
||||
2. Performs the write.
|
||||
3. Re-queries the language server, filters out diagnostics that were
|
||||
already in the baseline, and surfaces only the new ones.
|
||||
|
||||
The agent sees output like:
|
||||
|
||||
```
|
||||
{
|
||||
"bytes_written": 42,
|
||||
"dirs_created": false,
|
||||
"lint": {"status": "ok", "output": ""},
|
||||
"lsp_diagnostics": "LSP diagnostics introduced by this edit:\n<diagnostics file=\"/path/to/foo.py\">\nERROR [42:5] Cannot find name 'foo' [reportUndefinedVariable] (Pyright)\nERROR [50:1] Argument of type \"str\" is not assignable to \"int\" [reportArgumentType] (Pyright)\n</diagnostics>"
|
||||
}
|
||||
```
|
||||
|
||||
The `lint` field carries the syntax-check result (microsecond
|
||||
in-process parse via `ast.parse`, `json.loads`, etc.); the
|
||||
`lsp_diagnostics` field carries the semantic diagnostics from the
|
||||
real language server. Two channels, independent signals — the
|
||||
agent sees a syntax-clean file with semantic problems as
|
||||
``lint: ok`` plus a populated ``lsp_diagnostics``.
|
||||
|
||||
## Supported languages
|
||||
|
||||
| Language | Server | Auto-install |
|
||||
|----------|--------|--------------|
|
||||
| Python | `pyright-langserver` | npm |
|
||||
| TypeScript / JavaScript / JSX / TSX | `typescript-language-server` | npm |
|
||||
| Vue | `@vue/language-server` | npm |
|
||||
| Svelte | `svelte-language-server` | npm |
|
||||
| Astro | `@astrojs/language-server` | npm |
|
||||
| Go | `gopls` | `go install` |
|
||||
| Rust | `rust-analyzer` | manual (rustup) |
|
||||
| C / C++ | `clangd` | manual (LLVM) |
|
||||
| Bash / Zsh | `bash-language-server` | npm |
|
||||
| YAML | `yaml-language-server` | npm |
|
||||
| Lua | `lua-language-server` | manual (GitHub releases) |
|
||||
| PHP | `intelephense` | npm |
|
||||
| OCaml | `ocaml-lsp` | manual (opam) |
|
||||
| Dockerfile | `dockerfile-language-server-nodejs` | npm |
|
||||
| Terraform | `terraform-ls` | manual |
|
||||
| Dart | `dart language-server` | manual (dart sdk) |
|
||||
| Haskell | `haskell-language-server` | manual (ghcup) |
|
||||
| Julia | `julia` + LanguageServer.jl | manual |
|
||||
| Clojure | `clojure-lsp` | manual |
|
||||
| Nix | `nixd` | manual |
|
||||
| Zig | `zls` | manual |
|
||||
| Gleam | `gleam lsp` | manual (gleam install) |
|
||||
| Elixir | `elixir-ls` | manual |
|
||||
| Prisma | `prisma language-server` | manual |
|
||||
| Kotlin | `kotlin-language-server` | manual |
|
||||
| Java | `jdtls` | manual |
|
||||
|
||||
For "manual" entries, install the server through whatever toolchain
|
||||
manager makes sense for that language (rustup, ghcup, opam, brew,
|
||||
…). Hermes auto-detects the binary on PATH or in
|
||||
`<HERMES_HOME>/lsp/bin/`.
|
||||
|
||||
## CLI
|
||||
|
||||
```
|
||||
hermes lsp status # service state + per-server install status
|
||||
hermes lsp list # registry, optionally --installed-only
|
||||
hermes lsp install <id> # eagerly install one server
|
||||
hermes lsp install-all # try every server with a known recipe
|
||||
hermes lsp restart # tear down running clients
|
||||
hermes lsp which <id> # print resolved binary path
|
||||
```
|
||||
|
||||
`hermes lsp status` is the best starting point — it shows which
|
||||
languages will get semantic diagnostics today and which need a
|
||||
binary installed.
|
||||
|
||||
## Configuration
|
||||
|
||||
The defaults work for typical setups; nothing to set if the binaries
|
||||
are on PATH.
|
||||
|
||||
```yaml
|
||||
# config.yaml
|
||||
lsp:
|
||||
# Master toggle. Disabling skips the entire subsystem — no servers
|
||||
# spawn, no background event loop runs.
|
||||
enabled: true
|
||||
|
||||
# How long to wait for diagnostics after each write.
|
||||
wait_mode: document # "document" or "full"
|
||||
wait_timeout: 5.0
|
||||
|
||||
# How to handle missing server binaries.
|
||||
# auto — install via npm/pip/go install into <HERMES_HOME>/lsp/bin
|
||||
# manual — only use binaries already on PATH
|
||||
install_strategy: auto
|
||||
|
||||
# Per-server overrides (all optional).
|
||||
servers:
|
||||
pyright:
|
||||
disabled: false
|
||||
command: ["/abs/path/to/pyright-langserver", "--stdio"]
|
||||
env: { PYRIGHT_LOG_LEVEL: "info" }
|
||||
initialization_options:
|
||||
python:
|
||||
analysis:
|
||||
typeCheckingMode: "strict"
|
||||
typescript:
|
||||
disabled: true # skip TS even when its extensions match
|
||||
```
|
||||
|
||||
### Per-server keys
|
||||
|
||||
* `disabled: true` — skip this server entirely even when its
|
||||
extensions match a file.
|
||||
* `command: [bin, ...args]` — pin a custom binary path. Bypasses
|
||||
auto-install.
|
||||
* `env: {KEY: value}` — extra env vars passed to the spawned process.
|
||||
* `initialization_options: {...}` — merged into the LSP
|
||||
`initializationOptions` payload sent in the `initialize`
|
||||
handshake. Server-specific; consult the language server's docs.
|
||||
|
||||
## Installation locations
|
||||
|
||||
When `install_strategy: auto`, Hermes installs binaries into
|
||||
`<HERMES_HOME>/lsp/bin/`. NPM packages land in
|
||||
`<HERMES_HOME>/lsp/node_modules/` with bin symlinks one level up.
|
||||
Go binaries come from `go install` with `GOBIN` pointed at the
|
||||
staging dir.
|
||||
|
||||
Nothing is ever installed to `/usr/local/`, `~/.local/`, or any other
|
||||
shared location — the staging dir is fully Hermes-owned and is
|
||||
removed when you reset the profile.
|
||||
|
||||
## Performance characteristics
|
||||
|
||||
LSP servers are **lazy-spawned** on first use. Editing a Python file
|
||||
in a project that's never seen `.py` traffic spawns pyright; the
|
||||
spawn takes 1-3 seconds for most servers (rust-analyzer can take 10+
|
||||
on a cold project). Subsequent edits in the same workspace re-use
|
||||
the running server.
|
||||
|
||||
The LSP layer adds a few milliseconds to clean writes when no
|
||||
diagnostics are emitted. When diagnostics are emitted, the wait
|
||||
budget is `wait_timeout` seconds — typically the server responds in
|
||||
tens of milliseconds for pyright/tsserver and a few seconds for
|
||||
rust-analyzer mid-indexing.
|
||||
|
||||
Servers are kept alive for the life of the Hermes process. There's
|
||||
no idle-timeout reaper — the cost of restarting the server's index
|
||||
on every write would be far higher than holding the daemon.
|
||||
|
||||
## Disabling
|
||||
|
||||
Set `lsp.enabled: false` in `config.yaml` to disable the entire
|
||||
subsystem. The post-write check falls back to the in-process syntax
|
||||
check (`ast.parse` for Python, `json.loads` for JSON, etc.) which
|
||||
ships unchanged from earlier versions.
|
||||
|
||||
To disable a single language without disabling the whole layer:
|
||||
|
||||
```yaml
|
||||
lsp:
|
||||
servers:
|
||||
rust-analyzer:
|
||||
disabled: true
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**`hermes lsp status` shows a server as "missing"**
|
||||
|
||||
The binary isn't on PATH and isn't in `<HERMES_HOME>/lsp/bin/`. Run
|
||||
`hermes lsp install <server_id>` to attempt an auto-install, or
|
||||
install the binary manually through the language's normal toolchain.
|
||||
|
||||
**Server starts but never returns diagnostics**
|
||||
|
||||
Check `~/.hermes/logs/agent.log` for `[agent.lsp.client]` entries —
|
||||
both stderr from the language server and protocol errors land
|
||||
there. Some servers (rust-analyzer especially) need to finish a
|
||||
project-wide index before they emit per-file diagnostics; the first
|
||||
edit after server start may complete with no diagnostics, with
|
||||
subsequent edits picking them up.
|
||||
|
||||
**Server crashed**
|
||||
|
||||
A crashed server is added to the broken-set and won't be retried for
|
||||
the rest of the session. Run `hermes lsp restart` to clear the set;
|
||||
the next edit re-spawns.
|
||||
|
||||
**Editing a file outside any git repo**
|
||||
|
||||
By design, LSP only runs inside git worktrees. Run `git init` in the
|
||||
project, or accept the in-process syntax-only fallback.
|
||||
@@ -907,6 +907,19 @@ When the agent tries to run a potentially dangerous command, it asks you for app
|
||||
|
||||
Reply "yes"/"y" to approve or "no"/"n" to deny.
|
||||
|
||||
## Interactive Prompts (clarify)
|
||||
|
||||
When the agent calls the `clarify` tool — to ask which approach you prefer, get post-task feedback, or check before a non-trivial decision — Telegram renders the question with **inline keyboard buttons**:
|
||||
|
||||
> ❓ Which framework should I use for the dashboard?
|
||||
>
|
||||
> [1. Next.js] [2. Remix] [3. Astro]
|
||||
> [✏️ Other (type answer)]
|
||||
|
||||
Tap a button to answer, or tap **Other** to type a free-form response (the next message you send becomes the answer). Open-ended `clarify` calls (no preset choices) skip the buttons and just capture your next message.
|
||||
|
||||
Configure the response timeout via `agent.clarify_timeout` in `~/.hermes/config.yaml` (default `600` seconds). If you don't respond within the timeout, the agent unblocks with a sentinel message and adapts rather than hanging.
|
||||
|
||||
## Security
|
||||
|
||||
:::warning
|
||||
|
||||
@@ -68,6 +68,7 @@ Your job description says "route, don't execute." The rules that enforce that:
|
||||
- **For any concrete task, create a Kanban task and assign it.** Every single time.
|
||||
- **Split multi-lane requests before creating cards.** A user prompt can contain several independent workstreams. Extract those lanes first, then create one card per lane instead of bundling unrelated work into a single implementer card.
|
||||
- **Run independent lanes in parallel.** If two cards do not need each other's output, leave them unlinked so the dispatcher can fan them out. Link only true data dependencies.
|
||||
- **Never create dependent work as independent ready cards.** If a card must wait for another card, pass `parents=[...]` in the original `kanban_create` call. Do not create it first and link it later, and do not rely on prose like "wait for T1" inside the body.
|
||||
- **If no specialist fits the available profiles, ask the user which profile to create or which existing profile to use.** Do not invent profile names; the dispatcher will silently drop unknown assignees.
|
||||
- **Decompose, route, and summarize — that's the whole job.**
|
||||
|
||||
@@ -85,7 +86,7 @@ Before creating anything, draft the graph out loud (in your response to the user
|
||||
2. Map each lane to one of the profiles you discovered in Step 0. If a lane doesn't fit any existing profile, ask the user which to use or create.
|
||||
3. Decide whether each lane is independent or gated by another lane.
|
||||
4. Create independent lanes as parallel cards with no parent links.
|
||||
5. Create synthesis/review/integration cards with parent links to the lanes they depend on.
|
||||
5. Create synthesis/review/integration cards with parent links to the lanes they depend on. A child created with unfinished parents starts in `todo`; the dispatcher promotes it to `ready` only after every parent is done.
|
||||
|
||||
Examples of prompts that should fan out (using placeholder profile names — substitute whatever exists on the user's setup):
|
||||
|
||||
@@ -133,6 +134,8 @@ t4 = kanban_create(
|
||||
|
||||
`parents=[...]` gates promotion — children stay in `todo` until every parent reaches `done`, then auto-promote to `ready`. No manual coordination needed; the dispatcher and dependency engine handle it.
|
||||
|
||||
If the task graph has dependencies, create the parent cards first, capture their returned ids, and include those ids in the child card's `parents` list during the child `kanban_create` call. Avoid creating all cards in parallel and linking them afterward; that creates a window where the dispatcher can claim a child before its inputs exist.
|
||||
|
||||
### Step 4 — Complete your own task
|
||||
|
||||
If you were spawned as a task yourself (e.g. a planner profile was assigned `T0: "investigate Postgres migration"`), mark it done with a summary of what you created:
|
||||
|
||||
@@ -21,7 +21,7 @@ Build, test, and debug Hermes Agent RL environments for Atropos training. Covers
|
||||
| License | MIT |
|
||||
| Platforms | linux, macos, windows |
|
||||
| Tags | `atropos`, `rl`, `environments`, `training`, `reinforcement-learning`, `reward-functions` |
|
||||
| Related skills | [`axolotl`](/docs/user-guide/skills/bundled/mlops/mlops-training-axolotl), [`fine-tuning-with-trl`](/docs/user-guide/skills/bundled/mlops/mlops-training-trl-fine-tuning), `lm-evaluation-harness` |
|
||||
| Related skills | [`axolotl`](/docs/user-guide/skills/optional/mlops/mlops-training-axolotl), [`fine-tuning-with-trl`](/docs/user-guide/skills/optional/mlops/mlops-training-trl-fine-tuning), `lm-evaluation-harness` |
|
||||
|
||||
## Reference: full SKILL.md
|
||||
|
||||
|
||||
Reference in New Issue
Block a user