Skip to content

feat(runtime): add config-driven session_mode for agent triggers#2341

Merged
houko merged 4 commits into
librefang:mainfrom
neo-wanderer:claude/agent-session-triggers-lXO4h
Apr 13, 2026
Merged

feat(runtime): add config-driven session_mode for agent triggers#2341
houko merged 4 commits into
librefang:mainfrom
neo-wanderer:claude/agent-session-triggers-lXO4h

Conversation

@neo-wanderer

@neo-wanderer neo-wanderer commented Apr 12, 2026

Copy link
Copy Markdown
Contributor

Closes #2342.

Motivation

Agents invoked by automated sources (cron ticks, event triggers, agent_send) currently always reuse the agent's persistent session. This means context accumulates indefinitely across background invocations — fine for stateful assistants, but wrong for stateless jobs like "summarize today's emails" or "run a health check" where each invocation should start clean.

This PR adds a session_mode knob so agent authors can opt into fresh sessions per invocation without changing any existing behavior.

Changes

Types (librefang-types/src/agent.rs)

  • New SessionMode enum (Persistent | New) with #[serde(rename_all = "snake_case")], Default = Persistent.
  • New session_mode: SessionMode field on AgentManifest with #[serde(default)] — backward-compatible, existing TOML files parse unchanged.

Trigger engine (librefang-kernel/src/triggers.rs)

  • New session_mode: Option<SessionMode> on Trigger — per-trigger override, None inherits from the target agent's manifest.
  • New TriggerMatch struct replaces the old (AgentId, String) tuple returned by evaluate(), carrying agent_id, message, and session_mode_override through dispatch.
  • All existing tests updated from tuple destructuring to TriggerMatch field access.

Kernel dispatch (librefang-kernel/src/kernel.rs)

  • send_message_full gains a session_mode_override: Option<SessionMode> parameter (all existing call sites pass None).
  • New private send_message_with_session_mode — used exclusively by trigger dispatch to plumb per-trigger overrides without changing public API surface.
  • Session ID resolution in both send_message_full and execute_llm_agent:
    • Channel-derived sessions are unaffected — always deterministic per-channel.
    • Non-channel invocations resolve: per-trigger override > agent manifest session_mode > Persistent (default).
    • SessionMode::NewSessionId::new() (fresh UUID each time).
  • publish_event now iterates Vec<TriggerMatch> and calls send_message_with_session_mode.

Wizard (librefang-kernel/src/wizard.rs)

  • Setup wizard sets session_mode: SessionMode::default() on generated manifests.

Docs

  • AGENTS.md: documents the session_mode field and per-trigger overrides.
  • CHANGELOG.md: added under [Unreleased].
  • CLAUDE.md: architecture note on session resolution.

Scope

Intentionally does not:

  • Add API routes for toggling session_mode at runtime — it's a manifest/TOML field, changed by editing agent.toml and reloading.
  • Affect hands differently — hands share AgentManifest and the execution pipeline, so session_mode works automatically.
  • Touch channel-derived sessions — those remain deterministic per-channel regardless of session_mode.

Test plan

  • All existing trigger tests updated to TriggerMatch field access and pass
  • cargo clippy --workspace --all-targets -- -D warnings clean
  • cargo fmt --check clean
  • Create agent with session_mode = "new", verify each cron/trigger invocation starts a fresh session (new session ID in logs)
  • Create agent with default config, verify persistent session behavior unchanged
  • Register trigger with session_mode = "new" override on an agent that defaults to "persistent", verify override wins
  • Send message via channel (Telegram/Discord) to a session_mode = "new" agent, verify channel-derived session is used (not a fresh one)

Add SessionMode enum (persistent/new) to AgentManifest so agents can
control whether automated invocations (cron ticks, event triggers,
agent_send) reuse the persistent session or start fresh each time.

- Per-agent default via `session_mode` in agent.toml
- Per-trigger override via `Trigger.session_mode: Option<SessionMode>`
- Channel-derived sessions unaffected (always deterministic per-channel)
- Default is "persistent" (backward-compatible)
- Applies to hands automatically (shared pipeline)

Introduces TriggerMatch struct to plumb per-trigger session_mode
through trigger evaluation and dispatch.

https://claude.ai/code/session_01WLeiKnazjiRnjyFRQy9inY
@github-actions github-actions Bot added area/docs Documentation and guides area/kernel Core kernel (scheduling, RBAC, workflows) labels Apr 12, 2026
@github-actions
github-actions Bot requested review from houko and removed request for houko April 12, 2026 07:29
@neo-wanderer neo-wanderer changed the title feat: config-driven session_mode for agent triggers feat(runtime): add config-driven session_mode for agent triggers Apr 12, 2026
houko

This comment was marked as spam.

@houko houko left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Clean, backward-compatible change:

  • Resolution chain is correct: per-trigger override > manifest session_mode > Persistent default
  • Channel-derived sessions are properly excluded — session_mode only affects non-channel branches, so Telegram/Discord conversations are not disrupted
  • Session resolution logic in send_message_full and execute_llm_agent stays consistent; no half-updated path
  • TriggerMatch struct replaces the tuple cleanly, carrying the override all the way from evaluate() through dispatch
  • #[serde(default)] + Default = Persistent keeps existing TOML and call sites behaving exactly as before
  • Routing through a private send_message_with_session_mode instead of changing the public send_message signature keeps the API surface tight

Recommend running a live integration check post-merge to confirm fresh session IDs actually differ per invocation in the logs.

@houko
houko merged commit 85a60bf into librefang:main Apr 13, 2026
12 checks passed
DaBlitzStein pushed a commit to DaBlitzStein/librefang that referenced this pull request Apr 13, 2026
…hardening

Brings in:
- feat(channels): Telegram rich media, polls, interactive commands, channel_send tool (librefang#2356)
- feat(runtime): config-driven session_mode for agent triggers (librefang#2341)
- fix(agents): refuse direct DELETE on hand-spawned agents (librefang#2361)
- fix(channels): harden poll_options parsing and poll context cleanup (librefang#2364)

Conflicts resolved: bridge.rs (new trait methods), prompt_builder.rs
(9-param build_channel_section merging bot_username/group_members with
granted_tools), tool_runner.rs (cron-tz tests + poll_options tests).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/docs Documentation and guides area/kernel Core kernel (scheduling, RBAC, workflows)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: per-agent session_mode for automated invocations

3 participants