Automation Engine

Building HexClaw's Cyber Workhorse: Compile a Sentence into a Self-Healing Sandbox Script That Works Around the Clock

First published on WeChat Originally published in the official technical column of the WeChat account 「hexclaw」 (ID: hexclaw) · Published June 24, 2026 · About 12 minutes
WeChat original ↗

Watch competitors daily and send only updates since the last run to Feishu. Fetch new content from several sources and add it to the knowledge base. Organize these local documents into a weekly report. Repetitive, mechanical, scheduled work used to be scattered across cron scripts, Zapier / IFTTT, and glue code nobody wanted to maintain.

With AI Agents, the entry point should collapse into a sentence: state your intent and let it break down, execute, and finish the task. The result is handled the way you request—sent to you, posted to a group, or saved as a file. One particularly valuable destination is the Agent's own knowledge base and memory. It feeds itself fresh data and learns more about you with use: an around-the-clock cyber workhorse that never complains and keeps acquiring skills.

Between saying a sentence and trusting it to work reliably for months lie many engineering problems. This engine must compile natural language into a self-healing sandbox script and automatically decide whether the task should run as a script or as an Agent。

This article skips the obvious claim that AI can automate things and examines the engineering required for an Agent to take over scheduled work。


The Core Judgment
The first and most important design decision for Agent-driven scheduled work is: should every trigger call a large model?
HexClaw's answer is no. Call an LLM at task creation to compile the request into a script. Every later execution runs deterministic code without a runtime model call.
That decision forces four further questions. This article answers them.

First Decision: Compile Once, Rather Than Reason Every Time

The simplest AI implementation of a scheduled job is to send the description to a model at every trigger and let it work out what to do. It works, but each run incurs a complete LLM call: money, connectivity, latency, and uncertainty. Fetch the ranking might work today and drift tomorrow. An hourly job means 720 conversations a month for the same mechanical operation. At a few thousand tokens per run and typical cloud-model prices, a rough estimate is a few to tens of yuan per month just for one job's heartbeat overhead. More jobs or higher frequency increase the bill linearly. Compilation reduces that runtime model overhead to zero; only creation needs a call. This is an order-of-magnitude estimate, not a measurement; actual costs vary with the model and token volume.

HexClaw divides the timeline into creation and execution. The compiler contract states this plainly (cron/compiler.go):

// JobSpecCompiler 把用户的自然语言任务需求编译成可执行 JobSpec(一次性 LLM 调用)。// 实现要求:// - 创建期一次性消耗 LLM tokens;运行时不再调用 LLM// - 失败必须返回错误(拒绝入库),避免空 Spec 进入运行时// - 输出脚本须通过 validators.go 中的安全校验

Those three requirements determine the engine's character:

  • Cost — spend tokens once at creation, then none even over ten thousand executions. High-frequency runs have zero marginal model-token cost. To be precise, the saving is in runtime tokens. Compilation still uses cloud compute or your local CPU/GPU; that expense moves from every run to one initial call.
  • Offline model independence — scripts use sandbox built-ins and do not depend on an available model at runtime. Compiled jobs keep running if the cloud model is down or its API key expires.
  • Determinism — the same script follows the same logic today and tomorrow, without model-driven drift.
  • Auditability — the output is readable, editable code rather than a black box. That matters especially for unattended automation on your own machine.
Separate understanding intent from executing it in time: tokens at compilation, none at execution. This is the common source of the design's benefits.

What Happens During Compilation

Compilation uses temperature = 0 for stability rather than creativity, asking the model for pure JSON containing runtime, script, and timeout. Two engineering details matter:

  • The compilation model follows your current choice — it is resolved dynamically from your current default on every compilation, not fixed at startup. Switch from a cloud model to local Ollama in the interface and the next compilation follows immediately, without a restart.
  • Failed compilation never enters storage — incomplete scripts do not reach runtime. A visible creation error is better than a silent failure at midnight. The compiler automatically retries 5xx responses, timeouts, and rate limits with exponential backoff, at most three times, without interrupting you.

Several hard rules in the system prompt came from actual failures and determine whether collection scripts survive small page changes:

  • Check the status after every HTTP call; any non-2xx response is a failure. Sending a request does not prove success.
  • Use URLs verbatim; the model must not invent or rewrite them.
  • Prefer structured JSON embedded in the page over mutable CSS classes, which can break after a redesign.

Prompt constraints alone are insufficient. The engine supplies another safeguard: every http_get / http_post gets a browser User-Agent by default. This also came from experience: some sites, including Baidu's trending page in an actual incident, respond to Go's default Go-http-client/1.1 with anti-bot HTML. The script's json_decode then fails immediately at the opening < . Scripts may still override the UA through headers , but the default avoids rejection simply for looking like a crawler.

The compiled script looks like this. Notice that it has no import statements; all capabilities are injected:

def run(): resp = http_get("https://example.com/board", headers = {"User-Agent": "Mozilla/5.0"}) if resp["status"] < 200 or resp["status"] >= 300: return {"status": "error", "error": "fetch non-2xx: %d" % resp["status"]} blocks = re_findall("(?s)<!--data:(.*?)-->", resp["body"]) data = json_decode(blocks[0]) titles = [it["title"].strip() for it in data.get("list", [])] return {"status": "success", "data": {"title": "榜单 " + now()["date"], "content": "\n".join(titles)}}emit(run())

Compiling a fixed script immediately creates four unavoidable questions:

  • Question 1: Why trust newly AI-written code to run on your machine?
  • Question 2: What if the task should not be compiled into a fixed script at all?
  • Question 3: Scripts stay fixed while the world changes. What happens when a website redesign breaks one?
  • Question 4: What if the user's small local model cannot reliably produce well-formed JSON?

Question 1: Why Trust AI-Written Code on Your Machine?

Generating and executing code raises an obvious fear: what if the model, or someone manipulating it, writes os.system("rm -rf ~") on the user's machine?

The guiding assumption is therefore: treat every generated program as untrusted and potentially adversarial input. The model may be manipulated, hallucinate, or simply make a mistake. The sandbox must contain the program even when it is wrong. Every safeguard below follows from that premise.

A common approach is an AST denylist that blocks import os, eval, __import__, and more. The fundamental problem is that you are always chasing escapes. Capabilities start open, and defenders must close each hole. An attacker needs only one overlooked path. The history of bypassed Python sandboxes illustrates this problem.

HexClaw takes the opposite approach, known as capability-based security: no authority by default, with capabilities granted explicitly. The default engine uses Starlark, Google's Python dialect for Bazel, implemented entirely in Go and designed for deterministic, repeatable builds. It has no import, eval, exec, filesystem, or network. By default, a Starlark script can do nothing outside its computation.

Every operation a script can perform is a built-in function explicitly injected by the host (cron/engine_starlark.go):

predeclared := starlark.StringDict{ "http_get": ..., // GET,返回 {"status", "body"} "http_post": ..., "json_decode": ..., "json_encode": ..., "re_findall": ..., "re_sub": ..., "html_unescape": ..., "url_encode": ..., "sha256": ..., "now": ..., "kb_ingest": ..., // 写入本地知识库(in-process) "state_get": ..., "state_set": ..., // 跨运行持久化状态(增量采集 / 去重) "wake_agent": ..., // "有料"才升级成完整 Agent(见问题二) "emit": ..., // 提交最终结果(脚本必须调一次)}
An AST denylist starts with all capabilities and removes them, leaving you chasing escapes.
A structured sandbox starts with none and grants them individually; there is no ambient outside capability to escape into.

The script-validation surface is consequently just three things (validateStarlarkSource):

func validateStarlarkSource(script string) error { f, err := opts.Parse("job.star", script, 0) // 1. 能不能解析 if err != nil { return ... } for _, stmt := range f.Stmts { if _, ok := stmt.(*syntax.LoadStmt); ok { // 2. 禁 load() 加载外部模块 return fmt.Errorf("load() is not allowed in cron scripts") } } if !strings.Contains(script, "emit(") && // 3. 必须产出结果(emit !strings.Contains(script, "wake_agent(") { // 或 wake_agent,门任务可只 wake) return fmt.Errorf("must call emit({...}) or wake_agent(...)") } return nil}

Parse successfully, forbid load() , and require a result through emit() or, for a gate task, wake_agent(). No long prohibited-function list is needed because the capability surface is closed.

Two Resource Guards and a Counterintuitive Network Choice

The sandbox defines callable capabilities. Two resource risks remain, followed by one network tradeoff:

  • Memory exhaustion — an individual HTTP response is capped at 8 MiB, so one uncontrolled large page cannot overwhelm the process.
  • Infinite loops — each Starlark execution is capped at 100 million steps. while True is stopped by the step budget rather than waiting for a timeout. A declared execution timeout, timeout_s, defaults to 300 seconds, with cooperative cancellation as another fallback.
  • Network access——http_get / http_post is the script's only reach outside the process. Should the engine also fence in its network? We tried that, then deliberately removed the restriction. That choice needs explanation.

A textbook solution is an SSRF guard that blocks resolved IPs at dial time: loopback, private ranges, link-local addresses, and cloud metadata addresses such as 169.254.169.254—the credential-theft entry point in the 2019 Capital One SSRF incident. This also guards against DNS rebinding. That was our initial implementation.

We removed it for a specific reason. Many users run transparent proxies in fake-IP mode, such as Clash or Surge. They map public domains into reserved addresses before forwarding internally. The guard saw those addresses and blocked them, so legitimate public-web collection was rejected by our own guard。

That exposed a product boundary: a desktop application running on your machine should share your network context—your proxy, hosts file, and LAN. Local tools commonly access localhost and use the host proxy; IDEs, Docker, and development servers do so daily. Imposing a blanket intranet block obstructs legitimate work without necessarily stopping the real threat.

More fundamentally, a script's authority depends on its injected capabilities, not just on the destination IP. The structured sandbox already limits it to roughly a dozen built-ins. It has no files, shell, or arbitrary sockets; its only external reach is http_get / http_post. Within that design, destination-IP policy is orthogonal. Rather than guessing which ranges to block and breaking normal traffic, we preserve the host's network semantics and concentrate enforcement on the capability surface。

The intended security boundary is the capability surface rather than an IP denylist. With the callable surface constrained, this local tool shares the host network like the browser and terminal.

Starlark is the default but not the only engine. The abstraction lets the scheduler route by Runtime . There is also an optional python3 engine for legacy jobs requiring more expressiveness. It invokes the system interpreter through a shell, so it is not the default. HexClaw's requirement is a single binary with no external dependencies. Pure-Go Starlark behaves consistently across the three major platforms and requires no user installation, making it suitable as the default.


Question 2: What If This Should Not Be a Script?

Compilation suits mechanical fetch–clean–store pipelines. But a task such as summarize and comment on yesterday's news every day loses its purpose when frozen into a script. Summarizing and commenting require fresh reasoning over different content each day.

The engine therefore classifies the task before compiling (cron/agent_mode.go):

// 命中"认知动词" → 每次都要 Agent 现场推理 → agent 模式var reasoningVerbs = []string{ "总结", "分析", "判断", "评估", "筛选出最", "点评", "撰写", "起草", "解读", "建议", "诊断", ...}// 采集 / 下载 / 保存 / 同步 等机械动词,刻意不在表里 → 编译成脚本func ClassifyTaskMode(prompt string) string { lower := strings.ToLower(prompt) for _, v := range reasoningVerbs { if strings.Contains(lower, v) { return RuntimeAgent } } return "" // 默认:脚本模式(零 LLM 执行)}

Mechanical I/O becomes a script with no runtime LLM calls. Cognitive work becomes Agent mode, running a full Agent on every trigger. Classification uses deterministic keywords, not another LLM call, so it is free of model cost and directly testable.

This routing expands scheduled work into two categories:

  • Script mode: mechanical, zero tokens — fetch pages or APIs on a schedule, detect page changes using sha256 and persistent cross-run state_get/set for deduplication, collect only content new since the last run, clean structured data, and write results directly into the knowledge base through kb_ingest .
  • Agent mode: cognitive, a reasoning run with the full toolchain each time — organize local files, compile weekly reports, and summarize, analyze, or comment on collected content. It uses the same eng.Process as chat, so filesystem tools, the knowledge base, Skills, and MCP are all available.

There is a useful capability boundary here too: the Starlark sandbox has no filesystem access, so organizing local documents naturally belongs in Agent mode. Routing aligns cost with a capability boundary: mechanical work stays in the sandbox, while cognitive tasks unlock the full toolchain.

The choice is no longer entirely static. A script can also serve as a cheap gate: each tick costs no tokens, and only meaningful new information causes it to call the built-in wake_agent(context) and temporarily upgrade that run into a full Agent, at most once per hour. The workhorse conserves feed until there is work worth thinking about. This conditional upgrade gate draws on Hermes's wakeAgent. Gate scripts may call wake_agent without emit and still satisfy the output contract.

Why use such simple keyword classification? Because the costs of the two misclassifications differ. The code comments explain that misclassifying a cognitive task as a script produces a script that fails, with self-healing as a fallback. Misclassifying mechanical work as an Agent merely spends additional tokens. With a cheap error in one direction and a fallback in the other, the verb list stays conservative, marking fewer cognitive terms rather than needlessly routing work into paid inference.

Agent mode has two further guards drawn from actual needs:

  • Frequency guard — each Agent run consumes a full conversation, so the minimum interval is one hour. An analyze-every-minute request is rejected during creation with a suggestion to use @hourly / @daily.
  • Hallucination guard — a task promising ingestion must actually call a knowledge_ingest tool. If the Agent claims completion without an ingestion call, the engine records failure. Content that was never stored remains unstored; claiming completion is not a result.

Question 3: Fixed Code Meets a Changing World

A compiled script reflects the page structure at creation time. Websites change: a collector that works today may return nothing next month after the page's <!--data:--> comment format changes.

This is the price of compiling once and spending no runtime tokens: the script stays fixed while the world changes. It is easy to overlook, but determines whether the work can actually be left unattended.

HexClaw answers with self-healing. After consecutive failures reach a threshold, the engine recompiles the script using the failure context (maybeSelfHeal):

const selfHealThreshold = 3 // 连续失败 3 次才触发const selfHealMaxPerWindow = 2 // 24h 内最多自愈 2 次const selfHealWindow = 24 * time.Hour// 把失败原因拼进 prompt,让模型针对性地改enriched := fmt.Sprintf( "%s\n\n[自愈上下文] 该任务此前编译的脚本已连续失败 %d 次,最近错误:\n%s\n"+ "请根据错误原因生成修正后的脚本。", job.SourcePrompt, selfHealThreshold, failCtx)

A code comment gives a particularly apt analogy: JIT compiler deoptimization:

. A JIT first assumes a hot path can use compiled machine code. If runtime evidence breaks an assumption, such as a type change, it returns to the slower interpreter path and optimizes again。
. Here, the fast path is a zero-token script. When its assumption about page structure fails, the engine falls back to an LLM recompilation and produces a new fast-path script.

A realistic example: a ranking collector parses embedded JSON under <!--data:...--> . A redesign changes the marker to <!--board:...-->. The script's re_findall finds nothing, and emit reports an error. After three failed ticks, the engine sends the original request and the latest error, data block missing, back to the model. A replacement script reads <!--board:--> instead. The next tick works; you may never notice the interruption.

Crucially, healing is bounded rather than endless retry:

  • At most two repairs per 24 hours — a permanently unavailable upstream cannot trigger infinite recompilation and token spending. Once the allowance is exhausted, the engine requests human attention. That alert is itself throttled to one per cooldown window, avoiding repeated overnight notifications.
  • Count only failures since the last successful repair — old failures cannot trigger premature healing after a fix. The check uses insertion-ordered row IDs rather than comparisons of datetime strings.
  • Agent tasks do not self-heal this way — they have no compiled script to repair. Repeated failures instead trigger an alert for human intervention.
Healing has a threshold of three consecutive failures, a quota of two repairs per day, and an endpoint of handing unresolved problems to a person. That is what makes unattended operation credible.
The cyber workhorse finds a new route when the world breaks its old one; after two unsuccessful detours, it calls you.

Many similar schedulers can run scripts without an LLM. The distinction is where the script comes from, where it runs, and who repairs it:

  • A common approach — you or an Agent write a shell command or script and schedule it in the machine's full-authority environment, constrained by OS permissions. When it breaks, you repair it.
  • HexClaw — compiles your natural-language request into a structured sandbox with no ambient authority. When it breaks, it recompiles using the error context。
Both schedule code, but the difference is: in the common approach you operate it; HexClaw maintains its own script.

Question 4: A Small Local Model That Struggles with JSON

The design so far assumes the model can write the script. But HexClaw runs locally, and you cannot choose the user's model. People use models such as local glm-4-flash or small Qwen variants. They may write collection logic well enough, but struggle with pure JSON containing a multiline script: perhaps fenced Python instead of JSON, or correct JSON around a script containing an unescaped regex backslash.

The compiler adds three layers of self-correction(cron/llm_compiler.go):

  • Layer 1: Correct the Format — if JSON parsing fails, send the output back with a request for pure JSON without fences. The script already exists, so this extra turn is cheap and usually sufficient.
  • Layer 2: Salvage the Script — if the retry still cannot produce valid JSON, extract the script with a regex from a fenced code block or badly escaped script field, then add fixed metadata. A model that cannot wrap JSON may still have produced usable code.
  • Layer 3: Correct Validation Failures — if the structure is correct but validation fails, return the engine's exact error and request a fix limited to that error.
// 第三层:把"精确的引擎报错"反馈给模型,让它只修这一处user("The Starlark script failed validation with this exact error:\n" + verr.Error() + "\nFix ONLY that error, then re-emit one pure JSON object.")

I like the comment on this third layer: It can recover most small mistakes that changing to a larger model would not fix. A larger model addresses ability; these are slips. Identifying the exact mistake is more useful than changing brains.

Demand fault tolerance from the system, not perfection from the model. Local deployment means accepting the user's model. Accommodating its imperfections is what lets smaller models use this automation.

Connecting Four Answers to One Sentence

Those four questions concern whether the system works. Users experience what happens after they state their intent, and that layer needs engineering too.

LLM compilation can take thirty to fifty seconds. A spinner without explanation is poor feedback. Creation is split into four observable stages streamed to the frontend using SSE (cron/compiler.go):

const ( StageAnalyzing = "analyzing" // 解析需求 / 选 provider StageCallingLLM = "calling_llm" // 调 LLM 生成脚本(最长一段) StageValidating = "validating" // AST + 语法 + 输出契约校验 StagePersisting = "persisting" // 序列化 + 入库)

A small but useful detail is that the backend detects interface support before choosing streaming. Compilers implementing the progress extension emit each stage; other compilers are treated as one stage. They still work, with less detail. Progress is an enhancement rather than an all-or-nothing capability. Format correction also emits a progress message explaining that malformed output is being automatically corrected, so users can distinguish recovery from a hang.

Compilation does not create the task immediately. It first presents a confirmation card(CronJobConfirmCard.vue):

  • Interpretation-source badges — the card explains how the intent was understood: ⌘ Slash (slash command, highest confidence) / ⚡ Recognized (deterministic fast path, no LLM) / 🤖 AI-assisted / 💬 More information needed. Simple rule-recognizable intent never invokes a large model.
  • A plain-language cron preview——0 8 * * * shows every day at 08:00 beside the expression, making the schedule understandable without cron expertise.
  • Editable Before Confirmation, with AI Help — edit the name, timing, action, and delivery destination (chat / push / feishu / discord / wechat) on the card. If the interpretation is wrong, choose the AI editing action to parse it again. A human confirms before the AI creates a job.

The complete chain looks like this:

HexClaw scheduled-task engine architecture
The full chain: 1. creation-time compilation with four stages and three correction layers → SQLite persistence → 2. runtime without LLM calls for scripts, with script and full-Agent engines → results feed knowledge and memory, optionally delivered through Feishu / WeChat / email and other channels → 3. self-healing after three consecutive failures, with at most two repairs per 24 hours.

One actual collection-to-knowledge-base run looks like this:

Automation page: two actual scheduled tasks running Data source: Baidu's live trending list Result: a Baidu TOP20 document automatically written into the knowledge base The time series of documents accumulated by cron in the knowledge base
From top to bottom: 1. two real jobs on the automation page, created from natural language, labeled pure-Go engine with zero dependencies, with successful executions around one second, no runtime LLM calls, and a View Script action; 2. a job's source, the Baidu trending list; 3. its cleaned TOP20 knowledge-base document, indexed into eight chunks with a collection timestamp; 4. a time series whose sources are marked cron-gh-stars / cron-baidu-hotsearch, one document daily, twenty-one so far. The Agent continually feeds itself and its second brain keeps growing. On the next question, the material is already there.

Several lower-level safeguards support this. Creation uses a unified entry point (POST /api/v1/cronjob), with an idempotency key preventing duplicate creation for the same request within five minutes, a quota of thirty active tasks per user, and atomic replacement on update with automatic restoration of the original on failure. On restart, active jobs are restored from SQLite (restarts do not lose jobs). Run history is trimmed to the latest 200 records per task on writes to avoid unbounded growth slowing startup. Failures may have a separate delivery target; otherwise they use throttled alerts. Execution errors and delivery errors are recorded independently and cleared on their respective successes, so one failed notification does not falsely mark the whole job as broken. Scheduling uses one runLoop goroutine to dispatch by next-run time, with a separate goroutine per due job so jobs do not block each other. Cron semantics also need care: @hourly and the union versus intersection of day-of-month and day-of-week can otherwise fire jobs at the wrong time. These details stop an accidental request, restart, or network hiccup from scrambling unattended work.

Two further scheduling problems are handled as well; readers interested only in usage may skip these details. Misfires — if sleep or restart misses a trigger by more than half a period, bounded between 120 seconds and two hours, advance to the next future time. Skip rather than replaying an accumulated backlog to avoid flooding the machine at startup. Within the grace period, run once as normal. One-time reminders are an exception and still ring when late. Overlapping executions — if the previous run is unfinished when the next tick arrives, an in-process running flag skips that tick. Failures remain visible: execution failures use LastError, delivery failures use LastDeliveryError, and per-job history makes problematic tasks identifiable in the automation page.


The Boundaries: What It Cannot Do

Engineering notes need limitations. Know these before creating jobs:

  • Starlark is not full Python. It has no imports or third-party libraries, only the injected built-ins. Jobs needing numpy, pandas, or complex parsing libraries cannot use the default engine; use the optional python3 engine with Python installed, or leave script mode. It suits fetch–clean–store/send I/O pipelines rather than general computation.
  • Self-healing is not universal. It can adapt to small page changes by giving the model a new error, but cannot fix a closed website, a new login wall, or an IP block. Once the daily allowance is exhausted, it stops and alerts a person. That is an intentional boundary.
  • The healing window is held in memory. The two-per-day counter resets on a sidecar restart. For this desktop application, restarting is uncommon and the worst case is some additional repair calls; the count stays bounded within one process lifetime. Strict persistence is a known simplification.
  • This is a single-machine desktop engine, not a server. Concurrency is approximately one. Capacity concerns job counts, thirty per user by default, and per-run bounds of 8 MiB responses, 100 million steps, and a default 300-second timeout, rather than QPS. A multi-tenant service would require process/container isolation and replacing the local runLoop scheduler with a distributed delay queue. The present reentrancy guard is an in-process running flag preventing only local overlap. Multiple replicas would each trigger the same tick without an additional external lock or deduplication mechanism. That is a different engineering project outside this article.
  • Classification uses keywords rather than an LLM. Mixed requests involving both collection and analysis can be misclassified. Human confirmation and self-healing contain the cost, but this is not deep semantic classification.
  • Agent mode spends a full conversation each run. The enforced minimum one-hour interval suits low-frequency cognitive work such as daily summaries or weekly reports. High frequency plus inference is not free.

This Is Work an AI Agent Should Handle

The technical details support one point: scheduled, repetitive, mechanical tasks should not require human attention or fresh model reasoning every time. Taking them over is an Agent's proper job.

Looking back, each design layer arose from a problem in the previous one: compile into a script to reduce cost and uncertainty → ask whether AI code can run safely on a user's machine, then add a sandbox with no ambient authority (we also tried fencing its network, then removed the guard after it blocked legitimate traffic through transparent proxies, settling the boundary on callable capabilities rather than IPs) → encounter tasks unsuited to compilation, then add mechanical/cognitive routing → scripts break as the world changes, so add self-healing → small local models struggle with JSON, so add three correction layers. Each layer responds to a concrete cost introduced earlier.

Taking over work requires more than a successful demo. Anyone can generate code, run it once, and show a screenshot. The difficult part is infrastructure that can be left unattended and safely forgotten:

  • Feed the Agent itself — collected and organized results return to its knowledge and memory, so the next time you ask, it already knows. A simple tool pushes results to you; an Agent also feeds them back into itself. That is the feedback loop beyond RSS or Zapier alone.
  • Low cost — compile once, then use no runtime tokens, rather than increasing model costs linearly.
  • Local, with model-independent execution — deterministic code runs in the structured sandbox, with data remaining on your machine.
  • Contained execution on your machine — capability-based authority granted explicitly to a default-empty sandbox, rather than an expanding list of escape patches.
  • Repair when the world changes — recompile broken scripts with error context, requesting human help only when repair fails.

Together, these turn scheduled Agent automation into something that can actually be delegated. Notifications are only one output. The deeper value is maintaining an increasingly current second brain over time.

Compile intent into code and run it on your machine. Use the restricted capability surface for containment, routing for affordable execution, and self-healing for changing conditions, then feed results into the Agent's knowledge and memory.That is HexClaw's engineering answer to scheduled Agent automation: maintain a continuously refreshed brain through the work it performs.

The best automation starts with one sentence,
then lets you stop thinking about it, because it runs and attempts its own recovery.


Postscript: What Has Grown Since the Previous Article

Since Making an AI Agent Speak Uyghur: HexClaw v0.4.0 Localization in Practice, the product has reached v0.4.6 and added or improved the following, some to be covered separately:

  • 🔌 Data connectors — GitHub / Yuque cloud notes / Feishu Docs / email integrations, with encrypted credential storage
  • 🧩 Workflows — webhook triggers and task graphs
  • 🪪 Unified permission gates and an unattended risk adviser — risk assessment before automated actions
  • 🧠 Editable assistant personality (SOUL) and memory library
  • 📝 Prompt management — store frequently used prompts for reuse
  • 🎯 Skill / Prompt retrieval — manually search and attach relevant Skills and prompts in chat; automatic retrieval already existed
  • 🧪 Evaluation framework — regression checks for Agent behavior

Data connectors with scheduled collection, and webhook workflows, will extend this article's feedback-loop theme in later articles.


Further Reading — Earlier articles:

  1. HexClaw vs OpenClaw / Hermes — Three paths for local AI Agents in 2026
  2. Claude Code SOP — Design-driven development, testing feedback, and multi-Agent collaboration
  3. Architecture Deep Dive — One person, Claude Code, five months, six repositories
  4. The Thinking Process — The last mile for Qwen / Gemma / DeepSeek-R1
  5. Complete Guide — From zero to a working local AI Agent in five minutes
  6. Former Leader Skill — Distilling a leader into an AI Skill
  7. From Raising Lobsters to Raising Crabs — Product launch and the OpenClaw starting point

References

Upstream repositories are authoritative for the code and behavior described here. Checked on 2026-06-23.