Everything started because I wanted to understand how autonomous agents actually work. Not from reading docs or watching talks, but from building one myself. I’ve tried implementations like OpenClaw and it’s variants, used agentic frameworks like LangChain and AutoGPT and thinking “ok but what is actually happening inside?” The best way to answer that question was to write my own.
From the beginning the philosophy was clear: no black boxes. Everything should be readable, every decision traceable. Something I could learn from, evolve over time, and use to teach others. Always thinking on the educational side.
The code is at https://github.com/arcturus/miclaw. If you’re curious about how agent systems work under the hood, go read it. That’s the whole point.
Why not use an existing framework?
I looked at the usual suspects. LangChain, AutoGPT, CrewAI. They all reimplement everything from scratch: tool execution, LLM calls, context management. You end up maintaining wrapper code that does worse versions of things Claude Code already handles natively.
I didn’t want that. Claude Code is already damn good at reading files, running commands, talking to MCP servers. Why rewrite all of that? What I needed was the orchestration layer: who is the agent, what does it remember, when does it run, and who can talk to it.
That’s how miclaw started. About 3,000 lines of TypeScript that sit on top of `claude -p` and add identity, memory, learning, scheduling, and security. Every tool call still flows through Claude Code. Miclaw just coordinates.
The soul directory
This is my favorite part. Your agent’s personality lives in markdown files inside a `soul/` directory:
– `AGENTS.md` defines the role
– `SOUL.md` defines the style and rules
– `IDENTITY.md` adds background context
– `TOOLS.md` gives tool-specific guidance
At runtime we concatenate them into the system prompt. Want to see how your agent’s personality evolved? `git log soul/`. Want to test a different tone? Branch it.
Just files and git. No database, no admin panel for editing prompts. I really like how simple this turned out.
Memory
After some time spent thinking about it, we ended up with three tiers.
Long-term memory
Lives in `MEMORY.md`. Facts the agent should always know. Think of it as the agent’s notebook.
Journals
Are timestamped daily logs in `journals/`. Every interaction gets recorded with who said what and when. Since this is an educational bot, we keep everything. No truncation, full content, preserved for later analysis.
Learnings
Are the fun one. After each conversation turn, a lightweight model (haiku, to keep costs down) reflects on the interaction and extracts patterns. User preferences, recurring mistakes, things that worked. These get deduplicated and fed back into the next conversation. So the agent actually gets better over time.
Sessions persist too. Miclaw maps each channel-user-agent combination to a Claude Code session ID, so picking up where you left off works out of the box.
One agent, many channels
The same agent works over CLI and web. Both implement the same `Channel` interface, so if we want to add Telegram or Discord later, it’s just another adapter. No need to restructure anything.
The web channel comes with a chat UI and an admin dashboard. The admin panel shows active sessions, cron job status, audit logs, and security violations. I wasn’t expecting to need all of that, but once you start running an agent that talks to people, you really want visibility into what’s happening.
Cron jobs
This is where it stops feeling like a chatbot and starts feeling like an assistant. Cron jobs live in a JSON file:
{
"daily-review": {
"schedule": "0 9 * * *",
"agent": "assistant",
"message": "Review pending items for {{DATE}}",
"outputMode": "journal"
}
}
Template variables like `{{DATE}}` and `{{JOURNALS_LAST_N}}` get resolved at runtime. Output can go to the journal, broadcast to a channel, or both. And you can hot-reload the jobs file through an API endpoint without restarting the process.
An agent that reviews your day every morning or summarizes open threads on Friday, without you asking. That’s cool.
Security
Ok so this part is important. When you put an LLM behind a web endpoint, you’re giving host-level tool access to untrusted input. That’s scary if you think about it for more than a minute.
Miclaw has eight layers of defense running together: input validation, tool whitelisting per channel, real-time path enforcement (the process gets killed mid-stream if it tries to touch `~/.ssh`), URL filtering, rate limiting, cost caps, memory isolation, and audit logging.
The web channel defaults to read-only tools: Read, Glob, Grep. The CLI gets full access. Cron jobs run unrestricted but with a 10-minute timeout.
The part I’m most proud of is how `runner.ts` works. It parses Claude Code’s NDJSON output stream in real time, checking every tool invocation against the security policy before the result comes back. If the agent tries to access a blocked path, we kill the process immediately. Not a polite “please don’t do that” in the next prompt. Kill.
Small on purpose
The whole thing is about 3,000 lines across a dozen files. Each file is one concept. `soul.ts` assembles personality. `memory.ts` manages state. `runner.ts` spawns Claude Code. `orchestrator.ts` wires them together.
I wanted this to be something you can read in an afternoon and actually understand. If you want to learn how personality injection, session management, self-learning loops, or security enforcement work at the implementation level, you can trace every line. No hidden state, no magic.
The code is on GitHub. Clone it, read the architecture doc, change a soul file, give it a spin. Still a lot to do, but it’s been a fun project to build.






