Skip to content

Add SessionsManager and make ChatObject session-aware#4

Merged
JohnRichard4096 merged 12 commits intomainfrom
feat/sessions-manage
Feb 10, 2026
Merged

Add SessionsManager and make ChatObject session-aware#4
JohnRichard4096 merged 12 commits intomainfrom
feat/sessions-manage

Conversation

@JohnRichard4096
Copy link
Member

@JohnRichard4096 JohnRichard4096 commented Feb 10, 2026

Summary by Sourcery

Introduce session-aware architecture with a SessionsManager and per-session tool/preset/config management, update ChatObject and related components to use session data, and add comprehensive tests and documentation for the new session isolation model.

New Features:

  • Add SessionsManager and SessionData abstractions to manage per-session memory, tools, presets, MCP clients, and configuration.
  • Make ChatObject session-aware, allowing it to derive memory, config, and presets from session data and optionally auto-create sessions.
  • Provide a new demo showcasing session-isolated conversations using SessionsManager.

Enhancements:

  • Refactor tools and preset managers into reusable multi-instance bases (MultiToolsManager, MultiPresetManager) with singleton wrappers for global usage.
  • Update built-in OpenAI adapter to respect model-level configuration (temperature, top_p) from ModelPreset.
  • Improve ChatObject lifecycle handling, including response storage and recommended context manager usage in examples.
  • Enhance MCP client management with a MultiClientManager that integrates with the multi-tools manager and keeps mappings consistent.
  • Add ResponseWrapper base class for future response abstraction support.
  • Refine defaults and validation in ModelConfig and SendMessageWrap, and add clarifying docstrings across chat utilities.

Build:

  • Add pytest-asyncio as a development dependency to support asynchronous tests.

CI:

  • Update GitHub Actions CI workflow to run the pytest test suite with uv before linting.

Documentation:

  • Extend security mechanism docs (EN/zh) with detailed SessionsManager-based session isolation patterns and examples.
  • Update getting-started, concepts management, appendix, builtins, and introduction docs (EN/zh) to reflect session isolation, ChatObject usage patterns, and updated terminology.
  • Simplify README documentation section to point to the hosted docs site and fix paths/formatting in CONTRIBUTING and project structure docs.
  • Add a new demo script demonstrating session isolation with multiple concurrent sessions.

Tests:

  • Add extensive unit and integration tests for ChatObject, ChatManager, SessionsManager, tools manager, types, and overall session-based workflows.
  • Introduce pytest configuration, async test setup, and mark conventions, and wire tests into CI to run on every push.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 10, 2026

Reviewer's Guide

Introduce a SessionsManager-based session isolation system, refactor tools/presets/MCP managers to support per-session instances while keeping singletons, wire sessions into ChatObject and agent tool-calling, add extensive tests and examples for sessions and core types, and enhance docs, CI, and configuration around testing and streaming usage.

Sequence diagram for ChatObject creation with SessionsManager session isolation

sequenceDiagram
    actor User
    participant App as ApplicationCode
    participant SM as SessionsManager
    participant SD as SessionData
    participant CO as ChatObject
    participant PM as PresetManager

    User->>App: Provide session_id and user_input

    App->>SM: is_session_registered(session_id)
    alt session not registered and auto_create_session
        App->>SM: init_session(session_id)
    end

    App->>SM: get_session_data(session_id, default=None)
    alt Session exists
        SM-->>App: SessionData
        App->>SD: read memory
        App->>SD: read config
        App->>SD: presets.get_default_preset()
        SD-->>App: Memory, AmritaConfig, ModelPreset
        App->>CO: __init__(train, user_input, context=None, session_id, config=session.config, preset=session.presets.get_default_preset(), auto_create_session=True)
        CO->>SM: get_session_data(session_id)
        SM-->>CO: SessionData
        CO->>SD: read memory
        SD-->>CO: MemoryModel
        CO-->>App: ChatObject instance
    else Session not found and no auto_create_session
        SM-->>App: None
        App->>PM: get_default_preset()
        PM-->>App: ModelPreset
        App->>CO: __init__(train, user_input, context=Memory, session_id, config=get_config(), preset=default_preset)
        CO-->>App: ChatObject instance
    end

    App->>CO: begin() as async context manager
    CO-->>User: Streamed responses via get_response_generator()
Loading

Class diagram for SessionsManager-based session isolation and managers

classDiagram
    direction LR

    class SessionData {
        +str session_id
        +MemoryModel memory
        +MultiToolsManager tools
        +MultiPresetManager presets
        +ClientManager mcp
        +AmritaConfig config
    }

    class SessionsManager {
        -dict~str, SessionData~ _session2DataMap
        -SessionsManager _instance
        -object __marker
        +is_session_registered(session_id: str) bool
        +get_registered_sessions() dict~str, SessionData~
        +init_session(session_id: str) void
        +set_session(data: SessionData) void
        +get_session_data(session_id: str) SessionData
        +get_session_data(session_id: str, default: T) SessionData | T
        +new_session() str
        +drop_session(session_id: str) void
    }

    class MultiToolsManager {
        -dict~str, ToolData~ _models
        -set~str~ _disabled_tools
        -bool _inited
        +has_tool(name: str) bool
        +register_tool(tool: ToolData) void
        +remove_tool(name: str) void
        +get_tool(name: str) ToolData | None
        +tools_meta_dict() dict~str, dict~str, any~~
        +disable_tool(name: str) void
        +enable_tool(name: str) void
        +get_disabled_tools() list~str~
    }

    class ToolsManager {
        -ToolsManager _instance
        -bool _initialized
    }

    class MultiPresetManager {
        -ModelPreset | None _default_preset
        -dict~str, ModelPreset~ _presets
        -bool _inited
        +set_default_preset(preset: ModelPreset | str) void
        +get_default_preset() ModelPreset
        +add_preset(preset: ModelPreset) void
        +get_preset(name: str) ModelPreset | None
        +list_presets() list~ModelPreset~
        +test_single_preset(preset: ModelPreset) PresetReport
        +test_presets() PresetReport* (async generator)
    }

    class PresetManager {
        -PresetManager _instance
        -bool _initialized
    }

    class MultiClientManager {
        +list~MCPClient~ clients
        +dict~str, MCPClient~ script_to_clients
        +dict~str, MCPClient~ name_to_clients
        +dict~str, str~ tools_remapping
        +dict~str, str~ reversed_remappings
        +MultiToolsManager tools_manager
        +Lock _lock
        +bool _is_initialized
        +bool _initted
        +get_client_by_script(server_script: MCP_SERVER_SCRIPT_TYPE) MCPClient
        +get_client_by_name(name: str) MCPClient | None
        +register_only(server_script: MCP_SERVER_SCRIPT_TYPE, client: MCPClient) MultiClientManager
        +update_tools(client: MCPClient) void
        +initialize_scripts_all(scripts: Iterable~MCP_SERVER_SCRIPT_TYPE~) void
        +initialize_all(lock: bool) void
        +unregister_client(script_name: str | Path, lock: bool) void
        -_load_this(client: MCPClient, fail_then_raise: bool) void
        -_tools_wrapper(origin_name: str) function
    }

    class ClientManager {
        -ClientManager _instance
        -bool _initialized
    }

    class ChatObject {
        -dict~str, str~ train
        -Memory data
        -str session_id
        -USER_INPUT user_input
        -Message user_message
        -AmritaConfig config
        -ModelPreset preset
        -datetime timestamp
        -datetime time
        -datetime last_call
        -asyncio.Queue _response_queue
        -asyncio.Queue _response_overflow_queue
        -bool _queue_done
        -UniResponse response
        +__init__(train: dict~str, str~, user_input: USER_INPUT, context: Memory | None, session_id: str, callback: RESPONSE_CALLBACK_TYPE | None, config: AmritaConfig | None, preset: ModelPreset | None, auto_create_session: bool, queue_size: int, overflow_queue_size: int)
        +begin() async context manager
        +get_response_generator() AsyncGenerator~str | ResponseWrapper | UniResponse[str, None], None~
        +yield_response(message: MessageWithMetadata) void
        +set_queue_done() void
        +terminate() void
    }

    class AgentCorePrehook {
        +agent_core(event: PreCompletionEvent, config: AmritaConfig) void
    }

    class ToolsUsageInAgent {
        -str agent_last_step
        -SessionsManager session_manager
        -MultiToolsManager tools_manager
        +run_tools(...)
    }

    SessionsManager "1" o-- "*" SessionData : manages
    SessionData "1" o-- "1" MultiToolsManager : tools
    SessionData "1" o-- "1" MultiPresetManager : presets
    SessionData "1" o-- "1" ClientManager : mcp
    SessionData "1" o-- "1" AmritaConfig : config
    SessionData "1" o-- "1" MemoryModel : memory

    ToolsManager --|> MultiToolsManager
    PresetManager --|> MultiPresetManager
    ClientManager --|> MultiClientManager

    ChatObject ..> SessionsManager : uses
    ChatObject ..> SessionData : reads config, memory, presets

    AgentCorePrehook ..> SessionsManager : get_session_data
    AgentCorePrehook ..> MultiToolsManager : per-session tools_manager
    ToolsUsageInAgent ..> ToolsManager : previous global usage
    ToolsUsageInAgent ..> MultiToolsManager : new per-session usage
Loading

File-Level Changes

Change Details Files
Introduce SessionsManager and SessionData to manage per-session memory, tools, presets, MCP clients, and config, and expose session utilities at the package level.
  • Add SessionData dataclass encapsulating memory, tools, presets, MCP client, and config for a session.
  • Implement SessionsManager singleton with APIs for creating, initializing, querying, and dropping sessions and cleaning up associated ChatObjects.
  • Export SessionsManager from the top-level package and add a load_session helper that initializes a session and logs its loading.
src/amrita_core/sessions.py
src/amrita_core/__init__.py
src/amrita_core/chatmanager.py
src/amrita_core/builtins/agent.py
demo/basic_with_sessions.py
docs/guide/concepts/management.md
docs/guide/security-mechanisms.md
docs/zh/guide/security-mechanisms.md
Refactor tools, presets, and MCP client management to support multi-instance managers with a singleton facade and integrate them with sessions.
  • Split ToolsManager into a reusable MultiToolsManager plus a singleton ToolsManager subclass; adjust tests and MCP client code to use injected MultiToolsManager.
  • Split PresetManager into MultiPresetManager and a singleton PresetManager subclass; update code that uses presets to work with the new types.
  • Refactor MCP ClientManager into MultiClientManager with dependency injection for tools manager, then add a singleton ClientManager wrapper and update tool registration/unregistration to use the per-instance tools manager.
src/amrita_core/tools/manager.py
src/amrita_core/preset.py
src/amrita_core/tools/mcp.py
src/amrita_core/sessions.py
tests/test_tools.py
tests/test_sessions.py
tests/test_integration.py
Wire SessionsManager into ChatObject lifecycle and agent tool-calling to use per-session memory, config, and presets, and simplify ChatManager state.
  • Extend ChatObject constructor to accept optional context and auto_create_session flag; if enabled, initialize missing sessions and pull memory/config/presets from SessionsManager.
  • Ensure ChatObject uses session-specific config and presets by default and stores the final UniResponse on completion.
  • Use SessionsManager in the built-in agent prehook to select the appropriate tools manager for the session instead of the global ToolsManager.
  • Trim unused fields from ChatManager (remove custom_menu and running_messages_poke) and rely on running_chat_object tracking.
src/amrita_core/chatmanager.py
src/amrita_core/builtins/agent.py
src/amrita_core/__init__.py
src/amrita_core/sessions.py
demo/basic_with_sessions.py
tests/test_chatmanager.py
tests/test_integration.py
Improve model adapter and core chat utilities with better configuration usage and documentation, and adjust model configuration defaults.
  • Update OpenAIAdapter to use ModelPreset.config (ModelConfig) for temperature and top_p in both streaming and non-streaming calls for completions and tools.
  • Adjust ModelConfig defaults (top_p, temperature) and docstrings, and remove the thought_chain_model flag.
  • Add detailed docstrings to libchat helpers (text_generator, get_tokens, _validate_msg_list, _call_with_reflection, tools_caller, call_completion, get_last_response), and tweak SendMessageWrap to avoid popping memory when no user_query is provided.
src/amrita_core/builtins/adapter.py
src/amrita_core/types.py
src/amrita_core/libchat.py
src/amrita_core/protocol.py
Expand documentation and demos to emphasize SessionsManager usage, streaming via async context managers, and updated terminology.
  • Add English and Chinese documentation sections describing SessionsManager, its API, and usage patterns for session isolation and secure conversations.
  • Update security mechanism examples to construct ChatObject with session-specific memory/config, use async context managers for begin(), and rely on SessionsManager for session creation and validation.
  • Introduce a demo script illustrating session-isolated conversations and advanced session management, and adjust existing basic examples to prefer async with chat.begin().
  • Update various Chinese docs to use the term “智能体” instead of “智能Agent” and clarify documentation structure and references.
docs/guide/security-mechanisms.md
docs/zh/guide/security-mechanisms.md
docs/guide/concepts/management.md
docs/guide/function-implementation.md
docs/zh/guide/getting-started/basic-example.md
docs/zh/guide/builtins.md
docs/zh/guide/introduction/index.md
docs/zh/guide/appendix.md
README.md
CONTRIBUTING.md
demo/basic.py
demo/basic_with_sessions.py
Strengthen testing and CI by adding pytest configuration, async test handling, and comprehensive tests for sessions, chat manager, types, and tools.
  • Add pytest.ini with async settings, strict markers, and coverage configuration, plus a conftest.py that sets up the project path and initializes AmritaCore for async tests.
  • Introduce unit and integration tests covering ChatObject lifecycle, MemoryLimiter behavior, ChatManager object management, concurrent sessions/ChatObjects, SessionsManager behavior, ModelConfig/ModelPreset/Message/SendMessageWrap/UniResponse/MemoryModel, and MultiToolsManager basics.
  • Update CI workflow to run the pytest suite and add pytest-asyncio as a dev dependency in pyproject.toml and uv.lock.
pytest.ini
tests/conftest.py
tests/test_chatmanager.py
tests/test_integration.py
tests/test_sessions.py
tests/test_types.py
tests/test_tools.py
.github/workflows/CI.yml
pyproject.toml
uv.lock

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 9 issues, and left some high level feedback:

  • In SendMessageWrap.__init__ you reference user_query in if not user_query: but the parameter is named query, which will raise a NameError; this should be corrected to use the actual argument name (or a clearer condition) before release.
  • The new Multi* manager classes (MultiToolsManager, MultiPresetManager, MultiClientManager) still guard initialization with a class-level _inited flag but store state on instances, so subsequent instances will skip initialization and may lack attributes like _models/clients; consider removing the shared _inited flag or making the classes explicitly singleton to avoid partially initialized instances.
  • In SessionsManager.drop_session you index chat_manager.running_chat_object[session_id] directly, which will throw a KeyError if the session has no running chat objects; using .get(session_id, []) or an explicit in check would make session cleanup more robust.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `SendMessageWrap.__init__` you reference `user_query` in `if not user_query:` but the parameter is named `query`, which will raise a `NameError`; this should be corrected to use the actual argument name (or a clearer condition) before release.
- The new `Multi*` manager classes (`MultiToolsManager`, `MultiPresetManager`, `MultiClientManager`) still guard initialization with a class-level `_inited` flag but store state on instances, so subsequent instances will skip initialization and may lack attributes like `_models`/`clients`; consider removing the shared `_inited` flag or making the classes explicitly singleton to avoid partially initialized instances.
- In `SessionsManager.drop_session` you index `chat_manager.running_chat_object[session_id]` directly, which will throw a `KeyError` if the session has no running chat objects; using `.get(session_id, [])` or an explicit `in` check would make session cleanup more robust.

## Individual Comments

### Comment 1
<location> `src/amrita_core/types.py:226-227` </location>
<code_context>
             raise ValueError("Invalid query message, expecting user message!")
         self.user_query = query
-        self.memory.pop()
+        if not user_query:
+            self.memory.pop()

     @classmethod
</code_context>

<issue_to_address>
**issue (bug_risk):** Undefined variable `user_query` and likely inverted condition in `SendMessageWrap.__init__`.

In `SendMessageWrap.__init__`, `if not user_query:` will raise a `NameError` because `user_query` isn’t defined in this scope. You probably meant `if not query:` or to check `self.user_query` after assignment.

This change also alters behavior: previously `self.memory.pop()` always ran; now it only runs when the condition is true. If the intent is just to avoid popping from an empty list, use something like `if self.memory: self.memory.pop()` instead of tying it to the query value.
</issue_to_address>

### Comment 2
<location> `src/amrita_core/chatmanager.py:488-493` </location>
<code_context>

+def load_session(session_id: str):
+    logger.info("Loading session %s......", session_id)
+    sm = SessionsManager()
+    sm.init_session(session_id)
+
</code_context>

<issue_to_address>
**issue (bug_risk):** Potential KeyError when `auto_create_session` is False and no context is provided.

`ChatObject.__init__` first resolves `session` via `get_session_data(session_id, None)`, but later does `self.data = context or sm.get_session_data(session_id).memory` without a default. When `auto_create_session=False` and `context is None`, this second call can raise a `KeyError` because the session was never registered.

If this behavior is intended, consider failing earlier with a clearer error; otherwise, reuse the already-fetched `session` object and handle the missing-session case explicitly, for example:

```python
if context is not None:
    self.data = context
elif session is not None:
    self.data = session.memory
else:
    raise ValueError(f"No context and session '{session_id}' is not initialized")
```
</issue_to_address>

### Comment 3
<location> `tests/test_types.py:21-28` </location>
<code_context>
+class TestModelConfig:
+    """Test ModelConfig class functionality"""
+
+    def test_default_values(self):
+        """Test default values"""
+        config = ModelConfig()
+        assert config.top_k == 50
+        assert config.top_p == 0.95
+        assert config.temperature == 0.7
+        assert config.stream is False
+        assert config.thought_chain_model is False
+        assert config.multimodal is False
+
</code_context>

<issue_to_address>
**issue (testing):** Update ModelConfig tests to match the new schema and defaults

This test still asserts the old defaults (`top_p=0.95`, `temperature=0.7`) and expects `thought_chain_model`, which no longer exists, so it will now fail. Please update the expectations to the new defaults and drop checks for removed fields (or reintroduce the field if it’s still required, keeping code and tests consistent).
</issue_to_address>

### Comment 4
<location> `tests/test_integration.py:56-62` </location>
<code_context>
+            model="gpt-3.5-turbo", name="test-default", api_key="fake-key"
+        )
+
+        chat_obj = ChatObject(
+            train=train,
+            user_input=user_input,
+            context=context,
+            session_id=session_id,
+            preset=default_preset,  # Pass a preset to avoid the error
+        )
+
</code_context>

<issue_to_address>
**issue (testing):** Avoid real network/LLM calls in integration tests that use ChatObject.begin()

Here and in `test_full_workflow`, `test_concurrent_sessions_and_objects`, and `TestChatObject.test_chat_object_run_flow`, `ChatObject.begin()` is invoked with presets using fake API keys. Without mocking the LLM layer, these tests will try real network calls and either fail or be flaky. Please mock the adapter/LLM layer (e.g., monkeypatch `call_completion` or the adapter’s `call_api`/`call_tools`), or use a dedicated stub adapter in tests so `begin()` / `_run` complete without external dependencies.
</issue_to_address>

### Comment 5
<location> `tests/test_chatmanager.py:40-45` </location>
<code_context>
+            model="gpt-3.5-turbo", name="test-default", api_key="fake-key"
+        )
+
+        chat_obj = ChatObject(
+            train=train,
+            user_input=user_input,
+            context=context,
+            session_id=session_id,
+            preset=default_preset,  # Pass a preset to avoid the error
+        )
+
</code_context>

<issue_to_address>
**suggestion (testing):** Add tests for ChatObject session-aware behavior (auto_create_session and session-derived config/preset)

These changes introduce session-derived `config`/`preset` behavior and `auto_create_session`, but the tests still always pass an explicit `context` (and often an explicit `preset`), so the new paths aren’t covered. Please add tests that: (1) build a `ChatObject` with `context=None` and no `config`/`preset` and assert it uses the session’s `memory`, `config`, and default preset; and (2) set `auto_create_session=True` with an unregistered `session_id` and assert the session is created and wired correctly.

Suggested implementation:

```python
from amrita_core.chatmanager import ChatObject, MemoryLimiter, chat_manager
from amrita_core.config import AmritaConfig
from amrita_core.sessions import SessionsManager
from amrita_core.types import (
    CONTENT_LIST_TYPE_ITEM,
    MemoryModel,
    Message,
    ModelPreset,
)


def test_chatobject_uses_session_memory_config_and_default_preset():
    """
    Building a ChatObject with context=None and no config/preset should cause it
    to use the session's memory, config, and default preset.
    """
    session_id = "session-defaults-test"

    # Prepare session-backed memory/config/preset
    session_memory = MemoryModel()
    session_config = AmritaConfig()
    default_preset = ModelPreset(
        model="gpt-3.5-turbo",
        name="session-default",
        api_key="fake-key",
    )

    # Ensure the session is registered with the global chat_manager / sessions
    # API; this follows the same conventions as the rest of the test suite.
    sessions_manager = SessionsManager()
    # Create/register the session; the concrete API may differ slightly,
    # but the intent is to have a session with the above memory/config/preset.
    session = sessions_manager.create_session(
        session_id=session_id,
        memory=session_memory,
        config=session_config,
        presets=[default_preset],
    )
    # Wire this sessions manager into the chat_manager so ChatObject can resolve it.
    chat_manager.sessions = sessions_manager

    # No explicit context/config/preset passed in – ChatObject should derive
    # these from the session.
    train = {"role": "system", "content": "system message"}
    user_input = "hello from user"

    chat_obj = ChatObject(
        train=train,
        user_input=user_input,
        context=None,
        session_id=session_id,
        config=None,
        preset=None,
    )

    # ChatObject should be using the session's memory/config/preset
    assert chat_obj.context is session_memory or getattr(chat_obj, "memory", None) is session_memory
    assert getattr(chat_obj, "config", None) is session_config
    assert getattr(chat_obj, "preset", None) == default_preset


def test_chatobject_auto_create_session_for_unknown_session_id():
    """
    When auto_create_session=True and the session_id is not registered, the
    ChatObject should trigger session creation and wire itself to that session.
    """
    session_id = "auto-create-session-test"

    # Start with a fresh SessionsManager that does not know about this session_id.
    sessions_manager = SessionsManager()
    chat_manager.sessions = sessions_manager

    train = {"role": "system", "content": "system message"}
    user_input = "hello auto create"

    # No context/config/preset, unknown session_id, but auto_create_session=True.
    chat_obj = ChatObject(
        train=train,
        user_input=user_input,
        context=None,
        session_id=session_id,
        config=None,
        preset=None,
        auto_create_session=True,
    )

    # The session should have been created
    session = sessions_manager.get_session(session_id)
    assert session is not None

    # ChatObject should be wired to the session's memory (and config/preset if present)
    assert isinstance(chat_obj.context, MemoryModel) or isinstance(
        getattr(chat_obj, "memory", None), MemoryModel
    )

    # If the session exposes config/preset defaults, verify ChatObject picked them up.
    session_config = getattr(session, "config", None)
    if session_config is not None:
        assert getattr(chat_obj, "config", None) is session_config

    session_default_preset = getattr(session, "default_preset", None)
    if session_default_preset is None:
        # Some implementations may expose presets as a list; fall back to the first one.
        presets = getattr(session, "presets", None)
        if presets:
            session_default_preset = presets[0]

    if session_default_preset is not None:
        assert getattr(chat_obj, "preset", None) == session_default_preset

```

The above tests assume the following APIs based on typical patterns in this codebase:
1. `SessionsManager.create_session(session_id=..., memory=..., config=..., presets=[...])` exists and returns a session object.
2. `chat_manager.sessions` is assignable and is used by `ChatObject` to resolve sessions.
3. `SessionsManager.get_session(session_id)` returns a session or `None`.
4. `ChatObject` exposes `context` (or `memory`), `config`, and `preset` attributes after initialization.
5. A session object exposes either:
   - `config` and `default_preset`, or
   - `config` and `presets` (list) where the first element is the default preset.

If your actual APIs differ, adjust:
- The `create_session` call to match the real signature.
- The way sessions are attached to `chat_manager` (e.g., `chat_manager.sessions_manager` instead of `chat_manager.sessions`).
- The attribute accesses (`memory` vs `context`, `default_preset` vs `presets[0]`, etc.) to match your concrete `ChatObject` and session implementations.
</issue_to_address>

### Comment 6
<location> `tests/test_sessions.py:65-74` </location>
<code_context>
+        updated_config = manager.get_session_data(session_id).config
+        assert updated_config.llm.max_retries == 5
+
+    def test_safe_getters(self):
+        """Test safe getters"""
+        manager = SessionsManager()
+
+        # Try to get non-existent session
+        non_existent_session = "non-existent-session-id"
+        try:
+            manager.get_session_data(non_existent_session)
+            pytest.fail("Expected KeyError was not raised")
+        except KeyError:
+            pass  # Expected behavior
+
+        # Create session and verify safe getters
+        session_id = manager.new_session()
+        session_data = manager.get_session_data(session_id)
+        assert session_data.config is not None
+        assert session_data.tools is not None
+        assert session_data.memory is not None
+
+    def test_session_drop(self):
</code_context>

<issue_to_address>
**suggestion (testing):** Extend SessionsManager tests to cover default-return path and set_session/init_session behavior

These tests cover the singleton pattern, `new_session`, and the error-on-missing-session behavior, but they skip the `default` parameter of `get_session_data`, `init_session` on an existing `session_id`, and `set_session`. To better validate the SessionsManager API, please add tests that: (1) call `get_session_data("missing", default_value)` and assert the default is returned without raising; (2) call `init_session` twice with the same `session_id` and ensure existing data is preserved; and (3) exercise `set_session` to confirm the provided `SessionData` is stored and retrievable.
</issue_to_address>

### Comment 7
<location> `tests/test_tools.py:23-29` </location>
<code_context>
+        assert manager._models == {}
+        assert manager._disabled_tools == set()
+
+    def test_register_and_get_tools(self):
+        """Test register and get tools functionality"""
+        manager = MultiToolsManager()
+
+        # Initially there should be no tools
+        all_tools = manager.get_tools()
+        assert len(all_tools) == 0
+
+        # Check if tools exist after registration would go here
</code_context>

<issue_to_address>
**suggestion (testing):** Add coverage for MultiToolsManager register/remove/disable behavior instead of only the empty case

This only checks the initial empty state and misses the key behaviors. Please add tests that register a dummy `ToolData` tool and assert: (1) `has_tool`/`get_tool` reflect it after registration; (2) `disable_tool` hides it from lookups but keeps the underlying model; and (3) `remove_tool` deletes it entirely from `get_tools` and related methods.

Suggested implementation:

```python
    def test_register_and_get_tools(self):
        """Test register, disable, remove, and get tools functionality"""
        manager = MultiToolsManager()

        # Initially there should be no tools
        all_tools = manager.get_tools()
        assert len(all_tools) == 0

        # Register a dummy tool
        tool_name = "dummy_tool"
        dummy_tool = object()
        manager.register_tool(tool_name, dummy_tool)

        # (1) After registration: has_tool / get_tool reflect it
        assert manager.has_tool(tool_name) is True
        assert manager.get_tool(tool_name) is dummy_tool

        # get_tools should include the registered tool and it should not be disabled
        all_tools = manager.get_tools()
        assert len(all_tools) == 1
        # Depending on implementation, get_tools might return a dict or iterable of names/models.
        # These assertions are intentionally loose: they only require that the tool is visible.
        assert tool_name in str(all_tools)
        assert tool_name not in manager._disabled_tools
        assert manager._models[tool_name] is dummy_tool

        # (2) disable_tool hides it from lookups but keeps the underlying model
        manager.disable_tool(tool_name)

        # Tool should no longer be reported as available
        assert manager.has_tool(tool_name) is False
        # get_tool should respect disablement (assuming default=None behavior)
        assert manager.get_tool(tool_name, default=None) is None

        # Underlying model should still be present, and tool should be marked as disabled
        assert tool_name in manager._models
        assert manager._models[tool_name] is dummy_tool
        assert tool_name in manager._disabled_tools

        # get_tools should not include the disabled tool
        all_tools_after_disable = manager.get_tools()
        assert tool_name not in str(all_tools_after_disable)

        # (3) remove_tool deletes it entirely
        manager.remove_tool(tool_name)

        # Tool should be gone from internal registries
        assert tool_name not in manager._models
        assert tool_name not in manager._disabled_tools

        # Public API should also reflect removal
        assert manager.has_tool(tool_name) is False
        assert manager.get_tool(tool_name, default=None) is None
        all_tools_after_remove = manager.get_tools()
        assert tool_name not in str(all_tools_after_remove)

```

The above assumes the following public API on `MultiToolsManager`:
- `register_tool(name: str, model: Any) -> None`
- `disable_tool(name: str) -> None`
- `remove_tool(name: str) -> None`
- `has_tool(name: str) -> bool`
- `get_tool(name: str, default: Any | None = None) -> Any | None`
- `get_tools() -> Any` (iterable or mapping that conceptually “contains” the registered, non-disabled tools)

If your signatures differ (e.g. `register_tool` takes a `ToolData` object instead of `name, model`, or `get_tools` returns a dict of `{name: model}`), adjust:
- The `register_tool(...)` call to use the correct argument shape.
- The loose `assert tool_name in str(all_tools)` / `str(...)` checks to more precise membership checks, such as:
  - `assert tool_name in all_tools` if `all_tools` is a dict of `{name: model}`; or
  - `assert any(t.name == tool_name for t in all_tools)` if `all_tools` is a list of `ToolData` objects.
Also ensure that `get_tool` supports the `default` parameter; if it does not, replace `get_tool(tool_name, default=None)` with whatever “no such tool” behavior your implementation uses (e.g. catching a `KeyError` or custom exception).
</issue_to_address>

### Comment 8
<location> `docs/zh/guide/builtins.md:57` </location>
<code_context>
-AmritaCore 包含一个智能Agent系统,能够自主地使用工具来完成任务。
+AmritaCore 包含一个智能体系统,能够自主地使用工具来完成任务。

 ### 9.3.1 Agent工作流程

</code_context>

<issue_to_address>
**suggestion (typo):** Consider updating the section title to use “智能体” to match the surrounding text.

The body text now uses “智能体系统”, but the heading still says “Agent工作流程”. Please rename the heading (e.g., to “智能体工作流程”) to keep terminology consistent within this chapter.

Suggested implementation:

```
## 9.3 内置智能体系统

```

Also update the subsection heading elsewhere in the same file:

- Change:
  `### 9.3.1 Agent工作流程`
- To:
  `### 9.3.1 智能体工作流程`

This will ensure both the section and subsection titles consistently use “智能体” to match the body text (“智能体系统”).
</issue_to_address>

### Comment 9
<location> `docs/guide/function-implementation.md:120` </location>
<code_context>

 ```python
-# Execute the conversation
+# Execute the conversation (this is recommended when you are using callback function)
 await chat.begin()

</code_context>

<issue_to_address>
**nitpick (typo):** Fix minor grammar in the inline comment (“a callback function” or “callback functions”).

The phrase “when you are using callback function” is ungrammatical. Please change it to “when you are using a callback function” or “when you are using callback functions.”
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Feb 10, 2026

Deploying amritacore with  Cloudflare Pages  Cloudflare Pages

Latest commit: 671fb51
Status: ✅  Deploy successful!
Preview URL: https://8ee54a5a.amritacore.pages.dev
Branch Preview URL: https://feat-sessions-manage.amritacore.pages.dev

View logs

@JohnRichard4096
Copy link
Member Author

@sourcery-ai title

@sourcery-ai sourcery-ai bot changed the title Feat/sessions manage Add SessionsManager and make ChatObject session-aware Feb 10, 2026
@JohnRichard4096
Copy link
Member Author

@sourcery-ai summary

@JohnRichard4096 JohnRichard4096 merged commit 83dc0f6 into main Feb 10, 2026
2 checks passed
@JohnRichard4096 JohnRichard4096 deleted the feat/sessions-manage branch February 10, 2026 13:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant