diff --git a/flake.lock b/flake.lock index 8aae8f3..58a3778 100644 --- a/flake.lock +++ b/flake.lock @@ -60,16 +60,16 @@ ] }, "locked": { - "lastModified": 1775004246, - "narHash": "sha256-P6Md0WzHK2/oAZ6VbpYnabVJyVcqwuYizoOqbxaf+lU=", + "lastModified": 1774996501, + "narHash": "sha256-1sEkQDdV/qU4/N9oHR4mptllcRWt503k6HZ8Yp4EooE=", "owner": "anomalyco", "repo": "opencode", - "rev": "6314f09c14fdd6a3ab8bedc4f7b7182647551d12", + "rev": "892bdebaacbed3fc76976431c7aa7b81ab639fb6", "type": "github" }, "original": { "owner": "anomalyco", - "ref": "v1.3.13", + "ref": "v1.3.12", "repo": "opencode", "type": "github" } diff --git a/flake.nix b/flake.nix index b87ff5b..4322819 100644 --- a/flake.nix +++ b/flake.nix @@ -12,7 +12,7 @@ # opencode needs newer bun from master opencode = { - url = "github:anomalyco/opencode/v1.3.13"; + url = "github:anomalyco/opencode/v1.3.12"; inputs.nixpkgs.follows = "nixpkgs-master"; }; diff --git a/modules/nixos/honcho.nix b/modules/nixos/honcho.nix new file mode 100644 index 0000000..1e4e775 --- /dev/null +++ b/modules/nixos/honcho.nix @@ -0,0 +1,374 @@ +# NixOS Module for Honcho AI Memory Server +# +# This module provides systemd services for the Honcho API server and +# background deriver process, with configurable PostgreSQL and Redis backends. +# +# Usage in your NixOS configuration: +# +# imports = [ inputs.m3ta-nixpkgs.nixosModules.default ]; +# +# m3ta.honcho = { +# enable = true; +# port = 8000; +# host = "127.0.0.1"; +# +# # Database (PostgreSQL with pgvector) +# database = { +# connectionUri = "postgresql+psycopg://honcho:password@localhost:5432/honcho"; +# }; +# +# # Redis cache +# cache = { +# url = "redis://localhost:6379/0"; +# }; +# +# # LLM API keys (use agenix or sops-nix for secrets) +# environmentFile = "/run/secrets/honcho-env"; +# }; +# +# Using with m3ta.ports (recommended): +# +# m3ta.ports = { +# enable = true; +# definitions = { honcho = 8000; }; +# currentHost = config.networking.hostName; +# }; +# +# m3ta.honcho = { +# enable = true; +# port = config.m3ta.ports.get "honcho"; +# }; +{ + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.m3ta.honcho; + + # Python environment with honcho and FastAPI CLI + pythonEnv = pkgs.python3.withPackages (ps: + with ps; [ + cfg.package + ]); + + # Start script for the API server + startScript = pkgs.writeShellScript "honcho-start" '' + set -e + + # Load environment file if specified + ${optionalString (cfg.environmentFile != null) '' + if [ -f "${cfg.environmentFile}" ]; then + set -a + source "${cfg.environmentFile}" + set +a + fi + ''} + + # Create state directory + mkdir -p ${cfg.stateDir} + + # Run database migrations + ${pythonEnv}/bin/python -c " +import sys; sys.path.insert(0, '${cfg.package}/${pythonEnv.sitePackages}') +from scripts.provision_db import * +" 2>/dev/null || echo "Skipping database provisioning (script may not be available)" + + # Run the API server + exec ${pythonEnv}/bin/fastapi run \ + --host ${cfg.host} \ + --port ${toString cfg.port} \ + ${cfg.package}/${pythonEnv.sitePackages}/src/main.py + ''; + + # Start script for the deriver background worker + deriverScript = pkgs.writeShellScript "honcho-deriver" '' + set -e + + # Load environment file if specified + ${optionalString (cfg.environmentFile != null) '' + if [ -f "${cfg.environmentFile}" ]; then + set -a + source "${cfg.environmentFile}" + set +a + fi + ''} + + # Create state directory + mkdir -p ${cfg.stateDir} + + # Run the deriver + exec ${pythonEnv}/bin/python -m src.deriver + ''; +in { + options.m3ta.honcho = { + enable = mkEnableOption "Honcho AI memory server"; + + package = mkOption { + type = types.package; + default = pkgs.honcho; + defaultText = literalExpression "pkgs.honcho"; + description = "The honcho package to use."; + }; + + host = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "Host address to bind the API server to."; + }; + + port = mkOption { + type = types.port; + default = 8000; + description = "Port to run the API server on."; + }; + + stateDir = mkOption { + type = types.path; + default = "/var/lib/honcho"; + description = "Directory to store honcho data and state."; + }; + + user = mkOption { + type = types.str; + default = "honcho"; + description = "User account under which honcho runs."; + }; + + group = mkOption { + type = types.str; + default = "honcho"; + description = "Group under which honcho runs."; + }; + + environmentFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Environment file containing configuration and secrets. + This file should contain KEY=value pairs, one per line. + Use this for API keys, database credentials, and other secrets. + See the honcho .env.template for available variables. + ''; + example = "/run/secrets/honcho-env"; + }; + + # Database Configuration + database = { + connectionUri = mkOption { + type = types.str; + default = "postgresql+psycopg://honcho:honcho@localhost:5432/honcho"; + description = '' + PostgreSQL connection URI with pgvector support. + Must use postgresql+psycopg prefix for SQLAlchemy compatibility. + ''; + example = "postgresql+psycopg://honcho:password@localhost:5432/honcho"; + }; + + schema = mkOption { + type = types.str; + default = "public"; + description = "Database schema to use."; + }; + + poolSize = mkOption { + type = types.int; + default = 10; + description = "Connection pool size."; + }; + + maxOverflow = mkOption { + type = types.int; + default = 20; + description = "Maximum connection pool overflow."; + }; + }; + + # Cache Configuration + cache = { + url = mkOption { + type = types.str; + default = "redis://localhost:6379/0"; + description = '' + Redis cache URL. + Set suppress=true to suppress connection errors: redis://localhost:6379/0?suppress=true + ''; + example = "redis://localhost:6379/0?suppress=true"; + }; + }; + + # Deriver (background worker) Configuration + deriver = { + enable = mkOption { + type = types.bool; + default = true; + description = '' + Enable the honcho deriver background worker. + Processes asynchronous tasks like representation updates and session summarization. + ''; + }; + + workers = mkOption { + type = types.int; + default = 1; + description = "Number of deriver worker processes."; + }; + + provider = mkOption { + type = types.str; + default = "google"; + description = "LLM provider for the deriver."; + }; + + model = mkOption { + type = types.str; + default = "gemini-2.5-flash-lite"; + description = "LLM model for the deriver."; + }; + }; + + # Logging + logLevel = mkOption { + type = types.enum ["CRITICAL" "ERROR" "WARNING" "INFO" "DEBUG"]; + default = "INFO"; + description = "Logging level for the server."; + }; + + # Authentication + auth = { + enable = mkOption { + type = types.bool; + default = false; + description = "Enable JWT authentication."; + }; + + jwtSecretFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Path to file containing the JWT secret key. + Required when auth is enabled. + ''; + example = "/run/secrets/honcho-jwt-secret"; + }; + }; + }; + + config = mkIf cfg.enable { + # Create user and group + users.users.${cfg.user} = { + isSystemUser = true; + group = cfg.group; + description = "Honcho service user"; + home = cfg.stateDir; + createHome = true; + }; + + users.groups.${cfg.group} = {}; + + # API Server systemd service + systemd.services.honcho = { + description = "Honcho AI Memory API Server"; + after = ["network.target"]; + wantedBy = ["multi-user.target"]; + + serviceConfig = { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + ExecStart = startScript; + Restart = "on-failure"; + RestartSec = "5s"; + + # Security hardening + NoNewPrivileges = true; + PrivateTmp = true; + ProtectSystem = "strict"; + ProtectHome = true; + ReadWritePaths = [cfg.stateDir]; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + RestrictRealtime = true; + RestrictNamespaces = true; + LockPersonality = true; + MemoryDenyWriteExecute = false; # Python needs this + RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"]; + }; + + environment = + { + PYTHONUNBUFFERED = "1"; + DB_CONNECTION_URI = cfg.database.connectionUri; + DB_SCHEMA = cfg.database.schema; + DB_POOL_SIZE = toString cfg.database.poolSize; + DB_MAX_OVERFLOW = toString cfg.database.maxOverflow; + CACHE_URL = cfg.cache.url; + LOG_LEVEL = cfg.logLevel; + AUTH_USE_AUTH = + if cfg.auth.enable + then "true" + else "false"; + DERIVER_ENABLED = + if cfg.deriver.enable + then "true" + else "false"; + } + // optionalAttrs (cfg.auth.jwtSecretFile != null) { + AUTH_JWT_SECRET_FILE = cfg.auth.jwtSecretFile; + }; + }; + + # Deriver background worker systemd service + systemd.services.honcho-deriver = mkIf cfg.deriver.enable { + description = "Honcho Deriver Background Worker"; + after = ["network.target"]; + wantedBy = ["multi-user.target"]; + + serviceConfig = { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + ExecStart = deriverScript; + Restart = "on-failure"; + RestartSec = "5s"; + + # Security hardening + NoNewPrivileges = true; + PrivateTmp = true; + ProtectSystem = "strict"; + ProtectHome = true; + ReadWritePaths = [cfg.stateDir]; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + RestrictRealtime = true; + RestrictNamespaces = true; + LockPersonality = true; + MemoryDenyWriteExecute = false; # Python needs this + RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"]; + }; + + environment = + { + PYTHONUNBUFFERED = "1"; + DB_CONNECTION_URI = cfg.database.connectionUri; + DB_SCHEMA = cfg.database.schema; + CACHE_URL = cfg.cache.url; + LOG_LEVEL = cfg.logLevel; + DERIVER_ENABLED = "true"; + DERIVER_WORKERS = toString cfg.deriver.workers; + DERIVER_PROVIDER = cfg.deriver.provider; + DERIVER_MODEL = cfg.deriver.model; + METRICS_ENABLED = "true"; + } + // optionalAttrs (cfg.auth.jwtSecretFile != null) { + AUTH_JWT_SECRET_FILE = cfg.auth.jwtSecretFile; + }; + }; + + # Open firewall port if binding to non-localhost + networking.firewall.allowedTCPPorts = mkIf (cfg.host != "127.0.0.1" && cfg.host != "localhost") [cfg.port]; + }; +} diff --git a/pkgs/default.nix b/pkgs/default.nix index 2445c67..fdf4ec3 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -10,6 +10,7 @@ in { sidecar = pkgs.callPackage ./sidecar {}; td = pkgs.callPackage ./td {}; code2prompt = pkgs.callPackage ./code2prompt {}; + eigent = pkgs.callPackage ./eigent {}; hyprpaper-random = pkgs.callPackage ./hyprpaper-random {}; launch-webapp = pkgs.callPackage ./launch-webapp {}; mem0 = pkgs.callPackage ./mem0 {}; @@ -23,6 +24,7 @@ in { openshell = pkgs.callPackage ./openshell {}; zellij-ps = pkgs.callPackage ./zellij-ps {}; vibetyper = pkgs.callPackage ./vibetyper {}; + honcho = pkgs.callPackage ./honcho {}; # Imported from flake inputs (pass-through, no modifications) basecamp = inputs.basecamp.packages.${system}.default; diff --git a/pkgs/eigent/default.nix b/pkgs/eigent/default.nix index 399567f..2a22093 100644 --- a/pkgs/eigent/default.nix +++ b/pkgs/eigent/default.nix @@ -6,38 +6,66 @@ nodePackages, uv, python3, - makeWrapper, + nix-update-script, }: let pname = "eigent"; version = "0.0.89"; src = fetchurl { url = "https://github.com/eigent-ai/eigent/releases/download/v${version}/Eigent-${version}.AppImage"; - hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; + hash = "sha256-9KuiFjegfXhCu1W/FCinWX4ae/DsNPudeBcXFfW18Hc="; }; appimageContents = appimageTools.extractType2 {inherit pname version src;}; in appimageTools.wrapType2 { inherit pname version src; - nativeBuildInputs = [makeWrapper]; - - extraPkgs = pkgs: [ + extraPkgs = _: [ nodejs nodePackages.npm uv python3 ]; - extraInstallCommands = '' - install -m 444 -D ${appimageContents}/Eigent.desktop -t $out/share/applications - substituteInPlace $out/share/applications/Eigent.desktop \ - --replace 'Exec=AppRun' 'Exec=${pname}' - install -m 444 -D ${appimageContents}/Eigent.png \ - $out/share/icons/hicolor/256x256/apps/Eigent.png - wrapProgram $out/bin/${pname} \ - --prefix PATH : ${lib.makeBinPath [nodejs nodePackages.npm uv python3]} + # Runs before bubblewrap launches — sets up writable state for the sandbox. + extraPreBwrapCmds = '' + # eigent writes to multiple dirs under resources/ at runtime: + # prebuilt/ → pyvenv.cfg, .terminal_venv_fixed sentinel + # backend/ → creates runtime/ dir for temporary state + # Nix store is read-only → EROFS. Copy to writable location on first + # launch (or when the package version changes). + DATA_DIR="$HOME/.local/share/${pname}" + mkdir -p "$DATA_DIR" + for subdir in prebuilt backend; do + SRC="${appimageContents}/resources/$subdir" + DST="$DATA_DIR/$subdir" + if [ ! -f "$DST/.nix-src" ] || [ "$(cat "$DST/.nix-src")" != "${appimageContents}" ]; then + rm -rf "$DST" + cp -r "$SRC" "$DST" + chmod -R u+w "$DST" + echo "${appimageContents}" > "$DST/.nix-src" + fi + done ''; + # Bind-mount writable copies over the read-only store paths so the app + # sees its files at the expected locations but can write to them. + extraBwrapArgs = [ + "--bind $HOME/.local/share/${pname}/prebuilt ${appimageContents}/resources/prebuilt" + "--bind $HOME/.local/share/${pname}/backend ${appimageContents}/resources/backend" + ]; + + extraInstallCommands = '' + install -m 444 -D ${appimageContents}/eigent.desktop -t $out/share/applications + substituteInPlace $out/share/applications/eigent.desktop \ + --replace-fail 'Exec=AppRun --no-sandbox %U' 'Exec=${pname} %U' + install -m 444 -D ${appimageContents}/eigent.png \ + $out/share/icons/hicolor/256x256/apps/eigent.png + ''; + + passthru = { + updateScript = nix-update-script {}; + }; + meta = { description = "Open source AI cowork desktop app — local alternative to Claude Cowork"; homepage = "https://github.com/eigent-ai/eigent"; diff --git a/pkgs/honcho/default.nix b/pkgs/honcho/default.nix new file mode 100644 index 0000000..bdf7d0a --- /dev/null +++ b/pkgs/honcho/default.nix @@ -0,0 +1,86 @@ +{ + lib, + nix-update-script, + python3, + fetchFromGitHub, +}: +python3.pkgs.buildPythonPackage rec { + pname = "honcho"; + version = "3.0.5"; + pyproject = true; + + src = fetchFromGitHub { + owner = "plastic-labs"; + repo = "honcho"; + rev = "refs/heads/main"; + hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; + }; + + # Relax Python dependency version constraints + pythonRelaxDeps = true; + + build-system = with python3.pkgs; [ + hatchling + ]; + + dependencies = with python3.pkgs; [ + fastapi + groq + python-dotenv + sqlalchemy + pgvector + greenlet + psycopg + httpx + rich + nanoid + alembic + pyjwt + tenacity + tiktoken + langfuse + openai + pydantic + pydantic-settings + google-genai + pdfplumber + typing-extensions + json-repair + turbopuffer + lancedb + pyarrow + redis + cashews + scikit-learn + prometheus_client + cloudevents + fastapi-pagination + sentry-sdk + ]; + + # Skip tests - they require a running PostgreSQL with pgvector and Redis + doCheck = false; + + # Disable imports check because honcho tries to connect to database at import time + pythonImportsCheck = []; + + passthru.updateScript = nix-update-script {}; + + meta = with lib; { + description = "Memory library for building stateful AI agents"; + longDescription = '' + Honcho provides a sophisticated memory and reasoning system for AI agents: + - Rich Reasoning System: Multiple implementation methods that extract + conclusions from interactions and build comprehensive peer representations + - Chat API: Reasoning-informed responses integrating conclusions with context + - Background Processing: Asynchronous pipeline for expensive operations + - Multi-Provider Support: Configurable LLM providers (OpenAI, Anthropic, Google, Groq) + - REST API server for easy integration with any application + - PostgreSQL with pgvector for storage + ''; + homepage = "https://github.com/plastic-labs/honcho"; + license = licenses.agpl3Only; + platforms = platforms.linux; + mainProgram = "honcho"; + }; +} diff --git a/pkgs/openshell/sources.json b/pkgs/openshell/sources.json index 3b16b06..659a9d1 100644 --- a/pkgs/openshell/sources.json +++ b/pkgs/openshell/sources.json @@ -1,17 +1,17 @@ { - "version": "v0.0.22", + "version": "v0.0.23", "sources": { "aarch64-linux": { - "url": "https://github.com/NVIDIA/OpenShell/releases/download/v0.0.22/openshell-aarch64-unknown-linux-musl.tar.gz", - "hash": "sha256-yeogvG6sxUWijYTUl5QbHyb9z0f84dKwKGsmSlxyPqk=" + "url": "https://github.com/NVIDIA/OpenShell/releases/download/v0.0.23/openshell-aarch64-unknown-linux-musl.tar.gz", + "hash": "sha256-x+TMlj8sc68rbkxwW80NrmyC0xaeC81TJMNEtUNhOLg=" }, "x86_64-linux": { - "url": "https://github.com/NVIDIA/OpenShell/releases/download/v0.0.22/openshell-x86_64-unknown-linux-musl.tar.gz", - "hash": "sha256-5qJCwQJgI8XgQ64WetNZc9jS++buT1Z0PqfBC+dYu94=" + "url": "https://github.com/NVIDIA/OpenShell/releases/download/v0.0.23/openshell-x86_64-unknown-linux-musl.tar.gz", + "hash": "sha256-WLmYWn7mCC6VzUFEFN/O49hui81U0zPI6f3E5Hc9SjI=" }, "aarch64-darwin": { - "url": "https://github.com/NVIDIA/OpenShell/releases/download/v0.0.22/openshell-aarch64-apple-darwin.tar.gz", - "hash": "sha256-XZLhP/7jLnkt0qmxVtZumsAwQCss7alaeiWbUW4njwM=" + "url": "https://github.com/NVIDIA/OpenShell/releases/download/v0.0.23/openshell-aarch64-apple-darwin.tar.gz", + "hash": "sha256-Bm8YP+7+CRKjfjNevirKRWHFBrdy3h5XV7gegvvcWXc=" } } }