CodeCompanion session_id #24

Closed
opened 2026-10-14 14:46:37 +00:00 by rpickler · 3 comments
Owner

@hermes-bot On line 88 of users/modules/neovim/default.nix, we define a session id to persist my chat between hermes and codecompanion. On the line before, this is defined using the current working directory in some way.

Instead, I would like it to be defined from the name of the git repository I'm working in, and the branch I'm on. It's fine if this only done at startup, but if there's a way to make it aware of the current repository changing, please let me know.

Before you implement, let me know how you're going to do it in a plan posted as a response here

@hermes-bot On line 88 of users/modules/neovim/default.nix, we define a session id to persist my chat between hermes and codecompanion. On the line before, this is defined using the current working directory in some way. Instead, I would like it to be defined from the name of the git repository I'm working in, and the branch I'm on. It's fine if this only done at startup, but if there's a way to make it aware of the current repository changing, please let me know. Before you implement, let me know how you're going to do it in a plan posted as a response here
Member

Plan (no code pushed yet, per your request):

How it works today

Line 87-88 computes session_id once from fnamemodify(getcwd(), ":t") inside the adapter factory function. That function runs when the adapter is first resolved, so the id is effectively fixed at startup and only reflects the cwd name.

The mechanism I'll use

I read the current CodeCompanion source (olimorris/codecompanion.nvim). Two facts matter:

  1. Every HTTP request rebuilds the headers via adapter_utils.set_env_vars(adapter, adapter.headers) (http.lua -> write_headers_file).
  2. set_env_vars evaluates function values in the headers table on each call: replaced[k] = replace_var(adapter, v(adapter)) (adapters/utils/init.lua).

So if I make X-Hermes-Session-Id a function instead of a static string, it is re-evaluated on every message you send - which answers your second question: it automatically tracks repo and branch changes, no autocmd needed (the id switches the next time you send a message after a git switch or :cd into another repo).

The change (users/modules/neovim/default.nix, adapter __raw block)

local function hermes_session_id()
  local ok = vim.system({ "git", "rev-parse", "--show-toplevel" }, { text = true }):wait(500)
  if ok.code ~= 0 then
    -- not a git repo: fall back to cwd name, as today
    return "nvim-" .. vim.fn.fnamemodify(vim.fn.getcwd(), ":t")
  end
  local repo = vim.fn.fnamemodify(vim.trim(ok.stdout), ":t")
  local br = vim.system({ "git", "rev-parse", "--abbrev-ref", "HEAD" }, { text = true }):wait(500)
  local branch = (br.code == 0 and vim.trim(br.stdout) or "head")
  -- HTTP header values must be ASCII-safe: sanitize "feature/foo bar" -> "feature-foo-bar"
  branch = branch:gsub("[^%w%._-]", "-")
  return ("nvim-%s-%s"):format(repo, branch)
end

-- in headers:
["X-Hermes-Session-Id"] = hermes_session_id,

Details:

  • vim.system (Neovim 0.10+, no shell quoting issues), ~1-5 ms per message send - negligible next to an LLM request.
  • Detached HEAD (HEAD output) still yields a valid, stable-per-checkout id; non-repo directories keep today's behavior.
  • Result: nvim-nixos-main, nvim-SeesDeployment-RP_S1-5940_van-deployment, etc. Hermes persists a separate conversation per repo+branch, which is exactly the persistence boundary you described.

Semantics worth knowing

  • The id is recomputed per message, so it's always current. If you switch branch mid-chat, the next message lands in the new branch's session (chat buffer history stays where it is; the server-side session key changes). If you'd rather freeze the id per chat buffer, that's a small variant (capture once at ChatCreated) - say the word.
  • CodeCompanion also sends its own generated session_id in the request body; the Hermes gateway keeps using our X-Hermes-Session-Id header, so nothing else changes.
  • Tab-local cwd: getcwd()/git commands resolve from the editor's cwd. If you use tab-local working dirs and want the id per-tab, I can resolve git from vim.fn.getcwd(-1, <tab>) instead - tell me if you use that.

If this looks right, I'll open a PR changing only that __raw block.

Plan (no code pushed yet, per your request): ## How it works today Line 87-88 computes `session_id` once from `fnamemodify(getcwd(), ":t")` inside the adapter factory function. That function runs when the adapter is first resolved, so the id is effectively fixed at startup and only reflects the cwd name. ## The mechanism I'll use I read the current CodeCompanion source (`olimorris/codecompanion.nvim`). Two facts matter: 1. Every HTTP request rebuilds the headers via `adapter_utils.set_env_vars(adapter, adapter.headers)` (`http.lua` -> `write_headers_file`). 2. `set_env_vars` **evaluates function values** in the headers table on each call: `replaced[k] = replace_var(adapter, v(adapter))` (`adapters/utils/init.lua`). So if I make `X-Hermes-Session-Id` a *function* instead of a static string, it is re-evaluated **on every message you send** - which answers your second question: it automatically tracks repo and branch changes, no autocmd needed (the id switches the next time you send a message after a `git switch` or `:cd` into another repo). ## The change (users/modules/neovim/default.nix, adapter `__raw` block) ```lua local function hermes_session_id() local ok = vim.system({ "git", "rev-parse", "--show-toplevel" }, { text = true }):wait(500) if ok.code ~= 0 then -- not a git repo: fall back to cwd name, as today return "nvim-" .. vim.fn.fnamemodify(vim.fn.getcwd(), ":t") end local repo = vim.fn.fnamemodify(vim.trim(ok.stdout), ":t") local br = vim.system({ "git", "rev-parse", "--abbrev-ref", "HEAD" }, { text = true }):wait(500) local branch = (br.code == 0 and vim.trim(br.stdout) or "head") -- HTTP header values must be ASCII-safe: sanitize "feature/foo bar" -> "feature-foo-bar" branch = branch:gsub("[^%w%._-]", "-") return ("nvim-%s-%s"):format(repo, branch) end -- in headers: ["X-Hermes-Session-Id"] = hermes_session_id, ``` Details: - `vim.system` (Neovim 0.10+, no shell quoting issues), ~1-5 ms per message send - negligible next to an LLM request. - Detached HEAD (`HEAD` output) still yields a valid, stable-per-checkout id; non-repo directories keep today's behavior. - Result: `nvim-nixos-main`, `nvim-SeesDeployment-RP_S1-5940_van-deployment`, etc. Hermes persists a separate conversation per repo+branch, which is exactly the persistence boundary you described. ## Semantics worth knowing - The id is recomputed **per message**, so it's always current. If you switch branch mid-chat, the *next* message lands in the new branch's session (chat buffer history stays where it is; the server-side session key changes). If you'd rather freeze the id per chat buffer, that's a small variant (capture once at `ChatCreated`) - say the word. - CodeCompanion also sends its own generated `session_id` in the request *body*; the Hermes gateway keeps using our `X-Hermes-Session-Id` header, so nothing else changes. - Tab-local cwd: `getcwd()`/git commands resolve from the editor's cwd. If you use tab-local working dirs and want the id per-tab, I can resolve git from `vim.fn.getcwd(-1, <tab>)` instead - tell me if you use that. If this looks right, I'll open a PR changing only that `__raw` block.
Author
Owner

Please implement this and push a PR. I'll give it a test when you're done.
While you're in there, could you also refactor codecompanion into a separate plugin like the other neovim plugins are defined? Just to make it cleaner.

Please implement this and push a PR. I'll give it a test when you're done. While you're in there, could you also refactor codecompanion into a separate plugin like the other neovim plugins are defined? Just to make it cleaner.
Member

Implemented in PR #26 — #26 (mergeable).

Both parts are in there:

  1. X-Hermes-Session-Id is now the function from the plan above: nvim-<repo>-<branch>, recomputed on every message (CodeCompanion evaluates function header values per request), with the cwd-name fallback outside git repos, HEAD for detached checkouts, and header-safe sanitisation of branch names (feature/foo bar → feature-foo-bar).
  2. CodeCompanion refactored into users/modules/neovim/plugins/codecompanion.nix, imported from plugins/default.nix like telescope/lsp/cmp. Its three keymaps (<leader>a, <leader>ac, <leader>ae) moved with it; nothing else changed.

To test: rebuild as usual, open nvim in a git repo, and check the id in the gateway request logs (or evaluate the header function directly in :lua). It should read nvim-<repo>-<branch> and flip on the next message after a git switch.

Verified locally: the embedded Lua parses, and all three changed .nix files pass nix-instantiate --parse. Real-world behaviour is yours to confirm per the issue — happy to adjust if you'd rather freeze the id per chat buffer (variant noted in the plan comment).

Implemented in PR #26 — https://git.richardpickler.com/richardpickler.com/nixos/pulls/26 (mergeable). Both parts are in there: 1. `X-Hermes-Session-Id` is now the function from the plan above: `nvim-<repo>-<branch>`, recomputed on every message (CodeCompanion evaluates function header values per request), with the cwd-name fallback outside git repos, `HEAD` for detached checkouts, and header-safe sanitisation of branch names (`feature/foo bar` → `feature-foo-bar`). 2. CodeCompanion refactored into `users/modules/neovim/plugins/codecompanion.nix`, imported from `plugins/default.nix` like telescope/lsp/cmp. Its three keymaps (`<leader>a`, `<leader>ac`, `<leader>ae`) moved with it; nothing else changed. To test: rebuild as usual, open nvim in a git repo, and check the id in the gateway request logs (or evaluate the header function directly in `:lua`). It should read `nvim-<repo>-<branch>` and flip on the next message after a `git switch`. Verified locally: the embedded Lua parses, and all three changed `.nix` files pass `nix-instantiate --parse`. Real-world behaviour is yours to confirm per the issue — happy to adjust if you'd rather freeze the id per chat buffer (variant noted in the plan comment).
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
richardpickler.com/nixos#24
No description provided.