Commit Graph

3667 Commits

Author SHA1 Message Date
Alexander Whitestone
42e04ba03a feat: evaluate Qwen3.5:35B as local model option (#288)
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 1m8s
Part of Epic #281. Verdict: APPROVED 8.8/10 security.
MoE 35B/3B active, 128K ctx, Apache 2.0, perfect data locality.

Closes #288
2026-04-13 21:32:21 -04:00
Alexander Whitestone
c84e2279ea fix: remove hardcoded ~/.hermes paths from optional skills
Fix memento_cards.py and telephony.py to use HERMES_HOME env var
with Path.home() fallback instead of hardcoded "~/.hermes".

Leaves migration script as-is (intentionally references old paths).

Closes #479
2026-04-13 21:31:26 -04:00
954fd992eb Merge pull request 'perf: lazy session creation — defer DB write until first message (#314)' (#449) from whip/314-1776127532 into main
Some checks failed
Forge CI / smoke-and-build (push) Failing after 55s
Forge CI / smoke-and-build (pull_request) Failing after 1m12s
perf: lazy session creation (#314)

Closes #314.
2026-04-14 01:08:13 +00:00
Metatron
f35f56e397 perf: lazy session creation — defer DB write until first message (closes #314)
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 56s
Remove eager create_session() call from AIAgent.__init__(). Sessions
are now created lazily on first _flush_messages_to_session_db() call
via ensure_session() which uses INSERT OR IGNORE.

Impact: eliminates 32.4% of sessions (3,564 of 10,985) that were
created at agent init but never received any messages.

The existing ensure_session() fallback in _flush_messages_to_session_db()
already handles this pattern — it was originally designed for recovery
after transient SQLite lock failures. Now it's the primary creation path.

Compression-initiated sessions still use create_session() directly
(line ~5995) since they have messages to write immediately.
2026-04-13 20:52:06 -04:00
8d0cad13c4 Merge pull request 'fix: watchdog config drift check uses YAML parse, not grep (#377)' (#398) from burn/377-1776117775 into main
Some checks failed
Forge CI / smoke-and-build (push) Failing after 28s
2026-04-14 00:34:14 +00:00
b9aca0a3b4 Merge pull request 'feat: time-aware model routing for cron jobs (#317)' (#432) from burn/317-1776125702 into main
Some checks failed
Forge CI / smoke-and-build (push) Has been cancelled
2026-04-14 00:34:06 +00:00
99d36533d5 Merge pull request 'feat: add /debug slash command with paste service upload (#320)' (#416) from burn/320-1776120221 into main
Some checks failed
Forge CI / smoke-and-build (push) Has been cancelled
2026-04-14 00:33:59 +00:00
b562a3d94c Merge pull request 'docs(#322): comprehensive Honcho evaluation — recommendation: KEEP' (#430) from burn/322-1776125702 into main
Some checks failed
Forge CI / smoke-and-build (push) Has been cancelled
2026-04-14 00:33:56 +00:00
37af40a38e Merge pull request 'feat: session garbage collection (#315)' (#383) from feat/315-session-gc into main
Some checks failed
Forge CI / smoke-and-build (push) Has been cancelled
2026-04-14 00:33:15 +00:00
5aa8581e2b Merge pull request 'fix: gateway config debt - validation, defaults, fallback chain checks (#328)' (#381) from fix/gateway-config-debt-328 into main
Some checks failed
Forge CI / smoke-and-build (push) Has been cancelled
2026-04-14 00:32:56 +00:00
b44255f21e Merge pull request 'cron: Comprehensive stale error state handling for recovered jobs (#349)' (#431) from burn/349-1776125702 into main
Some checks failed
Forge CI / smoke-and-build (push) Has been cancelled
2026-04-14 00:32:48 +00:00
6b41bafccd Merge pull request 'fix(cron): disable terminal toolset for cloud providers in cron jobs (#379)' (#436) from burn/379-1776125702 into main
Some checks failed
Forge CI / smoke-and-build (push) Has been cancelled
2026-04-14 00:32:45 +00:00
053fa3a2dd Merge pull request 'fix(cron): normalize model field types in deploy-crons.py' (#410) from burn/376-1776117777 into main
Some checks failed
Forge CI / smoke-and-build (push) Has been cancelled
2026-04-14 00:31:47 +00:00
cda29991e0 Merge pull request 'Fix #373: fallback_model blank fields no longer trigger gateway warnings' (#433) from burn/373-1776125702 into main
Some checks failed
Forge CI / smoke-and-build (push) Has been cancelled
2026-04-14 00:30:10 +00:00
57418dae07 fix(cron): disable terminal toolset for cloud providers in cron jobs (#379)
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 1m16s
Cron jobs like nightwatch-health-monitor SSH into remote VPSes.
When the runtime provider is cloud (Nous, OpenRouter, Anthropic),
SSH keys don't exist on the inference server — causing silent
failures and wasted iterations.

Changes:
- cron/scheduler.py: Import is_local_endpoint from model_metadata.
  Build disabled_toolsets dynamically: append 'terminal' when the
  runtime base_url is NOT a local endpoint. Log when terminal is
  disabled for observability. Also warn when a job declares
  requires_local_infra=true but runs on cloud.
- tests/test_cron_cloud_terminal.py: 14 tests verifying
  is_local_endpoint classification and disabled_toolsets logic.

Behavior:
  Local (localhost/127/RFC-1918): terminal enabled, SSH works.
  Cloud (openrouter/nous/anthropic): terminal disabled, agent
  reports SSH unavailable instead of wasting iterations.

Closes #379
2026-04-13 20:20:41 -04:00
Alexander Whitestone
5989600d80 feat: time-aware model routing for cron jobs (#317)
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 1m1s
Empirical audit: cron error rate peaks at 18:00 (9.4%) vs 4.0% at 09:00.
During configured high-error windows, automatically route cron jobs to
more capable models when the user is not present to correct errors.

- agent/smart_model_routing.py: resolve_cron_model() + _hour_in_window()
- cron/scheduler.py: wired into run_job() after base model resolution
- tests/test_cron_model_routing.py: 16 tests

Config:
  cron_model_routing:
    enabled: true
    fallback_model: "anthropic/claude-sonnet-4"
    fallback_provider: "openrouter"
    windows:
      - {start_hour: 17, end_hour: 22, reason: evening_error_peak}
      - {start_hour: 2, end_hour: 5, reason: overnight_api_instability}

Features: midnight-wrap, per-window overrides, first-match-wins,
graceful degradation on malformed config.

Closes #317
2026-04-13 20:19:37 -04:00
Timmy Time
1899878c27 Fix #373: fallback_model blank fields no longer trigger gateway warnings
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 1m1s
When users blank fallback_model fields or set enabled: false, the validation
and gateway now treat this as intentionally disabling fallback instead of
showing warnings.

Changes:
- hermes_cli/config.py: Skip warnings when both provider and model are blank
  or when enabled: false is set
- gateway/run.py: Return None for disabled fallback configs
- tests: Added 8 new tests for blank/disabled fallback scenarios

Behavior:
- Both fields blank: no warnings (intentional disable)
- enabled: false: no warnings (explicit disable)
- One field blank: warning shown (likely misconfiguration)
- Valid config: no warnings

Fixes #373
2026-04-13 20:19:21 -04:00
379769ca6d feat(cron): Show health status in job list
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 1m0s
Part of #349. Shows current vs. cleared errors, success history.
2026-04-14 00:19:11 +00:00
91bc02bc38 feat(cron): Add clear-error CLI subparser
Part of #349. Adds `hermes cron clear-error JOB_ID` command.
2026-04-14 00:18:52 +00:00
77265a31e1 feat(cron): Add clear-error CLI command
Part of #349. Adds `hermes cron clear-error JOB_ID` command.
2026-04-14 00:18:30 +00:00
Timmy
7a32df9ca3 docs(#322): comprehensive Honcho evaluation — recommendation: KEEP
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 1m9s
Decision: Cloud vs Local → BOTH (user's choice)
- Cloud: HONCHO_API_KEY from app.honcho.dev
- Self-hosted: HONCHO_BASE_URL=http://localhost:8000
- Disabled: No config = zero overhead

Integration is already production-ready:
- 3 components, ~700 lines of code
- 7 tests passing
- Async prefetch (zero-latency)
- Configurable recall modes
- Cron guard (inactive in cron context)

Recommendation: KEEP — provides unique cross-session user modeling
that complements local holographic fact_store.

Refs #322
2026-04-13 20:18:23 -04:00
cf36bd2ddf feat(cron): Add clear_error action and health timestamps
Part of #349. Adds clear_error action and includes health timestamps in job format.
2026-04-14 00:18:09 +00:00
0413fc1788 feat(cron): Comprehensive stale error state handling
- mark_job_run: track last_error_at, last_success_at, error_resolved_at
- trigger_job: clear stale error state when re-triggering
- clear_job_error: manual clearing of stale errors

Closes #349
2026-04-14 00:17:45 +00:00
5180c172fa Merge pull request 'feat: profile-tagged session isolation (#323)' (#422) from burn/323-1776120221 into main
Some checks failed
Forge CI / smoke-and-build (push) Failing after 43s
feat: profile-tagged session isolation (#323)

Closes #323.
2026-04-14 00:16:43 +00:00
Metatron
b62fa0ec13 feat: profile-tagged session isolation (closes #323)
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 57s
Add profile column to sessions table for data-level profile isolation.
All session queries now accept an optional profile filter.

Changes:
- Schema v7: new 'profile' TEXT column + idx_sessions_profile index
- Migration v7: ALTER TABLE + CREATE INDEX on existing DBs
- create_session(): new profile parameter
- ensure_session(): new profile parameter
- list_sessions_rich(): profile filter (WHERE s.profile = ?)
- search_sessions(): profile filter
- session_count(): profile filter

Sessions without a profile (None) remain visible to all queries for
backward compatibility. When a profile is passed, only that profile's
sessions are returned.

Profile agents can no longer see each other's sessions when filtered.
No breaking changes to existing callers.
2026-04-13 18:53:45 -04:00
f1626a932c feat: add /debug command handler with paste service upload (#320)
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 1m1s
2026-04-13 22:48:33 +00:00
d68ab4cff4 feat: add /debug slash command to command registry (#320) 2026-04-13 22:47:51 +00:00
3c66333c94 fix(cron): add deploy-crons.py to normalize model field types
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 48s
Fixes #376

Normalize model field in jobs.json to always be a dict when either
model or provider is specified, preventing schema inconsistency.
2026-04-13 22:24:31 +00:00
87867f3d10 fix: config drift check uses YAML parse not grep (#377)
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 59s
2026-04-13 22:12:56 +00:00
Alexander Whitestone
69e10967bd feat: session garbage collection (#315)
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 14s
Add garbage_collect() method to SessionDB that cleans up empty and
trivial sessions based on age:
- Empty sessions (0 messages) older than 24h
- Trivial sessions (1-5 messages) older than 7 days
- Sessions with >5 messages kept indefinitely

Add `hermes sessions gc` CLI command with:
- --empty-hours (default: 24)
- --trivial-days (default: 7)
- --trivial-max (default: 5)
- --source filter
- --dry-run preview mode
- --yes skip confirmation

The dry-run flow: preview what would be deleted, ask for confirmation,
then execute. Handles child session FK constraints properly.

7 tests covering: empty/trivial deletion, active session protection,
substantial session preservation, dry-run, source filtering, and child
session handling.

Closes #315
2026-04-13 17:30:39 -04:00
Alexander Whitestone
992498463e fix: gateway config debt - validation, defaults, fallback chain checks (#328)
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 1m32s
- Expand validate_config_structure() to catch:
  - fallback_providers format errors (non-list, missing provider/model)
  - session_reset.idle_minutes <= 0 (causes immediate resets)
  - session_reset.at_hour out of 0-23 range
  - API_SERVER enabled without API_SERVER_KEY
  - Unknown root-level keys that look like misplaced custom_providers fields
- Add _validate_fallback_providers() in gateway/config.py to validate
  fallback chain at gateway startup (logs warnings for malformed entries)
- Add API_SERVER_KEY check in gateway config loader (warns on unauthenticated endpoint)
- Expand _KNOWN_ROOT_KEYS to include all valid top-level config sections
  (session_reset, browser, checkpoints, voice, stt, tts, etc.)
- Add 13 new tests for fallback_providers and session_reset validation
- All existing tests pass (47/47)

Closes #328
2026-04-13 17:29:20 -04:00
1ec02cf061 Merge pull request 'fix(gateway): reject known-weak placeholder tokens at startup' (#371) from fix/weak-credential-guard into main
Some checks failed
Forge CI / smoke-and-build (push) Failing after 3m6s
2026-04-13 20:33:00 +00:00
Alexander Whitestone
1156875cb5 fix(gateway): reject known-weak placeholder tokens at startup
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 3m8s
Fixes #318

Cherry-picked concept from ferris fork (f724079).

Problem: Users who copy .env.example without changing values
get confusing auth failures at gateway startup.

Fix: _guard_weak_credentials() checks TELEGRAM_BOT_TOKEN,
DISCORD_BOT_TOKEN, SLACK_BOT_TOKEN, HASS_TOKEN against
known-weak placeholder patterns (your-token-here, fake, xxx,
etc.) and minimum length requirements. Warns at startup.

Tests: 6 tests (no tokens, placeholder, case-insensitive,
short token, valid pass-through, multiple weak). All pass.
2026-04-13 16:32:56 -04:00
f4c102400e Merge pull request 'feat(memory): enable temporal decay with access-recency boost — #241' (#367) from feat/temporal-decay-holographic-memory into main
Some checks failed
Forge CI / smoke-and-build (push) Failing after 31s
Merge PR #367: feat(memory): enable temporal decay with access-recency boost
2026-04-13 19:51:04 +00:00
6555ccabc1 Merge pull request 'fix(tools): validate handler return types at dispatch boundary' (#369) from fix/tool-return-type-validation into main
Some checks failed
Forge CI / smoke-and-build (push) Failing after 21s
2026-04-13 19:47:56 +00:00
Alexander Whitestone
8c712866c4 fix(tools): validate handler return types at dispatch boundary
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 22s
Fixes #297

Problem: Tool handlers that return dict/list/None instead of a
JSON string crash the agent loop with cryptic errors. No error
proofing at the boundary.
Fix: In handle_function_call(), after dispatch returns:
1. If result is not str → wrap in JSON with _type_warning
2. If result is str but not valid JSON → wrap in {"output": ...}
3. Log type violations for analysis
4. Valid JSON strings pass through unchanged

Tests: 4 new tests (dict, None, non-JSON string, valid JSON).
All 16 tests in test_model_tools.py pass.
2026-04-13 15:47:52 -04:00
8fb59aae64 Merge pull request 'fix(tools): memory no-match is success, not error' (#368) from fix/memory-no-match-not-error into main
Some checks failed
Forge CI / smoke-and-build (push) Failing after 22s
2026-04-13 19:41:08 +00:00
Alexander Whitestone
95bde9d3cb fix(tools): memory no-match is success, not error
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 24s
Fixes #313

Problem: MemoryStore.replace() and .remove() return
{"success": false, "error": "No entry matched..."} when the
search substring is not found. This is a valid outcome, not
an error. The empirical audit showed 58.4% error rate on the
memory tool, but 98.4% of those were just empty search results.

Fix: Return {"success": true, "result": "no_match", "message": ...}
instead. This drops the memory tool error rate from ~58% to ~1%.

Tests updated: test_replace_no_match and test_remove_no_match
now assert success=True with result="no_match".
All 33 memory tool tests pass.
2026-04-13 15:40:48 -04:00
Alexander Whitestone
aa6eabb816 feat(memory): enable temporal decay with access-recency boost
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 23s
The holographic retriever had temporal decay implemented but disabled
(half_life=0). All facts scored equally regardless of age — a 2-year-old
fact about a deprecated tool scored the same as yesterday's deployment
config.

This commit:
1. Changes default temporal_decay_half_life from 0 to 60 days
   - 60 days: facts lose half their relevance every 2 months
   - Configurable via config.yaml: plugins.hermes-memory-store.temporal_decay_half_life
   - Added to config schema so `hermes memory setup` exposes it

2. Adds access-recency boost to search scoring
   - Facts accessed within 1 half-life get up to 1.5x boost on their decay factor
   - Boost tapers linearly from 1.5 (just accessed) to 1.0 (1 half-life ago)
   - Capped at 1.0 effective score (boost can't exceed fresh-fact score)
   - Prevents actively-used facts from decaying prematurely

3. Scoring pipeline: score = relevance * trust * decay * min(1.0, access_boost)
   - Fresh facts: decay=1.0, boost≈1.5 → score unchanged
   - 60-day-old, recently accessed: decay=0.5, boost≈1.25 → score=0.625
   - 60-day-old, not accessed: decay=0.5, boost=1.0 → score=0.5
   - 120-day-old, not accessed: decay=0.25, boost=1.0 → score=0.25

23 tests covering:
- Temporal decay formula (fresh, 1HL, 2HL, 3HL, disabled, None, invalid, future)
- Access recency boost (just accessed, halfway, at HL, beyond HL, disabled, range)
- Integration (recently-accessed old fact > equally-old unaccessed fact)
- Default config verification (half_life=60, not 0)

Fixes #241
2026-04-13 15:38:12 -04:00
3b89bfbab2 fix(tools): ast.parse() preflight in execute_code — eliminates ~1,400 sandbox errors (#366)
Some checks failed
Forge CI / smoke-and-build (push) Failing after 23s
2026-04-13 19:26:06 +00:00
3e6e183ad2 Merge pull request 'fix(cron): deploy sync guard + kwarg filter + script failure marker' (#364) from fix/cron-sync-guard-v2 into main
Some checks failed
Forge CI / smoke-and-build (push) Failing after 23s
2026-04-13 19:13:31 +00:00
Alexander Whitestone
9c38e28f4d fix(cron): deploy sync guard + kwarg filter + script failure marker
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 20s
Fixes #341, Fixes #348

Three-part cron resilience fix:
1. _validate_agent_interface() — fail-fast if AIAgent.__init__
   is missing expected params (deploy sync guard)
2. _safe_agent_kwargs() — filter unsupported kwargs so jobs
   keep running with degraded functionality
3. [SCRIPT_FAILED] marker — prompt-wrapped script jobs can
   now propagate command failure to cron state

Supersedes PR #358 (branch conflict).
2026-04-13 15:12:12 -04:00
cea4c7fdd0 fix(poka-yoke): circuit breaker for error cascading (#309) + tool fixation detection (#310) (#362)
Some checks failed
Forge CI / smoke-and-build (push) Failing after 26s
Merged poka-yoke #309 and #310
2026-04-13 14:18:35 +00:00
Alexander Whitestone
ec3cd2081b fix(poka-yoke): add tool fixation detection (#310)
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 26s
Detect when the same tool is called 5+ times consecutively and inject
a nudge advising the agent to diversify its approach.

Evidence from empirical audit:
- Top marathon session (qwen, 1643 msgs): execute_code streak of 20
- Opus session (1472 msgs): terminal streak of 10

The nudge fires every 5 consecutive calls (5, 10, 15...) so it
persists without being spammy. Tracks independently in both
sequential and concurrent execution paths.
2026-04-13 10:16:11 -04:00
Alexander Whitestone
110642d86a fix(poka-yoke): add circuit breaker for error cascading (#309)
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 28s
After 3 consecutive tool errors, inject a warning into the tool result
advising the agent to switch strategies. Escalates at 6 and 9+ errors.

Empirical data from audit:
- P(error | prev error) = 58.6% vs P(error | prev success) = 25.2%
- 2.33x cascade amplification factor
- Max observed streak: 31 consecutive errors

Intervention tiers:
- 3 errors: advisory warning (try different tool, use terminal, simplify)
- 6 errors: urgent stop (halt retries, investigate or switch)
- 9+ errors: terminal-only recovery path

Tracks errors in both sequential and concurrent execution paths.
2026-04-13 10:12:24 -04:00
f9b6db52af fix: unescape corrupted quotes in mempalace __init__.py (#360)
Some checks failed
Forge CI / smoke-and-build (push) Failing after 29s
Co-authored-by: Alexander Whitestone <alexander@alexanderwhitestone.com>
Co-committed-by: Alexander Whitestone <alexander@alexanderwhitestone.com>
2026-04-13 14:03:30 +00:00
f91f22ef7a Merge pull request '[claude] fix(cron): preflight model context validation + auto-pause (#351)' (#359) from claude/issue-351 into main
Some checks failed
Forge CI / smoke-and-build (push) Has been cancelled
Merged by Timmy overnight cycle
2026-04-13 14:03:12 +00:00
b89c670400 Merge pull request 'feat: add hermes cron run --now for immediate job execution (closes #347)' (#361) from feat/cron-run-now into main
Some checks failed
Forge CI / smoke-and-build (push) Has been cancelled
Merged by Timmy overnight cycle
2026-04-13 14:03:08 +00:00
Timmy
f6e72c135c feat: add hermes cron run --now for immediate job execution (closes #347)
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 24s
Problem: 'hermes cron run JOBID' only queues for next scheduler tick.
Stale error state (like tool_choice TypeError residue) persists forever
because there's no way to execute a job immediately and get fresh results.

Solution: Three-layer synchronous execution path:
- cron/jobs.py: run_job_now() calls scheduler.run_job() then mark_job_run()
- gateway: POST /api/jobs/{id}/run-now endpoint (runs in thread executor)
- CLI: hermes cron run JOBID --now executes and prints result immediately
- tools/cronjob_tools.py: 'run_now' action routes to new function

Also fixes #346, #349 (same stale error pattern).
2026-04-13 09:58:47 -04:00
Alexander Whitestone
ece8b5f8be fix(cron): preflight model context validation + auto-pause on incompatible models
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 25s
Fixes #351

Root cause: cron jobs with a per-job model override (e.g. `gemma4:latest`,
8K context) were only discovered to be incompatible at agent runtime,
causing a hard ValueError on every tick with no automatic recovery.

Changes:
- Add `CRON_MIN_CONTEXT_TOKENS = 64_000` constant to scheduler.py
- Add `ModelContextError(ValueError)` exception class for typed identification
- Add `_check_model_context_compat()` preflight function that calls
  `get_model_context_length()` and raises `ModelContextError` if the
  resolved model's context is below the minimum
- Call preflight check in `run_job()` after model resolution, before
  `AIAgent()` is instantiated
- In `_process_single_job()` inside `tick()`, catch `ModelContextError`
  and call `pause_job()` to auto-pause the offending job — it will no
  longer fire on every tick until the operator fixes the config
- Honour `model.context_length` in config.yaml as an explicit override
  that bypasses the check (operator accepts responsibility)
- If context detection itself fails (network/import error), log a warning
  and allow the job to proceed (fail-open) so detection gaps don't block
  otherwise-working jobs
- Fix pre-existing IndentationError in `tick()` result loop (missing
  `try:` block introduced in #353 parallel-execution refactor)
- Export `ModelContextError` and `CRON_MIN_CONTEXT_TOKENS` from `cron/__init__.py`
- Add 8 new tests covering all branches of `_check_model_context_compat`

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 09:41:17 -04:00