name: agentic-runtime-safety description: > MANDATORY before editing runtime Lua under lua/agentic, except pure tests or docs. Covers multi-tab safety, public API session resolution, scoped Neovim state, buffer/window/keymap isolation, and scheduled callback rules.
Agentic Runtime Safety
Every runtime feature must be multi-tab safe.
Architecture
SessionRegistrymapstab_page_id -> SessionManager.AgentInstanceowns one shared ACP provider subprocess per provider.- Each tabpage owns one ACP session ID,
SessionManager,ChatWidget, status animation, permission manager, file list, and code selection. - Per-tab runtime data belongs on tab-scoped instances or Neovim scoped storage, never module-level mutable variables.
Public API chain
lua/agentic/init.lua is the public surface. Anything reached through this chain
inherits its safety guarantees.
SessionRegistry.get_session_for_tab_page(nil, function(session)
session.widget:show()
end)
Inside the callback:
- Tabpage and session are resolved against the requested tab.
- Callback is wrapped in
pcall; errors are notified, not raised.
Outside the callback:
- Bare-return calls can return
nilwhen no ACP provider is configured. - After
vim.schedule, re-enter via the callback form withself.tab_page_idor validatenvim_tabpage_is_valid(self.tab_page_id). - Never use the
nilform after a schedule boundary when the original tab matters;nilresolves to the then-current tab.
Scoped storage
Use the narrowest valid scope.
| Scope | Accessor | Purpose |
|---|---|---|
| Buffer | vim.b[bufnr] |
Custom variables |
| Buffer | vim.bo[bufnr] |
Built-in options |
| Window | vim.w[winid] |
Custom variables |
| Window | vim.wo[winid] |
Built-in options |
| Tabpage | vim.t[tabpage] |
Custom variables |
vim.b,vim.w, andvim.tare custom variables.vim.boandvim.woare built-in options.- Invalid option names in
vim.boorvim.wothrow. - Write window-local options through
vim.wo[winid][0].opt = valueso they stay local to the window/buffer pair.
Isolation rules
- Module-level constants are fine.
- Module-level namespace IDs are fine: namespaces are global, extmarks are buffer-scoped.
- Module-level mutable per-tab runtime state is forbidden.
- Highlight groups are global and defined in
lua/agentic/theme.lua. - Buffers and windows are tabpage-specific; use
vim.api.nvim_tabpage_*APIs when looking up or validating UI state. - For window lookups, use
vim.api.nvim_tabpage_list_wins(self.tab_page_id). - Prefer buffer-local autocommands.
- Filter global autocommands by tabpage.
- Use
BufHelpers.keymap_setandBufHelpers.keymap_delfor buffer-local keymaps. Do not callvim.keymap.setorvim.keymap.deldirectly with buffer options.
Cleanup path
flowchart LR
A[TabClosed autocmd] --> B["SessionRegistry.destroy_session(tab_page_id)"]
B --> C["SessionManager:destroy"]
Public entries
- Widget lifecycle: open, close, toggle, rotate layout.
- Context attach: selection, files, diagnostics.
- Session operations: new, restore, stop.
- Provider switch: UI history replay only; the new provider receives no prior LLM context.
- Config entry: setup.