[{"content":"Cada agente de código guarda as próprias skills, sessões e configuração numa pasta diferente, num formato diferente. O lazyagents junta tudo num terminal só, no espírito do lazygit.\nFunciona com: Claude Code, Codex, Gemini CLI, OpenCode e Hermes Agent.\nAba Para que serve Skills Instala de um repositório do GitHub, pasta ou zip, e ativa por agente com um symlink, sem copiar nada Sessões Retoma qualquer conversa no próprio CLI e na pasta certa, lê o transcript e busca em todos os agentes Consumo Limites da assinatura (janela de sessão e semanal) e tokens por dia, agente, projeto e modelo Providers e hooks Perfis de endpoint e hooks aplicados na configuração de cada agente, com confirmação e backup antes de escrever Plugins Qualquer executável vira uma aba e um comando novo, em qualquer linguagem Instalar # go install github.com/rogeriojunior31/lazyagents@latest Também há binários para Linux, macOS e Windows na página de releases. O passo a passo completo está em Primeiros passos.\n","externalUrl":null,"permalink":"/docs/lazyagents/visao-geral/","section":"Documentação","summary":"Cada agente de código guarda as próprias skills, sessões e configuração numa pasta diferente, num formato diferente. O lazyagents junta tudo num terminal só, no …","title":"Visão geral · lazyagents","type":"docs"},{"content":"This page takes you from install to a skill enabled in two agents and a session resumed, in about five minutes.\nInstall # With Go 1.26 or newer:\ngo install github.com/rogeriojunior31/lazyagents@latest Or download a binary from Releases (Linux, macOS Intel and Apple Silicon, Windows) and check it against SHA256SUMS:\ntar -xzf lazyagents_\u0026lt;version\u0026gt;_linux_amd64.tar.gz ./lazyagents --version Nothing else is required. Some features use tools you may already have:\nTool Used for git installing skills from GitHub gh, authenticated searching GitHub for skills (S in the Skills tab) sqlite3 reading OpenCode sessions lsof outside Linux, the \u0026ldquo;live\u0026rdquo; badge on Claude Code sessions that are running now (Linux reads /proc) Check what lazyagents sees # lazyagents doctor doctor lists the agents it found, validates every skill and reports broken links, hooks and plugins. An agent counts as installed when its binary is in PATH or its config dir exists. The full matrix of what each agent supports is in the agent support reference.\nOpen the TUI # lazyagents Tabs sit at the top: tab and shift+tab move between them and 1–9 jump to tab N (a click works too), ? lists every key of the current tab and : opens the command palette. q quits. The detail pane sits beside the table on wide terminals and below it on narrow ones; the mouse wheel scrolls and a click selects.\nEnable a skill in two agents # In the Skills tab, press i and type a source: a GitHub repository (user/repo or its URL), a local folder or a .zip. If the source has several skills, pick which ones to install; a single skill installs directly. They are copied into your library, ~/.local/share/lazyagents/skills/. Select a skill, move to an agent column with ←/→ and press space. lazyagents creates a symlink in that agent\u0026rsquo;s skills dir. space again removes it. a enables the skill in every agent at once, x disables it everywhere. Skills you already had inside an agent show up too, as local skills. o adopts one into the library so the other agents can use it. More in the skills guide.\nResume a session # Open the Sessions tab. It lists conversations from Claude Code, Codex, Gemini CLI, OpenCode, Pi and Crush, newest first. Type / to filter, or f to show one agent. Press enter: lazyagents suspends itself and resumes the session in its own CLI, in the right folder. When you exit the agent, you are back in lazyagents. v reads the transcript without resuming. More in the sessions guide.\nSee your usage # The Usage tab shows subscription limits (session and weekly windows, with reset times) and token consumption from your local transcripts. p changes the period and ←/→ the view. Claude Code limits are fetched from the network only when you open the tab or run lazyagents usage or doctor, and cached for 5 minutes. Until you have signed in to an agent or used it once, its limits show as not available yet: that is expected. More in the usage guide.\nUse it from scripts # Every tab has a headless counterpart, and query commands take --json:\nlazyagents list --json # skills and where they are enabled lazyagents enable my-skill --all # enable in every agent lazyagents sessions --here --limit 5 # recent sessions in this folder lazyagents usage daily --since 30d lazyagents help \u0026lt;command\u0026gt; explains each command; the same text is in the CLI reference.\nUninstall # Undo what lazyagents placed in the agents\u0026rsquo; dirs before deleting its data, or their skill links will point to a library that no longer exists (lazyagents doctor lists broken links):\nlazyagents disable \u0026lt;skill\u0026gt; for each enabled skill (lazyagents list shows them). A skill you want to keep in one agent: copy its folder from the library into that agent\u0026rsquo;s skills dir first. lazyagents provider clear and lazyagents hooks disable \u0026lt;name\u0026gt; for each installed hook. Delete the binary, ~/.config/lazyagents and ~/.local/share/lazyagents. The backups dir is inside the latter: keep it if you may want anything back. Next steps # Change the theme, tab order or start tab: Configuration and Themes. Point an agent at another API endpoint: Providers. Run a command on agent events: Hooks. Know what lazyagents changes on disk before you trust it: Safety and data. ","externalUrl":null,"permalink":"/en/docs/lazyagents/getting-started/","section":"Docs","summary":"This page takes you from install to a skill enabled in two agents and a session resumed, in about five minutes.\nInstall # With Go 1.26 or newer:\ngo install …","title":"Getting started · lazyagents","type":"docs"},{"content":"There are two ways in. Most people want a theme for an app they already use — that needs no tool at all. The rest want to add a port or retune the palette, and that is what spn is for.\nUse a theme # Every port is its own repository under github.com/sp-night, and ships plain theme files with no build step. Installing one is always the same three moves:\nPick a flavour. noite is the default, garoa is the flat grey one, jaragua turns the dark towards green. All three are dark. Copy the file for that flavour from the port\u0026rsquo;s themes/ folder to the path the app reads. Activate it with the one line the app expects. Ghostty, for example:\nmkdir -p ~/.config/ghostty/themes curl -fsSL -o ~/.config/ghostty/themes/sp_night_noite \\ https://raw.githubusercontent.com/sp-night/ghostty/main/themes/sp_night_noite echo \u0026#39;theme = sp_night_noite\u0026#39; \u0026gt;\u0026gt; ~/.config/ghostty/config Each port\u0026rsquo;s README, and its page on sp-night.github.io/ports, gives the exact install path and activation line, a preview per flavour, and the table of which key of the app gets which role. Those pages are generated from the same catalogue as the theme files, so they cannot disagree with what you install.\nInstall spn # You only need the engine to add a port, change a mapping, or retune the palette.\ngo install github.com/sp-night/sp-night/cmd/spn@latest spn version Release binaries for Linux and macOS (amd64 and arm64) are on the releases page. The palette and the port catalogue travel inside the binary, so spn works from any directory.\nBuild from source # git clone https://github.com/sp-night/sp-night cd sp-night go build ./cmd/spn go test ./... go run ./cmd/spn check -v --palette palette One dependency, for YAML. The test suite renders the mappings of ports that have already shipped and compares the output against the files users installed, byte for byte.\nLook at the palette # spn palette # every colour, per flavour spn palette --roles # what each role resolves to spn check -v # the contrast audit, pair by pair Next # Before writing or changing a mapping, read the spec. To add an app, follow Adding a port. Every command and flag is in the command reference. ","externalUrl":null,"permalink":"/en/docs/sp-night/getting-started/","section":"Docs","summary":"There are two ways in. Most people want a theme for an app they already use — that needs no tool at all. The rest want to add a port or retune the palette, and …","title":"Getting started · SP Night","type":"docs"},{"content":" Install # Releases ship a .deb and an .rpm (Ubuntu 22.04, Debian 12, Fedora and newer) and a preview installer for Windows (no global shortcuts, desktop notifications, Open terminal or tray presets yet). There is no AppImage for now. Get them from the latest release and check a download with sha256sum -c SHA256SUMS --ignore-missing. To check that it was built by this repository\u0026rsquo;s release workflow: gh attestation verify \u0026lt;file\u0026gt; --repo rogeriojunior31/Vults.\nOn Arch Linux, build the package with makepkg: packaging/aur/vults builds the latest release, packaging/aur/vults-git follows main. Both build from source, so the first install takes a while. They are not on the AUR yet.\ngit clone https://github.com/rogeriojunior31/Vults cd Vults/packaging/aur/vults \u0026amp;\u0026amp; makepkg -si Or build from source:\ngit clone https://github.com/rogeriojunior31/Vults cd Vults npm install npm run tauri dev # run it npm run bundle:linux # or build the .deb and .rpm into target/release/bundle/ Linux needs webkit2gtk-4.1, gtk3, gtk-layer-shell, libayatana-appindicator, openssl, alsa-lib, gst-plugins-good (WebKitGTK plays the island\u0026rsquo;s sounds through GStreamer) and the Vulkan loader (building also needs cmake, the Vulkan headers and glslc). On KDE Plasma, Hyprland, Sway and other compositors with layer-shell, the island sits on the top edge like a panel; on GNOME it is a regular always-on-top window. Settings → General → Start with the desktop opens Vults when you log in.\nConnecting your agents # Open Set up agents… from the tray icon and click Install hooks… next to an agent. You see the file that will change, the exact diff, and a Write the file button; a dated backup is taken first, and hooks from other tools are kept. Remove hooks… takes out only what Vults added.\nAgent File After installing Claude Code ~/.claude/settings.json Nothing: new sessions report to the island Codex ~/.codex/hooks.json Open Codex, type /hooks and trust the Vults hooks (Codex runs a hook only when it is trusted and on); Settings shows how many still wait. A reinstall asks again Antigravity ~/.gemini/config/hooks.json Nothing (it asks in its own terminal) OpenCode ~/.config/opencode/plugins/vults.js, a plugin of ours Restart OpenCode (its cards) Qwen Code ~/.qwen/settings.json Restart Qwen Code (its cards) Copilot CLI ~/.copilot/hooks/vults.json, a file of ours Restart Copilot CLI (its cards) Cursor ~/.cursor/hooks.json Restart Cursor (it asks in its own prompt) Factory Droid ~/.factory/settings.json Restart Droid (it asks in its own prompt) Kimi Code ~/.kimi-code/config.toml Restart Kimi (it asks in its own prompt) With CLAUDE_CONFIG_DIR, CODEX_HOME, QWEN_HOME, COPILOT_HOME, FACTORY_HOME_OVERRIDE, KIMI_CODE_HOME or XDG_CONFIG_HOME set, the file in that folder is used (details). Antigravity\u0026rsquo;s is always ~/.gemini/config/hooks.json: GEMINI_CLI_HOME does not move it.\nOther tools (Pi…) can report too, with a few lines in their own config: see Other agents.\nComing from Vultures AI # Vults was called Vultures AI up to 0.1.5; on its first start it moves the old folders, keyring entry and login entry to the new names, and the .deb and .rpm replace the old package. Then click Update hooks… next to each agent in Set up agents… (Codex asks for its trust again).\nKeys # On Linux, from anywhere: Ctrl+Alt+Space opens the island and hands it the keyboard, Ctrl+Alt+Y / Ctrl+Alt+N allow or deny the card in front, Ctrl+Alt+J / Ctrl+Alt+K put the next or previous session in front, and holding Ctrl+Alt+V talks to Zeca (all of them).\nLanguage # Vults speaks English, Brazilian Portuguese, Spanish and Simplified Chinese. It follows your desktop\u0026rsquo;s language; Settings → General → Language picks another for the island, Settings, the tray and the notifications. What your agents say stays in their own words.\nNext # The island: the flock, folded and open, getting to a session Approving from the island Activity Other agents The Nest: the whole flock, tasks, records and connections Tasks Chatting with Zeca Zeca\u0026rsquo;s memory Connectors The phone ","externalUrl":null,"permalink":"/en/docs/vults/getting-started/","section":"Docs","summary":" Install # Releases ship a .deb and an .rpm (Ubuntu 22.04, Debian 12, Fedora and newer) and a preview installer for Windows (no global shortcuts, desktop …","title":"Getting started · Vults","type":"docs"},{"content":"This document exists to answer one question: when I port the theme to a new platform, which colour goes where? Without a written answer every port becomes a different interpretation of the theme — that is how the Dracula ports drifted apart from each other.\nThe same rules are published at sp-night.github.io/spec. This copy is the one the tool enforces.\nThe three layers # palette/sp_night.json 23 colours per flavour. São Paulo names. ↓ palette/roles.json semantic roles → a palette key ↓ \u0026lt;app\u0026gt;.tmpl asks for a role, never the raw palette Hard rule: a mapping never references a palette colour directly.\n✗ palette = 4={{ .C.marginal }} ✓ palette = 4={{ .R.ansi.blue }} Why: moving syntax.keyword from marginal to temporal has to repaint Neovim, bat, fish and the website at once. A mapping with marginal written into it stays behind and nobody notices.\nspn lint fails on the first form. It walks the parse tree, not the text, so a counter-example inside a comment is not a finding.\nThe exception is the variable lists — waybar.css, gtk.css, hyprland.conf — which publish the raw palette as @define-color / $var because the end user will want to write @sp_sodio in their own stylesheet. Those declare raw_palette: true in their frontmatter. Even there, everything the template itself styles still uses a role.\nThe palette # The four bands below are not headings in this document — they are the groups block of sp_night.json, and they partition the palette: every colour belongs to exactly one band, and the tool refuses a palette where one is missing, doubled or unknown.\nThey are data rather than Go because the website vendors the contract and publishes it. When the partition lived in the tool, the site had to re-type it, and a colour the palette declared but the site\u0026rsquo;s copy did not know about disappeared from the published palette with nothing failing anywhere. accents and vivo are the two bands the audit addresses by name; the rest of a band is its label and its members.\nSurfaces — names neutral about lightness # These names describe function and depth, not a specific hex. That is what lets one mapping serve every flavour.\nkey function reference vao the deepest recess: float, popup, tab bar the free span of the MASP laje the default background — the plane everything sits on a concrete slab concreto panels, cards, cursorline, statusline exposed concrete vidro selection, visual mode, active item glass reflecting the street fiacao borders, dividers, indent guides overhead wiring Text # key use fg_vivo bold default text, ansi.bright_white fg main text, identifiers, variables fg_dim comments, punctuation, secondary text fg_muted line numbers, guides, disabled text Accents # key ANSI reference brasa red the MASP, brake lights sodio — the sodium street lamp taxi yellow traffic light, taxi ibira green Ibirapuera, a green light estaiada — the Estaiada bridge lit up sereno cyan the dew before dawn marginal blue the Marginal, the metro temporal magenta the sky just before the rain Bright ANSI # The six terminal brights (brasa_vivo, taxi_vivo, ibira_vivo, sereno_vivo, marginal_vivo, temporal_vivo) are the same colour with +0.06 Oklch lightness — hue and chroma untouched. Bold text in a terminal uses bright; if bright equals normal, that information disappears. They live in the palette rather than being derived in a template, so they pass the same contrast audit as everything else.\nfg_vivo is that same lift applied to the text ramp, and it exists for the same reason. A target that separates default text from bold default text — Alacritty\u0026rsquo;s colors.primary.bright_foreground, kitty\u0026rsquo;s bold font colour — had nothing above ui.fg to point at, so a mapping had to write fg into both keys and claim a lift that was not there. ui.fg_bright names it, and ansi.bright_white resolves to it. ansi.white stays fg_dim: the ANSI table keeps the two ends of the ramp, and fg remains the terminal\u0026rsquo;s plain foreground.\nFor the text ramp +0.06 is the ceiling, not an inherited constant. fg in noite already sits at Oklch L 0.88; at +0.07 the blue channel clips and chroma starts to fall (0.028 → 0.023). At +0.06 all three flavours land the lift exactly with chroma intact — measured, which is what decided the value.\nsodio is the signature colour: cursor, active border, active workspace, clock, title. If a port needs \u0026ldquo;the theme\u0026rsquo;s colour\u0026rdquo;, that is it — in a terminal or a bar. In an application widget (selection and focus in GTK/Qt/KDE) the accent is ui.accent_alt, blue: an orange system accent makes a whole file manager look like a warning. Tokyo Dark makes the same split, and its primary was always blue.\nsodio and estaiada have no ANSI slot — the 16 slots already have owners. They exist for the interface, not the terminal.\nAssignment rules # Syntax — the logic is: what the code does gets blue-cyan, what the code is gets purple-orange, literal data gets green-orange.\nrole colour keyword, conditional, repeat marginal function, method, operator sereno type, namespace temporal constant, number, boolean sodio string, character ibira parameter, macro, escape estaiada builtin, attribute taxi tag brasa variable, property, field fg comment, punctuation fg_dim Variables stay fg on purpose. A theme where everything is coloured has no hierarchy — the eye needs a rest, and the ordinary identifier is the largest volume of text on screen.\nDiagnostics and git follow the universal convention and must not be reinterpreted: error = red, warning = yellow, info = blue, hint = cyan, ok/added = green, modified = yellow, removed = red.\nA diff has one pattern, in every target: added = ibira, modified and renamed = taxi, removed and conflict = brasa. All the structure — hunk, header, index, untracked — is neutral (fg_dim / fg_muted). A diff that also uses purple, blue and orange becomes a rainbow where only three states matter.\nSurface temperature # Blue-yellow opposition is the strongest axis a theme has, and in a dark theme it does not get resolved in the accents: blue has intrinsically low luminance, so a blue that clears AA on concreto has to be light and desaturated — the opposite of a pole. Measured, repainting the accents moves the axis from 1.03 to 1.35 and costs the character of the whole palette.\nIt gets resolved in the background, which is not text and therefore has no contrast constraint. noite has a blue-violet laje at chroma 0.037; the accents stay warm. The tension between field and accent mass goes from 0.020 to 0.051 without changing a single accent.\nHence the split between the two dark flavours: noite is cold and saturated, garoa is flat grey (chroma 0.005). The garoa does not cool the city down, it washes it out — the distinction is saturation, not temperature.\nThe lesson of the robust themes (KDE) # What makes Breeze Dark and Catppuccin work in any app is not the palette — it is the discipline of the scheme:\nOne single set of foregrounds, repeated literally across View, Window, Button, Tooltip, Complementary and Header. Nothing is derived per section; consistency is the absence of derivation. The Selection section is self-contained. Every foreground in it is chosen to read on the accent, and its BackgroundAlternate is a neighbour of the accent — never the general alternate. That substitution is what produced 1.11:1 in Dolphin. [WM] is dark and calm. A title bar never takes the accent. And one none of them has: the tool parses the .colors file it has just built and fails the build if any fg/bg pair of any section falls below 4.5:1 (internal/audit/kde.go). The real Breeze has pairs at 2–3:1 in Selection; SP Night does not compile with one.\nContrast # spn check measures every text/surface pair of every flavour and fails the build when one falls short. The floor depends on what the surface is:\nsurface floor why laje, vao, concreto 4.5:1 (AA) where you read code for hours vidro 3.0:1 selection: a transient state, you look at shape fg_muted on any 3.0:1, warning ornament, not reading text fiacao on laje/vao 1.5:1, warning a border, not text 74 pairs per flavour. A gate that fails stops the build; a warning is reported.\nfg_dim is the delicate case: comments need full AA on laje. An unreadable comment is the number-one issue of every popular dark theme, and always because nobody measured.\nSeparation between accents # Contrast against the background is not enough. Two accents can clear AA comfortably and still be confused with each other — that is how estaiada and sereno slipped past the first version of this audit: near-identical luminance on laje, separable by hue alone.\nΔE alone is not a usable threshold. In an eight-accent palette covering the hue circle, neighbouring hues land naturally at ΔE 0.07–0.09 in Oklab; demanding more of every pair would be demanding a palette with fewer colours. Measured across the flavours, the genuinely confusable pairs are not the ones with the smallest ΔE — they are the ones with the smallest ΔL.\nThe rule: a perceptually neighbouring pair (ΔE \u0026lt; 0.10) has to separate by lightness (ΔL ≥ 0.04). That is what keeps the two distinguishable in greyscale — and, for the same reason, for someone with a colour vision deficiency.\nColour vision deficiency # spn check -v also simulates protanopia, deuteranopia and tritanopia (Viénot 1999 matrices) and reports how many accent pairs sit too close for each.\nThis is a diagnostic, not a gate, for a concrete reason: separating eight accents under colour blindness, keeping the palette\u0026rsquo;s smoky character, and clearing AA on a dark background is an overdetermined system. A search over hue, chroma and lightness finds solutions with much better separation, but all of them get there by moving the theme somewhere else — yellow drops to olive, red becomes terracotta, the cyans hit the edge of the gamut and turn neon. No popular theme solves all three; Catppuccin, Tokyo Night and Dracula do not either.\nThe number exists so the choice is deliberate. If the priority ever changes, the tool already measures it.\nAbout the ΔL rule # It has a blind spot worth knowing: it only examines pairs of neighbouring hue in normal vision. Under deuteranopia the pairs that collapse are the distant ones — red and green land in the same place. One does not cover the other, which is why both measurements coexist.\nIt is a warning, not a failure: these are the theme\u0026rsquo;s identity colours, and the decision to repaint them is the maintainer\u0026rsquo;s. The accents\u0026rsquo; lightness ladder keeps the list empty today.\nRun spn check before touching a colour.\nAdding a colour # Add it to colors in every flavour, and to meaning. Add it to the band it belongs to in groups. spn check, then go test ./.... Step 2 is not optional bookkeeping: a colour outside every band fails validation, because anything that walks the palette walks the bands. If it is text or an accent, decide the floor it has to clear — internal/audit names the keys it measures — and if it is a _vivo, it lifts its base by 0.06 Oklch lightness and the suite checks that it does.\nAdding a flavour # Add the block to palette/sp_night.json → flavors. spn check. That is all — order comes from the JSON itself, so there is no second list in Go to keep in step.\nA new flavour has to distinguish itself from the existing ones by hue, not only by lightness. garoa and noite are the example: the first version of garoa was noite with the blacks raised, and of ΔE 0.034 between the surfaces, 0.033 was ΔL — the same colour, lighter. Pulling the grey towards blue (Oklab B from −0.005 to −0.017) raised Δchroma 7× and dropped ΔL: they became two atmospheres rather than two brightnesses.\nNo mapping changes. If a mapping has to change to accommodate a new flavour, that is a sign it is using the raw palette where it should use a role.\nAdding a target # List it in registry/ports.yml. spn new \u0026lt;slug\u0026gt;. Write the mapping using .R. The frontmatter accepts an empty matrix to render once — for an app that reads a single fixed config file. Formats that need a light block alongside the dark one mirror the dark side, since the theme is dark-only; .All reaches the other flavours: {{ (index .All \u0026quot;garoa\u0026quot;).R.ui.bg }}.\nFor \u0026ldquo;text that reads on this accent\u0026rdquo;, use readable, which picks between two options by measured contrast instead of guessing:\n\u0026#34;mOnPrimary\u0026#34;: \u0026#34;{{ readable .C.vao .C.fg .R.ui.accent }}\u0026#34; Helpers available in a mapping: nohash, rgb, rgbn, rgba, hexa, argb, sgrfg, sgrbg, mix, lighten, darken, contrast, readable, kebab, pad, repeat, tojson, upper, lower, r, g, b.\n","externalUrl":null,"permalink":"/en/docs/sp-night/spec/","section":"Docs","summary":"This document exists to answer one question: when I port the theme to a new platform, which colour goes where? Without a written answer every port becomes a …","title":"The SP Night spec · SP Night","type":"docs"},{"content":"A port is a mapping, not a copy of colours: it decides which key of an app\u0026rsquo;s config gets which role, and the tool resolves the rest. Read SPEC.md first — in particular the rule that a mapping never references the raw palette.\nThe canonical example is sp-night/ghostty.\nNon-negotiables # No hex is chosen by hand. Not in a theme file, not in a README, not in a preview. If something needs to change, the mapping or the catalogue changes and everything is regenerated. Conventional Commits, in English, lower case, no trailing period: feat(themes):, docs(readme):, feat(ports):, ci:, test:, chore:. Never commit to main directly. Branch → pull request → merge. Public docs in English. Colour and flavour names stay Portuguese — they are the identity. MIT in every repository. 1 — List the target in the catalogue # registry/ports.yml is what the README, the preview and the generated header all read from. Nothing can be generated for a port that is not listed.\nStart from a printed entry rather than a blank page:\nspn entry \u0026lt;slug\u0026gt; \u0026gt;\u0026gt; registry/ports.yml That writes every required field, a mapping table to extend, and a complete preview — the fake session, span by span, in the shape the shipped previews share. It validates as printed, so the entry can be checked in while the TODOs are still being replaced. The preview is a starting point and nothing more: rewrite the session once the port has a voice of its own, the way eza\u0026rsquo;s did.\nThe fields it leaves you:\n- slug: kitty # also the repository name under the org name: kitty group: terminal blurb: One line on what the port covers. homepage: https://sw.kovidgoyal.net/kitty/ repo: https://github.com/sp-night/kitty # derived from slug, and checked install: ~/.config/kitty/sp_night_{flavor}.conf activate: include sp_night_{flavor}.conf template: kitty.conf.tmpl mapping: - key: \u0026#34;`background`\u0026#34; role: \u0026#34;`ui.bg`\u0026#34; meaning: \u0026#34;*laje* under the main text\u0026#34; preview: title: kitty — sp_night_{flavor} frame: terminal # terminal | editor | app | pane swatches: {label: palette 0–15, roles: [ansi.black, ...]} body: - - {t: \u0026#34;~/sp-night \u0026#34;, r: ui.accent} - {t: \u0026#34;❯ \u0026#34;, r: ui.accent_alt} - {t: \u0026#34;kitty +kitten themes --dump-theme\u0026#34;, r: ui.fg} Then check it:\nspn registry --registry registry/ports.yml --copy registry/copy.yml mapping is not decoration — it is the table a reader checks before trusting a port, and validation refuses an entry without one.\nframe is the chrome the preview is drawn in, and it should be the kind of window the app actually lives in. The body is only the session; the frame draws the rest, so a port never has to fake a gutter or a statusline span by span.\nframe for what the renderer adds takes terminal (default) a terminal emulator titlebar with three dots and the centred title — editor an editor a bufferline with title as the open tab, numbered lines, a statusline bar, cursor_line app a full-screen TUI title as the header, a bottom bar bar pane a CLI that runs in someone else\u0026rsquo;s terminal nothing but the border — bar is {left: [spans], right: [spans]} in the same span shape as the body; cursor_line is the 1-based body line the editor highlights. Each frame fits a fixed number of body lines above the colour strip (13 for a pane, 11 for the rest), and spn registry refuses a session that runs past it.\n2 — Create the repository # gh repo create sp-night/\u0026lt;slug\u0026gt; --public --license mit \\ --description \u0026#34;🌃 SP Night for \u0026lt;App\u0026gt; — a dark colour scheme with São Paulo as its reference\u0026#34; The initial commit carries only LICENSE. Clone it next to the others, then scaffold:\nspn new \u0026lt;slug\u0026gt; That writes the mapping stub with the canonical header already wired to the catalogue, the six-line workflow, the Renovate config, and the editor settings.\n3 — Write the mapping # This is the only file a human writes. Ask for a role, never a colour:\n✗ background {{ .C.laje }} ✓ background {{ .R.ui.bg }} spn palette --roles lists every role and what it resolves to in each flavour. What each one means is in SPEC.md.\nThe frontmatter declares the contract:\n--- spn: version: \u0026#34;1.2.0\u0026#34; # the exact engine this was generated with matrix: [flavor] # one file per flavour filename: \u0026#34;themes/sp_night_{{ .Flavor }}.conf\u0026#34; # where it goes --- Leave matrix out for an app that reads one fixed config file — eza is the case: its mapping renders once, and each flavour still ships as a file in themes/.\nversion is an exact version, not a range, and nobody edits it by hand. spn new writes the version of the binary that scaffolded the port, and from then on the engine\u0026rsquo;s release fan-out bumps it in the same pull request that regenerates the theme files.\nThat pairing is the point. The committed files and the version that produced them are one fact, so a port\u0026rsquo;s CI installs the pinned engine and checks the files against it. A range — ^1.0 was the original — makes the CI resolve to whatever is newest, so a port\u0026rsquo;s pull request goes from green to red because the engine released, with nothing in the port having changed. It also made the Renovate rule that was supposed to propagate engine bumps inert: every 1.x satisfies ^1.0, so there was never anything to bump.\nThen generate everything else:\nspn gen \u0026amp;\u0026amp; spn readme \u0026amp;\u0026amp; spn preview spn lint \u0026amp;\u0026amp; spn gen --check 4 — Commit history # On branch feat/\u0026lt;slug\u0026gt;-port, mirroring ghostty:\nfeat(themes): add the noite, garoa and jaragua flavours docs(readme): add install guide and palette-generated previews ci: check the mapping and the generated files Then gh pr create, and merge through the pull request.\nBefore pushing, confirm the generated header points at this port\u0026rsquo;s repository and carries the install path and the activation line. If it does not, the catalogue entry is wrong — do not edit a generated file.\n5 — The website, which is not a step # There is nothing to do here, and nothing to edit. The website vendors this catalogue rather than keeping its own list, so merging step 1 to main is what publishes the port: the push triggers sync-ports.yml, whose site job copies ports.yml and copy.yml across, redraws the previews, regenerates the site\u0026rsquo;s derived assets, runs its suite, and opens a pull request. Merging that deploys /ports/\u0026lt;slug\u0026gt; — a page built from the entry, with the install guide, the key-to-role table and a screenshot per flavour.\nListing a port is publishing it. If you find yourself editing src/data/ by hand, the change will disappear at the next sync without leaving a trace.\nChecklist # Listed in registry/ports.yml (start with spn entry \u0026lt;slug\u0026gt;), spn registry clean spn lint clean — the mapping asks for roles, never colours spn gen --check, spn readme --check, spn preview --check all clean Three flavours present (noite, garoa, jaragua) Generated header points at this port\u0026rsquo;s repository .github/workflows/theme.yml calls the reusable workflow Branch feat/\u0026lt;slug\u0026gt;-port, Conventional Commits, merged via pull request Catalogue merged to main, and the website\u0026rsquo;s sync pull request merged When a port needs something the roles do not offer # That is a real finding, not a reason to reach for the palette. A missing role means the semantic layer has a hole — open an issue on this repository describing the key you could not map. Adding a role repaints every port that wants it; writing a colour into one mapping helps exactly one port and hides the gap.\n","externalUrl":null,"permalink":"/en/docs/sp-night/port-creation/","section":"Docs","summary":"A port is a mapping, not a copy of colours: it decides which key of an app\u0026amp;rsquo;s config gets which role, and the tool resolves the rest. Read SPEC.md first — …","title":"Adding a port · SP Night","type":"docs"},{"content":"A skill is a folder with a SKILL.md: instructions an agent loads when the task matches its description. Each agent reads skills from its own directory, so the same skill usually ends up copied in several places. lazyagents keeps one copy in a central library and makes it visible to each agent through a symlink, so you install and update a skill once and choose per agent where it is enabled. The Skills tab shows this as a skill × agent matrix, and the same operations are available from the CLI.\nConcepts # Library. The folder where lazyagents keeps the skills it manages: ~/.local/share/lazyagents/skills/ by default, or libraryDir in config.yaml (see Configuration). Each skill is one subfolder, named after the skill.\nActivation is a symlink. Enabling a skill in an agent creates a symlink \u0026lt;agent skills dir\u0026gt;/\u0026lt;skill\u0026gt; → \u0026lt;library\u0026gt;/\u0026lt;skill\u0026gt;. Disabling removes that symlink and nothing else. Since the agent reads the library folder through the link, editing or updating a skill applies to every agent at once. Where each agent reads its skills is in the agent reference.\nLocal skills. A skill that lives in an agent\u0026rsquo;s directory as a real folder, or as a symlink created by another tool, is local: lazyagents shows it, but never deletes or disables it. To manage it, adopt it into the library (see Adopt local skills).\nShared directories. ~/.agents/skills is read by Codex, Gemini CLI, OpenCode, Pi and Crush. Enabling a skill for one agent only shows it to that agent: lazyagents links it in the agent\u0026rsquo;s own directory (~/.codex/skills, ~/.gemini/skills…). Only when the skill is enabled for every installed agent that reads ~/.agents/skills does it become a single link there; disabling it for one of them moves it back to the own directory of each agent that keeps it. The exception is ~/.claude/skills, Claude Code\u0026rsquo;s own directory, which OpenCode and Crush also read: a skill enabled for Claude Code shows up in them too.\nMatrix markers. Each cell of the matrix shows the skill\u0026rsquo;s state in one agent:\nMarker Meaning ● enabled by lazyagents (a symlink in this agent\u0026rsquo;s own directory) ◆ visible through another agent\u0026rsquo;s directory (OpenCode reading ~/.claude/skills); it cannot be toggled for this agent alone ▪ local: real content lazyagents does not manage ○ not visible to this agent Profiles. A profile is a named snapshot of the matrix: which library skills are enabled in which agents. Applying one brings the matrix back to that state, for example to switch between a \u0026ldquo;work\u0026rdquo; and a \u0026ldquo;personal\u0026rdquo; set.\nBackups. Before a skill folder is removed, replaced, restored over, adopted or migrated, lazyagents writes a .tar.gz of it to ~/.local/share/lazyagents/backups/. The 20 newest backups of each skill are kept.\nIn the TUI # The matrix has one row per skill and one column per installed agent that has a skills directory. ←/→ pick the agent column; the selected skill\u0026rsquo;s detail (description, source, per-agent state and lint warnings) shows beside or below the table. The complete list of keys is in the key reference, and ? shows it inside the TUI.\nBrowse and read # Move through the list and type / to filter by name. enter opens the SKILL.md rendered as Markdown; inside the reader, e opens it in your editor and esc goes back. r rescans the library and the agents\u0026rsquo; directories. The tab title counts the local skills that are not in the library yet.\nEnable and disable # space toggles the selected skill in the agent under the cursor (←/→ picks the column). a enables the skill in every installed agent that has a skills directory; x disables it everywhere lazyagents manages it. A cell marked ▪ or ◆ cannot be toggled from its column: the toast explains why (adopt a local skill with o, or use x for a skill that reaches the agent through another agent\u0026rsquo;s directory). Only library skills can be enabled.\nInstall from GitHub, a folder or a zip # Press i and type a source:\nowner/repo or any git URL (https://…, git@…, ….git): cloned with git clone --depth 1 into a temporary folder, so git must be in PATH. A path to a .zip file. A local folder (~ is expanded). lazyagents looks for every folder with a SKILL.md, at any depth. If the source root has one, the source itself is the skill. .git, node_modules and vendor are skipped; when two folders have the same name, the one outside hidden directories and closest to the root wins. A picker then lists what was found, all skills checked: space toggles one, a toggles all, enter installs, esc cancels.\nInstalling only copies into the library and records the source in .origin.json inside the skill folder. Nothing is enabled until you enable it. A name that already exists in the library is not overwritten: that item fails and the rest of the install goes on.\nMarketplaces. When the source has a Claude Code marketplace manifest (.claude-plugin/marketplace.json) that declares skills, lazyagents reads the manifest instead of scanning, and the picker prefixes each skill with the name of its plugin. Plugins whose source is another repository or a package are not fetched; they show up as notes in the picker.\nHooks in the same source. If the source ships hooks/hooks.json (the Claude Code plugin format), its hooks appear in the same picker, unchecked, because a hook runs a third-party command on every agent event. See the hooks guide.\nSearch GitHub # S searches GitHub code for SKILL.md files that contain your term and lists the repositories found, with their descriptions. enter on a result starts the install flow above for that repository. The search goes through gh api, so it needs the GitHub CLI installed and logged in. It only runs when you ask for it.\nCreate and edit # n asks for a name in kebab-case (lowercase letters, digits and hyphens, at most 64 characters) and creates \u0026lt;library\u0026gt;/\u0026lt;name\u0026gt;/SKILL.md from a template with name and description filled in. e opens the selected skill\u0026rsquo;s SKILL.md in $EDITOR (vi if unset), suspending the TUI until the editor exits.\nAdopt local skills # o adopts the selected local skill: its folder is copied into the library, the original is backed up, and the original is replaced by a symlink to the library copy. The agent keeps seeing the skill, and the other agents can now enable it. A adopts every local skill at once, after a confirmation that lists them.\nA skill that is a symlink created by another tool cannot be adopted: manage it with the tool that created it. A name that already exists in the library is refused too.\nUpdate from source # Skills installed from a git source can be updated; skills from a zip, a folder or created by hand cannot.\nu updates the selected skill after a confirmation: the repository is cloned again, the current folder is backed up and its content is replaced in place, so the agents\u0026rsquo; symlinks keep working. U checks every git-sourced skill, cloning each repository once, and marks the matrix: ↑ update available, ~ edited locally. If updates are available, it asks to apply them all. Skills edited locally are skipped, so your changes are not overwritten; u updates one of them anyway, with a backup first. lazyagents detects local edits by comparing the folder\u0026rsquo;s content with the hash it saved at install or update time.\nProfiles # p opens the profile list:\ns saves the current matrix as a profile. Only the enablements lazyagents controls are recorded: local skills and skills seen through another agent\u0026rsquo;s directory are left out. enter on a profile shows what applying it would change (+ enable, − disable, per skill and agent) and asks for confirmation. d deletes the selected profile after a confirmation. Skills stay enabled as they are. Applying a profile enables each listed library skill in the agents the profile names and disables the other library skills where lazyagents enabled them. Local skills are never touched. A profile that names a skill no longer in the library is refused.\nProfiles are stored in ~/.local/share/lazyagents/profiles.json.\nBackups and restore # b lists the backups of the selected skill, newest first. enter restores the selected one after a confirmation. The current folder, if any, is backed up before being replaced, so a restore can itself be undone. The backup is unpacked to a temporary folder and checked before the current folder is swapped out, so a corrupt backup leaves the skill as it was.\nSKILL.md lint # Every skill is checked against the Agent Skills format, and problems show in the detail panel and in lazyagents doctor:\nname is present, in kebab-case and equal to the folder name; description is not empty and has at most 1024 characters; there are instructions after the frontmatter. A SKILL.md whose frontmatter cannot be parsed is flagged as invalid instead.\nFrom the CLI # The CLI covers the everyday operations. Every command is described in the CLI reference, and lazyagents help \u0026lt;command\u0026gt; prints the same text.\nlazyagents install anthropics/skills # install every skill in a repository lazyagents install ./my-skills.zip --hooks # also install the hooks it ships lazyagents install anthropics/skills pdf docx # only these skills from the repository lazyagents install DietrichGebert/ponytail --all # install and enable in every installed agent lazyagents list # skills, agent count, library or local lazyagents enable pdf --agent claude-code # enable in one agent lazyagents disable pdf --agent codex lazyagents enable pdf # every installed agent, like a in the TUI lazyagents adopt my-notes --agent claude-code # move a local skill into the library lazyagents remove pdf # remove from the library, with backup lazyagents list --json | jq \u0026#39;.[] | select(.valid | not) | .dir\u0026#39; lazyagents skills \u0026lt;command\u0026gt; is an alias for the same commands. Search, create, update, profiles and restore are only in the TUI for now.\nConfiguration # # ~/.config/lazyagents/config.yaml libraryDir: ~/.agents/skills libraryDir moves the library. Setting it by hand only changes where lazyagents looks; the existing skills and the symlinks that point to them stay where they were. To move them too, use lazyagents migrate-library \u0026lt;dir\u0026gt;: it backs up and copies every skill, repoints the agents\u0026rsquo; symlinks, saves libraryDir, and only then removes the old copies. It refuses when a skill name already exists in the destination (symlinks included) or when one directory contains the other; resolve the conflict and run it again.\nPointing the library at ~/.agents/skills makes every skill visible to Codex, Gemini CLI, OpenCode, Pi and Crush without any symlink, since they read that directory; the price is that they can no longer be enabled per agent. The other options are in the configuration guide.\nFiles # Path Written when ~/.local/share/lazyagents/skills/\u0026lt;name\u0026gt;/ install, create, adopt, update, restore, remove ~/.local/share/lazyagents/skills/\u0026lt;name\u0026gt;/.origin.json install and update: source type, URL or path, subfolder, content hash each agent\u0026rsquo;s skills directory enable and disable (symlinks only), adopt (the original becomes a symlink), remove (links into the library) ~/.local/share/lazyagents/backups/\u0026lt;name\u0026gt;.\u0026lt;timestamp\u0026gt;.tar.gz remove, update, adopt, restore, migrate-library (mode 0600, 20 per skill) ~/.local/share/lazyagents/profiles.json saving a profile ~/.config/lazyagents/config.yaml migrate-library (libraryDir) Git clones and extracted zips go to temporary folders that are removed after the install.\nLimits # Symlinks inside a skill folder are not copied on install, adopt or migrate, so a skill cannot pull in files from outside its folder. Zip entries that are symlinks or escape the archive root are skipped, and a zip over the limits fails the install before anything reaches the library: an entry over 64 MB, more than 512 MB uncompressed in total, or more than 10,000 entries. Enabling uses symlinks. On Windows, creating them may need Developer Mode or administrator rights. Hermes Agent\u0026rsquo;s per-project skill dirs (\u0026lt;project\u0026gt;/.hermes/skills, after hermes skills trust) are not read; its external_dirs are. Skills bundled inside Claude Code plugins are not managed. The TUI does not watch the filesystem: press r after changing skill folders by hand. ","externalUrl":null,"permalink":"/en/docs/lazyagents/guide/skills/","section":"Docs","summary":"A skill is a folder with a SKILL.md: instructions an agent loads when the task matches its description. Each agent reads skills from its own directory, so the …","title":"Skills · lazyagents","type":"docs"},{"content":"The island hangs from the top edge of your screen. On KDE Plasma, Hyprland, Sway and other compositors with layer-shell it sits above every window like a panel; on GNOME it is a regular always-on-top window. On X11 it shows on every workspace. It takes the keyboard only while you type in the chat, or after Ctrl+Alt+Space (below).\nWith more than one screen, the desktop picks one (usually the focused screen at start). To pin the island to a screen, choose it in Settings → General → Screen. If that screen goes away, the island moves to another one and comes back when it returns.\nOn Windows (a preview build) there are no global shortcuts, desktop notifications, Open terminal or tray presets yet.\nThe flock # Each agent session is one bird, a vulture. A project is a species: all its sessions share it, and it keeps it every time the app starts. A small band at the base of each neck shows the agent: orange for Claude Code, teal for Codex, pink for OpenCode, indigo for Qwen Code, blue for Copilot CLI, and violet for any other tool (Antigravity, Pi, Cursor…).\nZeca is not a session: he sits on the wire when nothing runs, and he is the one you talk to in the chat. The session in front (the one that needs you, the one you clicked, or else the first to arrive) takes his spot. Every other session is a vult.\nThe birds act out what their sessions do: they read, peck the wire while editing, take off for the web, and spread their wings when a permission waits (with a mark over the head; at dawn the resting birds open theirs to the sun, with no mark). New sessions fly in (one a task of yours started takes off from Zeca\u0026rsquo;s place on the pill, or from the bird in front when one sits there); ended ones fly off. Reduced motion keeps them on their perches. Settings → Flock \u0026amp; Zeca picks the species pool, Zeca\u0026rsquo;s species and the rare visitors. Animations lists every clip.\nZeca\u0026rsquo;s seasonal looks # Zeca dresses for the season: a witch hat in October, a Santa hat in December, a party hat at New Year, bunny ears at Easter, by your computer\u0026rsquo;s date. Settings → Flock \u0026amp; Zeca → Look (with Zeca drawn beside it: in the look you pick, in his own feathers on Auto) keeps one look all year, or none, and adds sunglasses and fourteen outfits with no season. Right-click Zeca on the island to pick one in place: he wears whatever the pointer rests on, a click keeps it, Escape leaves him as he was. Only Zeca wears them.\nWithout Zeca # Zeca is optional. Settings → Flock \u0026amp; Zeca → Zeca turns him off; the flock keeps working: sessions on the wire, permission and question cards, the Allow / Deny shortcuts, notifications, connectors and the presets. What goes with him:\nThe chat, its tab, files dropped on the island, the Nest\u0026rsquo;s Chat, the tray\u0026rsquo;s Chat…, the microphone and the talk shortcut. The usage meters stay: they read your agents\u0026rsquo; subscriptions. His hello at start-up. His place on the wire: the session in front sits there as its own bird, and with nobody running the pill reads Nothing running. Turn him back on in the same place; no restart either way.\nCompact, open and hidden # Compact (the size switch\u0026rsquo;s Mini), the island is a small pill with no text, only the flock: Zeca on the left with the session in front, up to eight vults spread along the wire, and under it a hairline per plan showing its usage, in the usage meters\u0026rsquo; colors (amber from 70%, red from 90%). For sessions, colors mean the same everywhere: amber for a permission, cyan for a question, green when done, red when it failed. A vult in one of those states wears a badge of that color, and of its own shape, so no state rests on color alone: a triangle for a permission, a diamond for a question, a circle when done, a square when it failed. While someone needs you the pill\u0026rsquo;s edge pulses amber; when a session failed (and nobody needs you) it stays red; a cyan dot at its corner says a connector has news. Rest the pointer on a vult and it looks at you (one in the air comes down); click it to open the island on its session, right-click it for its quick actions. Connector news waits in the open island.\nClick anywhere else on the pill to open the island. It folds back 15 seconds after the pointer leaves (a thin line at the bottom counts down; coming back cancels it). The size switch at the top right changes how much you see, keeping what you were looking at: Mini folds it to the pill at once, Quick is the island, Room opens the Nest on what the island shows (the flock, the chat, your connections or this week; Settings from the gear), and the full-screen button beside it opens it on the whole screen. While a card waits, it takes the flock\u0026rsquo;s place. Settings → General → Fold the island sets the wait.\nWith Settings → General → Open on hover on, resting the pointer on the pill opens it without a click. Opened that way, it folds as soon as the pointer leaves, unless you clicked in it. Off by default, and not in Panel.\nTwo things keep it open until you are done:\nA permission card. It opens the island by itself and stays until you answer. Nothing else opens it by itself: a session that finishes, fails or asks something plays its sound and gets its badge. The chat. The island never folds while you type. With nobody on the wire for a minute, the island hides. Move the pointer to the middle of the top edge of the screen to bring it back.\nPresence: how much it shows # Settings → General → Presence, or the tray\u0026rsquo;s menu (Linux), picks one of four presets, at once.\nPreset At rest A card that needs you Island (the default) The flock at the top of the screen Opens the island, with its sound Panel Nothing at the top; Zeca in the tray Opens the island by the panel, with its sound Quiet Nothing, and no sound for finished or failed sessions or connector news. The top edge still brings the island back Opens the island at the top, with its sound Paused A pill that says Paused None: the agent asks in its terminal, as if the app were closed Nothing hides or quiets a card an agent waits on: not a preset (except Paused), not muting or hiding a project, not Do not disturb. The card opens the island and waits for your click.\nPaused is for no interruptions at all: permissions and questions, including cards already waiting, go to the agent\u0026rsquo;s terminal, and your Always rules answer nothing. Connectors stop checking, and no desktop notifications show. Pick another preset to come back.\nIn the panel # Nothing is drawn at the top of the screen. Zeca sits in the panel\u0026rsquo;s tray and shows the flock\u0026rsquo;s state: perched, working, wings open with an amber mark (something waits for you), green sparkles (done), a red mark (failed).\nClick him and the island opens in the panel\u0026rsquo;s corner. It folds once the pointer leaves, with Mini or Escape, and when you open Settings. Right-click him for the menu: Chat… (with Zeca on), Nest… (with Zeca off too), Set up agents…, the four presets (Linux), Quit. A card opens the island by the panel, and Zeca calls for attention in the tray until you answer. Allow and Deny are always clicks on the card. Connector news plays its sound but does not open the island: click Zeca to read it. On KDE Plasma the island opens beside the panel that holds the tray; elsewhere at the bottom right. The tray needs a panel that shows StatusNotifierItems (Plasma does; on GNOME, the AppIndicator extension, where a left click may open the menu instead). Without a tray, start Vults again to open its settings.\nIn the other presets Zeca is in the tray too, with the same states and menu, and a click opens the island at the top.\nThe corner widget # Settings → General → Corner widget puts a small window in a corner you pick; it is off until you pick one. It works with every preset. It shows up to three birds (the session in front, then the ones that want you most) and how many sessions need you or are working. An amber rim means someone needs you.\nClick it to open the island, on the card when one waits. Click a bird to put its session in front first; if that bird\u0026rsquo;s card waits behind another, it comes first. The widget never answers a card.\nIt stays clear of panels and under full-screen windows. Without layer-shell it is an always-on-top window. It costs about 40 MiB of memory while it is on.\nDesktop notifications # Only in Panel, where the island is out of sight, Vults also uses your desktop\u0026rsquo;s notifications (any Linux desktop with a notification server). At the top of the screen the island already shows it all, so you are never told twice; Paused shows none.\nThey say when a session finished (with the first line of its reply), failed (with the error), needs you (a card waits), or has gone quiet. Each session has at most one, titled with its project; it goes away once the card is answered, the session works again, or it leaves (a finished session leaves the wire after 10 minutes, a silent one after 30). What happened while they were off is not raised later.\nOpen (or a click) brings up the island with that session in front. A notification never has Allow or Deny. Turn them off in Settings → General → Notifications.\nThe open island # Open, the island\u0026rsquo;s header has its mark and name, a sound toggle, the search (the palette, as Ctrl+K), the size switch and the full-screen button. Under it, its five tabs as icons, as the prototype draws them:\nFlock, with how many sessions need you; Chat, Zeca\u0026rsquo;s, in a compact form: the engine and your conversations at the top, the composer with a paperclip (to hand Zeca files) and the microphone, the folder and model under it; Activity: this week, counted on this computer, its four figures and a bar per project (the most first; by day when no project is named), and ‹ Previous week / This week; Connections, with how many rows its connectors show and you have not marked seen: a chip per connector and a card per row, with Ask Zeca why, Review with Zeca, Squash and merge and ✓ (Connectors); with none on, + leads to the Nest\u0026rsquo;s Connections; Settings (the gear): Presence and Fold the island right there; its page list also draws Activity (the local history\u0026rsquo;s switch) and Flock \u0026amp; Zeca (Zeca\u0026rsquo;s switch and where the flock draws from) in place; any other page of Settings in the Nest, and All settings (at Agents while an agent\u0026rsquo;s hooks are missing or out of date, at General otherwise). Over the Chat, Activity, Connections and Settings tabs, the chips of the sessions that need you stay under the tabs; one opens its session. Activity has Open in the Nest for the rest, and your tasks are in the Nest, Vults\u0026rsquo; own window, with tabs of its own, which the arrow at the top of the chat also opens. The song and your plans\u0026rsquo; usage take a line under the tabs. Below, the island\u0026rsquo;s whole width (about 500 px on screen, the size switch\u0026rsquo;s Quick):\nat rest, the flock: every session\u0026rsquo;s vult on one wire, twice its size, its project\u0026rsquo;s name under it (boxed for the one in front) and ! when it needs you (up to six; past that, five stay and +N takes the last place, opening the Nest\u0026rsquo;s Flock, amber when one of them needs you), with its badge; rest the pointer on one for its project, agent and what it is doing. Under the wire, a chip for each session that wants a look: a card or a question, a failure, a bird gone quiet; a session, once you click its vult or its chip: ‹ Flock goes back, and a dot per session, in its agent\u0026rsquo;s color, opens another. A permission card opens here by itself and keeps its session in front until you answer. On the left Zeca, large, with a glow and a mark in the color of the session\u0026rsquo;s state (an amber ! for a permission, a cyan ?, green sparkles when done, a red # when it failed, sweat at a usage limit); beside him the project, its agent and its state, and under both the session\u0026rsquo;s card. Session Card Working Its project, the agent, steps so far, running subagents and, for Claude Code, ≈$1.37: what its tokens would cost at API prices (a subscription is not charged it), then the step just done and the current one Idle Waiting for the next prompt, and the last step Needs permission The exact command, file or URL, with Deny and Allow (Approvals) Has a question For a question the island can\u0026rsquo;t answer (Qwen Code, OpenCode 2): the question, and Open terminal. Claude Code\u0026rsquo;s and OpenCode 1\u0026rsquo;s questions get a card Finished The first paragraph of the agent\u0026rsquo;s last reply, OK and Open terminal Failed The error, OK and Open terminal A step reads like Editing main.rs, Running cargo build or, for an MCP tool, Calling github · list_prs.\nA finished edit shows the lines it added and removed (Editing main.rs +4 −2). Click them to see the diff in place of the card, with line numbers when the agent sends them (Claude Code does; Codex\u0026rsquo;s diffs have none). × or Esc goes back. Diffs are kept for the session\u0026rsquo;s last eight steps, up to 400 lines each; a longer one says it stops short.\nOK says you have seen it: the badge goes and the session shows as idle. With nobody on the wire, the card offers Ask Zeca.\nZeca notices you: he looks toward the pointer, preens if you rest it on him, and startles at a click. At start he says hello, by your first name when your user account has a full name (never taken from the network).\nNow playing # Turn on Settings → General → Now playing and the island shows the song your player is playing (any player that speaks MPRIS: Spotify, a browser tab, mpv). The compact island shows it when nothing runs; the open island shows it in the top bar, with previous, play/pause and next on hover. While it plays, Zeca dances and the flock\u0026rsquo;s idle birds court on the wire instead of soaring.\nSubscription usage # The open island\u0026rsquo;s top bar shows how much of your plans\u0026rsquo; limits you have used, next to each agent\u0026rsquo;s dot: 7d 12% is 12% of the weekly window. It turns amber at 70% and red at 90%; hover to see when it resets. No token of yours is read: each number comes from the CLI you logged into.\nCodex: with Settings → General → Codex usage on, asked every 5 minutes, read-only, never while the screen is locked. It reaches OpenAI through your codex, so it is off on a new install. Claude Code (Pro or Max): it reports usage only to a status line, so installing the hooks adds one of ours, which runs yours if you have one (how). The numbers arrive after a session\u0026rsquo;s first reply. Getting to a session # Click a vult in the flock to open that session; it stays in front until you pick another or it leaves. A waiting card still comes first. With the keyboard (Ctrl+Alt+Space), ↑ and ↓ move between sessions: at rest the boxed name moves, in a session the next one opens.\nOpen terminal on its card brings its terminal forward:\na herdr, tmux, kitty or wezterm pane, on any desktop (kitty needs listen_on unix:/tmp/kitty and allow_remote_control socket-only in kitty.conf); the terminal window itself, on KDE Plasma (Wayland or X11) and in any X11 session, on its own workspace and unminimized (of several windows, the one titled after the session\u0026rsquo;s folder first); on GNOME, Hyprland or Sway under Wayland, only a terminal running through XWayland. When there is nothing to bring forward, the card says so. A session started in VS Code\u0026rsquo;s or Cursor\u0026rsquo;s terminal carries a VS Code or Cursor tag, and its button raises that editor instead, on KDE and in X11 sessions.\nGlobal shortcuts work from any window:\nCtrl+Alt+Y / Ctrl+Alt+N allow or deny the permission on screen (Approvals). Ctrl+Alt+J / Ctrl+Alt+K put the next or previous session in front. A waiting card stays in front; the keys choose who comes after it. Ctrl+Alt+V, held, talks to the chat (Speaking). Ctrl+Alt+1, Ctrl+Alt+2 and Ctrl+Alt+3 switch to Mini, Quick and Room. Ctrl+K, in the open island or the Nest, opens the palette. Ctrl+Alt+Space opens the island and hands it the keyboard until it folds: ↑ ↓ (or K J) change the session in front; Enter opens its terminal; M opens its quick actions; C opens the chat. Y or N answer a permission; 1 to 9 pick a question\u0026rsquo;s choice. Esc folds the island and gives the keys back. On Wayland it is the way back to your terminal. Opened with a click, the island takes no keys. The shortcuts go through the desktop portal: KDE asks once, and System Settings → Shortcuts changes the keys (worth it if your editor uses Ctrl+Alt+J or K, as JetBrains IDEs do). Settings → General lists them.\nQuick actions # Right-click a vult in the pill or the flock, or a session\u0026rsquo;s card (away from Zeca) for that session\u0026rsquo;s quick actions:\nOpen terminal, as above. Where the desktop can\u0026rsquo;t, it is greyed out and offers the folder. Open folder: in VS Code when code is on your PATH, else in your file manager. Only an existing folder, by its full path, is opened, with no shell. Activity: the session\u0026rsquo;s last eight steps, each finished edit with its diff. View the last diff, and Open its file in VS Code at the first changed line (or show it in the folder; a file is never run or opened with whatever handles its type). Keep in front, or Let the flock choose. Mute, Pin or Hide this project, and This project\u0026rsquo;s bird… (below). The menu never has Allow or Deny, and does not open over a waiting card. To reach a card waiting behind another, see More than one at a time.\nWhile you were away # While the screen is locked (a desktop that says so through org.freedesktop.ScreenSaver, as Plasma and GNOME do), the island rests (no animation) and connectors stop checking. Agents and cards go on as usual.\nWhen you unlock, or leave Paused, a line says what happened meanwhile: While you were away: 2 finished, 1 failed, 1 waits for you for 12 min. In Island it opens the island once; it stays until you dismiss it with ×. Hidden and muted projects are left out.\nA quiet bird # A session that is working and sends nothing for 5 minutes may be stuck (a command waiting for input, a hung tool). Its bird gets a grey badge: No news for 5 min. At 15 minutes the badge turns amber, one alert plays, and in Panel a notification says so. Long thinking is not flagged; any news clears it.\nThe card offers three answers, and none touches the agent:\nSnooze: gone for 15 minutes, then back if still quiet. Keep going: it is fine; the grey flag comes back only after 30 minutes, the amber one 10 minutes later. Dismiss: no flag again until its next prompt. Each answer counts as news, so the bird stays on the wire (a silent one leaves after 30 minutes); after Keep going it stays until its flag could come back.\nPer project: mute, pin, hide # These choices belong to the project (its folder): every session in it follows them, now and later.\nMute: no sounds or notifications when its sessions finish or fail. Pin: its sessions come first on the wire, in the pill and in the flock. Hide: its sessions stay off the island, the tray and the corner widget. This project\u0026rsquo;s bird…: picks the species for all its sessions; Automatic gives it back to the flock\u0026rsquo;s draw. Settings → Projects lists them, and is the way back for a hidden project.\nSounds # Short 8-bit blips when a session needs you, finishes or fails, and for connector news; softer ones when the island opens or folds and when Zeca reacts to you. The speaker button on the island, or Settings → General → Sounds, turns them off; Volume sets how loud. A card that keeps waiting sounds again, and Do not disturb silences sounds and notifications for a while: see When nobody answers.\n","externalUrl":null,"permalink":"/en/docs/vults/guide/island/","section":"Docs","summary":"The island hangs from the top edge of your screen. On KDE Plasma, Hyprland, Sway and other compositors with layer-shell it sits above every window like a panel; …","title":"The island · Vults","type":"docs"},{"content":"When Claude Code, Codex, OpenCode, Qwen Code or Copilot CLI asks permission for a tool call, the island opens on a card that shows exactly what Allow authorizes (Antigravity, Cursor, Factory Droid and Kimi Code are the exceptions, below):\nthe agent\u0026rsquo;s own words for it, when it gives them (Run the test suite, then the linter); the whole command, file or URL, not just the tool\u0026rsquo;s name. A long command shows in full (up to four lines, then it scrolls); for an edit, the lines it adds and removes (+12 −3). Codex patches show their files and counts too. The card takes the whole width of the island until you answer. Then it says what happened for a moment (Allowed in green, Denied in red) before the next thing shows.\nAnswering # Allow and Deny answer the agent directly; it carries on at once. Under the command, one line says why it asks when there is more to it than \u0026ldquo;nothing allows it\u0026rdquo;: Destructive: no policy ever allows it, or Your policy asks first for this. Ctrl+Alt+Y and Ctrl+Alt+N do the same from anywhere, without leaving your window (shortcuts). The buttons show the keys actually bound. A shortcut only answers a card on screen; with nothing waiting, it does nothing. In the Nest, the card is answered where you are: while its window has the focus, the island stays aside and the session\u0026rsquo;s card on Flock has the same buttons (and takes the same keys). Leave the Nest with a card waiting and the island comes up for it. In Panel mode the island is out of sight, so a card also shows as a desktop notification. Its Open brings the island up on the card; it has no Allow or Deny (notifications). While the app is Paused (presence) no card shows: the agent asks in its terminal at once, and neither Always rules nor policies answer. Always allow allows the request and every identical one from then on: the same agent, tool and exact command, file or URL, in the same project folder. cargo test does not cover cargo test \u0026amp;\u0026amp; rm -rf build, nor the same command in another project. Those requests are answered without a card, and the step says always allowed; an identical request already waiting is answered too. Settings → Approvals lists every rule, and Remove takes one away. A command of more than one line, or over 300 characters, offers no Always allow: a rule would only see its start. A destructive command (rm -rf, a forced push) always asks: its card says so in orange, makes Deny the bright button, outlines Allow in red and offers no Always allow. No rule or policy ever answers it, even a rule saved before. Only you answer: with a click or a shortcut now, or earlier with Always allow or a policy you accepted. No timer or default ever approves anything. Policies # A policy answers cards for you: you write it in Settings → Approvals → Policies, see it as a diff against the one in place, and accept it with a click. One policy per project folder, plus one for everywhere; a newer one replaces the older. Revoke turns it off at once, and its cards ask again. For now a policy allows only Claude Code\u0026rsquo;s calls, the one agent whose cards show the whole call (the others\u0026rsquo; shells run in a folder the card leaves out); for Codex, OpenCode, Qwen Code and Copilot CLI it can deny or ask. Agents whose permissions get no card (Antigravity, Cursor, Factory Droid, Kimi Code) are not answered by one. Zeca\u0026rsquo;s chat follows them only when you choose Your policies or Full access for it (Who answers Zeca\u0026rsquo;s cards).\n# Lines that start with # are notes. project ~/Projects/site agents claude codex allow read allow write allow command git status allow command git diff * deny external ask tool WebFetch project sets the folder it holds in (and below); without it, it holds everywhere. agents limits it to some agents; without it, every built-in agent. allow, deny or ask, then a class (read, read-external, write, external), command and its words (a last * lets more words follow), or tool and a tool\u0026rsquo;s exact name (a shell or an MCP tool only with deny or ask). A destructive action (rm, a forced push, a redirect over a file) always asks: no line can answer it. A deny beats an ask, and an ask beats an allow, across every policy that holds; a deny beats an older Always allow too. A deny or ask line covers whatever follows its words, * or not, and a command holding its first two words anywhere (git --no-pager push, or a bare git push, under deny command git push origin main) is never allowed. It names one program: deny command curl says nothing about wget. deny tool Bash stands for every agent\u0026rsquo;s shell. An allow needs Vults to know what the action does: an MCP tool, an unknown tool or command, an interpreter, a build or a test (cargo test runs the project\u0026rsquo;s code) always ask, and so do the git commands that run the repository\u0026rsquo;s hooks (commit, merge, rebase, pull, push, checkout, switch, cherry-pick, revert, am, worktree, clone). So does a command with a shell symbol, quote, glob or tab, a long or multi-line command, and a card whose target is cut. Always allow still covers one exact command. An allow reaches only the session\u0026rsquo;s folder: the one Vults saw it start in, never / or your home folder, and nothing once the agent moved out of it (a session Vults first saw later, after a restart, gets no allow). A path outside it, one that climbs (..), several paths in one target, or a write under .git asks. An allowed command\u0026rsquo;s words name no absolute path, URL, @file, .., .git or option glued to a path, so a fetch with a URL always asks. A broad line covers all its class: allow external covers git push, gh pr merge, gh workflow run, gh gist create and other actions beyond this computer Vults knows, including ones that send a file from the folder out; allow write covers every known write that can be undone. Prefer command lines. An answer by a policy shows no card; the step says allowed by your policy, and the audit log names the policy\u0026rsquo;s consent (Safety). Editing a policy so it names another folder revokes the one you edited when you accept; the review says so. What it can\u0026rsquo;t see: links. A symlink inside the folder that leads outside it reads as inside. And allow write lets the agent change files that run later, when you or a tool run them (a Makefile, .envrc, package.json\u0026rsquo;s scripts). A policy is a row in your local database: another program running as you could add one, as it could change any of your files. More than one at a time # Requests wait in line, each for its own agent. The card shows the first one with 1 of 3, under a dot per session in its agent\u0026rsquo;s color. Answer one and the next comes up.\nTo answer one further back first, click Open on its notification (in Panel) or its bird in the corner widget: its card comes to the front, keeping its own deadline. Muted and hidden projects (per project) still get their cards and sounds.\nA question half answered starts over when another card comes in front of it. A card whose agent moved on is not brought forward, but a subagent working in parallel does not take a card away. When one agent asks for two calls at once, answering the first leaves the second waiting.\nWhen nobody answers # A request waits as long as its agent does (a little under two minutes); meanwhile the terminal says Waiting for your answer in Vults. During the last 30 seconds the card counts down, Goes back to the terminal in 0:25, and then the terminal asks as if Vults were not there. If you answer in the terminal instead, the card says so and goes away.\nWhile it waits, a card climbs a ladder, so a card you missed reaches you:\nIt opens the island, with its sound (in Panel, with a desktop notification too, if notifications are on). Its sound plays again at 45, 75 and 105 seconds. Do not disturb (Settings → General, for 30 minutes, 1 hour or 4 hours) silences the sounds and notifications of everything at rest, and this ladder\u0026rsquo;s reminders, until it ends by the clock (a suspend does not stretch it). A moon in the island\u0026rsquo;s header says it is on; a click on it ends it. A card still opens the island with its sound (and in Panel its notification): nothing hides a card an agent is waiting on.\nIf the app is closed or not responding, the hook answers nothing within milliseconds and every agent asks in its terminal as usual.\nClaude Code\u0026rsquo;s auto mode # In auto mode the classifier clears most requests in a blink, so the island only shows a card, plays a sound or opens for a request still waiting after a moment. The same goes for \u0026ldquo;finished\u0026rdquo;: only a session that stays done is news.\nQuestions # When Claude Code or OpenCode asks you something with choices (Claude Code\u0026rsquo;s AskUserQuestion tool, OpenCode\u0026rsquo;s question tool), the island opens on a question card:\nOne question at a time, with its tag (Theme) and 1 of 2 when it asks several. A click on a choice answers it. When several may be picked, tick them and press Next (or Send on the last one). Other… opens a field for your own words; Enter sends it, Escape goes back. Reply in the terminal puts the question back in the agent\u0026rsquo;s terminal. Until then, or until the card runs out of time, Claude Code waits for the island. Ctrl+Alt+Y and Ctrl+Alt+N never answer a question. This needs Claude Code 2.1.85 or later and hooks installed by this version. Older hooks show Update available in Settings → Agents; until you update them, the island shows the question and takes you to the terminal, as it does for Qwen Code.\nOpenCode # OpenCode\u0026rsquo;s permissions and questions get a card through the plugin Settings → Agents → OpenCode writes (checked with OpenCode 1.18.35 and 2.0.26). OpenCode\u0026rsquo;s own prompt stays up meanwhile: answer on the island or in OpenCode, and the first answer wins; the card goes away when you answer in OpenCode.\nAllow is OpenCode\u0026rsquo;s Allow once; Deny its Reject. Always allow is a Vults rule: OpenCode\u0026rsquo;s own Always, which would change its settings, is never used. Some questions stay in OpenCode\u0026rsquo;s prompt: all of them in OpenCode 2, which lets no plugin answer them, and in OpenCode 1 those with more than four choices or more than four at once. The island shows that one waits there. With no click in time, the app closed or an outdated plugin (Update plugin… in Settings → Agents), OpenCode simply waits in its own prompt. Qwen Code # Qwen Code\u0026rsquo;s permissions get a card through the hooks Settings → Agents → Qwen Code installs (checked with Qwen Code 0.25.0). Qwen reads the same answer as Claude Code, so Allow, Deny and Always allow work as they do there.\nWhile the card is up, Qwen shows only its spinner; with no click in time, or the app closed, it asks in its own prompt. Its questions (ask_user_question) stay in its terminal: Qwen takes no hook answer to them. The island shows that it waits there. Cards come up in Qwen\u0026rsquo;s default approval mode (Shift+Tab in Qwen, or qwen --approval-mode default); a new install starts in auto, where its own classifier decides. In yolo Qwen approves everything, so no card comes up. Copilot CLI # Copilot CLI\u0026rsquo;s permissions get a card through the hooks file Settings → Agents → Copilot CLI writes, ~/.copilot/hooks/vults.json (checked with Copilot CLI 1.0.96). Copilot reads every file in that folder, so the one Vults writes holds only its own hooks; a file there that Vults did not write is never touched.\nAllow and Deny answer that one call; Always allow is a Vults rule. An edit\u0026rsquo;s card shows the lines it adds and removes; a new file shows as a write. With no click in time, or the app closed, Copilot asks in its own prompt. It has no question tool, so no question comes to the island. A failed command shows as a failure; what a tool printed never leaves the hook. Agents that ask in their own prompt # Antigravity, Cursor, Factory Droid and Kimi Code ask their permissions in their own terminal or prompt, and the island never answers them: their birds show the steps. What each agent can do sums them up, and each one\u0026rsquo;s section there says why it has no card. A task Vults runs on Kimi Code asks with a card for each write and command, as any task\u0026rsquo;s agent does (Tasks).\n","externalUrl":null,"permalink":"/en/docs/vults/guide/approvals/","section":"Docs","summary":"When Claude Code, Codex, OpenCode, Qwen Code or Copilot CLI asks permission for a tool call, the island opens on a card that shows exactly what Allow authorizes …","title":"Approving from the island · Vults","type":"docs"},{"content":"spn \u0026lt;command\u0026gt; [flags] spn with no arguments, spn help or spn -h prints the list of commands. Flags may come before or after the positional arguments: spn new eza --out x and spn new --out x eza are the same command.\nThe palette and the port catalogue are embedded in the binary. The flags below that take a file or directory read that copy from disk instead, which is how the contract gets changed and measured before it ships.\nCommands that write files # --check on gen, readme and preview writes nothing and fails when what is committed is out of date. That is what a port\u0026rsquo;s CI runs.\nspn gen # Render a port\u0026rsquo;s mapping into finished theme files.\nspn gen [templates...] With no arguments it renders every *.tmpl in the current directory, so a port repository runs bare spn gen.\nflag does --check do not write; fail if any file on disk is out of date --stdout write to stdout instead of to files --app \u0026lt;slug\u0026gt; registry slug for this port (default: inferred from the template name) --registry \u0026lt;file\u0026gt; read the port catalogue from this file --palette \u0026lt;dir\u0026gt; read the contract from this directory -q print only failures spn readme # Render a port\u0026rsquo;s canonical README from the catalogue.\nspn readme [slug] flag does --out \u0026lt;file\u0026gt; file to write (default README.md) --check do not write; fail if the README is out of date --stdout write to stdout instead of to a file --registry \u0026lt;file\u0026gt; read the port catalogue from this file --copy \u0026lt;file\u0026gt; read the shared README prose from this file --palette \u0026lt;dir\u0026gt; read the contract from this directory spn preview # Draw the synthetic preview for each flavour. The previews are SVGs drawn from the palette, so they cannot show a colour the user will not get.\nspn preview [slug] flag does --out \u0026lt;dir\u0026gt; directory to write the previews into (default assets) --check do not write; fail if any preview is out of date --registry \u0026lt;file\u0026gt; read the port catalogue from this file --copy \u0026lt;file\u0026gt; read the shared README prose from this file --palette \u0026lt;dir\u0026gt; read the contract from this directory spn new # Scaffold a new port repository: the mapping stub with the canonical header, the six-line workflow, the Renovate config and the editor settings. The port has to be listed in the catalogue first.\nspn new \u0026lt;slug\u0026gt; flag does --out \u0026lt;dir\u0026gt; directory to scaffold into (default: the slug) --force overwrite files the scaffold would replace --registry \u0026lt;file\u0026gt; read the port catalogue from this file --copy \u0026lt;file\u0026gt; read the shared README prose from this file spn pin # Rewrite the engine version a mapping declares in its frontmatter. The release fan-out runs it in the same pull request that regenerates a port\u0026rsquo;s files.\nspn pin \u0026lt;version\u0026gt; [templates...] flag does --check report whether the pin already matches, write nothing Commands that check # spn lint # Check that mappings ask for roles, never raw colours. It walks the template\u0026rsquo;s parse tree, so a counter-example inside a comment is not a finding.\nspn lint [templates...] spn check # Audit contrast, accent separation and colour vision for every flavour. Fails when a pair falls below its floor.\nflag does -v also list the passing pairs and the colour vision summary --json print what the audit measures and the policy it measures against, as data --palette \u0026lt;dir\u0026gt; read the contract from this directory spn registry # List and validate the port catalogue.\nflag does --json print the catalogue as JSON — used to build the fan-out matrix --registry \u0026lt;file\u0026gt; read the port catalogue from this file --copy \u0026lt;file\u0026gt; read the shared README prose from this file Commands that print # spn entry # Print a starter catalogue entry for a new port, with every required field, a mapping table to extend and a complete preview. It validates as printed.\nspn entry \u0026lt;slug\u0026gt; \u0026gt;\u0026gt; registry/ports.yml spn palette # Print the palette and the resolved role layer.\nflag does --roles print the resolved role layer instead of the colours --json print the contract as JSON instead of a table --palette \u0026lt;dir\u0026gt; read the contract from this directory spn version # Print the tool version. A build from source prints dev.\n","externalUrl":null,"permalink":"/en/docs/sp-night/reference/cli/","section":"Docs","summary":"spn \u0026amp;lt;command\u0026amp;gt; [flags] spn with no arguments, spn help or spn -h prints the list of commands. Flags may come before or after the positional arguments: spn …","title":"Command reference · SP Night","type":"docs"},{"content":"The Sessions tab puts the conversations of every agent into one list, newest first. You can resume a session in its own CLI, read it, search it, export it, name it and delete it. lazyagents only reads each CLI\u0026rsquo;s session files. The only time it changes them is when you delete a session.\nConcepts # Session: one conversation with an agent, with its first prompt as the title, the folder it ran in and the time it was last updated. Alias: a name you give a session. lazyagents stores it in its own file and never writes it into the CLI\u0026rsquo;s data. The filter matches aliases as well as titles. Live session: a session some agent process still has open. It shows a ● badge and cannot be deleted until you close it. Transcript index: lazyagents reads each transcript once and afterwards only the part appended since. That keeps reloads fast on large histories. The index is a disposable cache (see Files). Where sessions come from # Agent Read from Resumed with Notes Claude Code ~/.claude/projects/\u0026lt;project\u0026gt;/*.jsonl claude --resume \u0026lt;id\u0026gt; Live badge and token usage per session. The reader also reads subagent transcripts from \u0026lt;project\u0026gt;/\u0026lt;id\u0026gt;/subagents/ Codex ~/.codex/sessions/**/*.jsonl (rollouts) codex resume \u0026lt;id\u0026gt; Gemini CLI ~/.gemini/history/\u0026lt;project\u0026gt;/ and ~/.gemini/tmp/\u0026lt;project\u0026gt;/ chats gemini --resume \u0026lt;id\u0026gt; The folder comes from Gemini\u0026rsquo;s project map OpenCode ~/.local/share/opencode/opencode.db opencode --session \u0026lt;id\u0026gt; Read through the sqlite3 binary, read-only; the 500 most recent top-level sessions Crush \u0026lt;project\u0026gt;/.crush/crush.db for each project in ~/.local/share/crush/projects.json crush --session \u0026lt;id\u0026gt;, run in the project Read through the sqlite3 binary, read-only; a sub-agent\u0026rsquo;s session is shown as part of the one that started it Pi ~/.pi/agent/sessions/\u0026lt;project\u0026gt;/*.jsonl pi --session \u0026lt;file\u0026gt; Also PI_CODING_AGENT_SESSION_DIR or an absolute sessionDir in Pi\u0026rsquo;s settings.json. The title is the /name given in Pi, else the first prompt; the transcript shows the active branch only The paths are the defaults: an agent moved with its config variable (CLAUDE_CONFIG_DIR, CODEX_HOME, XDG_DATA_HOME or OPENCODE_DB…) is read there.\nClaude Desktop and Hermes Agent have no local sessions that lazyagents can read. What each agent supports beyond sessions is in the agent reference.\nRequirements:\nOpenCode needs sqlite3 in PATH, both to list sessions and to read transcripts. Without it the other agents still load and the error names the missing binary. Live badge: on Linux lazyagents reads /proc directly. On other systems it runs lsof once per load, and without lsof no session shows as live. In the TUI # Every key for this tab is in the key reference, and ? shows it inside the TUI. The side panel shows the selected session\u0026rsquo;s folder, id and tokens, when the agent records usage, plus an estimated cost when the agent is authenticated with an API key. A cost the agent recorded itself (Pi, Crush) is shown without ~; Crush records only a cost, not the tokens of the whole session, so its sessions show no token line.\nResume a session # Press enter. lazyagents suspends itself, runs the agent\u0026rsquo;s resume command in the session\u0026rsquo;s folder, and comes back when the agent exits. If the folder no longer exists, lazyagents asks for another one. To choose the folder yourself, press R. The prompt accepts ~.\nTo copy the command instead of running it, press c. It shows cd \u0026lt;folder\u0026gt; \u0026amp;\u0026amp; \u0026lt;command\u0026gt; in the status line.\nThe agent\u0026rsquo;s binary has to be in PATH. An agent that cannot resume from its CLI shows an error instead.\nRead a transcript # Press v to open the transcript as a log: each of your prompts is a numbered header, and each agent turn sits behind a bar in the agent\u0026rsquo;s color. A turn shows the agent\u0026rsquo;s answer, the messages it wrote after its last command; the work before that (progress messages, reasoning, commands) is folded into one ⋯ line that counts them.\nFor Claude Code, Codex, Pi, OpenCode and Crush, the reader also shows when each prompt was sent and how long the agent worked on it. Compactions, interruptions, model changes and, for Pi, the branches of the conversation (branch 2 of 3, then one line per other branch) appear as lines between turns, and slash commands as the command you typed. A command that failed is marked ✗ (Codex does not link a failure to its call, so its commands are never marked). A Crush summary shows as a compaction.\nEach call reads by what it did: $ command for a shell command, ✎ file +3 −1 for an edit with the lines it added and removed, a dimmed line for reads, searches and web lookups, and ⎇ for a subagent, with how many calls it made when Claude Code kept its transcript. The folded ⋯ line also counts the files edited. A plan the agent proposed (Claude Code plan mode) is shown as a document, folded or not, and a task list as its checklist with how many are done. Inside the reader:\nn and N jump between your own prompts. g and G go to the top and the end. From 150 columns, a rail on the left lists every prompt with its time and marks for what the reply did (✎ edited files, ✗ a command failed, ⎇ started a subagent), the one you are reading highlighted. m switches the view: log (the default above), conversation (prompts and answers, no steps) and actions (prompts and every call, no messages: what the agent did to your files and shell). ] and [ pick the next or previous turn with folded steps, subagent or branch (▶). enter unfolds or folds a picked turn alone, and opens a picked subagent\u0026rsquo;s or branch\u0026rsquo;s own transcript; esc returns to where you were. e unfolds every step of every turn, or folds them back. t shows tool commands (❯) one per line or summarized. It unfolds the steps too. r shows reasoning (💭) in full or only its first line. It unfolds the steps too. esc goes back to the list. Export to Markdown # In the transcript reader, press x. lazyagents writes the file to ~/.local/share/lazyagents/exports/ as \u0026lt;agent\u0026gt;-\u0026lt;id\u0026gt;-\u0026lt;timestamp\u0026gt;.md, with mode 0600. The file has a header (agent, date, folder), then one section per turn, with tool commands as a list and reasoning as quotes. An empty transcript is not exported.\nSearch every transcript # Press F and type a word or phrase. The search is case-insensitive and runs across the full text of all loaded sessions, in parallel. For Claude Code and Codex, files that cannot contain the text are skipped without being decoded. The list then shows only the matches, each with an excerpt around the first hit. esc clears the search.\n/ is a different tool: it filters the list by agent, alias, title and folder name, without opening any transcript.\nName a session # Press m and type an alias. Saving an empty alias removes it. The alias shows before the title everywhere, including lazyagents sessions.\nGroup and filter # g groups the list by project (the folder name) and agent. f cycles the agent filter: all agents, then one agent at a time. r reloads from disk. lazyagents does not watch the files. Delete sessions # Press d to delete the selected session. To delete several, mark them with space first; on a group header, space marks the whole group. A confirmation lists what will be deleted before anything happens.\nClaude Code, Codex, Gemini CLI and Pi: the session file is copied to ~/.local/share/lazyagents/backups/ (named \u0026lt;file\u0026gt;.\u0026lt;timestamp\u0026gt;) before it is removed. To restore a session, copy the backup back to its original folder and remove the timestamp suffix. OpenCode: lazyagents first saves opencode export \u0026lt;id\u0026gt; to ~/.local/share/lazyagents/backups/opencode-\u0026lt;id\u0026gt;.\u0026lt;timestamp\u0026gt;.json (mode 0600), then runs opencode session delete \u0026lt;id\u0026gt;. If the export fails, nothing is deleted. To restore, run opencode import \u0026lt;file\u0026gt;. Crush: lazyagents first saves crush session show \u0026lt;id\u0026gt; --json (the session and every message) to ~/.local/share/lazyagents/backups/crush-\u0026lt;id\u0026gt;.\u0026lt;timestamp\u0026gt;.json (mode 0600), then runs crush session delete \u0026lt;id\u0026gt;, both pointed at the session\u0026rsquo;s data dir. If the backup fails, nothing is deleted. Crush has no import command, so the backup is a record of the conversation, not something to load back. Live sessions are refused: close the agent first. From the CLI # lazyagents sessions # every agent, newest first lazyagents sessions --agent codex --limit 20 lazyagents sessions --here # sessions started in this folder lazyagents sessions --json | jq \u0026#39;.[0].id\u0026#39; # id, title, alias, cwd, updated, usage --json includes token usage when the agent records it, and cost_usd only for agents authenticated with an API key. Resuming, reading and deleting sessions are TUI actions. Every option is in the CLI reference.\nConfiguration # The Sessions tab has no config.yaml section. To open lazyagents on this tab, use startTab: sessions in the tui: section (configuration).\nFiles # Path Contents ~/.local/share/lazyagents/session-aliases.json Aliases, keyed by \u0026lt;agent\u0026gt;:\u0026lt;session id\u0026gt; ~/.local/share/lazyagents/exports/ Exported transcripts ~/.local/share/lazyagents/backups/ Copies of deleted session files, and OpenCode exports (opencode-\u0026lt;id\u0026gt;.\u0026lt;timestamp\u0026gt;.json) ~/.local/share/lazyagents/transcript-index.gob Transcript index. Deleting it only makes the next load read everything again Limits # Costs are estimates at Claude API list rates (Pi: Pi\u0026rsquo;s own recorded cost), only cover known Claude models and only show for API-key accounts: a subscription does not pay per token. The usage guide explains the rules. Session formats are private to each CLI and can change between versions. A file lazyagents cannot parse is skipped, and the other sessions keep loading. OpenCode lists at most 500 sessions, and subagent sessions are left out. ","externalUrl":null,"permalink":"/en/docs/lazyagents/guide/sessions/","section":"Docs","summary":"The Sessions tab puts the conversations of every agent into one list, newest first. You can resume a session in its own CLI, read it, search it, export it, name …","title":"Sessions · lazyagents","type":"docs"},{"content":"The Nest is Vults\u0026rsquo; own window, for when the island is too small: the whole flock, your tasks, the chat, what happened and your connections. Open it from the tray icon (Nest…) or with the arrow at the top of the island\u0026rsquo;s chat. It is built when you open it and gone when you close it, and it opens with Zeca off too, without its Chat. While it is open the island steps aside, pill and birds and all: one surface on screen at a time. The Nest has no system title bar: its own header moves it (a double click fills the screen) and its edges and corners resize it. It first opens large (up to 1180 × 820, within the screen), then at the size you last left it. It comes back when the Nest closes or goes back to Quick or Mini. A card that waits is answered where you are: in the Nest while you are in it (its window has the focus), and on the island, which comes up for it, while you are elsewhere.\nThe header counts the flock (4 sessions · 1 needs you) and holds the song your player is playing (with Now playing on; previous, play/pause and next beside it), the moon while do not disturb lasts (a click ends it), the sound, the search (the palette, as Ctrl+K), the size switch and the full-screen button. Under it, five tabs with their icons, the number that needs you on Flock and the connectors\u0026rsquo; rows on Connections, and Settings at the right, which shows Settings in this same window, under the same tabs; a tab brings the Nest back. Over every tab but Flock, a tag for each session that needs you (or failed); a click opens Flock with it in front (a card that waits comes first there), where you answer it. Settings stays a page of its own there: it is the only one that changes an agent\u0026rsquo;s config, a key, a policy or a phone, and it never shows an agent\u0026rsquo;s text. The switch keeps the tab you are on: Room is the window, the full-screen button the whole screen (Esc goes back to Room), Quick closes the Nest and opens the island, Mini closes it and leaves the pill. Alt+1 to Alt+5 pick a tab. At the bottom, Zeca on · optional / Zeca off switches him (as Settings → Flock \u0026amp; Zeca → Zeca does), Zeca\u0026rsquo;s look opens his page in Settings, Open terminal brings up the terminal of the session in front, and Back to the island brings the island up.\nCtrl+K, in the Nest or the open island (when it has the keyboard: opened with Ctrl+Alt+Space, or in its chat), opens the palette: type to jump to a session, to start /task, /delegate, /continue or /remember in the chat (with Zeca on), or to open a tab. It never answers a card: a session that waits for you only leads to its card.\nFlock # The flock across the window, three times its size, each bird over a card with its session\u0026rsquo;s project, state and agent, and by the heading how many are out on a web sortie and how many sit on the wire. A click on a bird or a card (or ↑ ↓) puts that session in front. A right click (or the menu key, or Shift+F10) opens its quick actions, as on the island: Open terminal, Open folder, Activity, View the last diff, Keep in front (or Let the flock choose), and mute, pin or hide its project. While a card waits, only that session\u0026rsquo;s Activity and diff can show. Under them, Session details: Needs you keeps only the sessions waiting for you, and Find a session… reads projects, repositories, branches, agents and what each is doing now.\nThe session in front has its card at the left: its state, what it is doing, and for one that waits, what it asks (the command, with Copy, and 1 of 2 when more wait). A working one shows its steps so far, its subagents and its cost in the head, the step just done and the one under way (a finished edit\u0026rsquo;s +N −M opens it in Changes), and, when it has gone quiet, Snooze, Keep going and Dismiss, as on the island. A finished or failed one shows its last reply or its error. Under the card, what its kept edits changed (from its last steps). At the right, its Changes (each finished edit\u0026rsquo;s diff, chosen in the list; Focus diff gives the diff the whole width), Activity (its last steps, each at the time it started), Context (worktree, repository, branch, folder, agent, editor, state and cost) and, with Zeca on, Zeca: Ask Zeca opens the Chat with the question ready. In Full, your plans\u0026rsquo; usage sits under it, amber from 70% and red from 90%.\nA session that waits for you is answered on its card, as on the island (Approving from the island): Deny, Allow and Always allow for a permission (Ctrl+Alt+Y and Ctrl+Alt+N too, while the Nest has the focus), the choices for a question. One with no card here (it asks in its terminal) says so. Open terminal shows where Vults can bring its terminal forward (The island).\nTasks # Your tasks, under the roost each belongs to (the ones with none last): each with its goal, criteria, owner, status and cost, why the router chose its agent, its review rounds as marks (up to three, the last in its verdict\u0026rsquo;s color), the buttons that start, review or hand it off, and today\u0026rsquo;s spend against your daily budget (set in Activity → Cost). New task keeps one without the chat, which is how you make one with Zeca off.\nChat # The island\u0026rsquo;s conversation with Zeca, wide, with replies in full Markdown (a link shows its address and is never followed); a message, New or another engine in either one shows in both. Cards are answered here too, with Deny, Allow and Always in this chat, as on the island. The title bar holds the conversation\u0026rsquo;s title, the engine pill (as on the island: the engine in its color and its model; its menu holds the engines, the models and the effort), the folder, and two panel buttons. The composer has a paperclip: the files you pick go to this chat, as a drop on the island does. In a wide window or Full, the left one shows Recent: your newest 30 conversations, the one going on marked, and + New conversation (both wait while Zeca replies); a click goes on with one. The right one opens Memory, what Zeca remembers, beside the conversation: what waits for your yes, the files and their history (Zeca\u0026rsquo;s memory). Before anything is said, the composer waits in the middle with a few questions to start. Without Zeca there is no Chat tab.\nActivity # What this computer keeps, read only here:\nWeek: the week\u0026rsquo;s four figures (time with your agents, turns, lines changed, your answers), a bar a day (a click shows that day), Save as image… with or without the project names, and the year\u0026rsquo;s grid, with the history\u0026rsquo;s switches (Activity). By project: this week\u0026rsquo;s time per project in bars (a repository\u0026rsquo;s worktrees add up under it, with how many it has), a search by project, branch or agent, and a card for each repository a session works in: its folders and branches, its sessions (a click puts one in front on Flock), your pull requests and checks (from the GitHub card; a pull request or the default branch\u0026rsquo;s checks open on GitHub), what Zeca remembers of the project and what its tasks cost (Zeca\u0026rsquo;s memory). Preferences ↗ opens Settings → Projects. History: each finished turn of your agents, counts only. Cost: what the agents cost today and the daily budget you set here, your plans\u0026rsquo; usage windows and what the agents cost over the last two weeks, against that budget, and by roost (Cost and a daily budget). Traces: Zeca\u0026rsquo;s own work, each chat turn with its model, tokens and cost, and under it each tool card with how long it waited for an answer; kept 30 days. Audit: who answered each card and how (you, your phone, an Always rule or a policy you accepted); kept 90 days and never changed. Connections # A chip for each connector you turned on, with how many rows it has; + opens Settings → Connectors. Under it, the chosen connector: when it last synced (or why its last poll failed), a repository filter (All repos or one of them), and ↻ Refresh to ask again (at most once a minute). For GitHub, four tabs with their counts: Inbox (what waits on you: reviews asked of you, your pull requests failing, sent back or ready to merge, a default branch failing; how many are new), Pull requests, Actions (your default branches\u0026rsquo; checks; red with how many fail) and Releases (each repository\u0026rsquo;s latest). A row picked shows beside them: its repository, branch, size (+120 −40 · 6 files), who reviewed or is asked, when it last changed, each check one by one (a bar while they run), a gh command to look closer with Copy (Vults never runs it), Open in GitHub ↗, and the row\u0026rsquo;s actions, as on the island (The GitHub card); a row marked ✓ Seen dims here. Add a connection lists each connector Vults has that is off, with what it watches; Turn on in Settings… and Manage connections open Settings → Connectors, the only place that turns one on (Connectors).\n","externalUrl":null,"permalink":"/en/docs/vults/guide/nest/","section":"Docs","summary":"The Nest is Vults\u0026amp;rsquo; own window, for when the island is too small: the whole flock, your tasks, the chat, what happened and your connections. Open it from …","title":"The Nest · Vults","type":"docs"},{"content":"The Usage tab answers two questions for each agent: how much of your subscription is left, and where your tokens went. Limits come from each agent\u0026rsquo;s account. Token counts come from the transcripts already on your disk. The tab is read-only.\nConcepts # Limit windows: the percentage a subscription has used in each window, with its reset time. The windows are the session window (5 hours) and the weekly window; some accounts also have a weekly window per model. Scripts should match a window on its kind (session, weekly, weekly_model). The label is display text and may change. Auth mode: how the agent is authenticated: subscription, API key or unknown. lazyagents reads it from the agent\u0026rsquo;s own files without keeping any secret. It decides whether a dollar cost is shown at all. Usage events: one per model response recorded in a transcript: time, model, folder and tokens (input, output, cache read, cache write). Every table in the tab sums these events. Current block: activity is split into 5-hour blocks, the same window as Claude Code\u0026rsquo;s session limit. A block starts at the first response after a gap and lasts 5 hours. The current block is the one that contains the present moment. Which agents report limits and usage history is in the agent reference.\nIn the TUI # The tab loads nothing until you open it for the first time. Every key is in the key reference. The limits of each agent are a block, laid side by side as far as the width allows, so many agents still fit on a few lines. When the table leaves room on the right (about 180 columns), the top agents, models and projects of the period sit beside it. Bars grow with the room up to 40 columns.\nCheck your limits # The top of the tab shows one bar per limit window, with the percentage used and the reset time, plus each agent\u0026rsquo;s plan or auth mode. The limits are read differently per agent:\nClaude Code: lazyagents calls the endpoint that Claude Code\u0026rsquo;s /usage uses, with the CLI\u0026rsquo;s own login. It only does this when you open the tab or refresh, never at startup. Codex: read from the limits Codex records in its latest session rollouts. There is no network call, so the numbers are as fresh as your last Codex session. Results are cached for 5 minutes. Press r to fetch again, ignoring the cache. When a fetch fails, the previous limits stay on screen with a warning. An agent with nothing to report (not installed, not signed in, signed in with an API key, or never used) is left out of the limits and of doctor.\nFilter by period, agent and view # p and P step the period forward and back: today, 7, 30 or 90 days, or all history. a and A cycle the agent: all agents, then one at a time. ← and → (or v) switch the table between days, agents, projects and models. Projects are grouped by the full folder path, so two folders with the same name stay apart. / filters the table rows by name. On the daily view it also matches the weekday (Tue). esc clears the text, and a second esc goes back to all agents. Changing a filter never reads the transcripts again. The daily view includes days with no usage, so the sparkline has no gaps.\nThe same filters are in the command palette (:), for example :usage period 30d, :usage view projects, :usage clear and :usage refresh.\nRead the current block # Below the limits, the summary shows the current 5-hour block, when there is one: when it started, how much time is left and the tokens used so far. Use it to see how close the session window is to its end.\nCost # A dollar column shows up only when an agent in the filter is authenticated with an API key. A subscription does not pay per token, so the column stays hidden for it. For API-key accounts:\nEach response is priced at its own model\u0026rsquo;s rate, and rows are the sum. A session\u0026rsquo;s cost in the Sessions tab is summed the same way, so a session that switched models is never priced at the last model\u0026rsquo;s rate. Prices exist only for Claude models, at Claude API list rates. Claude Code records how each response was billed, and lazyagents follows it: 1-hour cache writes at 2x input (5-minute ones at 1.25x), fast mode at its own price on Opus 4.8, Opus 5 and Opus 5.5 (a fast response on another model is unpriced), the Batch API at half price, and US-only inference (inference_geo: \u0026quot;us\u0026quot;) at 1.1x on Claude 4.6 and later (unpriced on earlier models, which do not offer it). Priority Tier and any other region have no published price, so those responses count as unpriced. If any response in a row has no price (an unknown model, for example Codex\u0026rsquo;s), the row shows — instead of a partial sum. A response with no tokens costs $0 whatever its model, so Claude Code\u0026rsquo;s API-error and interrupt lines (model \u0026lt;synthetic\u0026gt;) never blank a row. Pi records the cost of every response itself, from its own model catalog, so Pi rows use Pi\u0026rsquo;s figure for any model. Pi can use several providers at once, so each response is judged by its own provider: one signed in with OAuth (a ChatGPT or Claude subscription, through /login) costs $0.00, one with an API key (auth.json or apiKey in Pi\u0026rsquo;s models.json) keeps Pi\u0026rsquo;s cost. Pi\u0026rsquo;s figure is used as is, so a model Pi has no price for (a local one) shows $0.00, never a table estimate. Pi counts as API key when any of its providers has a stored API key; the placeholder key lazyagents writes for a keyless profile does not count. The official pricing is the reference. Treat the numbers as estimates.\nFrom the CLI # lazyagents usage # dashboard: limits, current block, period summary lazyagents usage limits # only the limit windows lazyagents usage daily --since 30d # tokens per day for the last 30 days lazyagents usage projects --limit 0 # every project, not only the top 10 lazyagents usage models --agent claude-code --since 2026-09-01 lazyagents usage limits --refresh --json --since takes a number of days (7d, counted from midnight), a number of hours (24h) or a date (2026-09-01). usage limits exits with 1 when no agent could report limits, which helps in scripts. Every view and option is in the CLI reference.\nConfiguration # The usage: section of config.yaml sets the filters the tab opens with:\nusage: period: 30d # today | 7d | 30d | 90d | all (default 7d) view: projects # daily | agents | projects | models (default daily) An unknown value keeps the default. The section only affects the TUI; the CLI uses its own flags. More in configuration.\nFiles # Path Contents ~/.local/share/lazyagents/usage-cache.json Limits fetched in the last 5 minutes. Safe to delete ~/.local/share/lazyagents/transcript-index.gob Transcript index shared with Sessions: tokens per 15-minute slot. Safe to delete lazyagents does not write any agent file from this tab.\nLimits # Tokens are counted from local transcripts only. Usage from another machine, or from sessions you deleted, is not counted. The limit endpoint Claude Code uses is not a public API and can change without notice. When it does, the tab shows the error and keeps the last cached values. Codex limits are only as recent as the last Codex session on this machine. A window whose reset time passed since then shows 0% with no reset time: the use recorded was from before the reset. Crush records a session\u0026rsquo;s cost but only the tokens of its last request, so it has no usage history here; its cost shows in the Sessions tab. Pi has no subscription limits to show, only token usage and cost. A provider whose API key comes only from an environment variable is not seen by lazyagents, so its calls keep Pi\u0026rsquo;s cost only when another Pi provider uses a stored API key. ","externalUrl":null,"permalink":"/en/docs/lazyagents/guide/usage/","section":"Docs","summary":"The Usage tab answers two questions for each agent: how much of your subscription is left, and where your tokens went. Limits come from each agent\u0026amp;rsquo;s …","title":"Usage · lazyagents","type":"docs"},{"content":"A provider profile is a named endpoint, model and token that lazyagents writes into an agent\u0026rsquo;s own config file, so you can switch Claude Code or Codex between the official API, a proxy or a local model without editing JSON or TOML by hand. It works like cc-switch: lazyagents only writes config and never runs a proxy itself. Which agents support it, and which file each one gets, is in the agent support reference.\nConcepts # Profile: name, baseUrl, model, token, envKey and wireApi. A profile has to set at least one of endpoint, model or token. Names are limited to 40 characters. Library: every profile lives in ~/.config/lazyagents/providers.json, mode 0600, because it may hold tokens. Applied: what an agent\u0026rsquo;s live config says right now. lazyagents reads it back from the agent\u0026rsquo;s file, so a profile you set by hand still shows up, as \u0026ldquo;provider set outside lazyagents\u0026rdquo;. An applied provider is matched to a library profile by endpoint, and by model when two profiles share an endpoint. Clear: removes only what lazyagents manages and restores what was there before. The rest of the file is left as it was. In the TUI # The Providers tab is a profile × agent matrix: ● applied, ○ agent default, – agent not installed. The detail pane shows the endpoint, model, token state (token ✓, never the value) and what each agent has applied. The full key list is in the key reference.\nCreate a profile # Press n to open the form. Its fields are name, endpoint, model, token, env var and wire api. tab moves between fields and enter saves. The token field stays masked while you type. e edits the selected profile: leave the token empty to keep the saved one, which the form never loads. Renaming a profile in the form renames it in the library.\nApply or clear per agent # Pick the agent column with ←/→ and press space. Before writing, a confirmation shows the file, the current endpoint and the new one (agent default → https://…). Pressing the key again on the profile that is already applied clears it. a applies the profile to every installed agent that supports providers, and x clears the provider from all of them. d deletes a profile from the library but leaves agents where it was applied untouched. Use x, or clear per agent, to undo those.\nWhat is written to Claude Code # Claude Code gets three keys in the env block of ~/.claude/settings.json:\n{ \u0026#34;theme\u0026#34;: \u0026#34;dark\u0026#34;, \u0026#34;env\u0026#34;: { \u0026#34;ANTHROPIC_AUTH_TOKEN\u0026#34;: \u0026#34;sk-…\u0026#34;, \u0026#34;ANTHROPIC_BASE_URL\u0026#34;: \u0026#34;https://proxy.example.com\u0026#34;, \u0026#34;ANTHROPIC_MODEL\u0026#34;: \u0026#34;my-model\u0026#34; } } Only those three keys change. Other env variables and every other key in the file keep their values and their order; a new key goes at the end of env. lazyagents remembers which of the three keys it wrote, so it only ever removes its own:\nA profile field that is empty leaves the key alone if you set it yourself. If lazyagents wrote it for a previous profile, it is removed: switching from a profile with a model to one without drops the old model. When a profile replaces an endpoint or model you had set by hand, clearing puts your value back. A token you had set by hand is not kept by lazyagents (it is a secret); it stays in the backup of settings.json. A key you edited after lazyagents wrote it is yours again, and clearing leaves it. Clearing removes the env block too if lazyagents created it and it ends up empty. Applying and then clearing gives back the original file. The record lives in ~/.local/share/lazyagents/claude-provider-state.json (mode 0600) and holds only a fingerprint of each value lazyagents wrote, never the token. Without a record (nothing applied yet, or a provider applied by v0.2 or earlier), no key counts as lazyagents\u0026rsquo;: x leaves them, so a proxy or Bedrock setup you wrote by hand is never cleared. Remove keys left by an old version by hand; the backups dir has the file as it was.\nWhen a token is written, the file mode is tightened to 0600.\nWhat is written to Codex # Codex keeps its own state in ~/.codex/config.toml, such as [projects.*] trust entries and hook trust hashes. lazyagents does not parse or rewrite that file. It edits only blocks between # lazyagents — managed block markers and copies every other line unchanged:\n# lazyagents — managed block start (do not edit by hand) model_provider = \u0026#34;lazyagents\u0026#34; model = \u0026#34;qwen3\u0026#34; # lazyagents: previous model = \u0026#34;gpt-5\u0026#34; # lazyagents — managed block end [projects.\u0026#34;/home/me/app\u0026#34;] trust_level = \u0026#34;trusted\u0026#34; # lazyagents — managed block start (do not edit by hand) [model_providers.lazyagents] name = \u0026#34;local\u0026#34; base_url = \u0026#34;http://localhost:11434/v1\u0026#34; env_key = \u0026#34;OLLAMA_KEY\u0026#34; wire_api = \u0026#34;responses\u0026#34; # lazyagents — managed block end The top-level keys go before the first table, and the provider table goes at the end. Your previous model and model_provider are kept as comments inside the block, and clearing puts them back. Applying and then clearing gives back the original file.\nFor Codex, a profile needs an endpoint. wireApi may only be responses, which is also Codex\u0026rsquo;s default.\nPi # Pi keeps custom providers in ~/.pi/agent/models.json. lazyagents writes one provider there, lazyagents, and makes it Pi\u0026rsquo;s default in ~/.pi/agent/settings.json (defaultProvider and defaultModel). Every other provider and setting stays as it is:\n{ \u0026#34;providers\u0026#34;: { \u0026#34;lazyagents\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;lazyagents: local\u0026#34;, \u0026#34;baseUrl\u0026#34;: \u0026#34;http://localhost:11434/v1\u0026#34;, \u0026#34;api\u0026#34;: \u0026#34;openai-completions\u0026#34;, \u0026#34;apiKey\u0026#34;: \u0026#34;$OLLAMA_KEY\u0026#34;, \u0026#34;models\u0026#34;: [{ \u0026#34;id\u0026#34;: \u0026#34;qwen3\u0026#34; }] } } } Pi only offers the models a custom provider declares, so a profile for Pi needs both an endpoint and a model. wireApi picks Pi\u0026rsquo;s API: empty or chat for OpenAI-compatible chat completions, responses for the OpenAI Responses API, anthropic for Anthropic Messages. Your previous default provider and model are remembered in lazyagents\u0026rsquo; data dir, and clearing puts them back, unless you already picked another provider in Pi. A models.json created by the profile is removed on clear.\nCrush # Crush\u0026rsquo;s config is crushrc, a Bash script of commands such as provider add and model large, run top to bottom so that later lines win. lazyagents adds one block at the end of ~/.config/crush/crushrc (or the dir CRUSH_GLOBAL_CONFIG names) and never touches your own lines:\n# lazyagents — managed block start: provider (do not edit by hand) provider add lazyagents --name \u0026#39;lazyagents: local\u0026#39; --type openai-compat --base-url \u0026#39;http://localhost:11434/v1\u0026#39; --api-key \u0026#34;$OLLAMA_KEY\u0026#34; model add \u0026#39;lazyagents/qwen3\u0026#39; --name \u0026#39;qwen3\u0026#39; model large \u0026#39;lazyagents/qwen3\u0026#39; # lazyagents — managed block end: provider Being last, the block\u0026rsquo;s model large wins over yours; clearing removes the block, so your own settings apply again with nothing to restore. Every value is single-quoted, so what a profile holds is never run as Bash, and an env var is written as \u0026quot;$VAR\u0026quot; for Crush to expand. A profile for Crush needs an endpoint and a model; wireApi is empty or chat (OpenAI-compatible) or anthropic. A crushrc lazyagents created is removed when the block goes. If you also keep a crush.json in the same folder, Crush merges both (the crushrc wins) and logs a warning.\nTokens and --env-key # Claude Code reads the token from settings.json, so lazyagents writes it there. Codex reads the token only from an environment variable named by env_key, so lazyagents never copies a token into config.toml. Pi takes either: a token goes into models.json, which lazyagents makes 0600, and an env var becomes \u0026quot;apiKey\u0026quot;: \u0026quot;$VAR\u0026quot;, which Pi resolves itself. Pi hides a custom provider\u0026rsquo;s models until it has a credential, so a profile with neither (a local Ollama, say) gets the placeholder \u0026quot;apiKey\u0026quot;: \u0026quot;lazyagents-no-key\u0026quot;, as Pi\u0026rsquo;s own docs do. For Codex, set the env var field (--env-key in the CLI) and export that variable in your shell:\nexport OLLAMA_KEY=… # in your shell profile Applying a profile that has a token but no envKey to Codex fails with an explanation, instead of writing a config that silently has no token.\nFrom the CLI # # a proxy for Claude Code; the token comes from stdin, so it stays out of shell history pass show work/proxy-token | lazyagents provider add proxy \\ --base-url https://proxy.example.com --model my-model --token - # a local model for Codex lazyagents provider add local --base-url http://localhost:11434/v1 \\ --model qwen3 --env-key OLLAMA_KEY --wire-api responses lazyagents provider apply proxy --agent claude-code lazyagents provider apply local --agent codex lazyagents provider list # tokens show as ••••, or $VAR for envKey lazyagents provider list --json # tokens left out; \u0026#34;hasToken\u0026#34;: true instead lazyagents provider clear # every installed agent lazyagents provider rm proxy Without --agent, apply and clear act on every installed agent that supports providers. If one agent fails, the others are still processed and the errors are reported together. All subcommands and flags are in the CLI reference.\nFiles # Path Contents ~/.config/lazyagents/providers.json the profile library (0600) ~/.local/share/lazyagents/claude-provider-state.json which Claude Code env keys lazyagents wrote (fingerprints only, 0600) ~/.claude/settings.json Claude Code: the env block ~/.codex/config.toml Codex: the managed blocks ~/.local/share/lazyagents/backups/ settings.json.\u0026lt;timestamp\u0026gt; and config.toml.\u0026lt;timestamp\u0026gt;, the 20 newest of each Safety # Every apply and clear first copies the agent\u0026rsquo;s file into the backups folder, with the same mode as the original. The new file is then written atomically, through a temporary file and a rename. Tokens are masked everywhere: the TUI shows token ✓, provider list shows •••• and --json leaves the field out. Only provider list --reveal prints them. Every write in the TUI goes through a confirmation that names the file. doctor has a providers section. It reports what each agent has applied, and flags a provider applied to an agent that is not installed or a config that could not be read. Limits # Only Claude Code, Codex, Pi and Crush support providers. Other agents appear in provider list only once they gain the capability. Multiline strings (\u0026quot;\u0026quot;\u0026quot; or ''') and arrays spread over several lines in Codex\u0026rsquo;s config are kept as written: a line inside them is never read as a key, a table or a lazyagents marker. A string or array that never closes is refused and the file left untouched; so are broken or nested managed blocks, and a [model_providers.lazyagents] table you wrote yourself outside a managed block. An applied profile is recognized by endpoint and model. Two profiles with the same endpoint and model look the same. ","externalUrl":null,"permalink":"/en/docs/lazyagents/guide/providers/","section":"Docs","summary":"A provider profile is a named endpoint, model and token that lazyagents writes into an agent\u0026amp;rsquo;s own config file, so you can switch Claude Code or Codex …","title":"Providers · lazyagents","type":"docs"},{"content":"A task is a goal with the criteria that say it is done, kept on this computer, that Vults can hand to an agent working in a clone of its own. The Nest\u0026rsquo;s Tasks tab lists them (The Nest).\nKeeping a task # /task in the chat keeps a task in one message, sent to no model: the goal on the first line, then a line for each criterion that says it is done, starting with -:\n/task Fix the flaky login test - passes 20 runs in a row - no sleep added On one line, done when: starts the criteria, split by ;. Its roost is the repository of the conversation\u0026rsquo;s folder. The Nest\u0026rsquo;s Tasks tab lists them, the ones still to do first, with each criterion to tick, who works on it (you, Zeca or an agent), where it stands (To do, Doing, Blocked, Done, Dropped) and what it cost once Vults starts sessions for it. New task makes one there too. With the GitHub connector on, a task in a GitHub repository gets Open an issue: on your click, your gh opens an issue with its goal and criteria, and the task links to it. Any task can link one you already have: #12 (in its GitHub repository) or the issue\u0026rsquo;s address, then Link. Tasks stay in vults.sqlite, on this computer.\nDelegating # /delegate makes a task the same way and starts it at once on the agent the router picks, and the task\u0026rsquo;s card says why. A project\u0026rsquo;s own rule picks first (/delegate default codex, or claude, typed in a conversation in that project; /delegate default none clears it); without one, Claude Code. If that agent\u0026rsquo;s plan is above 85% and the other\u0026rsquo;s is known to be under it, the task goes to the other. Once a project has 10 finished tasks of one kind (a fix, tests, docs, a feature), the agent that finished more of them there replaces the rule for that kind, though the 85% still holds. A task counts for the agent that owns it when it is marked done or dropped.\nStarting an agent on a task # Start with Claude Code works on a task in a repository. Vults makes the task a clone of its own, on a branch of its own (vults/task-12), under Vults\u0026rsquo; data folder: a --shared clone, which reads your repository\u0026rsquo;s objects but never writes them. It starts Claude Code there on the task\u0026rsquo;s goal and criteria, told to commit on that branch and never push. It runs in Claude Code\u0026rsquo;s sandbox (bubblewrap): it can\u0026rsquo;t start without it, every command still asks, and it writes only in its clone and the temp folders, never your repository, its branches, hooks or config. Commands you excluded from the sandbox in your own Claude Code settings still run outside it, each asking first. It reads your own settings, so it shows in the flock like any session and its permissions are island cards, answered by you, your rules or your policies. Two tasks run side by side without touching each other\u0026rsquo;s files. When its turn ends, Vults fetches the task\u0026rsquo;s branch, and only that branch, into your repository, without running anything of the clone\u0026rsquo;s; What it said last under the task keeps the end of its reply, and Go on continues the same session. Start fresh hands the task to a new session instead, when the old one has grown long: the old session writes a context pack (Objective, Constraints, Done, In progress, Decisions, Files, Failures, Next), Vults puts in what it knows for certain (the goal, which criteria are ticked, the files changed on the branch), and the new session starts from that pack alone. The pack stays under the task as Handoff notes; if the old session writes none, nothing is handed off and it keeps its session. Stop asks its turn to stop, waits, then ends it; only a session Vults started can be stopped, and a running task can\u0026rsquo;t be deleted. What it cost joins the task. Remove its folder brings the branch back and removes the clone; work never committed in it goes with it, so it asks twice.\nCodex # Start with Codex does the same with Codex, in its own sandbox (workspace-write: its clone and the temp folders). Codex asks for approvals on its own channel: Vults turns each into the island card its hook would send (the whole command, or an edit\u0026rsquo;s files and line counts; a call it can\u0026rsquo;t name whole is a no), so your rules, policies and phone answer it the same way, and a stopped run takes its waiting card away. On a task with a session, Hand off to Codex (or to Claude Code) is Start fresh on the other agent: the session writes its pack, and the other one starts from it.\nOther agents # Start with another agent… lists the agents installed here that speak the Agent Client Protocol: OpenCode, Qwen Code, Copilot CLI and Kimi Code. One works in the task\u0026rsquo;s clone the same way, logged in with its own account. Vults offers it no files and no terminal of its own: it uses its own tools and asks before each one that writes, runs or fetches, and each ask is an island card, answered only for that once, never with the agent\u0026rsquo;s own \u0026ldquo;always\u0026rdquo;. A change to a file outside the clone, or in its .git, a command run elsewhere, a move or a delete is a no without a card, and so is a file reached through a link that points out of the clone. OpenCode runs every tool unasked by default, and Qwen Code and Kimi Code may by your own settings: Vults starts each with a setting, for that process or session alone, that makes it ask (never written to its config), and a write or command that still runs unasked stops the turn. Their own hooks ask nothing in these sessions, so a call never shows two cards. Stop, Go on, Start fresh and Review work as with Codex; the review is Codex\u0026rsquo;s. Checked with OpenCode, and with Kimi Code 2.1.1 against a stand-in model; Qwen Code and Copilot CLI follow the same protocol but were not tried with a real account yet.\nReview # Review with Codex (or with Claude Code) has the other agent review a task\u0026rsquo;s work, in its clone, as a reviewer that wrote none of it, in a sandbox where it writes nothing: it runs the tests, reads the diff against where the branch began, checks each criterion, and lists what is wrong. Codex uses its own reviewer. When the review asks for changes, its findings go to the task\u0026rsquo;s session, which fixes them and commits, and the next round reviews again, up to three rounds. A pass ends it; a review with no verdict, or a third round still asking for changes, is yours to read. The last review stays under the task, and you still mark the task done yourself.\nCost and a daily budget # Vults counts what the agents report they cost: each turn of a session it started for a task or a /continue (Claude Code\u0026rsquo;s dollars; Codex reports tokens only, so its tasks show their tokens), and each Claude Code session it watches, from its status line, counted from the first time Vults sees it (what it cost before stays out of today). Nothing is guessed from a price list. Zeca\u0026rsquo;s own chat, summaries and sleep are not counted here, and the budget does not stop them. On a subscription the dollars are what the tokens would cost at API prices, which your plan does not charge; Vults says so when it can tell (a plan that reports its usage windows). Each task shows its cost and tokens, each roost in the Nest\u0026rsquo;s Activity → By project its cost today and in all, and the top of the Tasks tab today\u0026rsquo;s.\nThe Nest\u0026rsquo;s Activity → Cost sets a daily budget in US dollars. At 80% a notification says so; once it is spent another one does, and Vults starts no new session until the next day: no Start, handoff, review, /delegate or /continue. That is all it does. It never stops a session that is running, and never blocks or slows an agent you started yourself. Costs stay in vults.sqlite, on this computer; Clear history leaves them.\n","externalUrl":null,"permalink":"/en/docs/vults/guide/tasks/","section":"Docs","summary":"A task is a goal with the criteria that say it is done, kept on this computer, that Vults can hand to an agent working in a clone of its own. The Nest\u0026amp;rsquo;s …","title":"Tasks · Vults","type":"docs"},{"content":"Open the Chat tab, choose Chat… from the tray icon, or drop a file on the island. Zeca sits at the start of the conversation (small at the top of the island\u0026rsquo;s chat, large in the Nest\u0026rsquo;s): he thinks while the reply comes, tilts his head when he asks you something, and swallows the files you give him. Pick Claude or Codex at the top (or the API provider you set up, see below); on the island it is one pill, ● Claude Code · Default model ▾, whose menu holds the engine, the model and the effort. Its engines come in two groups, Your setup (the Claude Code and Codex CLIs, each Signed in, Installed or Not installed: Vults only looks whether the CLI and its login file are there, never reads them) and Vults harness (the API, Ready with a key or a local model), with Engines in Settings › under them. New (+ on the island) starts a fresh conversation, and so does switching to another provider; the one before stays in the list (see Conversations). Esc closes the chat from anywhere in it; the conversation is still there when you come back.\nThe chat is Zeca\u0026rsquo;s: with him turned off (Settings → Flock \u0026amp; Zeca → Zeca) there is no chat, no microphone and the talk shortcut does nothing, and files dropped on the island are not taken. See Without Zeca. On KDE Plasma the desktop keeps Ctrl+Alt+V for the app meanwhile; to use the key elsewhere, clear Hold to speak to the chat in System Settings → Shortcuts; the talk key then stays off until you give it a key there again.\nWhere it works # Before the first message, the folder (in vults ▾, at the top in the Nest, under the composer on the island) says where the conversation will work: the folder of the session in front, unless you pick another one of the sessions on the wire, or an empty folder of its own. Once the conversation starts it stays there until New.\nModel and effort # Beside the folder (on the island, in the pill at the top), Default model ▾ picks the model and how hard it thinks, for Claude and for Codex. Claude offers Opus, Sonnet and Haiku (Claude Code\u0026rsquo;s names for the latest of each); Codex lists the models your own codex offers, asked the first time you open the picker with Codex. The effort goes from Low to Max, with only the ones the chosen model takes. Default leaves either to the CLI, as your own settings set it.\nThe choice is remembered per folder and per engine: a new conversation in that folder starts with it. Like the folder, it is fixed once the conversation starts, so a reply never changes model midway; New starts another. The API chat\u0026rsquo;s model is chosen in Settings → Chat \u0026amp; voice.\nThe Nest # The arrow at the top of the island\u0026rsquo;s chat opens the Nest, Vults\u0026rsquo; own window. Its Chat tab is the same conversation, wide, with replies in full Markdown; its cards are answered there too. Memory, beside it, shows what Zeca remembers.\nContinuing a session # /continue \u0026lt;words\u0026gt; gives your words to a Claude Code or Codex session in the conversation\u0026rsquo;s folder that stopped in the last ten minutes, as its next turn: Vults resumes it here, with its own settings, in its sandbox, and it shows in the flock again with its cards. Only a session whose process is gone: one still open in its terminal is refused, so you tell it there. One at a time; /continue stop ends it. Like /task, it goes to no model of Zeca\u0026rsquo;s.\nTasks # /task keeps a task and /delegate starts one on an agent, both sent to no model: see Tasks.\nConversations # Every conversation is saved on this computer as it goes, and the list button at the top of the chat shows them, the latest used first, with the engine and folder of each. Click one to go on with it where it stopped, even after a restart: it keeps its engine, folder, model and effort, and its CLI resumes its own session (Claude Code\u0026rsquo;s session, Codex\u0026rsquo;s thread; the API chat replays what was said, with each file sent kept only as a label: a text file by its name, a document by its title, an image as \u0026ldquo;an image\u0026rdquo;, never the contents). The pencil renames one; the cross deletes it after a second click, with what was said in it.\nWhat is saved is what the chat shows: your messages and the names of the files you sent, Zeca\u0026rsquo;s replies, errors and stops; never a card\u0026rsquo;s answer (the audit log keeps those) and never an Always in this chat rule, so a conversation opened again asks as a new one would. The newest 200 are kept, in the same local vults.sqlite as the activity history; Settings → Activity → Clear history removes them all. A conversation whose folder is gone can\u0026rsquo;t be opened again.\nWhat Zeca knows # Each message carries, before your words, a \u0026lt;today\u0026gt; block with the date and time on this computer and a \u0026lt;flock\u0026gt; block with a line for each session the island shows (up to 12, then \u0026ldquo;and N more\u0026rdquo;): its agent, its project, its state and the step it is on (secrets redacted), never the command a card asks about. It goes to whoever runs the chat, the CLI\u0026rsquo;s provider or the API\u0026rsquo;s, with the message.\nWith the memory on, what Zeca remembers and recalls joins as well: see Zeca\u0026rsquo;s memory.\nWriting and reading # Enter sends, Shift+Enter starts a new line. You can type the next message while a reply comes, and send it when the reply ends. ↑ in an empty composer brings back your last message, to change it or send it again. While Zeca answers, the send button becomes Stop: it ends the turn there, and anything the turn was waiting on is a no. The reply streams in as it is written. Scroll up to read and it stays where you are, with a ↓ new text button to jump back to the end. Copy under the latest reply copies all of it, as the Markdown it was written in; an older reply shows it when the pointer is over it. A code block has its own Copy. A turn that fails says why, with Try again: it sends the same words and files once more. While a turn runs, Thinking… says so, with its seconds once it takes longer than three. In the Nest the conversation reads in one column as wide as the composer, however wide the window. Each reply is signed with Zeca and the engine that wrote it, and a code block names its language. Speaking instead of typing # Turn it on once in Settings → Chat \u0026amp; voice → Voice: download a model (Base is 60 MB and quick; Small and Large v3 Turbo understand accents and names better). A mic appears in the composer (by the send button in the Nest, by the paperclip on the island).\nClick the mic and speak. It stops by itself after about 1.5 s of quiet after your words, or when you click again; the words land in the input for you to read and fix, then Enter sends them. Silence before you start never stops it; if a pause does, click again to go on. Or hold Ctrl+Alt+V from anywhere: the chat opens and Zeca listens while you hold it (pauses never stop it); letting go turns it into text. The key can be changed in System Settings → Shortcuts. Esc while it listens throws the recording away. A recording stops by itself after a minute. Language you speak follows your system\u0026rsquo;s; pick another, or Detect it each time. A fixed language is far more reliable on short phrases. Base is weak outside English: in Portuguese use Small, or Large v3 Turbo with a GPU. Only your speech goes to whisper: a small speech detector (Silero VAD, under 1 MB) cuts the silence around it, so whisper makes no words up there. It downloads by itself while voice is on and it is missing; until then the mic only stops on a click. With a GPU (Vulkan: AMD, Intel or NVIDIA) a sentence takes a fraction of a second and a dimmed preview of your words shows as you speak; on the processor, pick Base.\nEverything happens on this computer: whisper.cpp transcribes the audio in memory, and the audio is never saved or sent anywhere. The models and the detector are checked against their known SHA-256 before they are used.\nWhat it may do # It reads files in that folder freely. Every command and every edit shows up in the conversation as a card like the island\u0026rsquo;s own: what Zeca means to do, the whole command or the file with its +/− lines, and Deny and Allow side by side with their keys (Ctrl+Alt+N / Ctrl+Alt+Y answer it from anywhere). Nothing runs until you answer, and a card nobody answers is a no. Once the conversation has read content from outside (a web page or search, a command that ran curl or git pull or gh, an MCP tool other than those of vults-zeca, the server Vults gives the chat\u0026rsquo;s Claude Code), every card after it says so in red: a page can carry words written to steer Zeca, so read what the card asks with care. Only a call that ran counts (a denied fetch read nothing), building and testing never do, and it holds for the rest of that conversation, also when it is opened again from the list. A new conversation starts clean. Always in this chat allows that exact call again (the whole command and input, not the card\u0026rsquo;s text) without a card, until the conversation ends: New, another engine, or Zeca turned off. A card that can\u0026rsquo;t name the call whole has no such button. Each time the rule answers, the chat says Allowed for this conversation and the audit log keeps it. Rules are never saved. Who answers Zeca\u0026rsquo;s cards # Settings → Chat \u0026amp; voice → Who answers Zeca\u0026rsquo;s cards chooses, for every conversation, at once:\nAsks first (the default): a card for each call, as above. Your policies: the policies you accepted answer the chat\u0026rsquo;s calls as they answer Claude Code\u0026rsquo;s on the island. A deny denies, an allow allows (Claude Code\u0026rsquo;s calls, inside the conversation\u0026rsquo;s folder), the rest asks. Full access: everything runs with no card, but a destructive command (rm, a forced push) still asks, what a policy denies is denied and what a policy asks first still asks. Once the conversation has read content from outside (a web page, a fetch, an MCP tool), its cards ask again until it ends: a page\u0026rsquo;s words could be steering it. Add deny lines to a policy for what must never run. The chat says each answer given this way (Allowed by your policy, Denied by your policy, Ran with full access), and the audit log keeps it. Its chip, beside the model (red with full access), shows the choice; from there you can only give access back. More access is given in Settings.\nFiles # Click the chat\u0026rsquo;s paperclip to pick files, or drag one over the island and drop it on the zone where Zeca waits with his bill open. Either way he swallows it, a vulture carries it across while a bar fills, and it joins your next message: a chip over the input (× takes it off). Click the input to write about it. You can send it with no text to ask what it is. Images, PDFs, code and text work best. Files are copied into the app\u0026rsquo;s inbox (up to 20 MB each) and deleted after a week; a folder, or a file over 20 MB, is refused with a line saying why.\nYour subscription, your login # The chat runs the claude or codex command you already logged into, so it uses your own subscription and Vults never reads your credentials. Both stream the reply as it is written: Claude through claude -p, Codex through one long-lived codex app-server.\nYour setup # The chat reads your own Claude Code and Codex setup, as in your terminal: your CLAUDE.md, skills, MCP servers, hooks and allow rules (a person icon at the top says so). A project\u0026rsquo;s own settings and .mcp.json servers join only in a folder you already trusted in Claude Code; its CLAUDE.md is read either way. Your allow rules apply in every folder. Cards still ask whatever your rules don\u0026rsquo;t allow, whatever defaultMode your settings set, and your hooks never make the chat show up as an agent session.\nTurn it off in Settings → Chat \u0026amp; voice → Your Claude Code and Codex setup, and Claude reads none of it; Codex then only leaves out your hooks. The switch applies to the next conversation. Worth knowing:\nclaude -p skips the trust prompt, so a repository you just cloned can\u0026rsquo;t bring rules or hooks of its own. An allow rule such as Bash(npm test:*) runs a cloned project\u0026rsquo;s test script with no card. With the switch off, Claude gets no MCP servers but Vults\u0026rsquo; own, and no saved rule skips a card. Codex\u0026rsquo;s config.toml still brings its MCP servers, and its AGENTS.md is still read. A conversation already running keeps what it read. See Safety for the rest.\nWithout a CLI: an API key, or a local model # The chat can also use a provider\u0026rsquo;s API with your own key, or a model running on your machine. In Settings → Chat \u0026amp; voice, pick the Provider, paste its key, and choose a Model; the provider\u0026rsquo;s name then appears at the top of the chat, next to Claude and Codex.\nProvider Key Notes Anthropic sk-ant-… Claude Opus 5.5 until you pick another model OpenAI sk-… Google Gemini AIza… (Google AI Studio) OpenRouter sk-or-… One key for hundreds of models Groq, DeepSeek, Mistral, xAI their own Ollama none Must be running on 127.0.0.1:11434 LM Studio none Its server must be on, at 127.0.0.1:1234 The models are listed live from the provider. Each provider keeps the model you chose for it. A key is kept in your system keyring, is never shown again, and is only sent to its own provider; Remove deletes it. Usage is billed to your account with that provider. This chat can\u0026rsquo;t run commands, edit files or look around your project. It reads what you type and the files you drop (images, text up to 512 KB, and PDFs with Anthropic), and calls tools when a question needs them: the flock tools (your sessions, the cards waiting, your plans\u0026rsquo; usage, your GitHub card), recall and memory_propose with the memory on, and the web tools with the web on. What a tool returns goes to the provider (commands with their secrets redacted); it never answers a card. Reasoning models that think out loud only show their answer: the \u0026lt;think\u0026gt; part is hidden and never sent back. A conversation keeps the provider and model it started with. With Anthropic, a request Claude declines on safety grounds is retried on another Claude model within the same call; if that one declines too, the bubble says so. The web # With Settings → Chat \u0026amp; voice → Web for Zeca on, the API chat can search the web and read pages. Searches go to Brave Search or Tavily with your own key (kept in the keyring), or to a SearXNG you run. Off, the model is never offered either.\nA page is untrusted, so the chat\u0026rsquo;s model never sees it: a separate call to your API provider, with no tools and told the page is data, reads it for the question, and only that answer comes back. A search\u0026rsquo;s titles and snippets do reach the model, marked untrusted. Pages are read only from public addresses. Each search and page is a request on your behalf with a daily cap (Safety), and the conversation then counts as having read from outside.\n","externalUrl":null,"permalink":"/en/docs/vults/guide/chat/","section":"Docs","summary":"Open the Chat tab, choose Chat… from the tray icon, or drop a file on the island. Zeca sits at the start of the conversation (small at the top of the …","title":"Chatting with Zeca · Vults","type":"docs"},{"content":"A hook is a shell command that an agent runs when something happens: a session starts, a tool is about to run, a turn ends. lazyagents keeps your hooks in a library and installs them in the agents that support hooks. You can write them by hand or import them from repositories that ship Claude Code plugin hooks. Which agents support hooks, and which events each one fires, is in the agent support reference.\nConcepts # Library: ~/.local/share/lazyagents/hooks/, one JSON file per hook, named \u0026lt;name\u0026gt;.json. You can edit these files by hand. Entry: a library hook has a name and one or more commands. Each command has an event, an optional matcher and a command. An entry imported from a plugin can hold many commands across several events, and it installs and uninstalls as a unit. You can still switch single commands off. Identity: an installed hook is recognized by the combination of event, matcher and command. lazyagents adds no marker to the agent\u0026rsquo;s file. Anything in the file that does not match a library entry is a foreign hook, and lazyagents only counts it, never edits or removes it. Reach: a command installs only in agents that fire its event. When an entry mixes events, each agent gets the commands it can run. An entry in the library:\n{ \u0026#34;name\u0026#34;: \u0026#34;notify\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;desktop notification\u0026#34;, \u0026#34;hooks\u0026#34;: [ { \u0026#34;event\u0026#34;: \u0026#34;Stop\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;notify-send \\\u0026#34;Claude finished\\\u0026#34;\u0026#34; } ] } Other fields you may meet: matcher, a filter such as a tool name for PreToolUse; timeout in seconds; async: true to run without blocking the turn; and off, the indexes of commands switched off. Imported entries also carry source, where they came from, and files, the folder with their scripts. The name must match the file name and may use letters, digits, - and _, up to 40 characters.\nIn the TUI # The Hooks tab is a hook × agent matrix: ● installed, ◐ partial (some commands missing), ○ not installed, – the agent fires none of the hook\u0026rsquo;s events. The full key list is in the key reference.\nInstall or uninstall per agent # Pick the agent column with ←/→ and press space. The confirmation shows event → command and the file that will be rewritten, plus any warning about that agent. Pressing the key again uninstalls. a installs the hook in every installed agent that fires one of its events, and x uninstalls it from all of them. d deletes the hook from the library, but agents where it is installed keep it. Uninstall first if you want it gone everywhere. Scripts of an imported hook are deleted along with it, unless another entry still uses them.\nSwitch single commands of a pack # enter opens the command list of the selected entry. space or enter switches a command on or off, and esc goes back. If the entry is installed somewhere, the change reaches those agents right away, after a confirmation. If it is not installed anywhere, only the library changes.\nRead and edit commands and scripts # v opens a full-screen reader with the command and the scripts it references (.sh, .py, .js and similar). ←/→ switches between them, and e edits the current one in $EDITOR. Before saving, a confirmation shows the text before and after. Saving a command also updates every agent where it is installed, and rolls back if one of them fails. Saving a script backs it up first and refuses if the file changed on disk after you opened it.\nImport hooks from a repository # Repositories with Claude Code plugins declare hooks in \u0026lt;plugin\u0026gt;/hooks/hooks.json. When you install from such a source with i in the Skills tab, those hook files show up in the same picker as the skills. They start unchecked, because installing a hook means running a third-party command on every event.\nOn import:\nthe folders the commands reference through ${CLAUDE_PLUGIN_ROOT} are copied into the library with their layout, together with hooks/. Other folders of the plugin are not copied. each command gets export CLAUDE_PLUGIN_ROOT='\u0026lt;the copy\u0026gt;'; in front, and ${CLAUDE_PLUGIN_ROOT} becomes $CLAUDE_PLUGIN_ROOT, since Claude Code rejects the braced form outside its own plugins. The author\u0026rsquo;s quoting is kept. one library entry is created per plugin, holding all of its commands. The import is all or nothing, and it refuses a name that is already in the library. The imported commands target the Claude Code protocol. Other agents may send a different payload on stdin, so the install confirmation reminds you where the hook came from.\nCodex: hooks switch and trust # Codex keeps hooks in ~/.codex/hooks.json, and two more things in config.toml: [features] hooks = true, and a trusted_hash per command. The hash records that you agreed to let that command run. lazyagents installs the hook but never writes either of these, because writing them would approve a command on your behalf. Instead it warns you, in the confirmation, in hooks list and in doctor:\n\u0026ldquo;hooks are off in Codex: set hooks = true under [features] in config.toml\u0026rdquo; \u0026ldquo;a new hook only runs after you confirm trust in Codex itself\u0026rdquo; Claude Code has no trust step: whatever is in settings.json runs.\nCrush # Crush runs only PreToolUse hooks, and reads their output in Claude Code\u0026rsquo;s format too. lazyagents installs them as hook add lines in a block at the end of ~/.config/crush/crushrc, one per hook, named lazyagents-\u0026lt;id\u0026gt; so each one is removed alone; the rest of the file is never touched. hooks list also shows the hooks in Crush\u0026rsquo;s crush.json; lazyagents never edits that file, so uninstalling one of those says so instead of pretending. Hooks your crushrc defines outside that block are Bash that only running the script would reveal, so they are not listed. A command or matcher that spans several lines cannot be installed in Crush: put it in a script and install the script.\nFrom the CLI # lazyagents hooks add notify --event Stop --command \u0026#39;notify-send \u0026#34;Claude finished\u0026#34;\u0026#39; --desc \u0026#34;desktop notification\u0026#34; lazyagents hooks add guard --event PreToolUse --matcher Bash --command ~/bin/check-bash.sh --timeout 10 lazyagents hooks enable notify # every installed agent that fires Stop lazyagents hooks enable guard --agent codex lazyagents hooks list # where each hook is installed, and foreign counts lazyagents hooks list --json lazyagents hooks disable guard lazyagents hooks rm guard lazyagents install owner/repo --hooks # also import the plugin hooks of a source hooks add warns when the command\u0026rsquo;s executable is not in PATH or not executable, since the hook would otherwise fail silently when the event fires. All subcommands and flags are in the CLI reference. Importing is part of install.\nFiles # Path Contents ~/.local/share/lazyagents/hooks/\u0026lt;name\u0026gt;.json library entries (0600) ~/.local/share/lazyagents/hooks/\u0026lt;plugin\u0026gt;/ scripts copied from an imported plugin ~/.claude/settings.json Claude Code: the hooks key ~/.codex/hooks.json Codex hooks ~/.local/share/lazyagents/backups/ a copy of the agent file before each write, the 20 newest per file Safety # Every install, uninstall and edit backs up the agent\u0026rsquo;s file first and writes it atomically. Hook groups lazyagents does not own are written back byte for byte, including fields it does not know. The one exception is a group that mixes one of your commands with a library command: when uninstalling, that group is rewritten without the library command. Every write in the TUI goes through a confirmation that shows the command and the file. Deleting a hook only removes scripts inside the hooks library, never anything outside it. doctor has a hooks section. It reports commands missing from PATH, library files that are invalid or whose name does not match the file, per-agent counts (from lazyagents, foreign, partial) and each agent\u0026rsquo;s note. Limits # Only Claude Code, Codex and Crush support hooks; Codex fires fewer events and Crush only PreToolUse. See the events table. Only command hooks are managed. Other hook types in an agent\u0026rsquo;s file are left alone. Imported scripts may assume a POSIX shell or tools the plugin author had installed. doctor checks the executable, not what the script needs. ","externalUrl":null,"permalink":"/en/docs/lazyagents/guide/hooks/","section":"Docs","summary":"A hook is a shell command that an agent runs when something happens: a session starts, a tool is about to run, a turn ends. lazyagents keeps your hooks in a …","title":"Hooks · lazyagents","type":"docs"},{"content":"The Agents tab is a read-only overview of every agent lazyagents knows: whether it is installed here, which version, and what lazyagents can manage in it. Use it to check why an agent is missing from another tab, or where its skills live.\nWhat each agent supports (skill dirs, providers, hooks, limits, hook events) is generated from the code in the agent support reference. This page explains how to read the tab.\nThe table # One row per agent, installed ones first:\nColumn Meaning agent name, in the agent\u0026rsquo;s color; ○ and dimmed when not installed version first line of \u0026lt;cli\u0026gt; --version skills skills enabled in the agent; — when the agent has no local skills dir sessions sessions found on disk for that agent hooks · provider · usage ✓ when lazyagents can manage hooks, apply a provider profile, or read usage for that agent The counter on the tab is the number of installed agents. On a narrow terminal (under 75 columns) the three capability columns move into the detail pane as a manages line. On a wide one the detail pane sits beside the table instead of below it.\nThe detail pane # Below the table, for the selected agent:\nskills in: the dir where lazyagents creates skill symlinks for this agent. reads too: other skill dirs the agent loads. A skill there shows in the agent even if lazyagents did not enable it. detection: the binary path, or the config dir when the binary is not in PATH. (shared) after a dir read by several agents (~/.agents/skills). lazyagents links a skill there only while it is enabled for every installed agent that reads it; see shared directories. An agent that is not installed only shows a hint to install its CLI and reopen lazyagents.\nHow detection works # An agent counts as installed when its binary is in PATH or its config dir exists (for example ~/.claude or ~/.codex). With only the config dir, the detail says the binary is not in PATH: skills and sessions still work, but resuming a session needs the CLI.\nThe version comes from running \u0026lt;cli\u0026gt; --version once, in the background, when the TUI starts (each CLI has up to 6 seconds). The CLI runs it only for the commands that need it, such as doctor.\nClaude Desktop is detected by its config dir only. Its skills and chats live in the claude.ai account, so there is nothing local to manage.\nHermes Agent enables skills in ~/.hermes/skills (%LOCALAPPDATA%\\hermes\\skills on Windows), or in the profile picked with hermes profile use, or in HERMES_HOME. It also reads the dirs its config.yaml lists under skills.create_dir and skills.external_dirs, resolved as Hermes does (~ and ${VAR} expanded, relative to the Hermes home); a dir that does not exist is left out. Listing ~/.agents/skills there makes Hermes one of the agents that share that dir.\nPi keeps everything in ~/.pi/agent, or in PI_CODING_AGENT_DIR when set. Other tools also ship a binary named pi, so without that dir the binary only counts when pi --version prints a bare version.\nAn agent whose files were moved with its own variable (CLAUDE_CONFIG_DIR, CODEX_HOME, XDG_DATA_HOME for OpenCode…) is found there too, as long as lazyagents runs with the same environment. The list is in the config overrides reference.\nCrush counts as installed with its binary, ~/.config/crush or ~/.local/share/crush. It loads skills from ~/.config/crush/skills (where lazyagents enables them), ~/.config/agents/skills, ~/.claude/skills and ~/.agents/skills. With CRUSH_SKILLS_DIR set, Crush loads skills from that dir only, and lazyagents enables them there.\nDetection runs once per launch. After installing a CLI, reopen lazyagents.\nFrom the CLI, lazyagents doctor prints the same detection in its first section (--json includes the version).\nKeys # ↑/↓ selects an agent and shift+↑/↓ scrolls the detail. The full list is in the key reference.\nAsking for a new agent # An agent is supported through an adapter in internal/agent, the only place that knows its paths and file formats. To ask for one, open an issue with the agent\u0026rsquo;s name, where it keeps skills, sessions and config, and links to its docs. To write the adapter yourself, see adding an agent.\n","externalUrl":null,"permalink":"/en/docs/lazyagents/guide/agents/","section":"Docs","summary":"The Agents tab is a read-only overview of every agent lazyagents knows: whether it is installed here, which version, and what lazyagents can manage in it. Use …","title":"Agents · lazyagents","type":"docs"},{"content":"Zeca can remember you, your projects and what your agents did. It is off until you turn it on, is kept on this computer, and only you write it.\nTurning it on # Turn it on in Settings → Chat \u0026amp; voice → Memory → Zeca\u0026rsquo;s memory. The app then keeps a journal in the local vults.sqlite for 90 days: each agent turn\u0026rsquo;s time, steps and changes, each card\u0026rsquo;s answer with its command or path, and the lines of Zeca\u0026rsquo;s chat, secrets redacted before anything is written. The section\u0026rsquo;s other switches appear once this one is on.\nOff, nothing of the memory reaches a model (no facts, no recall, no journal): the Memory beside the Nest\u0026rsquo;s Chat shows only Zeca\u0026rsquo;s memory is off…, By project shows no memory facts, and /remember is refused. Turning it off stops the writing and keeps what was written.\nWhat Zeca remembers is a git repository of small Markdown files in ~/.local/share/vults/memory/, each change a commit. Settings → Activity → Clear history removes the journal and what derives from it, but not that folder (what it removes): to erase his facts, delete it.\nWhat joins a conversation # When a conversation starts, his memory joins it after his instructions: who you are, what he learned, how to speak to you, and the memory of the project it works in. It stays fixed for the whole conversation; a fact added meanwhile joins the next one. Turning the memory off doesn\u0026rsquo;t take it from a conversation already running; reopened later from the list, it starts without his facts.\nBefore each message, the app searches the journal and the days\u0026rsquo; summaries for what your words are about, on this computer, and up to three passages join the message as the \u0026lt;memories\u0026gt; block, context and never instructions. Today\u0026rsquo;s chat is left out, and so is anything a conversation said after it read content from outside. The passages go to the chat\u0026rsquo;s provider with your message. The API chat can also ask for more with its recall tool, and suggest a fact with memory_propose, which only writes a proposal.\nWriting his memory # /remember \u0026lt;fact\u0026gt; in the chat, with the memory on, saves a fact to what Zeca learned; /remember project: \u0026lt;fact\u0026gt; to the project the conversation is in. /forget \u0026lt;words\u0026gt; removes the one fact that holds them (with several, it lists them). What Zeca proposes waits in the Memory beside the Nest\u0026rsquo;s Chat, with where it goes (a project by its name and folder) and the change as a diff: Remember saves it, Turn down drops it. The Memory also lists what he remembers, file by file with how full each is, and the history: who wrote each change (you, or Zeca with your yes) and Undo, which takes it back in a new change (refused when a later change touched the same file; never offered on one Vults did not write). A fact never gets in twice: /remember and every proposal are refused when another fact already says it, case, spaces and the final stop aside, and the same fact decided in the last month is not proposed again. A proposal passes the memory\u0026rsquo;s scan (no hidden characters, no words that address a model, no key shapes).\nWhat he does on his own # Each is a switch under Settings → Chat \u0026amp; voice → Memory, off until you turn it on. Haiku is Claude Code\u0026rsquo;s smallest model, through your claude and subscription, with no tools and none of your settings; each call goes through the network gate and the local trace (Safety).\nFeature Switch What it reads Model Requests a day A summary of each past day (2,000 bytes at most) A summary of each day One day of the journal, only days since you turned it on Haiku 8 Up to three facts to remember, when you start or open another conversation Suggest what to remember The conversation you left, redacted, with at least 2 of your messages and no outside content Haiku 20 Merges, and a debrief shown as While you were away Let Zeca sleep on it What he remembers about you and across projects, and the last 7 days\u0026rsquo; summaries Haiku 4 Recall by meaning, not only by words Recall by meaning Each summary, and each message you send nomic-embed-text in an Ollama on 127.0.0.1 none leave the computer The summary runs in an hourly pass: each past day without a summary, up to three a pass (it catches up after days away), three tries per day while the app runs. The embeddings run in that pass, for each summary without one, and before each chat message (ollama pull nomic-embed-text first).\nThe sleep comes once a day, from two in the morning and while nothing is working. Even without its switch, it proposes removing a fact that repeats another in its section, with no model. With it, it also proposes merges and writes the debrief, in your language, marked as his reading of the summaries. Every change waits for your yes.\nProject memory # The Nest\u0026rsquo;s Activity → By project has a card for each repository a session works in: its folders and branches, its sessions, your pull requests and checks (from the GitHub card), and what Zeca remembers of the project. It reads only this computer. A conversation that starts in a repository brings that memory along.\nSharing AGENTS.md with Claude # When a repository has an AGENTS.md (the project memory Codex reads) that its CLAUDE.md does not import, its card offers Share it with Claude…. It shows the change (@AGENTS.md added at the end of CLAUDE.md, @../AGENTS.md at the end of .claude/CLAUDE.md, or a new CLAUDE.md) and writes it only when you click Apply, over the very file you saw, after a dated backup in ~/.local/share/vults/backups/. Then Claude Code and Codex read the same project memory.\nThrough Claude Code # The chat hands Claude Code Zeca\u0026rsquo;s own MCP server, vults-zeca, with --mcp-config (your Claude config is never touched): flock_list (your sessions now), recall and journal_day (his journal), roost_memory (the project\u0026rsquo;s memory) and memory_propose; only flock_list with the memory off. Claude Code asks with a card before each call your rules don\u0026rsquo;t allow, and a proposal still waits in the Memory tab. These calls do not mark the conversation as having read outside content. The server is this app run again (vults --zeca-mcp), and sends nothing anywhere: flock_list reads a redacted snapshot of the island\u0026rsquo;s sessions the app keeps while it runs and removes when it quits, and the journal calls read vults.sqlite without ever upgrading it. Codex reads MCP servers only from its own config, so it does not get them.\n","externalUrl":null,"permalink":"/en/docs/vults/guide/memory/","section":"Docs","summary":"Zeca can remember you, your projects and what your agents did. It is off until you turn it on, is kept on this computer, and only you write it.\nTurning it on # …","title":"Zeca's memory · Vults","type":"docs"},{"content":"Connectors put news from outside services on the island. Each one is off until you switch it on in Settings → Connectors.\nGitHub # Every five minutes it checks, and every minute while CI checks are still running:\nyour open pull requests: checks failed or passed, approved, changes requested; pull requests where your review is requested; the checks on the default branch of the ten repositories you own that were pushed most recently, and the latest release of each. The same single query also brings what Room\u0026rsquo;s Connections tab shows of each row: a pull request\u0026rsquo;s branch, size, reviewers and last update, and each check by name (The Nest). Releases are only listed there: never an alert, never on the island.\nIt uses the GitHub CLI you are already logged into (gh auth login), so Vults never sees a token. The first check only learns how things are; alerts start with the next change. Click an alert to open it on GitHub, × to dismiss it. Passing checks replace a failure, and a newer review decision the older one.\nAfter an error it tries again in 15 minutes for a GitHub rate limit, 10 when gh is missing or signed out, and 4 to 15 (doubling) otherwise. Opening the island or the card checks sooner when the last check is over a minute old, except under a rate limit. GitHub is checked at most 3,000 times a day; past that, Settings → Connectors says Budget spent. A repository GitHub cannot answer for (say, behind SAML sign-in) leaves the rest showing.\nThe GitHub card # Once GitHub has answered, the open island\u0026rsquo;s Connections tab shows its card, with a chip for GitHub and a card per row:\nYour pull requests, most recently updated first: a dot for the checks (green passing, amber running, red failing) and the review (Approved or Changes requested); Waiting for your review; Default branches: the latest commit on each, with how its checks went. Repositories without checks are left out. Click a row to open it on GitHub. When a pull request is merged or closed, or a review request withdrawn, its alerts go with it.\nWith Zeca on, a row can ask him about it:\nReview with Zeca, on a review waiting for you: he reads the diff and tells you the risks before you approve; Ask Zeca why, on failing checks: he reads only the failed step\u0026rsquo;s log and tells you what broke and where. Each opens the chat with the question written in the composer. Nothing is asked until you send it, so you can change it first.\nSquash and merge sits on your own pull request once it is approved and its checks pass. The first click only asks, under the row, what the second will do; Merge then squashes it into its base branch and deletes its branch, through your gh login, and the card asks GitHub again, so it leaves with the next answer. It merges only the commit the card showed green: if someone pushed since, GitHub refuses. If GitHub refuses (a branch rule, a conflict), the row says why. Each merge writes a line in the local trace, and nothing else is merged on its own.\n✓ marks a row seen: it leaves the island, and the tab\u0026rsquo;s count, until it changes (its checks, its review, or a branch\u0026rsquo;s latest commit), and stays in the Nest\u0026rsquo;s Connections. Mark all seen clears the card at once. Which rows you marked is kept on this computer only; GitHub never hears of it.\nWhen a check fails, the card keeps what it last saw and shows when it last updated, and the error. While a permission card waits, the Connections tab is dimmed until it is answered.\nWhat the last check saw (the titles, links and check states the card shows, nothing else) is kept in connectors/github.json in the app\u0026rsquo;s data folder (~/.local/share/vults/ on Linux), so a restart does not alert again about old news.\nWant another service? See Adding a connector.\n","externalUrl":null,"permalink":"/en/docs/vults/guide/connectors/","section":"Docs","summary":"Connectors put news from outside services on the island. Each one is off until you switch it on in Settings → Connectors.\nGitHub # Every five minutes it checks, …","title":"Connectors · Vults","type":"docs"},{"content":"A plugin adds your own tab, command and diagnostics to lazyagents without touching its code. It is any executable, in any language, that talks JSON Lines over stdin and stdout. This page covers installing and using plugins and a short walkthrough for writing one; the full wire contract is in the plugin protocol.\nInstalling a plugin # Put the executable in the plugins dir and make it executable:\nmkdir -p ~/.config/lazyagents/plugins cp my-plugin ~/.config/lazyagents/plugins/ chmod +x ~/.config/lazyagents/plugins/my-plugin The dir is $XDG_CONFIG_HOME/lazyagents/plugins/ when that variable is set; see configuration for the macOS and Windows locations. On Windows, .exe files count as executable.\nThe file name without its extension is the plugin id. It must:\nmatch ^[a-z0-9][a-z0-9_-]{0,31}$ (lowercase letters, digits, - and _, up to 32 characters); not repeat a built-in tab or command (skills, sessions, list, doctor, help…); be unique: hello and hello.sh in the same dir collide. A file that breaks a rule is skipped with a notice (printed on exit in the TUI, before the output in the CLI) and listed in lazyagents doctor.\nWhat a plugin becomes # Where What TUI a tab titled by the plugin, placed after the built-in working tabs and before Usage and Agents Command palette its commands, as :\u0026lt;id\u0026gt; \u0026lt;command\u0026gt; Help (?) its own key list CLI lazyagents \u0026lt;id\u0026gt; [args…]: runs the executable with those arguments, stdio and exit code passed through lazyagents doctor a line in the plugins section with the result of its handshake and, if it asks for it, its own check The process for the tab starts with the TUI and stops when the TUI closes, together with any process it started.\nConfiguring a plugin # A top-level section named after the id in config.yaml is handed to the plugin whole when it starts:\nhello: greeting: hi To move or hide the tab, use the tui: section like any other tab (configuration). A hidden plugin is not even started, but lazyagents \u0026lt;id\u0026gt; still works:\ntui: hidden: [hello] Trust and safety # A plugin runs with your user\u0026rsquo;s permissions, like any program you install. lazyagents does not sandbox it, so only install plugins you trust.\nWhat lazyagents does guard against is a plugin breaking the TUI:\nEverything it sends is untrusted. The tab text keeps colors but loses any other terminal escape sequence (cursor movement, screen clearing, titles), and is clipped to the tab size. Titles, help and command names are shortened and cleaned the same way. A plugin that fails to answer within 3 seconds, sends invalid output or exits leaves its tab in an error state showing the reason and the end of its stderr. Press r (or :reload) to restart it. The rest of lazyagents keeps working. A plugin that stops reading its input is shut down. Writing a plugin # The shortest working plugin is the shell script in examples/plugins/hello. It shows the three ways lazyagents runs a plugin:\n\u0026lt;bin\u0026gt; serve runs the tab. The plugin reads one init line (size, paths, theme colors, its config section), answers with a manifest line (title, help, palette commands, whether it has a doctor), then writes frame lines with the whole tab text whenever it changes. Keys, resizes and palette commands arrive as more lines on stdin. When stdin closes, it exits. \u0026lt;bin\u0026gt; doctor runs only when the manifest says \u0026quot;doctor\u0026quot;: true. Print what you found; a non-zero exit counts as a problem. It runs without a terminal and its output is shown once it ends; after 30 s the check fails and the plugin and its children are stopped. \u0026lt;bin\u0026gt; \u0026lt;anything else\u0026gt; is lazyagents \u0026lt;id\u0026gt; … passed through. Try the example:\ncp examples/plugins/hello ~/.config/lazyagents/plugins/hello chmod +x ~/.config/lazyagents/plugins/hello lazyagents doctor # \u0026#34;plugins\u0026#34; section: handshake and hello\u0026#39;s own doctor lazyagents hello a b # pass-through lazyagents # the Hello tab echoes each key Tips:\nstdout is protocol only. Write logs to stderr; lazyagents keeps the last 4 KiB and shows them when the plugin fails. Every run gets LAZYAGENTS_HOME, LAZYAGENTS_CONFIG_DIR, LAZYAGENTS_DATA_DIR, LAZYAGENTS_LIBRARY_DIR and LAZYAGENTS_PROTOCOL in its environment. To open an editor or an agent CLI from the tab, ask the host with an exec message instead of spawning it yourself: lazyagents suspends the TUI and hands over the terminal. To debug without the TUI, pipe lines in by hand: printf '{\u0026quot;type\u0026quot;:\u0026quot;init\u0026quot;,\u0026quot;protocol\u0026quot;:1,\u0026quot;width\u0026quot;:80,\u0026quot;height\u0026quot;:24}\\n' | ./my-plugin serve. Message fields, limits and the lifecycle are specified in the plugin protocol. The protocol is versioned: a breaking change bumps protocol in init.\n","externalUrl":null,"permalink":"/en/docs/lazyagents/guide/plugins/","section":"Docs","summary":"A plugin adds your own tab, command and diagnostics to lazyagents without touching its code. It is any executable, in any language, that talks JSON Lines over …","title":"Plugins · lazyagents","type":"docs"},{"content":"The Nest\u0026rsquo;s Activity → Week shows what your agents did: a week\u0026rsquo;s recap and the last year as a grid; Settings → Activity shows the same page. It is counted on this computer, from a local history that Vults keeps as your agents work.\nWhat a turn is # A turn is what one prompt set going, until the agent stops. For each one Vults keeps when it ran, the agent, the project\u0026rsquo;s folder name and counts (steps, commands, lines changed, your answers). Never a prompt, a reply, a command, a file name or a path, and nothing leaves this computer (Safety).\nThe week # The card shows one week, Monday to Sunday, by your local days: this week first, and ‹ › to go through the weeks the history keeps (12).\nTime with your agents: turns running side by side count once. Turns and their steps, lines added and removed and the files they touched, commands, answers (allowed, denied, questions answered), and turns failed. Time per day as bars, the agent and project with the most turns, the busiest day and the longest turn. Sharing the week # Save as image… under the week draws it as a picture (1080 × 1350), with Zeca on his wire. Hide project names leaves the project out of it. The desktop\u0026rsquo;s Save as dialog asks where (on Windows it goes to your Pictures folder), and nothing is uploaded.\nOn Monday morning # From 08:00 on Monday, the island tells last week in one line (Last week: 41 turns, 6 h 20 min with your agents, most on site.), once, if your agents had a turn that week. It shows as the line after a screen lock does in each preset (The island), never in Paused. Open Activity shows the whole week in Settings; it and × both mark it read.\nThe grid # The last year, a column per week and a row per day, Monday at the top. A day\u0026rsquo;s shade follows its time with your agents, in four steps by the quartiles of your active days. Hover a day for its turns and time.\nThe GitHub tab shows your GitHub contribution calendar instead, with GitHub\u0026rsquo;s own levels and its weeks from Sunday, as on your profile. It needs the GitHub connector on (Settings → Connectors) and is asked through gh once when the tab opens, then kept an hour in memory, never on disk.\nThe history # Keep a history is on from the start. Off, nothing new is kept and what is there stays. Clear history… asks once, then removes the turns and days, the audit log of your answers, the local trace of requests (Zeca\u0026rsquo;s turns and every request made on your behalf), Zeca\u0026rsquo;s saved conversations, his journal, the day summaries and their recall data, the memory proposals and the sleep\u0026rsquo;s runs, and the snapshot of the flock kept for Zeca\u0026rsquo;s MCP server, in one go, leaving one line in the audit log: that you cleared it, and when. What stays: what Zeca remembers (his memory files, Settings → Chat \u0026amp; voice → Memory), the policies you accepted, your paired phones, your tasks and what the agents cost (Safety). The history lives in one local database, vults.sqlite (each turn for 12 weeks, each day\u0026rsquo;s totals for a year), in ~/.local/share/vults/ (%LOCALAPPDATA%\\Vults\\ on Windows). See Settings and files. ","externalUrl":null,"permalink":"/en/docs/vults/guide/activity/","section":"Docs","summary":"The Nest\u0026amp;rsquo;s Activity → Week shows what your agents did: a week\u0026amp;rsquo;s recap and the last year as a grid; Settings → Activity shows the same page. It is …","title":"Activity · Vults","type":"docs"},{"content":"Everything is optional: lazyagents runs with no config file. When you want to change something, create config.yaml in the config dir.\nWhere files live # Linux macOS Windows Config dir $XDG_CONFIG_HOME/lazyagents, default ~/.config/lazyagents ~/Library/Application Support/lazyagents %AppData%\\lazyagents Data dir $XDG_DATA_HOME/lazyagents, default ~/.local/share/lazyagents same as Linux same as Linux The rest of the docs use the Linux paths.\nPath Contents ~/.config/lazyagents/config.yaml this configuration ~/.config/lazyagents/providers.json provider profiles (mode 0600, may hold tokens) ~/.config/lazyagents/plugins/ external plugins, one executable each (plugins guide) ~/.config/lazyagents/themes/ your themes, \u0026lt;id\u0026gt;.yaml (themes) ~/.local/share/lazyagents/skills/ the skill library (movable, see libraryDir) ~/.local/share/lazyagents/profiles.json skill activation profiles ~/.local/share/lazyagents/hooks/ the hook library: one JSON per hook, plus imported scripts ~/.local/share/lazyagents/session-aliases.json session aliases ~/.local/share/lazyagents/usage-cache.json subscription limits cache ~/.local/share/lazyagents/claude-provider-state.json which Claude Code env keys lazyagents wrote (fingerprints, never the values), so it only removes its own ~/.local/share/lazyagents/pi-provider-state.json Pi\u0026rsquo;s default provider and model before a profile replaced them, restored on clear (no secret) ~/.local/share/lazyagents/transcript-index.gob transcript index; disposable, deleting it only makes the next load read everything again ~/.local/share/lazyagents/backups/ backups of skills, sessions and agent config files ~/.local/share/lazyagents/exports/ exported transcripts What lazyagents writes outside these dirs, in the agents\u0026rsquo; own files, is listed in Safety and data.\nconfig.yaml # theme: sp-night-garoa # a built-in theme or one of yours libraryDir: ~/.agents/skills # where the skill library lives tui: splash: true # false skips the start screen splashSeconds: 2 # how long it stays (0 skips; at most 10) startTab: sessions # tab the app opens on tabs: [sessions, skills, usage] # order; unlisted tabs follow in the default order hidden: [hooks, providers] # off the tab bar and the palette usage: period: 30d # today | 7d | 30d | 90d | all view: projects # daily | agents | projects | models hello: # a plugin\u0026#39;s section, keyed by its id greeting: hi lazyagents rewrites this file only when a command changes a setting it owns (today migrate-library, which sets libraryDir). Comments, key order and sections it does not know survive the rewrite.\nTop-level keys # Key Default Effect theme sp-night The TUI theme: a built-in id or the id of a file in themes/. Read at startup. An unknown id falls back to sp-night with a warning. The ids used before v0.3 (noite, garoa, jaragua) still work libraryDir ~/.local/share/lazyagents/skills Where the skill library lives. Prefer lazyagents migrate-library \u0026lt;dir\u0026gt;, which moves the skills and recreates the symlinks (skills guide) Only these two keys are global. Every other top-level key is a section owned by one module or plugin, named after its id.\ntui: tab layout # Key Effect splash false skips the start screen splashSeconds How long the start screen stays, at most 10; 0 skips it startTab The tab the app opens on tabs Tab order. Tabs you leave out follow in the default order hidden Tabs removed from the tab bar and the palette Tab ids are the ones the palette shows: skills, sessions, providers, hooks, usage, agents, plus each plugin\u0026rsquo;s id. A hidden built-in tab keeps loading in the background, because Usage and Agents are fed by the others. A hidden plugin is not started at all, but its CLI command still works.\nusage # The filters the Usage tab opens with: period and view. See the usage guide.\nPlugin sections # A plugin receives the whole section named after its id when it starts. What goes in it is up to the plugin.\nInvalid values never block startup # A config.yaml that does not parse is ignored: lazyagents starts with the defaults and prints a warning. An unknown tab id, theme or filter value becomes a warning and falls back to the default. Warnings print on stderr when the TUI exits. Config, theme and plugin warnings also print before the output of a CLI command; tui: layout warnings only show with the TUI, since the CLI has no tabs. Upgrading from config.json # Development builds before v0.1.0 kept the config in config.json. On the first launch lazyagents migrates it to config.yaml and keeps the original as config.json.migrated.\n","externalUrl":null,"permalink":"/en/docs/lazyagents/configuration/","section":"Docs","summary":"Everything is optional: lazyagents runs with no config file. When you want to change something, create config.yaml in the config dir.\nWhere files live # Linux …","title":"Configuration · lazyagents","type":"docs"},{"content":"Claude Code, Codex, Antigravity, OpenCode, Qwen Code, Copilot CLI, Cursor, Factory Droid and Kimi Code are built in (Settings → Agents). Any other tool can put its sessions on the wire too, if it runs a command on its events and sends Claude Code\u0026rsquo;s hook JSON on stdin (the format most agent tools copy). For tools that run plugins instead (Pi), a short plugin does the same; recipes are below.\nPoint the tool\u0026rsquo;s hooks at the relay with a name of your choice (on Windows the relay is in %LOCALAPPDATA%\\Vults\\bin\\):\n~/.local/share/vults/bin/vults-hook --agent my-tool SessionStart The name is 1 to 24 characters of a-z, 0-9 and -, and not one the wire has a kind for (claude, codex, copilot, opencode, qwen and other), nor gemini: the wire has no kind for it any more, but Gemini CLI\u0026rsquo;s old hooks may still be installed, so the name stays taken and they send nothing. The built-ins Vults sends by name (antigravity, cursor, droid, kimi) use those names themselves, so they are not reserved; a tool by any name gets no card, a built-in sent by name included. With a name that breaks these rules the hook sends nothing and exits at once. The event name comes from hook_event_name in the JSON, or from the last argument. Claude Code\u0026rsquo;s event names are read, and Cursor\u0026rsquo;s too. Each session needs an id: session_id, or conversation_id, sessionId or conversationId. An event without one is dropped. Sessions show under the tool\u0026rsquo;s name, in violet. The fields it reads are in Hook protocol. The relay always exits 0 and prints nothing for these tools, but a relay that is missing makes the command fail: end every hook command in || exit 0, or a tool that treats a failed hook as a \u0026ldquo;no\u0026rdquo; stops working when the app is not installed.\nWhat each agent can do # Settings → Agents, under What each agent can do, shows each built-in agent: whether its CLI is on the PATH and its version, whether its hooks are in and current (and, for Codex, trusted), and what it can do with Vults. Check again asks anew; the answer is kept ten minutes.\nPermissions Questions Diffs Stopping a turn Sandbox Started from Vults Claude Code a card a card lines, numbered at once, when Vults started it bubblewrap, if on yes Codex a card its terminal lines at once, when Vults started it its sandbox mode yes Antigravity its terminal its terminal none not from Vults yet none not yet OpenCode a card a card (OpenCode 1) lines at once, when Vults started it none a task, over ACP Qwen Code a card its terminal lines at once, when Vults started it a container, if on a task, over ACP Copilot CLI a card none lines at once, when Vults started it none a task, over ACP Cursor its terminal its terminal none not from Vults none no Factory Droid its terminal its terminal lines not from Vults none no Kimi Code its terminal; a card in a task its terminal lines at once, when Vults started it none a task, over ACP Vults stops a turn, and keeps the session, only in a session it started (the chat\u0026rsquo;s, or a task\u0026rsquo;s): no signal does that, one ends the whole session. OpenCode, Qwen Code, Copilot CLI and Kimi Code take tasks through the Agent Client Protocol (Start with another agent… in the Tasks tab, when the agent is installed); see Tasks. Codex\u0026rsquo;s sandbox depends on its mode: workspace-write writes only in its folder, the temp folders and the folders it is given. If on: the sandbox is off until you turn it on in that agent. None of these sandboxes has been tried with Vults yet.\nPermissions # The island never answers the permission of a tool that is not built in: it has no way to know what that tool expects back. A PermissionRequest shows as a question waiting in the terminal, the hook does not wait, and the tool asks you there as usual.\nAntigravity # Settings → Agents → Antigravity installs a hook named vults in ~/.gemini/config/hooks.json, the file the agy CLI, the Antigravity app and the IDE share (checked with agy 1.2.16). The other hooks there are never touched; if one of yours is already named vults, the installer writes nothing. Its sessions show as antigravity.\nThe island shows Antigravity thinking, each step (view_file, run_command…) and when it stops. Antigravity does not send its reply, so a finished session has no summary line, and it has no session start or end: a session leaves the wire once quiet (10 minutes after it finished, 30 otherwise). Its hook runs before every tool and does not say whether Antigravity would have asked, so Antigravity asks its permissions itself (Approving).\nCursor # Settings → Agents → Cursor adds its hooks to ~/.cursor/hooks.json, the file the editor and cursor-agent share; the hooks of other tools in it are kept, and Remove hooks… takes out only Vults\u0026rsquo; own. Restart Cursor after. Its sessions show as cursor, in the project Cursor works in.\nThe island shows each finished step, failures included, and when a turn stops. Its hooks that run before a step (preToolUse, beforeSubmitPrompt, beforeShellExecution…) are left out: Cursor reads what they print as a decision, so Cursor asks its permissions itself (Approving). What a tool printed and the account\u0026rsquo;s email never leave the hook.\nCursor also runs Claude Code\u0026rsquo;s hooks (its third-party hooks, on by default). The relay ignores Cursor\u0026rsquo;s events there, so a session never shows twice, once as Claude Code.\nFactory Droid # Settings → Agents → Factory Droid adds its hooks to ~/.factory/settings.json (Droid reads Claude Code\u0026rsquo;s hooks; the rest of the file and other tools\u0026rsquo; hooks are kept). Restart Droid after. Its sessions show as droid (checked with droid 0.239.0).\nThe island shows each step as it starts and ends, an edit\u0026rsquo;s lines, and when a turn stops. A command that fails sends no event of its own: its step ends with the turn. Droid has no permission event, and its PreToolUse runs before every tool whether it would ask or not, so Droid asks its permissions itself (Approving).\nKimi Code # Settings → Agents → Kimi Code adds its hooks to ~/.kimi-code/config.toml (KIMI_CODE_HOME moves it), as [[hooks]] tables beside your own settings: your comments and layout stay, and other tools\u0026rsquo; hooks are kept. Restart Kimi after. Its sessions show as kimi (checked with Kimi Code 2.1.1).\nThe island shows each step, an edit\u0026rsquo;s lines, a failed command and when a turn stops. Kimi tells its PermissionRequest hook about a permission without waiting for an answer, so the island shows it waiting in Kimi\u0026rsquo;s terminal and Kimi asks itself (Approving).\nKimi also takes tasks over ACP (kimi acp), and there each write and command is an island card. Its session is set to Kimi\u0026rsquo;s asking mode when it starts: your default_permission_mode in its config would otherwise run every tool unasked, while the session still says it asks.\nOpenCode # Settings → Agents → OpenCode writes a plugin to ~/.config/opencode/plugins/vults.js, with the same diff, backup and click as the hooks; restart OpenCode after. It reports every step and puts OpenCode\u0026rsquo;s permissions and questions on the island (Approving). A file there without the Generated by Vults first line is yours, and the installer leaves it alone. OpenCode has no event when it quits, so its birds leave the wire once quiet.\nRecipes # These were checked against each tool\u0026rsquo;s documentation and installed copy (versions below), with the relay missing too; none was run inside a live session of the tool. Each one only reports: it never answers a permission.\nPi # Pi (1.0) loads extensions from ~/.pi/agent/extensions/. Save this as ~/.pi/agent/extensions/vults.ts. It listens to tool_execution_start and _end, never to tool_call: a tool_call handler that fails stops the tool.\nimport { spawn } from \u0026#34;node:child_process\u0026#34;; import { homedir } from \u0026#34;node:os\u0026#34;; import { join } from \u0026#34;node:path\u0026#34;; const RELAY = join(homedir(), \u0026#34;.local/share/vults/bin/vults-hook\u0026#34;); function toolInput(args: any): Record\u0026lt;string, unknown\u0026gt; { const a = args ?? {}; const out: Record\u0026lt;string, unknown\u0026gt; = {}; if (typeof a.command === \u0026#34;string\u0026#34;) out.command = a.command; if (typeof a.path === \u0026#34;string\u0026#34;) out[a.pattern === undefined ? \u0026#34;file_path\u0026#34; : \u0026#34;path\u0026#34;] = a.path; if (typeof a.pattern === \u0026#34;string\u0026#34;) out.pattern = a.pattern; return out; } let queue: Promise\u0026lt;void\u0026gt; = Promise.resolve(); function run(event: string, payload: string): Promise\u0026lt;void\u0026gt; { return new Promise((done) =\u0026gt; { try { const child = spawn(RELAY, [\u0026#34;--agent\u0026#34;, \u0026#34;pi\u0026#34;, event], { stdio: [\u0026#34;pipe\u0026#34;, \u0026#34;ignore\u0026#34;, \u0026#34;ignore\u0026#34;], detached: true }); setTimeout(done, 2000).unref(); child.on(\u0026#34;error\u0026#34;, () =\u0026gt; done()); child.on(\u0026#34;exit\u0026#34;, () =\u0026gt; done()); child.stdin?.on(\u0026#34;error\u0026#34;, () =\u0026gt; {}); child.stdin?.end(payload); child.unref(); } catch { done(); } }); } function send(event: string, ctx: any, extra: Record\u0026lt;string, unknown\u0026gt; = {}): void { try { const payload = JSON.stringify({ hook_event_name: event, session_id: ctx.sessionManager.getSessionId(), cwd: ctx.cwd, ...extra }); queue = queue.then(() =\u0026gt; run(event, payload)); } catch {} } function text(content: any): string | undefined { if (!Array.isArray(content)) return undefined; const s = content.filter((c) =\u0026gt; c?.type === \u0026#34;text\u0026#34;).map((c) =\u0026gt; c.text).join(\u0026#34;\\n\u0026#34;).trim(); return s || undefined; } export default function (pi: any) { let last: string | undefined; let failed = false; pi.on(\u0026#34;session_start\u0026#34;, (_e: any, ctx: any) =\u0026gt; send(\u0026#34;SessionStart\u0026#34;, ctx)); pi.on(\u0026#34;session_shutdown\u0026#34;, (_e: any, ctx: any) =\u0026gt; send(\u0026#34;SessionEnd\u0026#34;, ctx)); pi.on(\u0026#34;before_agent_start\u0026#34;, (_e: any, ctx: any) =\u0026gt; { last = undefined; failed = false; send(\u0026#34;UserPromptSubmit\u0026#34;, ctx); }); pi.on(\u0026#34;tool_execution_start\u0026#34;, (e: any, ctx: any) =\u0026gt; send(\u0026#34;PreToolUse\u0026#34;, ctx, { tool_name: e.toolName, tool_input: toolInput(e.args) })); pi.on(\u0026#34;tool_execution_end\u0026#34;, (e: any, ctx: any) =\u0026gt; send(e.isError ? \u0026#34;PostToolUseFailure\u0026#34; : \u0026#34;PostToolUse\u0026#34;, ctx, { tool_name: e.toolName })); pi.on(\u0026#34;agent_end\u0026#34;, (e: any, ctx: any) =\u0026gt; { try { const reply = [...(e.messages ?? [])].reverse().find((m: any) =\u0026gt; m?.role === \u0026#34;assistant\u0026#34;); if (reply?.stopReason === \u0026#34;error\u0026#34;) { failed = true; send(\u0026#34;StopFailure\u0026#34;, ctx, { error: reply.errorMessage }); } else last = text(reply?.content) ?? last; } catch {} }); pi.on(\u0026#34;agent_settled\u0026#34;, (_e: any, ctx: any) =\u0026gt; { if (!failed) send(\u0026#34;Stop\u0026#34;, ctx, { last_assistant_message: last }); }); } ctx.sessionManager.getSessionId() is in Pi but not in its documentation; if a later Pi drops it, the extension sends nothing rather than failing.\nTrying it # echo \u0026#39;{\u0026#34;hook_event_name\u0026#34;:\u0026#34;SessionStart\u0026#34;,\u0026#34;session_id\u0026#34;:\u0026#34;s1\u0026#34;,\u0026#34;cwd\u0026#34;:\u0026#34;\u0026#39;$PWD\u0026#39;\u0026#34;}\u0026#39; \\ | ~/.local/share/vults/bin/vults-hook --agent my-tool echo \u0026#39;{\u0026#34;hook_event_name\u0026#34;:\u0026#34;PreToolUse\u0026#34;,\u0026#34;session_id\u0026#34;:\u0026#34;s1\u0026#34;,\u0026#34;tool_name\u0026#34;:\u0026#34;Bash\u0026#34;,\u0026#34;tool_input\u0026#34;:{\u0026#34;command\u0026#34;:\u0026#34;make\u0026#34;}}\u0026#39; \\ | ~/.local/share/vults/bin/vults-hook --agent my-tool echo \u0026#39;{\u0026#34;hook_event_name\u0026#34;:\u0026#34;SessionEnd\u0026#34;,\u0026#34;session_id\u0026#34;:\u0026#34;s1\u0026#34;}\u0026#39; \\ | ~/.local/share/vults/bin/vults-hook --agent my-tool The protocol behind it is in Hook protocol.\n","externalUrl":null,"permalink":"/en/docs/vults/guide/other-agents/","section":"Docs","summary":"Claude Code, Codex, Antigravity, OpenCode, Qwen Code, Copilot CLI, Cursor, Factory Droid and Kimi Code are built in (Settings → Agents). Any other tool can put …","title":"Other agents · Vults","type":"docs"},{"content":"The TUI colors come from a theme, picked with theme: in config.yaml and applied at startup. The list of built-in themes, with their ids, is in the built-in themes reference.\nBuilt-in themes # The default family is SP Night, a palette with São Paulo as its reference. Its three flavors (sp-night, sp-night-garoa, sp-night-jaragua) ship with the full original palette and every semantic role, unadapted. The ids used up to v0.2 (noite, garoa, jaragua) still work in theme: and extends:.\nWell-known community themes ship too, with their official palettes: Tokyo Night, Dracula, Gruvbox, Nord, Rosé Pine, Kanagawa, Everforest and One Dark. Where the original color of a role would leave text unreadable (contrast below 3:1 against the background or the selection), the role uses another color of the same palette. When no color of the palette passes, the file adds a derived one, marked with a comment.\nTo give your terminal and editor the same look, SP Night has ports for other tools at sp-night.github.io.\nYour own theme # Create ~/.config/lazyagents/themes/\u0026lt;id\u0026gt;.yaml and set theme: \u0026lt;id\u0026gt;. You only write what changes: anything missing comes from the theme in extends (default sp-night).\n# ~/.config/lazyagents/themes/my-dracula.yaml label: My Dracula extends: dracula palette: purple: \u0026#34;#caa9fa\u0026#34; # repaints every dracula role that uses purple ui: accent: \u0026#34;#ff79c6\u0026#34; # main highlight: active tab, focused borders bg: \u0026#34;#1e1f29\u0026#34; diagnostic: ok: \u0026#34;#50fa7b\u0026#34; The format:\nField Meaning id Optional. If present it must match the file name label, description What the theme is called and a line about it appearance dark or light. Informative only: it does not change the drawing extends The theme to inherit from. Default sp-night palette Named colors, name: \u0026quot;#rrggbb\u0026quot; ui, diagnostic, git, syntax, ansi Role groups. Each role takes \u0026quot;#rrggbb\u0026quot; (quoted) or a palette name Palette names are resolved after inheritance, so changing a named color in your theme repaints every inherited role that uses it.\nThe full list of roles, and what each one paints in the TUI, is in internal/tui/theme/README.md. The built-in files in internal/tui/theme/themes/ are complete examples.\nRules # A file cannot reuse a built-in theme\u0026rsquo;s id. To change a built-in theme, extends it. An invalid file (bad hex, unknown role, an extends cycle) is skipped with a warning; the other themes still load. Warnings print on stderr when the TUI exits, or before the output of a CLI command. Plugins and themes # Plugins receive the active theme when they start, with every token and role as hex, so their tabs can match. See the plugin protocol.\nContributing a theme # A new built-in theme is a complete YAML file in internal/tui/theme/themes/: palette plus every role, with no extends, and text roles at a contrast of at least 3:1 over ui.bg and ui.selection. go test ./internal/tui/theme/ enforces both. Add the license notice of the original palette to LICENSES-themes.md, then run go test ./docs -update so the reference lists it.\nLicenses # The palettes are MIT, except Tokyo Night (Apache-2.0). Notices: LICENSE-SP-Night and LICENSES-themes.md.\n","externalUrl":null,"permalink":"/en/docs/lazyagents/themes/","section":"Docs","summary":"The TUI colors come from a theme, picked with theme: in config.yaml and applied at startup. The list of built-in themes, with their ids, is in the built-in …","title":"Themes · lazyagents","type":"docs"},{"content":"lazyagents edits files that your agents depend on. This page says exactly what it touches, how to undo each change, and how secrets are handled. The module guides go deeper on each subject.\nPrinciples # Nothing changes without an action from you. Browsing, opening the TUI or running a query command never writes to an agent\u0026rsquo;s files. Config writes are confirmed. In the TUI, applying a provider or installing a hook shows the file and what changes before writing. Enabling a skill only adds or removes a symlink, so it happens at once. Backup before overwrite. An agent config file is copied to the backups dir before each write; removed skills and deleted sessions are archived first. Only what lazyagents manages is touched. Keys, hooks and TOML sections it did not write are kept as they were, in the same order. In Claude Code\u0026rsquo;s env block, applying or clearing a provider removes only the keys lazyagents wrote, and clearing puts back an endpoint or model a profile had replaced. Atomic writes. Files are written to a temp file and renamed over the original, so a crash never leaves half a file. Concurrent writes are never overwritten. An agent config is replaced only if it still holds what lazyagents read. If the agent itself, or another lazyagents, wrote it in between, Codex\u0026rsquo;s config.toml and Pi\u0026rsquo;s pair of files (models.json, settings.json) are read and edited again (up to three times, with one backup); Claude Code\u0026rsquo;s settings.json and the hooks files are left as the other program wrote them, and lazyagents says so. Removing a file lazyagents created follows the same rule. What lazyagents writes # In its own dirs (paths): the skill and hook libraries, profiles, aliases, caches, exports and backups.\nIn the agents\u0026rsquo; dirs, only on your action:\nAction What changes Undo Enable a skill A symlink in the agent\u0026rsquo;s skills dir, pointing to the library Disable it: the symlink is removed Disable a skill That symlink is removed. A real folder (a local skill, or another tool\u0026rsquo;s content) is never deleted Enable it again Adopt a skill The local folder moves into the library and a symlink takes its place Copy the folder back from the library Apply a provider Claude Code: the env block of ~/.claude/settings.json. Codex: model_provider and a delimited [model_providers.lazyagents] block in ~/.codex/config.toml. Pi: the lazyagents provider in ~/.pi/agent/models.json and defaultProvider/defaultModel in ~/.pi/agent/settings.json. Crush: a delimited block at the end of ~/.config/crush/crushrc, every value single-quoted Clear the provider, or restore the backup Install a hook One entry in the agent\u0026rsquo;s hooks file (~/.claude/settings.json, ~/.codex/hooks.json), or a hook add line in a delimited block of Crush\u0026rsquo;s crushrc Uninstall it, or restore the backup Delete a session The transcript file is removed after a copy goes to the backups dir. OpenCode sessions are exported with opencode export first, then deleted by opencode session delete, and Crush sessions saved with crush session show --json, then deleted by crush session delete; nothing is deleted if the backup fails Copy the backup back; for OpenCode, opencode import \u0026lt;file\u0026gt;; a Crush backup is a record only (Crush has no import) migrate-library Skills move to the new dir, symlinks are recreated, libraryDir is saved in config.yaml Run migrate-library back to the old dir The exact files per agent are in the agent support reference.\nBackups # Everything goes to ~/.local/share/lazyagents/backups/, named \u0026lt;file\u0026gt;.\u0026lt;timestamp\u0026gt;:\nWhat Kept Agent config files (settings.json, config.toml, hooks.json, models.json) the newest 20 per file Skills removed or updated (\u0026lt;skill\u0026gt;.\u0026lt;timestamp\u0026gt;.tar.gz) the newest 20 per skill Deleted sessions (\u0026lt;transcript file\u0026gt;.\u0026lt;timestamp\u0026gt;, opencode-\u0026lt;id\u0026gt;.\u0026lt;timestamp\u0026gt;.json, crush-\u0026lt;id\u0026gt;.\u0026lt;timestamp\u0026gt;.json) all Skill backups are restored from the TUI: b in the Skills tab lists them (skills guide). A config file backup is a plain copy: to restore, copy it over the live file.\nls -t ~/.local/share/lazyagents/backups/settings.json.* | head -1 # newest settings.json backup Secrets # Provider tokens live in ~/.config/lazyagents/providers.json with mode 0600, and backups keep the same mode. The TUI and --json show only token ✓; the plain value prints only with lazyagents provider list --reveal. Pass tokens with --token - to read them from stdin and keep them out of your shell history. For Codex, prefer --env-key VAR: config.toml then names the environment variable and never holds the token. For Claude Code, the token has to live in settings.json, as Claude Code reads it there. lazyagents makes that file 0600 when it writes a token. For Pi, --env-key VAR also keeps the token out of the file (\u0026quot;apiKey\u0026quot;: \u0026quot;$VAR\u0026quot;); a token otherwise goes into models.json, which lazyagents makes 0600. Agent credentials (for example Claude Code\u0026rsquo;s login) are read only to make the one call that needs them, the Claude Code limits request, and never stored, logged or shown. To tell the auth mode, lazyagents reads only whether a credential exists and its type (Pi\u0026rsquo;s auth.json: oauth or api_key; whether Pi\u0026rsquo;s models.json or Crush\u0026rsquo;s crush.json has an API key), never its value. Network # lazyagents works offline. It goes to the network only when you ask for something that needs it:\nWhen What You open the Usage tab, run lazyagents usage or lazyagents doctor Claude Code limits, from the same endpoint as its /usage command, cached for 5 minutes. Codex limits come from its local files You install from GitHub git clone --depth 1 of that repository You search GitHub (S) gh api with your gh login You check skill updates the skill\u0026rsquo;s source repository Nothing runs at startup, and there is no telemetry.\nConsent belongs to the agent # Some agents ask you to approve a command before running it. lazyagents never answers for you. Codex, for example, records your approval of a hook as a trusted_hash in config.toml and needs [features] hooks on: lazyagents installs the hook and warns that it is pending, but never writes the hash or turns the feature on. You approve it in Codex.\nThird-party content # Hooks run commands. A hook from a repository runs its command on every matching event, with your permissions. Hooks found while installing skills come unchecked; read them first (v in the Hooks tab opens the command and its scripts). Skills are instructions your agents will follow. Read a SKILL.md (enter) before enabling a skill from a source you do not know. Plugins are programs you install yourself. lazyagents treats everything a plugin sends as untrusted, strips terminal control sequences from it and keeps a failing plugin from taking the TUI down, but the plugin itself runs with your permissions. See the plugins guide. Reporting a vulnerability # See SECURITY.md.\n","externalUrl":null,"permalink":"/en/docs/lazyagents/safety/","section":"Docs","summary":"lazyagents edits files that your agents depend on. This page says exactly what it touches, how to undo each change, and how secrets are handled. The module …","title":"Safety and data · lazyagents","type":"docs"},{"content":"A phone you pair can answer the cards that wait for you while you are away from the desk. It only decides: this computer still does what you chose, and the island keeps every card too. The Vults phone app is not out yet; until it is, the command-line test client in The phone\u0026rsquo;s relay pairs and answers like a phone.\nTurning it on # In Settings → Phone, turn on Answer cards from a phone, then pick an option in How the phone reaches this computer and fill in the field below it:\nThrough a relay: a small server that keeps sealed messages until the other side reads them. Run your own (The phone\u0026rsquo;s relay) and give its address in Relay. Straight (LAN or tailnet): this computer runs the relay itself on the address you give in Listen on, such as its tailnet address and a port (100.64.0.2:8787). The phone must be able to reach it. Every request to the relay is a request on your behalf: off until you turn it on, at most 10,000 a day, each kept in the local trace (Safety).\nPairing # Press Pair: a QR shows for two minutes. Scan it with the phone. The QR holds this computer\u0026rsquo;s public key, the relay\u0026rsquo;s address and a one-time secret; only a phone that saw it can pair, and it pairs one phone. Below the QR, The code\u0026rsquo;s text, for the command-line test client gives the same code as text. The phone then shows under Paired phones.\nAnswering # Each card waiting on the island goes to every paired phone, and leaves it when the card leaves the line. In the Paused preset no card exists, so nothing reaches the phone. On the phone:\nDeny and Dismiss (send it to the terminal) need no biometrics. Allow, Always and answering a question need your fingerprint or face, for that one answer. An answer counts only if that phone\u0026rsquo;s key signed it for that exact card, in time, and it was never used before. A relay that drops, delays or replays messages can make a card wait, never answer it (What it sees). If this computer cannot save that an answer was used, the answer is dropped and the card keeps waiting. Always from the phone makes the same rule as on the island (Approving).\nRevoking # Revoke beside a phone and nothing it signs is taken from then on. A revoked phone stays in the list and cannot pair again under the same keys.\n","externalUrl":null,"permalink":"/en/docs/vults/guide/phone/","section":"Docs","summary":"A phone you pair can answer the cards that wait for you while you are away from the desk. It only decides: this computer still does what you chose, and the …","title":"The phone · Vults","type":"docs"},{"content":"What Vults promises:\nIt never blocks your agent. If the app is closed, slow or broken, the hook exits within milliseconds and prints nothing, and your agent asks in its terminal as usual. Only you answer a permission. No timer or default ever answers one. Besides your click, only these do: an exact rule you made with Always, for that command or file; a policy you wrote, saw as a diff and accepted with a click (a deny beats an allow); a phone you paired, when its answer is signed by its key for that very card, in time, never seen before (approving needs your biometrics on the phone, denying does not; see The phone); and, in Zeca\u0026rsquo;s chat only, the access you chose for its cards (Who answers Zeca\u0026rsquo;s cards); under its full access, a policy\u0026rsquo;s deny or ask still holds, and content from outside brings cards back. A destructive call (rm -rf, a forced push) always asks: its card offers no Always, and no rule, policy or full access ever answers it. A question from Claude Code is answered only with the choice or the words you gave on its card. Only your user account can talk to it. The socket lives in your private runtime folder with mode 0600, and both ends check the other process runs as you (on Windows, by SID). It never edits an agent\u0026rsquo;s config behind your back. Every change gets a dated backup and a diff you approve; hooks from other tools are kept, a Claude Code status line of your own keeps running (how), and Codex\u0026rsquo;s trusted_hash is never written (you trust our hooks yourself, in Codex\u0026rsquo;s /hooks). Plan usage comes from the CLIs only. Claude Code\u0026rsquo;s status line input is cut down in the hook to its rate_limits and the session\u0026rsquo;s cost before anything leaves it; Codex is asked with a read-only call, only while Settings → General → Codex usage is on. The chat uses the CLIs you logged into (claude, codex), and Vults never reads their credentials; every command and edit your own rules don\u0026rsquo;t allow waits for your Allow (see Your setup). API keys stay in your OS keyring. If you give the chat a provider\u0026rsquo;s API key, it is written only to the keyring (Secret Service on Linux), never to a file or a log, never sent back to a window, and only ever sent to that provider. Local models (Ollama, LM Studio) are reached on 127.0.0.1 only. The API chat can\u0026rsquo;t run commands or edit files. Its tools read what the island shows (sessions, cards waiting, usage, your GitHub card); with the memory on, recall reads the journal and memory_propose writes only a proposal; with the web on, it gets the web tools. What they return goes to that provider with the conversation, commands with their secrets redacted. Each chat message says what the island shows. Through a CLI or an API, a message to Zeca carries the date and time and a line for each session (agent, project, state, its step with secrets redacted), never a card\u0026rsquo;s command. With the memory on, what Zeca remembers joins when a conversation starts, and up to 3 recalled passages go with each message. The microphone only listens while you hold a recording. Voice is off until you download a model; then the mic records into memory only while its button is red (a minute at most), and whisper.cpp transcribes it on this computer. The audio is never saved or sent anywhere. Now playing is off until you turn it on. Then the app reads your media players over the session bus (MPRIS on Linux) to show the song; it is never stored or sent anywhere. What the app asks on its own goes through one gate. A request made without a click at that moment (a connector\u0026rsquo;s check, the Codex usage read, a paired phone\u0026rsquo;s cards and answers, the web tools, the journal summary, memory suggestions and Zeca\u0026rsquo;s sleep) asks the app\u0026rsquo;s network gate first: only a feature you switched on, each with its own daily cap (see the table), each request kept as a line in vults.sqlite (when, which feature, whether it answered; 30 days). A connector over its cap waits and says so in Settings → Connectors. What it keeps stays on this computer, is never sent anywhere, and is listed in Settings and files; Clear history removes most of it:\nThe edits it shows stay in memory: a finished edit\u0026rsquo;s changed lines (400 at most), for the session\u0026rsquo;s last steps, never written to a file or a log. Connectors keep only their last answer, so a restart does not replay old news (Connectors). The activity history is counts: never a prompt, a reply, a command, a file name or a path (Activity). Zeca\u0026rsquo;s turns are traced for 30 days: engine, model, tokens, cost and each tool\u0026rsquo;s redacted target, never a prompt or a reply (The Nest). Zeca\u0026rsquo;s conversations are kept, the newest 200, never a card\u0026rsquo;s answer nor an Always in this chat rule (Conversations). Zeca\u0026rsquo;s memory is off until you turn it on, and only you write it: what Zeca, a model or his MCP server would add waits for your yes, and a project\u0026rsquo;s CLAUDE.md changes only over the file you saw, after a dated backup (Zeca\u0026rsquo;s memory). Every answer is on record. An audit log keeps, for 90 days, each answer to a card, the flock\u0026rsquo;s and Zeca\u0026rsquo;s chat\u0026rsquo;s: who gave it (you, your paired phone, an Always rule, the chat\u0026rsquo;s Always in this chat, a policy with its consent, the chat\u0026rsquo;s full access, or the app when a card\u0026rsquo;s time ran out or Paused sent it to the terminal), what it did (allow, deny, always allow, allow in this conversation, answer, send to the terminal, or time ran out), the agent, the project\u0026rsquo;s folder name, the tool and the exact command or path, with tokens and NAME=secret values redacted first. An answer given in the agent\u0026rsquo;s own terminal is not in it. Each agent config the installer writes is kept too. A line is never changed and none leaves before 90 days, except through Clear history, which leaves one line: that you cleared it, and when. It is always on. What you agree to is kept for good. Each policy you accept is a row in vults.sqlite\u0026rsquo;s consents: when, what it is about, the words you accepted and a SHA-256 of the diff you saw. A row is never changed or deleted, and Clear history does not touch it: a policy must never lose the proof that you said yes. Revoking turns the policy off at once; the row stays, marked revoked. The row\u0026rsquo;s words are the policy itself, and one whose diff does not end at those words never answers. Secrets stay in your OS keyring, and there is no telemetry. Every request Vults makes # Nothing else reaches the network. CI checks it: scripts/check-network.sh fails on a crate that depends on an HTTP client, or a file that names one or starts a network command, outside scripts/network-allowlist.txt, which names each one and why; and on a webview allowed to connect anywhere but the app.\nRequest Who starts it What turns it on Where A chat turn through an API (Anthropic, OpenAI, Gemini, OpenRouter, Groq, DeepSeek, Mistral, xAI; Ollama and LM Studio on 127.0.0.1) You, with each message The provider and key you chose; sent only to that provider crates/chat/src/api.rs, openai.rs, providers.rs The list of a provider\u0026rsquo;s models You, opening Settings → Chat \u0026amp; voice with a key saved (or a local Ollama or LM Studio), or with Try again The provider and key you chose crates/chat/src/providers.rs A web search (Zeca\u0026rsquo;s web, Settings → Chat \u0026amp; voice, off until you turn it on) The API chat\u0026rsquo;s model, during a turn you sent; through the network gate (200 a day, with page reads) Brave Search or Tavily with your key, or your own SearXNG crates/chat/src/web.rs, app/src/web_tools.rs A web page read (the same switch) The API chat\u0026rsquo;s model, during a turn you sent; through the network gate (the same 200) The page\u0026rsquo;s site, public addresses only; its text then goes to your API provider in a call of its own, with no tools crates/chat/src/web.rs, app/src/web_tools.rs A paired phone\u0026rsquo;s cards and answers (Settings → Phone, off until you turn it on) The app: each card it sends, and a read of each phone\u0026rsquo;s box every 25 s; through the network gate (10,000 a day) The relay you chose, which sees only sealed messages; in Straight, this computer listens on the address you gave instead crates/relay/src/client.rs, app/src/phone.rs Codex\u0026rsquo;s list of models You, opening the chat\u0026rsquo;s model picker with Codex (once per run) The codex you logged into, through codex app-server\u0026rsquo;s model/list crates/chat/src/codex_server.rs A chat turn through claude or codex You, with each message The engine you chose; the CLI talks to its own service on your login crates/chat/src/claude.rs, codex.rs, codex_server.rs A task\u0026rsquo;s session (claude or codex app-server in the task\u0026rsquo;s worktree) You, clicking a task\u0026rsquo;s Start with Claude Code or Start with Codex; its turn runs until it ends or you Stop it The CLI talks to its own service on your login, in its sandbox, each command asking first app/src/runs.rs, crates/chat/src/claude.rs, codex_server.rs The Codex usage read (account/rateLimits/read, through codex app-server) The app, every 5 minutes and half a minute after a window resets, never while the screen is locked Settings → General → Codex usage, through the network gate (400 a day) app/src/usage.rs A past day of Zeca\u0026rsquo;s journal summed up (Settings → Chat \u0026amp; voice → Memory → A summary of each day, off until you turn it on) The app, in an hourly pass: each past day without a summary since you turned it on, up to 3 a pass; through the network gate (8 a day) The claude you logged into, its smallest model, with no tools and none of your settings crates/chat/src/summary.rs, app/src/journal.rs An embedding of a journal summary or of your message, for recall (Recall by meaning, off until you turn it on) The app, in the hourly journal pass for each summary without one, and before each chat message An Ollama on 127.0.0.1 with nomic-embed-text, never through a proxy; nothing leaves the computer crates/chat/src/embed.rs, app/src/recall.rs Memory suggestions from a conversation you left (Suggest what to remember, off until you turn it on) The app, when you start a new conversation or open another; through the network gate (20 a day), only with at least 2 of your messages, never for one that read outside content The claude you logged into, its smallest model, with no tools and none of your settings; what it proposes waits for your yes crates/chat/src/summary.rs, app/src/memory.rs Zeca\u0026rsquo;s sleep (Let Zeca sleep on it, off until you turn it on): what he remembers about you and across projects, and the last 7 days\u0026rsquo; summaries The app, once a day from 2 a.m. while nothing works; through the network gate (4 a day) The claude you logged into, its smallest model, with no tools and none of your settings; its merges wait for your yes app/src/sleep.rs, crates/chat/src/summary.rs A connector\u0026rsquo;s check (GitHub, through gh) The app, on its own schedule Each connector\u0026rsquo;s switch in Settings → Connectors, through the network gate (3,000 a day) crates/connectors/src/github.rs Your GitHub contribution calendar (through gh) You, opening Settings → Activity\u0026rsquo;s GitHub tab The GitHub connector on crates/connectors/src/github.rs An issue from a task (through gh) You, clicking a task\u0026rsquo;s Open an issue in the Nest The GitHub connector on crates/connectors/src/github.rs A squash merge of your pull request (through gh), with a line in the local trace You, clicking Squash and merge on its row in the island\u0026rsquo;s Connections, then Merge The GitHub connector on; only your own pull request, approved, with its checks passing crates/connectors/src/github.rs, app/src/connectors.rs A voice model\u0026rsquo;s download You, with a click in Settings → Chat \u0026amp; voice → Voice Nothing until you click; from Hugging Face (ggerganov/whisper.cpp), checked against its SHA-256 crates/voice/src/models.rs The speech detector\u0026rsquo;s download (Silero VAD, under 1 MB) The app, by itself, outside the gate, while voice is on and it is missing A voice model you chose; from Hugging Face (ggml-org/whisper-vad), checked against its SHA-256 crates/voice/src/models.rs, app/src/voice.rs Claude Code\u0026rsquo;s plan usage Claude Code, which hands it to our status line No request of ours the hook The update check Not built yet (ADR 0019) It will be off until you turn it on, through the gate — ","externalUrl":null,"permalink":"/en/docs/vults/safety/","section":"Docs","summary":"What Vults promises:\nIt never blocks your agent. If the app is closed, slow or broken, the hook exits within milliseconds and prints nothing, and your agent …","title":"Safety · Vults","type":"docs"},{"content":" Start with lazyagents doctor # lazyagents doctor # readable report, exits 1 when something is wrong lazyagents doctor --json # the same, for scripts and bug reports It lists the detected agents and checks skills, broken symlinks, providers, hooks, usage limits and plugins. Most entries below match a line it prints. When you open an issue, include its output (see the CLI reference).\nNotices about the config, themes or plugins are printed on stderr: before the output of a CLI command, and after you quit the TUI.\nAgents # An agent shows as not installed # Cause: lazyagents looks for the agent\u0026rsquo;s binary in PATH and for its config dir (~/.claude, ~/.codex, ~/.gemini, ~/.config/opencode, ~/.hermes or %LOCALAPPDATA%\\hermes on Windows, ~/.pi/agent, ~/.config/crush), or the dir its config variable names, such as CODEX_HOME. Neither was found. lazyagents reads that variable from the environment it was started in, so start it from the same shell as the agent.\nFix: install the CLI, or make sure it is in the PATH of the shell that starts lazyagents, then reopen lazyagents: detection runs once per launch. The Agents guide has the rules.\nThe agent is detected but has no version, or enter says the CLI \u0026ldquo;is not in PATH\u0026rdquo; # Cause: the config dir exists but the binary is not in PATH. Skills and sessions still work; resuming a session needs the CLI.\nFix: add the binary\u0026rsquo;s dir to PATH. The Agents tab detail shows config in … (binary not in PATH) in this case.\nSkills # \u0026ldquo;installing from GitHub requires git in PATH\u0026rdquo; # Fix: install git. Installing from a local folder or a .zip works without it.\ngit clone … fails # The message carries git\u0026rsquo;s own error. Check the repository name (user/repo or a full URL), your network, and, for private repositories, your git credentials.\n\u0026ldquo;no SKILL.md found in …\u0026rdquo; # The source has no SKILL.md at any depth. A skill is a folder with a SKILL.md file; check that you pointed at the right repository or folder.\n\u0026ldquo;searching requires the GitHub CLI (gh) in PATH, logged in\u0026rdquo; # Search (S in the Skills tab) uses gh api. Install the GitHub CLI and run gh auth login. Other errors from GitHub show as GitHub search: ….\n\u0026ldquo;… is local (unmanaged) — o to adopt\u0026rdquo; # Cause: the skill is a real folder (or another tool\u0026rsquo;s symlink) inside the agent\u0026rsquo;s skills dir, not a link to the lazyagents library. lazyagents never deletes real content, so it will not disable it.\nFix: adopt it (o, or A for all; lazyagents adopt \u0026lt;skill\u0026gt; --agent \u0026lt;id\u0026gt;). The folder moves into the library and a symlink takes its place, so you can enable it in other agents too. A third-party symlink cannot be adopted: manage it with the tool that created it.\n\u0026ldquo;a skill with this name already exists\u0026rdquo; when enabling # Something with that name is already in the agent\u0026rsquo;s skills dir. The message ends with its path. Remove or rename it, or adopt it if it is the same skill.\nA skill shows ◆ or turns on in agents you did not pick # OpenCode and Crush also read ~/.claude/skills, so a skill enabled for Claude Code shows up in them with ◆. A skill placed in ~/.agents/skills by hand, or by an older lazyagents version, is visible to Codex, Gemini CLI, OpenCode, Pi and Crush: disable it for the agents that should not have it and lazyagents moves it to the own directory of the others. See the agent support reference.\ndoctor reports \u0026ldquo;broken symlink\u0026rdquo; # A link in an agent\u0026rsquo;s skills dir points to a folder that no longer exists, usually after a library was moved or deleted by hand. Remove the link, or reinstall the skill and enable it again. Removals made by lazyagents keep a .tar.gz backup that b in the Skills tab restores.\nmigrate-library refuses to run # Message Meaning destination already exists or is inaccessible: \u0026lt;path\u0026gt; a skill with the same name (or a symlink) is already in the new dir. Nothing is assumed to be migrated: move or remove it, then retry libraries cannot contain each other / libraries overlap after resolving symlinks the new dir is inside the old one, or the reverse. Pick a separate dir When a migration fails partway, the copies are rolled back and the source is kept.\nSessions # OpenCode sessions do not show: \u0026ldquo;opencode sessions live in SQLite: install sqlite3 to list them\u0026rdquo; # OpenCode keeps sessions in opencode.db. Install the sqlite3 command-line tool.\nProviders # \u0026ldquo;TOML config has an unterminated string or array; file left untouched\u0026rdquo; # lazyagents edits Codex\u0026rsquo;s config.toml line by line, without rewriting the rest. It follows multiline strings (\u0026quot;\u0026quot;\u0026quot; or ''') and arrays spread over several lines, but a string or array that never closes leaves the file\u0026rsquo;s structure unknown, so nothing was changed. Codex would not load that file either: close the string or array, then apply again.\n\u0026ldquo;table model_providers.lazyagents already exists outside the managed block\u0026rdquo; # A [model_providers.lazyagents] table was written by hand outside the block lazyagents manages. Remove or rename that table and apply again.\n\u0026ldquo;it changed while lazyagents was editing it … try again\u0026rdquo; # The agent itself, or another lazyagents, wrote the same config file while lazyagents was changing it (Claude Code\u0026rsquo;s settings.json, a hooks file, Codex\u0026rsquo;s config.toml, Pi\u0026rsquo;s models.json or settings.json). lazyagents never overwrites that write: the file keeps what the other program wrote, and nothing of yours was applied to it. Codex and Pi edits are retried on their own before this message shows. Run the command again; if it keeps happening, close the agent first. See safety.\ndoctor: \u0026ldquo;provider applied to an agent that is not installed\u0026rdquo; # The agent\u0026rsquo;s config still carries a provider, but the agent is gone. Clear it (x in the Providers tab, or lazyagents provider clear --agent \u0026lt;id\u0026gt;) or reinstall the agent.\nHooks # A hook installed in Codex never runs # Codex has two gates of its own, and lazyagents deliberately writes neither:\n\u0026ldquo;hooks are off in Codex: set hooks = true under [features] in config.toml\u0026rdquo;: add it to ~/.codex/config.toml. \u0026ldquo;a new hook only runs after you confirm trust in Codex itself\u0026rdquo;: Codex asks you to approve each new hook command; accept it in Codex. That approval is your consent to run the command, so only you can give it. See safety.\ndoctor: \u0026ldquo;: not in PATH\u0026rdquo;, \u0026ldquo;not found\u0026rdquo; or \u0026ldquo;not executable\u0026rdquo; # The hook\u0026rsquo;s command cannot run as installed. Fix the path or permissions of the script, or edit the hook in ~/.local/share/lazyagents/hooks/.\nUsage # Subscription limits do not show # An agent that has nothing to report is left out of the limits and of doctor, with no message:\nClaude Code not installed, not signed in (run /login), or signed in with an API key, which has no subscription limits (cost is shown instead). Codex never used on this machine, or no limits recorded yet: Codex writes its limits into its session files, so use it once and refresh. Message Fix Claude Code session expired: open the CLI to renew it open Claude Code once Limits are cached for 5 minutes. Press r in the Usage tab or run lazyagents usage limits --refresh to fetch them again. Claude Code limits need network; Codex limits are read from local files.\nNumbers look stale or wrong after an update # Transcript data is cached in ~/.local/share/lazyagents/transcript-index.gob. An index from another version or a damaged one is discarded and rebuilt automatically. It is safe to delete: the next launch reads every transcript again, which may take a moment.\nConfiguration and themes # \u0026ldquo;parsing config: …; using defaults\u0026rdquo; # config.yaml is not valid YAML. lazyagents starts with the defaults and does not touch the file. Fix the syntax; the configuration guide lists every key.\n\u0026ldquo;config tui: …\u0026rdquo; # An unknown tab id or value in the tui: section. It is only a warning: the rest of the layout applies.\n\u0026ldquo;unknown theme: \u0026rdquo; # The full notice is unknown theme: \u0026lt;id\u0026gt; in \u0026lt;config path\u0026gt;; using \u0026quot;sp-night\u0026quot;. The theme: value is neither a built-in theme nor a file in ~/.config/lazyagents/themes/. Check the id against the built-in themes. A broken user theme file prints its own notice with the reason, such as a missing role or an invalid color.\nPlugins # \u0026ldquo;plugin ignored: …\u0026rdquo; # Reason Fix not executable chmod +x the file name must match … rename it: lowercase letters, digits, - and _, up to 32 characters duplicate id two files share a name without extension; keep one id reserved by a built-in tab or command rename it so it does not clash with skills, list, doctor… A plugin tab shows an error # The plugin did not answer within 3 seconds, wrote something that is not a protocol line, or exited. The tab shows the reason and the end of the plugin\u0026rsquo;s stderr. Fix the plugin and press r (or :reload) to restart it. Run lazyagents doctor to see the handshake result outside the TUI. The plugins guide has debugging tips.\nStill stuck? # Open an issue with the output of lazyagents doctor --json, your OS, and the agents and versions involved. Remove anything private (paths, provider URLs) before posting.\n","externalUrl":null,"permalink":"/en/docs/lazyagents/troubleshooting/","section":"Docs","summary":" Start with lazyagents doctor # lazyagents doctor # readable report, exits 1 when something is wrong lazyagents doctor --json # the same, for scripts and bug …","title":"Troubleshooting · lazyagents","type":"docs"},{"content":" Every command also prints its own page with lazyagents help \u0026lt;command\u0026gt;. Without arguments, lazyagents opens the TUI. Each external plugin adds one more command named after its id, described by lazyagents help once the plugin is installed (plugins guide).\nCommands exit with 0 on success and non-zero on failure.\nCommand What it does list list skills and how many agents have them enabled enable enable a library skill (symlink in the agent) disable disable a skill (removes the symlink) install install skills from a GitHub repo, zip or directory remove remove a skill from the library adopt move an agent\u0026rsquo;s local skill into the library migrate-library move the skills library to another directory skills the skill commands above, grouped (skills list = list) sessions list agent sessions, newest first provider manage agent provider profiles (endpoint and token) hooks list library hooks and install/uninstall them in agents usage agent consumption: subscription limits, tokens and cost per day, agent, project and model doctor diagnose agents, skills, hooks, providers, usage and plugins list # lazyagents list [--json] List skills and how many agents have them enabled.\noptions: --json JSON output: every skill with its source, lint state and per-agent state Lists the library skills and the local skills found in the agents\u0026#39; skill directories. ENABLED IN counts the agents that see the skill, LIBRARY says whether lazyagents manages it. enable # lazyagents enable \u0026lt;skill\u0026gt; [--agent id|--all] Enable a library skill (symlink in the agent).\noptions: --agent id only this agent (claude-code, codex, gemini-cli, …) --all every installed agent with a skills directory (the default) \u0026lt;skill\u0026gt; is the folder name or the frontmatter name. Enabling creates a symlink from the library into the agent\u0026#39;s skills directory; an agent that already sees the skill is left as is. Only library skills can be enabled: adopt a local skill first. disable # lazyagents disable \u0026lt;skill\u0026gt; [--agent id|--all] Disable a skill (removes the symlink).\noptions: --agent id only this agent --all every agent where lazyagents enabled it (the default) Removes the skill from the agent only; it stays in the library and in the other agents. A skill linked once in a shared directory (~/.agents/skills) moves to the own directory of each agent that keeps it. A local skill (a real folder or another tool\u0026#39;s symlink) is never deleted: the command fails for it instead. install # lazyagents install \u0026lt;source\u0026gt; [skill...] [--all] [--hooks] Install skills from a GitHub repo, zip or directory.\nsources: owner/repo GitHub repository (needs git in PATH) https://… · git@… · *.git any git URL, cloned with --depth 1 ./skills.zip a zip file ~/some/folder a local folder options: --all enable the installed skills in every installed agent with a skills directory, like lazyagents enable --all --hooks also install the plugin hooks the source ships (hooks/hooks.json) Every folder with a SKILL.md is installed into the library, found at any depth; a Claude Code marketplace (.claude-plugin/marketplace.json) is read through its manifest. Skill names after the source install only those; an unknown name installs nothing and lists the skills the source has. A name already in the library is skipped and reported. Without --all, installing does not enable anything: use lazyagents enable next. Without --hooks, hooks are only counted: a hook runs a third-party command on every agent event. remove # lazyagents remove \u0026lt;skill\u0026gt; Remove a skill from the library.\nRemoves the skill from the library and every lazyagents symlink that points to it. A .tar.gz backup is written first to the backups directory, and the Skills tab restores it (b). Local skills are not in the library and cannot be removed here. adopt # lazyagents adopt \u0026lt;skill\u0026gt; --agent \u0026lt;id\u0026gt; Move an agent\u0026rsquo;s local skill into the library.\noptions: --agent id the agent whose skills directory holds the local copy (required) Copies the local folder into the library, backs the original up, and replaces it with a symlink, so the agent keeps seeing the skill and other agents can now enable it. Symlinks created by other tools are refused: manage them with the tool that made them. migrate-library # lazyagents migrate-library \u0026lt;dir\u0026gt; Move the skills library to another directory.\nCopies every library skill to \u0026lt;dir\u0026gt;, repoints the agents\u0026#39; lazyagents symlinks, saves libraryDir in config.yaml and only then removes the old copies. Each skill is backed up first. The move is refused when a name already exists in \u0026lt;dir\u0026gt; or when one directory contains the other; on a failure the links and config stay as they were. Example: lazyagents migrate-library ~/.agents/skills skills # lazyagents skills list|enable|disable|install|remove|adopt|migrate-library … The skill commands above, grouped (skills list = list).\nlazyagents skills \u0026lt;sub\u0026gt; runs the same command as lazyagents \u0026lt;sub\u0026gt;, with the same options; without \u0026lt;sub\u0026gt; it runs list. It exists so skills reads like lazyagents hooks and lazyagents provider. sessions # lazyagents sessions [--agent id] [--here] [--limit n] [--json] List agent sessions, newest first.\noptions: --agent id only this agent (an unknown id lists the valid ones) --here only sessions started in the current directory --limit n at most n sessions (default 0 = all) --json JSON output Text output has AGENT, TITLE, CWD and UPDATED columns; an alias shows as \u0026#34;alias (title)\u0026#34;. --json adds each session\u0026#39;s id and, when the agent records it, token usage (input, output, cache_read, cache_write, model) and, for agents authenticated with an API key, cost_usd for known models: a subscription does not pay per token. To resume a session, open the TUI. provider # lazyagents provider list|apply \u0026lt;profile\u0026gt;|clear|add \u0026lt;profile\u0026gt;|rm \u0026lt;profile\u0026gt; [--agent id] [--json] [--reveal] Manage agent provider profiles (endpoint and token).\nsubcommands: list profiles and what each agent has applied (default) apply \u0026lt;profile\u0026gt; write the profile to the agents\u0026#39; config clear remove what lazyagents applied, keeping the rest of the file add \u0026lt;profile\u0026gt; create or replace a profile rm \u0026lt;profile\u0026gt; delete a profile from the library (agents keep what was applied) options: --agent id apply/clear: only this agent (default: every installed agent that supports it) --json list: JSON output --reveal list: show tokens in plain text add options: --base-url url compatible endpoint (http or https) --model m default model (empty = agent default) --token - read the token from stdin; a literal value also works but lands in shell history --env-key VAR Codex, Pi, Crush: environment variable holding the token --wire-api api Codex: only \u0026#34;responses\u0026#34;; Pi: chat (default), responses or anthropic; Crush: chat (default) or anthropic Profiles live in providers.json with mode 0600. Tokens are masked in text and JSON output unless --reveal is given. Every apply and clear backs the agent\u0026#39;s file up first. Codex never gets the token: set --env-key and export that variable. Pi and Crush need --base-url and --model. hooks # lazyagents hooks list|enable \u0026lt;name\u0026gt;|disable \u0026lt;name\u0026gt;|add \u0026lt;name\u0026gt;|rm \u0026lt;name\u0026gt; [--agent id] [--json] List library hooks and install/uninstall them in agents.\nsubcommands: list library hooks and where they are installed (default) enable \u0026lt;name\u0026gt; install the hook in the agents that fire its events disable \u0026lt;name\u0026gt; uninstall the hook add \u0026lt;name\u0026gt; create a library hook with one command rm \u0026lt;name\u0026gt; delete a hook from the library (agents where it is installed keep it) options: --agent id enable/disable: only this agent (default: every installed agent that supports hooks) --json list: JSON output add options: --event name event that fires the command: SessionStart, PreToolUse, Stop… --command text shell command to run --matcher re event filter, e.g. a tool name for PreToolUse (empty = all) --timeout n seconds (0 = agent default) --desc text description shown in the tab A hook runs a command on every event, so every install backs the agent\u0026#39;s file up first, and hooks lazyagents did not install are never touched. Codex only runs a new hook after you trust it in Codex itself; lazyagents never writes that trust. To import a plugin\u0026#39;s hooks from a repository: lazyagents install \u0026lt;source\u0026gt; --hooks. usage # lazyagents usage [limits|daily|agents|projects|models] [--agent id] [--since 7d] [--limit n] [--refresh] [--json] Agent consumption: subscription limits, tokens and cost per day, agent, project and model.\nviews: (none) dashboard: subscription limits, current 5h block and period summary limits limit windows (session, week) with usage and reset time daily tokens per day agents tokens per agent projects tokens per project (session directory) models tokens per model options: --agent id only this agent --since period start of the period: 7d, 24h or 2026-09-01 (default 7d) --limit n agents/projects/models rows in text output (default 10; 0 = all) --refresh ignore the limits cache (5 min) and fetch again --json JSON output (not cut by --limit) Tokens come from local transcripts; limits, from each agent\u0026#39;s account. USD cost only shows for agents authenticated with an API key: a subscription does not pay per token. doctor # lazyagents doctor [--json] Diagnose agents, skills, hooks, providers, usage and plugins.\nsections: detected agents each agent id and whether it is installed skills library and agent skills with an invalid or incomplete SKILL.md symlinks broken symlinks in any skill dir an agent reads providers the provider applied to each agent; one applied to an agent that is not installed is a problem hooks invalid library files, hook commands missing from PATH or not executable, and each agent\u0026#39;s hooks note usage authentication and subscription limits per agent (from the cache when fresh, otherwise fetched) plugins skipped plugin files, each plugin\u0026#39;s handshake and, when its manifest asks for it, its own doctor options: --json JSON output: {ok, agents, sections[{title, report, problems}]} Exits with 1 when any section reports a problem, 0 when all is OK. ","externalUrl":null,"permalink":"/en/docs/lazyagents/reference/cli/","section":"Docs","summary":" Every command also prints its own page with lazyagents help \u0026amp;lt;command\u0026amp;gt;. Without arguments, lazyagents opens the TUI. Each external plugin adds one more …","title":"CLI reference · lazyagents","type":"docs"},{"content":" Where things live (Linux) # Path What ~/.config/vults/settings.json The app\u0026rsquo;s settings. A field it can\u0026rsquo;t read (a wrong type) falls back to its default and the rest is kept; the file as it was is copied to settings.json.bad-\u0026lt;time\u0026gt; first. A file from a newer release is read as far as this one understands it, and copied to settings.json.v\u0026lt;version\u0026gt;-\u0026lt;time\u0026gt; first (see version below) ~/.local/share/vults/bin/vults-hook The hook relay your agents run ~/.local/share/vults/bin/statusline-previous.json Your own Claude Code statusLine, saved when the hooks went in; the hook runs it, and removing the hooks puts it back ~/.local/share/vults/inbox/ Copies of dropped files, deleted after a week ~/.local/share/vults/chat/ The empty folder chats use when no session is in front ~/.local/share/vults/connectors/ What each connector last saw ~/.local/share/vults/vults.sqlite The local database: the history of agent turns (counts only), for Settings → Activity, the audit log of answers to cards (90 days), the policies you accepted (kept until you revoke them, and after), the phones you paired (their public keys and the last counter each signed with; revoked ones stay, so a revoked phone never comes back), Zeca\u0026rsquo;s traces (30 days), his conversations (the newest 200), the journal and its day summaries (90 days), recall vectors, memory proposals (decided ones kept 90 days), sleep runs, your tasks and what the agents cost by day and roost (see Safety). Clear history empties most of it (what it removes and what stays). Earlier versions\u0026rsquo; history.jsonl and days.json move into it on first start ~/.local/share/vults/memory/ Zeca\u0026rsquo;s memory: a git repository of small Markdown files, each change a commit (mode of the folder as your umask makes it). Clear history leaves it; delete the folder to erase his facts (Zeca\u0026rsquo;s memory) ~/.local/share/vults/voice/ The voice models you downloaded and the speech detector ~/.local/share/vults/backups/ Dated backups of the files Vults edits inside a project, such as a CLAUDE.md it made read AGENTS.md (never kept in the repository) ~/.local/share/vults/zeca-mcp.json The MCP config the chat\u0026rsquo;s Claude Code gets with --mcp-config: this app run as Zeca\u0026rsquo;s server (vults --zeca-mcp); mode 0600 ~/.local/share/vults/zeca-flock.json The island\u0026rsquo;s sessions (redacted) for that server\u0026rsquo;s flock_list, rewritten when they change and removed when the app quits; mode 0600 $XDG_RUNTIME_DIR/vults.sock The socket the hook talks to (mode 0600) ~/.config/autostart/ The entry Start with the desktop adds System keyring, service io.github.rogeriojunior31.vults, account \u0026lt;provider\u0026gt;-api-key (anthropic-api-key, openai-api-key, …) The chat\u0026rsquo;s API keys, one per provider you gave one (never in a file) ~/.local/state/vults/logs/ The log: one file a day, the last five kept. It records what happened (event names, decisions, errors), never commands, paths or chat text $XDG_CONFIG_HOME and $XDG_DATA_HOME replace ~/.config and ~/.local/share when they are set. Settings → About shows the app\u0026rsquo;s version and the real paths.\nsettings.json # { \u0026#34;version\u0026#34;: 22, \u0026#34;connectors\u0026#34;: { \u0026#34;github\u0026#34;: true }, \u0026#34;sounds\u0026#34;: true, \u0026#34;volume\u0026#34;: 50, \u0026#34;fold_after\u0026#34;: 15, \u0026#34;api_provider\u0026#34;: \u0026#34;openrouter\u0026#34;, \u0026#34;api_models\u0026#34;: { \u0026#34;openrouter\u0026#34;: \u0026#34;anthropic/claude-opus-5.5\u0026#34; } } Key Default Meaning version 22 Schema version, so later releases can migrate the file. If it is newer than the app\u0026rsquo;s (you went back to an older release), the app (from 0.1.1 on) uses the keys it knows, keeps the file as it was in settings.json.v\u0026lt;version\u0026gt;-\u0026lt;time\u0026gt; and says so in the log; the next change you make saves only the keys it knows, as its own version. Until then each start keeps another copy. If no copy can be kept, changes are not saved. To go back to the newer release\u0026rsquo;s settings, restore that copy. An older file is read with defaults for the keys it lacks, and is saved as the app\u0026rsquo;s version connectors {} Connector id → switched on sounds true 8-bit sounds volume 50 How loud the sounds play, in percent (0 to 100); 50 is how loud 0.1.0 played them voice_model absent The chat\u0026rsquo;s voice model (base, small, turbo), downloaded into ~/.local/share/vults/voice/ (with the speech detector, ggml-silero-v6.2.0.bin); absent keeps voice off voice_language absent What the user speaks for the voice: a code (pt, en), auto to detect it each time, absent to follow the system\u0026rsquo;s language now_playing false Show the song your media players are playing on the island (MPRIS on Linux), with play, pause and skip codex_usage false Read Codex\u0026rsquo;s plan usage for the island every 5 minutes (and half a minute after a window resets), through the codex you logged into (each read reaches OpenAI, through the app\u0026rsquo;s network gate). Off on a new install; a file from before version 16 gets it on when a codex is on the PATH, so an upgrade keeps the meters it showed (from version 16) web false Zeca\u0026rsquo;s web tools for the API chat: a search and a page read the model may ask for, each a request through the network gate. Off, it is never offered them web_engine \u0026quot;brave\u0026quot; Where searches go: brave, tavily (each with its key in the keyring) or searxng searxng_url absent Your own SearXNG\u0026rsquo;s address (https://…), when that is the engine phone false Answer cards from a paired phone: each card goes to it and its answers are read back, through the network gate. Set from Settings → Phone (from version 17) phone_transport \u0026quot;relay\u0026quot; How the phone reaches this computer: relay (through phone_relay) or direct (this computer listens on phone_listen) phone_relay absent The relay\u0026rsquo;s address (https://…; http:// only on a network you trust) phone_listen absent In direct, this computer\u0026rsquo;s address on your LAN or tailnet and a port (100.64.0.2:8787) chat_picks absent The chat\u0026rsquo;s model and effort per folder (\u0026quot;\u0026quot; for its own empty folder), then per engine: { \u0026quot;/home/me/site\u0026quot;: { \u0026quot;claude\u0026quot;: { \u0026quot;model\u0026quot;: \u0026quot;sonnet\u0026quot;, \u0026quot;effort\u0026quot;: \u0026quot;high\u0026quot; } } }. model is a name the CLI takes (opus, gpt-6.1-sol), effort one of low, medium, high, xhigh, max; either absent is the CLI\u0026rsquo;s default. Set from the chat\u0026rsquo;s picker; a new conversation in that folder starts with it native_chat true The chat\u0026rsquo;s claude and codex read your own setup: your CLAUDE.md, skills, MCP servers, hooks and allow rules; a project\u0026rsquo;s settings only in a folder you already trusted in Claude Code. Cards still ask whatever your rules don\u0026rsquo;t allow. Off, Claude reads none of it and Codex leaves out your hooks (it always reads its own config.toml). Applies to the next conversation chat_access \u0026quot;ask\u0026quot; Who answers Zeca\u0026rsquo;s cards (ADR 0004): ask (a card each time), policies (your accepted policies answer what they cover) or full (everything runs with no card, but destructive commands and what a policy denies or asks first; cards come back once a conversation reads from outside). Changed in Settings → Chat \u0026amp; voice; a value it can\u0026rsquo;t read asks. Holds at once memory false Zeca\u0026rsquo;s memory and journal; off, nothing of it reaches a model (Zeca\u0026rsquo;s memory) (from version 18) journal_summary false With memory on, A summary of each day of the journal, by Claude Code\u0026rsquo;s smallest model (Zeca\u0026rsquo;s memory) (from version 18) local_embeddings false With memory on, Recall by meaning, through an Ollama on 127.0.0.1 (Zeca\u0026rsquo;s memory) (from version 18) memory_suggest false With memory on, Suggest what to remember from the conversation you left (Zeca\u0026rsquo;s memory) (from version 18) sleep false With memory on, Let Zeca sleep on it: his nightly sleep also asks a model for merges and a debrief (Zeca\u0026rsquo;s memory) (from version 18) journal_summary_since absent The day journal_summary was turned on (YYYY-MM-DD): the journal kept before it never goes to the model (from version 18) fold_after 15 Seconds the open island waits, once the pointer leaves, before folding (5 to 120) open_on_hover false Resting the pointer on the pill opens the island, without a click; opened that way it folds as soon as the pointer leaves, unless you clicked in it. Not by the panel. Set from Settings → General (from version 11) monitor absent The screen the island sits on, as maker and model (\u0026quot;Samsung Electric Company LS27AG32x\u0026quot;); absent lets the desktop choose. Two identical screens share a name, and the first one wins nest_size absent The Nest\u0026rsquo;s window as you last left it, [width, height] in logical pixels; absent, it opens at its default size (from version 21) rules [] Always-allow rules: { \u0026quot;agent\u0026quot;, \u0026quot;cwd\u0026quot;, \u0026quot;tool\u0026quot;, \u0026quot;target\u0026quot; }, each matched exactly zeca_species \u0026quot;atratus\u0026quot; Zeca\u0026rsquo;s species, by id (atratus is the black vulture; the ids are in ui/src/character/flock/species.ts); an unknown one draws the black vulture zeca_look \u0026quot;auto\u0026quot; What Zeca wears: auto (the calendar\u0026rsquo;s look: witch-hat Oct 1 to Nov 1, santa-hat Dec 1 to 26, party-hat Dec 31 to Jan 2, bunny-ears Good Friday to Easter Monday), none, or one of those or an all-year look (sunglasses, west-coast, fitted-cap, mountain-hat, headband, dreads, front-knot, durag, crown, bucket-hat, clock-chain, headphones, shutter-shades, chrome-chain, eye-patch). An unknown one is auto flock \u0026quot;brazil\u0026quot; Where the other sessions\u0026rsquo; birds are drawn from: brazil (Brazil\u0026rsquo;s vultures), americas (with both condors) or world (every vulture). Each project draws its species from it, the same at every start; changing it draws every project again. The king vulture comes by role either way presence \u0026quot;island\u0026quot; The presence preset: island (the flock at the top of the screen), panel (Zeca in the panel\u0026rsquo;s tray; the island opens by the panel when you click him or a card needs you), quiet (nothing at rest; a card still opens the island with its sound) or paused (cards go to the agents\u0026rsquo; terminals at once, connectors stop, no notifications). Version 4 files hold island or panel and read as is. An unknown value is island zeca true Zeca, the companion: off, no chat, microphone or talk shortcut, and the tray has no Chat…; the flock, cards, notifications and connectors work as ever widget absent The corner widget\u0026rsquo;s corner: top-left, top-right, bottom-left or bottom-right; absent (the default) for no widget. An unknown value is no widget projects absent Choices per project folder, set from a session\u0026rsquo;s quick actions or Settings → Projects: { \u0026quot;/home/me/site\u0026quot;: { \u0026quot;pin\u0026quot;: true }, \u0026quot;/home/me/x\u0026quot;: { \u0026quot;mute\u0026quot;: true, \u0026quot;hide\u0026quot;: true } }. mute: no sounds or notifications from its sessions at rest (a card keeps both); pin: its sessions first; hide: its sessions off the island, the tray and the widget, except while one has a card waiting; species: its sessions\u0026rsquo; bird, a species id of the renderer (\u0026quot;vultur\u0026quot;), any but the king vulture\u0026rsquo;s (\u0026quot;papa\u0026quot;), which is ignored like an unknown one (from version 12). Only the choices that are on are written, and a project with none is dropped (from version 8) delegate absent Which agent a project\u0026rsquo;s delegated tasks start on, by its roost ({ \u0026quot;github:me/site\u0026quot;: \u0026quot;codex\u0026quot; }, claude or codex), set with /delegate default in the chat; once the project has 10 finished tasks of a kind, their scores pick instead (from version 19) budget_daily_cents absent The daily budget, in US cents (500 is $5.00), set in the Nest\u0026rsquo;s Activity → Cost: at 80% a notification, and once spent another, and Vults starts no new session that day. It never stops or blocks an agent. Absent, no budget (from version 20) budget_told absent The budget\u0026rsquo;s step already told today (2026-10-11:1 for 80%, :2 once spent), so a restart does not tell it again (from version 20) dnd_until absent Do not disturb until then, in seconds since the Unix epoch: no sounds and no notifications at rest, no reminders; a card still opens the island with its sound (and in Panel its notification). Set from Settings → General; a time already past is off (from version 9) history true Keep a local history of agent turns for Settings → Activity: each finished turn\u0026rsquo;s counts (12 weeks) and each day\u0026rsquo;s totals (a year), in vults.sqlite in the data folder. Off, nothing new is kept and what is there stays; Clear history removes all of it (from version 13), and the audit log with it recap_shown_week absent The Monday of the last week whose recap card you read on the island, so it comes once (from version 14) language absent The app\u0026rsquo;s language: en, pt-BR, es or zh (Simplified Chinese), set in Settings → General. Absent follows the system (LC_ALL, LC_MESSAGES or LANG; the user\u0026rsquo;s locale on Windows), and a language Vults does not speak is English (from version 15) notifications true Desktop notifications, only in Panel (at the top of the screen the island shows it all): a session finished, failed or gone quiet, and a card waiting, at once. Their only action opens the island visitors true Now and then, while sessions are open, a vulture from outside the flock crosses the sky once, never landing api_provider \u0026quot;anthropic\u0026quot; The API chat\u0026rsquo;s provider: anthropic, openai, google, openrouter, groq, deepseek, mistral, xai, ollama, lmstudio api_models {} Provider → the model chosen for it (keys are never here) Agent configs Vults edits # Only when you click Write the file, after a dated backup and a diff you reviewed:\nAgent File What is added Claude Code ~/.claude/settings.json One hook entry per event, running vults-hook --agent claude, and a statusLine running vults-hook --agent claude --statusline (if you have one of your own, only its command changes, and the hook keeps running yours) Codex ~/.codex/hooks.json One hook entry per event, running vults-hook --agent codex Antigravity ~/.gemini/config/hooks.json One top-level hook named vults, with one handler per event running vults-hook --agent antigravity \u0026lt;Event\u0026gt; || exit 0 (timeouts in seconds). The other named hooks in the file are never touched, Remove hooks… deletes only the vults key, with anything inside it, and turning it off with \u0026quot;enabled\u0026quot;: false in Antigravity is kept. A vults hook of yours that does not run ours is never overwritten: installing is refused OpenCode ~/.config/opencode/plugins/vults.js (under $XDG_CONFIG_HOME when set) A plugin of ours, the whole file, that relays OpenCode\u0026rsquo;s steps, permissions and questions through vults-hook --agent opencode. A file there without its Generated by Vults first line is yours and is never written Qwen Code ~/.qwen/settings.json One hook entry per event, running vults-hook --agent qwen Copilot CLI ~/.copilot/hooks/vults.json A file of ours, one hook per event running vults-hook --agent copilot. Copilot reads every file in that folder; the others are never touched Cursor ~/.cursor/hooks.json One hook per event, running vults-hook --agent cursor \u0026lt;event\u0026gt; || exit 0; none of the hooks that run before a step, whose output Cursor reads as a decision Factory Droid ~/.factory/settings.json One hook entry per event, running vults-hook --agent droid Kimi Code ~/.kimi-code/config.toml One [[hooks]] table per event, running vults-hook --agent kimi; your comments and layout stay Vults no longer supports Gemini CLI. While hooks an earlier Vults installed in ~/.gemini/settings.json are still there, they send nothing, and Settings → Agents shows a Gemini CLI card whose only button is Remove hooks… (the same diff, dated backup and click).\nEach agent\u0026rsquo;s own folder variable moves its file, read the way the agent reads it: CLAUDE_CONFIG_DIR (Claude Code, in place of ~/.claude), CODEX_HOME (Codex, in place of ~/.codex, its config.toml too), QWEN_HOME (Qwen Code, in place of ~/.qwen), COPILOT_HOME (Copilot CLI, in place of ~/.copilot), FACTORY_HOME_OVERRIDE (Factory Droid, in place of the home: its .factory goes inside), KIMI_CODE_HOME (Kimi Code, in place of ~/.kimi-code) and GEMINI_CLI_HOME (Gemini CLI keeps its .gemini inside it; read only to find the old hooks to remove). Started from the desktop, the app does not see what your shell\u0026rsquo;s startup files export, so it asks your login shell once, when it starts, and keeps only these seven; a shell that takes more than two seconds is given up on, and the usual folders stand. The Claude Code and Codex the app starts itself (the chat, the Codex usage read) get them too, so they use the same folders as in your terminal. Each card in Settings → Agents shows the file it edits. OpenCode\u0026rsquo;s OPENCODE_CONFIG_DIR is not followed yet.\nA write never leaves half a file: the new text goes to a file of its own beside it, is synced to disk, and only then takes the old one\u0026rsquo;s place. A file with other names (a hard link, as some dotfiles managers and backup tools make) is written in place, so every name sees the change. A file saved with a byte-order mark or Windows line ends (CRLF) keeps them.\nEntries from other tools are kept, and Remove hooks… takes out only ours. If you put another tool\u0026rsquo;s hook in the same group as ours, that group stays: an update changes only our hook in it, and Remove takes out only our hook. In Codex, a hook or group that came after one of ours we take out moves up one place, so Codex may ask you to trust it again in /hooks. An update changes our entries where they are: the file keeps its key order, so the diff shows only what changed. Hooks that run another copy of vults-hook (from another data folder) say so in Settings → Agents, and Update hooks… points them to this app\u0026rsquo;s (if your own status line is saved beside that other hook, the card offers no update and says so: remove the hooks first, see below).\nA config holds a single statusLine, and Claude Code reports the plan\u0026rsquo;s usage only to it. If you have one of your own, installing saves its whole object in statusline-previous.json beside the hook and changes only its command to ours (your padding and other fields stay); the diff shows both files. Ours then hands the usage to the app, runs your command with the same input (through sh -c, up to 10 seconds) and prints exactly what it prints (up to 64 KiB), colors included. If your command is gone, fails to start or takes longer, the status line is blank that time, whatever it started in the background is stopped too, and Claude Code carries on. Remove hooks… puts your object back as it was; a statusLine you changed since is no longer ours and is left alone. Hooks installed by another data folder keep your line beside their hook: remove them before installing from this one.\nEnvironment # Variable Effect VULTS_NO_LAYER_SHELL Use a plain always-on-top window even where layer-shell exists VULTS_LOG Log filter, e.g. debug (default info) VULTS_LEAN Render the island in software (WebKit without compositing): about 44 MB less, but WebKitGTK 2.54 and newer leave parts of the island unpainted. Off by default ","externalUrl":null,"permalink":"/en/docs/vults/reference/settings/","section":"Docs","summary":" Where things live (Linux) # Path What ~/.config/vults/settings.json The app\u0026amp;rsquo;s settings. A field it can\u0026amp;rsquo;t read (a wrong type) falls back to its …","title":"Settings and files · Vults","type":"docs"},{"content":"vults-hook and the app exchange one JSON object per line over a local socket.\nTransport # Platform Endpoint Access control Linux $XDG_RUNTIME_DIR/vults.sock (fallback /tmp/vults-\u0026lt;uid\u0026gt;/, folder 0700) socket 0600, SO_PEERCRED Windows \\\\.\\pipe\\vults-\u0026lt;SID\u0026gt; both ends check the other process\u0026rsquo;s SID macOS planned getpeereid Messages # Hook to app, version 8:\n{ \u0026#34;kind\u0026#34;: \u0026#34;event\u0026#34;, \u0026#34;v\u0026#34;: 8, \u0026#34;id\u0026#34;: \u0026#34;18f…-1a2b\u0026#34;, \u0026#34;agent\u0026#34;: \u0026#34;claude\u0026#34;, \u0026#34;event\u0026#34;: \u0026#34;PermissionRequest\u0026#34;, \u0026#34;wants_reply\u0026#34;: true, \u0026#34;terminal\u0026#34;: { \u0026#34;cwd\u0026#34;: \u0026#34;/home/me/project\u0026#34;, \u0026#34;pid\u0026#34;: 4242, \u0026#34;env\u0026#34;: { \u0026#34;TERM_PROGRAM\u0026#34;: \u0026#34;kitty\u0026#34; } }, \u0026#34;payload\u0026#34;: { \u0026#34;tool_name\u0026#34;: \u0026#34;Bash\u0026#34;, \u0026#34;tool_input\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;cargo test\u0026#34; } } } agent: claude, codex, opencode (our plugin), qwen, copilot, or other for any other tool (see Other agents). Antigravity, Cursor, Factory Droid and Kimi Code are built in but sent as other, with agent_name antigravity, cursor, droid and kimi. agent_name: only with other, the tool\u0026rsquo;s name: 1 to 24 of a-z, 0-9 and -. Refused: the names the wire has a kind for (claude, codex, copilot, opencode, qwen and other) and gemini, which has no kind since version 7 but stays taken because Gemini CLI\u0026rsquo;s old hooks may still be installed. Any other name, those four built-ins\u0026rsquo; included, is a named other, and an other never gets a card. Absent otherwise. id: unique per message, opaque. zeca: true only on an event from the chat\u0026rsquo;s own claude or codex (the hook sees VULTS_ZECA=1 in its environment), absent otherwise. Such an event is Zeca\u0026rsquo;s work: the app never makes it a session, a card or a bird, and it never has wants_reply, since the chat asks its own cards. Setting the variable elsewhere only hides that agent from the island; its own terminal still asks. Every process the chat\u0026rsquo;s CLI starts inherits it: a claude the chat launches through a command is hidden too. terminal: every field is optional. env only lists the variables that were set among those identifying the terminal, plus VULTS_TASK in a session one of your tasks started (its bird takes off from Zeca). cwd is the payload\u0026rsquo;s cwd, else the first entry of workspacePaths (Antigravity) or workspace_roots (Cursor), else the hook\u0026rsquo;s own working folder. payload: the agent\u0026rsquo;s hook JSON without tool_response, transcript_path (and Antigravity\u0026rsquo;s transcriptPath and artifactDirectoryPath); strings are capped at 2000 bytes. Two things survive in tool_response: a tool\u0026rsquo;s error, and on a PostToolUse Claude Code\u0026rsquo;s structuredPatch, cut to its first 400 lines (with \u0026quot;cut\u0026quot;: true when lines were left out). On a PostToolUse of Codex\u0026rsquo;s apply_patch, tool_input.command (the patch) keeps up to 64 KiB. The fields are optional and the envelope did not change, so this needed no new version. An event that would still pass the 1 MiB message size has its strings cut shorter, then keeps only the fields that say what happened (hook_event_name, session_id, cwd, tool_name…): the app drops a longer line unread. Such an event never waits for an answer (wants_reply is false): its card could not show the whole of what Allow would authorize, so the agent\u0026rsquo;s own terminal asks. An event waits for a reply (wants_reply) when it is a PermissionRequest from Claude Code, Codex, OpenCode, Qwen Code or Copilot CLI, or a PreToolUse for AskUserQuestion from Claude Code or OpenCode sent by an entry installed with --ask (vults-hook --agent claude --ask PreToolUse, with a 120-second timeout; OpenCode\u0026rsquo;s plugin runs the relay the same way for its question tool, in Claude Code\u0026rsquo;s words). An entry without the flag has the short timeout of every other event, so it never waits.\nApp to hook, only when wants_reply is true:\n{ \u0026#34;kind\u0026#34;: \u0026#34;waiting\u0026#34;, \u0026#34;v\u0026#34;: 8, \u0026#34;id\u0026#34;: \u0026#34;18f…-1a2b\u0026#34; } { \u0026#34;kind\u0026#34;: \u0026#34;decision\u0026#34;, \u0026#34;v\u0026#34;: 8, \u0026#34;id\u0026#34;: \u0026#34;18f…-1a2b\u0026#34;, \u0026#34;decision\u0026#34;: \u0026#34;allow\u0026#34; } { \u0026#34;kind\u0026#34;: \u0026#34;answer\u0026#34;, \u0026#34;v\u0026#34;: 8, \u0026#34;id\u0026#34;: \u0026#34;18f…-1a2b\u0026#34;, \u0026#34;answers\u0026#34;: [\u0026#34;Blue\u0026#34;, [\u0026#34;S\u0026#34;, \u0026#34;M\u0026#34;]] } { \u0026#34;kind\u0026#34;: \u0026#34;unsupported\u0026#34;, \u0026#34;v\u0026#34;: 8, \u0026#34;id\u0026#34;: \u0026#34;18f…-1a2b\u0026#34; } waiting comes first, once the card is on screen: a human is being asked, and the decision may take a while. A hook that hears neither waiting nor the reply within 2 s gives up, so an app that is stopped or frozen (whose socket the kernel still accepts) holds an agent for 2 s, not for the whole decision budget. A decision that beats the card (an Always rule) comes without it.\nanswers has one entry per question, in the order of tool_input.questions: a string (a choice\u0026rsquo;s label, or the user\u0026rsquo;s own words) or, for a multi-select, a list of them. It goes by position because the app only saw the questions with their strings capped; the hook keys the answers with the questions it read.\nAn event from other never has wants_reply: nothing in the app answers another tool\u0026rsquo;s permission.\nStatusLine is Claude Code\u0026rsquo;s statusLine input, sent by vults-hook --agent claude --statusline. Its payload is only rate_limits (the plan\u0026rsquo;s 5-hour and weekly windows), cost.total_cost_usd (what the session\u0026rsquo;s tokens would cost at API prices, for its card) and session_id; the session\u0026rsquo;s paths, its other cost figures and its model never leave the hook. Before the session\u0026rsquo;s first reply there are no rate_limits, and with no cost either nothing is sent. The hook prints only what the user\u0026rsquo;s own status line prints (saved on install, see settings), or nothing when there is none, so Claude Code\u0026rsquo;s status line looks as it did before.\nVersion 2 added other and agent_name, then gemini. Version 3 added answer. Version 4 added opencode, which an older hook sent as other. Version 5 added waiting, then qwen, which an older hook sends as other (the app reads it as any other tool\u0026rsquo;s, without a card). Version 6 added zeca. Version 7 removed gemini: hooks left from Gemini CLI send nothing. Version 8 added copilot. The app installs its own hook when it starts, so the two always speak the same version; an event from another version gets unsupported.\nA connection that gets no reply, a reply for another id, or unsupported makes the hook print nothing.\nLimits # Limit Value Message size 1 MiB Hook: connect 300 ms Hook: event with no reply 2 s in total Hook: hearing waiting 2 s Hook: waiting for a decision 110 s App: UI acknowledgement of a card 800 ms App: decision 108 s App: a card\u0026rsquo;s answer accepted 107 s from when the hook\u0026rsquo;s line was read (1 s left for the answer to travel back) App: reading a message 5 s App: simultaneous connections 32 Agent output # The hook turns a decision into the format each agent expects. Claude Code and Codex read the same PermissionRequest output:\n{\u0026#34;hookSpecificOutput\u0026#34;:{\u0026#34;hookEventName\u0026#34;:\u0026#34;PermissionRequest\u0026#34;,\u0026#34;decision\u0026#34;:{\u0026#34;behavior\u0026#34;:\u0026#34;allow\u0026#34;}}} {\u0026#34;hookSpecificOutput\u0026#34;:{\u0026#34;hookEventName\u0026#34;:\u0026#34;PermissionRequest\u0026#34;,\u0026#34;decision\u0026#34;:{\u0026#34;behavior\u0026#34;:\u0026#34;deny\u0026#34;,\u0026#34;message\u0026#34;:\u0026#34;Denied from Vults\u0026#34;}}} Copilot CLI reads the bare decision:\n{\u0026#34;behavior\u0026#34;:\u0026#34;allow\u0026#34;} {\u0026#34;behavior\u0026#34;:\u0026#34;deny\u0026#34;,\u0026#34;message\u0026#34;:\u0026#34;Denied from Vults\u0026#34;} OpenCode\u0026rsquo;s plugin reads the reply it hands to OpenCode\u0026rsquo;s permission endpoint, once or reject (never OpenCode\u0026rsquo;s always: an Always allow is the app\u0026rsquo;s own rule):\n{\u0026#34;reply\u0026#34;:\u0026#34;once\u0026#34;} {\u0026#34;reply\u0026#34;:\u0026#34;reject\u0026#34;} and, for answers, one list of labels (or the user\u0026rsquo;s own words) per question, in order, which it hands to OpenCode\u0026rsquo;s question endpoint:\n{\u0026#34;answers\u0026#34;:[[\u0026#34;Blue\u0026#34;],[\u0026#34;S\u0026#34;,\u0026#34;M\u0026#34;]]} An answer becomes Claude Code\u0026rsquo;s PreToolUse output: the tool runs with the answers added to its own input, keyed by each question\u0026rsquo;s text (checked against Claude Code 2.1.286):\n{\u0026#34;hookSpecificOutput\u0026#34;:{\u0026#34;hookEventName\u0026#34;:\u0026#34;PreToolUse\u0026#34;,\u0026#34;permissionDecision\u0026#34;:\u0026#34;allow\u0026#34;, \u0026#34;updatedInput\u0026#34;:{\u0026#34;questions\u0026#34;:[…],\u0026#34;answers\u0026#34;:{\u0026#34;Which color?\u0026#34;:\u0026#34;Blue\u0026#34;,\u0026#34;Which sizes?\u0026#34;:[\u0026#34;S\u0026#34;,\u0026#34;M\u0026#34;]}}}} When the number of answers does not match the questions, the hook prints nothing and Claude Code asks in its terminal.\n","externalUrl":null,"permalink":"/en/docs/vults/reference/protocol/","section":"Docs","summary":"vults-hook and the app exchange one JSON object per line over a local socket.\nTransport # Platform Endpoint Access control Linux $XDG_RUNTIME_DIR/vults.sock …","title":"Hook protocol · Vults","type":"docs"},{"content":" The same lists show inside the TUI: ? opens the help of the current tab. : opens the command palette, which runs any entry below by typing part of its name.\nWhile a text field, filter or confirmation is open it owns the keyboard: esc closes it, enter confirms.\nNavigation # Key Action tab next tab shift+tab previous tab 1-9 go to tab N : command palette ? open or close help q quit Skills tab # Skills # Key Action enter read SKILL.md e edit in $EDITOR n new skill o adopt into library A adopt all local skills d remove (with backup) i install (GitHub/folder/zip) S search GitHub (registry) Activation # Key Action ←/→ pick agent (column) space toggle in picked agent a enable in all x disable in all Profiles \u0026amp; updates # Key Action p profiles: enter applies, s saves the matrix, d deletes u update this skill U check for updates b backups List # Key Action shift+↑/↓ scroll detail / filter r reload Sessions tab # Sessions # Key Action enter resume v read the transcript R resume in another folder c show the command m alias (empty removes it) d delete (with backup) List # Key Action shift+↑/↓ scroll the detail pgup/pgdn page through the list space select (batch/group) g group by project+agent f cycle the agent filter F search the transcripts / filter r reload Transcript (v) # Key Action n · N your next · previous prompt g · G top · end m view: log / conversation / actions ] · [ pick the next · previous turn or subagent enter unfold the picked turn · open the subagent e steps (⋯): every step / only the answer t commands (❯): one per line / summarized r reasoning (💭): full / first line only x export to Markdown esc back to the list Providers tab # Providers # Key Action ↑/↓ · j/k select profile ←/→ select agent (column) space apply to the selected agent (again to clear) a apply to all installed agents shift+↑/↓ · ctrl+u/d scroll the detail x clear the provider from all agents n new profile (form) e edit the profile; an empty token keeps the saved one d delete the profile from the library r reload Create a profile # Key Action lazyagents provider add create a profile from the CLI --token - read the token from stdin Hooks tab # Hooks # Key Action ↑/↓ · j/k select hook ←/→ select agent (column) space install in the selected agent (again uninstalls) a install in every installed agent that fires one of its events enter pick the commands of the pack (space toggles, esc goes back) x uninstall from every agent d delete the hook from the library pgup/pgdn · ctrl+u/d read the detail and long commands, also while selecting shift+↑/↓ scroll the detail one line v full-screen reader: ←→ switches command/scripts, e edits r reload Create a hook # Key Action lazyagents hooks add create a hook from the CLI ~/.local/share/lazyagents/hooks one JSON per hook, editable by hand Usage tab # Filters # Key Action p / P period: today · 7 · 30 · 90 days · all a / A agent: all or just one ←/→ · v view: day · agent · project · model / filter table rows by name esc clear the text; again, back to all agents Usage # Key Action r refresh limits (queries the API) ↑/↓ · j/k scroll pgup/pgdn · space scroll one page g / home back to the top Agents tab # Agents # Key Action ↑/↓ · j/k select agent shift+↑/↓ · ctrl+u/d scroll the detail g · G first · last Command palette # Type : and part of a name. Tab entries run inside their tab.\nEntry What it does skills open the Skills tab sessions open the Sessions tab providers open the Providers tab hooks open the Hooks tab usage open the Usage tab agents open the Agents tab providers new create a provider profile providers clear clear the provider from all agents usage refresh refresh usage limits usage period today usage: period today usage period 7d usage: period 7 days usage period 30d usage: period 30 days usage period 90d usage: period 90 days usage period all usage: period all usage view daily usage by day usage view agents usage by agent usage view projects usage by project usage view models usage by model usage clear usage: clear agent and text help open help for the current tab reload reload the current tab quit quit lazyagents ","externalUrl":null,"permalink":"/en/docs/lazyagents/reference/keys/","section":"Docs","summary":" The same lists show inside the TUI: ? opens the help of the current tab. : opens the command palette, which runs any entry below by typing part of its name. …","title":"Key reference · lazyagents","type":"docs"},{"content":" What lazyagents can do with each agent, read from the adapters in internal/agent. Paths are the defaults on Linux and macOS. How sessions are read per agent is in the sessions guide.\nSkills # A directory marked (shared) is read by several agents: lazyagents links a skill there only while it is enabled for every installed agent that reads it, and otherwise in each agent\u0026rsquo;s own directory.\nAgent id Enables skills in Also reads Claude Code claude-code ~/.claude/skills — Codex codex ~/.codex/skills ~/.agents/skills (shared) Gemini CLI gemini-cli ~/.gemini/skills ~/.agents/skills (shared) OpenCode opencode ~/.config/opencode/skills ~/.claude/skills, ~/.agents/skills (shared) Claude Desktop claude-desktop — (not manageable locally) — Hermes Agent hermes-agent ~/.hermes/skills — Pi pi ~/.pi/agent/skills ~/.agents/skills (shared) Crush crush ~/.config/crush/skills ~/.config/agents/skills, ~/.claude/skills, ~/.agents/skills (shared) Capabilities # Agent Providers (writes) Hooks (writes) Subscription limits Usage history Live-session badge Claude Code ~/.claude/settings.json ~/.claude/settings.json yes yes yes Codex ~/.codex/config.toml ~/.codex/hooks.json yes yes — Gemini CLI — — — — — OpenCode — — — — — Claude Desktop — — — — — Hermes Agent — — — — — Pi ~/.pi/agent/models.json — — yes — Crush ~/.config/crush/crushrc ~/.config/crush/crushrc — — — Config overrides # Environment variables that move an agent\u0026rsquo;s files. lazyagents reads the same ones, so it looks where the CLI does; a relative value is ignored.\nAgent Variable Moves Claude Code CLAUDE_CONFIG_DIR ~/.claude: sessions, skills, settings, hooks, login Codex CODEX_HOME ~/.codex: sessions, skills, config.toml, hooks OpenCode XDG_CONFIG_HOME ~/.config: opencode/ config and skills OpenCode XDG_DATA_HOME ~/.local/share: opencode/opencode.db OpenCode OPENCODE_DB the session database file itself Hermes Agent HERMES_HOME ~/.hermes (or the hermes profile use profile): skills and config.yaml Hermes Agent LOCALAPPDATA Windows only: Hermes\u0026rsquo; default home is %LOCALAPPDATA%\\hermes instead of ~/.hermes Crush XDG_CONFIG_HOME ~/.config: crush/ config and skills, and agents/skills Crush XDG_DATA_HOME ~/.local/share: crush/projects.json, the index of project sessions Crush CRUSH_GLOBAL_CONFIG the dir of crushrc and crush.json (not the skills, which stay in ~/.config/crush/skills) Crush CRUSH_GLOBAL_DATA ~/.local/share/crush itself Crush CRUSH_SKILLS_DIR replaces every global skill dir: Crush then loads skills from that dir only Pi PI_CODING_AGENT_DIR ~/.pi/agent: everything Pi PI_CODING_AGENT_SESSION_DIR \u0026lt;agent dir\u0026gt;/sessions Hook events # A hook only installs in agents that fire its event; the TUI marks the others with –.\nEvent Claude Code Codex Crush SessionStart yes yes — UserPromptSubmit yes yes — PreToolUse yes yes yes PostToolUse yes yes — Notification yes — — Stop yes — — SubagentStop yes — — PreCompact yes yes — SessionEnd yes yes — ","externalUrl":null,"permalink":"/en/docs/lazyagents/reference/agents/","section":"Docs","summary":" What lazyagents can do with each agent, read from the adapters in internal/agent. Paths are the defaults on Linux and macOS. How sessions are read per agent is …","title":"Agent support · lazyagents","type":"docs"},{"content":"A paired phone gets its cards and sends its answers through a relay: a small server that keeps sealed messages until the other side reads them. This page is for running one, or checking what one can see; pairing and answering are in The phone.\nWhat it sees # Each direction between the desktop and one phone has its own box, addressed by 32 random-looking bytes that only those two can derive from their keys. A message is sealed (XChaCha20-Poly1305, with a key from X25519) before it leaves either end and names no device, so the relay holds only addresses and ciphertext: no command, no project, no name. It still sees where requests come from and when, which can tell it that two boxes belong together. A message stays until the reader\u0026rsquo;s next read says it has it, and is dropped after 10 minutes even if nobody read it. There are no accounts and the relay writes nothing to disk.\nA box\u0026rsquo;s address is its only key: whoever learns it can read the ciphertext, or say it was read so the relay drops it. Reach the relay over HTTPS or a tailnet, and keep request paths out of a reverse proxy\u0026rsquo;s logs.\nAn answer from the phone counts only if it is signed by that phone\u0026rsquo;s key, made for that exact card, in time, and never seen before. A relay that drops, delays or replays messages can make a card wait or go to the terminal; it can never answer one.\nRunning it # From the repository\u0026rsquo;s root:\ndocker build -f packaging/relay/Dockerfile -t vults-relay . docker run -p 8787:8787 vults-relay or without a container:\ncargo run --release -p vults-relay --bin vults-relay VULTS_RELAY_ADDR says where it listens (0.0.0.0:8787 by default). It speaks plain HTTP: put a reverse proxy with TLS in front of it, or reach it only over a tailnet or your LAN. A proxy in front should limit connections per address too: the relay\u0026rsquo;s own caps are for the whole server.\nLimits # What Limit A message 64 KiB Messages waiting in one box 64 Boxes at once 10,000 Messages held in all 256 MiB A message left unread 10 minutes A read waiting for a message 25 seconds A request, its body included 35 seconds Connections at once (more wait their turn) 1,000 A connection that sends nothing 60 seconds HTTP # Request Does POST /v1/boxes/{box} Keeps the body (one sealed message). 202; 400 the body is not UTF-8; 404 an address that is not 64 lowercase hex characters; 413 too big; 429 the box is full; 503 too many boxes, or too many bytes held GET /v1/boxes/{box}?after=n\u0026amp;wait=s Drops the messages up to n (the reader has them) and hands out the rest as a JSON array of {\u0026quot;seq\u0026quot;, \u0026quot;msg\u0026quot;}; with nothing there, waits up to s seconds (25 at most) for something to come. 400 a query that is not two numbers; 404 an address that is not one GET /health ok Trying it # Two command-line stand-ins play the desktop and the phone:\ncargo run -p vults-relay --bin vults-relay cargo run -p vults-relay --example desk -- http://127.0.0.1:8787 cargo run -p vults-relay --example phone -- \u0026#39;\u0026lt;the QR text desk printed\u0026gt;\u0026#39; desk shows the QR\u0026rsquo;s text, waits for the phone, and sends it one card; phone pairs, shows the card and sends your answer, signed; desk prints what Vults would do with it.\n","externalUrl":null,"permalink":"/en/docs/vults/reference/relay/","section":"Docs","summary":"A paired phone gets its cards and sends its answers through a relay: a small server that keeps sealed messages until the other side reads them. This page is for …","title":"The phone's relay · Vults","type":"docs"},{"content":"Zeca and the vults are black vultures (Coragyps atratus). Every clip is built from something a real black vulture does, so the bird reads as a vulture before it reads as a status light.\nWhat a black vulture looks like # Matte black plumage with a faint blue sheen; a short, square tail. A bare, wrinkled, dark gray head on a short neck; a dark brown eye under a brow ridge; a slender, dark bill whose pale horn tip hooks down at the very end. Pale gray legs. In flight, broad short wings whose primaries are silvery-white underneath, splayed like fingers. How it moves # Behavior What it looks like Flapping flight Three or four quick, shallow, stiff beats, then a short glide on flat wings (no \u0026ldquo;V\u0026rdquo;, unlike the turkey vulture) Soaring Wide circles in a thermal, gliding, drifting upward Take-off A crouch and a hop, then deep, heavy downstrokes Landing Wings up and forward to brake, feet out, wings folded only after touching down Perched Hunched shoulders, head forward; it turns its head to watch and tilts it to inspect Sunning Wings spread wide, facing the sun: wrists highest, the hand hanging with its primaries splayed, the body round between them Threat Feathers raised, a hiss with the bill open Sleep Fluffed up, the head turned back and laid on the back, under the feathers Clips # Clip Activity or state Behavior it borrows idle idle Watching, blinking, a breath now and then, a shuffle of the feet, a look back over the shoulder think think Head drawn up and still, slow blinks read read Head down, scanning along a line and dropping to the next search search Neck out, quick head turns, a tilt to look closer edit edit The feeding motion: lean in, strike, tear back run run Quick tugs at the wire, feet shuffling for grip approval approval Sunning pose: it looks at you most of the time, turns its head to one side and the other between looks, and the spread wings settle and lift again question question The curious head tilt, held done done A hop that lands with a small squash, a wing-and-leg stretch, settle, then the species\u0026rsquo; signature (see below) fail fail Feathers up, a hiss, a shake; then slumped, head hung and a wing let down listen the chat\u0026rsquo;s mic is open The head cocked toward you, small nods as you speak dance music playing (Now playing on), Zeca on his own A bob on every beat, swaying, a foot tapping sleep sleep Fluffed up, the head turned back onto the back so only the crown shows over the shoulders swallow a file dropped on him Down to the wire, pick it up, toss the head back and gulp gape a file dragged over the island Head up, bill open, waiting for it to drop in preen the pointer resting on Zeca A bout of grooming, head into the wing startle a click on Zeca A jump with the wings flung up, head high hello the app starting A landing squash, a bounce up stretched tall and a softer landing; then he turns his head to you and waves a wing speak Unused: Zeca answers in text only, and nothing in the app plays it. It stays in the sprite and the lab, where the visual tests draw it Facing the chat, the bill opening and shutting in an uneven beat, a blink between phrases fly web A full sortie: take off, flap-flap-flap-glide out, glide home, flare, land, turn; each downstroke lifts the body a row The idle clip includes a preening bout (head into the wing, quick nibbles), and done stretches one wing down and back, with the leg on that side, before the species\u0026rsquo; signature: the stretch birds make when they finish something.\nFour more clips are built for every species from its own frames: doze (the head grows heavy twice, a jerk awake, then it turns onto the back and the bird sleeps), wake (an eye opens on the back, the head comes round, the wing-and-leg stretch (the king vulture, whose done clip stretches both wings, skips it) and a shake of the feathers), court (wings half open, head low, a hop every other beat and a look to each side; both condors open the wings fully, hold the head high and bow) and fed (after the swallow, the crop bulges under the band and empties in three sizes, in the bird\u0026rsquo;s own plumage, on the perch only).\nThe island plays them so:\ndoze: a bird resting on a perch where it does not soar nods off with it after 90 seconds, ±12% by its session, then sleeps: Zeca on the island\u0026rsquo;s empty wire and in the chat, the Nest\u0026rsquo;s flock and the corner widget (the island\u0026rsquo;s own idle flock soars instead). With reduced motion it goes straight to sleep. wake: work wakes a sleeping bird, which stretches before its work clip (or before it takes off, for web work). A permission, a question or a failure cuts straight in, with no stretch. court: with a song playing (Now playing), the flock\u0026rsquo;s idle birds court on the wire instead of soaring, and go back up when it stops. Zeca on his own still dances. fed: when Zeca swallows a file in the chat, the swallow runs on into the full crop, which goes down while he rests; thinking, listening or another file cuts it short. Zeca notices you # On the focus card and in the chat, a resting Zeca looks toward the pointer: over his shoulder when it is behind him, at you (with a blink) when it is on him. Rest it on him and he preens; click him and he startles; three clicks in a row and he puffs up and hisses. When the app starts he drops straight down onto his wire, lands with a squash (fluffed, sunk onto bent legs) and a bounce, and waves hello; the island folds once he is done. The squash is drawn with his own parts, so every species and every seasonal look lands the same way. With reduced motion there is no hello.\nMarks over the head # A small pixel mark over a bird\u0026rsquo;s head says its state at a glance, in the island\u0026rsquo;s state colors. It sits over whatever head pose the bird is in, and loops on its own.\nMark State Motion A thought bubble thinking its dots fill in one by one ! in amber a permission waits it hops ? in cyan a question it sways Green sparkles finished they twinkle and trade places # in red failed it shakes A drop of sweat rate limited it runs down z asleep they drift up They show on the focus card and in the chat (Zeca at 3x) and in the habitat (1x); the compact pill has no room over the heads and keeps its badges.\nThe flock on the wire # Zeca is never a session\u0026rsquo;s bird: he sits on the empty wire and in the chat. The session in front (the one that needs you, the one you picked, or the first to arrive) takes his spot in its own bird; every other session is a vult. With Zeca turned off (Settings → Flock \u0026amp; Zeca) an empty wire stays empty: no idle Zeca, no signature of his. A new session\u0026rsquo;s vult glides in from the right and lands; a session that ends takes off and flies away. Each running subagent sends out a scout: a Cathartes (the vultures that find food by smell) takes off from its session\u0026rsquo;s perch and circles low beside it, and flies off when the subagent ends. Three a session at most, six in all; only in the open island (the folded pill has no room), none with reduced motion. The flock and its scouts pass behind a connector\u0026rsquo;s card (the GitHub card), never over its text. Rare visitors: every ten to twenty minutes while sessions are open, a vulture nobody on the wire is (an Old World vulture or a condor) glides in from the right, rides the island\u0026rsquo;s thermal once and goes on its way, never landing; it passes behind the cards and rows, never over their text. Off in Settings → Flock \u0026amp; Zeca; none with reduced motion or while the island is hidden. The lab brings one at once: /lab/?visitor=1. After two seconds idle, a session\u0026rsquo;s bird takes off into the shared sky below the island. A finished session (the agent waiting for the next prompt) plays its done clip first, then takes off once that clip and the species\u0026rsquo; signature are over (six to eight seconds after its done state shows); its \u0026ldquo;done\u0026rdquo; badge stays until you dismiss it. Each session keeps its bird across compact/open transitions, at its own phase and lap speed. The flock circles indefinitely while idle, by day. Work brings only that session\u0026rsquo;s bird down; opening the island leaves the other birds in flight. No sessions means no decorative flock. A vulture\u0026rsquo;s day, by the local clock: no sun, no thermal. At dawn (6 to 9) the idle birds stay on the wire with their wings open to the sun (the spread-wing pose as its own sun clip, with no mark over it, so it never acts as a card); from 9 to 5 p.m. they soar; at dusk (5 to 7 p.m.) they come down, and at night they sleep on the wire, nodding off within seconds of resting. Zeca on his own suns himself at dawn too. The rare visitors come only by day, above the flock\u0026rsquo;s highest. Each species at its height: the Cathartes circle low, the black vulture in the middle, the condors, the king and the Old World\u0026rsquo;s high, in the pill as on the open island. Flights update at 30 fps using the desktop-compatible timer. Curved climbs and returns preserve orbital velocity, with subtle banking. In the thermal each species beats its wings as its own fly clip does (the Cathartes keep rocking on their V between bursts), and the wider the wings, the rarer the bursts: every 3.6 s for the black vulture, about every 10 s for a condor. A bird leaning into a turn is drawn, not rotated: in five steps the high wing lifts and the low one drops and loses a few columns, in whole cells the same size as level flight. Flight frames use the existing bitmap cache and nearest-neighbor rendering. The sky is a transparent, pointer-free canvas inside the existing 720 by 640 desktop surface; it does not enlarge the input region or remap the Linux layer-shell window. With reduced motion (including changes while flying), birds return to their static scenes. An idle bird on its perch dozes after 90 seconds, and an idle bird takes off after 2, each ±12% by its session, so the flock never moves in step. Hidden scenes stop their drawing timers. The agent shows as a small band at the base of the neck (Claude orange, Codex teal, OpenCode pink, Qwen indigo, Copilot blue, any other tool, Antigravity included, violet), never as the bird\u0026rsquo;s color. A clip plays at least 600 ms before a calmer one replaces it; approval, question and failure cut in at once. A flight always lands before the next clip starts. The species # Zeca is a black vulture unless you pick another species for him in Settings → Flock \u0026amp; Zeca. The flock draws from the vultures of Brazil by default; the same page widens it to the Americas (with both condors) or to every vulture in the world. Every species is built from Zeca\u0026rsquo;s rig in ui/src/character/flock/ (a palette, a size from the real bird\u0026rsquo;s measurements, a few pixel details, and only the clips where the real bird behaves differently), so all of Zeca\u0026rsquo;s clips work on every bird.\nSize: the wingspan follows the real average (Zeca\u0026rsquo;s 37 cells are 150 cm, so a condor spans 73). The extra span spreads evenly along the arm and hand of each wing, so a big bird keeps the wrist, the curve and the fingers instead of a stretched flat bar. The sunning pose is drawn at each species\u0026rsquo; size, rows and columns added inside the wing and the body\u0026rsquo;s outline drawn again, so a big bird is bigger all over, never a longer torso; the raised wing and the stretch grow with the bird too. The hand\u0026rsquo;s feather tips are pale only where the real bird\u0026rsquo;s are (the black vulture\u0026rsquo;s white primaries, the Cathartes\u0026rsquo; silver), spread, raised or in flight, and the Andean condor shows its white panel. The body length is compressed so the biggest birds still fit the island. Griffons, condors, the cinereous, lappet-faced and bearded vultures have a tall body of their own, with shoulders and a long neck that sinks into the ruff at rest and stretches when alert. Heads: every species wears Zeca\u0026rsquo;s head with its own skin, eye and bill. Facing you, the eyes sit at the edges of the face with the bill between them. The eight biggest (a perch of 26 rows or more: both condors, the bearded, griffon, Cape, Himalayan, cinereous and lappet-faced vultures) get a head one row deeper and, in profile, one column longer, or facing you a cheek column wider on each side, grown up and back so the bill tip and the neck stay put: Zeca\u0026rsquo;s head on a condor\u0026rsquo;s body read as a small bird\u0026rsquo;s. The brow ridge goes on after a species has drawn its skin, so a feathered forehead or a red head keeps its own color over the eye. Wings in flight: one wing is drawn at the glide, and the up- and downstrokes bend it at the shoulder, more in the hand than in the arm, so the beats read as one wing moving. Seen from below, its leading edge is lit, the flight feathers behind it are a paler band (silver on the turkey and yellow-headed vultures), the trailing edge is serrated and the primaries splay into fingers. Where the real bird\u0026rsquo;s flight feathers are dark under pale coverts (the Egyptian and palm-nut vultures, and every griffon), the back of the wing is dark; the California condor\u0026rsquo;s white is a triangle on the coverts, and the bearded vulture flies with its orange underparts and its long diamond tail. Every flight part also has a :molt copy with one flight feather missing on each wing, which the flight shows from December to March (the summer molt, by the local calendar). Plumage at rest: the turkey vulture is the brownest of the three Cathartes, the lesser yellow-headed the palest, with pale primary shafts streaking its folded wing, and the greater yellow-headed the blackest, with a blue-green sheen. The Egyptian vulture\u0026rsquo;s white tail is a short wedge, and the white-backed and white-rumped vultures show their white rump only as a strip at the back\u0026rsquo;s edge, under the folded wing. Who is which: Zeca is the species you picked. The core (crates/core/src/flock.rs) gives every other session a species from the pool you chose by a hash of its id and the season, a number the app picks at start-up: a session keeps its bird while it lives, and the flock changes each time the app starts. A project with three or more sessions crowns its oldest session king (the king vulture), and the crown stays there as the focus moves. The king is never drawn at random, so it stays rare. Signatures: every species has one clip of its own, from something the real bird does. A finished session\u0026rsquo;s bird plays it right after its done clip, before it takes off; Zeca alone on an empty wire (not in the chat) plays his twenty seconds after he settles, then once a minute until he dozes. A signature that leaves the perch (the lesser yellow-headed, Rüppell\u0026rsquo;s, white-backed and white-rumped vultures) happens in the sky instead: once a lap, while the bird soars, it plays its flight frames at its own point of the lap (growing from the glide and back, never past the island\u0026rsquo;s top), and the lesser yellow-headed skims down toward the island\u0026rsquo;s floor. The lab shows each one too (/lab/?species=\u0026lt;id\u0026gt;). With reduced motion, Zeca does not play his on his own. Species Signature Black vulture (Coragyps atratus) Cools off: urohidrosis, which is why its legs look white Turkey vulture (Cathartes aura) Sunning, wings spread (the horaltic pose); in flight, a V that rocks Lesser yellow-headed vulture (Cathartes burrovianus) Low over the grass, rocking fast Greater yellow-headed vulture (Cathartes melambrotus) Waits for the king to open the carcass King vulture (Sarcoramphus papa) Opens the carcass with the strongest bill Andean condor (Vultur gryphus) Its head flushes red: dominance California condor (Gymnogyps californianus) Courtship: red head, wings spread, a bow Egyptian vulture (Neophron percnopterus) Throws a stone at an egg Bearded vulture (Gypaetus barbatus) Drops a bone to break it Palm-nut vulture (Gypohierax angolensis) Walks the beach Hooded vulture (Necrosyrtes monachus) Its face flushes red when agitated Griffon vulture (Gyps fulvus) Stretches its neck out of the ruff Rüppell\u0026rsquo;s vulture (Gyps rueppelli) Climbs higher: the highest bird flight on record Cape vulture (Gyps coprotheres) Feels the heat with the blue patches on its neck Himalayan vulture (Gyps himalayensis) Shakes off the snow White-backed vulture (Gyps africanus) Lands in a crowd, braking with its wings Indian vulture (Gyps indicus) Reaches into the carcass with its bare neck Slender-billed vulture (Gyps tenuirostris) Probes with its long, thin bill White-rumped vulture (Gyps bengalensis) Takes off, showing the white back Cinereous vulture (Aegypius monachus) Looms over rivals Lappet-faced vulture (Torgos tracheliotos) Charges Red-headed vulture (Sarcogyps calvus) Shakes its head and lappets White-headed vulture (Trigonoceps occipitalis) Hunts live prey Looks # Zeca alone wears a look, for the season or for good; the vults keep their species\u0026rsquo; feathers. The core picks it from the user\u0026rsquo;s date and the setting (crates/core/src/looks.rs), and the view carries it.\nLook When (Auto) witch-hat October 1 to November 1 santa-hat December 1 to 26 party-hat December 31 to January 2 bunny-ears Good Friday to Easter Monday (Easter by the Meeus/Jones/Butcher computus) sunglasses Never: only when picked in the settings west-coast Never: a blue paisley bandana tied at the back, its tails down the nape, and wraparound shades fitted-cap Never: a black fitted cap, brim forward, a white wordmark across the front, and wraparound shades mountain-hat Never: the tall felt hat with a dented crown, a dark band and a wide brim headband Never: a white headband and a plaster under one eye dreads Never: twisted locks down the nape and by the face, past the jaw, with gold cuffs, and a gold grill front-knot Never: a red bandana knotted at the forehead with its ends up, and a gold cross on a thin chain durag Never: a white silk durag with its tail down the back, a gold grill, a chain with a medallion crown Never: a gold crown with red stones, a little tilted, and a chain with a medallion bucket-hat Never: a red bucket hat with a soft brim, and a thick gold rope chain clock-chain Never: big white-framed shades and a clock on a chain, swinging when he dances headphones Never: big silver headphones, the cup over the ear, and a gold chain shutter-shades Never: white slatted shades and a medallion chrome-chain Never: a thick chrome chain with a big square medallion eye-patch Never: an eye patch with a gold strap, and a stack of gold chains A seasonal look is drawn twice in zeca.py, in profile and facing you, with where it sits on the resting head and on the front head. The other poses follow from where their crown and eye moved; the look back wears the profile mirrored, and in flight it sits on the small flight head. The other looks are composed of pieces (HATS, EYES, GRILL, NECK in zeca.py): an outfit is one line naming a hat, eyewear, a grill and what hangs from the neck, and looks() stamps the head pieces into a grid per pose (the grill sits on the lower beak, which moves more than the crown), so the renderer reads them like any look. No look rises more than 5 rows over the head; zeca.py refuses one that does.\nThe neck slot is the body\u0026rsquo;s: a chain hangs from under the agent\u0026rsquo;s band (the band, the agent\u0026rsquo;s mark, stays whole) and a lowered head covers it the way it would. A pendant hangs from the strand and lags a cell behind when the body sways forward (the dance, the shake of a failure); the chest holds it on the way back. The sunning pose wears the front view; flight has no band, so nothing hangs there. Asleep, the head turned back under the feathers wears what covers the crown (a hat, a bandana, locks, headphones) and none of what sits on the face; the chain hangs on the fluffed breast.\nui/src/character/looks.ts bakes a look into a copy of the set\u0026rsquo;s heads and flight frames: every head grows by the same rows on top and its layers move up as much, so a pose swap mid-clip keeps the hat in place and every clip, species and frame cache works unchanged. A look may also run below a head\u0026rsquo;s last row (the locks hang past the jaw): the grid grows down, which moves no layer. On another species the look follows its eye (the Andean condor\u0026rsquo;s comb moves it down a row). The lab previews each one (/lab/?look=\u0026lt;id\u0026gt;).\nA right-click on Zeca opens his looks on the island (ui/src/island/looks.ts), grouped as above: the four seasonal ones, the head-only ones (sunglasses and the five drips with nothing at the neck) and the nine with a chain. Each tile is his idle frame wearing it; the one under the pointer goes on the perch as a live preview. The groups live in LOOK_GROUPS (ui/src/character/looks.ts), shared with Settings; a core test checks every look has one place there.\nZeca in the tray # In Panel mode (Settings) Zeca lives in the tray icon instead of the top island. The icon has two frames per state, chosen from core\u0026rsquo;s attention: idle, working, needs you (the loudest: a card waits), done and failed. Each frame is drawn at 22, 24 and 32 px, and the icon carries all three, so the panel picks the closest to its own (22 px on Plasma\u0026rsquo;s default) and shows it unscaled. At 32 px he is the island\u0026rsquo;s own frame (when he needs you, a narrower spread than the island\u0026rsquo;s, which is too wide for the icon); at 22 and 24 a smaller Zeca drawn for them: perched, his head lowered as he works, puffed up and hissing when it failed, wings spread when he needs you. Every state but idle also wears a round badge at the top right with a dark glyph: working dots on white (the last one blinks), needs you a bang on amber that flashes dark and back, done a check on green, failed a cross on red. Frames change slowly (a 0.2 s blink, otherwise holds of 0.5 to 4 s), none with reduced motion. A click opens the island by the panel; the icon only calls for attention, it never answers a card. The frames are drawn by zeca.py into app/icons/tray/\u0026lt;size\u0026gt;/, never at run time.\nWhere the art lives # design/mascots/zeca/zeca.py is the source: palette, parts (body, head poses, flight frames) and clips, the seasonal looks and the pieces the other looks are made of. Run it after a change; it writes ui/src/character/zeca/zeca.json, the tray frames in app/icons/tray/, and the review sheets design/mascots/zeca/clips.png and design/mascots/zeca/looks.png. A frame stacks parts at integer offsets, so a head pose or a blink is drawn once and reused. ui/src/character/flock/ turns Zeca\u0026rsquo;s rig into every other species at run time (rig.ts holds the transforms, species.ts the 23 species); nothing is generated or checked in for them. ui/src/character/sprites.ts draws frames and picks one by time; it knows nothing about vultures. The lab (npm run dev, then /lab/) loops every clip and flies a full sortie: take-off, flapping cruise, a thermal, a glide home, the landing. ","externalUrl":null,"permalink":"/en/docs/vults/animations/","section":"Docs","summary":"Zeca and the vults are black vultures (Coragyps atratus). Every clip is built from something a real black vulture does, so the bird reads as a vulture before it …","title":"Animations · Vults","type":"docs"},{"content":" Set one with theme: \u0026lt;id\u0026gt; in config.yaml. Writing your own is covered in the themes guide.\nid Name Appearance Description sp-night SP Night dark The city at 3 a.m.: tokyodark\u0026rsquo;s blue-violet dark, with the sodium streetlight burning warm on top. sp-night-garoa SP Night Garoa dark The same window seen through the drizzle. Flat grey: the garoa does not cool the city, it fades it. sp-night-jaragua SP Night Jaraguá dark The same night from the city\u0026rsquo;s highest point: the dark turned toward the green of dense forest, with the red-and-white tower lit at the top. dracula Dracula dark The vampire dark theme: purple, pink and neon green over blue-grey. everforest-dark Everforest Dark dark A comfortable green forest: moss background, soft tones that are easy on the eyes. gruvbox-dark Gruvbox Dark dark Retro and warm: earthy browns, orange and burnt yellow, medium contrast. kanagawa Kanagawa Wave dark Hokusai\u0026rsquo;s Great Wave: sumi ink, wave blue and the Fuji white of the paper. nord Nord dark Arctic and clean: Polar Night, Snow Storm, Frost and Aurora. onedark One Dark dark The Atom classic: graphite grey with balanced blue, purple, green and orange. rose-pine Rosé Pine dark All natural pine, faux fur and a bit of soho vibes: soft tones over an almost black purple. rose-pine-dawn Rosé Pine Dawn light Rosé Pine at dawn: the light variant, cream paper and purple ink. rose-pine-moon Rosé Pine Moon dark Rosé Pine under the moonlight: the same mood on a slightly lighter background. tokyonight Tokyo Night dark Downtown Tokyo lights at night: dark blue background, neon blue and magenta. tokyonight-storm Tokyo Night Storm dark Tokyo Night under a stormy sky: the same neon over a lighter slate blue. ","externalUrl":null,"permalink":"/en/docs/lazyagents/reference/themes/","section":"Docs","summary":" Set one with theme: \u0026amp;lt;id\u0026amp;gt; in config.yaml. Writing your own is covered in the themes guide.\nid Name Appearance Description sp-night SP Night dark The city …","title":"Built-in themes · lazyagents","type":"docs"},{"content":"Everything flows one way, through one loop:\nagent ──hook JSON──▶ vults-hook ──protocol line──▶ ipc ──┐ GitHub (gh) ─────────────▶ connectors runtime ─────────────────┤ island clicks (Allow, open, jump…) ────────────────────────────┤ ▼ core::reduce(state, input, now) ──▶ effects │ (answer the hook, State::view ▼ open a URL, jump) island renders Layers # The crates form three layers over a small base. A crate depends only on its own layer or a lower one; scripts/check-layers.sh checks it in CI (tests may reach further).\nLayer Crates What it owns Experience app, platform, ui/ Every surface: the island, the widget, settings, the tray, Zeca and the flock Connect connectors, chat, voice, media, zeca, link, relay, evals, spike What reaches past the agents: GitHub, the chat CLIs and APIs, the microphone, what is playing, a paired phone Core core, protocol, peer, ipc, hook, agents, agent-config, store Sessions, events, approvals and decisions: what the agents are doing and what the human said base brand, secrets The name, the keyring Only app wires the layers together: Connect never calls into Experience, and Core knows neither. That keeps a new surface cheap (it draws State::view and sends intents, ADR 0021) and keeps Zeca optional (ADR 0018): he uses Chat, Voice and Connect, and none of them knows about him. His body is in Experience; his brain is the zeca crate in Connect, which reads the flock but never answers a permission (ADR 0018).\nCrate Role Must not use brand The app\u0026rsquo;s name, slug and bundle id; generates ui/src/brand.ts anything protocol Versioned hook ↔ app messages, limits, socket and pipe names tokio, Tauri peer Same-user checks on both ends of the connection tokio, Tauri hook Reads the agent\u0026rsquo;s hook JSON, forwards it, prints the agent\u0026rsquo;s decision format tokio, HTTP ipc Async server: connection limits, ack-then-decide, routing to the app Tauri core Pure domain: sessions, approvals (each card an offer in the ledger: bound to its exact request, answered once, only in time), action classes, the audit lines, alerts, the view, whose TypeScript types it generates into ui/src/view.gen.ts (a test checks it is fresh); clock injected IO, async, Tauri agents The registry of every agent (id, name, wire, setup, how it runs, what it can do; ui/src/agents.gen.ts is its twin) and, per agent (Claude Code, Codex, Antigravity, OpenCode, Qwen Code, Copilot CLI, Cursor, Factory Droid, Kimi Code, any other): event names, tool → activity, install entries, Codex trust; the AgentDriver trait for an agent the app runs itself (no runtime) Tauri, tokio agent-config Safe edits of agent configs (JSON, TOML with its comments kept, whole plugin files): strict read, diff, fingerprint, backup, atomic write Tauri store The local database: one vults.sqlite (bundled SQLite), numbered migrations, the history of turns, the append-only audit log, the consents (never deleted), traces, Zeca\u0026rsquo;s conversations (with what each read from outside and its frozen context), the paired phones and the highest counter each signed with, Zeca\u0026rsquo;s journal and daily summaries with their full-text and vector recall, the memory proposals waiting for a yes, his nights, the user\u0026rsquo;s tasks, and what the agents cost by day and roost Tauri, async, the network chat Chat through the claude and codex CLIs, with permission requests, or an API: Anthropic or an OpenAI-compatible cloud with the user\u0026rsquo;s key, or a local Ollama / LM Studio Tauri zeca Zeca\u0026rsquo;s brain (ADR 0018): a read-only view of the flock, his memory as a git repository of small Markdown files of facts, each with a hard limit (each write a commit) core (so nothing in it can build an Intent), Tauri link A paired phone\u0026rsquo;s link (ADR 0004): pairing by QR, envelopes sealed with X25519 and XChaCha20-Poly1305 (a relay sees only ciphertext), answers signed by the phone\u0026rsquo;s Keystore keys and checked before core\u0026rsquo;s ledger redeems them IO, the network, Tauri relay The phone\u0026rsquo;s relay (ADR 0004): an axum server that stores and forwards sealed envelopes by box, capped and dropped after 10 minutes, and the client both ends use, each request through the app\u0026rsquo;s network gate; it depends only on link Tauri, core evals Evals with cassettes (dev only, never in the app): policy classes, redaction, the prompts sent to models, recorded streams replayed for injection being linked into the app spike The spike kit (dev only, never in the app): a fake model, a fake app on the hook\u0026rsquo;s socket and a terminal for TUIs, to record a real agent\u0026rsquo;s hooks without an account; scripts/spike.sh runs it per agent being linked into the app secrets The OS keyring (Secret Service, Credential Manager), keyed by the bundle id Tauri, files connectors Vults Connect: the Connector trait, the polling runtime, GitHub Tauri, core voice Push-to-talk: the microphone into memory (cpal), whisper.cpp on this computer, model downloads checked by SHA-256 Tauri, core media What is playing (MPRIS over the session bus, by its signals) and play/pause/skip Tauri, core platform Linux surface placement (layer-shell, an input region per window), the tray item (a StatusNotifierItem), global shortcuts (the desktop portal) and jump-to-terminal Tauri, core app The Tauri shell: the runtime loop, effects, commands, tray, settings — The UI (ui/) is TypeScript with no framework. src/bridge.ts is the only file that talks to Tauri; src/island/ renders the island from the view, src/character/ draws the birds from sprite data, and src/surfaces/ holds every other surface, one folder each (widget/, settings/, nest/) with its main.ts entry and its stylesheet. Code more than one surface uses sits at the root of src/; the exceptions are the chat panel (src/island/chat.ts, with its Markdown and icons) and the GitHub card (src/island/board.ts), which the Nest draws too, and the settings pages\u0026rsquo; look (surfaces/settings/pages.css), which the Nest\u0026rsquo;s Week uses inside a shadow root.\nMeaning in core, look in the surface # The view says what things mean, so every surface agrees (ADR 0021). Each session carries its attention, an ordered level (quiet \u0026lt; info \u0026lt; done \u0026lt; failed \u0026lt; needs-you), and the view carries the most of them; card marks the session whose card is first in line and still waits on it. The island only chooses the look: which sound a level makes, and how long a state must hold (1.5 s, 3 s for done) before it is news.\nThe runtime broadcasts the view, when it changed, to every window at once (publish_view), and keeps the last one for a page that loads later (current_view). Events meant for the island alone (pointer, shortcuts, chat) are sent to its label. A plain listen hears every target, so a new surface subscribes to those through window-scoped helpers in bridge.ts (getCurrentWebviewWindow().listen) to stay out of them.\nThe Nest\u0026rsquo;s window holds two webviews, the Nest\u0026rsquo;s and Settings\u0026rsquo;, one shown at a time under the same header (ADR 0021). Commands check the label of the webview that called: the calls only Settings may make are refused from the Nest\u0026rsquo;s, which shows agents\u0026rsquo; text.\nended lists the cards that left the line most recently, each with its outcome: answered here (allowed, denied, answered), released (sent to the terminal from here), terminal (the agent moved on), expired or rule (an Always on an identical card). The island reads it to say what became of the card it showed. An outcome is information only: it is recorded where a card leaves the line and never answers one.\nfront is the session in front, by one rule: the session whose card waits, else the one the user put in front (focus, set by Intent::Focus from a click on its row, forgotten when the session leaves), else the first. Work alone never moves it. Sessions come in the order they arrived, the order every surface draws them in, and the one Intent::FocusNext and FocusPrevious (global shortcuts) walk, wrapping. The chat works in the folder of the session in front.\nWhy the hook waits for an acknowledgement # A permission request keeps its connection open. The server only waits for a human once the app\u0026rsquo;s loop has taken the request and core has queued its card (Effect::AckPermission). If the app is stuck or not listening, the agent gets its answer (silence, so it asks in the terminal) within 800 ms instead of two minutes. The island opens on a queued card and stays open until it is answered; no presence mode may hide it once acknowledged (ADR 0021).\nWhy only core produces a decision # core::reduce turns a PermissionRequested event into pending state, and only a human\u0026rsquo;s click turns pending state into a RespondPermission effect: Intent::Decide (Allow, Deny) or Intent::DecideAlways, which also saves a rule. A saved rule answers only a request that matches it exactly: the same agent, folder, tool and target (ADR 0004). A question (QuestionAsked) waits the same way, and only an Intent::Answer that fits its questions turns it into an AnswerQuestion effect. A test feeds every other input in every order and checks none of them answers.\nThe island on Wayland # The island is a layer-shell surface mapped once, at a fixed size, and never resized or hidden: KWin stops showing a layer surface resized from the webview. The UI draws the island inside it and reports its rectangle, which becomes the only part that takes the mouse (the input region); everything else falls through to the windows below. WebKit pauses requestAnimationFrame while it thinks the page is hidden, so the UI measures the DOM synchronously and animates with timers.\nThe platform code is per window: each surface has a LayerSpec (namespace, fixed size, the edges it hangs from, margin, keyboard) and its own input region, kept by window label. A page\u0026rsquo;s layout and keyboard requests act on the window that sent them, and only if it is a surface (SURFACES in app/src/lib.rs). A new surface gets a spec, its label in the windows of app/capabilities/default.json and, for its own page, a Vite entry.\nTrying the island without an agent # cargo run -p vults-hook --example replay sends a recorded session through the real hook into the running app: reading, searching, the web, an edit, a permission (it waits for your answer, as an agent would), a command and the end. Pass your own JSONL file (one hook JSON per line) and --delay-ms or --agent codex to change it. ui/lab/ (npm run dev, then /lab/) shows every clip and the island with made-up states, without the app at all.\nDecisions # Why the app is built this way (Rust only, the safety rules, one core for every surface, Zeca optional, rules before models) is in the decision records.\nDocumentation # docs/ is the source for the docs on the website (rogeriojunior31.github.io/en/docs/vults), published on each release tag; docs/site.json holds the name and summary the website shows. Pages start with a # H1, use relative links, and keep images in docs/assets/. docs/adr/ holds the decision records. docs/dev/ is internal and not published; docs/pt-br/ holds the Brazilian Portuguese translation (see CONTRIBUTING). node tests/visual/docs-shots.mjs retakes the README\u0026rsquo;s and the guides\u0026rsquo; island images from the lab; run it before each release.\n","externalUrl":null,"permalink":"/en/docs/vults/architecture/","section":"Docs","summary":"Everything flows one way, through one loop:\nagent ──hook JSON──▶ vults-hook ──protocol line──▶ ipc ──┐ GitHub (gh) ─────────────▶ connectors runtime …","title":"Architecture · Vults","type":"docs"},{"content":"A plugin is an executable in ~/.config/lazyagents/plugins/ (or $XDG_CONFIG_HOME/lazyagents/plugins/). The file name, without extension, is the plugin id: it becomes the TUI tab, the lazyagents \u0026lt;id\u0026gt; subcommand and the \u0026lt;id\u0026gt;: section of config.yaml. Ids match ^[a-z0-9][a-z0-9_-]{0,31}$ and cannot repeat a built-in tab (skills, sessions, agents, providers, hooks, usage, plugins) or a CLI command.\nAny language works: the contract is JSON Lines over stdin/stdout. A complete shell example is in examples/plugins/hello.\nInvocation modes # Command When Contract \u0026lt;bin\u0026gt; serve the TUI opens the protocol below; the process stays alive until the TUI closes \u0026lt;bin\u0026gt; \u0026lt;args…\u0026gt; lazyagents \u0026lt;id\u0026gt; \u0026lt;args…\u0026gt; pass-through: stdin/stdout/stderr inherited, exit code forwarded \u0026lt;bin\u0026gt; doctor lazyagents doctor, only if the manifest has doctor: true write problems to stdout; exit ≠ 0 = problem; runs without a terminal and its output is shown once it ends; after 30 s the check fails and the plugin\u0026rsquo;s process tree is stopped Commands requested through exec get the plugin\u0026rsquo;s environment and are cancelled when the app closes. On Windows, discovery accepts .exe executables; the shell example needs a POSIX environment.\nIn every mode the environment has LAZYAGENTS_HOME, LAZYAGENTS_CONFIG_DIR, LAZYAGENTS_DATA_DIR, LAZYAGENTS_LIBRARY_DIR and LAZYAGENTS_PROTOCOL.\nProtocol v1 (serve) # One JSON message per line, UTF-8, at most 1 MiB per line. The type field discriminates. The host is lazyagents; the plugin reads stdin and writes stdout. stdout is protocol only: logs go to stderr, which the host captures (the last 4 KiB show in the tab when the plugin fails).\nHost → plugin # type fields when init protocol (1), id, home, configDir, dataDir, libraryDir, theme{id, colors{role: \u0026quot;#hex\u0026quot;}} (keys: tokens such as Primary/Bg/Info and roles such as ui.accent/ansi.red; see internal/tui/theme/README.md), config (the \u0026lt;id\u0026gt;: section of config.yaml, as JSON; absent if there is none), width, height first line after spawn resize width, height the tab\u0026rsquo;s usable area changed (header, tabs and margins already subtracted) key key, text a key press, only while the tab is active. key is the Bubble Tea name: a, enter, space, esc, ctrl+x, shift+tab paste text pasted text mouse mouse{kind: \u0026quot;wheel\u0026quot;|\u0026quot;click\u0026quot;, x, y, button} coordinates relative to the tab body command name the user picked \u0026lt;id\u0026gt; \u0026lt;name\u0026gt; in the palette (:) reload — :reload or the reload key agents agents[{id, name, installed, version, managedDir, readDirs}] after agent detection (and again after a respawn) exec_result execId, code, stdout, stderr, error an exec finished (stdout/stderr only for non-interactive ones, up to 1 MiB) lazyagents\u0026rsquo; global keys (q, ?, :, tab, shift+tab, 1–9) do not reach the plugin unless the last frame has capturing: true (use it while one of your inputs has focus).\nPlugin → host # type fields rules manifest title, help[{title, keys[[key, description]]}], commands[{name, desc}], doctor first line, within 3 s of init. title up to 40 characters; commands[].name matches ^[a-z0-9][a-z0-9_-]*$ (shown in the palette as \u0026lt;id\u0026gt; \u0026lt;name\u0026gt;) frame view, count, capturing the whole tab state, at any time and as often as you like; the last one wins. count is the number on the tab (omit it to hide) exec execId, argv, dir, interactive asks the host to run a command. interactive: true suspends the TUI and hands the terminal to the process (editor, agent CLI); otherwise it runs in the background and the output comes back in exec_result view is text with \\n. As in any JSON, control characters are escaped (\\u001b[1m for ESC). SGR colors (ESC[…m) are kept; any other escape sequence (cursor movement, screen clearing, OSC) and control characters are removed, and the host clips to the tab size. The theme background is repainted after every ESC[0m.\nLifecycle and failures # The plugin ends when stdin closes (EOF): handle it and exit; the host sends SIGTERM and, 2 s later, SIGKILL. On Linux and macOS the plugin runs in its own process group and both signals go to the whole group; once the plugin has exited, anything left in it is killed, so a process the plugin started never outlives it. On Windows the plugin starts suspended, joins its own kill-on-close Job Object and only then runs, so nothing it starts escapes: stopping it ends the whole tree (taskkill /T while it runs), anything left in the job is ended once the plugin exits, and the job also ends if lazyagents dies. The same holds for a non-interactive exec the plugin requests; an interactive one keeps the terminal and runs as a plain child. No manifest, a non-JSON line, an empty type, a line \u0026gt; 1 MiB or a dead process: the tab shows the error and the captured stderr; :reload restarts the plugin. None of this takes down the TUI. If the plugin stops reading stdin (queue of 256 messages full), the host shuts it down. Configuration # Put a section named after the plugin id in config.yaml; it arrives whole in init.config:\nhello: greeting: hi Testing # mkdir -p ~/.config/lazyagents/plugins cp examples/plugins/hello ~/.config/lazyagents/plugins/hello chmod +x ~/.config/lazyagents/plugins/hello lazyagents doctor # \u0026#34;plugins\u0026#34; section: handshake and `hello doctor` lazyagents hello a b # pass-through lazyagents # Hello tab To debug the protocol by hand: printf '{\u0026quot;type\u0026quot;:\u0026quot;init\u0026quot;,\u0026quot;protocol\u0026quot;:1,\u0026quot;width\u0026quot;:80,\u0026quot;height\u0026quot;:24}\\n{\u0026quot;type\u0026quot;:\u0026quot;key\u0026quot;,\u0026quot;key\u0026quot;:\u0026quot;x\u0026quot;}\\n' | ./hello serve.\n","externalUrl":null,"permalink":"/en/docs/lazyagents/plugins/","section":"Docs","summary":"A plugin is an executable in ~/.config/lazyagents/plugins/ (or $XDG_CONFIG_HOME/lazyagents/plugins/). The file name, without extension, is the plugin id: it …","title":"External plugins · lazyagents","type":"docs"},{"content":"How the code is organized, and where a change goes. The short, rule-by-rule version for AI assistants is CLAUDE.md; this page is for people.\nThe big picture # lazyagents is one Go binary. Without arguments it opens a Bubble Tea v2 TUI; with a subcommand it runs headless. Both are built from the same modules: each module brings its domain logic, its tab, its CLI commands and its doctor checks in a single package.\nmain.go dispatch only: --version, CLI or TUI internal/ ├── app/ composition root: boot, migrations, THE registry (features.go) ├── feature/ the contract between a module and the root: Feature and Deps ├── core/ paths (XDG), config.yaml — bottom of the stack ├── fsutil/ atomic writes, backups, rotation ├── agent/ one adapter per agent + optional capability interfaces ├── cli/ headless framework: dispatch, help, flags, doctor ├── tui/ TUI framework: root, tabs, help, palette, widgets, themes └── modules/ one package per module ├── skills/ sessions/ providers/ hooks/ usage/ agents/ └── plugins/ external plugins: process, protocol, proxy tab Dependencies only point one way:\nfsutil ← core ← agent ← {cli, tui/*} ← feature ← modules/* ← app ← main cli and tui are frameworks and know no module. A module imports them, never the other way around.\nModules # A module is internal/modules/\u0026lt;name\u0026gt;/:\nFile Contents service.go and one file per subject The domain: Service, types and I/O. Never imports tui/ tab.go, view.go, help.go, msgs.go The tab. Its model is Tab, built by newTab cli.go commands(svc) and checks(svc), unexported feature.go Feature() feature.Feature, the only exported entry point A tab file about the same subject as a domain file gets the _ui suffix (install.go / install_ui.go).\nThe tab never does I/O itself: it calls the service inside a tea.Cmd and gets the result back as a message. Errors become toasts, never panics.\nTabs talk to each other only through messages in internal/tui/events, and those carry aggregates (map[agent]int), never a module\u0026rsquo;s types.\nAdding a module # Create internal/modules/\u0026lt;name\u0026gt;/ with the files above. Register it with one line in features() in internal/app/features.go. The order there is the tab order; Last: true sends a read-only tab to the end. If it needs settings, read its own config.yaml section with d.Config.Section(\u0026quot;\u0026lt;name\u0026gt;\u0026quot;, \u0026amp;cfg). Never add a global key. If it touches agent files, add a capability (next section). Document it: docs/guide/\u0026lt;name\u0026gt;.md, linked from docs/README.md. A test fails until the guide exists (Documentation). app/app.go, tui/app.go and cli/cli.go are never edited to add a module.\nAgents # internal/agent is the only package that knows where agents keep things and in which format. Every agent implements Adapter (detect, list sessions, resume, read a transcript, delete a session). Anything more is an optional interface, checked with a type assertion:\nInterface Used by What it adds ProviderHost providers apply and clear an endpoint profile in the live config HooksHost hooks read, add and remove hooks; the events the agent fires RateLimitReader usage subscription limit windows UsageEventReader, UsageReader, AuthModeReader usage, sessions tokens per response, per session, and whether the account pays per token LiveChecker sessions whether a session is running now TranscriptProber sessions a fast pre-check for full-text search Adapter itself does not grow: a new ability is a new interface, implemented only by the agents that support it.\nA transcript reaches the reader as []agent.Entry, the same shape for every agent: role (prompt, answer, reasoning, tool call, event), time, and for a call its kind (agent/toolkind.go: shell, edit, read, agent, plan, todo), lines added and removed, whether it failed, and Sub, a transcript of its own (a subagent, another branch) that the same adapter reads as a Session.Path. Each adapter fills what its format records and leaves the rest zero; the reader shows what is there.\nAdding an agent # Write the adapter in internal/agent/\u0026lt;agent\u0026gt;.go: Detect (binary in PATH or config dir; --version only here), sessions and transcripts. Confirm the formats against the agent\u0026rsquo;s docs or source; never guess. Add it to AllWithIndex in internal/agent/registry.go. The order there is the column order in the TUI. Implement the capabilities it supports. A new transcript reader that extracts tokens or previews does it in its lineScanner, through the shared index (internal/agent/index.go), so each file is read once. Give it a color in the themes (theme.AgentColor). Tests use fixtures under a temporary home (core.PathsIn(t.TempDir())), never the real ~. If the CLI can move its files with an environment variable, add it to agent.ConfigOverrides after checking it against the CLI; a test package that builds adapters calls agenttest.ClearOverrides() in its TestMain. Pin the CLI\u0026rsquo;s real formats too: add a recorder for it to scripts/record-fixtures.go, which runs the CLI in a throwaway home against a local fake model, then go test ./internal/agent -run TestRecordedFixtures -update. See Recorded CLI fixtures. Run go test ./docs -update: the agent support reference picks the new agent up. Mention it in the sessions guide if it has sessions. Recorded CLI fixtures # Agents change their private session formats between releases without notice. internal/agent/testdata/fixtures/\u0026lt;agent\u0026gt;/\u0026lt;version\u0026gt;/ holds sessions written by the real CLIs, one dir per version, and TestRecordedFixtures runs each adapter over them against a golden.json. The fixture matrix lists every pinned version and where each fixture came from.\ngo run scripts/record-fixtures.go records every CLI installed on the machine: each runs in a throwaway home against fakellm, a deterministic local model server inside the script (OpenAI Chat Completions, OpenAI Responses, Anthropic Messages), with no account and no network. Paths become /work/proj and /home/user, system prompts are cut, and a recording that still names the machine (user, hostname, kernel) fails. A new CLI release gets a new version dir. Keep the old ones: they are the matrix. Then go test ./internal/agent -run TestRecordedFixtures -update writes the goldens; review their diff, since that is where a format change shows. Codex limits cannot come from a fake model. -codex-limits-from ~/.codex/sessions copies the rate_limits shape of the newest real rollout, with every value that could describe the account replaced. Claude Code limits come from the network. -claude-limits makes the one call the Usage tab makes, with your Claude Code login, through Claude.RateLimits itself; it keeps only the fields lazyagents reads (limits, five_hour, seven_day), replaces every value, and the login is never written anywhere. The test serves that limits.json with a made-up login. Rules that protect user data # These hold everywhere; review checks them.\nConfig writes go through fsutil.WriteAtomic. A live agent file gets fsutil.Backup first, and unknown keys are preserved. JSON agent config is edited through the settings primitive (internal/agent/settings.go), which touches only the target key and keeps the file order. TOML (Codex) is never reserialized. lazyagents edits only blocks delimited by # lazyagents — … comments and copies every other line as it is. Skill activation is a symlink. Deactivating removes the symlink; a real folder is never deleted. Secrets live in 0600 files, are masked in the TUI and in --json, and never reach a log, an error or an exported struct. No network at boot. Calls happen on demand only. Consent is never forged. When an agent asks the user to approve something (Codex\u0026rsquo;s hook trusted_hash), lazyagents warns and leaves the approval to the agent. Plugins are untrusted. Everything they send is sanitized; a failing plugin becomes a dead tab. The user-facing version of these rules is Safety and data.\nThemes # Colors exist only in internal/tui/theme. UI code uses tokens (theme.Primary, theme.AgentColor(id)) that resolve when rendering, so a style must never be rendered at init. Details: internal/tui/theme/README.md and Themes.\nDocumentation # The docs are built to survive change. Anything that is a list in the code is generated from the code; everything else is written by hand and checked by tests.\nKind Where Source of truth Kept current by Reference docs/reference/ the code: cli.Command Usage/Summary/Help, each tab\u0026rsquo;s Help() and palette Commands(), the adapters, the theme list go test ./docs fails when a page is stale; go test ./docs -update rewrites them Guides docs/guide/\u0026lt;module\u0026gt;.md written by hand, one per module a test fails if a registered module has no guide or the index does not link it Topics docs/*.md written by hand the link test Plugin protocol docs/plugins.md written by hand changes only with a protocol version bump Translations docs/pt-br/\u0026lt;same path\u0026gt; the English page, translated by hand the docs workflow warns on the pull request when the English page changed after the translation Every relative link and #anchor in every Markdown file of the repository is checked by go test ./docs, so renaming a heading or a file breaks the build instead of the docs.\nThe docs are also published at rogeriojunior31.github.io/docs/lazyagents. .github/workflows/docs.yml calls a workflow shared by the author\u0026rsquo;s projects: on a pull request it checks that each page starts with a # H1 (the page title on the site) and that relative links exist; on a v* tag it tells the site to rebuild, so the website always shows the docs of the latest release. Write pages as plain GitHub Markdown: the site uses the first # H1 as the title, docs/README.md as the index and its link order as the sidebar order, and maps relative links to its own pages. docs/dev/ is not published.\nTranslations live in docs/pt-br/, one file per English page, at the same path. A page that is not translated yet links to the English one (../configuration.md), and each translation keeps the mark of the English version it was made from, right under its title:\n# Skills The mark is sha256sum docs/guide/skills.md | cut -c1-12. When the English page changes, the docs workflow warns on the pull request and the website shows the Portuguese page as out of date until the translation is reviewed and the mark updated. Only update the mark after reviewing the translation. scripts/check-english.sh skips docs/pt-br/ and the localized metadata in docs/site.json.\nWebsite integration # docs/site.json owns the project\u0026rsquo;s display name and summaries per website language. It uses schema: 1, name, and summary with en and pt-br keys. The website reads this metadata from the same imported revision as the Markdown; it does not discover or register arbitrary repositories automatically. A new repository still needs an initial entry and module mount in the website.\nrequiredTranslations: [\u0026quot;pt-br\u0026quot;] makes complete, current Portuguese documentation mandatory. Every public English Markdown page must have a corresponding translation with the current source hash; missing or stale translations fail the docs tests, shared workflow and website build. Review and translate changes before updating the source mark. Commands, identifiers and literal program messages remain unchanged. Stable section anchors use HTML id anchors so links work in both GitHub and the website.\nEnglish pages stay directly under docs/; a Portuguese translation lives at docs/pt-br/\u0026lt;same path\u0026gt;. Use relative links to translated pages when they exist; otherwise link to the English original. The website keeps navigation in the selected language and labels missing or outdated translations. The metadata describes the project, not translation coverage: creating it does not translate any pages. Coverage and current source marks are available at the translation status page.\nTo repeat this setup in another project: copy the structure of docs/site.json, write docs/README.md, add matching docs/pt-br/ pages with reviewed source marks, and reuse .github/workflows/docs.yml. Validate links, references and metadata here with go test ./docs; the shared workflow validates the metadata, headings, links and translation freshness before notifying the website. Without SITE_DISPATCH_TOKEN, the website checks for released docs in its daily build.\nWhat to update when you change something:\nChange Update New or changed CLI command or flag Its Help in cli.go (a test requires one), then go test ./docs -update New or changed key or palette entry The tab\u0026rsquo;s Help()/Commands(), then go test ./docs -update New agent, capability or hook event The adapter, then go test ./docs -update New theme The YAML, then go test ./docs -update New module docs/guide/\u0026lt;name\u0026gt;.md and a line in docs/README.md Behavior a user would notice The guide of that module and CHANGELOG.md New file on disk, network call or write to an agent file Configuration and Safety and data Guides describe tasks and concepts. They name a key or a flag where a task needs it and link to the reference for the full list, instead of copying tables that would go stale.\nChecks # test -z \u0026#34;$(gofmt -l .)\u0026#34; \u0026amp;\u0026amp; go vet ./... \u0026amp;\u0026amp; go test -race ./... \u0026amp;\u0026amp; go build ./... scripts/check-english.sh CI runs the same steps; go test ./... includes the docs checks.\n","externalUrl":null,"permalink":"/en/docs/lazyagents/architecture/","section":"Docs","summary":"How the code is organized, and where a change goes. The short, rule-by-rule version for AI assistants is CLAUDE.md; this page is for people.\nThe big picture # …","title":"Architecture · lazyagents","type":"docs"},{"content":"Architecture decision records: one file per topic that shapes the project, with the rule in force, the context it was made in and what it costs. Code and guides say what the app does; these say why.\nTopic Record Status Folded into it How we decide 0001 Record decisions as ADRs Accepted Stack 0002 Rust for every backend part; no Python sidecars Accepted The hook 0003 The hook never blocks an agent Accepted Permissions 0004 Who answers a permission Accepted 0014, 0017, 0025, 0026 Permissions 0027 An open reply for any tool that asks for one Proposed Agent configs 0005 Agent configs, and the agents we support Accepted 0029 Actions 0011 Offer only the actions the agents allow Accepted Zeca 0018 Zeca: optional, apart from the flock, rules before models Accepted 0010, 0012 Network and secrets 0019 Network, secrets and telemetry Accepted 0006, 0016 Releases 0020 Releases: small and often, 0.2.0 at the Operations gate Accepted 0007, 0015 Surfaces 0021 Surfaces: one core, four sizes, a card answered where you are Accepted 0008, 0009, 0022, 0023, 0024, 0030 Voice 0028 Voice: local listening, opt-in spoken replies Accepted 0013 A number in the last column was a record of its own until 2026-10-12; an old commit or PR that cites it means the record it was folded into. Numbers are never reused: the next topic is 0031.\nWriting one # A decision on a topic that already has a record is an edit to that record, in its own PR, with a dated line in its History. Only a new topic gets a new number: copy the shape of an existing record (Status, Context, Decision, Consequences), number it next in line and add it to the table. Write or edit one when a choice changes a rule in CLAUDE.md, the architecture, the release plan or what the app promises users (0001).\n","externalUrl":null,"permalink":"/en/docs/vults/adr/","section":"Docs","summary":"Architecture decision records: one file per topic that shapes the project, with the rule in force, the context it was made in and what it costs. Code and guides …","title":"Decisions · Vults","type":"docs"},{"content":"A connector watches an outside service and puts its news on the island: a failed check, an approved pull request, a review waiting for you. GitHub is the first one; adding another takes one Rust file, one line in the registry and one entry in the settings UI.\nWhat you write # pub trait Connector: Send + Sync { fn id(\u0026amp;self) -\u0026gt; \u0026amp;\u0026#39;static str; // \u0026#34;github\u0026#34; fn interval(\u0026amp;self, last: \u0026amp;Snapshot) -\u0026gt; Duration; // wait after a good poll, from what it saw fn poll(\u0026amp;self) -\u0026gt; Poll\u0026lt;\u0026#39;_\u0026gt;; // the service\u0026#39;s current state, as a Snapshot fn diff(\u0026amp;self, before: \u0026amp;Snapshot, after: \u0026amp;Snapshot) -\u0026gt; Vec\u0026lt;Event\u0026gt;; // news between two states fn board(\u0026amp;self, snapshot: \u0026amp;Snapshot) -\u0026gt; Option\u0026lt;Vec\u0026lt;Row\u0026gt;\u0026gt; { None } // optional: its card } A Snapshot is a map from a stable key (pr:owner/repo#12) to whatever JSON you need to tell what changed. You never decide when something is news against the wall clock: you compare two snapshots. That is what makes the rest automatic.\nWhat the runtime does for you # Polls only while the user has the connector switched on, and stops at once when it is switched off. Saves the last snapshot to disk, so a restart does not replay old news. On the very first poll it only records a baseline: no flood of alerts about things that were already true. Backs off on errors (from 4 minutes, doubling, up to 15, whatever your interval), waits out Error::RateLimited { retry_after }, and checks only every 10 minutes when the user must act (Error::Unavailable, Error::Auth). Waits interval(\u0026amp;last_snapshot) after a good poll, so a connector can poll sooner while something is running (GitHub: 60 s while checks run, else 300 s). Polls early when the island opens and the last poll, good or failed, is more than a minute old (Runtime::refresh_if_stale): fresh news, or a retry after an error the user may have fixed. Never while off, already polling, or rate limited. Sends no events from a poll that ends after the user switched the connector off; its snapshot is still saved as the baseline. Sends the card (board, when the connector has one) after each good poll, after its events, and takes it away when switched off. A row\u0026rsquo;s item is its snapshot key: when an item leaves the card, the core drops the alerts whose keys start with \u0026lt;item\u0026gt;:. Asks the app\u0026rsquo;s network gate (ADR 0019) before each poll: only while switched on, at most 3,000 in any 24 hours, a request span per poll in vults.sqlite. A refusal waits 10 minutes and shows Budget spent. Runtime::start takes the gate; there is no way to poll around it. Reports status (enabled, last successful poll, error, items watched, over budget) to Settings. Steps # crates/connectors/src/\u0026lt;id\u0026gt;.rs: implement Connector. Keep poll to one request when the service allows it, and turn its answer into a Snapshot in a pure function you can test. crates/connectors/src/lib.rs: add pub mod \u0026lt;id\u0026gt;; and the connector to all(). ui/src/connectors.ts: add { id, name, about }. about says, in one sentence, what it watches and how it signs in. Links: an event\u0026rsquo;s url is shown only if core::SafeUrl accepts it (https, allowed host). Add the service\u0026rsquo;s host to HOSTS in crates/core/src/safe_url.rs. Tests: feed recorded answers to your snapshot function and check diff for each kind of news, and for \u0026ldquo;nothing changed means no news\u0026rdquo;. Credentials # Prefer the service\u0026rsquo;s own CLI that the user is already logged into, as GitHub does with gh: the app never handles a token at all. When there is no such CLI, the token goes in the OS keyring (Secret Service, Credential Manager), never in a file, and never in a log.\nEvent keys # Event::key identifies the news, not just the thing: pr:owner/repo#12:ci-failed. The island replaces an alert with the same key instead of stacking duplicates, and a new failure on another commit (branch:owner/repo:ci-failed:\u0026lt;sha\u0026gt;) is a new alert.\nEvent::topic names the story a news belongs to (pr:owner/repo#12:ci): a newer event of the same topic retires the older alerts, so checks passed does not sit next to the failure it fixed. Leave it None for news that stands alone. Every event is news to the island, even under a key it has shown before: a review requested again sounds again.\n","externalUrl":null,"permalink":"/en/docs/vults/contributing/connectors/","section":"Docs","summary":"A connector watches an outside service and puts its news on the island: a failed check, an approved pull request, a review waiting for you. GitHub is the first …","title":"Adding a connector · Vults","type":"docs"},{"content":"Vults shows a coding agent\u0026rsquo;s sessions through the hooks the agent runs on its own events. Most agents are a few hundred lines: one file in crates/agents, a row in the registry, a recorded session, and the docs. This page is the checklist, in the order the work goes.\nWatch or answer # Decide this first, from a real run of the agent, never from its docs alone: Gemini CLI, Antigravity and Cursor all looked answerable on paper and are not.\nObserve-only (Antigravity, Cursor, Factory Droid, Kimi Code): the island shows the steps; the agent asks its permissions in its own prompt. This is the default, and the right first PR. Answerable (Claude Code, Codex, Qwen Code, Copilot CLI): the agent has an event sent only when it would ask the user (a PermissionRequest), takes an allow or a deny from the hook, and on an empty answer asks in its own prompt. A hook that runs before every tool (PreToolUse) is not enough: it says nothing of whether the agent would have asked. Silence is never an approval: no click means the agent\u0026rsquo;s own prompt, never allow, never deny.\n1. Record a session # The spike kit runs the real CLI against a fake model and a fake app, with no account and nothing outside a scratch folder:\nscripts/spike.sh \u0026lt;agent\u0026gt; [allow|deny|none] [script.json] Add a recipe for the agent in scripts/spike.sh: point its own model settings (most take a custom OpenAI or Anthropic base URL) at $base. If the fake model does not know the agent\u0026rsquo;s tool names, teach crates/spike/src/model.rs (plain()), with a test. The run leaves events.jsonl (what the hook sent), replay.txt (what the island makes of it) and fixture.jsonl. An agent that needs its vendor\u0026rsquo;s servers cannot be recorded this way: say so in the PR and in the plan.\n2. Write the agent # One file, crates/agents/src/\u0026lt;agent\u0026gt;.rs, implementing the Agent trait:\nparse: the agent\u0026rsquo;s events in Claude Code\u0026rsquo;s words. Most agents copied Claude Code\u0026rsquo;s hook JSON; map what differs (event names, tool names such as Execute → Bash, argument names such as old_str → old_string), then hand it to other::as_claude. Unknown events and tools fail soft: a generic step, never a crash. config_file, hook_entries, and install / uninstall / installed / up_to_date when the file is not Claude Code\u0026rsquo;s shape. vults-agent-config has the writers: Claude-style JSON hooks, named keys (named), flat lists per event (flat), TOML (toml, set up with Setup::Toml; Kimi Code is the example) and whole plugin files (plugin). Other tools\u0026rsquo; hooks in a shared file are always kept. A folder variable that moves the agent\u0026rsquo;s files goes in dirs.rs (DIR_VARS), read the way the agent reads it. If a failing hook stops the agent, end every command in || exit 0. If the agent reads any output of a hook as a decision, do not register that hook. Fields that carry a tool\u0026rsquo;s output or an account (tool_output, user_email) are dropped in the hook (DROPPED_FIELDS in crates/hook/src/lib.rs); only an error or an exit code is kept. Test it against the recorded fixture (crates/agents/tests/fixtures/\u0026lt;agent\u0026gt;-session.jsonl, paths rewritten to /home/me), plus an install and a removal that keep the rest of the file.\n3. Add the registry row # crates/agents/src/registry.rs holds one Entry per agent: its id, name, command, how it goes on the wire (Wire::Named for an observe-only agent, sent as other with its name), its setup, how Vults runs it, and its Caps (what Settings → Agents shows). Update the registry\u0026rsquo;s tests, then regenerate the UI\u0026rsquo;s twin:\nVULTS_REGEN=1 cargo test -p vults-agents agents_ts An answerable agent also needs its own AgentKind (a protocol VERSION bump), its reply shape in crates/hook/src/output.rs, its name in crates/core/src/notify.rs, the view\u0026rsquo;s twin (VULTS_REGEN=1 cargo test -p vults-core view_ts), its color in the UI, and the fuzz targets' agent lists. Copilot CLI\u0026rsquo;s PR is the example.\n4. Settings and docs # A note under its card in Settings → Agents (ui/src/surfaces/settings/main.ts), with its translations in ui/src/i18n/{pt-BR,es,zh}.ts. In English and Brazilian Portuguese: its section in Other agents (and its row in What each agent can do; it leaves the recipes), Approving, the table in Getting started, the agent lists in the READMEs, docs/README.md and architecture.md. A line in CHANGELOG.md, and its step marked done in docs/dev/plan-every-agent.md with what the spike showed. Then the checks in CONTRIBUTING.md; a new agent widens a select or two, so the visual tests may need new baselines on purpose.\n","externalUrl":null,"permalink":"/en/docs/vults/contributing/agents/","section":"Docs","summary":"Vults shows a coding agent\u0026amp;rsquo;s sessions through the hooks the agent runs on its own events. Most agents are a few hundred lines: one file in crates/agents, …","title":"Adding an agent · Vults","type":"docs"},{"content":"Status: Accepted, 2026-10-04. Amended on 2026-10-12: records on one topic may be consolidated.\nContext # Choices were spread over CLAUDE.md, the internal plans in docs/dev/ and pull request threads. The plans are deleted once done, so their reasons went with them. Later, records that amended one another piled up on the same topic, and finding the rule in force meant reading four of them.\nDecision # Every choice that shapes the project gets a short record in docs/adr/, numbered, with its status, context, decision and consequences. Plans in docs/dev/ point to the records; the records outlive the plans.\nRecords on the same topic may be consolidated into one living record: the current rule, whole, with a short History of the records folded into it. A consolidation decides nothing new and drops only what a later record had already superseded. The folded records are deleted, and every link to them points at the living record; the index lists the old numbers each one holds, and the git history keeps their text. Numbers are never reused.\nFrom then on a topic grows in place: a new decision on a topic that has a living record is an edit to that record, made in its own PR with a dated line in its History. A new number is only for a new topic.\nConsequences # A contributor can learn why the app works this way without the history. A change to a rule starts in its record (the living record of its topic, or a new record for a new topic), not with an edit to CLAUDE.md alone. One topic, one place to read the rule in force. ","externalUrl":null,"permalink":"/en/docs/vults/adr/0001-record-decisions/","section":"Docs","summary":"Status: Accepted, 2026-10-04. Amended on 2026-10-12: records on one topic may be consolidated.\nContext # Choices were spread over CLAUDE.md, the internal plans …","title":"0001. Record decisions as ADRs · Vults","type":"docs"},{"content":"Status: Accepted, 2026-10-04 (the rule dates from the start of the project).\nContext # The app runs all day next to the user\u0026rsquo;s agents. Every extra runtime is memory, start-up time, a packaging problem and one more thing to keep safe. Tools like small decision models are often shipped as Python services.\nDecision # Every backend crate and tool is Rust. TypeScript only renders the UI. No Swift, no Go, and no Python process started by the app. A model the app runs itself goes through a Rust binding (whisper.cpp through whisper-rs; ONNX through ort if one is ever needed). A model server the user already runs (Ollama, LM Studio) is reached over its local HTTP API.\nConsequences # One toolchain, one binary, packages without an interpreter. A library that only exists in Python is out until it has a Rust path, however good it is. ","externalUrl":null,"permalink":"/en/docs/vults/adr/0002-rust-backend-no-sidecars/","section":"Docs","summary":"Status: Accepted, 2026-10-04 (the rule dates from the start of the project).\nContext # The app runs all day next to the user\u0026amp;rsquo;s agents. Every extra runtime …","title":"0002. Rust for every backend part; no Python sidecars · Vults","type":"docs"},{"content":"Status: Accepted, 2026-10-04 (the rule dates from the start of the project).\nContext # The hook runs on every event of every agent. If it hangs or prints the wrong thing, the user\u0026rsquo;s work stops, and they did not ask us to be in that path.\nDecision # On any failure (app closed, slow, a bad message) the hook exits 0 with empty stdout, and the agent goes on as if we were not there. A permission request waits for the app\u0026rsquo;s acknowledgement for 800 ms at most; a human for at most the decision budget (110 s), then the agent asks in its terminal.\nConsequences # The hook is std and serde_json only: it starts fast and has little to break. Features that would need the agent to wait for us (a stop, a pause) cannot be built on the hook alone. See 0011. ","externalUrl":null,"permalink":"/en/docs/vults/adr/0003-never-block-an-agent/","section":"Docs","summary":"Status: Accepted, 2026-10-04 (the rule dates from the start of the project).\nContext # The hook runs on every event of every agent. If it hangs or prints the …","title":"0003. The hook never blocks an agent · Vults","type":"docs"},{"content":"Status: Accepted, 2026-10-04. Consolidated on 2026-10-12 from 0014, 0017, 0025 and 0026.\nContext # Allowing a command is the one thing the app does that can cause harm. It must never come from a timer, a guess, a model or another program, nor from a message that merely claims to be the user.\nDecision # core turns a pending permission into an answer from these inputs only, each a human\u0026rsquo;s choice:\nA click: Intent::Decide (Allow or Deny, or Ctrl+Alt+Y/N with the card visible), or Intent::DecideAlways (Always: allows this request and saves a rule). An exact rule the user saved: the same agent, folder, tool and target. cargo test does not allow cargo test \u0026amp;\u0026amp; rm -rf build. A policy the user wrote, saw as a diff and clicked to accept, like an agent config (0005). Policies class actions as read, external read, write, destructive, spend or external (core::policy), and a deny beats an allow. A paired Android phone\u0026rsquo;s answer, which counts as a click only when it passes these checks: Pairing is a QR shown on the desktop: the phone\u0026rsquo;s public key, the relay\u0026rsquo;s URL and a one-time secret that lives 2 minutes. The user can revoke a phone from Settings. Every answer is signed by the paired device\u0026rsquo;s key. Approve, answer, hand off, accept a task and a service action also need a key kept in the Android Keystore with BIOMETRIC_STRONG, unlocked by the user\u0026rsquo;s biometrics for that one use; deny and dismiss do not. Sending a message or moving money does not exist in the protocol. Every answer goes through core\u0026rsquo;s ledger: bound to a hash of the whole request, used once, refused after min(120 s, limits::SERVER_DECISION_TIMEOUT), with a counter per device. The desktop stays the source of truth and the only one that acts; the phone only decides. A relay sees only encrypted envelopes; turning the phone on is a request on the user\u0026rsquo;s behalf (0019). On the desktop the island stays the only card host (0021). Kotlin is allowed for the Android shell only; its logic is the Rust core through UniFFI. Android is the one exception to \u0026ldquo;Linux first and only\u0026rdquo;; iOS and macOS come later on the same protocol. In Zeca\u0026rsquo;s chat only, what the user chose in Settings → Chat \u0026amp; voice → Who answers Zeca\u0026rsquo;s cards: Asks first (the default): a card for every call no Always rule covers (Always in this chat is one exact command, until the conversation ends). Your policies: accepted policies answer the chat\u0026rsquo;s calls exactly as they answer Claude Code\u0026rsquo;s on the island: a deny denies, an allow allows (Claude Code\u0026rsquo;s calls only, inside the conversation\u0026rsquo;s folder), the rest asks. Full access: every call runs with no card, except that what a policy denies is denied, what a policy asks first still asks, and once the conversation has read content from outside (a web page, a fetch, an MCP tool) its cards ask again for the rest of that conversation. More access is given only in Settings; the chat\u0026rsquo;s picker shows the choice and can only give some back. The chat says what ran on whose word, and its CLI still runs with --permission-mode default so the answers come from Vults. The flock\u0026rsquo;s sessions are untouched. Above all of these:\nA destructive call always asks: no policy, no Always rule (one saved before this rule included) and no chat setting answers it. Its card offers no Always allow; if the core gets one anyway, it is a plain Allow for that card, audited as such, and no rule is saved. Every answer (a human\u0026rsquo;s, a rule\u0026rsquo;s, a policy\u0026rsquo;s with its consent, the phone\u0026rsquo;s, the chat\u0026rsquo;s full-access) goes to an append-only audit log the user can read. A budget\u0026rsquo;s \u0026ldquo;hard stop\u0026rdquo; only stops new sessions the app would start; it never blocks or kills an agent the app only watches. Nothing else answers. A test feeds every other input and checks that none answers; every new Intent joins it, with a case for each phone decision, valid and forged (no signature, a revoked device, an expired or reused offer, a binding that differs). Consequences # No notification, tray item, palette, model, voice or assistant can allow anything; they can only bring the card up. A lost phone without its biometrics cannot approve anything; with them, it can until revoked. Full access trusts the model in the chat\u0026rsquo;s folder: a mistaken edit or a git push runs. The setting says so, the chip in the chat turns red, and a policy\u0026rsquo;s deny lines keep actions out of reach. One test pins each half of the destructive rule: Always on a destructive card saves no rule, and an old rule does not answer one. History # 0014, 2026-10-10: accepted policies may answer; a deny beats an allow, destructive always asks, the audit log, the budget\u0026rsquo;s hard stop. 0017, 2026-10-10: a signed answer from a paired phone counts as a click; Kotlin and Android as the one exception. 0025, 2026-10-12: no Always rule answers a destructive call. 0026, 2026-10-12: who answers Zeca\u0026rsquo;s chat cards is the user\u0026rsquo;s choice. ","externalUrl":null,"permalink":"/en/docs/vults/adr/0004-a-human-answers-permissions/","section":"Docs","summary":"Status: Accepted, 2026-10-04. Consolidated on 2026-10-12 from 0014, 0017, 0025 and 0026.\nContext # Allowing a command is the one thing the app does that can …","title":"0004. Who answers a permission · Vults","type":"docs"},{"content":"Status: Accepted, 2026-10-04 (the rule dates from the start of the project). Consolidated on 2026-10-12 from 0029.\nContext # Installing our hook means editing files the user and other tools own: each supported agent\u0026rsquo;s config. Agents also come and go (Antigravity took Gemini CLI\u0026rsquo;s place), and users who installed our hooks in an agent we drop must still be able to remove them.\nDecision # Before any write: a strict read, a diff the user sees, a click, then a dated backup and an atomic write. Hooks from other tools stay. Codex\u0026rsquo;s trusted_hash is never written: the user trusts our hooks in Codex. Anything that injects our own config into an agent (skills, MCP servers) goes through the same screen, or does not happen. The supported agents are Claude Code, Codex, Antigravity, OpenCode, Qwen Code, Copilot CLI, Cursor, Factory Droid and Kimi Code, each a driver in crates/agents. Adding one is a driver, an installer entry and the docs. Retiring an agent is a checklist, not a deletion: the protocol version is bumped; a remove-only installer stays: Settings still finds the hooks an earlier Vults wrote and offers to remove them (diff, dated backup, click), and never installs them again; its name stays reserved (valid_agent_name in crates/protocol), so its old hooks send nothing and no other tool can take the name; its old turns are left out of the history, and a policy naming it is ignored whole: nothing it left behind widens access. Consequences # Installing takes one screen more than a silent setup. Gemini CLI is retired this way (protocol version 7): crates/agents/src/gemini.rs only removes its hooks. History # 0029, 2026-10-12: the list of supported agents, and how one is retired. ","externalUrl":null,"permalink":"/en/docs/vults/adr/0005-agent-configs-backup-diff-click/","section":"Docs","summary":"Status: Accepted, 2026-10-04 (the rule dates from the start of the project). Consolidated on 2026-10-12 from 0029.\nContext # Installing our hook means editing …","title":"0005. Agent configs, and the agents we support · Vults","type":"docs"},{"content":"Status: Accepted, 2026-10-04.\nContext # Quick actions on a bird were proposed: open terminal, view diff, approve, stop, rename. But the app only reaches an agent while a permission or question hook waits for an answer, and it does not own the agent\u0026rsquo;s process. Jumping to a terminal raises the window on KDE and focuses tmux, kitty, wezterm and herdr panes; elsewhere it cannot raise a window.\nDecision # A menu shows only what works today, and says when it cannot (Open terminal on a desktop where we cannot raise windows). Stop is not offered until an agent gives a supported way to stop it from outside; until then it is research. Preferences such as mute, pin and hide are kept per project, because sessions leave after 10 to 30 minutes and are not saved.\nConsequences # Fewer buttons than the mock-ups, but none that lies. Sessions the app starts itself (0.2.0) can be stopped, because there we own the process. ","externalUrl":null,"permalink":"/en/docs/vults/adr/0011-only-actions-that-work/","section":"Docs","summary":"Status: Accepted, 2026-10-04.\nContext # Quick actions on a bird were proposed: open terminal, view diff, approve, stop, rename. But the app only reaches an …","title":"0011. Offer only the actions the agents allow · Vults","type":"docs"},{"content":"Status: Accepted, 2026-10-10. Consolidated on 2026-10-12 from 0010 and 0012.\nContext # The app does two jobs: it shows what the user\u0026rsquo;s agents are doing (the flock), and it offers a companion who chats, listens, reads the flock, remembers, creates tasks and hands them to the user\u0026rsquo;s agents (Zeca). Some users want only the first, and the second\u0026rsquo;s power must not leak into it. A small local decision model was also proposed for the app\u0026rsquo;s own choices, but the events it receives are already typed, and such models without fine-tuning score below a majority-class guess, with confidence we cannot calibrate without collecting data (0019).\nDecision # Optional. Everything about the flock works with Zeca off: sessions, cards, notifications, connectors, the Nest with its tasks and records (0021). With him off: no chat, no microphone, no talk shortcut, no Chat… in the tray, no Chat tab or Ask Zeca, the island has its own idle look, and zeca starts no process, writes no file and makes no request. Where: his brain is a zeca crate in the Connect layer, beside chat; his body (sprites, the island, the Nest) stays in Experience. No crate below zeca knows it exists; app wires it in. What he reads: the flock through a FlockReader built from the view, with no path to an Intent. Flock content is untrusted input: it may carry an injection. What he does: typed actions of his own; zeca does not depend on vults-core, so app maps each to core\u0026rsquo;s policy classes (read, external read, write, destructive, spend, external) and ledger, like any agent\u0026rsquo;s. He never builds Decide or DecideAlways, by type, and never answers a permission, his own or another agent\u0026rsquo;s (0004). What he remembers: a git repository of his own (through gix): every write is a commit, undone by a revert. Code is the agents\u0026rsquo; work: on the harness (an API key or a local model) he never runs code himself; he makes it a task for an agent, which asks the user as it always does. His commands and edits in the chat ask the user like any chat. His work is never a session: his CLI processes carry VULTS_ZECA=1; the hook marks their events and core sends them to the chat\u0026rsquo;s state (taint and activity), never to the flock. Any process could set that variable, so a marked event never hides or drops a permission request: it is still asked of the user, through the chat\u0026rsquo;s approver. Rules before models in the app\u0026rsquo;s own decisions: What can be decided from typed state is decided by plain rules in core, with tests: notifications, attention, Zeca\u0026rsquo;s look, which agent is installed and has quota. Routing a task between agents is a user preference plus availability, not a guess. Free text from the user (what they ask Zeca) goes to the chat model. A learned classifier may come later only behind the same interface, only for free text, only local, and only if an evaluation set on this repository shows the rules fall short. No model ever answers a permission. Consequences # The companion\u0026rsquo;s growth never changes who answers a permission; decisions stay exact, testable and explainable. scripts/check-layers.sh places zeca in Connect; a compile-fail test pins that FlockReader cannot reach an Intent. The protocol gained a field for the marked event (version 6). History # 0010, 2026-10-04: Zeca is optional and kept apart from the flock; his handle cannot build a decision. 0012, 2026-10-04: rules before models in the app\u0026rsquo;s own decisions. ","externalUrl":null,"permalink":"/en/docs/vults/adr/0018-zeca-as-an-agent/","section":"Docs","summary":"Status: Accepted, 2026-10-10. Consolidated on 2026-10-12 from 0010 and 0012.\nContext # The app does two jobs: it shows what the user\u0026amp;rsquo;s agents are doing …","title":"0018. Zeca: optional, apart from the flock, rules before models · Vults","type":"docs"},{"content":"Status: Accepted, 2026-10-10. Consolidated on 2026-10-12 from 0006 and 0016.\nContext # The app sees commands, file names, diffs and API keys, next to private work. Some features still need the network while the user is not asking at that moment: connectors, web search and fetch during a chat turn, Zeca\u0026rsquo;s sleep, routines, the phone\u0026rsquo;s relay and push, spoken replies, the update check. Each sends something the user cares about off the machine, costs money or quota, and could become telemetry by accident.\nDecision # Secrets only in the OS keyring, never in a file or a log. Never telemetry. The app sends nothing about its use; no request carries usage, an id or anything about the user beyond what the feature needs to do its job. A request the user starts with a click or a message (a chat turn, an API chat) is their explicit choice, said where it is turned on. A request on the user\u0026rsquo;s behalf (one that leaves the machine without a click at that moment, including a web search or fetch the model makes during a turn the user started) is a feature with: its own switch in Settings, off by default; turning it on says what it sends, where, and what it costs; a budget (requests, tokens or money per day), and it stops when it is spent; a local trace the user can read (what, where, when, cost), sent nowhere else; one network gate owned by app (app/src/gate.rs), which checks the switch and budget. No crate opens a connection for such a feature around it. Connectors (GitHub through gh today) are such features: each is off until the user connects it, and its polling goes through the gate. Updates are asked for and signed: The check is off until the user turns it on (in Settings, and offered once on first run). When on, it fetches one static manifest from GitHub Releases at most once a day and sends nothing beyond the request: no id, no usage, no version in a query string. An update installs only after a click that shows the version and its notes. A running agent is never interrupted: the restart waits until the user chooses it. Three channels, each with its own manifest URL: stable (the default), beta (release candidates) and nightly (selected commits). A build knows its channel at compile time, so a stable build never reads the nightly manifest; moving between channels is a reinstall. Every update is signed. The private key lives only in the release job\u0026rsquo;s secrets; the public key is built into the app. A manifest or package that fails the check is ignored and logged. Packages a distribution updates (AUR, a future Flatpak) ship with the updater compiled out. Consequences # A test runs a day with every feature off and counts zero requests. No usage numbers steer the roadmap: we learn from issues and from using it ourselves. Anything that would learn from users\u0026rsquo; data has to learn on the user\u0026rsquo;s machine or not at all. The updater lands after 0.2.0 (0020). release.yml will sign the packages and publish one manifest per channel; the key is created once and backed up offline (losing it means users reinstall by hand). Nightly and beta builds get their own bundle id suffix, so they install beside stable without sharing settings or the keyring. History # 0006, 2026-10-04: secrets only in the keyring; no telemetry; leaving the machine is the user\u0026rsquo;s choice. 0016, 2026-10-08: the update check, asked for, signed, one channel at a time. ","externalUrl":null,"permalink":"/en/docs/vults/adr/0019-requests-on-the-users-behalf/","section":"Docs","summary":"Status: Accepted, 2026-10-10. Consolidated on 2026-10-12 from 0006 and 0016.\nContext # The app sees commands, file names, diffs and API keys, next to private …","title":"0019. Network, secrets and telemetry · Vults","type":"docs"},{"content":"Status: Accepted, 2026-10-10. Consolidated on 2026-10-12 from 0007 and 0015.\nContext # The first plan gave each version up to 0.5 one theme, which kept users waiting months for work ready in days and made a version number promise a whole theme. The plan that followed (docs/dev/plan-zeca.md) grew: Zeca\u0026rsquo;s memory and work, a companion that speaks when it should, an Android phone, and leftovers. Holding 0.2.0 until all of it is done would keep the Operations milestone waiting for work it does not need.\nDecision # The first release is 0.1.0. Releases stay small and frequent: a 0.1.x goes out whenever one or two steps are merged and the R3 smoke check passes; urgent fixes are the next one too. A patch number does not mean \u0026ldquo;fixes only\u0026rdquo;, and may pass 9. 0.2.0 is tagged at the gate of plan-zeca.md\u0026rsquo;s wave 4: the Nest, policies (0004), tasks, starting agents from the app and their review. It ships with the Living Flock layout (0021); the gate is the R3 smoke on that layout, once after U1 and again after U13, and the tag comes after the second. After it, waves 5 to 7 (the companion, the phone, the leftovers) ship as 0.2.x releases under the same rule. Linux first through 0.2.x, with Android as the one exception (0004). Windows and macOS betas may land late in 0.2.x (plan-zeca X7); their full support comes after. 1.0 means \u0026ldquo;done for Linux\u0026rdquo;. Consequences # Users get each feature when it is ready; the changelog is per release. Steps once promised by 0.2.0 that the plan puts later (the command palette, open-mic voice, the side panel, the desktop bird, Vercel, the voice dictionary) arrive in 0.2.x. plan-zeca.md, Waves, points at this record. History # 0007, 2026-10-04: the first release is 0.1.0; one theme per version up to 0.5 (dropped by 0015). 0015, 2026-10-04: small 0.1.x releases, with everything planned by 0.2.0 (narrowed here to the wave 4 gate). ","externalUrl":null,"permalink":"/en/docs/vults/adr/0020-0.2.0-at-wave-4-then-0.2.x/","section":"Docs","summary":"Status: Accepted, 2026-10-10. Consolidated on 2026-10-12 from 0007 and 0015.\nContext # The first plan gave each version up to 0.5 one theme, which kept users …","title":"0020. Releases: small and often, 0.2.0 at the Operations gate · Vults","type":"docs"},{"content":"Status: Accepted, 2026-10-11. Consolidated on 2026-10-12 from 0008, 0009, 0022, 0023, 0024 and 0030.\nContext # Users want several levels of presence: the island, a tray icon, a corner widget, notifications, a palette, a full app. Built naively, each would re-implement who needs attention, which session is in front and how a card ended. The owner\u0026rsquo;s Living Flock prototype (design/explorations/08-living-flock.html) draws the app as one identity at four distances, with the flock in a shared sky in every size. Some surfaces show agents\u0026rsquo; text, which may carry words meant to steer whatever reads them.\nDecision # Meaning in core, look in the surface. core says who needs the user (an ordered attention level), which session is in focus, and how each card ended (here, in the terminal, expired, by a rule). Surfaces choose clips, icons, colors and sounds, from one small view model (a projection per surface only when a payload grows), and speak only in Intents through the same commands. Four sizes: Mini is the pill (340 x 38, the flock and no text: news reaches it only as a body, a color or a motion). Quick is the island (440 px wide) inside the fixed layer surface. Room is the Nest window, resizable down to 480 x 360 px with no maximum, first opened large within the screen. Full is the Nest maximized or in full screen; Esc returns to the size before it. The layer-shell overlay never grows past its fixed size. Surfaces at rest are layer surfaces mapped once at a fixed size, one input region each; the corner widget sits in a corner the user picks and is not a window to drag. At rest one webview lives (the island, plus the widget if chosen); the Nest is created when opened. The palette is not a surface: it opens inside the island\u0026rsquo;s or the Nest\u0026rsquo;s page. A card is answered where the user is: on the island or in the Nest. The island holds permission and question cards and Ctrl+Alt+Y/N (in panel mode it anchors by the tray, the same window). The Nest answers the flock\u0026rsquo;s cards (Allow, Deny, Always allow, a question\u0026rsquo;s choices, Reply in the terminal) on the Flock\u0026rsquo;s card of the session in front, and its Chat\u0026rsquo;s cards in the Chat, with the island\u0026rsquo;s buttons and rules: destructive keeps Deny the bright one and offers no Always (0004). The user is in the Nest when its window has the focus and shows the Nest, not Settings; then a card does not open the island, the Nest\u0026rsquo;s need strip and sound say it waits, and Ctrl+Alt+Y/N answer the card on screen there. Anywhere else a card opens the island, so an agent never waits unseen. Going into the Nest folds an island that holds a card; leaving it brings the island back. Only these two answer: the app checks the webview behind each answer, and Settings, the widget and the palette never do. Every answer is still a click (or its key) by the user, and is audited. A paired phone may also answer (0004). No presence mode leaves an acknowledged card unseen. In Island, Panel and Quiet a pending card opens the island and plays its sound, or, in the Nest the user is in, shows in its need strip with its sound (Quiet only quiets what happens at rest). In Paused core does not acknowledge cards and releases them at once, so the agent asks in its terminal with no wait, and connectors stop polling. The Nest has five tabs, Flock, Tasks, Chat, Activity (Week with By project, History, Cost, Traces, Audit) and Connections, with Memory a side panel of the Chat, Settings behind a gear and the agents\u0026rsquo; capability table in Settings › Agents. Changing size keeps the selection: the session is core\u0026rsquo;s focus; the tab, filters and chosen connection are look the app hands to the Nest. The Nest opens with Zeca off, without the Chat tab and Ask Zeca. One surface at a time (the island and the Nest stay two surfaces underneath). While the Nest is open and in sight (Room or Full), the island is hidden, pill and sky and all, with no region that takes the mouse. It comes back when the Nest closes or goes back to Quick or Mini (or is minimized, where the system says so); the footer\u0026rsquo;s Back to the island is Quick. Nothing else wakes it but a card while the user is outside the Nest; answered, it folds after its usual delay and rests hidden again. The Nest\u0026rsquo;s window (nest-window) moves, resizes and is in Alt+Tab, without the system\u0026rsquo;s title bar: the prototype\u0026rsquo;s rounded frame, a header that moves it, edges that resize it. It holds two webviews under one header, the Nest\u0026rsquo;s (nest) and Settings\u0026rsquo; (settings), one shown at a time. The gear shows Settings; a tab shows the Nest; open_settings(section) opens the window on Settings, at a page when asked. Only Settings\u0026rsquo; webview holds the powers: writing an agent\u0026rsquo;s config (0005), saving or clearing a key, making or taking a policy, pairing, revoking or reaching a phone (0004), and turning on a request on the user\u0026rsquo;s behalf (0019). Commands check the label of the webview that called, never the window\u0026rsquo;s (settings_page, policies::from_settings). The two webviews share the origin\u0026rsquo;s storage, which Settings never reads. Settings shows no agent\u0026rsquo;s text: its frame holds tab names and counts, the size switch, sound, full screen, and a footer that switches Zeca and brings up the terminal of the session in front (it keeps only who that session is). Its pages are General, Agents, Chat \u0026amp; voice, Approvals, Projects, Activity, Connectors, Phone, Flock \u0026amp; Zeca, About. Consequences # A test checks every preset: an acknowledged card is always shown, on the island or in the Nest the user is in, and Paused never acknowledges one. While a card waits, the agent\u0026rsquo;s terminal says Waiting for your answer in Vults; the palette\u0026rsquo;s Review its card opens it in the Nest or on the island. The Nest open behaves as the Panel preset\u0026rsquo;s rest for the island. A future page picks its webview by what it shows: agents\u0026rsquo; text goes in the Nest\u0026rsquo;s, powers in Settings\u0026rsquo;. A surface that needs a power opens Settings at its page. Child webviews need Tauri\u0026rsquo;s unstable feature. On Linux the webviews of a window share its box, so they are switched, never laid over each other, and the hidden one is kept out of GTK\u0026rsquo;s show-all. Opening on Settings loads the Nest\u0026rsquo;s page too, hidden and resting. An event the app sends reaches every webview\u0026rsquo;s listeners; a page still draws only what it asked for. On Wayland the compositor places the Nest\u0026rsquo;s window; it cannot be pinned under the island\u0026rsquo;s spot. History # 0008, 2026-10-04: one core, many surfaces; the island is the only card host. 0009, 2026-10-04: no presence mode hides an acknowledged card. 0022, 2026-10-12: only Settings holds the powers (then as a window of its own). 0023, 2026-10-12: Settings becomes a webview of the Nest\u0026rsquo;s window; checks go by webview label. 0024, 2026-10-12: the island steps aside while the Nest is open. 0030, 2026-10-12: the Nest answers a card too, while the user is in it. ","externalUrl":null,"permalink":"/en/docs/vults/adr/0021-four-sizes-one-nest/","section":"Docs","summary":"Status: Accepted, 2026-10-11. Consolidated on 2026-10-12 from 0008, 0009, 0022, 0023, 0024 and 0030.\nContext # Users want several levels of presence: the …","title":"0021. Surfaces: one core, four sizes, a card answered where you are · Vults","type":"docs"},{"content":"Status: Proposed, 2026-10-12. Amends 0004, within 0003.\nContext # Vults answers a permission only for the agents it knows: each one reads its own reply shape (Claude Code\u0026rsquo;s hookSpecificOutput, Copilot CLI\u0026rsquo;s bare behavior), so each was added by hand after a recorded session. Any other tool can already put its sessions on the wire with vults-hook --agent \u0026lt;name\u0026gt; and Claude Code\u0026rsquo;s hook JSON, but its PermissionRequest is shown as a question waiting in its terminal and the hook never waits: nothing in the app knows what that tool would read back (see Permissions in Other agents).\nThat keeps the door shut for the tools people build themselves (scripts, in-house agents, orchestrators running several agents), which are what the flock and its cards are for. A tool that wants a card should be able to ask for one, if it takes one fixed answer and honors it.\nDecision # A tool asks for the reply. Its hook command adds --reply vults: vults-hook --agent \u0026lt;name\u0026gt; --reply vults PermissionRequest. Without it nothing changes: the hook never waits for a named tool, as today. Built-in names (claude, codex, copilot, gemini, opencode, qwen, other, and every agent the registry lists, such as cursor or kimi) cannot take it. It sends Claude Code\u0026rsquo;s hook JSON, the format most tools already copy: a PermissionRequest with session_id, cwd, tool_name and tool_input. The card is built from it as for any agent: the whole command, file or URL, never a summary the tool wrote. It reads one fixed reply on stdout, one JSON line: {\u0026quot;decision\u0026quot;:\u0026quot;allow\u0026quot;} or {\u0026quot;decision\u0026quot;:\u0026quot;deny\u0026quot;,\u0026quot;reason\u0026quot;:\u0026quot;Denied from Vults\u0026quot;}. Anything else, empty stdout included, means ask the user yourself: no click in time, the app closed or paused, an unsupported protocol version, any failure. The hook still exits 0 and never blocks the tool (0003); silence is never an allow. Who answers is who answers today (0004): a click, a signed answer from a paired phone (0004), or an exact Always rule the user made on that tool\u0026rsquo;s own card. Rules are kept by the tool\u0026rsquo;s name, its folder, the tool and the target: a rule made for one named tool never answers another, nor a built-in agent. Policies (0004) reach a named tool only when they name it (agents my-tool), and then only to deny or ask: a policy never allows a named tool\u0026rsquo;s call, as it never allows any agent\u0026rsquo;s but Claude Code\u0026rsquo;s today. A destructive call always asks (0004). Every answer is audited with the tool\u0026rsquo;s name. A named tool gains nothing a built-in agent\u0026rsquo;s card would not show. It reaches the same socket any process of the user\u0026rsquo;s can already reach (same user, checked by peer); its card shows exactly what it sent; an Allow goes back only to the call that asked. Vults cannot make a tool honor the reply, as it cannot make any agent: a tool that ignores it is one that never asked. A name that becomes a built-in agent later loses its rules: they are not carried over to an agent that reads another reply. Consequences # G2 builds it: the hook\u0026rsquo;s flag and reply, a protocol VERSION bump (an old app answers unsupported, so the tool asks itself), rules and policies keyed by name in the core, the named tools in Settings → Agents with their rules, and Hook protocol written as a spec with an example tool in shell and in TypeScript. CLAUDE.md\u0026rsquo;s rule 2, Safety and Other agents say a tool that asks with --reply vults gets a card, and how it is answered. A test tool in shell gets a card, an allow and a deny, and asks itself when the app is closed; an old hook stays silent. ","externalUrl":null,"permalink":"/en/docs/vults/adr/0027-an-open-reply-for-any-tool/","section":"Docs","summary":"Status: Proposed, 2026-10-12. Amends 0004, within 0003.\nContext # Vults answers a permission only for the agents it knows: each one reads its own reply shape …","title":"0027. An open reply for any tool that asks for one · Vults","type":"docs"},{"content":"Status: Accepted, 2026-10-12. Consolidated on 2026-10-12 from 0013.\nContext # Voice is how many users want to talk to Zeca, and hear him. Audio is personal, and the best engines are either cloud services or large local models. Push-to-talk shipped in #15. The local voice that shipped for spoken replies (Kokoro, with espeak-ng phonemes) was removed on 2026-10-07: it sounded too poor to keep, above all in Portuguese. Cloud transcription was never built, and the user dropped it from the plan on 2026-10-12.\nDecision # Speech to text runs on this computer: whisper.cpp through whisper-rs, on the GPU through Vulkan when there is one; models downloaded from Settings and checked by SHA-256. Audio stays in memory and is never saved. No cloud transcription. No wake word and no always-on microphone: the mic opens only when the user asks. Spoken replies come from a realtime model the user turns on. It is a request on the user\u0026rsquo;s behalf (0019): off by default, with its own switch, budget and trace, through the network gate in app. Its key lives only in the OS keyring. Turning it on says that audio leaves the machine and where it goes. No GPL code in our binary: Piper (GPL-3.0) is out, and so is the sherpa-onnx crate\u0026rsquo;s default build, which links espeak-ng (GPL-3.0). A GPL tool the user installed may run as a separate process. Each engine or model that runs locally is checked for its license (code and model) before it goes in. Voice never answers a card (0004), whatever the model says. Consequences # With the feature off, Zeca does not speak; nothing about voice leaves the machine. No speech engine or voice ships in our binary, so the license checks apply to whisper.cpp and its models. Bigger downloads and a GPU path to keep working. History # 0013, 2026-10-04: local speech to text, no always-on mic, no GPL in the binary, voice never answers a card. Its local spoken replies and opt-in cloud transcription were superseded here. ","externalUrl":null,"permalink":"/en/docs/vults/adr/0028-spoken-replies-through-a-realtime-model/","section":"Docs","summary":"Status: Accepted, 2026-10-12. Consolidated on 2026-10-12 from 0013.\nContext # Voice is how many users want to talk to Zeca, and hear him. Audio is personal, and …","title":"0028. Voice: local listening, opt-in spoken replies · Vults","type":"docs"},{"content":"lazyagents is the lazygit for AI coding agents: a terminal UI and CLI that manages, in one place, what your coding agents use: skills, sessions, usage, providers and hooks. New here? Start with Getting started.\nAlso on the web at rogeriojunior31.github.io/docs/lazyagents, and fully translated into Brazilian Portuguese: docs/pt-br.\nGuides # One page per module, organized by task.\nGuide What it covers Skills The skill library, enabling skills per agent, installing from GitHub, folders and zips, profiles, updates and backups Sessions One history across agents: resume, read and export transcripts, search, aliases, cleanup Usage Subscription limits, token consumption by day, agent, project and model, cost estimates Providers Endpoint and model profiles applied to each agent\u0026rsquo;s live config Hooks A hook library installed per agent, and hooks imported from repositories Agents Which agents are detected and what each one supports Plugins Adding tabs and commands with external executables, in any language Topics # Configuration: config.yaml, tab layout, where every file lives. Themes: built-in themes and writing your own. Safety and data: what lazyagents writes, backups and restore, secrets, consent. Troubleshooting: common problems and how to fix them. Reference # Generated from the code, so they always match the version in this tree.\nCLI: every command and option. Keys: every key of every tab, and the command palette. Agent support: skill dirs, capabilities and hook events per agent. Built-in themes. Plugin protocol: the JSON Lines contract external plugins implement. Contributing # Architecture: how the code is organized and how to add a module, an agent or a theme. Writing docs: what is generated, what is written by hand and what the tests check. CONTRIBUTING, SECURITY and the Code of Conduct. ","externalUrl":null,"permalink":"/en/docs/lazyagents/","section":"Docs","summary":"lazyagents is the lazygit for AI coding agents: a terminal UI and CLI that manages, in one place, what your coding agents use: skills, sessions, usage, …","title":"lazyagents documentation","type":"docs"},{"content":"The sodium lamp turns the whole city this colour. SP Night is a dark colour scheme with São Paulo as its reference — the sodium street lamp, exposed concrete, the free span of the MASP, the drizzle before the rain.\nThis repository is the contract and the tool. The contract is a palette and a role layer that says which colour means what. The tool, spn, turns a port\u0026rsquo;s mapping into finished theme files, measures the palette against contrast floors, and refuses to build when it falls short.\nWhere to start # Getting started: pick a flavour, install a port, or build spn yourself. The spec: the palette, the roles, and the rule for which colour goes where. Read it before touching a mapping. Adding a port: from a catalogue entry to a published repository, step by step. Command reference: every spn command and flag. The three flavours # All three are dark, by decision — three ways of looking at the same city.\nid label idea noite Noite Paulista the city at 3am; blue-violet dark, the sodium lamp burning warm on top garoa Garoa the same window through the drizzle; flat grey, chroma near zero jaragua Pico do Jaraguá the same night from the highest point; the dark turned towards the forest Links # Website, with the palette and a page per port: sp-night.github.io Install guides for every port: sp-night.github.io/ports Source: github.com/sp-night/sp-night License: MIT ","externalUrl":null,"permalink":"/en/docs/sp-night/","section":"Docs","summary":"The sodium lamp turns the whole city this colour. SP Night is a dark colour scheme with São Paulo as its reference — the sodium street lamp, exposed concrete, …","title":"SP Night","type":"docs"},{"content":"Vults shows your coding agents on your desktop and gives you a companion to work with. The flock: every Claude Code, Codex, Antigravity, OpenCode, Qwen Code, Copilot CLI, Cursor, Factory Droid and Kimi Code session is a vulture that shows what it is doing; approve or deny its permissions with a click, answer its questions, jump to its terminal, and get news from GitHub. Zeca: a black vulture who chats with you through the CLIs you already use and listens to your voice. He is optional: turn him off and the flock works without him.\nVersion 0.1, early. Packages are on the latest release; see Getting started.\nGetting started The island Approving from the island The Nest Tasks Chatting with Zeca Zeca\u0026rsquo;s memory Connectors Activity Other agents The phone Safety Settings and files Hook protocol The phone\u0026rsquo;s relay Animations Architecture Decisions (ADRs) Adding a connector Adding an agent ","externalUrl":null,"permalink":"/en/docs/vults/","section":"Docs","summary":"Vults shows your coding agents on your desktop and gives you a companion to work with. The flock: every Claude Code, Codex, Antigravity, OpenCode, Qwen Code, …","title":"Vults","type":"docs"},{"content":"","date":"Oct 5, 2026","externalUrl":null,"permalink":"/en/tags/ai/","section":"Tags","summary":"","title":"AI","type":"tags"},{"content":"","date":"Oct 5, 2026","externalUrl":null,"permalink":"/en/tags/desktop/","section":"Tags","summary":"","title":"Desktop","type":"tags"},{"content":"Terminal tools, open source and AI experiments.\nExplore projects · Documentation · Resume · Contact me\nOPEN SOURCE / RUST / TAURI / LINUXVults_ Your coding agents, alive on your desktop.\nEvery agent session is an 8-bit vulture at the top of the screen, doing what its session does. Approve, answer and jump to the right terminal in one click. Zeca, the companion, chats through the CLIs you already use.\nClaude Code · Codex · Gemini CLI · Antigravity\nExplore the docs → Download ↗ GitHub ↗ About the project the islandKDE Plasma Island → Card → TerminalScreenshot from the docs, at this release OPEN SOURCE / GO / TUIlazyagents_ Your agents. One terminal.\nEnable skills, find conversations and track usage across your AI coding agents. All in one workspace.\nClaude Code · Codex · Gemini CLI · OpenCode · Hermes\nExplore the docs → GitHub ↗ About the project ~/lazyagents13s Download the demo Skills → Sessions → UsageRecorded with demo data · no audio OPEN SOURCE / GO / COLOUR SCHEMESP Night_ The sodium lamp turns the whole city this colour.\nA dark colour scheme with São Paulo as its reference. Every colour is measured for contrast before it ships, and every port is generated from the same palette.\nGhostty · Kitty · Alacritty · Eza · Helix · Herdr · Foot\nExplore the docs → Install a port ↗ GitHub ↗ ~/sp-nightspn palette noite Noite Paulista\nThe city at 3am. Blue-violet dark, the sodium lamp burning warm on top.\ngaroa Garoa\nThe same window, seen through the drizzle. Flat grey — the garoa does not cool the city down, it washes it out.\njaragua Pico do Jaraguá\nThe same night, seen from the city\u0026#39;s highest point. Near-black surfaces, with the forest left to the accents — and the red-and-white tower lit at the summit.\n23 colours · 3 flavoursRead from palette/sp_night.json ","date":"Oct 5, 2026","externalUrl":null,"permalink":"/en/","section":"Home","summary":"Personal site of Rogerio de Queiroz Junior: projects, posts and resume.","title":"Home","type":"page"},{"content":"","date":"5 Oct 2026","externalUrl":null,"permalink":"/tags/ia/","section":"Tags","summary":"","title":"IA","type":"tags"},{"content":"","date":"Oct 5, 2026","externalUrl":null,"permalink":"/en/tags/open-source/","section":"Tags","summary":"","title":"Open Source","type":"tags"},{"content":"","date":"Oct 5, 2026","externalUrl":null,"permalink":"/en/projects/","section":"Projects","summary":"Things I have built.","title":"Projects","type":"projects"},{"content":"","date":"Oct 5, 2026","externalUrl":null,"permalink":"/en/tags/rust/","section":"Tags","summary":"","title":"Rust","type":"tags"},{"content":"","date":"Oct 5, 2026","externalUrl":null,"permalink":"/en/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"Oct 5, 2026","externalUrl":null,"permalink":"/en/tags/tauri/","section":"Tags","summary":"","title":"Tauri","type":"tags"},{"content":" OPEN SOURCE / RUST / TAURI / LINUXVults_ Your coding agents, alive on your desktop.\nEvery agent session is an 8-bit vulture at the top of the screen, doing what its session does. Approve, answer and jump to the right terminal in one click. Zeca, the companion, chats through the CLIs you already use.\nClaude Code · Codex · Gemini CLI · Antigravity\nExplore the docs → Download ↗ GitHub ↗ the islandKDE Plasma Island → Card → TerminalScreenshot from the docs, at this release Running several coding agents at once means losing track of them: one finished, one failed, one has been waiting for a permission for ten minutes in a terminal you cannot find. Vults puts them on your desktop. Every Claude Code, Codex, Gemini CLI or Antigravity session becomes a vult, an 8-bit vulture on an island at the top of the screen, doing what its session does.\nWorks with: Claude Code, Codex, Gemini CLI and Antigravity, on Linux (KDE Plasma and other layer-shell compositors first).\nApprovals in one place: the card shows the whole command, file or diff; Allow, Deny or a global shortcut. Nothing is approved without your click. Live diffs and jump to the terminal: each edit shows its lines added and removed, and one click focuses the session\u0026rsquo;s tmux, kitty, wezterm or herdr pane. News from GitHub and plan usage: failed checks, reviews and your Claude and Codex limits, through the CLIs you already use. Zeca, the companion: a black vulture who chats through the claude or codex CLI, listens when you hold a key and speak (whisper.cpp, on your computer) and takes the files you drop on him. One switch turns him off. Safe by design: the hook never blocks your agent, agent configs change only after a backup and a diff you approve, secrets live in the OS keyring, no telemetry. Download the .deb or .rpm from the latest release, or build the Arch package with makepkg.\nDocumentation Download github.com/rogeriojunior31/Vults ","date":"Oct 5, 2026","externalUrl":null,"permalink":"/en/projects/vults/","section":"Projects","summary":"A Rust and Tauri desktop app for people who run several coding agents at once: every session is an 8-bit vulture at the top of the screen, with approvals, chat and a companion named Zeca.","title":"Vults","type":"projects"},{"content":"","date":"Sep 28, 2026","externalUrl":null,"permalink":"/en/tags/cli/","section":"Tags","summary":"","title":"CLI","type":"tags"},{"content":"","date":"Sep 28, 2026","externalUrl":null,"permalink":"/en/tags/go/","section":"Tags","summary":"","title":"Go","type":"tags"},{"content":" OPEN SOURCE / GO / TUIlazyagents_ Your agents. One terminal.\nEnable skills, find conversations and track usage across your AI coding agents. All in one workspace.\nClaude Code · Codex · Gemini CLI · OpenCode · Hermes\nExplore the docs → GitHub ↗ ~/lazyagents13s Download the demo Skills → Sessions → UsageRecorded with demo data · no audio Every AI coding agent keeps its skills, sessions and settings in its own folder, in its own format. lazyagents brings them into one terminal, in the spirit of lazygit: one skill library you enable per agent with a keypress, every agent\u0026rsquo;s session history in one place, and your subscription usage always in sight.\nWorks with: Claude Code, Codex, Gemini CLI, OpenCode and Hermes Agent.\nSkills: install from a GitHub repository, a folder or a zip, and enable per agent with a symlink, nothing copied. Sessions: resume any conversation in its own CLI and folder, read the transcript, search across every agent. Usage: subscription limits (session and weekly windows) and tokens by day, agent, project and model. Providers and hooks: endpoint profiles and hooks applied to each agent\u0026rsquo;s config, confirmed and backed up before writing. Plugins: any executable becomes a new tab and command, in any language. go install github.com/rogeriojunior31/lazyagents@latest Binaries for Linux, macOS and Windows are on the releases page too.\nDocumentation github.com/rogeriojunior31/lazyagents The TUI uses the SP Night palette.\n","date":"Sep 28, 2026","externalUrl":null,"permalink":"/en/projects/lazyagents/","section":"Projects","summary":"A Go TUI to manage, in one place, what your AI coding agents use: skills, sessions, usage, providers and hooks.","title":"lazyagents","type":"projects"},{"content":"","date":"Sep 28, 2026","externalUrl":null,"permalink":"/en/tags/tui/","section":"Tags","summary":"","title":"TUI","type":"tags"},{"content":"","date":"Aug 2, 2026","externalUrl":null,"permalink":"/en/tags/astro/","section":"Tags","summary":"","title":"Astro","type":"tags"},{"content":" OPEN SOURCE / GO / COLOUR SCHEMESP Night_ The sodium lamp turns the whole city this colour.\nA dark colour scheme with São Paulo as its reference. Every colour is measured for contrast before it ships, and every port is generated from the same palette.\nGhostty · Kitty · Alacritty · Eza · Helix · Herdr · Foot\nExplore the docs → Install a port ↗ GitHub ↗ ~/sp-nightspn palette noite Noite Paulista\nThe city at 3am. Blue-violet dark, the sodium lamp burning warm on top.\ngaroa Garoa\nThe same window, seen through the drizzle. Flat grey — the garoa does not cool the city down, it washes it out.\njaragua Pico do Jaraguá\nThe same night, seen from the city\u0026#39;s highest point. Near-black surfaces, with the forest left to the accents — and the red-and-white tower lit at the summit.\n23 colours · 3 flavoursRead from palette/sp_night.json A dark colour scheme with São Paulo as its reference: the sodium street lamp, exposed concrete, the free span of the MASP, the drizzle before the rain. Three flavours, all dark by decision: noite, garoa and jaragua, three ways of looking at the same city.\nThe project is a contract and a tool. The contract is the palette and a role layer that says which colour means what. The tool, spn, turns each app\u0026rsquo;s mapping into finished theme files and refuses to build when the palette falls below its contrast floors.\nNo hex picked by hand: themes, READMEs and previews come from the palette, so they cannot show a colour you will not get. Contrast is a gate: every colour pair is measured on every build; an unreadable comment does not ship. Accents kept apart: neighbouring pairs must differ in lightness, not only in hue. Reproducible ports: the suite renders the published ports and compares them byte for byte with what was installed. To use a theme, pick a flavour, copy the port\u0026rsquo;s file to the app\u0026rsquo;s config path and activate it with one line. Every port\u0026rsquo;s guide is at sp-night.github.io/ports.\nDocumentation sp-night.github.io github.com/sp-night/sp-night This site uses the Pico do Jaraguá flavour.\n","date":"Aug 2, 2026","externalUrl":null,"permalink":"/en/projects/sp-night/","section":"Projects","summary":"A dark colour scheme with São Paulo as its reference: three flavours, contrast-checked colours and ports generated from one palette for terminals, editors and CLIs.","title":"SP Night","type":"projects"},{"content":"","date":"2 Aug 2026","externalUrl":null,"permalink":"/tags/tema/","section":"Tags","summary":"","title":"Tema","type":"tags"},{"content":"","date":"Aug 2, 2026","externalUrl":null,"permalink":"/en/tags/theme/","section":"Tags","summary":"","title":"Theme","type":"tags"},{"content":" rogeriojunior31/Fine-tune-Gemma-models-in-Keras-using-LoRA Jupyter Notebook 0 0 The goal # Explore instruction tuning of Gemma 2B with Keras and LoRA without training every model parameter. The notebook uses Databricks Dolly 15k and the JAX backend.\nThe experiment # Load and prepare instruction examples. Generate responses before tuning for comparison. Train for one epoch with LoRA rank 4 and sequences up to 512 tokens. Generate responses to the same prompts after tuning. This is a qualitative comparison, not a benchmark establishing a general improvement in model quality.\nExplore it # Open the notebook on GitHub to read the full workflow. To run it, follow its installation and Kaggle configuration cells in an environment with enough resources to load and train the model, preferably with a GPU.\nGemma access requires credentials and the model\u0026rsquo;s terms. Do not publish your kaggle.json or tokens. This is a 2024 experiment; check dependency compatibility before running it in a current environment.\nView repository and instructions\n","date":"Apr 26, 2024","externalUrl":null,"permalink":"/en/projects/gemma-lora/","section":"Projects","summary":"Notebook fine-tuning Gemma models in Keras using LoRA.","title":"Fine-tuning Gemma with LoRA","type":"projects"},{"content":"","date":"Apr 26, 2024","externalUrl":null,"permalink":"/en/tags/keras/","section":"Tags","summary":"","title":"Keras","type":"tags"},{"content":"","date":"Apr 26, 2024","externalUrl":null,"permalink":"/en/tags/llm/","section":"Tags","summary":"","title":"LLM","type":"tags"},{"content":"","date":"Apr 26, 2024","externalUrl":null,"permalink":"/en/tags/lora/","section":"Tags","summary":"","title":"LoRA","type":"tags"},{"content":" rogeriojunior31/house_price_predictor Python 0 0 The problem # Estimate housing values from the California Housing Dataset and explore how its features relate to prices. This is a machine learning study, not a commercial property valuation tool.\nHow it works # Python and scikit-learn to load data and split training and test sets. Linear regression as a baseline; Ridge, cross-validation and GridSearchCV in the enhanced version. RMSE to evaluate predictions on the test set. Matplotlib and Seaborn to visualize data features and compare actual and predicted values. Run it # With Python 3.9 or later (for the repository\u0026rsquo;s pinned dependencies), in a virtual environment:\ngit clone https://github.com/rogeriojunior31/house_price_predictor.git cd house_price_predictor python -m venv .venv # Linux/macOS; on Windows, use .venv\\Scripts\\activate source .venv/bin/activate pip install -r requirements.txt python main.py Running the script produces RMSE and analysis plots. The initial dataset download may require internet access. Results depend on the run; no metric published here establishes performance in other housing markets.\nView code and instructions on GitHub\n","date":"Feb 4, 2024","externalUrl":null,"permalink":"/en/projects/house-price-predictor/","section":"Projects","summary":"Python machine learning model to estimate house prices.","title":"House price predictor","type":"projects"},{"content":"","date":"Feb 4, 2024","externalUrl":null,"permalink":"/en/tags/machine-learning/","section":"Tags","summary":"","title":"Machine Learning","type":"tags"},{"content":"","date":"Feb 4, 2024","externalUrl":null,"permalink":"/en/tags/python/","section":"Tags","summary":"","title":"Python","type":"tags"},{"content":"I\u0026rsquo;m a developer from São Paulo. I started freelancing in 2017, moved to back-end at Linkapi and was a Tech Lead at Samsung SDS and Kruzer. These days I\u0026rsquo;m a Software Engineer Specialist at CSU Digital, working on AI tooling and LLM apps.\nI spend most of the day in the terminal. This is my setup:\nrogerio@archlinux: ~❯ fastfetch -` .o+` `ooo/ `+oooo: `+oooooo: -+oooooo+: `/:-:++oooo+: `/++++/+++++++: `/++++++++++++++: `/+++ooooooooooooo/` ./ooosssso++osssssso+` .oossssso-````/ossssss+` -osssssso. :ssssssso. :osssssss/ osssso+++. /ossssssss/ +ssssooo/- `/ossssso+/:- -:/+osssso+- `+sso+:-` `.-/+oso: `++:. `-/+/ .` `/rogerio@archlinux-----------------OS: Arch Linux x86_64Shell: fishTerminal: tmuxTheme: SP NightFont: JetBrains MonoLanguages: TypeScript, JavaScript, Python, Go, BashFocus: AI · LLM · back-end · DevOpsLocation: São Paulo · BRLocale: pt_BR.UTF-8, en_US.UTF-8❯ What I\u0026rsquo;m working on # rogerio@archlinux: ~❯ systemctl --user list-units UNITACTIVESUBDESCRIPTION● ai-tooling.serviceactiverunningAI tools for developers● llm-apps.serviceactiverunningRAG, agents, fine-tuning● backend.serviceactiverunningAPIs and distributed systems● devops.serviceactiverunningCI/CD, cloud and infra● architecture.serviceactiverunningdesign and code review● self-hosting.serviceactiverunninghomelab and open source● kaggle.timeractivewaitingML experiments❯ Off the clock # SP Night: a dark colour scheme with São Paulo as its reference. This site uses the Pico do Jaraguá flavour. lazyagents: a TUI to manage the skills, sessions and usage of AI coding agents, open source since v0.3. Homelab: self-hosting and open source. Kaggle: ML experiments, like fine-tuning Gemma with LoRA. Retro games: chasing achievements on RetroAchievements. Everything else lives on Steam. The full story is in my resume.\n","externalUrl":null,"permalink":"/en/about/","section":"Home","summary":"Who I am, my setup and what I'm working on.","title":"About","type":"page"},{"content":"","externalUrl":null,"permalink":"/en/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","externalUrl":null,"permalink":"/en/docs/","section":"Docs","summary":"Documentation for my projects, built from each repository's docs/ folder.","title":"Docs","type":"docs"},{"content":"","externalUrl":null,"permalink":"/en/posts/","section":"Posts","summary":"Articles and notes.","title":"Posts","type":"posts"},{"content":" Experience # rogerio@archlinux: ~/cv❯ cat ~/cv/experience.tsv | column -t COMPANYROLEPERIOD● CSU DigitalSoftware Engineer SpecialistAug 2026 - now● KruzerTech LeadJan 2026 - Aug 2026● Samsung SDSTech Lead2022 - Dec 2025● Linkapi SolutionsBack-End Engineer2021 - 2022● FreelanceFreelancer2017 - 2021❯ Stack # rogerio@archlinux: ~❯ eza --tree ~/stack ~/stack├── languages│ └── typescript javascript python go bash├── back-end│ └── node.js bun nestjs api-design microservices├── cloud-devops│ └── aws azure docker github-actions elastic-stack├── ai-ml│ └── langchain llamaindex pytorch keras lora-fine-tuning llm-apis└── databases └── postgresql mysql mongodb redis5 directories, 25 files❯ ","externalUrl":null,"permalink":"/en/resume/","section":"Home","summary":"Work experience and stack.","title":"Resume","type":"page"}]