AI agent projects often look much bigger than they need to be. If you start with a full framework, it is easy to get the impression that an agent requires a large architecture before it can do anything useful. CDNsunClaw is valuable because it cuts through that. It shows the core mechanics in a codebase small enough to read in one sitting.
At CDNsun, we like examples that make the moving parts visible. That is exactly what this project does. The main lesson is simple: an LLM is the brain, but an AI agent is the brain plus tools. Once you understand that loop, the rest stops feeling magical and starts feeling buildable.
In this article, we will walk through the CDNsunClaw repository, explain what each important file does, and follow the full path from one Telegram message to one final answer.
What this tutorial is really about
This is not a setup-heavy Telegram bot guide, and it is not a framework comparison post.
This is a practical teardown of a very small tool-using AI agent. The goal is understanding, not spectacle. By the end, you should be able to explain:
- what an agent means in this project,
- how the files fit together,
- how tool calling works,
- how per-chat session memory works.
That is the right promise for a beginner-friendly project. Not “build a superhuman autonomous system,” but “understand the real mechanics of a tiny one.”
What an AI agent means here: LLM brain plus tools
It helps to define terms early.
In CDNsunClaw, the agent is not just a prompt sent to a model. It is a model connected to:
- a system prompt,
- a set of tool definitions,
- code that executes tool calls,
- session history,
- and a message channel.
That matters because plain chat and tool use are different things.
A plain model can answer questions from its context window. A tool-using agent can also decide, “I should call a function now,” wait for the result, and continue reasoning with that result. In CDNsunClaw, that function layer is implemented as skills.
So the core idea is not abstract at all:
- the LLM provides language reasoning,
- the skills provide actions,
- the runtime loop connects the two.
Why CDNsunClaw exists: the smallest educational example
The README.md describes CDNsunClaw as “the smallest and simplest tool-using AI agent for educational purposes written in Python.” After reading the repo, that description is fair.
CDNsunClaw is deliberately small:
- one entry point,
- one runtime loop,
- one Telegram channel,
- one session file,
- one skill-loading pattern,
- and one example skill.
That smallness is not a weakness. It is the teaching strategy.
It is also important to be precise about scope. CDNsunClaw is a real tool-using AI agent, but it is not meant to replace OpenClaw. OpenClaw is the broader platform. CDNsunClaw is the smallest educational example. It is fast and easy to understand partly because it does much less.
High-level architecture
Before we go file by file, here is the whole shape of the project.
A Telegram user sends a message. The bot checks whether that user is allowed. If yes, the message is stored in per-chat history. The runtime builds a system prompt, sends history plus available tools to the OpenAI Responses API, and then either:
- returns a final answer, or
- receives a tool call, executes it, sends the tool result back to the model, and continues.
The main components are:
main.py– wiring and startuptelegram_channel.py– Telegram polling and message handlingsession_manager.py– per-chat history inSESSIONS.jsonskill_loader.py– skill discovery and tool dispatchcontext_builder.py– system prompt assemblyagent_runtime.py– the model/tool loopdebug_utils.py– readable debug loggingSOUL.md– personality and high-level rulesskills/*– tool packages made ofSKILL.mdplushandler.py
That is the full architecture. Small enough to hold in your head.
File-by-file walkthrough
main.py: the wiring layer
main.py is the clean entry point.
It does five things:
- loads environment variables with
python-dotenv, - creates the
SessionManager, - loads all skills from the
skills/directory, - creates the
AgentRuntimewithMODEL_NAMEandOPENAI_API_KEY, - starts the Telegram channel with the bot token and allowlist.
The structure is intentionally direct. There is no dependency injection framework and no hidden bootstrap process. You can see the whole startup flow in one file.
That makes main.py a good first read for anyone trying to understand how the pieces fit together.
agent_runtime.py: the heart of the agent
If you want the single most important file, it is agent_runtime.py.
This is where CDNsunClaw becomes an agent rather than a simple chat wrapper.
The AgentRuntime.run() method:
- builds the system prompt,
- converts session history into Responses API input format,
- collects tool definitions from loaded skills,
- sends the request to the OpenAI Responses API,
- checks whether the model asked to call a tool,
- executes tools if needed,
- sends tool outputs back to the model,
- stops when the model finally returns plain text.
The crucial design choice is the tool loop. The code uses a while rounds < MAX_TOOL_ROUNDS loop, with MAX_TOOL_ROUNDS = 5.
That means the runtime allows up to five rounds of tool use before stopping. For a starter project, that is a sensible guardrail. It keeps the loop understandable and prevents accidental runaway behavior.
The flow is effectively this:
while rounds < 5:
result = call_model(...)
if result contains tool_calls:
execute tools
send function_call_output back
continue
else:
return final text
That is the core mechanic many people want to understand when they say they want to “build an agent.”
A few concrete details in this file are especially worth noticing:
- It uses the OpenAI Responses API.
- Tool schemas are sent as
type: "function"definitions. - Tool results are sent back as
function_call_output. - The runtime stores
previous_response_idbetween rounds, so the model can continue the same chain. - Output parsing is explicit: tool calls are read from
outputitems of typefunction_call, while final assistant text is extracted frommessagecontent items of typeoutput_text.
This is exactly the sort of detail that disappears inside larger abstractions. Here, you can study it directly.
context_builder.py: where the system prompt comes from
context_builder.py is small, but it explains an important idea clearly.
The system prompt is built from three things:
- the contents of
SOUL.md, - the names and descriptions of active skills,
- the current UTC time.
That is one of the most useful lessons in the repo. The agent’s behavior is not defined only by a hardcoded string in Python. It is assembled from a personality file plus live runtime context.
If SOUL.md is missing, the code falls back to a small base prompt:
- be helpful,
- be concise,
- use tools when helpful.
Then it appends the available skill descriptions and a UTC timestamp.
So when we say the agent “knows what tools it has,” that knowledge is partly carried through the prompt itself, not only through the function definitions sent to the API.
telegram_channel.py: the outer interface
telegram_channel.py translates between Telegram and the runtime.
It uses python-telegram-bot in polling mode and listens for text messages. When a message arrives, the file does a few practical things:
- checks
effective_user, - verifies the sender’s Telegram user ID is in the allowlist,
- gets the chat ID,
- stores the message in session history,
- shows Telegram’s typing indicator,
- runs the agent,
- sends the reply back,
- stores the assistant reply in history.
There is also a /new command that clears the current session history for that chat.
The allowlist is basic, but it matters. If a sender’s user ID is not in TELEGRAM_ALLOW_USER_IDS, the bot ignores the message. That is not enterprise-grade policy design, but it is a real safety boundary, and exactly the right kind of one for a small starter agent.
One more practical detail: the file splits long replies into chunks of 4096 characters because Telegram messages have a size limit. That is the kind of small operational fact that turns a toy into something usable.
skill_loader.py: how skills become tools
skill_loader.py implements one of the simplest useful plugin patterns you can build in Python.
A skill is just a subdirectory under skills/ that contains both:
SKILL.mdhandler.py
The loader scans the directory, skips incomplete entries, reads the skill metadata from SKILL.md, imports handler.py dynamically with importlib, and stores:
- the skill name,
- the description,
- the
toolslist, - the
executefunction.
That means skills are not magic modules. They follow a very plain contract.
This is also where the repo teaches an important pattern: tool definition and tool execution are separate concerns.
SKILL.mdtells the system what the skill is.handler.pytells the system what tools it exposes and how to run them.
session_manager.py: simple per-chat memory
session_manager.py handles persistence, and it does it in the most understandable way possible: one JSON file.
The default path is SESSIONS.json. Each session is keyed as channel:client_id, so a Telegram chat becomes something like telegram:123456789.
For each session, the manager stores:
client_id,channel,created_at,history.
Every user message and assistant reply is appended to the history and written back to disk.
This is intentionally simple memory. There is no vector database, no summarization pass, no retrieval layer. Per-chat memory here means conversation history stored in SESSIONS.json.
For a learning project, that simplicity is excellent. You can open the file and inspect what the agent actually remembers.
debug_utils.py: small helper, big teaching value
debug_utils.py only provides three helpers:
ts()for UTC timestamps,debug_json()for readable JSON output,truncate()for trimming large logs.
But this file supports one of the most educational parts of the whole repo: visible debug output.
The runtime and Telegram channel both use DEBUG = 1. That means the project prints:
- inbound Telegram messages,
- unauthorized message attempts,
- OpenAI request bodies,
- OpenAI response JSON,
- parsed tool calls,
- outbound assistant messages.
For learning, this is extremely useful. When you are new to agents, the hardest part is often understanding what happened between “user asked something” and “model answered something odd.” Visible logs turn that black box into a traceable sequence.
SOUL.md: the personality file
SOUL.md is short, but it shows a strong pattern.
Instead of burying behavior rules inside Python strings, the project keeps them in a Markdown file. The current version says the agent should be:
- friendly,
- concise,
- precise,
- honest,
- careful not to invent facts,
- cautious about destructive commands.
That is enough to make the system prompt easy to read and easy to edit. It also makes the role of the prompt concrete for beginners. A lot of “agent personality” is just structured text that gets injected consistently.
skills/datetime: the example skill
The included example skill is skills/datetime, and it is exactly the right size for teaching.
Its SKILL.md contains frontmatter with:
name: datetimedescription: Get the current date and time.
Its handler.py exposes one tool:
get_current_time
That tool takes no arguments and returns a readable UTC timestamp.
This example is intentionally modest. It shows the full skill contract without distracting you with API integrations or risky system access.
env-example: the configuration checklist
The repo also includes env-example, which you rename to .env.
It defines four required settings:
TELEGRAM_BOT_TOKENTELEGRAM_ALLOW_USER_IDSOPENAI_API_KEYMODEL_NAME
One subtle but important detail from the README: TELEGRAM_ALLOW_USER_IDS is a JSON array of strings. If it is missing or empty, nobody can talk to the bot.
Again, small detail, real operational value.
How a single message flows through the system
Now that the pieces are clear, here is the end-to-end path for one message.
- A Telegram user sends a text message.
telegram_channel.pyreceives it through polling.- The bot checks whether the sender’s user ID is allowlisted.
- The bot creates or reuses a session ID based on the chat ID.
- The user message is appended to that session’s history in
SESSIONS.json. agent_runtime.pybuilds the system prompt fromSOUL.md, skill descriptions, and current UTC time.- The runtime sends history plus tool definitions to the OpenAI Responses API.
- If the model requests a tool, the runtime dispatches the call through
skill_loader.py. - The skill returns structured output.
- The runtime sends that tool result back to the model as
function_call_output. - The model continues and returns final assistant text.
telegram_channel.pysends the reply back to Telegram.- The assistant response is stored in session history.
That is the whole agent loop:
model response -> tool call -> tool result -> final answer
Once you understand that chain, most “AI agent” discussions become much less mysterious.
Why the debug output is a feature for learning
In production systems, teams often reduce visible logs or route them into more structured observability pipelines. That is reasonable.
But for a teaching repo, the current CDNsunClaw approach is better. The debug output is not just noise. It is one of the features.
Because logs are printed openly, you can inspect:
- the exact request body sent to OpenAI,
- the exact response JSON that came back,
- how tool calls were parsed,
- what text was finally extracted,
- what Telegram message triggered the run.
That makes debugging much more concrete.
If the model refuses to use a tool, you can inspect the tool schema.
If the tool arguments look wrong, you can inspect the parsed function call.
If the final text feels surprising, you can inspect the preceding round.
For beginners, that visibility is one of the fastest ways to learn how a tool-using agent actually behaves.
Safety note: use a VM, especially before adding stronger tools
This part should not be an afterthought.
CDNsunClaw starts with a safe example skill, but the whole point of an agent is that you can add stronger tools later. That is where risk rises quickly.
A read-only time tool is one thing. Tools that read files, send messages, call APIs, or run shell commands are something else entirely.
That is why VM-first experimentation is the right advice.
If you are going to extend a project like this with stronger capabilities, run it on a virtual machine you control, keep secrets separate, and expose only the minimum access you really need. The Telegram allowlist is a good first boundary, but it is not the whole safety story. The more powerful the tools become, the more careful your isolation needs to be.
This is exactly the boundary to keep in mind with a small educational agent. CDNsunClaw teaches the mechanics clearly, but once you move beyond harmless tools, isolation and access control matter much more.
Conclusion
If you want to understand how a simple AI agent works, CDNsunClaw is a useful place to start.
It keeps the essential pieces visible: one runtime loop, one channel, one session store, one skill contract, and one prompt builder. That makes it easier to read the code, follow the message flow, and see how tool use works in practice.
As a small educational example, it does exactly what it should do. It turns abstract agent talk into a concrete system you can inspect, run, and learn from.
If you can walk through these files and explain the loop back to someone else, the project has already done its job, which is exactly the kind of practical clarity we like at CDNsun.

