remove config drift check for nix (#3061)
This commit is contained in:
@@ -12,7 +12,31 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
configMergeScript = pkgs.callPackage ./configMergeScript.nix { };
|
configMergeScript = pkgs.callPackage ./configMergeScript.nix { };
|
||||||
|
|
||||||
|
# Auto-generated config key reference — always in sync with Python
|
||||||
|
configKeys = pkgs.runCommand "hermes-config-keys" {} ''
|
||||||
|
set -euo pipefail
|
||||||
|
export HOME=$TMPDIR
|
||||||
|
${hermesVenv}/bin/python3 -c '
|
||||||
|
import json, sys
|
||||||
|
from hermes_cli.config import DEFAULT_CONFIG
|
||||||
|
|
||||||
|
def leaf_paths(d, prefix=""):
|
||||||
|
paths = []
|
||||||
|
for k, v in sorted(d.items()):
|
||||||
|
path = f"{prefix}.{k}" if prefix else k
|
||||||
|
if isinstance(v, dict) and v:
|
||||||
|
paths.extend(leaf_paths(v, path))
|
||||||
|
else:
|
||||||
|
paths.append(path)
|
||||||
|
return paths
|
||||||
|
|
||||||
|
json.dump(sorted(leaf_paths(DEFAULT_CONFIG)), sys.stdout, indent=2)
|
||||||
|
' > $out
|
||||||
|
'';
|
||||||
in {
|
in {
|
||||||
|
packages.configKeys = configKeys;
|
||||||
|
|
||||||
checks = lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
|
checks = lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
|
||||||
# Verify binaries exist and are executable
|
# Verify binaries exist and are executable
|
||||||
package-contents = pkgs.runCommand "hermes-package-contents" { } ''
|
package-contents = pkgs.runCommand "hermes-package-contents" { } ''
|
||||||
@@ -101,63 +125,6 @@
|
|||||||
echo "ok" > $out/result
|
echo "ok" > $out/result
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# ── Config drift detection ────────────────────────────────────────
|
|
||||||
# Extracts leaf key paths from Python's DEFAULT_CONFIG and compares
|
|
||||||
# against the committed reference in nix/config-keys.json.
|
|
||||||
config-drift = pkgs.runCommand "hermes-config-drift" {
|
|
||||||
nativeBuildInputs = [ pkgs.jq ];
|
|
||||||
referenceKeys = ./config-keys.json;
|
|
||||||
} ''
|
|
||||||
set -e
|
|
||||||
export HOME=$(mktemp -d)
|
|
||||||
|
|
||||||
echo "=== Extracting DEFAULT_CONFIG leaf keys from Python ==="
|
|
||||||
${hermesVenv}/bin/python3 -c '
|
|
||||||
import json, sys
|
|
||||||
from hermes_cli.config import DEFAULT_CONFIG
|
|
||||||
|
|
||||||
def leaf_paths(d, prefix=""):
|
|
||||||
paths = []
|
|
||||||
for k, v in sorted(d.items()):
|
|
||||||
path = f"{prefix}.{k}" if prefix else k
|
|
||||||
if isinstance(v, dict) and v:
|
|
||||||
paths.extend(leaf_paths(v, path))
|
|
||||||
else:
|
|
||||||
paths.append(path)
|
|
||||||
return paths
|
|
||||||
|
|
||||||
json.dump(sorted(leaf_paths(DEFAULT_CONFIG)), sys.stdout)
|
|
||||||
' > /tmp/actual-keys.json
|
|
||||||
|
|
||||||
echo "=== Comparing against reference ==="
|
|
||||||
jq -r '.[]' $referenceKeys | sort > /tmp/reference.txt
|
|
||||||
jq -r '.[]' /tmp/actual-keys.json | sort > /tmp/actual.txt
|
|
||||||
|
|
||||||
ADDED=$(comm -23 /tmp/actual.txt /tmp/reference.txt || true)
|
|
||||||
REMOVED=$(comm -13 /tmp/actual.txt /tmp/reference.txt || true)
|
|
||||||
FAILED=false
|
|
||||||
|
|
||||||
if [ -n "$ADDED" ]; then
|
|
||||||
echo "FAIL: New keys in DEFAULT_CONFIG not in nix/config-keys.json:"
|
|
||||||
echo "$ADDED" | sed 's/^/ + /'
|
|
||||||
FAILED=true
|
|
||||||
fi
|
|
||||||
if [ -n "$REMOVED" ]; then
|
|
||||||
echo "FAIL: Keys in nix/config-keys.json missing from DEFAULT_CONFIG:"
|
|
||||||
echo "$REMOVED" | sed 's/^/ - /'
|
|
||||||
FAILED=true
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$FAILED" = "true" ]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
ACTUAL_COUNT=$(wc -l < /tmp/actual.txt)
|
|
||||||
echo "PASS: All $ACTUAL_COUNT config keys match reference"
|
|
||||||
mkdir -p $out
|
|
||||||
echo "ok" > $out/result
|
|
||||||
'';
|
|
||||||
|
|
||||||
# ── Config merge + round-trip test ────────────────────────────────
|
# ── Config merge + round-trip test ────────────────────────────────
|
||||||
# Tests the merge script (Nix activation behavior) across 7
|
# Tests the merge script (Nix activation behavior) across 7
|
||||||
# scenarios, then verifies Python's load_config() reads correctly.
|
# scenarios, then verifies Python's load_config() reads correctly.
|
||||||
|
|||||||
@@ -1,129 +0,0 @@
|
|||||||
[
|
|
||||||
"_config_version",
|
|
||||||
"agent.max_turns",
|
|
||||||
"approvals.mode",
|
|
||||||
"auxiliary.approval.api_key",
|
|
||||||
"auxiliary.approval.base_url",
|
|
||||||
"auxiliary.approval.model",
|
|
||||||
"auxiliary.approval.provider",
|
|
||||||
"auxiliary.compression.api_key",
|
|
||||||
"auxiliary.compression.base_url",
|
|
||||||
"auxiliary.compression.model",
|
|
||||||
"auxiliary.compression.provider",
|
|
||||||
"auxiliary.flush_memories.api_key",
|
|
||||||
"auxiliary.flush_memories.base_url",
|
|
||||||
"auxiliary.flush_memories.model",
|
|
||||||
"auxiliary.flush_memories.provider",
|
|
||||||
"auxiliary.mcp.api_key",
|
|
||||||
"auxiliary.mcp.base_url",
|
|
||||||
"auxiliary.mcp.model",
|
|
||||||
"auxiliary.mcp.provider",
|
|
||||||
"auxiliary.session_search.api_key",
|
|
||||||
"auxiliary.session_search.base_url",
|
|
||||||
"auxiliary.session_search.model",
|
|
||||||
"auxiliary.session_search.provider",
|
|
||||||
"auxiliary.skills_hub.api_key",
|
|
||||||
"auxiliary.skills_hub.base_url",
|
|
||||||
"auxiliary.skills_hub.model",
|
|
||||||
"auxiliary.skills_hub.provider",
|
|
||||||
"auxiliary.vision.api_key",
|
|
||||||
"auxiliary.vision.base_url",
|
|
||||||
"auxiliary.vision.model",
|
|
||||||
"auxiliary.vision.provider",
|
|
||||||
"auxiliary.vision.timeout",
|
|
||||||
"auxiliary.web_extract.api_key",
|
|
||||||
"auxiliary.web_extract.base_url",
|
|
||||||
"auxiliary.web_extract.model",
|
|
||||||
"auxiliary.web_extract.provider",
|
|
||||||
"browser.command_timeout",
|
|
||||||
"browser.inactivity_timeout",
|
|
||||||
"browser.record_sessions",
|
|
||||||
"checkpoints.enabled",
|
|
||||||
"checkpoints.max_snapshots",
|
|
||||||
"command_allowlist",
|
|
||||||
"compression.enabled",
|
|
||||||
"compression.protect_last_n",
|
|
||||||
"compression.summary_base_url",
|
|
||||||
"compression.summary_model",
|
|
||||||
"compression.summary_provider",
|
|
||||||
"compression.target_ratio",
|
|
||||||
"compression.threshold",
|
|
||||||
"delegation.api_key",
|
|
||||||
"delegation.base_url",
|
|
||||||
"delegation.model",
|
|
||||||
"delegation.provider",
|
|
||||||
"discord.auto_thread",
|
|
||||||
"discord.free_response_channels",
|
|
||||||
"discord.require_mention",
|
|
||||||
"display.bell_on_complete",
|
|
||||||
"display.compact",
|
|
||||||
"display.personality",
|
|
||||||
"display.resume_display",
|
|
||||||
"display.show_cost",
|
|
||||||
"display.show_reasoning",
|
|
||||||
"display.skin",
|
|
||||||
"display.streaming",
|
|
||||||
"honcho",
|
|
||||||
"human_delay.max_ms",
|
|
||||||
"human_delay.min_ms",
|
|
||||||
"human_delay.mode",
|
|
||||||
"memory.memory_char_limit",
|
|
||||||
"memory.memory_enabled",
|
|
||||||
"memory.user_char_limit",
|
|
||||||
"memory.user_profile_enabled",
|
|
||||||
"model",
|
|
||||||
"personalities",
|
|
||||||
"prefill_messages_file",
|
|
||||||
"privacy.redact_pii",
|
|
||||||
"quick_commands",
|
|
||||||
"security.redact_secrets",
|
|
||||||
"security.tirith_enabled",
|
|
||||||
"security.tirith_fail_open",
|
|
||||||
"security.tirith_path",
|
|
||||||
"security.tirith_timeout",
|
|
||||||
"security.website_blocklist.domains",
|
|
||||||
"security.website_blocklist.enabled",
|
|
||||||
"security.website_blocklist.shared_files",
|
|
||||||
"smart_model_routing.cheap_model",
|
|
||||||
"smart_model_routing.enabled",
|
|
||||||
"smart_model_routing.max_simple_chars",
|
|
||||||
"smart_model_routing.max_simple_words",
|
|
||||||
"stt.enabled",
|
|
||||||
"stt.local.model",
|
|
||||||
"stt.openai.model",
|
|
||||||
"stt.provider",
|
|
||||||
"terminal.backend",
|
|
||||||
"terminal.container_cpu",
|
|
||||||
"terminal.container_disk",
|
|
||||||
"terminal.container_memory",
|
|
||||||
"terminal.container_persistent",
|
|
||||||
"terminal.cwd",
|
|
||||||
"terminal.daytona_image",
|
|
||||||
"terminal.docker_forward_env",
|
|
||||||
"terminal.docker_image",
|
|
||||||
"terminal.docker_mount_cwd_to_workspace",
|
|
||||||
"terminal.docker_volumes",
|
|
||||||
"terminal.env_passthrough",
|
|
||||||
"terminal.modal_image",
|
|
||||||
"terminal.persistent_shell",
|
|
||||||
"terminal.singularity_image",
|
|
||||||
"terminal.timeout",
|
|
||||||
"timezone",
|
|
||||||
"toolsets",
|
|
||||||
"tts.edge.voice",
|
|
||||||
"tts.elevenlabs.model_id",
|
|
||||||
"tts.elevenlabs.voice_id",
|
|
||||||
"tts.neutts.device",
|
|
||||||
"tts.neutts.model",
|
|
||||||
"tts.neutts.ref_audio",
|
|
||||||
"tts.neutts.ref_text",
|
|
||||||
"tts.openai.model",
|
|
||||||
"tts.openai.voice",
|
|
||||||
"tts.provider",
|
|
||||||
"voice.auto_tts",
|
|
||||||
"voice.max_recording_seconds",
|
|
||||||
"voice.record_key",
|
|
||||||
"voice.silence_duration",
|
|
||||||
"voice.silence_threshold",
|
|
||||||
"whatsapp"
|
|
||||||
]
|
|
||||||
@@ -196,7 +196,7 @@ Both are deep-merged at evaluation time. Nix-declared keys always win over keys
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
:::tip Discovering available config keys
|
:::tip Discovering available config keys
|
||||||
The full set of config keys is defined in [`nix/config-keys.json`](https://github.com/NousResearch/hermes-agent/blob/main/nix/config-keys.json) (127 leaf keys). You can paste your existing `config.yaml` into the `settings` attrset — the structure maps 1:1. The build-time `config-drift` check catches any drift between the reference and the Python source.
|
Run `nix build .#configKeys && cat result` to see every leaf config key extracted from Python's `DEFAULT_CONFIG`. You can paste your existing `config.yaml` into the `settings` attrset — the structure maps 1:1.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@@ -607,7 +607,6 @@ nix build .#checks.x86_64-linux.entry-points-sync # pyproject.toml ↔ Nix pack
|
|||||||
nix build .#checks.x86_64-linux.cli-commands # gateway/config subcommands
|
nix build .#checks.x86_64-linux.cli-commands # gateway/config subcommands
|
||||||
nix build .#checks.x86_64-linux.managed-guard # HERMES_MANAGED blocks mutation
|
nix build .#checks.x86_64-linux.managed-guard # HERMES_MANAGED blocks mutation
|
||||||
nix build .#checks.x86_64-linux.bundled-skills # skills present in package
|
nix build .#checks.x86_64-linux.bundled-skills # skills present in package
|
||||||
nix build .#checks.x86_64-linux.config-drift # config keys match Python source
|
|
||||||
nix build .#checks.x86_64-linux.config-roundtrip # merge script preserves user keys
|
nix build .#checks.x86_64-linux.config-roundtrip # merge script preserves user keys
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -621,7 +620,6 @@ nix build .#checks.x86_64-linux.config-roundtrip # merge script preserves use
|
|||||||
| `cli-commands` | `hermes --help` exposes `gateway` and `config` subcommands |
|
| `cli-commands` | `hermes --help` exposes `gateway` and `config` subcommands |
|
||||||
| `managed-guard` | `HERMES_MANAGED=true hermes config set ...` prints the NixOS error |
|
| `managed-guard` | `HERMES_MANAGED=true hermes config set ...` prints the NixOS error |
|
||||||
| `bundled-skills` | Skills directory exists, contains SKILL.md files, `HERMES_BUNDLED_SKILLS` is set in wrapper |
|
| `bundled-skills` | Skills directory exists, contains SKILL.md files, `HERMES_BUNDLED_SKILLS` is set in wrapper |
|
||||||
| `config-drift` | Leaf keys extracted from Python's `DEFAULT_CONFIG` match the committed `nix/config-keys.json` reference |
|
|
||||||
| `config-roundtrip` | 7 merge scenarios: fresh install, Nix override, user key preservation, mixed merge, MCP additive merge, nested deep merge, idempotency |
|
| `config-roundtrip` | 7 merge scenarios: fresh install, Nix override, user key preservation, mixed merge, MCP additive merge, nested deep merge, idempotency |
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
Reference in New Issue
Block a user