API Reference

This section provides a detailed API reference for the core components of manolo-bot.

AI Components

exception manolo_bot.ai.llmbot.FileTooLargeError[source]

Bases: ValueError

Exception raised when a file exceeds the allowed size.

class manolo_bot.ai.llmbot.LLMBot(llm, bot_config, system_instructions, messages_storage, tools=None, documents_storage=None, system_instructions_mapping=None)[source]

Bases: object

Base class for a Telegram LLM Chat Bot.

Handles interaction with the LLM, message processing, and context management.

async answer_document_message(chat_id, text, document_url, filename)[source]

Answer a document message.

Parameters:
  • chat_id (int) – Chat ID

  • text (str) – Text to answer (user prompt/caption)

  • document_url (str) – URL to download the document

  • filename (str) – Original filename

Return type:

BaseMessage

Returns:

Response

async answer_image_message(chat_id, text, image)[source]

Answer an image message. :type chat_id: int :param chat_id: Chat ID :type text: str :param text: Text to answer :type image: str :param image: Image to answer

Return type:

BaseMessage

Returns:

Response

async answer_message(chat_id, message)[source]

Processes a text message and returns the LLM’s response.

Parameters:
  • chat_id (int) – The ID of the chat.

  • message (str) – The text of the message to process.

Return type:

BaseMessage

Returns:

The response message from the LLM.

async answer_voice_message(chat_id, text, audio)[source]

Answer a voice message. :type chat_id: int :param chat_id: Chat ID :type audio: str :param audio: Voice message audio

async answer_webcontent(message_text, response_content)[source]

Answer a web content message. :type message_text: str :param message_text: Text to answer :type response_content: str :param response_content: Response content :param chat_id: Chat ID

Return type:

str | None

Returns:

New response content if the call was successful, None otherwise

bind_tools_on_init = True
async call_sdapi(prompt)[source]

Call the StableDiffusion API. :type prompt: str :param prompt: The prompt to send to the StableDiffusion API.

Return type:

dict | None

Returns:

The response from the StableDiffusion API.

async clean_context()[source]

Clean the chat context.

Return type:

None

async close()[source]

Close all async resources.

Return type:

None

count_tokens(messages)[source]

Count the number of tokens in the messages using the LLM provider’s native method.

Parameters:

messages (list[BaseMessage]) – List of messages

Return type:

int

Returns:

Number of tokens

async generate_feedback_message(prompt, max_length=200, chat_id=None)[source]

Generate a feedback message using the LLM.

Parameters:
  • prompt (str) – Prompt to generate the feedback message

  • max_length (int) – Maximum length of the feedback message

  • chat_id (int | None) – Optional chat ID for metadata

Return type:

str

Returns:

Generated feedback message

async generate_image(prompt)[source]

Generate an image. :type prompt: str :param prompt: Prompt to generate the image

Return type:

str | None

Returns:

Image representation in base64 format if the call was successful, None otherwise

async initialize_async_resources()[source]

Initialize all async resources (MCP, etc.).

Return type:

None

async postprocess_response(response, message_text, chat_id)[source]

Postprocess the response from the LLM. :type response: BaseMessage :param response: Response from the LLM :type message_text: str :param message_text: Text of the user message :type chat_id: int :param chat_id: Chat ID return: Final response data

Return type:

dict | None

property system_instructions: list[langchain_core.messages.BaseMessage]

Get the system instructions mapping.

async truncate_chat_context()[source]

Compact the chat context when it exceeds the token limit.

When summarization is enabled, the oldest messages are folded into an incremental LLM-generated summary that is persisted at the front of the message list and included in subsequent prompts. At most one summarization LLM call is made per invocation: the number of recent messages kept intact is reduced (down to a floor of 2) to the largest retention that can plausibly fit together with a summary capped at summary_max_tokens, so a single pass has the best chance to bring the context under budget. Drop-oldest is only used as a last resort: when summarization is disabled, fails, or is exhausted.

Return type:

None

class manolo_bot.ai.llmbot.LLMBuilder(llm_config)[source]

Bases: object

Factory class for creating LangChain Chat Model instances.

get_llm()[source]

Creates and returns an instance of the configured LLM.

Return type:

BaseChatModel

Returns:

A LangChain BaseChatModel instance.

Raises:

Exception – If no LLM configuration is found.

class manolo_bot.ai.llmagent.LLMAgent(llm, bot_config, system_instructions, messages_storage, tools=None, documents_storage=None, system_instructions_mapping=None)[source]

Bases: LLMBot

Advanced Telegram LLM Chat Bot using a LangGraph-based agent.

This bot can use tools and dynamically integrate with MCP servers.

async answer_document_message(chat_id, text, document_url, filename)[source]

Answer a document message using the agent.

Parameters:
  • chat_id (int) – Chat ID

  • text (str) – Text to answer

  • document_url (str) – Document URL

  • filename (str) – Original filename

Return type:

BaseMessage

Returns:

Response

async answer_image_message(chat_id, text, image)[source]

Answer an image message. :type chat_id: int :param chat_id: Chat ID :type text: str :param text: Text to answer :type image: str :param image: Image to answer

Return type:

BaseMessage

Returns:

Response

async answer_message(chat_id, message)[source]

Processes a text message and returns the LLM’s response.

Parameters:
  • chat_id (int) – The ID of the chat.

  • message (str) – The text of the message to process.

Return type:

BaseMessage

Returns:

The response message from the LLM.

async answer_voice_message(chat_id, text, audio)[source]

Answer a voice message. :type chat_id: int :param chat_id: Chat ID :type text: str :param text: Text to answer :type audio: str :param audio: Audio to answer

Return type:

BaseMessage

Returns:

Response

bind_tools_on_init = False
async initialize_async_resources()[source]

Initialize async resources and create agent with all tools.

Return type:

None

class manolo_bot.ai.llmdeepagent.LLMDeepAgent(llm, bot_config, system_instructions, messages_storage, tools=None, documents_storage=None, system_instructions_mapping=None, backend=None, skills_paths=None, skills_backend=None, memory_paths=None, memory_backend=None, memory_add_cache_control=False)[source]

Bases: LLMAgent

Advanced Telegram LLM Chat Bot using LangChain Deep Agents harness.

Extends LLMAgent with the full deep agents stack: - To-do list planning (TodoListMiddleware) - Virtual filesystem (FilesystemMiddleware + StateBackend) - Sub-agents (via create_deep_agent subagents parameter) - Skills (progressive disclosure via SkillsMiddleware) - Long-term memory (AGENTS.md files loaded into the system prompt via MemoryMiddleware)

The deep agent harness has its own internal system prompt (planning, filesystem, sub-agent instructions). The bot’s character/persona instructions from system_instructions are passed as the system_prompt string so they are merged with the harness prompt: bot_instructions + "\n\n" + harness_base_prompt.

The virtual filesystem backend is injected from outside (see main.instance_llm_bot()), keeping the class decoupled from chat-specific path logic. Skills follow the same explicit injection pattern: LLMDeepAgent does not instantiate a skills backend itself. Pass skills_paths together with an explicit skills_backend wrapper (typically a SkillsFilesystemDeepAgentBackend constructed once at module load and shared across every chat in the process). If skills_backend is None the SkillsMiddleware is omitted entirely, even when skills_paths is set.

Skills are operator-provided, not per-chat state, and are intentionally not cleared by clean_context().

When skills_paths or memory_paths is configured, the agent’s main filesystem backend is wrapped with a CompositeBackend whose routes come from the injected wrappers’ routes() factories — each wrapper owns the storage-specific path→backend mapping (filesystem wrappers return sandboxed FilesystemBackend routes rooted at each source; Redis/DB wrappers may map prefixes to StoreBackend-style backends or return {}). This lets the agent’s runtime read_file / write_file / edit_file / ls tools reach those files at their absolute paths — the per-chat backend’s virtual_mode=True root would otherwise reject any path outside the chat workspace. For skills this is read-only access to capability bundles; for memory it also enables durable write-back: learnings the agent persists via edit_file land on the real memory file on disk.

Long-term memory follows the same explicit-injection pattern as skills: LLMDeepAgent does not instantiate a memory backend itself. Pass an explicit memory_backend wrapper (typically a per-chat MemoryFilesystemDeepAgentBackend constructed alongside the main backend in main.instance_llm_bot()). If memory_backend is None the MemoryMiddleware is omitted entirely. The injected wrapper supplies the backend used to read the memory file, the chat-scoped source path (derived automatically when memory_paths is not given), and the langgraph store forwarded to create_deep_agent(store=...); the MemoryMiddleware itself is constructed by the agent, exactly like SkillsMiddleware. memory_add_cache_control is passed through to MemoryMiddleware (adds an Anthropic prompt-cache breakpoint on the memory block; no-op on other models).

Memory is per-chat state: each chat gets its own independent, seeded AGENTS.md file (no cross-chat leakage), and clean_context() clears it along with the chat workspace — /flushcontext wipes both. The agent writes new knowledge back to memory via edit_file.

Memory files are fully loaded into the system prompt on every turn, so keep them concise — unlike skills, they cost tokens on every message.

bind_tools_on_init = False
async clean_context()[source]

Clean the chat context.

Return type:

None

async initialize_async_resources()[source]

Initialize async resources and create deep agent with all tools.

Return type:

None

MCP Manager for handling Model Context Protocol servers.

class manolo_bot.ai.mcp_manager.MCPManager(config)[source]

Bases: object

Manages MCP server connections and tool loading.

async connect()[source]

Initialize MCP client and connect to configured servers.

Return type:

None

async disconnect()[source]

Close all MCP server connections.

Return type:

None

async get_tools()[source]

Get all loaded MCP tools.

Return type:

list[BaseTool]

property is_connected: bool

Check if MCP is connected.

class manolo_bot.ai.document_loaders.DocumentLoader[source]

Bases: object

Utility class for extracting text from different document formats using LangChain native parsers.

SUPPORTED_EXTENSIONS = ['pdf', 'docx', 'txt', 'md', 'csv']
extract_text(file_content, filename)[source]

Dispatcher method to extract text based on file extension.

Return type:

str

extract_text_from_docx(file)[source]

Extracts text from a DOCX file using DocxParser.

Return type:

str

extract_text_from_pdf(file)[source]

Extracts text from a PDF file using PyPDFParser.

Return type:

str

extract_text_from_txt(file)[source]

Extracts text from a TXT/MD/CSV file using TextParser.

Return type:

str

classmethod validate_filename(filename)[source]

Validates if a filename has a supported extension.

Parameters:

filename (str) – The filename to validate.

Raises:

UnsupportedFileError – If the extension is not supported.

Return type:

None

class manolo_bot.ai.document_loaders.DocxParser(*args, **kwargs)[source]

Bases: BaseBlobParser

Parser for DOCX files using python-docx. Follows the LangChain BaseBlobParser interface. We use this instead of MsWordParser to avoid the heavy ‘unstructured’ dependency.

lazy_parse(blob)[source]
Return type:

Iterator[Document]

exception manolo_bot.ai.document_loaders.UnsupportedFileError[source]

Bases: ValueError

Exception raised when a file format is not supported.

manolo_bot.ai.document_loaders.clean_text(text)[source]

Basic text cleaning to reduce token usage. Removes multiple whitespaces and newlines.

Return type:

str

Storage Components

class manolo_bot.storage.deep_agent_backends.base.BaseDeepAgentBackend(bot_uuid, chat_id)[source]

Bases: ABC

Abstract base class for deep agent filesystem backends.

Handles the bot_uuid and chat_id scoping, delegating file operations to the underlying deepagents backend (StateBackend, FilesystemBackend, etc.).

property backend: deepagents.backends.protocol.BackendProtocol

Returns the underlying deepagents backend instance.

Returns:

The BackendProtocol instance used for file operations.

abstractmethod async clear()[source]

Clears the backend state for the current chat.

For memory backends, this removes the cached StateBackend instance. For filesystem backends, this removes the chat directory.

Return type:

None

class manolo_bot.storage.deep_agent_backends.base.BaseMemoryBackend[source]

Bases: ABC

Abstract base class for deep-agent memory backends.

Memory is per-chat, durable long-term knowledge (a single AGENTS.md file per chat) loaded into the agent’s system prompt via MemoryMiddleware. It is scoped by (bot_uuid, chat_id) exactly like BaseDeepAgentBackend — each chat gets its own independent memory so no information leaks between chats. The agent writes new knowledge back to memory via edit_file, so the file persists across sessions.

Subclasses provide the per-chat backend instance; the constructor contract is (bot_uuid, chat_id, ...) like BaseDeepAgentBackend. clear() semantics are implementation-defined: the filesystem implementation deletes the chat’s memory directory (/flushcontext wipes chat memory along with the workspace).

Library users may subclass this to provide custom storage (Redis, S3, in-memory, etc.) — same extension model as BaseDeepAgentBackend.

Note

routes() is a breaking addition to this ABC vs. the previously merged memory shape (issue #71) — acceptable, this is a project-internal library.

abstract property backend: deepagents.backends.protocol.BackendProtocol

The underlying BackendProtocol instance used by MemoryMiddleware.

abstractmethod async clear()[source]

Clear the backend state.

Memory is per-chat state. The filesystem implementation deletes the chat’s memory directory; other implementations define their own semantics (e.g. dropping the chat’s memory keys).

Return type:

None

abstractmethod routes(sources)[source]

Return CompositeBackend route entries (prefix → BackendProtocol) so the agent’s runtime tools can reach memory files.

Keys are virtual path prefixes ending in "/"; values are backends rooted at the routed location — CompositeBackend strips the route prefix before delegating, so each value must resolve the stripped path inside its own root. Unlike skills, memory routes must support WRITE (durable edit_file write-back), not just read. Non-filesystem implementations may map prefixes to StoreBackend-style backends, or return {} if memory is served exclusively through MemoryMiddleware.

Return type:

dict[str, BackendProtocol]

class manolo_bot.storage.deep_agent_backends.base.BaseSkillsBackend[source]

Bases: ABC

Abstract base class for deep-agent skills backends.

Skills are operator-provided, versioned, auditable capability bundles described by SKILL.md files. They are global (shared across all chats and bot instances in the same process) and must never be cleared by LLMDeepAgent.clean_context().

This class is intentionally not scoped by (bot_uuid, chat_id) — it parallels BaseDeepAgentBackend but for the global (not per-chat) namespace. Subclasses provide a single backend instance that every chat shares.

Library users may subclass this to provide custom storage (Redis, S3, in-memory, etc.) — same extension model as BaseDeepAgentBackend.

Note

routes() is a breaking addition to this ABC vs. the previously merged skills shape (issue #70) — acceptable, this is a project-internal library.

abstract property backend: deepagents.backends.protocol.BackendProtocol

The underlying BackendProtocol instance used by SkillsMiddleware.

abstractmethod async clear()[source]

Clear the backend state.

Skills are operator-provided, not per-chat state. Implementations should typically be a no-op — wiping operator content when a user runs /flushcontext would be destructive and surprising.

Return type:

None

abstractmethod routes(sources)[source]

Return CompositeBackend route entries (prefix → BackendProtocol) so the agent’s runtime tools can reach skill content.

Keys are virtual path prefixes ending in "/"; values are backends rooted at the routed location — CompositeBackend strips the route prefix before delegating, so each value must resolve the stripped path inside its own root. Non-filesystem implementations may map prefixes to StoreBackend-style backends, or return {} if skill content is served exclusively through SkillsMiddleware.

Return type:

dict[str, BackendProtocol]

class manolo_bot.storage.deep_agent_backends.memory_backend.MemoryDeepAgentBackend(bot_uuid, chat_id)[source]

Bases: BaseDeepAgentBackend

In-memory implementation of the deep agent filesystem backend.

Uses a module-level dict of StateBackend instances keyed by (bot_uuid, chat_id), mirroring the pattern used by MemoryMessagesStorage while preventing cross-bot data leakage in multi-tenant deployments.

async clear()[source]

Clears the backend state for the current chat.

Removes the cached StateBackend instance for this (bot_uuid, chat_id) pair. If a new instance is requested after clearing, a fresh StateBackend is created.

Return type:

None

class manolo_bot.storage.deep_agent_backends.filesystem_backend.FilesystemDeepAgentBackend(bot_uuid, chat_id, workspace_path, virtual_mode=True)[source]

Bases: BaseDeepAgentBackend

Filesystem-based implementation of the deep agent filesystem backend.

Builds a per-chat filesystem path from bot_uuid and chat_id, mirroring the pattern used by FileDocumentsStorage.

Security model — defense in depth, two layers:

  • Layer 1 — input validation at construction time (fail-fast). bot_uuid is required to match [A-Za-z0-9_-]+ (refuses /, .., control characters, etc.). workspace_path is required to be an absolute path. A misconfigured operator gets a ValueError at startup, before any agent can run.

  • Layer 2 — containment check at use time (defense in depth). _build_backend() and clear() resolve the constructed chat_path and verify it is still under workspace_path. This catches symlink-based escapes that pass Layer 1 (the workspace root itself contains a symlink pointing outside) and runtime mutations of _workspace_path.

Note: workspace_path should be a directory only the bot process can read. The default (/tmp/manolo_bot/workspace) lives under the system temp directory, which on Linux is world-readable; on shared hosts set DEEP_AGENT_WORKSPACE_PATH to a private path (e.g. ~/.local/share/manolo_bot/workspace with mode 0700).

property backend: deepagents.backends.filesystem.FilesystemBackend

Returns the underlying deepagents backend instance.

Returns:

The BackendProtocol instance used for file operations.

async clear()[source]

Clears the backend state for the current chat.

For memory backends, this removes the cached StateBackend instance. For filesystem backends, this removes the chat directory.

Return type:

None

Filesystem-based backend for the deep agent’s SkillsMiddleware.

Skills are operator-provided, versioned, auditable capability bundles described by SKILL.md files. They are global (shared across all chats and bot instances in the same process) and must never be cleared by clean_context — clearing a filesystem-backed skills store would delete operator content when a user runs /flushcontext.

Unlike FilesystemDeepAgentBackend, this class is intentionally not scoped by (bot_uuid, chat_id). It wraps FilesystemBackend with virtual_mode=False so the configured skill source paths (DEEP_AGENT_SKILLS_PATHS) can be at any absolute location on the host filesystem, not just inside a per-chat workspace subdirectory.

virtual_mode=False is safe here because this instance is handed exclusively to SkillsMiddleware, which only calls ls() and download_files() (read-only) at the configured source paths. No write/edit/delete is ever invoked through this backend.

class manolo_bot.storage.deep_agent_backends.skills_filesystem_backend.SkillsFilesystemDeepAgentBackend(workspace_path=None)[source]

Bases: BaseSkillsBackend

Filesystem-backed implementation of BaseSkillsBackend.

One instance is intended to be reused across every agent in the process: manolo_bot.main constructs a single instance at module load and passes it to every LLMDeepAgent it creates.

Parameters:

workspace_path (str | None) – Optional root directory for relative skill source paths. Absolute paths in DEEP_AGENT_SKILLS_PATHS are unaffected. Defaults to None (uses the process current working directory).

Example

from manolo_bot.storage.deep_agent_backends.skills_filesystem_backend import (
    SkillsFilesystemDeepAgentBackend,
)

# Shared — reuse across every chat in the process.
skills_backend = SkillsFilesystemDeepAgentBackend()
llm_bot = LLMDeepAgent(
    ...,
    skills_paths=["/etc/manolo_bot/skills", ("$HOME/.local/share/manolo_bot/skills", "User")],
    skills_backend=skills_backend,
)
property backend: deepagents.backends.filesystem.FilesystemBackend

The underlying deepagents.backends.filesystem.FilesystemBackend instance.

async clear()[source]

No-op.

Skills are operator-provided, not per-chat state. They are intentionally not cleared by LLMDeepAgent.clean_context() — wiping operator content when a user runs /flushcontext would be destructive and surprising. If a caller really wants to drop skills, they can replace the singleton with a fresh instance.

Return type:

None

routes(sources)[source]

Build CompositeBackend routes for the given skill source paths.

Each source (bare path or (path, label) tuple) becomes a route prefix <path>/ mapped to a sandboxed FilesystemBackend rooted at that source with virtual_mode=True. CompositeBackend strips the prefix before delegating, so the agent’s runtime read_file / ls / glob / grep tools reach skill files at their absolute paths — the per-chat backend’s virtual_mode=True root would otherwise reject any path outside the chat workspace. Repeated prefixes are deduped.

Return type:

dict[str, FilesystemBackend]

Filesystem-based backend for the deep agent’s MemoryMiddleware.

Memory is per-chat, durable long-term knowledge (a single AGENTS.md file) loaded into the agent’s system prompt via MemoryMiddleware. Each chat gets its own independent memory directory under memory_root — no information leaks between chats. The agent writes new knowledge back to memory via edit_file, so the file persists across sessions.

Unlike the skills backend (global), this class is scoped by (bot_uuid, chat_id) exactly like FilesystemDeepAgentBackend.

The wrapper exposes two distinct FilesystemBackend instances because no single virtual_mode serves both consumers:

  • backend (virtual_mode=False) is handed to MemoryMiddleware, whose sources are RAW ABSOLUTE paths (<chat_memory_dir>/AGENTS.md). With virtual_mode=True those absolute paths would be appended under the root (<chat_memory_dir>/<chat_memory_dir>/AGENTS.md) and silently not found — hence the middleware-facing backend must be virtual_mode=False so absolute paths pass through unchanged. Containment is guaranteed by construction (the root is derived from the validated bot_uuid/chat_id via the Layer-1/Layer-2 security model) and by the fact this backend is handed ONLY to MemoryMiddleware, which accesses exactly the one chat-scoped source the wrapper derives.

  • routes() returns a separate virtual_mode=True instance for the agent’s main CompositeBackend: _route_for_path strips the route prefix and hands the target a key WITH a leading slash ("/AGENTS.md"), which virtual_mode=True correctly resolves under the chat root — a virtual_mode=False target would resolve it against the filesystem root (wrong and unsafe). This mirrors how the skills wrapper separates its middleware-facing backend from its routes() targets.

On construction the chat memory directory is created (if missing) and seeded with a minimal NON-EMPTY AGENTS.md template. The template must be plain markdown text — deepagents’ MemoryMiddleware._format_agent_memory skips empty sources (HTML comments are stripped, so a comment-only file counts as empty) and would render “(No memory loaded)” while hiding the source path from the agent.

class manolo_bot.storage.deep_agent_backends.memory_filesystem_backend.MemoryFilesystemDeepAgentBackend(bot_uuid, chat_id, memory_root, store=None)[source]

Bases: BaseMemoryBackend

Filesystem-backed, per-chat implementation of BaseMemoryBackend.

One instance is constructed per chat (typically inside main.instance_llm_bot(), alongside the main FilesystemDeepAgentBackend) and passed to that chat’s LLMDeepAgent. Memory is scoped by (bot_uuid, chat_id) so chats never share or leak memory.

Security model — defense in depth, two layers (mirrors FilesystemDeepAgentBackend):

  • Layer 1 — input validation at construction time (fail-fast). bot_uuid must match [A-Za-z0-9_-]+; memory_root must be a non-empty absolute path. A misconfigured operator gets a ValueError at startup, before any agent can run.

  • Layer 2 — containment check at use time (defense in depth). _safe_memory_path() resolves the chat memory dir and verifies it is still under memory_root, catching symlink-based escapes that pass Layer 1.

Parameters:
  • bot_uuid (str) – Bot identifier (validated against [A-Za-z0-9_-]+).

  • chat_id (int) – Chat identifier; scopes the memory directory.

  • memory_root (str) – Absolute root directory under which each chat’s memory lives at memory_root/bot_uuid/chat_id.

  • store (BaseStore | None) – Optional langgraph BaseStore forwarded to create_deep_agent(store=...). Defaults to a process-local InMemoryStore created at construction time — the default lives here, in the storage module, never inside LLMDeepAgent.

Example

from manolo_bot.storage.deep_agent_backends.memory_filesystem_backend import (
    MemoryFilesystemDeepAgentBackend,
)

# One instance per chat — pass it to that chat's agent.
memory_backend = MemoryFilesystemDeepAgentBackend(
    bot_uuid="bot-1", chat_id=1001, memory_root="/var/lib/manolo_bot/memory"
)
llm_bot = LLMDeepAgent(
    ...,
    memory_backend=memory_backend,
)
property backend: deepagents.backends.filesystem.FilesystemBackend

The middleware-facing FilesystemBackend (virtual_mode=False).

Handed to MemoryMiddleware, whose sources are raw absolute paths that must pass through unchanged. Never exposed to agent tools; see the module docstring for the two-instance rationale.

async clear()[source]

Delete the chat’s memory directory (destructive).

Memory is per-chat state — /flushcontext wipes the chat’s memory along with its workspace. Layer 2 containment check runs first so a tampered _memory_root or a symlink escape can never delete directories outside the configured root.

Return type:

None

routes(sources)[source]

Build CompositeBackend routes for the chat memory file.

The chat memory directory becomes a single route prefix <dir>/ mapped to a SEPARATE FilesystemBackend with virtual_mode=True (distinct from the middleware-facing backend): CompositeBackend strips the route prefix and hands the target a leading-slash key ("/AGENTS.md"), which virtual_mode=True resolves under the chat root — a virtual_mode=False target would resolve it against the filesystem root (wrong and unsafe). This is what lets the agent’s runtime read_file / write_file / edit_file tools reach the real seeded AGENTS.md on disk for durable write-back. The sources argument is accepted for ABC compatibility; the route is always the chat’s own memory directory.

Return type:

dict[str, FilesystemBackend]

property source: str

The chat-scoped memory file path loaded by MemoryMiddleware.

property store: langgraph.store.base.BaseStore

The langgraph BaseStore forwarded to create_deep_agent(store=...).

This property is the one addition over BaseSkillsBackend: MemoryMiddleware / create_deep_agent need a langgraph store for persistent agent state, and skills did not — so the store is a storage concern owned by this wrapper, not by LLMDeepAgent.

class manolo_bot.storage.messages.base.BaseDBHelper[source]

Bases: ABC

async connect()[source]

Connects to the database.

Return type:

None

abstractmethod async disconnect()[source]

Disconnects from the database.

Return type:

None

class manolo_bot.storage.messages.base.BaseMessagesStorage(bot_uuid, chat_id)[source]

Bases: ABC

Abstract base class for message storage.

Provides the interface for persisting and retrieving chat messages.

add_message(message)[source]

Adds a new message.

Return type:

None

abstractmethod async clear_messages()[source]

Clears all messages from the storage.

Return type:

None

abstractmethod async commit()[source]

Include new messages and remove deleted messages from the database asynchronously.

Return type:

None

delete_message(index)[source]

Deletes a message from the storage by index.

The index refers to the position among non-deleted messages (the same indexing used by messages). Note that inserting a summary via set_summary() shifts these indices, so callers should operate on fresh state.

Return type:

None

get_summary()[source]

Returns the content of the persisted conversation summary, if any.

The summary is stored as a SystemMessage (flagged with SUMMARY_PREFIX) at the front of the message list. This method scans non-deleted messages and returns the first one that carries the marker prefix, so it keeps working even if a backend reorders messages (e.g. after a refresh).

Return type:

str | None

Returns:

The summary text without the marker prefix, or None if absent.

property messages: list[langchain_core.messages.BaseMessage]

Returns a list of non-deleted messages.

abstractmethod async refresh_messages()[source]

Updates the messages list from the database asynchronously.

Return type:

None

set_summary(text)[source]

Replaces the conversation summary with the given text.

Any existing summary message is marked as deleted, then a new SystemMessage (new=True) is inserted at the front of the message list, before the first non-deleted message, so it is the first message returned by messages. Both Memory and Redis backends persist it transparently as a regular message.

Note: inserting the summary shifts the non-deleted indices used by delete_message(). Callers should always operate on fresh state (i.e. query messages/get_summary after any insertion).

Parameters:

text (str) – The summary text.

Return type:

None

manolo_bot.storage.messages.base.SUMMARY_PREFIX = 'CONVERSATION SUMMARY: '

Marker prefix that distinguishes the auto-generated conversation summary (stored as a leading SystemMessage) from any other system message.

class manolo_bot.storage.messages.base.StorageMessage(message, deleted=False, new=False)[source]

Bases: object

deleted: bool = False
message: langchain_core.messages.BaseMessage
new: bool = False
manolo_bot.storage.messages.base.convert_json_to_message(json_message)[source]

Converts a JSON string representation of a message into a BaseMessage instance.

Return type:

BaseMessage

manolo_bot.storage.messages.base.get_messages_key(bot_uuid, chat_id)[source]

Generates a key for storing messages in a database based on bot UUID and chat ID.

Return type:

str

class manolo_bot.storage.messages.memory_storage.MemoryMessagesStorage(bot_uuid, chat_id)[source]

Bases: BaseMessagesStorage

In-memory implementation of message storage.

async clear_messages()[source]

Clears all messages from the memory storage for the current chat.

Return type:

None

async commit()[source]

Include new messages and remove deleted messages from the memory storage.

The persisted list is rebuilt from the in-memory non-deleted messages so that insertion order is preserved — including summaries inserted at the front via set_summary.

Return type:

None

async refresh_messages()[source]

Updates the messages list from the memory storage.

Return type:

None

class manolo_bot.storage.messages.redis_storage.RedisDBHelper(db_url)[source]

Bases: BaseDBHelper

Helper class for Redis database operations.

async connect()[source]

Connects to the Redis database.

Return type:

None

async disconnect()[source]

Disconnects from the Redis database.

Return type:

None

class manolo_bot.storage.messages.redis_storage.RedisMessagesStorage(db, bot_uuid, chat_id)[source]

Bases: BaseMessagesStorage

Redis-based implementation of message storage.

async clear_messages()[source]

Clears all messages from the Redis database for the current chat.

Return type:

None

async commit()[source]

Persist the current in-memory message list to Redis.

The whole list is rewritten (delete + rpush in order) so that messages inserted mid-list — e.g. the conversation summary placed at the front by set_summary — keep their relative order after a refresh.

Return type:

None

async refresh_messages()[source]

Updates the messages list from the Redis database.

Return type:

None

class manolo_bot.storage.documents.base.BaseDocumentStorage(bot_uuid)[source]

Bases: ABC

Abstract base class for document storage.

Provides the interface for persisting and retrieving extracted document text.

abstractmethod async clear(chat_id)[source]

Clears all stored documents for a specific chat.

Parameters:

chat_id (int) – The ID of the chat.

Return type:

None

abstractmethod async list_documents(chat_id)[source]

Lists all stored documents for a specific chat.

Parameters:

chat_id (int) – The ID of the chat.

Return type:

list[str]

Returns:

A list of filenames.

abstractmethod async retrieve(chat_id, filename)[source]

Retrieves the extracted text of a document.

Parameters:
  • chat_id (int) – The ID of the chat.

  • filename (str) – The name of the document.

Return type:

str | None

Returns:

The extracted text or None if not found.

abstractmethod async store(chat_id, filename, text)[source]

Stores the extracted text of a document.

Parameters:
  • chat_id (int) – The ID of the chat.

  • filename (str) – The name of the document.

  • text (str) – The extracted text.

Return type:

None

class manolo_bot.storage.documents.file_storage.FileDocumentsStorage(bot_uuid, base_path=None)[source]

Bases: BaseDocumentStorage

File-based implementation of document storage.

async clear(chat_id)[source]

Clears all stored documents for a specific chat from the filesystem.

Parameters:

chat_id (int) – The ID of the chat.

Return type:

None

async list_documents(chat_id)[source]

Lists all stored documents for a specific chat in the filesystem.

Parameters:

chat_id (int) – The ID of the chat.

Return type:

list[str]

Returns:

A list of filenames.

async retrieve(chat_id, filename)[source]

Retrieves the extracted text of a document from the filesystem.

Parameters:
  • chat_id (int) – The ID of the chat.

  • filename (str) – The name of the document.

Return type:

str | None

Returns:

The extracted text or None if not found or path is insecure.

async store(chat_id, filename, text)[source]

Stores the extracted text of a document in the filesystem.

Parameters:
  • chat_id (int) – The ID of the chat.

  • filename (str) – The name of the document.

  • text (str) – The extracted text.

Return type:

None

Configuration

class manolo_bot.ai.config.BotConfig(bot_uuid, bot_name, bot_username, bot_token, user_id, agent_instructions=None, allowed_chat_ids=<factory>, bot_instructions='', bot_instructions_character='', bot_instructions_extra='', simulate_typing=True, simulate_typing_wpm=100, simulate_typing_max_time=10, use_tools=False, enable_mcp=False, mcp_servers_config=<factory>, context_max_tokens=4096, context_summarization=True, summary_max_tokens=512, summary_keep_messages=6, preferred_language='English', add_no_answer=False, is_image_multimodal=False, is_audio_multimodal=False, is_document_multimodal=False, is_group_assistant=False, agent_mode=False, web_content_request_timeout=10, max_document_size=2097152, max_voice_size=2097152, can_use_tavily_search=False, sdapi_url='', sdapi_params=<factory>, sdapi_negative_prompt='')[source]

Bases: object

add_no_answer: bool = False
agent_instructions: str | None = None
agent_mode: bool = False
allowed_chat_ids: list
bot_instructions: str = ''
bot_instructions_character: str = ''
bot_instructions_extra: str = ''
bot_name: str
bot_token: str
bot_username: str
bot_uuid: str
context_max_tokens: int = 4096
context_summarization: bool = True
enable_mcp: bool = False
is_audio_multimodal: bool = False
is_document_multimodal: bool = False
is_group_assistant: bool = False
is_image_multimodal: bool = False
max_document_size: int = 2097152
max_voice_size: int = 2097152
mcp_servers_config: dict
preferred_language: str = 'English'
sdapi_negative_prompt: str = ''
sdapi_params: dict
sdapi_url: str = ''
simulate_typing: bool = True
simulate_typing_max_time: int = 10
simulate_typing_wpm: int = 100
summary_keep_messages: int = 6
summary_max_tokens: int = 512
use_tools: bool = False
user_id: int
web_content_request_timeout: int = 10
class manolo_bot.ai.config.LLMConfig(google_api_key, google_api_model, openai_api_key, openai_api_model, openai_api_base_url, ollama_model, rate_limiter_requests_per_second=0.25, rate_limiter_check_every_n_seconds=0.1, rate_limiter_max_bucket_size=10)[source]

Bases: object

google_api_key: str
google_api_model: str
ollama_model: str
openai_api_base_url: str
openai_api_key: str
openai_api_model: str
rate_limiter_check_every_n_seconds: float = 0.1
rate_limiter_max_bucket_size: int = 10
rate_limiter_requests_per_second: float = 0.25
class manolo_bot.config.Config(lazy=False)[source]

Bases: EnvModel

add_no_answer = <envmodel.fields.BooleanField object>
agent_instructions = <envmodel.fields.StringField object>
agent_mode = <envmodel.fields.BooleanField object>
ai_mode = <envmodel.fields.StringField object>
allow_private_chats = <envmodel.fields.BooleanField object>
allowed_chat_ids = <envmodel.fields.StringListField object>
bot_instructions = <envmodel.fields.StringField object>
bot_instructions_character = <envmodel.fields.StringField object>
bot_instructions_extra = <envmodel.fields.StringField object>
bot_name = <envmodel.fields.StringField object>
bot_token = <envmodel.fields.StringField object>
bot_username = <envmodel.fields.StringField object>
bot_uuid = <envmodel.fields.StringField object>
context_max_tokens = <envmodel.fields.IntegerField object>
deep_agent_backend = <envmodel.fields.StringField object>
deep_agent_memory_add_cache_control = <envmodel.fields.BooleanField object>
deep_agent_memory_path = <envmodel.fields.StringField object>
deep_agent_skills_paths = <envmodel.fields.StringListField object>
deep_agent_workspace_path = <envmodel.fields.StringField object>
default_sdapi_params = {'cfg_scale': 1, 'height': 512, 'steps': 1, 'timestep_spacing': 'trailing', 'width': 512}
document_storage_path = <envmodel.fields.StringField object>
property effective_ai_mode: str

Resolve the effective AI mode, handling backward compatibility with AGENT_MODE.

Precedence:
  1. AI_MODE if set to a non-empty value

  2. "agent" when AGENT_MODE is True (backward-compat)

  3. "agent" by default (documented default)

enable_context_summarization = <envmodel.fields.BooleanField object>
enable_mcp = <envmodel.fields.BooleanField object>
google_api_key = <envmodel.fields.StringField object>
google_api_model = <envmodel.fields.StringField object>
is_audio_multimodal = <envmodel.fields.BooleanField object>
is_document_multimodal = <envmodel.fields.BooleanField object>
is_group_assistant = <envmodel.fields.BooleanField object>
is_image_multimodal = <envmodel.fields.BooleanField object>
logging_level = <envmodel.fields.StringField object>
max_document_size = <envmodel.fields.IntegerField object>
max_voice_size = <envmodel.fields.IntegerField object>
mcp_servers_config = <envmodel.fields.JsonField object>
ollama_model = <envmodel.fields.StringField object>
openai_api_base_url = <envmodel.fields.StringField object>
openai_api_key = <envmodel.fields.StringField object>
openai_api_model = <envmodel.fields.StringField object>
preferred_language = <envmodel.fields.StringField object>
rate_limiter_check_every_n_seconds = <envmodel.fields.FloatField object>
rate_limiter_max_bucket_size = <envmodel.fields.IntegerField object>
rate_limiter_requests_per_second = <envmodel.fields.FloatField object>
redis_url = <envmodel.fields.StringField object>
sdapi_negative_prompt = <envmodel.fields.StringField object>
sdapi_params = <envmodel.fields.JsonField object>
sdapi_url = <envmodel.fields.StringField object>
simulate_typing = <envmodel.fields.BooleanField object>
simulate_typing_max_time = <envmodel.fields.IntegerField object>
simulate_typing_wpm = <envmodel.fields.IntegerField object>
storage_type = <envmodel.fields.StringField object>
summary_keep_messages = <envmodel.fields.IntegerField object>
summary_max_tokens = <envmodel.fields.IntegerField object>
use_tools = <envmodel.fields.BooleanField object>
user_id = <envmodel.fields.IntegerField object>
web_content_request_timeout = <envmodel.fields.IntegerField object>