TeleFire is a CLI for Telegram, QQ/OneBot, and Matrix automation.
The current codebase is uv-first, Python 3.14+, and built around explicit runtime layers instead of protocol logic living directly in command classes.
- Telegram runtime on Telethon
- QQ runtime through an authenticated OneBot 11 reverse WebSocket
- Matrix runtime on mautrix
- optional native Matrix E2EE with Olm/Megolm and cross-signing
- shared command runner for one-shot and long-running commands
- account-aware config in
~/.telefire/config.toml - Telegram session storage in
~/.telefire/telegram/ - Matrix session, sync, and state storage in
~/.telefire/matrix/<account>/ - Fire command wrappers with real signatures, so required args can be positional
From the repo:
uv sync
uv run telefire --helpEnable Matrix E2EE commands:
uv sync --extra e2eeIf python-olm fails to build with a CMake policy error, use:
CMAKE_POLICY_VERSION_MINIMUM=3.5 uv sync --extra e2eeOne-shot:
uvx telefire --helpGlobal install:
uv tool install telefire
pipx install telefiretelefire telegram <command> [args...]
telefire matrix <command> [args...]
telefire initTelegram requires a user API ID and API hash from https://my.telegram.org.
Run the interactive setup:
uv run telefire initThat writes ~/.telefire/config.toml.
telefire init configures the default Telegram and Matrix accounts. Optional named accounts can be added manually later.
Before running commands, validate the current setup:
- Check config:
cat ~/.telefire/config.toml- Validate Telegram:
uv run telefire telegram get_entity me- Validate Matrix:
uv run telefire matrix whoamiThe first Telegram command will prompt for login if the selected session file does not exist yet.
Create a named Telegram session explicitly with:
uv run telefire telegram login --account workThe first Matrix command can bootstrap from the configured password, then persist access_token and device_id into the account store and reuse that session on later runs.
The normal setup path is the default account under [telegram] and [matrix].
[telegram]
api_id = 123456
api_hash = "..."
session_name = "telefire"
store_dir = "/home/you/.telefire/telegram"
[telegram.work]
session_name = "work"
[matrix]
base_url = "https://matrix.example.com"
user_id = "@you:example.com"
device_name = "telefire"
store_dir = "/home/you/.telefire/matrix/default"
password = "..."Optional extra accounts can be added manually as subtables:
[telegram.work]
session_name = "work"
[matrix.work]
base_url = "https://matrix.work.example"
user_id = "@you:work.example"
device_name = "telefire"
store_dir = "/home/you/.telefire/matrix/work"
password = "..."Notes:
- Telegram defaults to the config under
[telegram]. - Telegram uses
--accountto resolve an optional configured session alias. - Telegram also accepts
--sessionas a low-level override. - Matrix defaults to the config under
[matrix]. - Matrix uses
--accountto select an optional named account and store directory.
Telegram:
~/.telefire/telegram/telefire.session~/.telefire/telegram/work.session
Matrix:
~/.telefire/matrix/default/session.json~/.telefire/matrix/default/sync_store.json~/.telefire/matrix/default/state_store.bin~/.telefire/matrix/default/crypto.db~/.telefire/matrix/default/crypto_pickle.key~/.telefire/matrix/work/session.json
crypto.db stores Olm/Megolm sessions and Matrix device/cross-signing state.
crypto_pickle.key protects the local libolm account pickle. Keep both private.
Inspect available commands:
uv run telefire --help
uv run telefire telegram --help
uv run telefire matrix --help
uv run telefire telegram COMMAND --help
uv run telefire matrix COMMAND --helpTelegram examples:
uv run telefire telegram get_entity me
uv run telefire telegram get_entity me --session=work
uv run telefire telegram get_all_chats
uv run telefire telegram list_messages --chat=coder_ot --user=Fangliding
uv run telefire telegram search_messages --chat=coder_ot --query='keyword'The local deployment is split into four independently owned Compose projects:
memory/compose.ymlruns Hindsight with a locally built Control Plane. It has no Telefire, Telegram, or QQ dependency.agent/compose.ymlruns the Pi service and Agent Playground. It depends only on the Memory Stack HTTP API.docker-compose.ymlruns the Telegram and QQ/OneBot adapters. They call Pi and Hindsight over their private Docker networks and keep separate state databases.proxy/compose.ymlprovides one loopback dashboard ingress across the Memory and Agent networks usingnginx-proxyvirtual hosts.
Create private environment files from each stack's template:
cp memory/.env.example memory/.env
cp agent/.env.example agent/.env
cp .env.example .envConfigure the memory and agent providers in their respective files. Set the same
private token as PI_AGENT_TOKEN in agent/.env and TELEFIRE_PI_TOKEN in the
root .env. The memory embedding model and dimension define one fixed vector
space; changing either requires explicit re-ingestion and re-embedding. Reasoning
effort accepts none, minimal, low, medium, high, xhigh, or max when
supported by the selected model.
MEMORY_LLM_MODEL is Hindsight's global model and the fallback for every
operation. Optional MEMORY_RETAIN_LLM_MODEL,
MEMORY_CONSOLIDATION_LLM_MODEL, and MEMORY_REFLECT_LLM_MODEL values select
different primary models for those workloads. Hindsight v0.8.4 still uses the
single MEMORY_LLM_REASONING_EFFORT value across all three operations.
TELEFIRE_AI_EDIT_CADENCE is the account-wide minimum interval between Telegram
message edits and defaults to 4 seconds. Intermediate stream updates are skipped
when the edit slot is busy; final answers wait for the next slot.
TELEFIRE_MEMORY_COMMAND_DELETE_DELAY controls how many seconds accepted owner
memory commands remain visible and defaults to 3 seconds.
Clone Hindsight once, then build the pinned v0.8.4 Control Plane with the local bank-name patch. The source clone stays unchanged; the build script exports the exact upstream revision into a temporary build directory:
gh repo clone vectorize-io/hindsight "$HOME/workspace/cloned/hindsight"
./memory/build-hindsight-control-plane.sh "$HOME/workspace/cloned/hindsight"Start the Memory Stack first, then the Agent Stack, Telefire, and the dashboard proxy:
docker compose --env-file memory/.env -f memory/compose.yml up -d --build
docker compose --env-file agent/.env -f agent/compose.yml up -d --build
docker compose --env-file .env up -d --build
docker compose -f proxy/compose.yml up -dAll published ports bind to loopback because memory and agent sessions contain private data:
- Memory API:
http://127.0.0.1:18888 - Private Pi API:
http://127.0.0.1:18790
Both dashboards share port 18865 and are selected by hostname:
- Native Hindsight UI:
http://hindsight.telefire.localhost:18865 - Agent sessions and Playground:
http://sessions.telefire.localhost:18865
Set DASHBOARD_PROXY_EXPOSE_PORT when starting proxy/compose.yml to override
the shared port. The proxy joins only memory-platform and agent-platform;
the embedding network remains backend-only. nginx-proxy reads Docker metadata
through the read-only Docker socket, so this ingress must remain loopback-bound
and should not be treated as an authenticated public gateway.
The playground can run a plain model session with all tools disabled, or a Pi session with the same constrained web, code, and bank-pinned memory tools used by the chat adapters. It exposes the exact prepared request, recalled evidence, and transient tool snapshots in the browser without exposing provider keys or the Pi token. Pi's HTTP API remains a private authenticated interface, not an OpenAI-compatible endpoint.
TELEGRAM_API_ID and TELEGRAM_API_HASH must be set in the root .env. The
userbot container receives neither model-provider nor embedding credentials.
The OneBot adapter is optional. Configure a separate random
TELEFIRE_ONEBOT_TOKEN, set TELEFIRE_ONEBOT_SELF_ID to the logged-in QQ
account, and bind TELEFIRE_ONEBOT_PUBLISH_HOST only to an address reachable by
NapCat. Add an enabled NapCat reverse-WebSocket client with:
- URL
ws://<telefire-host>:<TELEFIRE_ONEBOT_PUBLISH_PORT>/onebot - the same bearer token
- array message format
- self-message reporting enabled
- a bounded reconnect interval and heartbeat
Do not expose this listener without the token or reuse another OneBot
integration's token. The health endpoint at /healthz reports both process
health and whether NapCat is currently connected.
A Telegram API login/session still needs to be initialized once:
docker compose run --rm -it ai telefire telegram login --account defaultThen restart the AI service:
docker compose restart aiThe QQ adapter starts with the rest of the root Compose project:
docker compose --env-file .env up -d --build onebot-ai
curl http://127.0.0.1:${TELEFIRE_ONEBOT_PUBLISH_PORT:-18867}/healthzMemory administration can be run quietly from the local CLI. These commands use the running OneBot adapter and print JSON locally; they do not send or delete QQ messages:
uv run telefire onebot memory dream-enable 694769138 "BetterGI v2"
uv run telefire onebot memory status 694769138
uv run telefire onebot memory backfill 694769138 500
uv run telefire onebot memory dream-disable 694769138When running the command inside Compose, use
docker compose exec onebot-ai python -m telefire.cli ... with the same
arguments after telefire. Backfill defaults to message-count mode; pass
--mode=days for a bounded day window.
The standalone Ollama Compose stack joins the external ollama-embedding network.
Point Hindsight at that service in memory/.env:
MEMORY_EMBEDDING_BASE_URL=http://ollama-embedding-ollama-1:11434/v1
MEMORY_EMBEDDING_API_KEY=ollamaFor a host Ollama process instead, use
MEMORY_EMBEDDING_BASE_URL=http://host.docker.internal:11434/v1.
Compose maps that hostname to the Linux host gateway. In Docker,
http://127.0.0.1:11434 is the Hindsight container itself and must not be used.
The root telefire-runtime volume contains the separate Telegram ai.db, QQ
onebot-ai.db, and Telegram sessions. memory-hindsight-data contains
Hindsight, and Pi's private Agent Sessions live in pi-agent-data.
Commands and reply behavior:
The command set below is shared by Telegram and QQ unless a bullet explicitly
describes a Telegram-only workflow. QQ uses plain-text answer formatting because
its clients do not reliably render Telegram Markdown or HTML. NapCat does not
provide message editing, so QQ receives one temporary Thinking... reply and
one final reply; TeleFire then recalls the temporary message. Intermediate Pi
tool and text snapshots stay internal.
/ai <question>starts a conversation. A replied message chain is reference context; only text after/aiis the current instruction. After a successful answer, human-authored messages in that bounded chain are ingested under their respective users. Relevant scoped memory is retrieved for the requester and the human reply-chain participants./ai10 <question>starts with up to 10 messages immediately before the command as additional chat context. Any number from 1 throughTELEFIRE_AI_MAX_CONTEXT_MESSAGESis accepted. When used in a reply, TeleFire merges the chronological recent messages with the reply path, removes duplicates, and preserves reply relationships. A prior AI answer on that reply path still selects the Agent Session; recent-only messages can supply memory participants but are not automatically retained as reply-thread evidence.- Reply directly to an AI answer without
/aito continue. Reply to an older AI answer to fork from that point. - Reply to a photo, image document, PDF, or UTF-8 text file with
/ai <question>to include a generated description in the reference context. An attachment on the/aimessage itself is also supported; attachment-only requests useDescribe the attached content.as their instruction. - Reply to a user's message with
/ai_allowor/ai_denyto manage delegated access. The owner is always allowed. A successfully executed owner command is deleted while its acknowledgement remains visible; usage and execution failures stay visible for inspection and retry. /ai_directory [source] [description]publishes one owner-approved group or channel in the shared Knowledge Directory without reading that source's memory. With no source selector it publishes the current group/channel and treats the suffix as a description. Telegram also accepts an accessible@username, at.memessage link, or the trusted origin of a replied forwarded message. QQ accepts an accessible positive group ID. Telegram users, QQ private chats, hidden forward origins, inaccessible sources, and cross-platform publication selectors are rejected. Retrying the same command message is idempotent and publication does not enable continuous capture, Dream, or backfill.- Reply to a whitelisted user's message with
/ai_bank_allow [source]or/ai_bank_deny [source]to grant or revoke that exact knowledge bank for that platform-qualified account. An omitted source means the current group. On Telegram use an@usernameor message link; on QQ use a numeric group ID. A bank from another chat platform requires its full canonical bank ID, such asqq:group:686743769. Creating a grant requires a valid Directory Publication; revocation does not depend on the publication or memory service still being available./ai_denyalso removes all of that account's bank grants, so re-allowing the user starts with primary-chat memory only. - Reply in a thread with
/ai_memoryto ingest the bounded human reply chain, attributing each message to its author. An accepted owner's command is deleted after the configured delay while its result or error acknowledgement remains visible. Invalid usage remains visible for correction. Add an instruction, such as/ai_memory Correct their employer to Acme, to ingest the chain, retain a separate owner-attributed correction Episode, and apply a reversible Hindsight-native Revision to the directly replied user. AI-generated answers and AI control commands are not retained as human evidence. - Telegram only: forward a message to the userbot account's Saved Messages to
ingest the original message and its bounded ancestor reply chain without
posting a command in the source chat. When Telegram hides the forward source,
paste the original public
or private supergroup/channel message link as the entire Saved Messages message;
Telefire resolves the linked message with the authenticated account and ingests
the same bounded chain. Forum message links are supported, while channel-comment
links with
?comment=are ignored in v1. Forwarded copies and pasted links remain in Saved Messages. Telefire replies withRemembering...while ingestion runs, then edits that reply toRemembered.or an actionable error. Premium accounts also get a best-effort✍success or👎failure tag. Telegram-delivery duplicates are suppressed, while forwarding or pasting the source again intentionally retries ingestion. Other directly typed Saved Messages are not memory requests. /ai_cancelcancels the requester's active Agent Run.- Every successful
/airequest retains its bounded human reply path plus the current prompt. No chat-level memory switch is required./ai_memory_enableinstead enables continuous capture of all eligible settled messages arriving after the command;/ai_memory_disablestops that background capture. Both commands accept an optional numeric chat target, so the owner can manage another accessible chat from anywhere. Telegram accepts its signed group/channel ID, for example/ai_memory_enable -1002064685671; QQ accepts a positive group ID. Remote enable starts after that target's latest message. /ai_dream_enableseparately enables scheduled Dream scans and/ai_dream_disabledisables them. These commands accept the same optional numeric chat target. Continuous capture overrides Dream while both settings are enabled, so the same chat is never scanned by both workers./ai_memory_dreamruns one bounded Dream scan immediately when Dream is enabled and continuous capture is disabled./ai_memory_statusreports both modes, their cursor, and the latest Dream attempt, success, and failure for the current chat./ai_memory_listreports every chat with continuous memory or Dream enabled, including its display name, canonical numeric ID, cursor, override state, and latest errors. Recall and one-shot/ai_memorydo not depend on either mode. Accepted owner memory-management commands delete on the configured timer.- Dream-enabled chats are scanned on
TELEFIRE_MEMORY_DREAM_CRON(hourly by default). Lookback, overlap, settlement delay, concurrency, transport batch size, and bounded retry settings use theTELEFIRE_MEMORY_DREAM_*variables documented in.env.example. Continuous polling and scope concurrency use theTELEFIRE_MEMORY_CONTINUOUS_*variables. Set the Dream cron value tooffto disable scheduled scans without changing per-chat settings. /ai_memory_dreammanually scans the configured settled time window for a Dream-enabled chat. Standalone messages become one-message Episodes; replies are grouped by their bounded root. The fixed scan watermark advances only after all document updates are accepted. A window or thread over its configured bound fails without advancing, so the owner can narrow the lookback or raise the bound safely./ai_memory_backfill days 7performs a one-shot scan of the rolling seven-day window ending at the configured settlement cutoff./ai_memory_backfill messages 500instead scans the latest 500 settled seed messages. Both forms are owner-only, operate on the current chat Bank, and work even when both background modes are disabled. Reply ancestors may be added as context, so retained event count can exceed the requested seed count. Backfill shares the per-chat Dream lease and ingestion pipeline but never changes the scheduled Dream watermark. The first version accepts 1-30 days or 1-5,000 messages; a day window containing over 5,000 messages fails before retention. Safely rerun the same command after interruption because accepted Episode documents are receipt-backed and idempotent.
Unauthorized users are ignored. Delegated users get one request in flight and a 30-second cooldown by default. AI invocation is controlled by the owner and per-user whitelist; there is no chat-level AI gate.
Every authorized request uses the same constrained web_search, fetch_content,
and QuickJS code_exec policy. Code has no host filesystem, environment, shell,
process, or network APIs, and fetched URLs cannot resolve to loopback, private, or
container-internal addresses. Tool calls execute automatically; transient tool
snapshots are replaced when the final answer begins.
When initial recall is insufficient, both owner and delegated runs may use one
bank-pinned memory_reflect call and bounded memory_get_sources calls for IDs
already returned in that run. These tools are read-only and use fixed host budgets.
Attachment analysis is bounded to the current attachment plus three attachments from the reply chain. Files over 5 MiB are not downloaded. Images are normalized in memory before a non-persistent Pi vision call; PDFs and text-like files are text-extracted in memory and summarized. Audio, video, stickers, archives, and unsupported binaries contribute metadata only. Raw attachment bytes, Telegram download URLs, and temporary paths are never written to Pi sessions, AI state, or memory. Only bounded generated descriptions, OCR text, captions, and safe metadata can enter conversation context and per-user memory.
AI conversation-to-Pi mappings, access state, cooldown timestamps, capture labels, Dream state, and ingestion receipts are stored in each adapter's configured SQLite state path. Facts, observations, entities, source Episodes, and relationships live only in Hindsight. Pi's append-only Agent Sessions are stored in its own data volume. All locations contain private chat-derived data.
Back up the three active volumes while their writers are stopped, or through a storage-aware snapshot mechanism:
docker compose --env-file .env stop ai onebot-ai
docker compose --env-file agent/.env -f agent/compose.yml stop pi-agent
docker compose --env-file memory/.env -f memory/compose.yml stop memory-api
docker run --rm -v telefire_telefire-runtime:/source:ro \
-v "$PWD/backups":/backup alpine tar -C /source -czf /backup/telefire-runtime.tgz .
docker run --rm -v pi-agent-data:/source:ro \
-v "$PWD/backups":/backup alpine tar -C /source -czf /backup/pi-agent-data.tgz .
docker run --rm -v memory-hindsight-data:/source:ro \
-v "$PWD/backups":/backup alpine tar -C /source -czf /backup/memory-hindsight-data.tgz .
docker compose --env-file memory/.env -f memory/compose.yml up -d
docker compose --env-file agent/.env -f agent/compose.yml up -d
docker compose --env-file .env up -dRestore into empty volumes while the stack is stopped, then recreate the stack and check all four health endpoints before enabling Dream again. Accepted Episodes, receipts, cursors, and expired leases are restart-safe; an interrupted retain or Dream batch is retried from its stable document identity rather than rolled back. Do not restore an adapter state database or Hindsight independently from backups taken at unrelated times unless duplicate delivery is acceptable.
The retired Zvec source is preserved in the offline Docker volume
telefire-legacy-zvec for 30 days after cutover and is not mounted by the running
stack. The archive is intentionally managed outside Compose so recreation cannot
attach it to a runtime service. Create and verify that archive once, replacing
<legacy-zvec-volume> with the pre-cutover source volume:
docker volume create telefire-legacy-zvec
docker run --rm \
-v <legacy-zvec-volume>:/source:ro \
-v telefire-legacy-zvec:/archive \
alpine sh -c 'cp -a /source/. /archive/'
docker run --rm -v telefire-legacy-zvec:/archive:ro \
alpine sh -c 'test -n "$(find /archive -mindepth 1 -print -quit)"'
docker inspect telefire-ai pi-agent memory-api \
--format '{{range .Mounts}}{{.Name}} {{end}}'The final inspection must not print telefire-legacy-zvec. A dry run or explicit
migration from an archived source uses:
uv run --extra legacy-migration telefire-memory-migrate \
--source /path/to/legacy-memoryAdd --execute --hindsight-url http://127.0.0.1:18888 only after reviewing the
report. The migration imports source Observations and labels, not derived facts,
profiles, scores, or vectors. Recoverable legacy suppressions become marked
correction Episodes and are applied through reversible Hindsight invalidation.
Every rerun verifies destination document content, so restoring or replacing the
Hindsight volume cannot be masked by surviving local receipts. Stores above 100,000
legacy records fail explicitly instead of producing a partial migration.
First-version limits: banks never search one another automatically; identity is not merged across chats or platforms; Dream scans only explicitly enabled, adapter-owned scopes and only a bounded window; deleted chat messages do not retract retained evidence; raw media is not stored; revisions preserve source history; and no hard-delete, dashboard editing, high-availability database, or disaster-recovery automation is provided.
The loopback Hindsight API and inspection UIs trust other processes on the local host. They are not authenticated public services and must remain bound to loopback or be accessed through a trusted local tunnel.
If the userbot replies with AI request failed, inspect bounded service logs with
docker compose --env-file .env logs ai,
docker compose --env-file agent/.env -f agent/compose.yml logs pi-agent, and
docker compose --env-file memory/.env -f memory/compose.yml logs memory-api.
Responses and health checks do not expose API keys or raw provider payloads.
Matrix examples:
uv run telefire matrix whoami
uv run telefire matrix list_rooms
uv run telefire matrix list_rooms --account=work
uv run telefire matrix cleanup --days=30Matrix E2EE examples:
uv run --extra e2ee telefire matrix crypto_status
uv run --extra e2ee telefire matrix crypto_sync --seconds=30
uv run --extra e2ee telefire matrix decrypt_history '!room:id' --limit=20
uv run --extra e2ee telefire matrix decrypt_history '!room:id' --limit=20 --request_keys=TrueHeadless verification can use the Matrix recovery/security key:
uv run --extra e2ee telefire matrix verify_recovery_keyAfter the Telefire Matrix device is cross-signed, decrypt_history --request_keys=True can ask the account's other Matrix devices for missing Megolm room keys and store any received sessions in crypto.db.
Existing encrypted history is only recoverable when another trusted device or backup still has the relevant room keys.
Do not run multiple E2EE commands for the same Matrix account concurrently; the crypto store is SQLite and should be treated as single-writer.
Long-running commands should be kept alive in tmux, screen, or a service manager:
uv run telefire telegram plus_mode
uv run telefire telegram words_to_ifttt --event=event-name --key=webhook-key outage alert
uv run telefire matrix plus_modeThe core refactor moved the project to an explicit runtime design:
src/telefire/runtime/command.pyshared sync bridge forrun_once(...)andrun_forever(...)src/telefire/telegram/Telegram config, service, store, helpers, and command wrappersrc/telefire/matrix/Matrix config, service, store, helpers, and command wrappersrc/telefire/plugins/base.pycommand registry and Fire wrapper generation
This keeps protocol runtime, storage, and command orchestration separate, while still letting plugin commands stay small.
- TeleFire now targets Python 3.14 or newer.
- Use
uv run telefire ...for repo-local usage. - Protocol commands now live under
telefire telegram ...andtelefire matrix .... - Required arguments may be positional or flags, depending on the command signature shown by
--help. - The old "all arguments must use named flags" rule is no longer true.
- The default account is the primary setup path. Named accounts are optional manual config.