Summary
The channel-side /new command (Telegram, Discord, Slack, etc.) currently calls the kernel's reset_session(agent_id), which deletes every session for that agent — the default session plus every per-channel session and any cron/trigger-spawned ones — and creates one fresh session in its place.
For users who run an agent across multiple surfaces (e.g., dashboard + Telegram + Discord on the same hand), typing /new in any one channel silently destroys the in-flight history on all the others.
Repro
- Same agent reachable from the dashboard and from Telegram.
- Have an active session on each (different
SessionIds — one is SessionId::for_channel(agent,"telegram:<chat>"), the other is the default).
- Send
/new in Telegram.
- Observe: dashboard session row is gone (
GET /api/sessions/<dashboard-sid> → 404).
Code path
crates/librefang-channels/src/bridge.rs:4730 — "new" command handler calls handle.reset_session(aid) with no scope.
crates/librefang-kernel/src/kernel/session_ops.rs:168 — reset_session(agent_id):
- line 176:
get_agent_session_ids(agent_id) — iterates all sessions
- line 199-202:
delete_agent_sessions(agent_id) — transactional delete of every session row for that agent
- The comment at line 173-174 documents the agent-wide intent: "Auto-save session summaries for ALL sessions (default + per-channel) before clearing, so no channel's conversation history is silently lost."
Summaries are saved to memory before the delete, but the full transcripts are unrecoverable.
Expected
/new invoked from a channel should reset only that channel's SessionId::for_channel(agent, "<channel>:<chat>"). Other channels and the dashboard session should be untouched.
The agent-wide reset is still useful as a separate explicit operation (e.g., dashboard "Reset agent" button, POST /api/agents/{id}/reset), but the channel /new user-mental-model is "start a fresh chat here", not "wipe everything across every surface."
Proposed change
-
Add an optional scope to the kernel API:
pub fn reset_session(&self, agent_id: AgentId, scope: ResetScope) -> KernelResult<()>
// where
pub enum ResetScope { Agent, Session(SessionId) }
ResetScope::Session(sid) saves a summary for that one session, deletes that one row, creates a fresh per-channel session keyed the same way (so the channel's next message lands on the new one), and leaves all other sessions intact.
-
In bridge.rs:4730, derive the channel-scoped SessionId the same way inbound channel messages do (SessionId::for_channel(agent, "<channel>:<chat>")) and pass ResetScope::Session(sid) to the kernel.
-
Keep the agent-wide variant for the dashboard / explicit "reset agent" path. /reboot and compact likely want the same scoping treatment — currently they share the agent-wide footgun.
Backward compatibility
The HTTP route POST /api/agents/{id}/reset and the dashboard reset button can keep agent-wide semantics by passing ResetScope::Agent. Only the channel command handler changes user-visible behavior, which is the bug.
Notes
- Confirmed on
main (commit visible in librefang/crates/librefang-kernel/src/kernel/session_ops.rs:168).
- Workaround for now: don't use
/new in a channel if you have other live sessions on the same agent. Pull session summaries from the agent's memory store if you need to reconstruct what was deleted.
Summary
The channel-side
/newcommand (Telegram, Discord, Slack, etc.) currently calls the kernel'sreset_session(agent_id), which deletes every session for that agent — the default session plus every per-channel session and any cron/trigger-spawned ones — and creates one fresh session in its place.For users who run an agent across multiple surfaces (e.g., dashboard + Telegram + Discord on the same hand), typing
/newin any one channel silently destroys the in-flight history on all the others.Repro
SessionIds — one isSessionId::for_channel(agent,"telegram:<chat>"), the other is the default)./newin Telegram.GET /api/sessions/<dashboard-sid>→ 404).Code path
crates/librefang-channels/src/bridge.rs:4730—"new"command handler callshandle.reset_session(aid)with no scope.crates/librefang-kernel/src/kernel/session_ops.rs:168—reset_session(agent_id):get_agent_session_ids(agent_id)— iterates all sessionsdelete_agent_sessions(agent_id)— transactional delete of every session row for that agentSummaries are saved to memory before the delete, but the full transcripts are unrecoverable.
Expected
/newinvoked from a channel should reset only that channel'sSessionId::for_channel(agent, "<channel>:<chat>"). Other channels and the dashboard session should be untouched.The agent-wide reset is still useful as a separate explicit operation (e.g., dashboard "Reset agent" button,
POST /api/agents/{id}/reset), but the channel/newuser-mental-model is "start a fresh chat here", not "wipe everything across every surface."Proposed change
Add an optional scope to the kernel API:
ResetScope::Session(sid)saves a summary for that one session, deletes that one row, creates a fresh per-channel session keyed the same way (so the channel's next message lands on the new one), and leaves all other sessions intact.In
bridge.rs:4730, derive the channel-scopedSessionIdthe same way inbound channel messages do (SessionId::for_channel(agent, "<channel>:<chat>")) and passResetScope::Session(sid)to the kernel.Keep the agent-wide variant for the dashboard / explicit "reset agent" path.
/rebootandcompactlikely want the same scoping treatment — currently they share the agent-wide footgun.Backward compatibility
The HTTP route
POST /api/agents/{id}/resetand the dashboard reset button can keep agent-wide semantics by passingResetScope::Agent. Only the channel command handler changes user-visible behavior, which is the bug.Notes
main(commit visible inlibrefang/crates/librefang-kernel/src/kernel/session_ops.rs:168)./newin a channel if you have other live sessions on the same agent. Pull session summaries from the agent's memory store if you need to reconstruct what was deleted.