Conversation
ffacde9 to
7b96650
Compare
7b96650 to
64ac295
Compare
7e07f8b to
ce4e7cd
Compare
|
Ran into this exact issue today, would love to get this merged for sure |
…a session (NousResearch#28746) The _session_expiry_watcher sets expiry_finalized=True and evicts the cached agent when an idle session exceeds its reset policy, but it never called db.end_session() — so ended_at stayed NULL in SQLite indefinitely. GET /api/sessions filters on ended_at IS NULL to find live sessions, meaning external API clients kept treating a finalized session as live and injecting turns into a stale conversation that the gateway had already discarded internally. This surfaced when building on top of the session API extended in NousResearch#46165: polling /api/sessions by session_key suffix correctly resolves the most-recently-active session, but when the gateway has silently expired it via the watcher, the stale session still appears live — ended_at: null — causing clients to keep addressing it instead of letting Hermes create a fresh one on the next turn. Fix: after setting expiry_finalized=True, call session_store._db.end_session() with reason 'idle'. This matches the reason used by get_or_create_session on the next-message path so the two paths are consistent. The call is guarded by getattr so a missing _db (fallback JSONL mode) is a no-op. Add a regression test to test_session_boundary_hooks.py alongside the existing NousResearch#14981 test to verify the DB write happens on idle expiry.
44423c4 to
8e907b0
Compare
8e907b0 to
694529b
Compare
teknium1
left a comment
There was a problem hiding this comment.
Thanks for the focused API-surface fix. The current main implementation still strips these persisted routing fields in gateway/platforms/api_server.py:1681-1694, so the core allowlist change is needed.
Problems
website/docs/user-guide/features/api-server.md:339shows asessionsresponse member, while the API-server endpoint actually emits its rows underdataingateway/platforms/api_server.py:1754-1760. Please update the example envelope.
Suggested changes
- The new integration test in
tests/hermes_cli/test_web_server.py:670covers the dashboard's separate/api/sessionsroute, not the changed adapter serializer. Add an aiohttp route-level assertion intests/gateway/test_api_server.py; its_create_app()helper is at:618-634and can register_handle_list_sessions.
Automated hermes-sweeper review.
| # list sessions, including each session's gateway binding | ||
| curl "http://localhost:8642/api/sessions?limit=20" \ | ||
| -H "Authorization: Bearer $API_SERVER_KEY" | ||
| # → {"sessions": [ |
There was a problem hiding this comment.
The API-server handler returns list rows under data, not sessions (gateway/platforms/api_server.py:1754-1760). Please make this example match the actual response envelope.
| finally: | ||
| db.close() | ||
|
|
||
| rows = self.client.get("/api/sessions?limit=20&offset=0").json()["sessions"] |
There was a problem hiding this comment.
This exercises the dashboard FastAPI endpoint, which returns raw SessionDB rows and never invokes the changed APIServerAdapter._session_response. Please add the route-level API-server assertion in tests/gateway/test_api_server.py as well.
There was a problem hiding this comment.
Done, hopefully the right way. Thanks for pointing out
a6e4b5a to
2fd7d4a
Compare
|
Rebased on latest main state as well |
Add session_key, chat_id, chat_type, and thread_id to the client-safe field allowlist in _session_response, so external API consumers can map a live session to the specific chat/group/thread it originated from without inferring it from user_id alone (for Telegram, user_id is the sender's personal ID and is identical across DMs and group messages). The fields themselves are already persisted since the session-restart work in NousResearch#54442 and returned by list_sessions_rich — the API layer just stripped them. Sensitive snapshots (system_prompt, model_config) remain redacted to existence flags. Tests cover the allowlist at the _session_response unit level and through the aiohttp GET /api/sessions route (envelope + binding fields + NULL passthrough for rows without a gateway origin).
Describe the session_key / chat_id / chat_type / thread_id fields now
returned per session by GET /api/sessions, with a response example
matching the actual list envelope ({"object": "list", "data": [...]}),
and cross-link the inbound X-Hermes-Session-Key header that carries the
same routing key.
2fd7d4a to
98b416b
Compare
What does this PR do?
Exposes each session's gateway binding through
GET /api/sessions:session_key(the stable routing key, e.g.
agent:main:telegram:group:<chat_id>:<thread_id>)plus the structured
chat_id,chat_type, andthread_idfields.Since #54442 these fields are already persisted in the sessions table and
returned by
list_sessions_rich— but_session_responsestrips them, so APIconsumers still can't see them. This PR adds them to the client-safe allowlist
(one line);
system_prompt/model_configstay redacted to existence flags.All four fields are
nullfor sessions without a gateway origin.Why / what this is for
I'm building an external client that drives Hermes over its HTTP API. It needs
session persistency across restarts and must inject its turns into the right
live Telegram session — the one bound to a specific chat, group, or topic
thread.
user_idalone can't express that binding: it's the sender's personalID and is identical across DMs and group messages. With the gateway binding
exposed, the client can enumerate sessions over REST and deterministically
correlate each one to its channel instead of guessing.
Type of Change
Changes Made
gateway/platforms/api_server.py— addsession_key,chat_id,chat_type,thread_idto the_session_responsesafe-key allowlisttests/— allowlist unit tests + end-to-endGET /api/sessionstest(including
nullpassthrough for rows without a gateway origin)website/docs/user-guide/features/api-server.md— document the fieldsscripts/release.py— contributor email mapping for the attribution checkHow to Test
scripts/run_tests.sh tests/gateway/test_api_server_normalize.py tests/hermes_cli/test_web_server.pyGET /api/sessions— thebinding fields are present; CLI sessions return
null.Checklist
ruffclean