This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

configs: remove omnigent, covibe and herdr from all machines

+9 -771
-21
configs/herdr.nix
··· 1 - { self, pkgs, ... }: 2 - { 3 - # herdr: agent multiplexer (https://herdr.dev). Keeps coding-agent terminals 4 - # running in a background server that survives a dropped ssh session, marks 5 - # every pane working/blocked/idle and joins saved ssh machines into one 6 - # window, so a herd of agents across hosts can be driven from one client. 7 - # Taken from llm-agents (tracks upstream releases; nixpkgs lags). 8 - environment.systemPackages = [ self.inputs.llm-agents.packages.${pkgs.system}.herdr ]; 9 - 10 - # Multi-machine: `herdr machine add <host>.r --label <host>` on the client 11 - # host saves an ssh profile in ~/.local/state/herdr/client/endpoints.json and 12 - # starts the remote server. No bootstrap download is needed because 13 - # systemPackages puts herdr in the non-interactive ssh PATH 14 - # (/run/current-system/sw/bin), and every machine here shares the llm-agents 15 - # pin, so client and server versions match as long as they are deployed 16 - # together. A machine lagging behind shows "Attention" until it is updated. 17 - 18 - # The whip app on massulus (herdr's mobile client) drives the agents over 19 - # ssh as lass, so it gets in wherever herdr runs. 20 - users.users.mainUser.openssh.authorizedKeys.keys = [ self.keys.ssh.whip_massulus.public ]; 21 - }
+2 -2
configs/hermes-proxy.nix
··· 45 45 # This is what the WakeHermesClaw app on massulus points at when it is not on 46 46 # retiolum: the assistant button posts to /v1/chat/completions here. 47 47 # 48 - # SECURITY: behind this vhost sits an agent with terminal tools and, through 49 - # the herdr wrapper, code execution as lass. The gateway's only check on this 48 + # SECURITY: behind this vhost sits an agent with terminal tools. The 49 + # gateway's only check on this 50 50 # lane is the API_SERVER_KEY bearer token — there is no per-user allowlist like 51 51 # MATRIX_ALLOWED_USERS on the Matrix lane, and the app cannot do mTLS (it 52 52 # builds a bare OkHttpClient with no KeyManager), so a client certificate is
-191
configs/omnigent.nix
··· 1 - let 2 - domain = "omni.lassul.us"; 3 - port = 8771; 4 - stateDir = "/var/lib/omnigent"; 5 - in 6 - { 7 - config, 8 - lib, 9 - pkgs, 10 - self, 11 - ... 12 - }: 13 - let 14 - # Server-side agent catalog entry so a browser session can pick omp without 15 - # any per-user config: the generic ACP harness drives `omp acp` (omp speaks 16 - # the Agent Client Protocol natively). The command is unqualified on purpose 17 - # — it is resolved on the *host* that runs the session, not here, so that host 18 - # needs omp on PATH (`covibe host` registers one that way). 19 - # 20 - # This has to be a single-file spec, NOT a bundle directory with config.yaml: 21 - # the bundle parser stringifies nested values under `executor.config`, so 22 - # `acp_agent` arrives as "{'name': …}" and every turn dies with 23 - # "turn setup failed: executor acp_agent must be a mapping with name and 24 - # command" — while the session itself sits there looking idle. The single-file 25 - # parser keeps it a mapping. `--agent` accepts either shape (spec 26 - # materialize_bundle branches on file-vs-directory). 27 - ompAgent = pkgs.writeText "omnigent-agent-omp.yaml" '' 28 - name: omp 29 - description: Oh My Pi (omp) over ACP — runs on whichever machine hosts the session. 30 - prompt: You are omp (Oh My Pi), a concise coding assistant. 31 - 32 - executor: 33 - harness: acp 34 - acp_agent: 35 - name: Oh My Pi 36 - command: omp acp 37 - # See tools/covibe/flake-module.nix: omp fails session/new outright 38 - # when omnigent's MCP relay cannot start, and the relay's 39 - # `python -I -m omnigent…` child cannot import omnigent under a 40 - # nixpkgs-wrapped interpreter. 41 - omnigent_mcp: false 42 - 43 - os_env: 44 - type: caller_process 45 - cwd: "." 46 - sandbox: 47 - type: none 48 - 49 - # Without a declared terminal the dashboard refuses to open one ("Terminals 50 - # can only be created for agents whose spec declares them"), so a session 51 - # started from the browser would have no way to poke at the machine it runs 52 - # on. One entry is enough; the panel can open several instances of it. 53 - terminals: 54 - shell: 55 - command: bash 56 - os_env: inherit 57 - ''; 58 - 59 - # One identity per line. Admins bypass every per-session ACL, which is what 60 - # makes sharing manageable at all: a session's creator gets LEVEL_OWNER, and 61 - # only owners/managers can grant others read (watch + diffs) or edit (steer). 62 - admins = pkgs.writeText "omnigent-admins" '' 63 - lass@lassul.us 64 - ''; 65 - in 66 - { 67 - # omnigent: self-hosted agent meta-harness. The server owns sessions, 68 - # transcripts, per-session ACLs (read / edit / manage / owner), the per-file 69 - # diff view and the browser UI; the agents themselves run on whichever 70 - # machine registers as a *host*, so no coding agent runs on neoprism unless 71 - # someone points a host at it. `s covibe` in a project directory is the 72 - # client: it runs omp locally and registers the session here. 73 - # 74 - # Browser auth is the local pocket-id IdP. pocket-id has no declarative 75 - # client API, so register the client by hand at https://id.lassul.us: 76 - # name: omnigent 77 - # callback URL: https://omni.lassul.us/auth/callback 78 - # logout URL: https://omni.lassul.us/ 79 - # then paste its id + secret into the clan prompt below. 80 - 81 - # Generated cookie-signing secret plus the prompted pocket-id client 82 - # id/secret, materialised into one EnvironmentFile. 83 - clan.core.vars.generators.omnigent = { 84 - files."env" = { }; 85 - prompts.oidc_client = { 86 - description = '' 87 - Register an OIDC client in pocket-id (https://id.lassul.us): 88 - name: omnigent 89 - callback URL: https://omni.lassul.us/auth/callback 90 - Generate a client secret, then paste both lines: 91 - OMNIGENT_OIDC_CLIENT_ID=... 92 - OMNIGENT_OIDC_CLIENT_SECRET=... 93 - ''; 94 - type = "multiline"; 95 - persist = true; 96 - }; 97 - runtimeInputs = [ pkgs.openssl ]; 98 - script = '' 99 - { 100 - printf 'OMNIGENT_OIDC_COOKIE_SECRET=%s\n' "$(openssl rand -hex 32)" 101 - cat "$prompts"/oidc_client 102 - } > "$out/env" 103 - ''; 104 - }; 105 - 106 - systemd.services.omnigent = { 107 - description = "omnigent agent meta-harness (${domain})"; 108 - wantedBy = [ "multi-user.target" ]; 109 - after = [ 110 - "network-online.target" 111 - "nginx.service" 112 - ]; 113 - wants = [ "network-online.target" ]; 114 - environment = { 115 - HOME = stateDir; 116 - OMNIGENT_AUTH_ENABLED = "1"; 117 - OMNIGENT_AUTH_PROVIDER = "oidc"; 118 - OMNIGENT_OIDC_ISSUER = "https://id.lassul.us"; 119 - OMNIGENT_OIDC_REDIRECT_URI = "https://${domain}/auth/callback"; 120 - OMNIGENT_OIDC_LOGOUT_REDIRECT_URI = "https://${domain}/"; 121 - OMNIGENT_OIDC_ALLOWED_DOMAINS = "lassul.us"; 122 - # pocket-id emits `email` *and* `email_verified` in the id_token when the 123 - # `email` scope is requested (which omnigent does), but the value of 124 - # `email_verified` is the users.email_verified column, whose default comes 125 - # from the app-config key `emailsVerified` — shipped as false. omnigent 126 - # otherwise hard-rejects the login with "Could not determine user email 127 - # from IdP". The gate exists for IdPs where a user can self-assert an 128 - # arbitrary address; here users are provisioned by hand in pocket-id 129 - # (signups are disabled) and the domain allowlist above still applies, so 130 - # the marker carries no information for us. 131 - OMNIGENT_OIDC_SKIP_EMAIL_VERIFICATION = "1"; 132 - # Default is 8 hours, which expires the `s covibe` session JWT roughly 133 - # once a working day and forces a browser round-trip through pocket-id. 134 - # A week is the pragmatic setting for a single-operator host behind an 135 - # invite-less IdP; the tokens live 0600 in ~/.omnigent/auth_tokens.json, 136 - # so shorten this if a laptop ever leaves the house untrusted. 137 - OMNIGENT_OIDC_SESSION_TTL_HOURS = "168"; 138 - OMNIGENT_ADMIN_LIST_PATH = "${admins}"; 139 - # The wheel checks PyPI for updates on every start otherwise. 140 - OMNIGENT_NO_UPDATE_CHECK = "1"; 141 - # Off by default upstream; be explicit, it phones home otherwise. 142 - OMNIGENT_TELEMETRY_ENABLED = "0"; 143 - }; 144 - serviceConfig = { 145 - ExecStart = lib.concatStringsSep " " [ 146 - (lib.getExe self.packages.${pkgs.system}.omnigent) 147 - "server" 148 - "--host 127.0.0.1" 149 - "--port ${toString port}" 150 - "--no-open" 151 - "--database-uri sqlite:///${stateDir}/chat.db" 152 - "--artifact-location ${stateDir}/artifacts" 153 - "--agent ${ompAgent}" 154 - ]; 155 - EnvironmentFile = [ config.clan.core.vars.generators.omnigent.files."env".path ]; 156 - Restart = "always"; 157 - RestartSec = 5; 158 - DynamicUser = true; 159 - StateDirectory = "omnigent"; 160 - WorkingDirectory = stateDir; 161 - ProtectSystem = "strict"; 162 - ProtectHome = true; 163 - PrivateTmp = true; 164 - NoNewPrivileges = true; 165 - ProtectKernelTunables = true; 166 - ProtectControlGroups = true; 167 - RestrictAddressFamilies = [ 168 - "AF_INET" 169 - "AF_INET6" 170 - "AF_UNIX" 171 - ]; 172 - }; 173 - }; 174 - 175 - services.nginx.virtualHosts.${domain} = { 176 - enableACME = true; 177 - forceSSL = true; 178 - locations."/" = { 179 - proxyPass = "http://127.0.0.1:${toString port}"; 180 - # The session event stream is SSE (omnigent sets X-Accel-Buffering: no, 181 - # which nginx honours) and the terminal/host/runner tunnels are 182 - # WebSockets that idle between turns, so the default 60s read timeout 183 - # would sever live sessions. 184 - proxyWebsockets = true; 185 - extraConfig = '' 186 - proxy_read_timeout 1d; 187 - proxy_send_timeout 1d; 188 - ''; 189 - }; 190 - }; 191 - }
+1 -1
configs/pipewire.nix
··· 35 35 # format: Invalid argument"): the Lua hook throws, its AsyncEventHook 36 36 # transition never completes, and the event dispatcher stalls for good -- 37 37 # after which no node ever activates again, the ALSA sinks sit in SUSPENDED, 38 - # and every player (including herdr's notification pings) blocks until 38 + # and every player blocks until 39 39 # wireplumber is restarted by hand. Fail the transition instead. 40 40 services.pipewire.wireplumber.package = pkgs.wireplumber.overrideAttrs (old: { 41 41 postPatch = (old.postPatch or "") + ''
-2
configs/dns/lassul.us.zone
··· 57 57 kanban IN CNAME neoprism 58 58 n2n IN CNAME neoprism 59 59 id IN CNAME neoprism 60 - covibe IN CNAME neoprism 61 - omni IN CNAME neoprism 62 60 where.is IN CNAME neoprism 63 61 parley IN CNAME neoprism 64 62 review IN CNAME neoprism
-1
keys/ssh/whip_massulus.pub
··· 1 - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICapQBTNsuKdFiE/q41GXde9lssD+hc+1m7JPpfokqBh whip@massulus
-1
machines/coaxmetal/config.nix
··· 18 18 ../../configs/review.nix 19 19 ../../configs/sigexec/executor.nix 20 20 ../../configs/rad.nix 21 - ../../configs/herdr.nix 22 21 ../../configs/orca/ide.nix 23 22 ../../configs/orca/server.nix 24 23 ../../configs/crit.nix
+4 -121
machines/coaxmetal/hermes.nix
··· 1 1 { 2 2 self, 3 3 config, 4 - lib, 5 4 pkgs, 6 5 ... 7 6 }: ··· 43 42 ''; 44 43 }; 45 44 46 - # herdr keeps its API socket under the session owner's 47 - # $HOME/.config/herdr/herdr.sock (verified on this host: the running server 48 - # has HOME=/home/lass and listens on /home/lass/.config/herdr/herdr.sock), 49 - # and the herd runs as lass — so the hermes user cannot see any pane without 50 - # crossing the user boundary. sudo's env_reset leaves HOME pointing at the 51 - # *calling* user, which made the CLI look for a socket under 52 - # /var/lib/hermes/.config and report "no herdr server is running", so set it 53 - # here. Keeping the env setup inside a store script also means sudoers 54 - # matches one fixed path instead of an argv template. 55 - herdr-as-lass = pkgs.writeShellApplication { 56 - name = "herdr-as-lass"; 57 - text = '' 58 - export HOME=${config.users.users.mainUser.home} 59 - exec ${self.inputs.llm-agents.packages.${pkgs.system}.herdr}/bin/herdr "$@" 60 - ''; 61 - }; 62 - 63 - # What the agent actually calls: `herdr agent list`, `herdr agent read <id>`, 64 - # `herdr agent prompt <id> '...'`. Named plainly because the gateway's PATH 65 - # is hermes + bash + coreutils + git + extraPackages — /run/current-system/sw 66 - # is absent, so there is no real `herdr` to shadow. 67 - herdr = pkgs.writeShellApplication { 68 - name = "herdr"; 69 - text = '' 70 - exec /run/wrappers/bin/sudo -u ${config.users.users.mainUser.name} \ 71 - ${herdr-as-lass}/bin/herdr-as-lass "$@" 72 - ''; 73 - }; 74 45 in 75 46 { 76 47 imports = [ ··· 114 85 # to ffprobe for the duration + waveform metadata those bubbles carry. 115 86 pkgs.ffmpeg 116 87 hermes-tts 117 - # Drive the herd from a voice note: "read me what the agent on herdr is 118 - # stuck on" -> herdr agent list / read / prompt, as lass. 119 - herdr 120 88 # CalDAV (calendar skill below) is plain HTTP; curl is not otherwise on 121 89 # the gateway PATH. 122 90 pkgs.curl ··· 202 170 # "" = auto-detect per clip, instead of upstream's "en" hint — commands 203 171 # here switch between English and German mid-conversation. 204 172 language = ""; 205 - # Vocabulary bias (faster-whisper initial_prompt). Measured on this host: 206 - # "the agent on herdr is blocked" transcribes as "the agent on Herter" 207 - # without it and verbatim with it. Every name here is one the agent has 208 - # to route on — a misheard host or tool name is a failed command, not a 209 - # typo. 210 - prompt = "Jargon: herdr, omp, nix, clan, retiolum, coaxmetal, neoprism, ignavia, lassulus."; 173 + # Vocabulary bias (faster-whisper initial_prompt). Every name here is one 174 + # the agent has to route on — a misheard host or tool name is a failed 175 + # command, not a typo. 176 + prompt = "Jargon: omp, nix, clan, retiolum, coaxmetal, neoprism, ignavia, lassulus."; 211 177 local = { 212 178 # small is the first size whose transcripts survive our jargon; a 4s 213 179 # clip takes ~3s warm on this CPU (int8), ~6s from cold weights. ··· 252 218 # EMAIL_PASSWORD, EMAIL_IMAP_HOST and EMAIL_SMTP_HOST are set 253 219 # (gateway/config_env.py); only an explicit `enabled: false` stops that. 254 220 settings.platforms.email.enabled = false; 255 - 256 - # Teach the agent the herd. Without this the `herdr` wrapper is just an 257 - # unadvertised binary on PATH: nothing in the model's context would make it 258 - # reach for the herd when a voice note says "what is the agent stuck on". 259 - # Skills are loaded from $HERMES_HOME/skills/<name>/SKILL.md. 260 - hermesHomeFiles."skills/herdr/SKILL.md" = '' 261 - --- 262 - name: herdr 263 - description: Inspect and steer the coding agents running in lass's herdr session on this machine. 264 - version: 1.0.0 265 - license: MIT 266 - platforms: [linux] 267 - metadata: 268 - hermes: 269 - tags: [herdr, agents, terminal, coding, inspect] 270 - --- 271 - 272 - # herdr 273 - 274 - `herdr` on this host is a wrapper that talks to lass's running herdr 275 - server (the agent multiplexer holding the coding-agent panes). Every 276 - invocation prints JSON. 277 - 278 - Use it whenever the user asks about "the agents", "the herd", "the 279 - session on herdr", what something is "stuck on", or wants a prompt or 280 - answer delivered to a running agent. 281 - 282 - | Command | Use | 283 - | --- | --- | 284 - | `herdr agent list` | Every agent with its state: working, blocked, idle, done. Start here. | 285 - | `herdr agent get <agent>` | One agent's detail (workspace, tab, pane, cwd). | 286 - | `herdr agent read <agent>` | Recent terminal output. This is how you answer "what is it doing/asking". | 287 - | `herdr agent prompt <agent> '<text>'` | Submit a prompt to that agent, e.g. to unblock an approval. | 288 - | `herdr agent wait <agent> --state idle` | Block until it reaches a state. | 289 - | `herdr workspace list` / `herdr tab list` / `herdr pane list` | Structure, when agent-level views are not enough. | 290 - | `herdr --skill` | herdr's own, fuller usage reference — read it before improvising flags. | 291 - 292 - Answering by voice: the reply is spoken, so summarize what `agent read` 293 - shows in a sentence or two instead of reciting scrollback, and say the 294 - agent's name and state first. Read a diff or a stack trace aloud only if 295 - asked. 296 - 297 - Blocked agents are the interesting case: report what the agent is waiting 298 - for, and only run `herdr agent prompt` when the user actually decides. 299 - Never close panes or kill agents on your own. 300 - ''; 301 221 302 222 # The one shared calendar (configs/radicale.nix): lass's own events, the 303 223 # book.lassul.us requests and bookings, and anything Hermes adds. Hermes ··· 473 393 ]; 474 394 }; 475 395 476 - # The herd/hermes user boundary. herdr keeps its API socket in the session 477 - # owner's runtime dir, so reaching lass's panes means running as lass. 478 - # 479 - # Be clear about what this grants: `herdr pane run` / `agent prompt` execute 480 - # commands inside lass's panes, so this is lass-level code execution for 481 - # anything that can drive the agent — it does not narrow the existing threat 482 - # model (the gateway already has terminal tools as hermes), but it widens the 483 - # blast radius from the hermes user to lass. The gate stays 484 - # MATRIX_ALLOWED_USERS above. Restricting the command to one store path only 485 - # buys auditability, not containment. 486 - security.sudo.extraRules = [ 487 - { 488 - users = [ config.services.hermes-agent.user ]; 489 - runAs = config.users.users.mainUser.name; 490 - commands = [ 491 - { 492 - command = "${herdr-as-lass}/bin/herdr-as-lass"; 493 - options = [ 494 - "NOPASSWD" 495 - "SETENV" 496 - ]; 497 - } 498 - ]; 499 - } 500 - ]; 501 - 502 - # sudo is setuid, and the upstream unit sets NoNewPrivileges=yes, which makes 503 - # every setuid exec fail — so the wrapper above returned "sudo: The "no new 504 - # privileges" flag is set, which prevents sudo from running as root" and the 505 - # agent reported the herd as unreachable. Turning it off for this unit is the 506 - # price of the bridge; it only re-enables what the sudoers rule already 507 - # allows (one store path, runas lass), since sudo itself is the only setuid 508 - # binary the agent is pointed at. 509 - # The module hardens the unit with NoNewPrivileges = true, hence mkForce. 510 - systemd.services.hermes-agent.serviceConfig.NoNewPrivileges = lib.mkForce false; 511 - 512 396 # ssh as hermes signs with the phone key (keys/ssh/phonetpm_massulus.pub, 513 397 # authorized on all hosts): every login asks for a fingerprint on the phone. 514 398 # The daemon only starts once paired: ··· 621 505 # Bearer token for the API server. Generated, not prompted, and kept in a 622 506 # separate file as well so it can be read back out for the phone: 623 507 # clan vars get coaxmetal hermes-api/api_key 624 - # (same shape as the covibe api_key generator). 625 508 clan.core.vars.generators.hermes-api = { 626 509 files."api_key" = { }; 627 510 runtimeInputs = [
-1
machines/ignavia/config.nix
··· 33 33 ../../configs/yggdrasil.nix 34 34 ../../configs/container-tests.nix 35 35 ../../configs/rad.nix 36 - ../../configs/herdr.nix 37 36 ../../configs/tablet-screen.nix 38 37 # ../../configs/br.nix 39 38 ];
-6
machines/neoprism/config.nix
··· 109 109 # pocket-id SSO IdP (id.lassul.us) 110 110 ../../configs/pocket-id.nix 111 111 112 - # omnigent agent meta-harness (omni.lassul.us) 113 - ../../configs/omnigent.nix 114 - 115 112 # radicle public seed (radicle.lassul.us) 116 113 ../../configs/radicle.lassul.us.nix 117 114 # radicle identity + node for this machine (/var/lib/rad) 118 115 ../../configs/rad.nix 119 - 120 - # herdr agent multiplexer 121 - ../../configs/herdr.nix 122 116 123 117 # self-hosted iroh relay (relay.lassul.us) 124 118 ../../configs/iroh-relay
-184
pkgs/omnigent/package.nix
··· 1 - { 2 - lib, 3 - fetchPypi, 4 - python313Packages, 5 - }: 6 - let 7 - py = python313Packages; 8 - 9 - # 0.18.1 added a runtime dependency on `websockets` (realtime transport) that 10 - # nixpkgs' derivation does not declare yet, so its wheel fails the 11 - # runtime-deps check. Add it until nixpkgs catches up. 12 - openai-agents = py.openai-agents.overridePythonAttrs (o: { 13 - dependencies = o.dependencies ++ [ py.websockets ]; 14 - }); 15 - 16 - # omnigent-client declares `omnigent` as a dependency, and omnigent declares 17 - # omnigent-client: the two are released in lockstep from the same repo. Break 18 - # the cycle by dropping the back-reference here; the final application below 19 - # provides `omnigent` itself. 20 - omnigent-client = py.buildPythonPackage rec { 21 - pname = "omnigent_client"; 22 - version = "0.17.0"; 23 - format = "wheel"; 24 - 25 - src = fetchPypi { 26 - inherit pname version format; 27 - dist = "py3"; 28 - python = "py3"; 29 - hash = "sha256-840EK8+ZfBOPP0lp5h974rkKtIs4eLC8uLaK23y8FhY="; 30 - }; 31 - 32 - pythonRemoveDeps = [ "omnigent" ]; 33 - 34 - dependencies = [ 35 - py.httpx 36 - py.pydantic 37 - ]; 38 - 39 - # No pythonImportsCheck: every module imports `omnigent`, which only exists 40 - # in the application closure below. 41 - doCheck = false; 42 - 43 - meta = { 44 - description = "Python client library for the Omnigent server API"; 45 - homepage = "https://omnigent.ai"; 46 - license = lib.licenses.asl20; 47 - }; 48 - }; 49 - 50 - omnigent-ui-sdk = py.buildPythonPackage rec { 51 - pname = "omnigent_ui_sdk"; 52 - version = "0.17.0"; 53 - format = "wheel"; 54 - 55 - src = fetchPypi { 56 - inherit pname version format; 57 - dist = "py3"; 58 - python = "py3"; 59 - hash = "sha256-2I9JWM64pqFNawr6exvQRJ1uOvZ+6J6NZ37JBy2OLFA="; 60 - }; 61 - 62 - dependencies = [ 63 - omnigent-client 64 - py.prompt-toolkit 65 - py.pyyaml 66 - py.rich 67 - ]; 68 - 69 - doCheck = false; 70 - 71 - meta = { 72 - description = "Terminal UI SDK for Omnigent"; 73 - homepage = "https://omnigent.ai"; 74 - license = lib.licenses.asl20; 75 - }; 76 - }; 77 - in 78 - py.buildPythonApplication rec { 79 - pname = "omnigent"; 80 - version = "0.17.0"; 81 - format = "wheel"; 82 - 83 - # The published wheel ships the prebuilt web SPA under 84 - # omnigent/server/static/web-ui; the sdist does not. 85 - src = fetchPypi { 86 - inherit pname version format; 87 - dist = "py3"; 88 - python = "py3"; 89 - hash = "sha256-/ygrFeQOlXXilVU5H1zqdYBqR3YVZUo/HlfIfsxR/IQ="; 90 - }; 91 - 92 - # Upper bounds that nixpkgs has already moved past. All of these are 93 - # conservative caps in omnigent's pyproject, not known incompatibilities. 94 - pythonRelaxDeps = [ 95 - "argon2-cffi" # nixpkgs 25.1.0 vs <24 96 - "cachetools" # nixpkgs 7.1.4 vs <7 97 - "openai" # nixpkgs 2.53.0 vs <2.45 98 - "packaging" # nixpkgs 26.2 vs <26 99 - "protobuf" # nixpkgs 7.35.1 vs <7 100 - "rich" # nixpkgs 15.0.0 vs <15 101 - "websockets" # nixpkgs 16.1 vs <15 102 - ]; 103 - 104 - dependencies = [ 105 - omnigent-client 106 - omnigent-ui-sdk 107 - ] 108 - ++ (with py; [ 109 - alembic 110 - anyio 111 - argon2-cffi 112 - cachetools 113 - cel-python 114 - certifi 115 - claude-agent-sdk 116 - click 117 - fastapi 118 - filelock 119 - ftfy 120 - httpx 121 - json5 122 - keyring 123 - mcp 124 - openai 125 - openai-agents 126 - opentelemetry-api 127 - opentelemetry-exporter-otlp-proto-grpc 128 - opentelemetry-exporter-otlp-proto-http 129 - opentelemetry-instrumentation-fastapi 130 - opentelemetry-instrumentation-httpx 131 - opentelemetry-instrumentation-sqlalchemy 132 - packaging 133 - pexpect 134 - pillow 135 - prompt-toolkit 136 - protobuf 137 - psutil 138 - pydantic 139 - pyjwt 140 - pyte 141 - python-dateutil 142 - pyyaml 143 - rich 144 - sqlalchemy 145 - starlette 146 - tiktoken 147 - tomlkit 148 - uvicorn 149 - websockets 150 - zstandard 151 - ]) 152 - ++ py.pyjwt.optional-dependencies.crypto 153 - ++ py.uvicorn.optional-dependencies.standard; 154 - 155 - # The CLI respawns itself as `sys.executable -m omnigent.host._daemon_entry` 156 - # (cli.py:2945) for the host daemon, and the daemon spawns runners the same 157 - # way. That is the *bare* interpreter, without the site-dirs that 158 - # wrapPythonPrograms bakes into the console script, so those children die 159 - # with ModuleNotFoundError: No module named 'omnigent' and every session hangs 160 - # on "the connect daemon did not come online within 30s". Hand them the 161 - # transitive dependency closure through PYTHONPATH, which omnigent's own 162 - # daemon/runner env allowlist preserves (host/connect.py) while the agent 163 - # spawn allowlist drops it (inner/agent_env.py), so it never reaches omp. 164 - postFixup = '' 165 - buildPythonPath "$out $pythonPath" 166 - wrapProgram $out/bin/omnigent --prefix PYTHONPATH : "$program_PYTHONPATH" 167 - ''; 168 - 169 - pythonImportsCheck = [ 170 - "omnigent" 171 - "omnigent_client" 172 - "omnigent_ui_sdk" 173 - ]; 174 - 175 - passthru.updateScript = ./update.sh; 176 - 177 - meta = { 178 - description = "Open-source meta-harness giving a common orchestration layer over AI coding agents"; 179 - homepage = "https://omnigent.ai"; 180 - license = lib.licenses.asl20; 181 - mainProgram = "omnigent"; 182 - platforms = lib.platforms.unix; 183 - }; 184 - }
-58
pkgs/omnigent/update.sh
··· 1 - #!/usr/bin/env bash 2 - set -euo pipefail 3 - 4 - # omnigent, omnigent-client and omnigent-ui-sdk are released in lockstep and 5 - # all three are fetched as py3 wheels in package.nix; nix-update only bumps 6 - # the top-level src, so bump all of them here. 7 - attr=omnigent 8 - 9 - # Run by update-pkgs from the flake root ($0 is a read-only store copy). 10 - pkg=pkgs/omnigent/package.nix 11 - system=$(nix eval --raw --impure --expr 'builtins.currentSystem') 12 - 13 - new_version=$(curl -fsSL https://pypi.org/pypi/omnigent/json | jq -r .info.version) 14 - old_version=$(sed -n 's|^ version = "\([^"]*\)";|\1|p' "$pkg") 15 - if [ "$old_version" = "$new_version" ]; then 16 - echo "$attr: already up to date ($new_version)" 17 - exit 0 18 - fi 19 - 20 - # Hash of the py3-none-any wheel as published on PyPI, in SRI form. 21 - wheel_hash() { 22 - local project=$1 hex 23 - hex=$(curl -fsSL "https://pypi.org/pypi/$project/$new_version/json" | 24 - jq -r '.urls[] | select(.packagetype == "bdist_wheel" and (.filename | endswith("-py3-none-any.whl"))) | .digests.sha256') 25 - if [ -z "$hex" ]; then 26 - echo "$attr: no py3-none-any wheel for $project $new_version" >&2 27 - exit 1 28 - fi 29 - nix hash convert --hash-algo sha256 --to sri "$hex" 30 - } 31 - 32 - # Replace the `hash = ...` that follows `pname = "<pname>"` (blanking first so 33 - # nothing ever builds against a stale cached FOD). 34 - set_hash() { 35 - local pname=$1 hash=$2 36 - sed -i "/pname = \"$pname\";/,/hash = /s|^\(\s*\)hash = \"[^\"]*\"|\1hash = \"$hash\"|" "$pkg" 37 - } 38 - 39 - client_hash=$(wheel_hash omnigent-client) 40 - ui_sdk_hash=$(wheel_hash omnigent-ui-sdk) 41 - app_hash=$(wheel_hash omnigent) 42 - 43 - sed -i \ 44 - -e "s|^\(\s*\)version = \"$old_version\";|\1version = \"$new_version\";|" \ 45 - -e "s|^\(\s*\)hash = \".*\"|\1hash = \"\"|" \ 46 - "$pkg" 47 - 48 - set_hash omnigent_client "$client_hash" 49 - set_hash omnigent_ui_sdk "$ui_sdk_hash" 50 - set_hash omnigent "$app_hash" 51 - 52 - if grep -q 'hash = ""' "$pkg"; then 53 - echo "$attr: failed to fill in every hash" >&2 54 - exit 1 55 - fi 56 - 57 - echo "$attr: updated $old_version → $new_version" 58 - nix build --no-link ".#packages.$system.$attr"
-119
tools/covibe/flake-module.nix
··· 1 - { inputs, ... }: 2 - { 3 - perSystem = 4 - { 5 - pkgs, 6 - self', 7 - system, 8 - ... 9 - }: 10 - let 11 - server = "https://omni.lassul.us"; 12 - omp = inputs.llm-agents.packages.${system}.omp; 13 - 14 - # omnigent's `pi-native` harness runs the agent's own TUI in a tmux pane, 15 - # attaches your TTY to it, and mirrors the session (transcript, tool 16 - # calls, terminal, files) to the server — the shape covibe always had, 17 - # now with omnigent's per-session ACLs behind it. It resolves the CLI by 18 - # the literal name `pi` (onboarding/harness_install.py: the readiness gate 19 - # is `resolve_cli_binary("pi")`, which ignores OMNIGENT_PI_PATH and the 20 - # config override), so omp has to reach it under that name. Version-gated 21 - # flags are satisfied by omp reporting 17.x >= pi's declared 0.79.0 floor. 22 - piShim = pkgs.writeShellApplication { 23 - name = "pi"; 24 - text = '' 25 - OMP_BIN=${omp}/bin/omp 26 - '' 27 - + builtins.readFile ./pi-shim.sh; 28 - }; 29 - in 30 - { 31 - # Launch omp as a session on the omni.lassul.us dashboard: omp's own TUI 32 - # runs here, in a tmux pane owned by the local runner, while the server 33 - # holds the transcript, the diff view and the per-session ACLs — so a 34 - # session can be shared read-only or read-write with another pocket-id 35 - # account, and a teammate's message from the browser executes in this 36 - # TUI. 37 - # 38 - # Auth is the pocket-id login, but *only a browser* has to see pocket-id — 39 - # the machine running the agent only needs to reach the server. omnigent's 40 - # cli-ticket flow (POST /auth/cli-login, then poll /auth/cli-poll for five 41 - # minutes) prints a URL that any device with your passkey can complete, so 42 - # this works over ssh on a box with no display: run `covibe login`, open 43 - # the printed link on a laptop or phone, done. The session JWT is then 44 - # cached in ~/.omnigent/auth_tokens.json for the server's configured TTL. 45 - # Other arguments go through to omp, e.g. `covibe --continue`; 46 - # `covibe --resume` (no value) opens omnigent's session picker. 47 - # 48 - # Note the TUI path runs on a spec omnigent materialises itself 49 - # (`pi-native-ui`), so the only terminal it declares is omp's own pane; 50 - # extra shells from the dashboard are a property of the *browser-started* 51 - # omp agent, whose spec lives in configs/omnigent.nix. 52 - packages.covibe = 53 - (pkgs.writeShellApplication { 54 - name = "covibe"; 55 - runtimeInputs = [ 56 - self'.packages.omnigent 57 - piShim 58 - # The host daemon we spawn inherits this PATH, and sessions started 59 - # from the *browser* use the server's catalog entry, whose command is 60 - # a bare `omp acp` resolved here — without omp on PATH those turns 61 - # die with "inner executor error: [Errno 2] No such file or 62 - # directory". The `pi` shim alone is not enough: it only covers the 63 - # pi-native TUI path. 64 - omp 65 - pkgs.tmux 66 - pkgs.jq 67 - pkgs.git 68 - ]; 69 - text = '' 70 - server=${server} 71 - tokens="$HOME/.omnigent/auth_tokens.json" 72 - 73 - # `webbrowser.open` walks a candidate list that includes terminal 74 - # browsers; on a headless box one of those can seize the tty we are 75 - # about to hand to omp's TUI. With no display, make the "open" a 76 - # no-op — the URL is printed either way. 77 - if [ -z "''${DISPLAY:-}" ] && [ -z "''${WAYLAND_DISPLAY:-}" ]; then 78 - export BROWSER=''${BROWSER:-echo} 79 - fi 80 - 81 - login() { 82 - echo "covibe: authenticating against $server" >&2 83 - echo "covibe: open the link below on any device that has your pocket-id passkey" >&2 84 - omnigent login "$server" 85 - } 86 - 87 - case "''${1:-}" in 88 - login) 89 - login 90 - exit 0 91 - ;; 92 - host) 93 - # Register this machine so sessions can also be STARTED from the 94 - # browser (New Chat → this host → the `omp` agent). That path runs 95 - # `omp acp` from the runner's PATH, which only has omp when the 96 - # daemon was spawned with it — as it is here. Runs in the 97 - # foreground; ^C stops hosting. 98 - shift 99 - exec omnigent host "$server" "$@" 100 - ;; 101 - esac 102 - 103 - # The cached session is a JWT with a server-side TTL 104 - # (OMNIGENT_OIDC_SESSION_TTL_HOURS), so "a token exists" is not the 105 - # same as "we are logged in": a stale entry makes omnigent fail with 106 - # `Pi session creation failed (401): Authentication required` 107 - # instead of re-authenticating. Check the recorded expiry (with a 108 - # minute of slack, via jq's own clock so this needs no coreutils). 109 - if ! jq -e --arg s "$server" \ 110 - '((.[$s].expires_at // 0) > (now + 60)) and ((.[$s].token // "") != "")' \ 111 - "$tokens" >/dev/null 2>&1; then 112 - login 113 - fi 114 - exec omnigent pi --server "$server" "$@" 115 - ''; 116 - }).overrideAttrs 117 - { passthru.usage = builtins.readFile ./usage.kdl; }; 118 - }; 119 - }
-51
tools/covibe/pi-shim.sh
··· 1 - # shellcheck shell=bash 2 - # Argument translation between omnigent's `pi-native` harness and omp. 3 - # 4 - # omnigent builds `--extension <js> [--approve] --session-dir <dir> 5 - # [--session <id>] <user args>` (omnigent/runner/native/orchestration.py 6 - # _build_pi_native_args) against the upstream `pi` CLI at 0.79.x. omp 17.x 7 - # differs in three ways: 8 - # 9 - # 1. `--approve` (pi's project-trust override) does not exist; omp exits 10 - # "unknown flag: --approve". Drop it — a human is attached to the TUI and 11 - # omp's own approval prompts are what we want. 12 - # 2. `--session <id>` is spelled `--resume <id>` in omp. 13 - # 3. omnigent's bridge extension is CommonJS (`module.exports = fn`), while 14 - # omp dynamic-imports the file and accepts only `module` / 15 - # `module.default` as the factory — a CJS file fails with "Extension does 16 - # not export a valid factory function". Point omp at a generated ESM 17 - # adapter that re-exports the CJS factory as a default export instead. 18 - # Verified: the status line then reads "· linked", prompts typed in the 19 - # TUI appear in the dashboard transcript, and messages sent from the 20 - # dashboard execute in the TUI. 21 - argv=() 22 - while [ "$#" -gt 0 ]; do 23 - case "$1" in 24 - --approve) 25 - shift 26 - ;; 27 - --session) 28 - argv+=(--resume "$2") 29 - shift 2 30 - ;; 31 - --extension) 32 - ext=$2 33 - shift 2 34 - adapter="$(dirname "$ext")/omnigent-esm-adapter.mjs" 35 - if [ ! -e "$adapter" ]; then 36 - cat >"$adapter" <<EOF 37 - import { createRequire } from "node:module"; 38 - const require = createRequire(import.meta.url); 39 - export default require("$ext"); 40 - EOF 41 - fi 42 - argv+=(--extension "$adapter") 43 - ;; 44 - *) 45 - argv+=("$1") 46 - shift 47 - ;; 48 - esac 49 - done 50 - 51 - exec "$OMP_BIN" "${argv[@]}"
-9
tools/covibe/usage.kdl
··· 1 - bin "covibe" 2 - about "Run omp's TUI as a shared session on the omni.lassul.us omnigent dashboard (transcript, diff view and read-only/read-write sharing live on the server)" 3 - 4 - cmd "login" help="Authenticate this machine against omni.lassul.us; prints a link to open on any device with your pocket-id passkey" 5 - cmd "host" help="Keep this machine registered as a host so sessions can also be started from the browser" 6 - 7 - flag "-c --continue" help="Continue the previous omp session in this directory" 8 - flag "-r --resume" help="Resume an omnigent session (no value opens the picker)" 9 - arg "[omp-args...]" help="Arguments passed through to omp"
-1
tools/hermes-voice/config.json
··· 6 6 { "term": "NixOS", "spoken": "nix O S" }, 7 7 "nix", 8 8 "clan", 9 - { "term": "herdr", "spoken": "herder" }, 10 9 "omp", 11 10 "retiolum", 12 11 "coaxmetal",
+1 -1
tools/hermes-voice/hermes_voice.py
··· 103 103 104 104 Delegation policy: 105 105 Backend tools: 106 - - Hermes agent: a full AI agent with tools. It can run commands on lass's machines, read and edit files, manage the calendar, inspect the coding agents in herdr, browse the web, search, and remember things across sessions. It is the one who actually does work and knows facts. 106 + - Hermes agent: a full AI agent with tools. It can run commands on lass's machines, read and edit files, manage the calendar, browse the web, search, and remember things across sessions. It is the one who actually does work and knows facts. 107 107 108 108 Delegate to the backend when: 109 109 - The user asks a question that needs facts, current information, or careful reasoning.
+1 -1
tools/zsh/flake-module.nix
··· 335 335 336 336 # Workspace manager integration: cd to workspace directory on new terminal. 337 337 # Only for shells that start in $HOME: a pane spawned with an explicit 338 - # directory (herdr, tmux split in cwd) must keep it. 338 + # directory (tmux split in cwd) must keep it. 339 339 if [[ "$PWD" == "$HOME" ]]; then 340 340 _ws_dir=$(${self.packages.${pkgs.system}.workspace-manager}/bin/workspace-manager dir 2>/dev/null) 341 341 [[ -n "$_ws_dir" && -d "$_ws_dir" ]] && cd "$_ws_dir"