This repository has no description
0

Configure Feed

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

superconfig / 2configs / omnigent.nix
7.1 kB 182 lines
1let 2 domain = "omni.lassul.us"; 3 port = 8771; 4 stateDir = "/var/lib/omnigent"; 5in 6{ 7 config, 8 lib, 9 pkgs, 10 self, 11 ... 12}: 13let 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 50 # One identity per line. Admins bypass every per-session ACL, which is what 51 # makes sharing manageable at all: a session's creator gets LEVEL_OWNER, and 52 # only owners/managers can grant others read (watch + diffs) or edit (steer). 53 admins = pkgs.writeText "omnigent-admins" '' 54 lass@lassul.us 55 ''; 56in 57{ 58 # omnigent: self-hosted agent meta-harness. The server owns sessions, 59 # transcripts, per-session ACLs (read / edit / manage / owner), the per-file 60 # diff view and the browser UI; the agents themselves run on whichever 61 # machine registers as a *host*, so no coding agent runs on neoprism unless 62 # someone points a host at it. `s covibe` in a project directory is the 63 # client: it runs omp locally and registers the session here. 64 # 65 # Browser auth is the local pocket-id IdP. pocket-id has no declarative 66 # client API, so register the client by hand at https://id.lassul.us: 67 # name: omnigent 68 # callback URL: https://omni.lassul.us/auth/callback 69 # logout URL: https://omni.lassul.us/ 70 # then paste its id + secret into the clan prompt below. 71 72 # Generated cookie-signing secret plus the prompted pocket-id client 73 # id/secret, materialised into one EnvironmentFile. 74 clan.core.vars.generators.omnigent = { 75 files."env" = { }; 76 prompts.oidc_client = { 77 description = '' 78 Register an OIDC client in pocket-id (https://id.lassul.us): 79 name: omnigent 80 callback URL: https://omni.lassul.us/auth/callback 81 Generate a client secret, then paste both lines: 82 OMNIGENT_OIDC_CLIENT_ID=... 83 OMNIGENT_OIDC_CLIENT_SECRET=... 84 ''; 85 type = "multiline"; 86 persist = true; 87 }; 88 runtimeInputs = [ pkgs.openssl ]; 89 script = '' 90 { 91 printf 'OMNIGENT_OIDC_COOKIE_SECRET=%s\n' "$(openssl rand -hex 32)" 92 cat "$prompts"/oidc_client 93 } > "$out/env" 94 ''; 95 }; 96 97 systemd.services.omnigent = { 98 description = "omnigent agent meta-harness (${domain})"; 99 wantedBy = [ "multi-user.target" ]; 100 after = [ 101 "network-online.target" 102 "nginx.service" 103 ]; 104 wants = [ "network-online.target" ]; 105 environment = { 106 HOME = stateDir; 107 OMNIGENT_AUTH_ENABLED = "1"; 108 OMNIGENT_AUTH_PROVIDER = "oidc"; 109 OMNIGENT_OIDC_ISSUER = "https://id.lassul.us"; 110 OMNIGENT_OIDC_REDIRECT_URI = "https://${domain}/auth/callback"; 111 OMNIGENT_OIDC_LOGOUT_REDIRECT_URI = "https://${domain}/"; 112 OMNIGENT_OIDC_ALLOWED_DOMAINS = "lassul.us"; 113 # pocket-id emits `email` *and* `email_verified` in the id_token when the 114 # `email` scope is requested (which omnigent does), but the value of 115 # `email_verified` is the users.email_verified column, whose default comes 116 # from the app-config key `emailsVerified` — shipped as false. omnigent 117 # otherwise hard-rejects the login with "Could not determine user email 118 # from IdP". The gate exists for IdPs where a user can self-assert an 119 # arbitrary address; here users are provisioned by hand in pocket-id 120 # (signups are disabled) and the domain allowlist above still applies, so 121 # the marker carries no information for us. 122 OMNIGENT_OIDC_SKIP_EMAIL_VERIFICATION = "1"; 123 # Default is 8 hours, which expires the `s covibe` session JWT roughly 124 # once a working day and forces a browser round-trip through pocket-id. 125 # A week is the pragmatic setting for a single-operator host behind an 126 # invite-less IdP; the tokens live 0600 in ~/.omnigent/auth_tokens.json, 127 # so shorten this if a laptop ever leaves the house untrusted. 128 OMNIGENT_OIDC_SESSION_TTL_HOURS = "168"; 129 OMNIGENT_ADMIN_LIST_PATH = "${admins}"; 130 # The wheel checks PyPI for updates on every start otherwise. 131 OMNIGENT_NO_UPDATE_CHECK = "1"; 132 # Off by default upstream; be explicit, it phones home otherwise. 133 OMNIGENT_TELEMETRY_ENABLED = "0"; 134 }; 135 serviceConfig = { 136 ExecStart = lib.concatStringsSep " " [ 137 (lib.getExe self.packages.${pkgs.system}.omnigent) 138 "server" 139 "--host 127.0.0.1" 140 "--port ${toString port}" 141 "--no-open" 142 "--database-uri sqlite:///${stateDir}/chat.db" 143 "--artifact-location ${stateDir}/artifacts" 144 "--agent ${ompAgent}" 145 ]; 146 EnvironmentFile = [ config.clan.core.vars.generators.omnigent.files."env".path ]; 147 Restart = "always"; 148 RestartSec = 5; 149 DynamicUser = true; 150 StateDirectory = "omnigent"; 151 WorkingDirectory = stateDir; 152 ProtectSystem = "strict"; 153 ProtectHome = true; 154 PrivateTmp = true; 155 NoNewPrivileges = true; 156 ProtectKernelTunables = true; 157 ProtectControlGroups = true; 158 RestrictAddressFamilies = [ 159 "AF_INET" 160 "AF_INET6" 161 "AF_UNIX" 162 ]; 163 }; 164 }; 165 166 services.nginx.virtualHosts.${domain} = { 167 enableACME = true; 168 forceSSL = true; 169 locations."/" = { 170 proxyPass = "http://127.0.0.1:${toString port}"; 171 # The session event stream is SSE (omnigent sets X-Accel-Buffering: no, 172 # which nginx honours) and the terminal/host/runner tunnels are 173 # WebSockets that idle between turns, so the default 60s read timeout 174 # would sever live sessions. 175 proxyWebsockets = true; 176 extraConfig = '' 177 proxy_read_timeout 1d; 178 proxy_send_timeout 1d; 179 ''; 180 }; 181 }; 182}