hermes-agent/gateway
Teknium 5854aad8b5
feat(gateway): durable delivery-obligation ledger for final responses (#67181)
A final response generated but not confirmed-delivered was the one
artifact the gateway could lose without a trace: crash or planned
restart between finalize and platform ACK dropped it silently, and the
resume path re-ran the whole turn at full cost (#58818 P1, #41696,
#63695's gateway half).

gateway/delivery_ledger.py records each outbound final response in
state.db (same conventions as the async-delegation ledger: WAL, owner
pid + process-start-time liveness, bounded retention):

  pending -> attempting -> delivered | failed
  startup sweep on dead-owner rows -> redeliver | abandoned

Contract (the lessons from the closed delivery-outbox attempt #61790):
- obligation recorded BEFORE the first send attempt; cleared only on
  SendResult.success (destination acceptance, #51184)
- ambiguity is labeled, never silently retried: rows that were mid-send
  when the process died redeliver with a visible '♻️ Recovered reply —
  may be a duplicate' prefix (honest at-least-once)
- stable ids from session_key + inbound message id + content, so
  distinct threads/topics can never collide
- poison rows bounded: 3 attempts / 24h freshness -> abandoned; claim
  atomically re-stamps ownership so racing sweeps can't double-claim
- redelivery clears resume_pending for the session so the resume path
  never re-runs a turn whose answer the ledger already holds
- best-effort everywhere: ledger failure can never block or delay a send
- slash-command/ephemeral/empty responses are not recorded; cron and
  proactive delivery stay on DeliveryRouter (separate subsystem)

Config: gateway.delivery_ledger (default on; no version bump needed).

Validation: 30 ledger+producer tests; 352 blast-radius gateway tests
green; cross-process E2E (record in process A, kill it mid-send, claim
+ marker + redeliver in a fresh process B against the same state.db).
2026-07-19 00:45:32 -07:00
..
assets chore: limit generic status phrases to long-running notifications 2026-07-05 06:29:26 -07:00
builtin_hooks remove: BOOT.md built-in hook (#17093) 2026-04-28 09:50:27 -07:00
platforms feat(gateway): durable delivery-obligation ledger for final responses (#67181) 2026-07-19 00:45:32 -07:00
relay fix(relay): normalize a 0/negative max_message_length at the descriptor boundary 2026-07-16 07:21:23 -07:00
__init__.py docs(gateway): mention Weixin in gateway help and docstrings 2026-05-12 17:08:51 -07:00
authz_mixin.py fix(gateway): multiplex secret_scope for authz, Slack, webhooks 2026-07-16 05:39:58 -07:00
cgroup_cleanup.py fix: satisfy ruff encoding + windows-footgun lints for cgroup reaper 2026-06-28 02:05:50 -07:00
channel_directory.py fix(gateway): offload channel directory session scans 2026-07-09 06:27:04 -07:00
code_skew.py fix(gateway): refuse model switch on stale checkout to avoid env_float ImportError 2026-06-24 04:16:54 +05:30
config.py feat(gateway): live per-tool status line on Slack 2026-07-18 12:28:59 -07:00
cwd_placeholder.py fix(gateway): resolve terminal.cwd placeholders per backend and mount mode 2026-07-04 13:28:47 -07:00
dead_targets.py fix(gateway): skip confirmed-dead delivery targets (deleted groups, blocked bots) (#55115) 2026-06-29 13:23:29 -07:00
delivery.py refactor(gateway): share error-text blob between not_found classifiers 2026-07-01 15:11:38 +05:30
delivery_ledger.py feat(gateway): durable delivery-obligation ledger for final responses (#67181) 2026-07-19 00:45:32 -07:00
display_config.py feat(gateway): live per-tool status line on Slack 2026-07-18 12:28:59 -07:00
drain_control.py feat(gateway): suppress home-channel shutdown broadcast on flagged drains (#54824) 2026-06-29 12:18:11 -07:00
hooks.py feat(hooks): expose thread_id and chat_type in agent:start/end context (#41672) 2026-06-07 19:16:36 -07:00
kanban_watchers.py fix(kanban): route notifier wake via profile chokepoint; harden review findings 2026-07-02 00:05:48 +05:30
memory_monitor.py Port from cline/cline#10343: periodic gateway memory logging (#27102) 2026-05-16 12:55:23 -07:00
message_timestamps.py feat(gateway): inject stable human-readable message timestamps 2026-06-16 15:49:59 -07:00
mirror.py refactor: consolidate gateway session metadata into state.db (#58899) 2026-07-05 14:01:03 -07:00
pairing.py fix: merge split gateway pairing stores 2026-07-07 02:18:17 -07:00
platform_registry.py perf(startup): lazy-load gateway platform adapters (#54448) 2026-06-28 15:11:59 -07:00
profile_routing.py fix(gateway): profile routing — conjunctive matching + universal gateway_runner 2026-07-15 09:50:05 -07:00
readiness.py fix(gateway): ground readiness in live runtime state 2026-07-11 08:42:21 -07:00
response_filters.py fix(gateway): tolerate punctuation on silence markers 2026-07-05 02:12:26 -07:00
restart.py fix(gateway): preserve external supervisor ownership 2026-07-16 05:08:56 -07:00
restart_loop_guard.py fix(gateway,cron): guard cron model-tool path + add auto-resume loop breaker (#30719) 2026-07-01 02:48:36 -07:00
rich_sent_store.py style(profile): frame comments around what the code does 2026-06-30 15:30:06 -07:00
run.py feat(gateway): durable delivery-obligation ledger for final responses (#67181) 2026-07-19 00:45:32 -07:00
runtime_footer.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
scale_to_zero.py feat(gateway): scale-to-zero idle detection + dormant-quiesce (Phase 0) 2026-06-24 18:47:18 -07:00
session.py refactor: shared helpers for api_content sidecar pop/drop/extract 2026-07-19 08:25:35 +05:30
session_context.py fix(delegate): declare stateless channel in one-shot and cron so delegate_task returns results 2026-07-18 00:05:25 -07:00
shutdown_forensics.py chore: ruff auto-fixes — collapsible-else-if, if-stmt-min-max, dict.fromkeys (#23926) 2026-05-11 11:03:29 -07:00
shutdown_watchdog.py fix: drain logs + release PID/lock before watchdog os._exit, drop infographic PNG 2026-07-18 19:53:00 +05:30
slash_access.py feat(gateway): per-platform admin/user split for slash commands (salvage of #4443) (#23373) 2026-05-10 12:33:54 -07:00
slash_commands.py refactor: shared helpers for api_content sidecar pop/drop/extract 2026-07-19 08:25:35 +05:30
status.py fix(gateway): launchd ThrottleInterval + portable respawn-storm circuit breaker 2026-07-18 19:38:21 +05:30
status_phrases.py chore: limit generic status phrases to long-running notifications 2026-07-05 06:29:26 -07:00
sticker_cache.py fix: guard yaml.safe_load, flock unlock, TOCTOU races, and atomic writes 2026-05-19 00:12:41 -07:00
stream_consumer.py fix(discord): persist streamed final delivery 2026-07-18 14:01:33 -07:00
stream_dispatch.py feat(gateway): structured stream-event protocol + Telegram draft formatting parity (#37250) 2026-06-02 00:33:50 -07:00
stream_events.py feat(gateway): structured stream-event protocol + Telegram draft formatting parity (#37250) 2026-06-02 00:33:50 -07:00
systemd_notify.py fix(gateway): recover Discord websocket and event-loop stalls 2026-07-18 20:01:55 +05:30
whatsapp_identity.py fix(whatsapp): resolve LID aliases on modern platforms/ session layout 2026-06-28 02:05:26 -07:00