简体中文 English 繁體中文 日本語 Русский
本文为静态镜像,内容以交互版为准 在交互式文档中心打开 →

Configuration File Guide

This document introduces the framework's configuration file. For third-party module configurations, please refer to the module's documentation.

ErisPulse uses a TOML-formatted configuration file config/config.toml to manage project configurations.

Configuration File Location

The configuration file is located in the config/ folder in the project root directory:

project/
├── config/
│   └── config.toml
├── main.py

Multiple Instance Warning and Lock File

When the framework starts, it creates a .erispulse_config.lock lock file in the config/ directory and holds it until the process exits (used to detect multiple instances sharing the configuration directory). If the log shows a warning like "Detected that the configuration file may be in use by another ErisPulse instance," it means there are more than two ErisPulse processes writing to the same configuration (typical scenario: multiple containers mounted the same host config/ directory) — concurrent writes will overwrite each other. Please use separate configuration directories for each instance.

Configuration Loading Error Handling

When the framework loads config.toml, it distinguishes three error states and provides actionable diagnostic information instead of silently reverting to default configurations:

Error State Trigger Condition Framework Behavior
File Missing config.toml does not exist Normal on first startup, silently uses empty configuration (no warning)
TOML Syntax Error File exists but format is invalid (e.g., missing quotes, unbalanced parentheses) Outputs line/column number and reason of the error, and retains the last valid configuration to continue running (this file modification does not take effect)
Permission/Other Errors No read permission, IO errors, etc. Outputs clear reason, and retains the last valid configuration to continue running

Note that "last valid configuration" ≠ default configuration: When the file is damaged, the framework uses the last successfully parsed configuration before this startup (if the configuration file is edited and damaged during runtime, it uses the old value), not resetting all configuration items to factory defaults. Do not assume "configuration has been reset" during troubleshooting.

For example, if you accidentally write the configuration as port = 8000 (missing quotes around the string), the log will output something like:

[ERROR] [Config] Configuration file config/config.toml has a syntax error (line 3, column 1): ...
[WARNING] [Config] Failed to read the configuration file. Continuing to run with the last valid configuration. This file modification did not take effect — please fix and reload or restart.

This allows you to immediately locate the problem at the default INFO level and not be confused about "why my modified configuration did not take effect."

What if you edit the configuration file while the robot is running? If you manually edit config.toml during robot operation and introduce a syntax error, the framework will output "The configuration file is damaged (syntax error, line X), cannot merge and write — please fix the configuration file and restart" when it next attempts to write (merge configuration), rather than a confusing "write failed." The configuration items to be written are retained and will not be lost.

Comment Retention and Minimal Disk Write

Comments and key order in config.toml are fully retained after framework writes: Whether through code setConfig(), CLI configuration wizard save, or adapter/module first-generation configuration templates, the framework only modifies the involved keys. Your comments and organized order will not be erased or rearranged (based on tomlkit comment-retaining round-trip implementation).

The framework keeps disk writes minimal:

Environment Variable Override

The framework supports overriding ErisPulse.* configuration items using environment variables (suitable for Docker/containerized/CI deployment, no need to modify config.toml).

Naming rule: Convert the dot-separated path ErisPulse.<section>.<key> to all uppercase, replace . with _, and add the ERISPULSE_ prefix:

Configuration Item Environment Variable Example Value
ErisPulse.server.port ERISPULSE_SERVER_PORT 9000
ErisPulse.server.host ERISPULSE_SERVER_HOST 0.0.0.0
ErisPulse.logger.level ERISPULSE_LOGGER_LEVEL DEBUG
ErisPulse.framework.strict_mode ERISPULSE_FRAMEWORK_STRICT_MODE false

Behavior description:

# Docker deployment example: No need to modify config.toml, directly override the port
ERISPULSE_SERVER_PORT=9000 docker compose up -d

Note: ErisPulse.server.port and other framework configurations accessed via get_server_config() and similar APIs are affected by environment variable overrides.

Module Configuration Environment Variable Binding (2.9.0+)

Module-specific declarative configurations (ConfigClass) support field-level environment variable binding — declare env in field(metadata=...):

@dataclass
class MyConfig(BaseConfig):
    api_key: str = field(default="", metadata={
        "description": "API key",
        "env": "MYMODULE_API_KEY",   # Environment variable binding
    })
    retries: int = field(default=3, metadata={"env": "MYMODULE_RETRIES"})

Behavior description:

# Docker deployment example: No need to modify config.toml, directly inject module key
MYMODULE_API_KEY=sk-xxx docker compose up -d

Configuration class vs model field: which to choose? Configuration class manages "how the module operates" (behavior parameters, hot updates), ORM's Field() manages "what data the user generates" (database tables, queries). Both share the same constraint vocabulary and validator engine; refer to the mapping table in Data Model Layer · When to Use Which Declaration.

Configuration Hot Update

Since version 2.7.0, the framework has provided systematic support for configuration hot updates. After external modification of config.toml (background watcher checks every 5 seconds), or after code calls setConfig(), components automatically respond:

Component Hot-updatable Configuration Behavior
Logger logger.level / log_files / log_dir (including segmentation parameters) / memory_limit / format / exclude_levels Automatically reapplies (with change detection)
Command System event.command.prefix / case_sensitive / allow_space_prefix / must_at_bot Takes effect on the next message
Adapter Concurrency framework.handler_max_concurrency Invalidates cached semaphore, rebuilds with new value
Proactive GC framework.proactive_gc_* Configuration changes immediately restart GC tasks, supporting runtime adjustment/disabling/re-enabling
Master System master.users Each is_master() check reads in real-time, no restart needed
Module/Adapter Configuration Their own configuration items Triggers on_config_update(old, new) callback

Configurations that require restart (cannot be safely hot-switched, warnings are output when changed: "Restart process to take effect"):

Configuration Reason
router.cors.* / router.security.* Middleware is written into FastAPI at service startup, cannot be safely hot-switched at runtime
storage.use_global_db SQLite file handle is already open at runtime, switching paths is unsafe

What if editing and saving goes wrong midway? If a transient syntax error occurs while editing config.toml, the framework will retain the last valid configuration and output diagnostic logs, not broadcasting an empty configuration to components (avoiding on_config_update receiving empty values and mistakenly reverting to default).

Internal Breakdown of Hot Update Chain

"How do components know when the configuration is changed?" — Behind this is a detection → reload → broadcast chain:

flowchart TD
    A["External edit of config.toml"] --> B{"Who detects it first?"}
    B -->|"Background watcher thread<br/>Polls mtime every 5 seconds"| C["_check_file_change determines change"]
    B -->|"Code reads configuration when<br/>Cache exceeds 60 seconds"| C
    C --> D["_load_config re-parses TOML"]
    D --> E{"Parsing successful?"}
    E -->|"No (syntax error)"| F["Retains last valid configuration<br/>Does not broadcast, outputs diagnostic logs"]
    E -->|"Yes"| G["lifecycle.emit config.updated<br/>Carries old_config / new_config"]
    G --> H["Component listeners respond<br/>(logger / scope / command / GC ... )"]

Two detection paths (either is sufficient, both can serve as fallback):

Path Mechanism Trigger Timing
Background watcher Daemon thread config-watcher polls file mtime every 5 seconds Detects external file edits within at most 5 seconds
Lazy detection Any getConfig() read checks file if cache exceeds 60 seconds Next time configuration is read

The framework does not accidentally hurt itself: When setConfig() writes to disk, it records the "mtime written by itself," and the watcher excludes it when comparing, only treating external edits as changes.

Two types of configuration change events:

Event Trigger Data Typical Scenario
config.set Code / Dashboard calls setConfig() {key, old_value, new_value} Single-key write (template generation, status recording, runtime configuration change)
config.updated External edit captured by watcher/lazy detection {old_config, new_config, config_file} Hand-editing config.toml

setConfig() defaults to delayed disk write (merges multiple writes) after 5 seconds; immediate=True writes immediately. After the watcher detects an external modification, it only updates the in-memory cache, and does not write external changes back to the file.

List of components that automatically respond (both event types are usually subscribed to, with consistent response content):

Component Listener Response
Logger config.set + config.updated Level/file/directory segmentation/memory limit/format/exclude levels re-applied (with change detection, no change means no action)
Scope config.updated Scope binding cache rebuilt
Command System config.updated Prefix/case-sensitive/space prefix/must_at_bot parsing parameters refreshed, takes effect on next message
Adapter Concurrency config.set + config.updated handler_max_concurrency invalidates and rebuilds semaphore
Proactive GC config.set + config.updated proactive_gc_* immediately restarts GC background task
Adapter Routes to on_config_update Each adapter's on_config_update(old, new) callback
Module Routes to on_config_update Each module's on_config_update(old, new) callback
Storage config.updated use_global_db change only warns (requires restart)
Router config.updated cors.* / security.* change only warns (requires restart)

Complete Configuration Example

[ErisPulse.server]
host = "0.0.0.0"
port = 8000
auto_start = true
ssl_certfile = ""
ssl_keyfile = ""

[ErisPulse.master]
# users supports two writing methods (choose one):
#   Global master (effective on all platforms): users = ["123456", "789012"]
#   Master per platform: users = { yunhu = ["123456"], telegram = ["789012"] }
users = {}

[ErisPulse.logger]
level = "INFO"
format = "rich"
log_files = []
log_dir = ""
log_rotation = "size"
log_max_size_mb = 10
log_backup_count = 5
log_rotation_when = "midnight"
memory_limit = 1000
exclude_levels = []

[ErisPulse.framework]
enable_lazy_loading = true
uninit_timeout = 30
strict_mode = 0

[ErisPulse.framework.strict_mode_exceptions]
modules = []
adapters = []

[ErisPulse.storage]
backend = "sqlite"
use_global_db = false

[ErisPulse.event.command]
prefix = "/"
case_sensitive = true
allow_space_prefix = false
must_at_bot = false

[ErisPulse.event.message]
ignore_self = true

[ErisPulse.i18n]
language = "auto"

Server Configuration

[ErisPulse.server]
host = "0.0.0.0"
port = 8000
auto_start = true
ssl_certfile = "/path/to/cert.pem"
ssl_keyfile = "/path/to/key.pem"
# For container / no file mount scenarios, PEM content can be inlined (higher priority than certfile/keyfile path)
# ssl_cert = """-----BEGIN CERTIFICATE-----
# ...
# -----END CERTIFICATE-----"""
# ssl_key = """-----BEGIN PRIVATE KEY-----
# ...
# -----END PRIVATE KEY-----"""
Configuration Item Type Default Value Description
host string 0.0.0.0 Listening address, 0.0.0.0 means all interfaces
port integer 8000 Listening port number
auto_start boolean true Whether to automatically start the routing server in sdk.init(). Set to false to skip routing server startup (pure event/no WebUI scenario)
ssl_certfile string empty SSL certificate file path
ssl_keyfile string empty SSL private key file path
ssl_cert string empty Inlined PEM certificate content (not path). Used with ssl_key, takes precedence over ssl_certfile/ssl_keyfile path; framework temporarily writes to disk to build SSL context and immediately deletes the temporary file
ssl_key string empty Inlined PEM private key content (not path), semantics same as above

Port occupancy is not a fatal error: If the framework detects that the port is already occupied at startup, it will skip routing server startup and issue a warning, adapters and modules will still run (only HTTP/WS/SSE routing and WebUI will be unavailable). When troubleshooting "robot runs but WebUI does not open," first confirm the port.

Master System Configuration

The master system is used to identify the "master" account of the framework (e.g., bot administrator). master.users supports two writing methods:

[ErisPulse.master]
# Method 1: Global master (effective on all platforms)
users = ["123456", "789012"]

# Method 2: Master per platform (dict)
# users = { yunhu = ["123456"], telegram = ["789012"] }
Configuration Item Type Default Value Description
users array / object empty Master account list. list format is global master (effective on all platforms); dict format is platform-specific (key is platform name, value is the master account list for that platform)

Code checks using master.is_master(event) or master.is_master(platform, user_id), each call reads the configuration in real-time (supports hot update, no restart needed):

from ErisPulse.Core import master

if master.is_master(event):
    await event.reply("Hello, master")

Determination Chain and Runtime Add/Remove

The master determination chain is configuration master → runtime record → provider chain:

from ErisPulse.Core import master

master.is_master(event)                      # Determine from event
master.is_master("yunhu", "123")             # Explicit determination
master.add("yunhu", "123")                   # Runtime add (default persistent; persist=False only in memory)
master.remove("yunhu", "123")                # Remove (default persistent)
master.list()                                # Aggregate: {"global": [...], "<platform>": [...]}

Custom Identity Source (Provider)

In addition to configuration, you can register a custom identity source: fn(platform, user_id) -> bool, which is tried in sequence if built-in identity sources (configuration + runtime record) do not match. If any provider allows, it is recognized as a master. Suitable for integrating adapter administrator interfaces, database roles, and other external identity systems.

Registration entry master.provider supports both decorator and function styles, and unregistration is done via the unregistered function's fn.unregister():

from ErisPulse.Core import master

# Style 1: Decorator (persistent identity source, recommended)
@master.provider
def admin_provider(platform, user_id):
    return user_id in {"999"}     # Custom determination logic

master.is_master("yunhu", "999")   # True
admin_provider.unregister()        # Unregister when no longer needed

# Style 2: Function-style (register during module loading / unregister during module unloading)
fn = master.provider(admin_provider)
fn.unregister()

Provider exceptions are caught and skipped, not blocking the identity determination chain. Instance methods cannot be bound with unregister, so use module-level functions for scenarios requiring paired registration/unregistration.

User Priority: Master Scope Decided by User

The master=True in commands is only the developer's default: users can override or tighten/restrict it via ErisPulse.event.overrides.command.<module>.<cmd>.master = true/false (see Unified Event Override Configuration, explicit user configuration takes effect).

Logging Configuration

[ErisPulse.logger]
level = "INFO"
log_files = []                # Explicit log file list (mutually exclusive with log_dir, higher priority)
log_dir = ""                  # Log output directory (auto-created). If set, automatically segments and rotates logs into `erispulse.log` in the directory according to `log_rotation`; mutually exclusive with `log_files`, `log_files` takes precedence)
log_rotation = "size"         # Segmentation method: "size" / "date" / "none"
log_max_size_mb = 10          # Size mode single file size limit (MB), rotates to `.1`/`.2` backup after exceeding
log_backup_count = 5          # Number of historical log files to retain
log_rotation_when = "midnight"  # Date mode rotation cycle: S/M/H/D/midnight (default daily at midnight)
memory_limit = 1000
exclude_levels = ["EVENT"]
Configuration Item Type Default Value Description
level string INFO Log level: TRACE, DEBUG, INFO, WARNING, ERROR, CRITICAL (TRACE is the lowest level, outputs detailed framework internal debugging information)
format string rich Log output format: rich (colored, default), plain (plain text without color, suitable for log collection/pipeline redirection), json (JSON structured, suitable for ELK, etc.)
log_files array empty List of explicit log output files (explicit paths, not segmented)
log_dir string empty Log output directory (auto-created). If set, logs are written into erispulse.log in the directory and automatically segmented according to log_rotation; mutually exclusive with log_files, log_files takes precedence
log_rotation string size Segmentation method: size (by size) / date (by time) / none (no segmentation)
log_max_size_mb float 10 Size mode single file size limit (MB), rotates to .1/.2 backup after exceeding
log_backup_count integer 5 Number of historical log files to retain, oldest backups beyond this are automatically deleted
log_rotation_when string midnight Date mode rotation cycle: S/M/H/D/midnight (default daily at midnight)
memory_limit integer 1000 Number of log entries saved in memory
exclude_levels array empty Levels to exclude. Logs of excluded levels are completely discarded (not written to memory, not pushed to Dashboard or other subscribers, not printed, not written to file). Supports hot update

You can also dynamically switch in code:

from ErisPulse.Core import logger

# Segment by size: single file 10MB, retain 5 files
logger.set_output_dir("logs", rotation="size", max_size_mb=10, backup_count=5)

# Segment by time: rotate daily at midnight, retain 7 files
logger.set_output_dir("logs", rotation="date", backup_count=7)

Note

log_dir and related segmentation configurations require ErisPulse 2.8.0+.

Privacy protection: Message sending and receiving content are logged at the EVENT level (value 21). Setting exclude_levels = ["EVENT"] prevents the backend (e.g., Dashboard log panel) from seeing messages in groups/private chats, while not affecting logs of other levels.

Note

The exclude_levels feature requires ErisPulse 2.8.0+.

Framework Configuration

[ErisPulse.framework]
enable_lazy_loading = true
uninit_timeout = 30
strict_mode = 0

[ErisPulse.framework.strict_mode_exceptions]
modules = []
adapters = []
Configuration Item Type Default Value Description
enable_lazy_loading boolean true Whether to enable lazy loading of modules
uninit_timeout integer 30 Graceful shutdown timeout (seconds), forcibly terminates after exceeding. 0 means no timeout set
strict_mode integer 0 Strict mode level, see below "Strict Mode" explanation
handler_max_concurrency integer 64 Maximum concurrent task number for event handlers, increasing improves throughput but increases memory usage
offline_bot_expiry integer 3600 Automatic expiration time for offline bot records (seconds), 0 means no expiration

Proactive GC Configuration

After SDK initialization, a proactive GC background task is started, periodically performing Python GC and internal resource recycling (cleaning up offline bots, etc.). All parameters support hot updates, and tasks are immediately restarted when changes occur.

Configuration Item Type Default Value Description
proactive_gc_interval number 300 Recycle interval (seconds), supports decimals. 0 means disable proactive GC
proactive_gc_generation integer 0 Regular round GC generation (0/1/2, clamped to 0..2). Note that gc.collect(2) is equivalent to full GC, default 0 keeps it lightweight; full GC is triggered periodically by proactive_gc_full_every
proactive_gc_full_every integer 20 Full GC every N rounds, 0 means disable periodic full GC. Full GC is constrained by the proactive_gc_memory_growth_mb threshold
proactive_gc_memory_growth_mb integer 32 Full GC memory growth threshold (MB): compared to the memory baseline after the last full GC (preferring tracemalloc, otherwise RSS), full GC is only executed when the growth reaches this value. 0 means no threshold set
proactive_gc_idle_only boolean false When enabled, Python GC is skipped during event peaks (pending handlers exist), avoiding pauses and message processing competition; internal resource recycling is unaffected
proactive_gc_gen0_min integer 500 Lower bound for triggering regular round GC of gen0 garbage: gc.get_count()[0] is below this value directly skipped (empty round nearly zero overhead). 0 means always recycle

2.7.1 Change: The default proactive_gc_generation is adjusted from 2 to 0, and proactive_gc_full_every from 0 to 20. Previously generation=2 meant full GC every round; the new default maintains coverage while significantly reducing empty round overhead. Explicitly configured old values still follow literal semantics.

Strict Mode

Strict mode controls the handling strategy for non-compliant or failed loading of modules/adapters during the loading phase. Modern modules/adapters should inherit corresponding base classes (BaseModule/BaseAdapter); components not inheriting base classes affect the framework's context system and fallback cleanup, potentially causing resource leaks.

2.5.2 Change: The default level is adjusted from 1 (skip) to 0 (lenient) to reduce loading issues for new users. Components not inheriting base classes are still attempted to load with a WARNING, rather than being directly rejected. To restore the old behavior, explicitly set strict_mode = 1.

Level Name Behavior
0 Lenient (default) Only warns for violations, components not inheriting base classes are still attempted to load (compatible with old components)
1 Strict-Skip Rejects components not inheriting base classes and skips, other components start normally
2 Strict-Fatal Collects all violations and reports them collectively, then terminates the entire startup

In all levels, "errors during loading/registration/initialization phase" (component self-crashes) are always skipped; the difference lies in:

Exception List

If certain components cannot be migrated temporarily (e.g., depending on old modules), you can add them to the exception list. Components listed here will be treated as lenient even if non-compliant, and continue to load:

[ErisPulse.framework.strict_mode_exceptions]
modules = ["SeTu", "SomeLegacyModule"]
adapters = ["OldAdapter"]

When a component is rejected by strict mode, the log will clearly indicate how to restore loading (add to exception list or lower the level).

Storage Configuration

Since version 2.8.0, the storage engine supports three asynchronous backends, with completely consistent APIs and one-click configuration switching:

Backend Driver Installation Features
SQLite (Default) aiosqlite Ready-to-use out-of-the-box Zero configuration, single file, WAL concurrency
MySQL / MariaDB aiomysql pip install ErisPulse[mysql] Existing MySQL infrastructure, multi-instance sharing
PostgreSQL asyncpg pip install ErisPulse[postgres] Strong transaction capability, high concurrency
[ErisPulse.storage]
backend = "sqlite"        # "sqlite" (default) / "mysql" / "postgres"
use_global_db = false     # Only for SQLite: use the package's global database data/config.db

[ErisPulse.storage.mysql]      #生效时backend = "mysql"
host = "127.0.0.1"
port = 3306
user = "erispulse"
password = ""
database = "erispulse"
# charset = "utf8mb4"
# pool_min = 1
# pool_max = 10

[ErisPulse.storage.postgres]   #生效时backend = "postgres"
host = "127.0.0.1"
port = 5432
user = "erispulse"
password = ""
database = "erispulse"
# pool_min = 1
# pool_max = 10
Configuration Item Type Default Value Description
backend string sqlite Storage backend: sqlite / mysql / postgres, switching requires no code changes
use_global_db boolean false Only for SQLite: whether to use the package's global database instead of the project's independent database
storage.mysql.* table See above MySQL connection parameters (host / port / user / password / database / charset / pool)
storage.postgres.* table See above PostgreSQL connection parameters (host / port / user / password / database / pool)

Environment variables are also supported for overriding (Docker / 12-factor): ErisPulse.storage.postgres.host → ERISPULSE_STORAGE_POSTGRES_HOST.

Tip

  • Connection parameter changes require framework restart to take effect; transient connection pool creation failures automatically retry with exponential backoff
  • Before switching backends, use a verification script to self-check: python tests/devs/test_storage_backend_verify.py --backend mysql
  • For complete explanations on transactions, dialect differences, and custom backends, see Storage Backends

Event Configuration

Command Configuration

[ErisPulse.event.command]
prefix = "/"
case_sensitive = true
allow_space_prefix = false
Configuration Item Type Default Value Description
prefix string / Command prefix
case_sensitive boolean true Whether to distinguish case (/Help and /help are different commands)
allow_space_prefix boolean false Whether to allow space as prefix
must_at_bot boolean false Whether to require mentioning the bot to trigger the command (private chat is not restricted)

Message Configuration

[ErisPulse.event.message]
ignore_self = true
Configuration Item Type Default Value Description
ignore_self boolean true Whether to ignore the robot's own messages

Internationalization Configuration

[ErisPulse.i18n]
language = "auto"
Configuration Item Type Default Value Description
language string auto Language for displaying framework built-in text. Set to auto to automatically detect system language, or set to a specific code: zh-CN, zh-TW, en, ja, ru

Module Configuration

Each module can define its own configuration in the configuration file:

[MyModule]
api_url = "https://api.example.com"
timeout = 30
enabled = true

In the module, read and write configuration:

from ErisPulse import sdk

# Read configuration
config = sdk.config.getConfig("MyModule", {})
api_url = config.get("api_url", "https://default.api.com")

# Write configuration at runtime (delayed save)
sdk.config.setConfig("MyModule.timeout", 60)

# Immediately save to file
sdk.config.setConfig("MyModule.timeout", 60, immediate=True)

setConfig uses delayed writing by default (about every 5 seconds batch save to file), set immediate=True to immediately persist. Configuration changes trigger the config.set lifecycle event.

Scope Configuration (scope)

Note

This feature requires ErisPulse 2.8.0+.

Scope declares "what scope it applies to" — which modules are available in which platform / Bot / session (① module dimension), whether events from which user / group / Bot / adapter are received (② identity dimension), and which outbound calls a module can initiate (③ outbound dimension):

[ErisPulse.scope]
default_allow = true        # Global fallback (false = implicit denial strict mode; does not affect outbound dimension)
cache_size = 1024           # LRU cache size

# ① Module dimension (priority: session > Bot > platform; entries support exact / glob / re: regex)
[ErisPulse.scope.platforms.onebot11]
modules = ["Chat", "Tool*"]
blocked = ["re:^Danger"]

# Sub-level binding with merge = true merges with lower priority entries (default overall overwrite)
[ErisPulse.scope.bots.onebot11."123456"]
modules = ["Music"]
merge = true

# ② Identity dimension (priority: user > session > Bot > adapter; only allow or deny per level)
[ErisPulse.scope.identity.adapters.onebot11]
deny = true                 # Deny all events on this platform at the entry
[ErisPulse.scope.identity.users.onebot11]
allow = ["u_admin"]         # User keys support glob / re: regex
deny = ["u_bad", "spam_*"]

# ③ Outbound dimension (default all allowed; rules are inline tables, entries support exact / glob / re: regex)
[ErisPulse.scope.actions.MyModule]
send = { deny = true }                    # Deny all sending
api = { allow = ["get_*"] }               # Allow only standard query APIs
request = { deny = true }                 # Deny request handling
Configuration Item Type Description
scope.default_allow boolean Global fallback: allow/deny for modules/identity not matched by rules (true)
scope.cache_size integer LRU cache size (default 1024)
scope.platforms / bots / sessions table ① Module three-level binding: {modules=[...], blocked=[...], merge=bool?}
scope.identity.adapters / bots / sessions / users table ② Identity four-level binding: {allow=true} / {deny=true}
scope.actions.<module>.<action> table ③ Outbound rules: `{allow=[...], deny=true

For detailed explanations and runtime APIs (dimensional sdk.scope.set_module() / set_identity() / set_action(), determination is_allowed() / is_identity_allowed() / is_action_allowed(), and dictionary-style fallback get() / set() / delete()), see Scope (scope).

Unified Event Override Configuration (event.overrides)

Unified override system: overwrite behavior of any module handler by event type, without modifying module code. OneBot12 standard types (meta / message / notice / request) and extended types (command) each have their own overridable parameters:

[ErisPulse.event.overrides]

# message: Text trigger conditions (AND with code-side conditions)
[ErisPulse.event.overrides.message.ChatModule]
pattern = "闲聊*"

# notice / request / meta: detail_type whitelist (entries support exact / glob / re: regex)
[ErisPulse.event.overrides.notice.MyModule]
detail_types = ["group_increase"]

# command (extended type): Implement parameter override (user priority; disable via acl deny)
[ErisPulse.event.overrides.command.MyModule.restart]
master = true               # Override to only framework master (false opens developer's master restriction)
hidden = true               # Hide in help list
aliases = ["rs"]            # Effective alias

# acl (command-specific): User allow/deny lists for commands (command names support glob / re: regex, exact keys take priority)
[ErisPulse.event.overrides.acl."roll*"]
allow = ["onebot11:u_vip"]  # User identifier "platform:user_id"
deny = ["onebot11:u_bad"]

# ACL fallback: Allow (true) / strictly deny (false) commands without ACL configuration
acl_default_allow = true
Configuration Item Type Description
event.overrides.message.<module> table Text condition: {pattern="...", regex="..."}
event.overrides.notice / request.<module> table {detail_types=[...], pattern, regex}
event.overrides.meta.<module> table {detail_types=[...]}
event.overrides.command.<module> table Module-level parameter override (scalar flags like hidden = true)
event.overrides.command.<module>.<command> table Command-level override (command-level priority)
event.overrides.acl.<command name> table User allow/deny lists: {allow=[...], deny=[...]}
event.overrides.acl_default_allow boolean ACL fallback: Allow (true) / strictly deny (false) commands without ACL configuration

Runtime API (after from ErisPulse.Core.Event import overrides, call overrides.message.set() / overrides.command.set() / overrides.acl.set() by type sub-namespace, or access via sdk.Event.overrides) see Event Handling Basics · Event Override.

Command Parsing Configuration (event.command)

Next Steps