I am a garlic farmer from South Korea. Not a developer. Not an engineer. Just a farmer who grows garlic.
But with AI by my side, I built something I still can't fully believe — a personal AI agent system running entirely on my Android phone using Termux. I don't even understand half of what I built, honestly. Every single step was done in Korean, and AI helped me translate. So forgive me if the English feels a bit rough. That roughness is part of who I am.
This is the structure my agent analyzed and organized. I only understood the full picture after seeing it laid out like this. The architecture is complex, but it carries my personal design philosophy — everything runs on one phone, nothing more.
config.json — The Heart of System Configuration
config.json holds the entire configuration for garlic-agent. Every program reads this file to operate.
The security section defines what the agent can and cannot do. allowed_dirs lists the folders the agent is permitted to read and write: /garlic-agent/, /garliclang_full/, ~/gdrive_backup/, /storage/emulated/0/Download/ — only these 4 directories. If the agent tries to touch anything outside, security.py blocks it. For example, if the agent tries to read /etc/passwd, it gets "Access Denied."
blocked_commands is the list of forbidden commands. rm -rf / (delete entire system), rm -rf ~ (delete home folder), mkfs (format disk), dd if= (overwrite disk). These dangerous commands are blocked no matter what. Database commands are blocked too: DROP TABLE, DELETE FROM, ALTER TABLE, TRUNCATE — preventing the agent from wiping knowledge.db tables. Even sqlite3 direct execution is blocked, forcing the agent to access the DB only through tool:search.
allowed_domains lists the servers the agent can call APIs on. Only LLM servers like api.cerebras.ai, api.groq.com, generativelanguage.googleapis.com (Gemini), and api.minimax.io are allowed. The agent cannot connect to any other websites.
key_patterns defines API key patterns. Strings matching patterns like nvapi-, AIzaSy, gsk_ are automatically masked if they appear in agent responses. This prevents accidental API key exposure.
The providers section lists available AI companies. Each provider has model names and max_context (maximum tokens it can process). DeepSeek had max_output limited to 8192, which was the cause of output truncation earlier.
The agent section is the core agent configuration. max_loops 20 means the agent can call tools up to 20 times per task — infinite loop prevention. exec_timeout 30 means any tool:exec command that doesn't finish within 30 seconds gets killed. max_write_size 102400 means maximum file size per tool:write is 100KB — preventing the agent from creating 1GB files. context_turns 35 means only the most recent 35 turns of conversation history are sent to the LLM. max_tokens 38400 is the maximum token count for LLM responses.
The web section configures the web server. It runs on port 8080 with token garlic2026 as the authentication token, required when accessing localhost:8080 from the browser.
The verification section handles validation settings. llm_verify false means the LLM self-verification feature is off — it would double API costs. db_search true means RAG search is enabled. garliclang true means GarlicLang execution is active. strict normal means security level is standard.
tool_limit 5 means the agent can use tools up to 5 times per turn. This is why DeepSeek got blocked earlier when it exceeded 7 calls.
security.py — The Security Guard
If config.json sets the rules, security.py enforces them. Every time the agent tries to execute a command via tool:exec, security.py inspects it. Matched against blocked_commands? Blocked. Accessing paths outside allowed_dirs? Blocked. Suspicious patterns? Blocked. It stands between the agent and the actual system like a guard.
tools.py — The Tool Executor
When the agent writes [tool:read ~/garlic-agent/SOUL.md], agent.py parses it and passes it to tools.py. tools.py actually reads the file and returns the result. The execution logic for all 9 tools (read, write, exec, patch, search, garlic, screen, app) lives here. The _safe_backup function that automatically creates backups during tool:write is also inside tools.py.
agent.py — The Brain
The core of the system. Over 1,200 lines. Here's what it does: when the user sends a message from the web UI, web.py receives it and passes it to agent.py. agent.py reads SOUL.md, reads TOOLS.md, combines the user message with conversation history, and sends everything to the LLM. When the LLM responds, it parses the response for tool calls like [tool:exec ...]. If found, it executes them via tools.py, sends results back to the LLM for the next response. This loop can repeat up to max_loops 20 times.
The lines 1114, 1117, and 1119 that were modified control tool result buffer sizes. Line 1114 is the web UI display size (8,000 chars), line 1117 is the size sent to the LLM (20,000 chars), and line 1119 is the tool log size (4,000 chars).
web.py — The Front Door
A 5-line shim file. from web_server import main — the actual logic is in web_server.py. During yesterday's modularization, the 1,700-line web.py was split into 7 modules: web_server.py (boot), web_state.py (state management), web_auth.py (authentication), web_routes_admin.py (admin API), web_routes_docs.py (docs API), web_routes_gl.py (GarlicLang execution), web_routes_chat.py (chat API). When the browser hits localhost:8080, these modules handle the requests.
garliclang_bridge.py — The GarlicLang Bridge
Connects agent.py to the GarlicLang interpreter. When the agent calls [tool:garlic test.gl], garliclang_bridge.py passes the GL file to the interpreter, executes it, and returns PASS/FAIL results. GarlicLang itself is installed separately in ~/garliclang_full/.
search.py — The Search Engine
Activated when tool:search is called. It performs hybrid search. First, FTS5 keyword matching pulls the top 10 results. Then it sends the query to llama-server, converts it to a 768-dimensional vector, and pulls 5 more via cosine similarity. Duplicates are removed and up to 8 results are returned. This is why searching for "자율수정" (self-repair in Korean) also finds English documents containing "self-repair" — thanks to vector search.
SOUL.md — The Constitution
Defines the agent's behavioral rules in a layered structure. Layer 1 has the highest authority: "Tool results are truth. No hallucination." The agent reads this file every turn. Rules like "Don't use tools in chat mode" and "Only use tools in work mode" are written here. DeepSeek using tools in chat mode today was a violation of this constitution.
TOOLS.md — The Tool Manual
Contains exact usage instructions for all 9 tools. The agent reads this every turn before calling tools. Rules like "max 5 tools per turn" and "stop after 3 consecutive failures" are here. The GWRITE wrapper rule added today is also documented here.
HANDOVER.md — The Handover Document
A document for new AIs joining the system. Contains system philosophy, user profile, and past lessons. Information like "the user doesn't know coding but understands the system" and "the agent gets scolded when it makes mistakes" — real battlefield wisdom.
startall.sh — The Ignition Key
Like a car's ignition key. Run this one script and web.py, llama-server, watcher.sh, watchdog.sh, and autosnap.sh all start up. It cleans logs, kills leftover processes, and starts all services in order. Open a new Termux session, type startall, and everything comes alive.
watchdog.sh — The Auto-Recovery Worker
Checks every 3 seconds if web.py is alive. If dead, it automatically restarts it. The web.py PID changes observed during today's testing were traces of watchdog doing its recovery job.
watcher.sh — The File Watcher
Uses inotifywait to monitor file changes. When .py, .md, .json, .gl, or .html files are modified inside ~/garlic-agent/, it detects the change and calls auto_index.py, which re-indexes the changed file into knowledge.db.
autosnap.sh — The Auto-Photographer
Similar to watcher.sh but with a different role. When files change, it calls gsave.sh to save the pre-change content to the file_snapshots table. If the same file changes again within 3 seconds, it skips the duplicate save. Every modification automatically gets a snapshot taken.
anchors.json — Time Bookmarks
Attaches labels to specific points in time. PRE_WRITE means "right before file modification," AUTOSNAP means "auto-snapshot moment," RECOVERY means "after successful operation," GUARD_AUTO means "security block moment," ROLLBACK means "restoration point." Each anchor links to a snapshot_id, so running 앵커복원 N reverts files to that point in time.
fact_ledger.json — The Work Journal
Recorded every time the agent executes a tool. Maintains up to 200 entries. Logs "what time, which tool was used, and what the result was." Used later to check "what did the agent do today."
scripts/ folder — The Tool Shed
Contains operational scripts: backup.sh (full backup), gsave.sh (snapshot save), garlic_undo.sh (emergency restore), clean_bak.sh (.bak cleanup), regression_test.sh (regression test), http_golden_test.py (HTTP test), phase456_verify.gl (GL verification), and more.
skills/ folder — The App Store
12 skills exist as individual folders. Each folder contains a SKILL.md with trigger keywords and GL scripts to execute. Say "show backup list" and the backup-manager skill matches, executing list_backups.gl. To add a new skill, just create a folder and write one SKILL.md. That's it.
How Everything Connects
All these files are organically connected. User sends message from web UI → web.py receives it → agent.py reads SOUL.md + TOOLS.md, sends to LLM → parses tool calls from response → tools.py executes (security.py validates) → results sent back to LLM → if files changed, watcher.sh + autosnap.sh detect it → knowledge.db updated + snapshot saved → if problems arise, restore via anchors.json. The entire thing is one circular system.
Full System Diagram
╔══════════════════════════════════════════════════════════════════════════╗
║ garlic-agent Full System Diagram ║
║ (Android Termux Environment) ║
╚══════════════════════════════════════════════════════════════════════════╝
┌─────────────┐
│ User (Phone)│
│ Browser │
└──────┬──────┘
│ localhost:8080
▼
╔══════════════════════════════════════════════════════════════════════════╗
║ Web Server Layer ║
║ ║
║ ┌─────────┐ ┌──────────────┐ ┌──────────────┐ ┌────────────────┐ ║
║ │ web.py │→│ web_server.py │→│ web_auth.py │→│ token verify │ ║
║ │ (5-line │ │ (207L boot) │ │ (auth logic) │ │ garlic2026 │ ║
║ │ shim) │ └──────┬───────┘ └──────────────┘ └────────────────┘ ║
║ └─────────┘ │ ║
║ ├──→ web_routes_chat.py (chat: 8 handlers) ║
║ ├──→ web_routes_docs.py (docs: GET 7 + POST 2) ║
║ ├──→ web_routes_admin.py (admin: 6 handlers) ║
║ └──→ web_routes_gl.py (GarlicLang execution) ║
╚══════════════════════════════╤═══════════════════════════════════════════╝
│ user message passed
▼
╔══════════════════════════════════════════════════════════════════════════╗
║ Brain Layer — agent.py (1200+ lines) ║
║ ║
║ ┌────────────────────────────────────────────────────────────────┐ ║
║ │ 1. Read SOUL.md (behavioral rules = constitution) │ ║
║ │ 2. Read TOOLS.md (tool usage manual) │ ║
║ │ 3. Assemble conversation history (recent 35 turns) │ ║
║ │ 4. Attach RAG search results (knowledge.db → max 8 docs) │ ║
║ │ 5. Send to LLM ─────────────────────────────────────┐ │ ║
║ │ 6. Receive response ←────────────────────────────────┘ │ ║
║ │ 7. Parse tool calls ([tool:exec ...], [tool:write ...], etc) │ ║
║ │ 8. Execute tools → get results → send back to LLM (max 20) │ ║
║ └────────────────────┬───────────────────────────────────────────┘ ║
║ │ ║
║ ┌────────────────────┴───────────────────────────────────────┐ ║
║ │ Buffer Settings (modified today) │ ║
║ │ L1114: Web UI display → :8000 │ ║
║ │ L1117: LLM delivery → 20,000 chars │ ║
║ │ L1119: Tool log → :4000 │ ║
║ └────────────────────────────────────────────────────────────┘ ║
╚══════════╤══════════════════════╤═══════════════════════════════════════╝
│ tool calls │ API calls
▼ ▼
╔════════════════════════╗ ╔═══════════════════════════════════════════╗
║ Security Layer ║ ║ LLM Providers (config.json providers) ║
║ ║ ║ ║
║ ┌──────────────────┐ ║ ║ ┌─────────┐ ┌──────────┐ ┌──────────┐ ║
║ │ security.py │ ║ ║ │ Gemini │ │ DeepSeek │ │ MiniMax │ ║
║ │ │ ║ ║ └─────────┘ └──────────┘ └──────────┘ ║
║ │ Checks: │ ║ ║ ┌─────────┐ ┌──────────┐ ┌──────────┐ ║
║ │ · blocked_cmds │ ║ ║ │ Groq │ │ NVIDIA │ │ Cerebras │ ║
║ │ · allowed_dirs │ ║ ║ └─────────┘ └──────────┘ └──────────┘ ║
║ │ · allowed_域 │ ║ ║ ║
║ │ · key_patterns │ ║ ║ Config: max_context, max_output / model ║
║ └────────┬─────────┘ ║ ╚═══════════════════════════════════════════╝
║ │ passed ║
╚═══════════╪════════════╝
▼
╔══════════════════════════════════════════════════════════════════════════╗
║ Tool Execution Layer — tools.py ║
║ ║
║ ┌──────────┬──────────┬──────────┬──────────┬──────────────────────┐ ║
║ │tool:read │tool:write│tool:exec │tool:patch│tool:search │ ║
║ │file read │file write│cmd exec │file patch│DB search │ ║
║ │ │+auto bak │30s limit │+auto bak │FTS5+vector │ ║
║ ├──────────┼──────────┼──────────┼──────────┼──────────────────────┤ ║
║ │tool: │tool: │tool:app │ │ │ ║
║ │garlic │screen │app launch│ │ │ ║
║ │GL exec │capture │ │ │ │ ║
║ └────┬─────┴─────┬────┴──────┬──┴──────────┴──────────────────────┘ ║
║ │ │ │ ║
║ ▼ │ ▼ ║
║ garliclang_ │ search.py ──→ knowledge.db ║
║ bridge.py │ │ ║
║ (GL interpreter) │ ├──→ FTS5 (keyword) ║
║ │ │ └──→ llama-server :8081 (vector) ║
║ ▼ │ ║
║ [verify] PASS/ │ ║
║ FAIL │ ║
║ │ ║
║ ┌───────────┘ ║
║ ▼ ║
║ _safe_backup → archive/bak/ (timestamp.bak) ║
║ → file_snapshots (knowledge.db) ║
║ → anchors.json (PRE_WRITE anchor) ║
╚══════════════════════════════════════════════════════════════════════════╝
╔══════════════════════════════════════════════════════════════════════════╗
║ Background Process Layer — started by startall.sh ║
║ ║
║ ┌──────────────────┐ ┌───────────────────┐ ┌─────────────────────┐ ║
║ │ watchdog.sh │ │ watcher.sh │ │ autosnap.sh │ ║
║ │ │ │ │ │ │ ║
║ │ Every 3 sec: │ │ inotifywait: │ │ inotifywait: │ ║
║ │ web.py alive? │ │ detect file chg │ │ detect file chg │ ║
║ │ dead → restart │ │ │ │ │ │ │ ║
║ │ │ │ ▼ │ │ ▼ │ ║
║ │ llama-server │ │ auto_index.py │ │ gsave.sh │ ║
║ │ alive? │ │ → knowledge.db │ │ → file_snapshots │ ║
║ │ dead → restart │ │ update docs │ │ save snapshot │ ║
║ │ │ │ refresh embed │ │ → anchors.json │ ║
║ │ │ │ │ │ AUTOSNAP record │ ║
║ └──────────────────┘ └───────────────────┘ └─────────────────────┘ ║
║ ║
║ ┌──────────────────────────────────────────────────────────────────┐ ║
║ │ llama-server (:8081) │ ║
║ │ nomic-embed-text model (768-dim) │ ║
║ │ Role: text → vector conversion (embedding generation) │ ║
║ │ Used by: search.py (search), auto_index.py (indexing) │ ║
║ └──────────────────────────────────────────────────────────────────┘ ║
╚══════════════════════════════════════════════════════════════════════════╝
╔══════════════════════════════════════════════════════════════════════════╗
║ Data Storage Layer ║
║ ║
║ ┌─────────────────────────────────────────────────────────────────┐ ║
║ │ knowledge.db (225MB) │ ║
║ │ │ ║
║ │ ┌─────────────────────┐ ┌──────────────────────────────┐ │ ║
║ │ │ docs (6,611) │ │ file_snapshots (2,887) │ │ ║
║ │ │ documents + embeds │ │ snapshots + timestamps │ │ ║
║ │ │ + FTS5 full-text │ │ + path index │ │ ║
║ │ └─────────────────────┘ └──────────────────────────────┘ │ ║
║ └─────────────────────────────────────────────────────────────────┘ ║
║ ║
║ ┌──────────────────┐ ┌──────────────┐ ┌─────────────────────────┐ ║
║ │ anchors.json │ │ fact_ledger │ │ config.json │ ║
║ │ time bookmarks │ │ .json │ │ full configuration │ ║
║ │ PRE_WRITE │ │ tool exec │ │ security/providers/ │ ║
║ │ AUTOSNAP │ │ log (200) │ │ agent/web/verify/ │ ║
║ │ RECOVERY │ │ │ │ session/version │ ║
║ │ ROLLBACK │ │ │ │ │ ║
║ └──────────────────┘ └──────────────┘ └─────────────────────────┘ ║
║ ║
║ ┌──────────────────────────────────────────────────────────────────┐ ║
║ │ archive/bak/ .bak backup files (keep latest 3) │ ║
║ │ scripts/ operation scripts + GL files │ ║
║ │ skills/ 12 skills (SKILL.md + GL-DIRECT) │ ║
║ │ /storage/.../Download/ tar.gz project backups │ ║
║ │ Google Drive rclone remote backup │ ║
║ └──────────────────────────────────────────────────────────────────┘ ║
╚══════════════════════════════════════════════════════════════════════════╝
╔══════════════════════════════════════════════════════════════════════════╗
║ Core MD Document Layer (agent reads every turn) ║
║ ║
║ ┌────────────┐ ┌────────────┐ ┌─────────────┐ ┌──────────────────┐ ║
║ │ SOUL.md │ │ TOOLS.md │ │ HANDOVER.md │ │ GL_SYNTAX.md │ ║
║ │ Constitu- │ │ Tool │ │ Handover │ │ GarlicLang │ ║
║ │ tion │ │ Manual │ │ Document │ │ Grammar │ ║
║ │ Layer 1~5 │ │ 9 tools │ │ For new AI │ │ 32 commands │ ║
║ │ Supreme │ │ Call rules │ │ Philosophy │ │ 15 verify types │ ║
║ └────────────┘ └────────────┘ └─────────────┘ └──────────────────┘ ║
║ ┌─────────────────┐ ┌──────────────────┐ ┌────────────────────────┐ ║
║ │ BACKUP_POLICY.md│ │ AGENT_HANDBOOK.md│ │ GARLICLANG_SPEC.md │ ║
║ │ Backup policy │ │ Agent mistake │ │ GarlicLang design │ ║
║ │ 351 lines │ │ collection │ │ philosophy │ ║
║ │ 5-stage restore │ │ Common errors │ │ Verify-centric DSL │ ║
║ └─────────────────┘ └──────────────────┘ └────────────────────────┘ ║
╚══════════════════════════════════════════════════════════════════════════╝
5-Stage Restore System
① ② ③ ④ ⑤
[Anchor DB Snapshot .bak File tar.gz Google
Restore N] file_snapshots archive/bak/ Download/ Drive
anchors.json (per second) (per file) (project) (remote)
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
Fastest Restore by garlic_undo.sh tar xzf rclone
Web UI click snapshot_id 1-line restore full restore download
Garlic CMD Flow (Natural Language → Execution)
User: "show backup list"
│
▼
typo_guard (typo correction)
│
▼
skill_loader (trigger match: "backup" → backup-manager)
│
├─ GL-DIRECT script exists? ──→ YES ──→ execute immediately (API cost 0)
│ scripts/list_backups.gl
└─ NO ──→ pass to LLM ──→ generate GL ──→ execute ──→ show result
│
▼
save to scripts/ (next time → GL-DIRECT)
This is the rough structure of garlic-agent. The version goes up every day, so this is the latest architecture of my personal project as of now. I am not a developer. I just had a problem I wanted to solve, and AI helped me solve it, one piece at a time, all in Korean, all on one phone.
Thank you for reading this far. If the English feels clumsy in places, that is the honest voice of a Korean garlic farmer who built something with his own hands and a little help from AI.
Top comments (0)