Commit graph

1272 commits

Author SHA1 Message Date
Teknium
df5700ebe3
feat(auxiliary): per-task reasoning_effort for auxiliary models (#64597)
Every auxiliary task block (vision, web_extract, compression,
title_generation, curator, background_review, moa_reference, ...) now
accepts a reasoning_effort shorthand:

  auxiliary:
    compression:
      reasoning_effort: low
    vision:
      reasoning_effort: none

_get_task_extra_body() folds it into extra_body.reasoning, which every
auxiliary wire already translates: chat.completions passes it through,
the Codex Responses adapter maps it to top-level reasoning/include, and
the Anthropic auxiliary adapter now forwards it into
build_anthropic_kwargs(reasoning_config=...) (previously hardcoded None).

An explicit extra_body.reasoning on the same task wins over the
shorthand. Invalid levels are ignored with a warning. Empty string
(the shipped default) is a no-op — zero behavior change.

Config: reasoning_effort added to all 16 auxiliary task blocks in
DEFAULT_CONFIG (no version bump — deep-merge handles new keys).
2026-07-14 14:07:43 -07:00
kshitijk4poor
4554fe128a fix(slack): complete agent view workspace routing 2026-07-14 13:58:36 -07:00
ScotterMonk
d9cdb81923 feat(config): support per-model reasoning_effort overrides
Add agent.reasoning_overrides dict to config.yaml. Users can now set
a reasoning_effort per model, overriding the global agent.reasoning_effort.

Example:
  agent:
    reasoning_effort: "medium"       # global default
    reasoning_overrides:
      "openrouter/anthropic/claude-opus-4.5": "xhigh"
      "openai/gpt-5": "low"
      "claude-sonnet-4.6": "high"    # bare model name also works

The helper is spelling-tolerant: override keys match regardless of
provider prefix or dots-vs-dashes normalization, so users can write
keys in any sensible form and they'll match.

Resolution priority:
1. Session-scoped /reasoning --session override (gateway only; unchanged)
2. Per-model override from agent.reasoning_overrides (spelling-tolerant)
3. Global agent.reasoning_effort (existing)
4. Provider default (unchanged)

Wired into:
- CLI startup (cli.py)
- Messaging gateway agent construction (gateway/run.py)
- Desktop/TUI _load_reasoning_config (tui_gateway/server.py)
- Cron job scheduler (cron/scheduler.py)
- /model mid-session switch (agent/agent_runtime_helpers.py)
  + _primary_runtime now tracks reasoning_config for correct fallback recovery
- Fallback activation (agent/chat_completion_helpers.py::try_activate_fallback)
  + Re-resolves reasoning_config for the fallback model (best-effort)

Closes #21256 (per-model reasoning_effort defaults).

Note: no hermes config set agent.reasoning_overrides.<model> support;
users edit the YAML directly. _set_nested splits on "." and would
corrupt model keys containing version dots.
2026-07-14 11:46:40 -07:00
kshitijk4poor
33dfb7e4ec docs: add Upstage Solar to provider docs (env vars, fallback table, --provider list) 2026-07-15 00:09:24 +05:30
HexLab98
55d826ccef fix(file-safety): distinguish safe-root write denial from credential blocks
Return actionable errors when HERMES_WRITE_SAFE_ROOT blocks a path instead of
labeling every denial as a protected credential file. Wire the helper through
write_file, patch, delete/move, and the Copilot ACP shim; sync docs examples.
2026-07-14 17:09:40 +05:30
HexLab98
1b5ceec2a0 docs: clarify write safety, HERMES_WRITE_SAFE_ROOT, and file-mutation verifier
Document that safe-root violations are hard-blocked (not approval-gated),
add a security guide section for write_file/patch guards, and link cron
and verifier docs so users trust the footer over agent summaries.
2026-07-14 17:09:40 +05:30
kshitijk4poor
52cafa6f8e follow-up: integrate agent nudge + dispatcher retry docs and tests
- Nudge text now warns that repeated protocol violations will block the
  task and require manual intervention, so the model understands the
  consequence of ignoring the nudge.
- Kanban docs restructured to clearly separate the two defense layers:
  agent-side prevention (nudge, from #64350) and dispatcher-side
  recovery (bounded retry, from this PR).
- Two new integration tests verifying the nudge mentions blocking and
  that the agent-side and dispatcher-side budgets are independent.
2026-07-14 16:47:33 +05:30
slow4cyl
3cd8feb63c docs(kanban): worker-lifecycle + events table reflect the bounded protocol-violation retry
Fold in kevinb361's suggested lifecycle wording (#61817 conceded in favor of
this PR) and update the second stale site his sweep didn't cover: the
task-events table still said the dispatcher 'auto-blocks immediately instead
of retrying'. Both now describe the violation-only streak: protocol_violation
fires on every violation (its payload marker feeds the budget), below-budget
runs return the task to ready, and gave_up + auto-block happen only when the
consecutive streak reaches _PROTOCOL_VIOLATION_FAILURE_LIMIT (default 3,
per-task max_retries overriding).
2026-07-14 16:47:33 +05:30
Teknium
89bd0fba90
feat(codex): redeem banked usage-limit resets via /usage reset (#64280)
OpenAI lets ChatGPT-plan Codex users bank rate-limit reset credits, but
until now they could only be redeemed from the Codex CLI/app or the
website. This wires the same backend API into Hermes:

- /usage on the openai-codex provider now shows "You have N resets
  banked - use /usage reset to activate" (parsed from the
  rate_limit_reset_credits field the /usage endpoint already returns).
- New /usage reset subcommand (CLI + gateway) redeems one banked
  credit via POST .../rate-limit-reset-credits/consume with a UUID
  idempotency key, mirroring codex-rs backend-client semantics
  (PathStyle /wham vs /api/codex, ChatGPT-Account-Id header,
  reset/nothing_to_reset/no_credit/already_redeemed outcomes).
- Guard: redemption is refused while no rate-limit window is fully
  exhausted, since a banked reset restores the FULL 5h + weekly
  allowance and spending it early wastes it. /usage reset --force
  overrides. Zero banked credits and non-codex providers are refused
  with clear messages; nothing_to_reset reports the credit was NOT
  spent.
- i18n: new gateway.usage.unknown_subcommand / reset_wrong_provider
  keys across all 16 locales; docs updated (cli.md, messaging index).

Tested with unit tests plus a real-socket E2E against a local fake
Codex backend exercising redeem/guard/force and the /usage hint.
2026-07-14 03:23:19 -07:00
teknium1
af250d8494 docs(delegation): clarify background lifetime 2026-07-13 07:28:21 -07:00
teknium1
d0e9a42cec fix(delegation): harden durable completion delivery 2026-07-13 07:28:21 -07:00
hellno
b03c94dbed fix(approval): emit observer hooks for smart verdicts 2026-07-13 02:00:09 -07:00
Teknium
8030b01a2a fix(kanban): harden durable artifact handoff 2026-07-12 23:09:41 -07:00
Teknium
7c19eb80a8 docs(dashboard): align approval mode guidance 2026-07-12 17:42:42 -07:00
teknium1
837077dfae fix(api): stop producers after run transport expires 2026-07-12 04:15:47 -07:00
brooklyn!
04c7d104a7
Merge pull request #61885 from embwl0x/agent/tui-secret-overlay-expiry
fix(tui): dismiss expired sensitive prompts
2026-07-12 05:50:24 -05:00
teknium1
51382ac244 fix(skills): bind bundles to exact files and origins 2026-07-12 02:59:27 -07:00
Teknium
7550c594ce
feat(reasoning): add max and ultra effort levels (#62650) 2026-07-12 00:26:49 -07:00
Teknium
62a76bd3d5
feat: make smart approvals the default (#62661) 2026-07-12 00:25:55 -07:00
teknium1
6142203bd7 fix(gateway): ground readiness in live runtime state 2026-07-11 08:42:21 -07:00
teknium1
31152ae108 fix(providers): align Fireworks integration with project policy 2026-07-11 05:43:35 -07:00
ethernet
5e849942c3 feat(dev): add isolated sandbox script for local dev
scripts/desktop-sandbox.sh runs a Hermes desktop instance in an isolated
sandbox — separate HERMES_HOME, separate Electron userData, and a
distinct
app name (HERMES_DESKTOP_APP_NAME) so it doesn't compete with the main
desktop instance's single-instance lock.

Two modes:
- Ephemeral (default): temp dir, cleaned up on exit
- --persistent: stored under .hermes-sandbox/ in the worktree git root,
  survives restarts for repeat testing

In the Nix devShell the script is available as 'sandbox'.

Also makes APP_NAME overridable via HERMES_DESKTOP_APP_NAME in main.ts —
app.setName() runs before requestSingleInstanceLock(), so the overridden
name changes the lock key. collectRelaunchEnv already preserves
HERMES_DESKTOP_* vars through self-update relaunches; test updated to
cover the new env var.
2026-07-10 18:51:49 -04:00
embwl0x
f618984c64 fix(tui): dismiss expired sensitive prompts 2026-07-10 14:45:43 -04:00
kshitijk4poor
a0032f5f92 fix(routing): preserve profile and delegation parity 2026-07-10 13:10:45 +05:30
Gille
3aeaf3755d fix(nous): forward provider routing through Portal 2026-07-10 13:10:45 +05:30
Teknium
aabfedcac0
docs(webhook): complete filters + route-scripts coverage across doc surfaces (#60983)
Follow-up to #60944 (webhook payload filters and route scripts):
- reference/cli-commands.md (en+zh): document the new --script option on
  'hermes webhook subscribe'
- zh-Hans user-guide webhooks.md: mirror the Payload Filters and Script
  Filters/Transforms sections plus the filters/script route properties
  (the salvage shipped English-only docs)
- hermes-agent skill webhooks reference: teach the agent the filters/
  script surface so agent-driven subscriptions can use them
2026-07-08 11:56:24 -07:00
Grace
0cf2e39c41 feat(gateway): add webhook payload filters 2026-07-08 08:10:55 -07:00
teknium1
db9e3e4ef9 docs(discord): troubleshoot silent fail-closed denials
Docs portion of PR #57067: 'bot connects but never replies' section
pointing at the gateway.log warning and the allowlist/policy knobs.

Co-authored-by: ooovenenoso <120500656+ooovenenoso@users.noreply.github.com>
2026-07-07 17:01:08 -07:00
Teknium
551f00109d
docs(sessions): unify export docs under one overview section (#60554)
Restructures the five parallel export sections into a single 'Export
Sessions' section: a format table (jsonl/md/qmd/html/trace + --only
user-prompts), one shared-filters paragraph covering all formats, and
per-format subsections nested beneath. EN + zh-Hans.
2026-07-07 16:29:27 -07:00
teknium1
a6203839b4 docs(mcp): document idle_timeout_seconds / max_lifetime_seconds recycle keys + handshake-bound note 2026-07-07 15:16:00 -07:00
Teknium
0e04d14209
feat(sessions): trace export + HF upload via 'sessions export --format trace' (#60507)
* feat(trace): upload sessions to HF Agent Trace Viewer

Salvage trace upload as a smaller CLI-first feature: deterministic Claude Code JSONL export, fail-closed redaction, lazy Hugging Face dependency, and no gateway slash-command wiring.

* chore(trace): drop external porting references from docstrings

Describe the trace-upload design in Hermes' own terms.

* feat(sessions): fold trace upload into 'sessions export --format trace'

Integrates the HF Agent Trace Viewer exporter (PR #36145) onto the
unified export surface instead of a separate 'hermes trace' subcommand:

- --format trace: Claude Code JSONL to stdout/file, or one
  <id>.trace.jsonl per session for filtered bulk export; defaults to
  the most recent session when no --session-id/filters given.
- --upload pushes to the user's private HF traces dataset (--public to
  opt out of private); reads HF_TOKEN with guided setup when missing.
- traces are secret-redacted by default (force mode); --no-redact opts
  out after review; redaction failure blocks export (fail closed).
- hermes_cli/trace.py + subcommands/trace.py removed; agent/trace_upload.py
  is the single engine. Docs EN + zh-Hans; 4 new CLI tests.
2026-07-07 15:12:49 -07:00
teknium1
5e51b123f3 feat(mem0): add self-hosted mode to the setup wizard
The salvaged SelfHostedBackend made self-hosted servers reachable via
mem0.json / MEM0_HOST, but the setup wizard still offered only Platform
and OSS — exactly the gap users hit (Discord report: 'At memory setup
there's only 2 options'). Adds a third wizard mode:

- interactive picker: Platform / Self-hosted server / Open Source
- non-interactive: hermes memory setup mem0 --mode selfhosted
  --host http://... [--api-key ...] [--dry-run]
- host -> mem0.json (behavioral), API key -> .env as MEM0_API_KEY
  (secret), optional key for AUTH_DISABLED servers
- best-effort reachability check against the server, non-fatal
- README + memory-providers docs updated with the wizard path
2026-07-07 14:07:16 -07:00
teknium1
f76899facf feat(sessions): wire html + prompt-only formats into 'sessions export'
Salvage follow-up integrating PR #30481 (@simplast) and PR #57683
(@catbearlove1-lang) into the unified export surface:

- --format html: standalone self-contained HTML transcript (single
  session or multi-session with sidebar), works with all shared filters
  and --redact; requires a file output path.
- --only user-prompts: prompt-only export (jsonl records or md sections)
  via the shared session_export renderer; the separate export-prompts
  subcommand from the original PR is subsumed by this flag.
- AUTHOR_MAP entries for both contributors; docs EN + zh-Hans.
2026-07-07 13:29:58 -07:00
kshitijk4poor
9a322726ae fix(mem0): prune dead get_all, wire rerank config default, warn on MEM0_HOST env override
Review follow-ups on the salvage:

- get_all() pruned from the ABC and all three backends: mem0_list (its
  only caller) was removed by the recall-tuning commit, leaving new,
  tested, unreachable code — including SelfHostedBackend's _MAX_TOP_K
  over-fetch workaround. Tests for it dropped; fake-class stubs remain
  harmlessly. (The #52921 true-total fix lives on in the PR history if
  a lister ever returns.)
- The persisted rerank config key was write-only (setup prompted for it,
  nothing read it). initialize() now parses it into _rerank_default and
  mem0_search uses it when the model doesn't pass rerank explicitly;
  per-call args still win. Guard test added.
- Platform-mode setup now warns when MEM0_HOST is set in the environment:
  the json host-clear can't help there (_load_config seeds host from the
  env var, docs tell users to put it in .env) — the user would silently
  keep routing to the self-hosted server.
- SelfHostedBackend: connect-level retries (httpx.HTTPTransport(retries=2))
  so a single transient blip doesn't count toward the provider breaker;
  transport now injectable and the test helper uses the real __init__
  instead of mirroring it via __new__.
- plugin.yaml description no longer leads with reranking (off by default,
  platform-only); docs em-dash typo fixed.
2026-07-08 01:57:11 +05:30
kartik-mem0
2a14205ff4 feat(mem0): self-hosted dashboard backend + recall tuning (salvage #55614)
Salvage of #55614 by @kartik-mem0 (mem0 maintainer). Adds a SelfHostedBackend
that talks to a self-hosted Mem0 Docker server over httpx (X-API-Key auth,
/search + /memories routes), gated behind `host`. Also folds in the mem0
research-team recall tuning that rides with it: rerank defaults to false across
all modes, the mem0_list tool is removed (5->4 tools), search guidance is
de-shouted, and self-hosted get_all reports the true stored total (#52921).

Supersedes the self-hosted portion of #52487 (@liuhao1024, first-submitted).

Closes #52478
Fixes #52921
2026-07-08 01:57:11 +05:30
kshitijk4poor
d43863f005 fix: widen stale circuit breaker to non-streaming path + all provider-swap resets
Review findings on the salvaged #60332 breaker, fixed as follow-ups:

- restore_primary_runtime() now resets the streak (third provider-swap
  path; without it a recovered primary was short-circuited before a
  single attempt and could never be re-proven healthy except via /model).
- interruptible_api_call (non-streaming) now carries the same breaker
  (guard at entry, bump on stale_call_kill, reset on success). Quiet-mode
  / subagent / headless sessions — the profile most like #58962's
  unattended 494-failure session — take this path and had the identical
  infinite stale-retry class.
- Partial-stream stub return now resets the streak (chunks were received,
  provider demonstrably responsive).
- Consolidated the triple-duplicated counter arithmetic into shared
  helpers (_stale_streak/_bump_stale_streak/_reset_stale_streak/
  _check_stale_giveup) with one canonical comment block; error message
  now says 'consecutive stale attempts' (the counter counts kills, not
  turns — a single turn can produce several).

4 new tests (restore resets / no-op restore keeps latch / non-streaming
short-circuit / non-streaming success reset).
2026-07-08 01:50:14 +05:30
kshitijk4poor
437052f039 docs: document HERMES_STREAM_STALE_GIVEUP alongside sibling stream knobs 2026-07-08 01:50:14 +05:30
teknium1
acfefa4fda feat(sessions): full prune-filter set + --redact on sessions export
- export now shares _add_session_filter_args / build_prune_filters with
  prune/archive: AGE grammar (5h/2d/1w/ISO) on --older-than plus the full
  filter set (--model, --provider, --min-messages, --min-cost, --branch,
  --chat-id, ...) for both JSONL and md/qmd bulk exports; --dry-run works
  on JSONL too; removes the one-off list_export_candidates helper.
- new --redact flag runs exported message content and tool output through
  force-mode secret redaction (agent.redact) for jsonl, md, and qmd.
- docs EN + zh-Hans updated; new tests for AGE grammar, extended filters,
  filtered JSONL, and redaction.
2026-07-07 12:36:41 -07:00
teknium1
f3c27e30eb refactor(sessions): fold Markdown/QMD export into 'sessions export --format'
Replaces the parallel export-md subcommand from the salvaged commit with
a --format jsonl|md|qmd flag on the existing export subcommand, so all
session export formats share one surface. Adds AUTHOR_MAP entry.

Salvage follow-up for PR #59542 by @web3blind.
2026-07-07 12:36:41 -07:00
web3blind
91885a32b3 feat(sessions): export sessions to markdown 2026-07-07 12:36:41 -07:00
Teknium
9c272a306e
feat(gateway): default session auto-reset to off (mode: none) (#60194)
Sessions no longer auto-reset by default. SessionResetPolicy.mode now
defaults to "none" (was "both": 24h idle + daily 4am), matching the
setup wizard's existing no-reset default and community feedback that
surprise context loss hurts more than it helps.

- gateway/config.py: dataclass default + from_dict fallback -> "none";
  installs whose config.yaml lacks a session_reset section stop
  auto-resetting
- hermes_cli/setup.py: "Never auto-reset" is now the recommended/default
  choice in hermes setup agent; stale comment updated
- docs (en + zh-Hans): default is no auto-reset, opt in via
  session_reset in config.yaml

Users who explicitly configured idle/daily/both resets keep them.
2026-07-07 05:11:10 -07:00
floit
2718179134 fix(docs): discord permissions (add Create Public Threads, remove Use External Emojis) 2026-07-07 04:09:09 -07:00
hmirin
d1c8c03416 feat(agent): add Codex-native compaction paths 2026-07-07 02:39:54 -07:00
Teknium
299d5c6603 fix(cli): safe mode also skips shell-hook registration
--safe-mode promised to disable ALL customizations, but shell hooks
declared in config.yaml's hooks: block registered anyway —
register_from_config() runs independently of plugin discovery and
load_config() does not honor HERMES_IGNORE_USER_CONFIG. Gate it on
HERMES_SAFE_MODE at the single chokepoint so troubleshooting runs fire
zero user-configured code (plugins, MCP, and hooks).

Docs (en + zh) updated; positive + negative tests added.
2026-07-07 02:32:32 -07:00
Sanidhya Singh
fff2408961 fix(agent): dedupe Codex gpt-5.5 autoraise notice across agent inits
The Codex gpt-5.5 compaction-threshold autoraise notice re-fired on every
agent init. Because the gateway rebuilds the agent per inbound message, the
notice spammed long-running Discord/Telegram/etc. sessions, and the only
documented remedy (`compression.codex_gpt55_autoraise false`) disables the
useful autoraise behavior itself.

Gate both emission surfaces — the CLI startup print and the gateway
`_compression_warning` replay — on a persisted per-profile marker under
`$HERMES_HOME` (`.codex_gpt55_autoraise_notice`), keyed on the from→to
percentages the notice displays. The notice now shows at most once per
profile; the autoraise still fires and `codex_gpt55_autoraise: false` still
disables it; and a later change to the raised threshold re-notifies once.
Docs updated to match.
2026-07-06 12:46:20 -07:00
Teknium
d4bcd93bb9
docs: browser provider plugin guide + complete the plugin routing map (#59817)
- New developer-guide/browser-provider-plugin.md: BrowserProvider ABC
  (session lifecycle, CDP contract, bb_session_id back-compat key,
  raise/never-raise split between create and close/cleanup),
  get_setup_schema() hermes-tools integration, discovery, checklist.
  Closes the one gap in the provider-plugin family — the ABC and
  ctx.register_browser_provider() existed with zero docs.
- Register the page in the Plugins sidebar subcategory.
- Extend the routing map on the Plugins landing page (both locales)
  with the previously missing rows: web-search, browser, secret-source,
  and dashboard-auth surfaces.
2026-07-06 12:43:03 -07:00
Teknium
b24ff550c3
docs: Plugins subcategory under Extending + secret-source plugin guide + 1Password sidebar fix (#59613)
* docs(secrets): secret-source plugin developer guide + sidebar registration for 1Password page

- New developer-guide/secret-source-plugin.md: SecretSource contract
  (never raises/prompts, fetch-only, timeout budget), framework-vs-plugin
  ownership table, mapped-vs-bulk shape guidance, run_secret_cli()
  subprocess-safety, registration + timing note, conformance kit usage,
  ErrorKind reference.
- Register user-guide/secrets/onepassword in the sidebar (page shipped
  in #59498 but was not listed, so it was unreachable from nav).
- Cross-link the user-guide plugin section to the new dev guide.

* docs: group all plugin guides under a Plugins subcategory in Extending

- Move guides/build-a-hermes-plugin.md -> developer-guide/plugins/index.md
  (both locales) and make it the category landing page (slug pinned to
  /developer-guide/plugins).
- New sidebar subcategory Developer Guide > Extending > Plugins holding
  the general guide + all 8 provider-plugin docs (llm-access, memory,
  context-engine, secret-source, model, image-gen, video-gen, web-search);
  provider-doc URLs unchanged.
- Client redirect /guides/build-a-hermes-plugin -> /developer-guide/plugins.
- Update 30 cross-links across both locales.
2026-07-06 12:11:31 -07:00
Taylor H. Perkins
8a76de962f fix(secrets): make 1Password bootstrap token reliable outside systemd
The 1Password secret source resolves op:// references using
OP_SERVICE_ACCOUNT_TOKEN read from os.environ. Under systemd the gateway
gets that token via EnvironmentFile, but cron jobs, subprocesses, CLI
runs, macOS launchd, and Docker containers spawn fresh interpreters with
no inherited shell state — so they silently failed to resolve any
reference and fell back to empty strings.

Two patches close the gap, matching Bitwarden's reliability guarantees:

1. env_loader: auto-load ~/.hermes/.op.env after .env so the gitignored
   bootstrap token is available everywhere. override=False plus an
   explicit guard ensure it never clobbers a token already in env (e.g.
   from a systemd EnvironmentFile, which keeps precedence).

2. credential_pool: _get_env_prefer_dotenv() now prefers the resolved
   value in os.environ when .env still holds a raw op:// reference,
   instead of handing a URL to provider auth. Non-op:// values keep the
   existing .env-takes-precedence behaviour.

Also gitignore .op.env, document the three bootstrap-token options, and
add tests covering auto-load, no-override, and the resolved-vs-raw
precedence (plus regression guards).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 04:58:07 -07:00
Taylor H. Perkins
5c4c0e9d9b feat(secrets): add 1Password (op://) secret source
Resolve provider credentials from 1Password op://vault/item/field references
at startup via the official `op` CLI, alongside the existing Bitwarden source.
Users map env-var names to references in secrets.onepassword.env; after .env
loads, each is resolved with `op read` and injected into os.environ. Auth is
whatever `op` already uses (service-account token or desktop/interactive
session) — Hermes never authenticates or installs `op` itself.

Startup-safe and fail-open: a missing binary, expired auth, a bad reference,
or an empty value each warn and fall back to existing credentials, never
blocking startup. Successful, complete pulls are cached in-process and on disk
(<hermes_home>/cache/op_cache.json, 0600) via the shared DiskCache; only
secret values are stored, never the token (auth is fingerprinted into the
key). Adds `hermes secrets onepassword {setup,status,set,remove,sync,disable}`
(aliases op/1password), config defaults, the cli-config example, docs, and
hermetic tests.

Hardening applied across both backends in env_loader: each source runs in its
own guard, config sections are coerced to dict, and cache_ttl_seconds is
coerced defensively — so a malformed secrets: section can't abort startup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 04:58:07 -07:00
teknium1
2d16ec7fb7 feat(secrets): pluggable SecretSource interface + multi-source orchestrator
Introduces a first-class secret-source contract so password managers
(Bitwarden today, 1Password next, third-party vaults as plugins) plug
into one orchestrated startup path instead of each hardcoding into
env_loader.

- agent/secret_sources/base.py: SecretSource ABC (fetch-only contract:
  never raises, never prompts, sync with orchestrator-enforced timeout),
  shared ErrorKind taxonomy, FetchResult, run_secret_cli() minimal-env
  subprocess helper, API versioning for plugin compatibility.
- agent/secret_sources/registry.py: registration gating (name/scheme
  uniqueness, api_version, shape), apply_all() orchestrator owning
  precedence (mapped-beats-bulk, first-claim-wins, override_existing
  never crosses sources, protected bootstrap tokens), conflict warnings,
  per-var provenance, per-source wall-clock timeout.
- Bitwarden converted to a registered BitwardenSource (bulk shape);
  behavior unchanged, apply_bitwarden_secrets kept as legacy shim.
- env_loader._apply_external_secret_sources now drives the orchestrator;
  provenance labels resolve through registry (e.g. '(from 1Password)').
- PluginContext.register_secret_source() for external backends.
- secrets.sources optional ordering key in DEFAULT_CONFIG + example.
- tests/secret_sources/: 47 new tests incl. reusable conformance kit
  (SecretSourceConformance) that plugin authors run against their source.
2026-07-06 04:58:07 -07:00