consume QMD through OpenClaw tools
What: - consume QMD from nix-openclaw-tools instead of a separate upstream flake input - expose QMD as an internal OpenClaw battery on Darwin and Linux - add an opt-in Home Manager qmd model prewarm activation - keep plugin packages off the user's shell PATH by default while preserving the runtime PATH Why: - nix-openclaw-tools owns reproducible tool packages and cacheable plugin metadata - nixos-config should configure OpenClaw, not hand-wire runtime tools Tests: - nix build .#checks.aarch64-darwin.package-contents --accept-flake-config --no-link - nix build .#checks.aarch64-darwin.qmd-runtime --accept-flake-config --no-link - nix build .#checks.aarch64-darwin.bin-surface .#checks.aarch64-darwin.config-validity .#checks.aarch64-darwin.gateway-smoke --accept-flake-config --no-link - nix eval .#checks.x86_64-linux.default-instance.drvPath --accept-flake-config
This commit is contained in:
parent
0a70262dda
commit
d56fa8a75c
12
README.md
12
README.md
@ -400,7 +400,7 @@ Contract to implement:
|
||||
1) Add openclawPlugin output in flake.nix:
|
||||
- name
|
||||
- skills (paths to SKILL.md dirs)
|
||||
- packages (CLI packages to put on PATH)
|
||||
- packages (CLI packages to put on the OpenClaw runtime PATH)
|
||||
- needs (stateDirs + requiredEnv)
|
||||
|
||||
Example:
|
||||
@ -713,6 +713,16 @@ programs.openclaw.config = {
|
||||
|
||||
QMD stays inside the `openclaw` wrapper PATH, so users do not need to install a separate `qmd` command. The builtin `memorySearch.provider = "local"` path is an escape hatch for people who want to manage `node-llama-cpp` themselves; it is not the primary Nix-supported path.
|
||||
|
||||
Plugin CLIs are also kept on the OpenClaw runtime PATH by default, not on the user's login shell PATH. Set `programs.openclaw.exposePluginPackages = true` only when you explicitly want plugin CLIs in `home.packages`.
|
||||
|
||||
Optional model prewarming is also declarative:
|
||||
|
||||
```nix
|
||||
programs.openclaw.qmd.prewarmModels.enable = true;
|
||||
```
|
||||
|
||||
That runs `qmd pull` during Home Manager activation and stores the default embedding, expansion, and reranking models in the user's QMD cache. Expect about 2.25GB of cache use.
|
||||
|
||||
### What we manage vs what you manage
|
||||
|
||||
| Component | Nix manages | You manage |
|
||||
|
||||
@ -14,7 +14,7 @@ Every plugin artifact exposes the same fields (flake output `openclawPlugin` tod
|
||||
openclawPlugin = {
|
||||
name = "summarize"; # unique; last-wins on collision
|
||||
skills = [ ./skills/summarize ]; # dirs containing SKILL.md
|
||||
packages = [ pkgs.summarize-cli ]; # binaries placed on PATH
|
||||
packages = [ pkgs.summarize-cli ]; # binaries placed on the OpenClaw runtime PATH
|
||||
needs = {
|
||||
stateDirs = [ ".config/summarize" ]; # created under $HOME
|
||||
requiredEnv = [ "SUMMARIZE_API_KEY" ]; # must point to files
|
||||
|
||||
@ -50,7 +50,7 @@ plugin/
|
||||
That's it. No registry. No central authority. Point at a repo, get a plugin.
|
||||
|
||||
One install gives you:
|
||||
- **Binary** on PATH (built from source, pinned version)
|
||||
- **Binary** on the OpenClaw runtime PATH (built from source, pinned version)
|
||||
- **Skills** in workspace (agent knows how to use it)
|
||||
- **Config** validated (missing env = install fails, not runtime error)
|
||||
- **State dirs** created (plugin has a home)
|
||||
@ -203,7 +203,7 @@ voicecall status --call-id abc123 # Check for responses
|
||||
4. **Create state dirs** — from manifest
|
||||
5. **Add `openclaw plugins` CLI** — list, enable, disable, info
|
||||
|
||||
That's it. No dynamic code loading, no TypeBox registration, no RPC handlers. Just: find plugins, validate their needs, put binaries on PATH, copy skills to workspace.
|
||||
That's it. No dynamic code loading, no TypeBox registration, no RPC handlers. Just: find plugins, validate their needs, put binaries on the OpenClaw runtime PATH, copy skills to workspace.
|
||||
|
||||
### How nix-openclaw fits in
|
||||
|
||||
|
||||
33
flake.lock
generated
33
flake.lock
generated
@ -43,11 +43,11 @@
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1777976020,
|
||||
"narHash": "sha256-IsgLwW0Y6JYiWXbxmzN1FDO0//Osu2YpeID1tFMbwkk=",
|
||||
"lastModified": 1778052717,
|
||||
"narHash": "sha256-EsQiDKKwBS8so+OzjPOZH+z+JOJeREAsOJ/fJAx3WCY=",
|
||||
"owner": "openclaw",
|
||||
"repo": "nix-openclaw-tools",
|
||||
"rev": "08955054f466e2eb55628763c1d7ee2de5af9f6d",
|
||||
"rev": "a0e7ac5ef1b6f5d1940e3efdb9ebad2dc04467f1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -88,37 +88,12 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"qmd": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1775429264,
|
||||
"narHash": "sha256-bqIVaNRTa8H5vrw3RwsD7QdtTa0xNvRuEVzlzE1hIBQ=",
|
||||
"owner": "tobi",
|
||||
"repo": "qmd",
|
||||
"rev": "65cd1b3fd02891d1ee0eefa751620918664fa321",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "tobi",
|
||||
"ref": "v2.1.0",
|
||||
"repo": "qmd",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"home-manager": "home-manager",
|
||||
"nix-openclaw-tools": "nix-openclaw-tools",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"qmd": "qmd"
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
|
||||
12
flake.nix
12
flake.nix
@ -14,9 +14,6 @@
|
||||
home-manager.url = "github:nix-community/home-manager";
|
||||
home-manager.inputs.nixpkgs.follows = "nixpkgs";
|
||||
nix-openclaw-tools.url = "github:openclaw/nix-openclaw-tools";
|
||||
qmd.url = "github:tobi/qmd/v2.1.0";
|
||||
qmd.inputs.flake-utils.follows = "flake-utils";
|
||||
qmd.inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
outputs =
|
||||
@ -26,7 +23,6 @@
|
||||
flake-utils,
|
||||
home-manager,
|
||||
nix-openclaw-tools,
|
||||
qmd,
|
||||
}:
|
||||
let
|
||||
openclawToolPkgsFor =
|
||||
@ -35,14 +31,10 @@
|
||||
nix-openclaw-tools.packages.${system}
|
||||
else
|
||||
{ };
|
||||
qmdPkgsFor =
|
||||
system:
|
||||
if qmd ? packages && builtins.hasAttr system qmd.packages then qmd.packages.${system} else { };
|
||||
overlay =
|
||||
final: prev:
|
||||
import ./nix/overlay.nix {
|
||||
openclawToolPkgs = openclawToolPkgsFor prev.stdenv.hostPlatform.system;
|
||||
qmdPkgs = qmdPkgsFor prev.stdenv.hostPlatform.system;
|
||||
} final prev;
|
||||
sourceInfoStable = import ./nix/sources/openclaw-source.nix;
|
||||
systems = [
|
||||
@ -58,9 +50,7 @@
|
||||
overlays = [ overlay ];
|
||||
};
|
||||
openclawToolPkgs = openclawToolPkgsFor system;
|
||||
qmdPkgs = qmdPkgsFor system;
|
||||
qmdPackage =
|
||||
if pkgs.stdenv.hostPlatform.isDarwin then null else qmdPkgs.qmd or qmdPkgs.default or null;
|
||||
qmdPackage = openclawToolPkgs.qmd or null;
|
||||
packageSetStable = import ./nix/packages {
|
||||
pkgs = pkgs;
|
||||
sourceInfo = sourceInfoStable;
|
||||
|
||||
@ -205,12 +205,26 @@ let
|
||||
throw "secrets.providers file variant missing from generated config."
|
||||
);
|
||||
|
||||
qmdPrewarmEval = moduleEval {
|
||||
qmd.prewarmModels.enable = true;
|
||||
};
|
||||
qmdPrewarmActivation = builtins.toJSON qmdPrewarmEval.config.home.activation.openclawQmdPrewarm;
|
||||
qmdPrewarmCheck =
|
||||
builtins.deepSeq (requireNoAssertionFailures "qmd.prewarmModels" qmdPrewarmEval)
|
||||
(
|
||||
if lib.hasInfix "/bin/qmd pull" qmdPrewarmActivation then
|
||||
"ok"
|
||||
else
|
||||
throw "qmd.prewarmModels did not wire qmd pull activation."
|
||||
);
|
||||
|
||||
checkKey = builtins.deepSeq [
|
||||
defaultCheck
|
||||
customPluginCheck
|
||||
duplicateSkillCheck
|
||||
userPluginSkillCollisionCheck
|
||||
secretProviderCheck
|
||||
qmdPrewarmCheck
|
||||
] "ok";
|
||||
|
||||
in
|
||||
|
||||
@ -10,6 +10,7 @@ let
|
||||
cfg = openclawLib.cfg;
|
||||
homeDir = openclawLib.homeDir;
|
||||
appPackage = openclawLib.appPackage;
|
||||
qmdPackage = openclawLib.qmdPackage;
|
||||
|
||||
defaultInstance = {
|
||||
enable = cfg.enable;
|
||||
@ -262,7 +263,13 @@ in
|
||||
]
|
||||
++ files.documentsAssertions
|
||||
++ files.duplicateSkillAssertion
|
||||
++ plugins.pluginAssertions;
|
||||
++ plugins.pluginAssertions
|
||||
++ [
|
||||
{
|
||||
assertion = !cfg.qmd.prewarmModels.enable || qmdPackage != null;
|
||||
message = "programs.openclaw.qmd.prewarmModels.enable requires a qmd package in openclawPackages.";
|
||||
}
|
||||
];
|
||||
|
||||
home.packages = lib.unique (
|
||||
(map (item: item.package) instanceConfigs)
|
||||
@ -316,6 +323,19 @@ in
|
||||
${plugins.pluginGuards}
|
||||
'';
|
||||
|
||||
home.activation.openclawQmdPrewarm =
|
||||
lib.mkIf (cfg.qmd.prewarmModels.enable && qmdPackage != null)
|
||||
(
|
||||
lib.hm.dag.entryAfter [ "openclawDirs" ] ''
|
||||
run --quiet ${lib.getExe' pkgs.coreutils "env"} \
|
||||
HOME=${lib.escapeShellArg homeDir} \
|
||||
XDG_CACHE_HOME=${lib.escapeShellArg "${homeDir}/.cache"} \
|
||||
XDG_CONFIG_HOME=${lib.escapeShellArg "${homeDir}/.config"} \
|
||||
XDG_DATA_HOME=${lib.escapeShellArg "${homeDir}/.local/share"} \
|
||||
${qmdPackage}/bin/qmd pull
|
||||
''
|
||||
);
|
||||
|
||||
home.activation.openclawAppDefaults =
|
||||
lib.mkIf (pkgs.stdenv.hostPlatform.isDarwin && appDefaults != { })
|
||||
(
|
||||
|
||||
@ -21,13 +21,14 @@ let
|
||||
else
|
||||
cfg.package;
|
||||
appPackage = if cfg.appPackage != null then cfg.appPackage else defaultPackage;
|
||||
qmdPackage = pkgs.openclawPackages.qmd or null;
|
||||
generatedConfigOptions = import ../../../generated/openclaw-config-options.nix { lib = lib; };
|
||||
pluginCatalog = import ./plugin-catalog.nix;
|
||||
|
||||
bundledPluginSources =
|
||||
let
|
||||
openclawToolsRev = "08955054f466e2eb55628763c1d7ee2de5af9f6d";
|
||||
openclawToolsNarHash = "sha256-IsgLwW0Y6JYiWXbxmzN1FDO0//Osu2YpeID1tFMbwkk=";
|
||||
openclawToolsRev = "a0e7ac5ef1b6f5d1940e3efdb9ebad2dc04467f1";
|
||||
openclawToolsNarHash = "sha256-EsQiDKKwBS8so+OzjPOZH+z+JOJeREAsOJ/fJAx3WCY=";
|
||||
openclawTools =
|
||||
tool:
|
||||
"github:openclaw/nix-openclaw-tools?dir=tools/${tool}&rev=${openclawToolsRev}&narHash=${openclawToolsNarHash}";
|
||||
@ -66,6 +67,7 @@ in
|
||||
toolSets
|
||||
defaultPackage
|
||||
appPackage
|
||||
qmdPackage
|
||||
generatedConfigOptions
|
||||
bundledPluginSources
|
||||
bundledPlugins
|
||||
|
||||
@ -184,10 +184,16 @@ in
|
||||
|
||||
exposePluginPackages = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
default = false;
|
||||
description = "Add plugin packages to home.packages so CLIs are on PATH.";
|
||||
};
|
||||
|
||||
qmd.prewarmModels.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Download/check QMD's default GGUF models during Home Manager activation. This uses about 2.25GB under the user's QMD cache.";
|
||||
};
|
||||
|
||||
reloadScript = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
|
||||
@ -54,6 +54,12 @@
|
||||
linux = true;
|
||||
};
|
||||
|
||||
qmd = {
|
||||
tool = "qmd";
|
||||
description = "Search local markdown knowledge bases";
|
||||
linux = true;
|
||||
};
|
||||
|
||||
sonoscli = {
|
||||
tool = "sonoscli";
|
||||
description = "Control Sonos speakers";
|
||||
|
||||
@ -41,7 +41,7 @@ in
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = if pkgs ? openclaw-gateway then pkgs.openclaw-gateway else pkgs.openclaw;
|
||||
default = if pkgs ? openclaw then pkgs.openclaw else pkgs.openclaw-gateway;
|
||||
description = "OpenClaw gateway package.";
|
||||
};
|
||||
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
{
|
||||
openclawToolPkgs ? { },
|
||||
qmdPkgs ? { },
|
||||
}:
|
||||
final: prev:
|
||||
let
|
||||
packages = import ./packages {
|
||||
pkgs = prev;
|
||||
openclawToolPkgs = openclawToolPkgs;
|
||||
qmdPackage = qmdPkgs.qmd or qmdPkgs.default or null;
|
||||
qmdPackage = openclawToolPkgs.qmd or null;
|
||||
};
|
||||
toolNames =
|
||||
(import ./tools/extended.nix {
|
||||
@ -22,7 +21,7 @@ let
|
||||
import ./packages {
|
||||
pkgs = prev;
|
||||
openclawToolPkgs = openclawToolPkgs;
|
||||
qmdPackage = qmdPkgs.qmd or qmdPkgs.default or null;
|
||||
qmdPackage = openclawToolPkgs.qmd or null;
|
||||
inherit toolNamesOverride excludeToolNames;
|
||||
};
|
||||
in
|
||||
|
||||
@ -30,4 +30,5 @@ in
|
||||
openclaw-gateway = openclawGateway;
|
||||
openclaw = openclawBundle;
|
||||
}
|
||||
// (if qmdPackage != null then { qmd = qmdPackage; } else { })
|
||||
// (if isDarwin then { openclaw-app = openclawApp; } else { })
|
||||
|
||||
Loading…
Reference in New Issue
Block a user