From 528f7d9e092af83453258b7acbf4ed3d7118e04d Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 10 Jun 2026 18:01:47 +0300 Subject: [PATCH 1/4] Convert communication channel integrations into set of extensions Each integration can be added and loaded independently without modifying the main source code. --- channels/irc.metta | 19 ++++++++++++++ channels/mattermost.metta | 15 +++++++++++ channels/slack.metta | 15 +++++++++++ channels/telegram.metta | 15 +++++++++++ channels/test.metta | 10 +++++++ lib_omegaclaw.metta | 6 +---- src/channels.metta | 55 +++------------------------------------ src/ext.metta | 19 ++++++++++++++ 8 files changed, 97 insertions(+), 57 deletions(-) create mode 100644 channels/irc.metta create mode 100644 channels/mattermost.metta create mode 100644 channels/slack.metta create mode 100644 channels/telegram.metta create mode 100644 channels/test.metta create mode 100644 src/ext.metta diff --git a/channels/irc.metta b/channels/irc.metta new file mode 100644 index 00000000..e57171bf --- /dev/null +++ b/channels/irc.metta @@ -0,0 +1,19 @@ +!(import! &self (library OmegaClaw-Core ./channels/irc.py)) + +(= (IRC_channel) (empty)) +(= (IRC_server) (empty)) +(= (IRC_port) (empty)) +(= (IRC_user) (empty)) + +(= (commChannelInit) + (progn (configure IRC_channel ##omegaclaw) + (configure IRC_server "irc.quakenet.org") + (configure IRC_port 6667) + (configure IRC_user omegaclaw) + (py-call (irc.start_irc (IRC_channel) (IRC_server) (IRC_port) (IRC_user))))) + +(= (commChannelReceiveMessage) + (py-call (irc.getLastMessage))) + +(= (commChannelSendMessage $msg) + (py-call (irc.send_message $msg))) diff --git a/channels/mattermost.metta b/channels/mattermost.metta new file mode 100644 index 00000000..73636032 --- /dev/null +++ b/channels/mattermost.metta @@ -0,0 +1,15 @@ +!(import! &self (library OmegaClaw-Core ./channels/mattermost.py)) + +(= (MM_URL) (empty)) +(= (MM_CHANNEL_ID) (empty)) + +(= (commChannelInit) + (progn (configure MM_URL "https://chat.singularitynet.io") + (configure MM_CHANNEL_ID "8fjrmabjx7gupy7e5kjznpt5qh") + (py-call (mattermost.start_mattermost (MM_URL) (MM_CHANNEL_ID))))) + +(= (commChannelReceiveMessage) + (py-call (mattermost.getLastMessage))) + +(= (commChannelSendMessage $msg) + (py-call (mattermost.send_message $msg))) diff --git a/channels/slack.metta b/channels/slack.metta new file mode 100644 index 00000000..67c3d8b0 --- /dev/null +++ b/channels/slack.metta @@ -0,0 +1,15 @@ +!(import! &self (library OmegaClaw-Core ./channels/slack.py)) + +(= (SL_CHANNEL_ID) (empty)) +(= (SL_POLL_INTERVAL) (empty)) + +(= (commChannelInit) + (progn (configure SL_CHANNEL_ID "") + (configure SL_POLL_INTERVAL 60) + (py-call (slack.start_slack (SL_CHANNEL_ID) (SL_POLL_INTERVAL))))) + +(= (commChannelReceiveMessage) + (py-call (slack.getLastMessage))) + +(= (commChannelSendMessage $msg) + (py-call (slack.send_message $msg))) diff --git a/channels/telegram.metta b/channels/telegram.metta new file mode 100644 index 00000000..ae78956f --- /dev/null +++ b/channels/telegram.metta @@ -0,0 +1,15 @@ +!(import! &self (library OmegaClaw-Core ./channels/telegram.py)) + +(= (TG_CHAT_ID) (empty)) +(= (TG_POLL_TIMEOUT) (empty)) + +(= (commChannelInit) + (progn (configure TG_CHAT_ID "") + (configure TG_POLL_TIMEOUT 20) + (py-call (telegram.start_telegram (TG_CHAT_ID) (TG_POLL_TIMEOUT))))) + +(= (commChannelReceiveMessage) + (py-call (telegram.getLastMessage))) + +(= (commChannelSendMessage $msg) + (py-call (telegram.send_message $msg))) diff --git a/channels/test.metta b/channels/test.metta new file mode 100644 index 00000000..9cf115eb --- /dev/null +++ b/channels/test.metta @@ -0,0 +1,10 @@ +!(import! &self (library OmegaClaw-Core ./channels/mock.py)) + +(= (commChannelInit) + (py-call (mock.start_mock))) + +(= (commChannelReceiveMessage) + (py-call (mock.getLastMessage))) + +(= (commChannelSendMessage $msg) + (py-call (mock.send_message $msg))) diff --git a/lib_omegaclaw.metta b/lib_omegaclaw.metta index 7c5ca790..9fd81923 100644 --- a/lib_omegaclaw.metta +++ b/lib_omegaclaw.metta @@ -8,12 +8,8 @@ !(import! &self (library OmegaClaw-Core ./src/utils)) !(import! &self (library OmegaClaw-Core ./src/helper.py)) !(import! &self (library OmegaClaw-Core ./src/agentverse.py)) -!(import! &self (library OmegaClaw-Core ./channels/irc.py)) -!(import! &self (library OmegaClaw-Core ./channels/mattermost.py)) -!(import! &self (library OmegaClaw-Core ./channels/telegram.py)) -!(import! &self (library OmegaClaw-Core ./channels/slack.py)) !(import! &self (library OmegaClaw-Core ./channels/websearch.py)) -!(import! &self (library OmegaClaw-Core ./src/channels)) +!(import! &self (library OmegaClaw-Core ./src/ext)) !(import! &self (library OmegaClaw-Core ./src/skills)) !(import! &self (library OmegaClaw-Core ./src/memory)) !(import! &self (library OmegaClaw-Core ./src/context)) diff --git a/src/channels.metta b/src/channels.metta index 95e642e4..dcffd1ae 100644 --- a/src/channels.metta +++ b/src/channels.metta @@ -1,51 +1,10 @@ -;configured at runtime: -(= (IRC_channel) (empty)) -(= (IRC_server) (empty)) -(= (IRC_port) (empty)) -(= (IRC_user) (empty)) -(= (commchannel) (empty)) -(= (MM_URL) (empty)) -(= (MM_CHANNEL_ID) (empty)) -(= (TG_CHAT_ID) (empty)) -(= (TG_POLL_TIMEOUT) (empty)) -(= (SL_CHANNEL_ID) (empty)) -(= (SL_POLL_INTERVAL) (empty)) - ;Connect all the channels: (= (initChannels) (progn (println! "Initializing channels") - (configure commchannel irc) - (if (== (commchannel) irc) - (progn (configure IRC_channel ##omegaclaw) - (configure IRC_server "irc.quakenet.org") - (configure IRC_port 6667) - (configure IRC_user omegaclaw) - (py-call (irc.start_irc (IRC_channel) (IRC_server) (IRC_port) (IRC_user)))) - (if (== (commchannel) telegram) - (progn (configure TG_CHAT_ID "") - (configure TG_POLL_TIMEOUT 20) - (py-call (telegram.start_telegram (TG_CHAT_ID) (TG_POLL_TIMEOUT)))) - (if (== (commchannel) slack) - (progn (configure SL_CHANNEL_ID "") - (configure SL_POLL_INTERVAL 60) - (py-call (slack.start_slack (SL_CHANNEL_ID) (SL_POLL_INTERVAL)))) - (if (== (commchannel) mattermost) - (progn (configure MM_URL "https://chat.singularitynet.io") - (configure MM_CHANNEL_ID "8fjrmabjx7gupy7e5kjznpt5qh") - (py-call (mattermost.start_mattermost (MM_URL) (MM_CHANNEL_ID)))) - (py-call (mock.start_mock)))))))) + (commChannelInit))) ;Receive the latest user message considering all communication channels: -(= (receive) - (if (== (commchannel) irc) - (py-call (irc.getLastMessage)) - (if (== (commchannel) telegram) - (py-call (telegram.getLastMessage)) - (if (== (commchannel) slack) - (py-call (slack.getLastMessage)) - (if (== (commchannel) mattermost) - (py-call (mattermost.getLastMessage)) - (py-call (mock.getLastMessage))))))) +(= (receive) (commChannelReceiveMessage)) ;Send a message to all communication channels: !(change-state! &lastsend "") @@ -53,15 +12,7 @@ (if (!= $msg (get-state &lastsend)) (progn (change-state! &lastsend $msg) (let $safemsg (string-replace $msg "\n" "\\n") - (if (== (commchannel) irc) - (let $temp (cut) (py-call (irc.send_message $safemsg))) - (if (== (commchannel) telegram) - (let $temp (cut) (py-call (telegram.send_message $safemsg))) - (if (== (commchannel) slack) - (let $temp (cut) (py-call (slack.send_message $safemsg))) - (if (== (commchannel) mattermost) - (let $temp (cut) (py-call (mattermost.send_message $safemsg))) - (let $temp (cut) (py-call (mock.send_message $safemsg))))))))) _)) + (let $temp (cut) (commChannelSendMessage $safemsg)))))) ;Search the internet for some information: (= (search $msg) diff --git a/src/ext.metta b/src/ext.metta new file mode 100644 index 00000000..a51f0a7b --- /dev/null +++ b/src/ext.metta @@ -0,0 +1,19 @@ +; TODO: the main reason of moving loading the extensions loading out of the +; (init... ) functions is that dependency should be loaded before the dependent +; module in PeTTa otherwise the dependency functions will not be callable from +; the dependent module (see https://github.com/trueagi-io/PeTTa/issues/181). +; This module is loaded before loop.metta and thus the extensions API is +; available before loop.metta which is critical to call (initChannels) and +; similar. If #181 is fixed it will be possible to move code below into +; (initChannels) in channels.metta and similar. + +;configured at runtime: +(= (commchannel) (empty)) + +!(configure commchannel irc) + +!(let $commext (py-str ("./channels/" (commchannel))) + (progn (println! (py-str ("Loading communication channel extension" $commext))) + (import! &self (library OmegaClaw-Core $commext)) + (import! &self (library OmegaClaw-Core src/channels)) + )) From 0c8c73982ee6b3d44f88f8a20e65ba3d6064a7f7 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Wed, 10 Jun 2026 18:08:32 +0300 Subject: [PATCH 2/4] Move websearch out of the channels directory Websearch is not communication plugin thus it should be moved into other skills directory. --- Autotests/test_websearch_smoke.py | 2 +- docs/introduction.md | 2 +- docs/reference-channels.md | 4 ---- docs/reference-skills-communication.md | 2 +- lib_omegaclaw.metta | 2 +- src/channels.metta | 4 ---- src/skills.metta | 4 ++++ {channels => src}/websearch.py | 0 8 files changed, 8 insertions(+), 12 deletions(-) rename {channels => src}/websearch.py (100%) diff --git a/Autotests/test_websearch_smoke.py b/Autotests/test_websearch_smoke.py index 2653bb08..3a655457 100644 --- a/Autotests/test_websearch_smoke.py +++ b/Autotests/test_websearch_smoke.py @@ -12,7 +12,7 @@ def test_websearch_returns_results(): code = ( "import os, sys;" - "sys.path.insert(0, os.path.join(os.environ['OMEGACLAW_DIR'], 'channels'));" + "sys.path.insert(0, os.path.join(os.environ['OMEGACLAW_DIR'], 'src'));" "import websearch;" "print('COUNT', len(websearch.search_('test')))" ) diff --git a/docs/introduction.md b/docs/introduction.md index 5e38f08a..636a7e2f 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -121,6 +121,7 @@ lib_omegaclaw.metta loads all submodules ├── src/helper.py parenthesis balancing, normalization ├── src/agentverse.py remote-agent bridge ├── src/skills.pl Prolog helpers (shell, first_char) +├── src/websearch.py web search ├── lib_nal.metta NAL truth functions ├── lib_pln.metta PLN rules └── lib_llm_ext.py Claude / GPT / MiniMax / local embeddings @@ -129,7 +130,6 @@ channels/irc.py IRC adapter channels/telegram.py Telegram adapter channels/slack.py Slack adapter channels/mattermost.py Mattermost adapter -channels/websearch.py web search memory/prompt.txt system prompt (agent identity + values) memory/history.metta episodic trace (written at runtime) diff --git a/docs/reference-channels.md b/docs/reference-channels.md index a67e17c0..ad7819c4 100644 --- a/docs/reference-channels.md +++ b/docs/reference-channels.md @@ -59,10 +59,6 @@ Slack adapter using Slack Web API polling. - Adapter respects Slack `Retry-After` backoff on HTTP 429 and enforces a minimum 60s poll interval. - Uses the same one-time `auth ` ownership gate as the other adapters. -## `channels/websearch.py` - -Not a communication channel in the `send`/`receive` sense — this is the backend for the `search` skill. Exposes `search(query)`. - ## Adding a new channel See [tutorial-04-adding-a-channel.md](./tutorial-04-adding-a-channel.md). diff --git a/docs/reference-skills-communication.md b/docs/reference-skills-communication.md index 4f778a21..70205b45 100644 --- a/docs/reference-skills-communication.md +++ b/docs/reference-skills-communication.md @@ -68,7 +68,7 @@ The agent does not normally call `receive` itself; the loop wraps it: ``` ### Purpose -Perform a web search through the `channels/websearch.py` adapter. +Perform a web search through the `src/websearch.py` adapter. ### Parameters - `query` — the search string. diff --git a/lib_omegaclaw.metta b/lib_omegaclaw.metta index 9fd81923..fd6df7b9 100644 --- a/lib_omegaclaw.metta +++ b/lib_omegaclaw.metta @@ -8,7 +8,7 @@ !(import! &self (library OmegaClaw-Core ./src/utils)) !(import! &self (library OmegaClaw-Core ./src/helper.py)) !(import! &self (library OmegaClaw-Core ./src/agentverse.py)) -!(import! &self (library OmegaClaw-Core ./channels/websearch.py)) +!(import! &self (library OmegaClaw-Core ./src/websearch.py)) !(import! &self (library OmegaClaw-Core ./src/ext)) !(import! &self (library OmegaClaw-Core ./src/skills)) !(import! &self (library OmegaClaw-Core ./src/memory)) diff --git a/src/channels.metta b/src/channels.metta index dcffd1ae..9f42dcd6 100644 --- a/src/channels.metta +++ b/src/channels.metta @@ -13,7 +13,3 @@ (progn (change-state! &lastsend $msg) (let $safemsg (string-replace $msg "\n" "\\n") (let $temp (cut) (commChannelSendMessage $safemsg)))))) - -;Search the internet for some information: -(= (search $msg) - (py-call (websearch.search $msg))) diff --git a/src/skills.metta b/src/skills.metta index 7f751b97..5fdeb73b 100644 --- a/src/skills.metta +++ b/src/skills.metta @@ -62,3 +62,7 @@ (= (pin $x) PIN-SUCCESS) + +;Search the internet for some information: +(= (search $msg) + (py-call (websearch.search $msg))) diff --git a/channels/websearch.py b/src/websearch.py similarity index 100% rename from channels/websearch.py rename to src/websearch.py From eccf7601bb94dfd02291220c4da0d2c27cd43502 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 26 Jun 2026 21:02:24 +0300 Subject: [PATCH 3/4] Allow implement communication channel as a pure Pytho module Passing name of the module as the name of the channel allow loading module from the Python path and using it as a communication channel. --- lib_omegaclaw.metta | 1 + src/ext.metta | 29 +++++++++++++++++++++++------ src/ext.py | 20 ++++++++++++++++++++ 3 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 src/ext.py diff --git a/lib_omegaclaw.metta b/lib_omegaclaw.metta index fd6df7b9..b935ec43 100644 --- a/lib_omegaclaw.metta +++ b/lib_omegaclaw.metta @@ -9,6 +9,7 @@ !(import! &self (library OmegaClaw-Core ./src/helper.py)) !(import! &self (library OmegaClaw-Core ./src/agentverse.py)) !(import! &self (library OmegaClaw-Core ./src/websearch.py)) +!(import! &self (library OmegaClaw-Core ./src/ext.py)) !(import! &self (library OmegaClaw-Core ./src/ext)) !(import! &self (library OmegaClaw-Core ./src/skills)) !(import! &self (library OmegaClaw-Core ./src/memory)) diff --git a/src/ext.metta b/src/ext.metta index a51f0a7b..23445f53 100644 --- a/src/ext.metta +++ b/src/ext.metta @@ -9,11 +9,28 @@ ;configured at runtime: (= (commchannel) (empty)) - !(configure commchannel irc) -!(let $commext (py-str ("./channels/" (commchannel))) - (progn (println! (py-str ("Loading communication channel extension" $commext))) - (import! &self (library OmegaClaw-Core $commext)) - (import! &self (library OmegaClaw-Core src/channels)) - )) +(= (load-commchannel-py $extname) + (if (== 1 (py-call (ext.commchannel $extname))) + (progn (add-atom &self (= (commChannelReceiveMessage) (py-call (ext.commChannelReceiveMessage)))) + (add-atom &self (= (commChannelSendMessage $msg) (py-call (ext.commChannelSendMessage $msg)))) + True) + False)) + +(= (empty-to-false $x) + (case $x + ((() False) + ($_ True)))) + + +(= (load-commchannel $extname) + (progn (println! (atom_concat "Loading communication channel extension: " $extname)) + (if (empty-to-false (collapse (import! &self (library OmegaClaw-Core (atom_concat "./channels/" $extname))))) True + (if (empty-to-false (collapse (import! &self (library OmegaClaw-Core (atom_concat "./extensions/" $extname))))) True + (if (load-commchannel-py $extname) True + (progn (println! (atom_concat "Could not load communication channel extension: " $extname)) + False)))))) + +!(load-commchannel (commchannel)) +!(import! &self (library OmegaClaw-Core src/channels)) diff --git a/src/ext.py b/src/ext.py new file mode 100644 index 00000000..f1ba96cb --- /dev/null +++ b/src/ext.py @@ -0,0 +1,20 @@ +import importlib + +_mod = None + +def commchannel(extname): + global _mod + try: + _mod = importlib.import_module(extname) + if _mod is not None: + return 1 + except Exception as e: + return 0 + +def commChannelReceiveMessage(): + global _mod + return _mod.commChannelReceiveMessage() + +def commChannelSendMessage(msg): + global _mod + return _mod.commChannelSendMessage(msg) From b1a6fe72b0f375480d8b4759b0d29921c57044e6 Mon Sep 17 00:00:00 2001 From: Vitaly Bogdanov Date: Fri, 26 Jun 2026 21:22:11 +0300 Subject: [PATCH 4/4] Remove channels.metta --- src/channels.metta | 15 --------------- src/ext.metta | 17 ++++++++++++++++- 2 files changed, 16 insertions(+), 16 deletions(-) delete mode 100644 src/channels.metta diff --git a/src/channels.metta b/src/channels.metta deleted file mode 100644 index 9f42dcd6..00000000 --- a/src/channels.metta +++ /dev/null @@ -1,15 +0,0 @@ -;Connect all the channels: -(= (initChannels) - (progn (println! "Initializing channels") - (commChannelInit))) - -;Receive the latest user message considering all communication channels: -(= (receive) (commChannelReceiveMessage)) - -;Send a message to all communication channels: -!(change-state! &lastsend "") -(= (send $msg) - (if (!= $msg (get-state &lastsend)) - (progn (change-state! &lastsend $msg) - (let $safemsg (string-replace $msg "\n" "\\n") - (let $temp (cut) (commChannelSendMessage $safemsg)))))) diff --git a/src/ext.metta b/src/ext.metta index 23445f53..38ce4f76 100644 --- a/src/ext.metta +++ b/src/ext.metta @@ -33,4 +33,19 @@ False)))))) !(load-commchannel (commchannel)) -!(import! &self (library OmegaClaw-Core src/channels)) + +;Connect all the channels: +(= (initChannels) + (progn (println! "Initializing channels") + (commChannelInit))) + +;Receive the latest user message considering all communication channels: +(= (receive) (commChannelReceiveMessage)) + +;Send a message to all communication channels: +!(change-state! &lastsend "") +(= (send $msg) + (if (!= $msg (get-state &lastsend)) + (progn (change-state! &lastsend $msg) + (let $safemsg (string-replace $msg "\n" "\\n") + (let $temp (cut) (commChannelSendMessage $safemsg))))))