Your AI Agent is Stuck in a Loop. Here’s the Memory Layer That Breaks It and Saves You Money

Indexing workflow

Every time you open a new chat in Cursor, VS Code, Antigravity and even Claude Desktop, you paste your codebase back in. Or you let the IDE do it automatically, same result. You’re burning context tokens on files the agent already “knew” ten minutes ago in a different window. The agent re-reads my_models.py, your_script.js, their_script.ts, index.html and the three service files it touched yesterday, just to orient itself before answering your actual question.

That’s not a context window problem. It’s a memory architecture problem. There’s no persistence layer between sessions or IDEs. The fix isn’t a bigger context window; it’s not sending the same data twice.

In a real test session with Copilot, after three exchanges with four unnecessary re-queries, zerikai_memory’s tool results accounted for just 1.6% of a 128K context window. A raw file-chunk retrieval setup doing the same work would burn ~40% of that window before you got to your actual question. On the DeepSeek side, locking the project brief as a fixed system prefix means every query hits the KV cache at $0.0028/M tokens instead of $0.14/M, a 50x cost reduction on the largest token block per call.

That’s the exact problem zerikai_memory was built to solve. (Open source)

Hi, I’m Enrique, an AI software engineer with over 30 years of tech experience. I built zerikai_memory after diagnosing a structural bottleneck in my own workflows: context loss between sessions, redundant re-injection across IDEs, and rising per-query costs that compound with every sloppy retrieval. The rising cost of AI IDE subscriptions made the economics impossible to ignore, but the underlying architecture problem was already there. zerikai_memory is the engineering response to that diagnosis, built for developers who want precision and cost control.

What It Actually Does

Indexing workflow

zerikai_memory is a local MCP server. Any IDE that supports the Model Context Protocol (Cursor, pi, Antigravity, Copilot, and even Claude Desktop) can connect to it. When you run scan_workspace, it walks your project using tree-sitter to parse source files into individual code entities: Functions, Classes, Methods, HTML components.

Each entity gets its own embedding in a local ChromaDB collection, along with structured metadata: Language, Return type, Parent class, Line numbers, Param count, Decorators, Docstring presence.

That last part matters. Most RAG setups (including the ones built into commercial IDEs) chunk files by token count or line count, which means a function might get split across two chunks, or buried under 500 lines of imports. This is a corner being cut at your expense: you pay for the tokens, the agent gets noise. zerikai_memory treats each function as the atomic unit instead. extract_entities in code_indexer.py returns a list of CodeEntity objects, one per parsed symbol: clean signal, no noise.

When your agent queries memory, along with the raw answer, it gets inline source citations back:

Sources: `extract_entities` #code_indexer.py:184 (0.35)
`_extract_js_function` #code_indexer.py:595 (0.81)
`_extract_js_like` #code_indexer.py:543 (0.89)
`CodeEntity` #code_indexer.py:156 (0.83)
`_extract_html` #code_indexer.py:907 (0.84)

(raw answer synthesized from those sources)

The IDE agent doesn’t hunt for the function; the citations give it the exact file, line number, and L2 distance score. #code_indexer.py:184 is plain text that renders in every IDE and is clickable in VS Code Copilot, so you can jump straight to the source too. No additional search, no extra API call.

The Indexing Cost Is Zero

tree-sitter is a deterministic local parser. It runs entirely on your machine, no API calls, no LLM involvement. Parsing and embedding your codebase costs $0.00.

The only thing that costs money is the project brief: a 9-section structured summary (Overview, Technical Stack, Core Architecture, Primary Conventions, Purpose, Key Files, Data Flow, Development & Testing, Future Roadmap). That brief is generated once after the first scan using either the DeepSeek API or Ollama. With DeepSeek, it’s a few cents. With Ollama, it’s free. After that, the brief is locked and cached.

Why lock it? Because the brief is the fixed prefix of every system message sent to DeepSeek. DeepSeek’s KV cache means identical prefixes are stored server-side after the first call, so subsequent queries hit the cache at $0.0028/M tokens instead of $0.14/M tokens. That’s a 50× cost reduction on the largest token block per query. Intentionally keeping the brief stable is what makes that discount work consistently.

If you update the Architecture or implement a large feature, you can regenerate the brief by asking the MCP server to update_brief.

Auto-Routing: Paying Only for What Needs It

Not every query deserves a cloud LLM. A lookup like “where is process_data defined?” is a short, specific question that Ollama handles fine locally. An architectural question like “explain how the query routing pipeline decides between DeepSeek v4-flash and v4-pro” is a 20-word query touching design decisions, and that earns the cloud call.

The routing logic in _should_use_cloud() runs a 4-step priority chain on every incoming query:

  1. Explicit override: use_cloud=True or use_cloud=False passed directly; short-circuits everything else.
  2. Architectural keyword escalation: if the query contains design/architecture terms (configured in config.py), route to DeepSeek.
  3. Length escalation: queries of 40 words or more route to DeepSeek.
  4. Default fallback: fall back to whatever MEMORY_MODE is set to in .env (local, cloud, or hybrid).

Within the DeepSeek path, _select_model picks between v4-flash and v4-pro based on query complexity. Short cloud queries get the cheaper model; heavier ones get the capable one. You’re not paying v4-pro rates for a function lookup.

Tip: start in cloud mode (DeepSeek API), then you can try hybrid and then local (Ollama).

Lexical Re-ranking: Fixing the Semantic Similarity False Positive Problem

Lexical re-ranking

Semantic similarity alone has a well-known failure mode: a generic function that describes the same concept as your query can outscore the specific function you actually want. A file summary mentioning “parsing” might beat parse_entities() in cosine space if the embeddings aren’t tight.

zerikai_memory adds a re-ranking pass on top of ChromaDB’s semantic results. After distance filtering removes weak matches, survivors are re-scored by weighted keyword overlap in entity names and docstrings:

score = (1 / distance) + (keyword_hits × LEXICAL_RERANK_WEIGHT)

A function named extract_entities will outrank a vague file-level summary that happened to embed close to the query. No results are dropped; it’s a pure reorder. The weight is configurable via ENABLE_LEXICAL_RERANK in .env. It’s off by default; turn it on if you’re seeing generic summaries crowding out specific function hits.

The reranking test in the inline citations example above shows it working:

  • VS Code Query:
{
  "workspace": "zerikai_memory",
  "user_query": "extract function for parsing code entities from source files. Show me the Sources table."
}

Agent chat window screen grab

Returns extract_entities at distance 0.35 as the top hit, with the JS and HTML extractors behind it. Notably, _extract_markdown didn’t appear despite sharing vocabulary, which is exactly the false positive problem the re-ranker exists to solve.

Context Window Impact

Entity-level indexing matters most when the agent is sloppy. In a test session with Copilot, after three back-and-forth exchanges (including four re-queries it didn’t need), the context window sat at 17.1K of 128K tokens, just 13%. The tool results from Zerikai accounted for 1.6% of that total.

If zerikai returned raw file chunks instead of individual entities, a single retrieval might pull 500 lines of a file to find one function. Twelve retrievals across three turns could burn north of 40% of the window before you even got to your actual work.

Entity-level (zerikai, tested) Raw file chunks (estimated)
Tool results after 3 turns, 4 calls 1.6% of window (~2K tokens) ~40% of window (~50K tokens)
Space left for actual work ~111K tokens ~28K tokens
Agent re-queries Negligible cost Amplifies the burn

When the agent re-queries four times because it summarized too aggressively, entity-level indexing means it burned 800 tokens, not 50,000. The real cost of a bad agent layer is multiplied by how much data each retrieval returns (one of the reasons I migrated to pi.dev, but that’s another write-up).

Small retrievals save you twice: they stretch your IDE’s token quota and they shrink your DeepSeek bill. Pay for the brief once. Query your own code for free.

How Agents Display Source Citations

zerikai memory returns the full #file:line (distance) in every response. But each agent publisher decides how much of it to surface. The same query, same tool, four different agents:

  • Example: “Sources: _truncate_for_brief #main.py:401 (0.62)”
Agent Calls Failed Showed L2 Notes
Antigravity (Gemini) 1 0 Yes Re-rendered inline text as a table
Claude Desktop 1 0 Yes Showed full citation in body
Copilot 5 0 No Re-queried 4x, hid distance from user until asked
Pi (inside workspace) 1 0 Yes One clean call when in the project path

The data is always there in context; the agent uses #file:line (distance) for reasoning regardless.

Ask: “show me the full sources with distances” and it surfaces the complete line. What you see is filtered by your agent’s display layer, not by zerikai memory.

Workspace Isolation and .memignore

Every project gets its own ChromaDB sub-collection, its own SQLite records, and its own brief file. Queries for project_a don’t pull from project_b. Workspaces are identified by normalized filesystem path with deterministic UUIDs, so the same project opened from different IDE windows or different paths resolves to the same memory store.

Before any file hits the indexer, .memignore filters it out. It works identically to .gitignore: glob patterns, # comments, blank lines ignored. node_modules/, .git/, venv/, compiled output: configure it once per workspace, it’s enforced on every scan.

Scans are idempotent. Re-scanning the same file overwrites its existing records using deterministic hashing. Stale entries (files deleted or added to .memignore since the last scan) are automatically purged. Run scan_workspace as often as you want; no duplicates accumulate, no ghost records from renamed files.

Be discriminative with the files and folders added to .memignore. We tend to save test/ or research/, and other dated resources, which add unnecessary noise to the ChromaDB index. Add those types of folders and files to .memignore to ignore them during the scan.

Local Mode: Zero Cost, No Data Leaves Your Machine

If you’re working on a private codebase or just don’t want to touch a cloud API, set MEMORY_MODE=local in .env and point zerikai_memory at a running Ollama instance. Everything runs locally: parsing, embedding, retrieval, and synthesis. The inline citations still work. The project brief still generates. Auto-routing routes everything to Ollama. Token tracking still logs usage (it just logs zero cost).

This makes the local mode genuinely useful for confidential projects, not just a fallback. The main trade-off versus DeepSeek is synthesis quality on complex architectural queries, as Ollama models are capable but won’t match v4-pro on multi-hop reasoning about your codebase design.

What It Doesn’t Do

Worth being explicit: zerikai_memory doesn’t replace your IDE’s built-in context management for the file you’re actively editing. It’s additive; what it eliminates is the need to re-inject project-wide context at the start of every new session. It also doesn’t currently support all languages tree-sitter handles; Python, JS/TS, HTML, CSS, and Markdown are covered, and others are on the roadmap.

The project brief is intentionally locked after the first scan. If your architecture changes significantly, you need to trigger update_brief via the MCP server. That’s a manual step, and it’s a deliberate trade-off for KV cache stability over automatic freshness.

Stack

  • Python: MCP server, query routing, brief synthesis
  • ChromaDB: local vector store (file-based, stored in .brain/vector_db/)
  • zerikai.db: SQLite workspace registry and token tracking (stored in .brain/zerikai.db)
  • tree-sitter: deterministic local code parser, zero API cost
  • DeepSeek: cloud LLM (v4-flash and v4-pro), with KV cache optimization
  • Ollama: local LLM for zero-cost operation

Getting Started: Index zerikai_memory itself as your first workspace. It’s the fastest way to see how entity-level indexing, lexical re-ranking, and inline citations actually work before applying them to your own projects. If your language isn’t supported yet, search PyPI for tree-sitter-<language> to find the grammar bindings, then use the MCP server itself to guide you through adding it to code_indexer.py. Rescan, and you’re ready to index your other workspaces with the newly added language.

The repo is on GitHub: zerikai_memory

If you’re running agent workflows across multiple IDEs or projects, the context re-injection overhead adds up. zerikai_memory cuts that out at the root.

The more interesting problems turned out to be elsewhere: getting traceable, line-precise source citations out of a retrieval layer so agents stop guessing and start pointing, and getting lexical re-ranking to prevent semantically close but functionally irrelevant results from crowding out the right answer.

Have you thought about the impact of traceable sources in your own agent workflows? Have you hit the false-positive problem in semantic memory search? Drop your setup and approach below.

Thank you

Enrique

The Future Of GitHub Actions Security And What You Can Do Right Now

GitGuardian NHI Governance dashboard view of an incident

GitHub’s new Actions security roadmap is a sign that the industry has finally accepted something many defenders have been saying for years: CI/CD is no longer a convenience layer. It is production and identity infrastructure, and it’s secret-bearing. When it is compromised, the attacker does not just get a build. They get a path into source code, publishing systems, cloud environments, and the trust chain behind software delivery, as we have seen with several recent attacks.

GitHub is describing a structural shift in how Actions will work: deterministic workflow dependencies, centralized execution policy, tighter secret scoping, better telemetry, and native outbound network controls for hosted runners. Less ambient trust, fewer implicit permissions, more infrastructure-level control over what automation can do.

Most organizations are still operating in the messy reality where their workflows are already live, with their secrets already distributed across repositories, CI systems, collaboration platforms, registries, and cloud tooling. GitHub is building toward a safer actions platform. In the meantime, teams still need visibility, detection, and remediation for the environment they have today.

Changing The Trust Model

For a long time, GitHub Actions security has relied heavily on teams getting the YAML right. Pin your actions, avoid risky triggers, be careful with pull_request_target, scope your secrets tightly. All of that guidance still matters, but it has always placed a lot of burden on local workflow authors. The roadmap shows GitHub moving that burden upward into the platform itself.

One of the clearest examples is dependency locking for workflows. GitHub plans to add a dependencies section so workflows can lock direct and transitive dependencies to specific commit SHAs, with verification before jobs execute. That turns Actions dependencies into something closer to a managed dependency graph than a loose set of runtime references.

Recent supply chain attacks have repeatedly shown how dangerous mutable trust can be. A workflow that references an action by tag is convenient, but it’s also a place where trust can drift. GitHub’s roadmap effectively admits that convenience-first CI/CD needs stronger guardrails.

Execution policy is becoming a first-class control

Another major change is workflow execution protections built on GitHub rulesets. Many CI/CD incidents are not about one dramatic bug — they are about context collapse. The wrong actor triggers the wrong workflow on the wrong event, and suddenly an untrusted path gains access to trusted capabilities.

GitHub’s planned controls let organizations define who can trigger workflows and which events are permitted, with organization-level policy instead of per-file guesswork. That means security teams can set boundaries around things like workflow_dispatch, push, and pull_request, and evaluate those policies before enforcement.

This is the platform catching up to reality. CI/CD risk is governance risk. It is not just syntax risk.

Secrets are being treated as contextual assets instead of shared utilities

The roadmap’s changes around secrets may be the most important operationally. GitHub plans to introduce scoped secrets so credentials can be bound more precisely to repositories, branches, environments, workflow identities, and trusted reusable workflows. Secret management will no longer automatically ride along with repository write access.

This reflects a broader truth in modern software delivery: a secret is never just a string. It represents access, authority, and potential lateral movement. Once it leaves its intended lane, the problem is no longer limited to CI.

Better secret scoping inside Actions will help when it lands. It will not clean up the existing sprawl of tokens, API keys, service credentials, and cloud secrets already scattered across the rest of the development environment.

The near-term gap is not theoretical

GitHub’s roadmap addresses a real pattern. Recent incidents involving tj-actions/changed-files, Nx, and trivy-action all followed the same pattern: attackers compromise the automation layer, harvest credentials, then use those credentials to expand access and impact.

The real question for defenders right now: while these new Actions controls are being built and rolled out, how do you reduce risk in the environment you already have?

GitGuardian helps with the environment teams actually have today

GitGuardian’s Secrets Security and NHI Governance platform is built on the fact that secret exposure is rarely confined to a single repository or control plane. The platform scans across a broad set of internal sources where credentials may appear — version control systems, container registries, documentation platforms, messaging systems, package registries, and other connected systems. It is critical to think past the Git repo and consider anywhere an attacker is looking, including developers’ local machines.

A leaked token may start in code, then show up in CI logs, then be pasted into a ticket, then remain duplicated in a collaboration thread. Security teams need a way to find the credential wherever it traveled.

Detection and remediation still matter even when prevention improves

Better prevention changes the rate of new incidents. It does not automatically resolve the backlog of existing exposure, nor does it remove the need for fast incident response during active compromise.

GitGuardian supports both detection and automated remediation — investigating incidents, validating risk, assigning ownership, and driving remediation.

GitGuardian NHI Governance dashboard view of an incident

That workflow becomes even more useful during supply chain incidents, where the central problem is often not just that code was tampered with, but that credentials may already have been harvested, reused, or staged for later abuse.

Honeytokens fit the current moment especially well

GitHub plans to improve observability with an Actions Data Stream and a native egress firewall for GitHub-hosted runners — both roadmap items.

If you need an earlier warning system in the meantime, GitGuardian Honeytoken can help. Honeytokens are decoy credentials that track unauthorized usage, with a specific emphasis on DevOps pipeline security and supply chain attack detection. Place monitored fake credentials where an attacker searching for real ones is likely to find them, then alert when those credentials are touched.

How honeytokens work

Attackers who compromise build and automation systems often go hunting for secrets very early. A honeytoken gives defenders a chance to detect that behavior before the attacker has turned a workflow compromise into something larger.

A Governance Story About Non-human Identities

Software delivery systems are now full of non-human identities making privileged decisions. Workflows authenticate to APIs. Actions call cloud services. Build systems publish artifacts. Bots trigger pipelines. Reusable workflows inherit trust from one repository to another. All of that adds up to a dense machine-to-machine access environment mediated largely by secrets.

GitHub is improving that environment from the platform side. GitGuardian NHI Governance helps organizations discover where the keys to those lanes already lie, who owns them, where they have spread, and what needs to be fixed first.

GitGuardian NHI Governance Inventory view

What Changes Now For Defenders

The main takeaway from GitHub’s 2026 Actions security roadmap: CI/CD security is becoming more explicit, more policy-driven, and more infrastructure-aware. Deterministic dependencies, scoped secrets, execution protections, telemetry streams, and outbound network controls all reflect a more mature model for securing automation.

But the immediate defender takeaway is simpler. You do not need to wait for the roadmap to become generally available to start reducing risk.

  • You can reduce the ambient spread of secrets now.
  • You can monitor the broader internal ecosystem now.
  • You can tighten remediation around exposed credentials now.
  • You can place early warning tripwires for attacker behavior now.

That is where GitGuardian fits in this transition. GitHub is building a safer future state for Actions. GitGuardian helps security teams operate safely in the current state, where secrets are already distributed, workflows are already trusted, and attackers are already treating CI/CD as a high-value target.

Hermes Just Killed OpenClaw (Here’s Why)

This is a submission for the Hermes Agent Challenge.

I do not think OpenClaw is dead.

That title is deliberately dramatic because the shift is dramatic. OpenClaw did something important: it made a lot of developers believe that a personal AI assistant could be more than a chat box. It could sit on your machine, connect to your messages, call tools, browse, run commands, and actually move work forward.

But Hermes Agent changes the question.

OpenClaw asks:

What if I could run a personal AI assistant on my own devices?

Hermes asks:

What if my agent could live on my infrastructure, remember how I work, improve its own procedures, use tools across channels, and become more useful every week?

That second question is why Hermes feels like the next step.

Not because OpenClaw is bad. OpenClaw is popular for a reason. The official repo describes it as a personal AI assistant that runs on your own devices, answers through the channels you already use, and uses a Gateway as the control plane. That is a strong idea.

The problem is that the AI agent market is moving from “assistant I operate” to “worker I supervise.” Once that happens, the winning system is not the one with the loudest demo. It is the one with the better memory model, execution boundary, skill lifecycle, tool surface, and deployment story.

That is where Hermes starts to pull ahead.

The short version

If I had to explain the difference in one line:

OpenClaw feels like a local-first assistant. Hermes feels like agent infrastructure that happens to chat.

That distinction matters.

A real agent has to do more than respond. It needs to run somewhere reliable. It needs to work while I am away. It needs to remember the parts of my environment that matter. It needs to learn repeatable procedures. It needs to make tool use safer, especially when those tools touch files, browsers, credentials, APIs, and servers.

OpenClaw helped prove the demand.

Hermes is making the operating model more serious.

The five claims that matter

The loudest Hermes pitch right now is simple: install it, connect it, give it skills, run it on a server, and let it become your agent.

That pitch is exciting, but I would not judge Hermes by hype. I would judge it by which claims survive contact with architecture.

Claim Why it matters My read
“One-command install” Agents die when setup is fragile. If the first hour is dependency pain, most people quit. Useful, but not the real moat. Setup gets you to day one. Memory and skills decide day thirty.
“Run it on a VPS or sandbox” A serious agent should not need your personal laptop open all day. This is one of Hermes’ strongest arguments. Persistent agents belong on persistent infrastructure.
“Built-in skills” Skills turn vague AI behavior into repeatable procedures. Strong, especially because Hermes treats skills as something the agent can improve, not just something a user installs.
“Messaging integrations” Telegram, Discord, Slack, WhatsApp, and similar channels make the agent reachable from normal life. Important, but only if paired with background sessions. Otherwise it is just another bot in another inbox.
“Safer execution” Agents touch terminals, files, browsers, APIs, and credentials. That is dangerous by default. This is where Hermes feels more mature: command approval, allowlists, Docker, SSH, sandbox backends, and scoped toolsets all matter.

That is the lens for the rest of this post.

I do not care whether Hermes can produce a flashy demo once. Most agent frameworks can do that now.

I care whether Hermes has the bones for repeated work: memory, procedural learning, sandboxed execution, remote availability, and enough tool scoping to avoid turning convenience into a security incident.

Why OpenClaw won attention first

OpenClaw’s strength is obvious from its own README. It is broad, local, channel-heavy, and familiar to developers who want an assistant they can own.

The official repo highlights:

  • WhatsApp, Telegram, Slack, Discord, Signal, iMessage, Microsoft Teams, Matrix, LINE, WeChat, and many more channels
  • A local-first Gateway that owns messaging surfaces and routes requests
  • First-class tools for browser, files, exec, canvas, cron, sessions, image generation, video generation, TTS, and sub-agents
  • Skills based on SKILL.md
  • Native onboarding with openclaw onboard
  • Companion apps and nodes for macOS, iOS, Android, and headless devices

That is not small. That is why OpenClaw became a reference point for personal agents.

It also has a massive community. At the time I checked the GitHub API, OpenClaw had far more stars than Hermes. Popularity alone does not decide technical direction, but it does tell you something: OpenClaw made the category legible.

For context, I checked the public repos directly: openclaw/openclaw and NousResearch/hermes-agent. OpenClaw has the bigger gravity right now. Hermes has the more interesting agent-runtime thesis.

The issue is that popularity also brings a harsh spotlight. Once strangers, groups, plugins, browsers, shells, and personal accounts all meet inside one assistant, the security model becomes the product.

OpenClaw’s own security docs are honest about this. The guidance assumes a personal assistant trust boundary: one trusted operator boundary per gateway. It says OpenClaw is not a hostile multi-tenant security boundary for adversarial users sharing one gateway. It also says the product default for trusted single-operator setups allows host execution in the gateway or node context unless you tighten it.

That is not a cheap criticism. It is the tradeoff OpenClaw chose: powerful local assistant first, hardening second.

Hermes starts from a different center.

Hermes is built around compounding

The most important Hermes idea is not Telegram integration. It is not browser automation. It is not even the tool count.

The key idea is compounding.

Hermes describes itself as a self-improving agent with a built-in learning loop. Its docs talk about agent-curated memory, autonomous skill creation, skill improvement during use, session search, external memory providers, and user modeling.

That sounds abstract until you translate it into developer terms:

If the agent solves a hard workflow today, it should not rediscover that workflow next week.

That is the difference between a chatbot with tools and an agent that grows.

Hermes has two memory layers that are easy to reason about:

  • MEMORY.md for environment facts, project conventions, lessons learned, and workflow notes
  • USER.md for preferences, communication style, expectations, and profile details

Those are bounded on purpose. Hermes keeps them focused instead of stuffing an infinite pile of text into every prompt. For older conversations, it uses SQLite session storage with FTS5 search and summarization.

That design feels practical. The always-loaded memory stays small. The deeper history is searchable when needed.

This is exactly how I want a serious agent to behave. I do not want it to remember everything equally. I want it to remember what changes future behavior.

The skill system is the real “DNA”

Skills are where Hermes becomes interesting.

OpenClaw has skills too. Its docs explain that skills are AgentSkills-compatible SKILL.md folders that teach the agent how to use tools. OpenClaw loads bundled skills, managed/local skills, personal skills, project skills, and workspace skills.

Hermes takes the same basic idea and pushes it closer to procedural memory.

The Hermes docs say the agent can create, update, and delete its own skills through skill_manage. It creates skills after complex successful tasks, when it finds the path through errors, when a user corrects its approach, or when it discovers a non-trivial workflow.

That is the part that matters.

Not “skills as a plugin folder.”

Skills as the agent writing down how to be better next time.

This is the difference between installing extensions and building organizational memory. A good senior developer does not just solve an incident. They improve the runbook. Hermes is trying to make the agent do the same thing.

And it is not only local skills. Hermes supports:

  • Official optional skills
  • skills.sh
  • Well-known skill endpoints
  • Direct URL skills
  • GitHub skill installs
  • Community registries
  • External read-only skill directories
  • Security scanning and audit commands for installed hub skills

That gives Hermes a useful middle ground. It can learn locally, but it can also participate in a broader open skill ecosystem.

The execution story is stronger

This is where the comparison gets practical.

An agent that can run commands should make you slightly nervous. That is healthy.

Hermes treats terminal execution as a configurable backend. Commands can run locally, in Docker, over SSH, in Singularity, in Modal, in Daytona, or in Vercel Sandbox. The docs are clear about the tradeoff:

  • local is easy, but has no isolation
  • Docker gives container isolation
  • SSH moves execution to another server
  • Modal and Daytona give cloud sandbox options
  • Vercel Sandbox gives microVM-style cloud execution with snapshot persistence

The security page goes further. With Docker, Hermes applies hardened container flags: drop capabilities, no new privileges, PID limits, tmpfs mounts, and explicit resource limits. It also avoids forwarding host environment variables by default.

That matters for one simple reason:

The agent should not automatically inherit your entire laptop just because you wanted it to scrape a page or refactor a file.

OpenClaw can sandbox too. Its README points to Docker, SSH, and OpenShell options, and it recommends sandboxing for non-main sessions. Its security docs are detailed and serious.

But the default mental model is different.

OpenClaw is a personal assistant with optional hardening.

Hermes is an agent runtime where isolated execution is part of the normal deployment conversation.

That is why I would rather run Hermes on a VPS or cloud sandbox for always-on work.

Messaging is not the win. Remote agency is.

Both tools can talk through messaging platforms.

OpenClaw has a huge channel list. Hermes also supports a wide set: Telegram, Discord, Slack, WhatsApp, Signal, SMS, Email, Matrix, Mattermost, Home Assistant, DingTalk, Feishu/Lark, WeCom, Microsoft Teams, and more.

The interesting Hermes feature is not that you can message it.

The interesting feature is that messaging becomes a control surface for background work.

Hermes supports background sessions from messaging platforms. You can start a separate task, keep chatting in the main thread, and receive the result back in the same channel. That is a small feature on paper, but it changes the feel of the system.

It stops being:

I am chatting with a bot.

It becomes:

I am dispatching work to an agent that lives somewhere else.

That is the future I care about.

I do not want my personal agent trapped inside the laptop I am currently using. I want it on a server, reachable from my phone, able to run a long task, report back, and remember the result.

Hermes is built for that shape.

Tool breadth is now table stakes

There was a time when “this agent can browse the web and run commands” sounded wild.

That time is over.

Both OpenClaw and Hermes have serious tool surfaces.

OpenClaw ships built-in tools for shell execution, code execution, browser control, web search, file I/O, patching, messaging, canvas, nodes, cron, images, music, video, TTS, sessions, and sub-agents.

Hermes ships a broad registry too: web search, extraction, terminal, file editing, browser automation, vision, image generation, TTS, memory, session search, cron, messaging, delegation, code execution, Home Assistant, MCP tools, RL tools, and more.

So the question is not:

Which one has tools?

The better question is:

Which one makes tools safer, more composable, and easier to scope per situation?

Hermes has a clear toolset model. Toolsets can be enabled per session, per platform, or per task. There are platform presets like hermes-cli, hermes-telegram, and dynamic MCP toolsets. That gives you a cleaner way to say:

“This Telegram agent can do X, but not Y.”

For me, that is more important than raw tool count.

Hermes vs OpenClaw

Here is my practical comparison.

Area OpenClaw Hermes Agent
Core identity Personal AI assistant Self-improving agent runtime
Mental model Local-first Gateway assistant Persistent worker on your infrastructure
Setup CLI onboarding and Gateway daemon CLI, Gateway, and multiple runtime backends
Messaging Very broad channel coverage Channels plus background sessions
Skills Skills loaded from many locations Skills as procedural memory
Memory Workspace and session context Curated memory plus session search
Tooling Broad built-in tools Toolsets, MCP, delegation, media, web
Security Personal trust boundary, hardening available Approval, isolation, env filtering, scoped tools
Deployment Device or Gateway host Local, VPS, Docker, SSH, Modal, Daytona, Vercel Sandbox
Ideal user Power user with a device assistant Developer building a supervised digital worker
Biggest risk Too much power in one assistant boundary Newer ecosystem still proving itself

This table is why I do not read Hermes as “another OpenClaw clone.”

Hermes is competing on a different axis.

OpenClaw made the assistant powerful.

Hermes is trying to make the assistant compound.

The practical playbook

If you are reading this and wondering “okay, but what do I actually try first?”, this is the path I would take.

First, run Hermes somewhere disposable. A local machine is fine for learning, but the interesting path is Docker, SSH, Modal, Daytona, or another sandbox backend. The whole point is to avoid giving an experimental agent unlimited access to your daily machine on day one.

Then connect one messaging surface, not five. Telegram or Discord is enough. Make sure allowlists or DM pairing are enabled before you give the agent terminal access.

Then give Hermes one recurring workflow:

/background Research the latest Hermes Agent docs changes, summarize the developer impact, and send me 5 possible DEV post angles.

After that, watch for the compounding moment. If the workflow takes several tool calls, has a repeatable structure, or needs a correction from you, that is exactly the kind of thing that should become a skill.

A good first Hermes skill would not be “write blog posts.” Too vague.

A better one would be:

research-release-notes

When given a GitHub repo or docs page:
1. Find the latest release or docs update.
2. Prefer primary sources.
3. Extract concrete changes.
4. Separate confirmed facts from opinion.
5. Produce a DEV-ready outline with links.

That is where Hermes becomes more than a chat assistant. You are not just asking it to do a task. You are teaching it a durable way to do that class of task.

Where OpenClaw still wins

A good comparison should admit the other side.

OpenClaw still has big advantages:

  1. It has enormous attention and community gravity.
  2. Its channel ecosystem is very broad.
  3. Its native app and node story is compelling.
  4. Its local-first assistant feel is easier to explain to non-agent people.
  5. It has already shaped how people talk about personal AI assistants.

If your goal is “I want a personal AI assistant connected to my messaging apps and devices,” OpenClaw is still a serious answer.

But if your goal is “I want an agent that can become operational infrastructure,” Hermes is the more interesting answer.

Where Hermes wins

Hermes wins because it is opinionated about the hard parts.

1. It treats memory as a product surface

Memory is not just chat history. It is a curated behavioral layer. The split between MEMORY.md, USER.md, and searchable session history is simple enough to trust and flexible enough to grow.

2. It treats skills as learning

The agent can create and update skills after hard tasks. That is the closest thing to compounding engineering knowledge in this category.

3. It treats execution location as a first-class choice

Local, Docker, SSH, Modal, Daytona, Vercel Sandbox, Singularity. That is not a footnote. That is the difference between a toy assistant and something you can deploy with intent.

4. It treats messaging as dispatch

I can talk to the agent through Telegram or Discord, but the real value is sending background work and getting results back. That makes the chat app a command center, not the product itself.

5. It treats safety as architecture, not a disclaimer

Allowlists, DM pairing, command approval, container isolation, MCP credential filtering, context scanning, env var filtering, and scoped toolsets are not glamorous features. They are the features you need after the first impressive demo.

The bigger point

The agent space is splitting into two philosophies.

One philosophy says:

Give the user a powerful assistant and let them connect everything.

The other says:

Give the user an agent runtime that can be supervised, isolated, taught, remembered, and deployed.

OpenClaw represents the first philosophy extremely well.

Hermes represents the second.

That is why I think Hermes is the more important project to study right now.

OpenClaw proved people want agents with hands.

Hermes is asking what happens when those hands also get memory, runbooks, safer execution, background work, and a home outside your current laptop.

That is the jump.

What I would build with Hermes

If I were turning this into a real project, I would build a developer publishing agent.

Not a blog spammer. A proper assistant for technical writing:

  1. Watch official docs, GitHub releases, and challenge pages.
  2. Summarize what changed with links to primary sources.
  3. Keep a memory of my writing preferences and recurring projects.
  4. Create reusable skills for research, outline creation, source checking, and DEV formatting.
  5. Draft posts in my style, but keep claims grounded in citations.
  6. Send drafts to Telegram for review.
  7. Track comments and suggest follow-up posts based on real discussion.

That would use the Hermes shape well:

  • long-running background research
  • web extraction
  • session search
  • persistent memory
  • skills that improve over time
  • messaging delivery
  • scoped tool access
  • scheduled tasks

That is the kind of workflow where Hermes makes more sense than a one-shot chat assistant.

The point is not that Hermes can write.

The point is that Hermes can build a writing operation around memory, tools, and feedback.

Final take

Did Hermes literally kill OpenClaw?

No.

OpenClaw is too useful, too popular, and too culturally important to dismiss.

But Hermes may have killed the idea that a personal agent is only a local assistant with a chat interface.

That is the real shift.

The next generation of agents will not be judged only by how many apps they connect to. They will be judged by whether they can:

  • remember the right things
  • forget the wrong things
  • learn procedures
  • run in isolated environments
  • work asynchronously
  • integrate with open tools
  • stay useful after the first demo

By that standard, Hermes is not just another agent.

It is a strong argument for where agent software is going next.

That is my real test for any agent framework now:

Does it get more useful because I used it yesterday?

If the answer is no, it is still mostly a tool wrapper.

If the answer is yes, we are finally talking about agent software.

And yes, that is why the title says it:

Hermes just killed OpenClaw.

Not by replacing it overnight.

By making the category grow up.

The first thing I would personally validate is not whether Hermes can write a pretty paragraph. It is whether a Docker or SSH-backed Hermes research agent can run for a week, keep useful memory, and avoid turning one bad tool call into a machine-level mess. If you have tried either backend already, I would genuinely like to hear which one felt smoother and where it broke.

Sources

What do you think?

Is Hermes actually the next step after OpenClaw, or is OpenClaw still the better model for personal agents?

And of the five claims above, which one matters most to you: memory, skills, sandboxing, messaging, or running the agent on real infrastructure?

Why We Stopped Using Row-Level Security After 1M Rows

Row-level security is the recommended approach for multi-tenant PostgreSQL. It works well at small scale. Here is what happens when the table grows and the tenants multiply.
It started as the right decision
Row-level security in PostgreSQL is an elegant solution to a real problem. Instead of relying on every query to include a WHERE tenant_id = ? clause, you define a policy at the database level. The database enforces the filter on every query, every time, regardless of what the application does.
sqlALTER TABLE orders ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation ON orders
USING (tenant_id = current_setting(‘app.current_tenant’));
No more worrying about a developer forgetting the filter. No more accidental cross-tenant data exposure from a missing WHERE clause. The database itself guarantees isolation.
For a SaaS application with 20 tenants and a few hundred thousand rows, this works beautifully. The policies are invisible to the application. Query performance is nearly identical to queries without RLS. The security posture is stronger than application-level filtering.
The problems emerge gradually, and by the time they are visible, the architecture is deeply embedded.
The performance curve
RLS policies are evaluated on every query. For a simple SELECT by primary key, the overhead is negligible. But as the table grows, the cost of the policy evaluation grows with it.
The policy check is essentially an additional predicate injected into every query plan. On a table with 1 million rows spread across 50 tenants, the planner must consider the RLS predicate alongside every other condition. Composite indexes that previously served queries efficiently now have an additional dimension to navigate.
Benchmarks vary by workload, but a consistent pattern emerges across production systems: RLS adds 5 to 15 percent overhead on simple queries, and the overhead increases on complex queries with joins, subqueries, and aggregations. On a table with 10 million rows across 200 tenants, some queries see 20 to 30 percent degradation compared to the same query on a tenant-specific database with the same data volume.
The individual overhead is acceptable. The cumulative overhead across every query, every request, every tenant, every day, is significant.
The debugging problem
When a query returns no results, there are two possible explanations: the data does not exist, or the RLS policy filtered it out. Without RLS, the answer is always the first one. With RLS, you can never be sure without checking.
sql– Returns 0 rows. Is the order missing, or is the policy filtering it?
SELECT * FROM orders WHERE id = 12345;

— To debug, you need to check as a superuser with RLS bypassed
SET ROLE postgres;
SELECT * FROM orders WHERE id = 12345;
— Row exists. The policy was filtering it because app.current_tenant was wrong.
This debugging pattern repeats constantly in production. A customer reports missing data. The support team checks the table. RLS is active, so they see nothing. They escalate to engineering. Engineering checks with a superuser role and finds the data. The issue was a session variable not being set correctly in one specific code path.
Every engineer on the team learns to suspect the RLS policy whenever data appears to be missing. This creates a low-grade cognitive overhead that slows down every investigation.
The session variable dependency
RLS policies in PostgreSQL typically reference a session variable like app.current_tenant. Every connection must set this variable before executing any query.
sqlSET app.current_tenant = ‘acme’;
SELECT * FROM orders WHERE status = ‘active’;
This means every connection pool, every ORM configuration, every migration tool, every administrative script, and every background job must correctly set the session variable. If the variable is not set, the policy either blocks all rows (safe but disruptive) or allows all rows (dangerous), depending on how the policy is written.
Connection poolers like PgBouncer add complexity. In transaction pooling mode, the session variable must be set at the start of every transaction because sessions are shared across clients. If the SET statement is missed or fails silently, the next query runs with the wrong tenant context or no tenant context at all.
Production incidents caused by session variable misconfiguration are difficult to detect because they do not produce errors. The query succeeds. It just returns the wrong data or no data. There is no exception, no log entry, no alert. The failure is silent.
What about other databases?
RLS is a PostgreSQL feature. But the underlying pattern, query-time filtering for tenant isolation, exists in every database engine.
In MySQL, the equivalent is typically implemented through views with embedded WHERE clauses or application middleware that rewrites queries. The same session variable dependency applies. The same debugging challenges apply. MySQL does not have a native RLS mechanism, so the enforcement is weaker and the application bears more responsibility.
In MongoDB, tenant isolation in a shared collection relies on including the tenant field in every query. There is no database-level policy enforcement. A missing filter in a single query path exposes all tenants’ documents.
javascript// Must include tenant_id in every query
db.orders.find({ tenant_id: “acme”, status: “active” })

// Forgot the filter? Returns all tenants’ data.
db.orders.find({ status: “active” })
In Redis, isolation relies on key naming conventions. Every key is prefixed with the tenant identifier. There is no enforcement mechanism. A KEYS * command returns every tenant’s data. A mistyped prefix returns another tenant’s data or nothing at all.
// Convention-based isolation
GET tenant:acme:session:abc123

// No enforcement. Nothing prevents this:
KEYS *
In every case, the isolation depends on the application consistently doing the right thing on every operation. The database does not enforce it (except PostgreSQL with RLS), and even PostgreSQL’s enforcement comes with the costs described above.
The migration question
Removing RLS from a production system is not trivial. The policies are deeply integrated into the query execution path. Removing them means every query must now include explicit tenant filtering, which is the exact problem RLS was introduced to solve.
The alternative is not removing RLS and going back to application-level filtering. The alternative is removing the need for filtering entirely by giving each tenant their own database.
When each tenant has their own database, the concept of row-level security is irrelevant. There are no rows from other tenants to filter. Every query returns only the current tenant’s data because the database contains only the current tenant’s data.
sql– No RLS, no WHERE clause, no session variable
— The database only contains acme’s data
SELECT * FROM orders WHERE status = ‘active’;
The performance overhead disappears because there is no policy to evaluate. The debugging problem disappears because missing data means missing data, not a filter issue. The session variable dependency disappears because there is no variable to set.
The performance comparison
On a table with 500,000 rows belonging to a single tenant, comparing a query with RLS on a shared 5-million-row table versus the same query on a dedicated 500,000-row database:
The shared table query must evaluate the RLS policy, navigate a larger B-tree index, and compete with other tenants for buffer cache space. The dedicated database query skips the policy, navigates a smaller index, and has the full buffer cache available for one tenant’s data.
The difference is not just the RLS overhead. It is the compound effect of smaller indexes, warmer caches, no lock contention from other tenants, and simpler query plans. The dedicated database is faster for every query type, not just the ones where RLS is expensive.
When RLS still makes sense
RLS is a good fit for specific scenarios. Internal applications where the number of tenants is small and fixed. Systems where the total data volume will remain modest. Applications where adding a database provisioning layer is not feasible and the performance overhead is acceptable.
It is also a reasonable intermediate step. If you are not ready to move to database-per-tenant isolation, adding RLS to a shared table is strictly better than relying on application-level filtering alone. It catches the missing WHERE clause bug, which is the most common source of cross-tenant data leaks.
But for SaaS applications that are growing in tenant count, data volume, or compliance requirements, RLS becomes a ceiling that gets lower over time. The performance overhead accumulates. The debugging complexity compounds. The session variable becomes a source of silent failures.
Moving past query-time filtering
The fundamental limitation of RLS and all query-time filtering approaches is that they add a layer of enforcement on top of shared data. The data is co-mingled, and the enforcement must work perfectly on every query to maintain isolation.
Database-per-tenant isolation eliminates the need for this layer entirely. Isolation is structural, not enforced. There is no policy to evaluate, no session variable to set, no filter to forget. The architecture guarantees isolation by construction.
Curious how others have handled this. Did you stick with RLS at scale, or did you move to database-per-tenant? At what point did the tradeoff flip for your team?

How We Built a Free WCAG 2.1 Accessibility Scanner

How We Built a Free WCAG 2.1 Accessibility Scanner

Web accessibility remains one of the most overlooked aspects of web development. Most developers understand the importance—accessibility improves UX for everyone, expands market reach, and keeps you compliant with regulations like WCAG 2.1. Yet building accessible websites still feels like an afterthought for many teams.

That’s why we built Web Accessibility Checker—a free, open-minded scanner that instantly audits your site against WCAG 2.1 standards. In this article, I’ll walk you through the architecture we chose, the challenges we faced, and how we engineered a solution that processes accessibility audits in under 20 seconds, completely free.

The Problem We Solved

Existing accessibility tools fall into two camps:

  1. Enterprise solutions ($5k+/month) that are overkill for indie developers and SMEs
  2. Simplistic tools that miss critical issues because they don’t go deep enough

We wanted something in the middle: fast, thorough, and genuinely useful for developers making real decisions about accessibility improvements.

The challenge? Building a scanner that:

  • Handles JavaScript-rendered content (not just static HTML)
  • Doesn’t take forever to run
  • Stays within API rate limits
  • Gives actionable results in real time

Our Two-Phase Scanning Architecture

We settled on a hybrid approach: instant DOM scanning + asynchronous PSI analysis.

Phase 1: Instant DOM Parsing (< 1 second)

When you submit a URL to WAC, we immediately fetch and parse the HTML using PHP’s DOMDocument class. This phase checks for:

  • Missing alt text on images
  • Heading hierarchy violations (h1 → h3 jump)
  • Form label associations
  • ARIA attribute correctness
  • Color contrast (preliminary pass)
  • Semantic HTML usage

Here’s a simplified version of our DOM scanning core:

<?php
// Phase 1: Instant DOM Analysis
function scanDOM($html, $baseUrl) {
    $dom = new DOMDocument('1.0', 'UTF-8');
    @$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
    $xpath = new DOMXPath($dom);

    $issues = [];

    // Check 1: Images without alt text
    $images = $xpath->query('//img[@alt=""] | //img[not(@alt)]');
    foreach ($images as $img) {
        $issues[] = [
            'type' => 'missing_alt',
            'severity' => 'critical',
            'element' => $dom->saveHTML($img),
            'wcag_level' => '1.1.1',
            'message' => 'Image missing alt text (WCAG A)'
        ];
    }

    // Check 2: Heading hierarchy
    $headings = $xpath->query('//h1 | //h2 | //h3 | //h4 | //h5 | //h6');
    $prevLevel = 1;
    foreach ($headings as $heading) {
        $currentLevel = (int)$heading->nodeName[1];
        if ($currentLevel > $prevLevel + 1) {
            $issues[] = [
                'type' => 'heading_hierarchy',
                'severity' => 'warning',
                'element' => $heading->textContent,
                'wcag_level' => '1.3.1',
                'message' => "Heading hierarchy skips from h{$prevLevel} to h{$currentLevel}"
            ];
        }
        $prevLevel = $currentLevel;
    }

    // Check 3: Form labels
    $inputs = $xpath->query('//input[@type!="hidden" and @type!="submit" and @type!="button"]');
    foreach ($inputs as $input) {
        $id = $input->getAttribute('id');
        if (!$id) {
            $issues[] = [
                'type' => 'missing_form_label',
                'severity' => 'high',
                'element' => $dom->saveHTML($input),
                'wcag_level' => '1.3.1',
                'message' => 'Form input missing associated label or id'
            ];
            continue;
        }

        $label = $xpath->query("//label[@for='{$id}']");
        if ($label->length === 0) {
            $issues[] = [
                'type' => 'missing_form_label',
                'severity' => 'high',
                'element' => $dom->saveHTML($input),
                'wcag_level' => '1.3.1',
                'message' => "Input #{$id} has no associated <label>"
            ];
        }
    }

    // Check 4: ARIA button pattern
    $ariaButtons = $xpath->query('//div[@role="button"] | //span[@role="button"]');
    foreach ($ariaButtons as $btn) {
        if (!$btn->getAttribute('tabindex') || $btn->getAttribute('tabindex') < 0) {
            $issues[] = [
                'type' => 'aria_button_not_focusable',
                'severity' => 'high',
                'wcag_level' => '2.1.1',
                'message' => 'ARIA button role requires tabindex="0" for keyboard access'
            ];
        }
    }

    return $issues;
}
?>

This phase is instantaneous because we’re just parsing local HTML. Users get preliminary results in milliseconds.

Phase 2: Async PSI Analysis (Background)

Phase 1 gives you immediate feedback, but it misses issues that only a real browser engine can detect: JavaScript-injected content, computed styles, actual color contrast ratios, and more.

For this, we use Google’s PageSpeed Insights API (PSI), which runs Lighthouse audits on real Chrome. The catch? PSI takes 10–30 seconds. We didn’t want users staring at a loading spinner.

Solution: Asynchronous queuing.

When Phase 1 finishes, we immediately return the DOM results to the user’s browser. In the background, we queue a PSI scan and poll for results. Once PSI completes, we push an update via WebSocket (or polling as fallback).

Here’s the backend handler:

<?php
// /api/scan endpoint
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $url = filter_var($_POST['url'] ?? '', FILTER_VALIDATE_URL);

    if (!$url) {
        http_response_code(400);
        echo json_encode(['error' => 'Invalid URL']);
        exit;
    }

    // Create scan record
    $scanId = bin2hex(random_bytes(16));
    $db->insert('scans', [
        'scan_id' => $scanId,
        'url' => $url,
        'status' => 'pending',
        'created_at' => date('Y-m-d H:i:s')
    ]);

    // Phase 1: Instant DOM scan
    $html = @file_get_contents($url);
    $domIssues = scanDOM($html, $url);

    // Phase 2: Queue PSI scan (async)
    queuePSIScan($scanId, $url);

    // Return Phase 1 results immediately
    http_response_code(200);
    echo json_encode([
        'scan_id' => $scanId,
        'phase1_issues' => $domIssues,
        'phase2_status' => 'queued'
    ]);
    exit;
}

// PSI queue processor (runs via cron or background job)
function queuePSIScan($scanId, $url) {
    $db->insert('psi_queue', [
        'scan_id' => $scanId,
        'url' => $url,
        'status' => 'pending'
    ]);
}

// Cron job (runs every minute)
function processPSIQueue() {
    $pending = $db->query(
        "SELECT * FROM psi_queue WHERE status = 'pending' LIMIT 10"
    );

    foreach ($pending as $job) {
        $result = callPSIAPI($job['url']);

        $db->update('psi_queue',
            ['status' => 'completed', 'result' => json_encode($result)],
            ['scan_id' => $job['scan_id']]
        );
    }
}

function callPSIAPI($url) {
    $apiKey = getenv('PAGESPEED_API_KEY');
    $endpoint = 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed';

    $params = [
        'url' => $url,
        'key' => $apiKey,
        'category' => 'accessibility',
        'strategy' => 'mobile'
    ];

    $fullUrl = $endpoint . '?' . http_build_query($params);

    $ch = curl_init($fullUrl);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 45,
        CURLOPT_HTTPHEADER => ['Content-Type: application/json']
    ]);

    $response = curl_exec($ch);
    curl_close($ch);

    return json_decode($response, true);
}
?>

The Frontend: Real-Time Updates

On the client side, we use a simple polling mechanism while we wait for PSI results:

async function submitScan(url) {
    const response = await fetch('/api/scan', {
        method: 'POST',
        body: new FormData(Object.assign(document.createElement('form'), {
            elements: {
                url: { value: url }
            }
        }))
    });

    const data = await response.json();
    const scanId = data.scan_id;

    // Display Phase 1 results immediately
    renderResults(data.phase1_issues);

    // Poll for Phase 2 results
    const pollInterval = setInterval(async () => {
        const statusResp = await fetch(`/api/scan/${scanId}/status`);
        const status = await statusResp.json();

        if (status.phase2_status === 'completed') {
            clearInterval(pollInterval);
            renderResults([...data.phase1_issues, ...status.phase2_issues]);
        }
    }, 2000); // Poll every 2 seconds
}

Handling Scale: Rate Limits & Caching

The PSI API has quotas (300 requests/day on free tier). To stay within limits while serving thousands of daily scans:

  1. Cache PSI results for 24 hours (same URL = same results)
  2. Batch queue processing to avoid API spikes
  3. Graceful degradation: if PSI quota is exhausted, return Phase 1 results + a note that advanced checks are temporarily unavailable
function callPSIAPI($url) {
    // Check cache first
    $cached = $db->queryOne(
        "SELECT result FROM psi_cache WHERE url = ? AND created_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)",
        [$url]
    );

    if ($cached) {
        return json_decode($cached['result'], true);
    }

    // Call API and cache result
    $result = httpGetJSON($psiEndpoint);

    $db->insert('psi_cache', [
        'url' => $url,
        'result' => json_encode($result),
        'created_at' => date('Y-m-d H:i:s')
    ]);

    return $result;
}

Why This Approach Works

  • Instant feedback: Users don’t wait 20+ seconds for any results
  • Comprehensive: Phase 1 + Phase 2 cover static + dynamic issues
  • Scalable: Async processing means we can handle many concurrent users
  • Cost-efficient: We cache PSI results and batch API calls
  • User-friendly: Progressive enhancement—Phase 1 results are immediately useful

Open Source Lessons

Building a free, multi-language accessibility scanner taught us that:

  1. Perfect is the enemy of good: Launch with Phase 1, add Phase 2 later
  2. Accessibility tooling drives accessibility adoption: Free tools lower the barrier to entry
  3. Developer experience matters: Fast feedback loops encourage behavior change

If you want to audit your site’s accessibility, check out Web Accessibility Checker. It’s free, completely anonymous, and works in 10+ European languages. No signup, no tracking—just instant, honest feedback.

Have questions about building accessibility scanners? Drop them in the comments below. I’d love to hear about your favorite a11y tools, too.

Tags: #a11y #webdev #php #wcag #accessibility

What I’m Learning Building Startup Graveyard.

One thing I’m noticing while building Startup Graveyard:

A failed startup is not always a bad idea.

Sometimes the timing was wrong.
Sometimes distribution failed.
Sometimes people liked the product, but not enough to pay for it.
Sometimes the market existed, but the business model did not.

That’s the part I want the site to explore more: the patterns behind failure, not just the names of companies that shut down.

Startup Graveyard is a web app where people can browse failed startups, read what happened, vote, comment, and submit their own examples.

The bigger idea is to eventually make the data useful beyond just reading individual stories. I’d like people to be able to study the patterns, compare failure reasons, look at trends, and maybe pull the data in a way that helps founders, builders, students, and researchers learn from it.

The technical side has been a big learning process too. I’ve been working with Next.js, Supabase, authentication, profiles, moderation, SEO, and deployment through Vercel.

It started as a simple project to learn about web dev, deployment, and to finally see one whole idea through.

Hopefully this project inspires people to learn from others mistakes!

https://www.startupgraveyard.co

How to Set Up a Self-Hosted Server on Oracle Cloud Free Tier in 30 Minutes

How to Set Up a Self-Hosted Server on Oracle Cloud Free Tier in 30 Minutes

A step‑by‑step guide that lets you launch, configure, and secure your own server on Oracle Cloud’s free tier—no credit card required

Before We Start: What You’ll Walk Away With

By the end of this guide you’ll have a fully functional Oracle Cloud free tier server that you can log into from any terminal, just like you’d pick up a coffee after ordering it online.

First you’ll spin up a Compute instance and establish an SSH session, the same way you’d unlock a door with a key you already have.

Next you’ll set up DNS entries, open the right ports, and install a LAMP stack, which is essentially packing a suitcase with everything you need for a web app before a trip.

Finally you’ll harden the machine with basic security tweaks, giving you a production‑ready environment you can redeploy for any personal project, side‑hustle, or testing scenario.

  • Create a free Oracle Cloud Compute instance and connect via SSH.

  • Configure DNS, firewall rules, and install a LAMP stack.

  • Apply security hardening to make the server ready for production use.

  • SSH access: Think of it as a private tunnel that lets you control your server from anywhere.

  • DNS setup: Like assigning a memorable street address to a house you just built.

  • Firewall rules: Comparable to setting up a bouncer at the door, only letting the right traffic in.

  • LAMP stack: The kitchen of your server – Linux provides the floor, Apache serves the meals, MySQL stores the recipes, and PHP does the cooking.

With these steps completed, you’ll own a reliable, cost‑free server that’s ready for the real world.

What a Self‑Hosted Server on Oracle Cloud Actually Is (No Jargon)

Think of a self‑hosted server on Oracle Cloud as a completely private computer that lives inside Oracle’s huge data centre, but you are the one who decides what runs on it.

It’s a virtual machine – essentially a software‑defined PC – that Oracle spins up for you on demand. You get root access, can install any Linux distro, and configure networking exactly how you need it, just like you would on a physical box.

Picture renting an apartment in a massive skyscraper you don’t own. The apartment (the VM) is yours alone; you bring in the furniture (your web server, database, or whatever you’re testing) and you lock the door (firewall rules) so only the people you invite can walk in. The building (Oracle Cloud) takes care of electricity, water, and security, while you focus on living there the way you want.

  • Full control: install, update, or remove software at will.

  • Isolation: your VM doesn’t share resources with other users’ workloads.

  • Always‑on: as long as the free tier limits aren’t exceeded, it runs 24/7 without you paying a cent.

This simple model lets developers, hobbyists, and small‑business owners treat the cloud like a personal server rack, but without the hardware bill.

The 4 Mistakes Everyone Makes With Oracle Cloud Free Tier

Here’s where most people trip up when they first play with the Oracle Cloud free tier.

Skipping the “Always Free” eligibility check – It’s like ordering a meal without confirming it’s on the lunch special; you end up with a surprise bill. Before you launch anything, verify that your account is marked Always Free in the console. If the check is missed, you’ll hit quota limits the moment you spin up a second VM.

Leaving the default security list wide open – Imagine leaving every door and window of your house unlocked because the lock came pre‑installed. The default security list permits SSH, HTTP, and many other ports to the world. Tighten it down to only the ports you actually need (typically 22 for SSH and 80/443 for web traffic).

Choosing the wrong image – Selecting a Windows image for a tiny test app is like ordering a full banquet when all you wanted was a sandwich. A lightweight Linux distro (Ubuntu 22.04 or Oracle Linux 8) boots faster, costs fewer resources, and plays nicer with the free tier limits.

Forgetting a static IP or DNS – It’s the same as packing a suitcase without writing your destination on the tag; you’ll waste time hunting down the right address each time you reconnect. Reserve the free‑tier public IP and point a simple DNS record at it so you always know where to reach your server.

  • Cheat sheet: Always Free ✅ → Check quota, tighten security, pick Linux, pin a static IP.

Fix these four slips and your Oracle Cloud free tier server will stay up and running without surprise roadblocks.

How to Set Up a Self‑Hosted Server: Step‑by‑Step

  • Sign up and verify. Create your Oracle Cloud Free Tier account, then confirm the email and phone number. Think of it like ordering a coffee – you give your details, they send a receipt, and you’re ready to brew.

  • Open the console. In the dashboard go to Compute → Instances and hit “Create Instance.” This is the “new order” button you click after picking a drink.

  • Select the free shape and OS. Choose the Always Free shape — VM.Standard.E2.1.Micro — and pick the Ubuntu 22.04 image. It’s like selecting a small cup size that won’t cost extra.

  • Reserve a public IP and add SSH key. Click “Assign a reserved public IP” and paste your SSH public key. This gives you a permanent “phone number” for the server and a secure way to call it.

Build the network. Create a Virtual Cloud Network (VCN) with:

- a subnet (your kitchen counter space),

- an internet gateway (the doorway to the street),

- a route table linking the two (the path you walk).
  • Lock down the security list. Edit it to allow only ports 22, 80, and 443. It’s like setting a bouncer to check IDs for only the front door, the coffee bar, and the pastry case.

  • Launch and SSH. Hit “Create” and wait a minute. Then connect with: ssh -i ~/.ssh/id_rsa ubuntu@.

Install LAMP. Run:

- sudo apt update && sudo apt install apache2 mysql-server php

This stacks the web server, database, and PHP just like adding toppings to a pizza.

  • Harden the server. Enable UFW (sudo ufw enable), disable root SSH login (PermitRootLogin no in /etc/ssh/sshd_config), and turn on unattended upgrades (sudo dpkg-reconfigure –priority=low unattended-upgrades).

  • Optional: point a domain. In your registrar’s DNS, create an A record pointing to the reserved IP. Think of it as handing out your new address to friends.

Now your Oracle Cloud free tier server is live, secure, and ready for whatever you want to host.

A Real Example: Deploying a Personal Blog for Maya, the Freelance Designer

Maya signs up for Oracle Cloud, picks Ubuntu 22.04, and names the VM “maya‑blog.”

  • Create the SSH keys. On her Mac she runs ssh-keygen -t ed25519 -C maya@blog, saves the pair, then copies ~/.ssh/id_ed25519.pub into the “Add SSH Keys” field in the console. The private key stays in ~/.ssh for later logins.

  • Launch and bind a static IP. After the instance boots, Maya clicks “Assign Public IP” and gets, for example, 138.1.2.3. She heads to GoDaddy, opens the DNS zone for maya‑design.com, and creates an A‑record pointing blog.maya‑design.com to that IP – like putting a permanent address on a mailbox.

  • Install LAMP. She SSHs in with ssh -i ~/.ssh/id_ed25519 [email protected] and runs:

  • sudo apt update && sudo apt upgrade -y

  • sudo apt install -y apache2 mysql-server php php-mysql

  • sudo systemctl enable –now apache2

Visiting http://138.1.2.3 now shows the default Apache page – the test drive is complete.

  • Lock down the firewall. Maya thinks of it like only letting the delivery driver in:

  • sudo ufw allow OpenSSH

  • sudo ufw enable

Then she edits /etc/ssh/sshd_config to set PasswordAuthentication no and restarts SSH.

  • Automate updates. A daily cron entry keeps the server fresh without manual effort:

  • 0 3 * * * root apt update && apt -y upgrade

With the stack running, Maya uploads her static site files to /var/www/html and the blog is live at https://blog.maya‑design.com. The Oracle Cloud free tier server setup is now a reliable home base for her design portfolio.

The Tools That Make This Easier

Grab these five utilities and the rest of the setup feels like ordering a coffee you already know you love.

  • Oracle Cloud Console – the web UI where you click to spin up an instance, just like picking a seat on a restaurant floor plan. Everything from shapes to storage lives here, so you never have to hunt across tabs.

  • Oracle Cloud CLI (v3.2) – think of it as Google Maps for your infrastructure. A single command can launch a VM, attach a VCN, or tag resources, making repeatable builds as easy as copying a route.

  • PuTTY / OpenSSH – your trusted SSH client. On Windows, PuTTY is the familiar key‑card; on macOS and Linux, OpenSSH is the built‑in door opener. Either lets you walk straight into your server without a lobby.

  • UFW (Uncomplicated Firewall) – the minimalist security guard for Ubuntu. A few lines (“allow 22/tcp”, “deny 0.0.0.0/0”) lock down the perimeter like a suitcase zip that only opens for the right combination.

  • Let’s Encrypt Certbot (v2.11) – the automatic SSL baker. Run one command and it hands you a fresh certificate, renewing every 90 days without you lifting a finger, just like a subscription that never expires.

These tools cover the whole lifecycle: visual provisioning, scriptable automation, secure access, firewall hardening, and HTTPS setup—all under the Oracle Cloud free tier server setup umbrella.

Keep them handy; the next section shows how they fit together step by step.

Quick Reference: Self‑Hosted Server Cheat Sheet

Here’s the whole Oracle Cloud free tier server setup compressed into a coffee‑break cheat sheet.

  • ✅ Sign up → Verify → Free Tier eligibility. Think of it like creating a loyalty card before you can order a free coffee.

  • ✅ Create instance: Ubuntu 22.04 on an E2.1.Micro. It’s the “small sedan” that fits city parking and never asks for extra fees.

  • ✅ Reserve static IP & attach SSH key. Like reserving a permanent locker and handing over a unique key so only you can open it.

  • ✅ VCN setup: add a subnet, an Internet Gateway, and a Route Table. This is your Google Maps – you’re telling traffic where it can go.

  • ✅ Security List: open ports 22, 80, 443 only. Example: Maya, a freelance dev, keeps her door cracked just enough for the delivery guy (SSH) and guests (HTTP/HTTPS) – everything else stays shut.

  • ✅ SSH in → install LAMP: sudo apt update && sudo apt install lamp-server^. It’s like ordering a combo meal – you get Apache, MySQL, PHP in one bite.

  • ✅ Harden the box: enable UFW, disable root/password login, turn on automatic updates. Think of it as adding a deadbolt, a security camera, and a scheduled cleaning service.

  • ✅ (Optional) DNS A‑record → static IP. Point your domain to the IP just like writing a home address on a return envelope.

  • ✅ Verify: browse to http://your‑ip or your domain and you should see the Apache default page. If it loads, you’ve got a production‑ready Oracle Cloud free tier server setup.

Keep this list handy; it’s the cheat sheet you’ll return to whenever you spin up another instance.

What to Do Next

Pick one of these upgrades and watch your free Oracle Cloud server feel like a real production box.

  • Add HTTPS with Certbot – Think of it like putting a lock on your front door. Run sudo apt install certbot python3-certbot-apache then sudo certbot –apache. The wizard will ask for a domain, confirm you own it, and spin up a free SSL certificate. Your site goes from “http://” to the secure “https://” version in seconds.

  • Spin up Docker – Imagine ordering a pizza and having every topping pre‑packed in its own box. Install Docker with sudo apt install docker.io, enable it, and pull an image: docker run -d -p 80:80 nginx. Now any app you build can live in its own container, isolated from the host, and you can start, stop, or upgrade it with a single command.

  • Automate deployments with CI/CD – This is the GPS that drives you straight from code to live server without manual detours. Create a GitHub Actions workflow that uses the Oracle Cloud CLI (oci) to upload your Docker image and restart the container. A push to main triggers the whole chain, giving you zero‑touch releases.

Cheat sheet:

  • Certbot: sudo certbot –apache -d yourdomain.com

  • Docker install: sudo apt install docker.io && sudo systemctl enable –now docker

  • GitHub Actions snippet: use oracle/actions-setup-oci then oci compute instance update

Which of these steps are you tackling next?

About the Author

Abdullah Sheikh is the Founder & CEO at Exteed, where he leads a team of skilled developers specializing in Web2 and Web3 applications, Custom Smart Contracts, and Blockchain solutions.

With 6+ years of experience, Abdullah has built CRMs, Crypto Wallets, DeFi Exchanges, E-Commerce Stores, HIPAA Compliant EMR Systems, and AI-powered systems that drive business efficiency and innovation.

His expertise spans Blockchain, Crypto & Tokenomics, Artificial Intelligence, and Web Applications — delivering scalable, secure, and high-performance solutions tailored to each client’s unique needs.

📧 [email protected] · 🔗 LinkedIn · 🌐 abdullah-sheikh.com

5 Markdown Tricks Every Developer Should Know (That Aren’t in the Cheat Sheet)

We all know the basics: **bold**, *italic*, # Headings. But Markdown (especially GitHub Flavored Markdown) has some lesser-known tricks that can make your READMEs, docs, and PR descriptions much more polished. Here are five I use constantly.

1. Collapsible Sections with <details>

Got a long README? Hide secondary content behind expandable sections:

<details>
<summary>Click to see the full changelog</summary>

- v2.1: Added dark mode
- v2.0: Complete rewrite
- v1.0: Initial release

</details>

This renders as a clickable toggle. Perfect for changelogs, setup instructions, or verbose examples you don’t want cluttering the main view. GitHub, GitLab, and most static site generators support this.

2. Auto-Linking to Headings

GitHub auto-generates anchor links for every heading. You can link to any section with a simple relative link:

See the [Installation](#installation) section for setup instructions.

The rule: lowercase everything, replace spaces with hyphens, remove punctuation. So ## API Reference (v2) becomes #api-reference-v2.

Bonus: This works across files too — [See docs](docs/API.md#authentication).

3. Task Lists in PR Descriptions

GFM supports interactive checkboxes:

- [x] Add unit tests
- [x] Update documentation
- [ ] Bump version number
- [ ] Deploy to staging

These render as actual checkboxes on GitHub Issues and PRs. Use them in PR templates to create a checklist the author can tick off before merging. They also show up in project boards as trackable items.

4. Escaping Backticks Inside Inline Code

Sometimes you need to show a literal backtick character inside inline code. The trick? Use double backticks as delimiters:

To display a variable, use `` `varname` `` in your template.

Renders as: To display a variable, use `varname` in your template.

The outer double-backtick pair lets you nest single backticks inside. Add a space before/after the inner backtick for readability.

5. Mermaid Diagrams in Fenced Code Blocks

Many platforms (GitHub, GitLab, Notion) support Mermaid diagrams directly in Markdown. Just use mermaid as the language tag:

```

mermaid
graph TD
    A[User] --> B[Login Page]
    B --> C{Valid?}
    C -->|Yes| D[Dashboard]
    C -->|No| E[Error Message]


```
```

`

This renders a flowchart directly in your README — no external tools, no image exports. Mermaid supports flowcharts, sequence diagrams, Gantt charts, class diagrams, and more.

---

## The Best Way to Learn? Preview in Real Time

The fastest way to get comfortable with Markdown is to see it render as you type. I use [CodeToolbox Markdown Preview](https://codetoolbox.pro/tools/markdown-preview.html) when drafting READMEs — it's free, runs entirely in the browser (nothing gets uploaded), and supports full GFM including tables, task lists, and syntax-highlighted code blocks.

What Markdown tricks do you use that aren't in the standard cheat sheet? Drop them in the comments 👇

Putting my own MCP server behind my own MCP gateway

I built Agent Toolbelt last year. It’s an MCP server with 27 tools my agents call when they need stock research, schema generation, regex help, or any of the other small things that came up in my workflow.

A few months ago I built Cordon. It’s an MCP gateway that sits between an agent and any upstream MCP server, logs every tool call, and lets you write policies that block or gate calls before they fire.

This post is what happens when I run one in front of the other.

The gap

Agent Toolbelt has tools that cost money to call. stock_thesis is five cents per invocation. count_tokens is a hundredth of a cent. The cheap ones are fine to let an agent loop on. The expensive ones are not.

When I’m prototyping, this doesn’t matter. I run the agent, I see the output, if it does something weird I notice and stop it. Production is a different story. The agent runs on a schedule. Nobody is watching when it hits stock_thesis 50 times in a minute because it got stuck in a planning loop.

Cordon fixes that for me. Every tool call shows up in an audit log, calls I’ve tagged as expensive get blocked or paused for my approval and the agent can’t ruin my month.

The setup

┌─────────────────┐     ┌──────────────────────┐     ┌──────────────────────┐
│ Claude Desktop  │ ──→ │ Cordon (port 7777)   │ ──→ │ Agent Toolbelt MCP   │
│   (or Cursor)   │     │  • audit log         │     │   server (stdio)     │
└─────────────────┘     │  • policy engine     │     │  • 27 tools          │
                        │  • Bearer auth       │     └──────────────────────┘
                        └──────────┬───────────┘
                                   │
                                   ▼
                          audit JSON to stdout
                          or to the dashboard

Claude Desktop talks to Cordon over HTTP. Cordon talks to Agent Toolbelt over stdio (the way MCP servers are normally launched). From Claude’s perspective nothing about the tool surface has changed. The only difference is that every call now has a record.

Step 1. Get an Agent Toolbelt API key

Agent Toolbelt’s MCP server ships on npm as agent-toolbelt-mcp. We don’t need to install it directly. Cordon will spawn it as a child process via npx in the next step. The one thing you do need is a free API key.

curl -X POST 'https://agent-toolbelt-production.up.railway.app/api/clients/register' 
  -H 'Content-Type: application/json' 
  -d '{"email":"[email protected]"}'

Save the atb_... key it returns. Free tier covers a thousand calls a month, no card.

Step 2. Put Cordon in front of it

Cordon ships as @getcordon/cli on npm.

npm install -g @getcordon/cli

Write a cordon.config.ts that points at Agent Toolbelt as a stdio upstream. Cordon will run npx -y agent-toolbelt-mcp under the hood and pipe stdio to it.

import { defineConfig } from '@getcordon/policy';

export default defineConfig({
  servers: [
    {
      name: 'agent-toolbelt',
      transport: 'stdio',
      command: 'npx',
      args: ['-y', 'agent-toolbelt-mcp'],
      env: {
        AGENT_TOOLBELT_KEY: process.env.AGENT_TOOLBELT_KEY,
        AGENT_TOOLBELT_URL: 'https://agent-toolbelt-production.up.railway.app',
      },
      policy: 'allow',
    },
  ],
  gateway: {
    transport: 'http',
    port: 7777,
    authToken: process.env.CORDON_GATEWAY_TOKEN,
  },
  audit: { enabled: true, output: 'stdout' },
});

Start it.

export AGENT_TOOLBELT_KEY=atb_your_key_here
export CORDON_GATEWAY_TOKEN=$(openssl rand -hex 16)
cordon start --http

Cordon spawns Agent Toolbelt as a child process. The gateway starts listening on http://127.0.0.1:7777/mcp. The stderr line you’re waiting for looks like this.

[cordon] HTTP gateway listening on http://127.0.0.1:7777/mcp

Step 3. Point Claude Desktop at Cordon

Open claude_desktop_config.json (or your editor’s MCP config equivalent). On Windows it’s at %APPDATA%Claudeclaude_desktop_config.json. On macOS it’s at ~/Library/Application Support/Claude/claude_desktop_config.json.

Replace the Agent Toolbelt entry with a Cordon entry.

{
  "mcpServers": {
    "agent-toolbelt-via-cordon": {
      "url": "http://127.0.0.1:7777/mcp",
      "headers": {
        "Authorization": "Bearer <your CORDON_GATEWAY_TOKEN>"
      }
    }
  }
}

Restart Claude Desktop. The hammer icon shows the same 27 tools as before. Nothing in Claude’s view has changed.

Step 4. Run something and watch the log

Ask Claude something that fires a tool call.

“List all the tools available in the Agent Toolbelt.”

Claude calls list_tools. Cordon proxies it through. The audit log writes JSON to stderr.

{"event":"tool_call_received","timestamp":1747366621337,"callId":"call_001","serverName":"agent-toolbelt","toolName":"list_tools","args":{}}
{"event":"tool_call_completed","timestamp":1747366621703,"callId":"call_001","toolName":"list_tools","durationMs":366}

Two lines, one when the call entered Cordon, one when it came back. The durationMs is round-trip latency through the upstream MCP server. That’s it.

If you flip audit.output to 'hosted' with a Cordon API key, those same events land in the dashboard at app.getcordon.com and the call shows up as a row in a table. Same data, friendlier surface.

Step 5. Gate the expensive calls

Right now my policy is 'allow' for everything. Fine for setup, not useful for the actual reason I’m doing this.

Here’s the version I run.

servers: [
  {
    name: 'agent-toolbelt',
    transport: 'stdio',
    command: 'npx',
    args: ['-y', 'agent-toolbelt-mcp'],
    env: { /* same as before */ },
    policy: 'allow',
    tools: {
      stock_thesis:   { action: 'approve', reason: 'Costs $0.05 per call' },
      moat_analysis:  { action: 'approve', reason: 'Costs $0.05 per call' },
      bear_vs_bull:   { action: 'approve', reason: 'Costs $0.05 per call' },
      compare_stocks: { action: 'approve', reason: 'Costs $0.05 per call' },
    },
  },
],
approvals: { channel: 'terminal', timeoutMs: 60_000 },

Now when the agent tries to call stock_thesis, Cordon pauses and asks me before the call fires. In the terminal it shows up as an [A]/[D] prompt with the tool name, arguments, and the reason I wrote. Approve and the call goes through. Reject and the agent gets back a clean error it can recover from.

If you’ve wired up Slack approvals, the prompt comes as a DM with the same context. Useful when the agent is running headless on a server and you’re not at the terminal.

The cheap tools keep flowing through. count_tokens doesn’t need approval. text-extractor doesn’t either. Only the four tools at five cents a call wait for my green light. That’s the trade I want.

Why I built both

I built Agent Toolbelt because I wanted my agents to have tools without writing every one from scratch. Stock research, schema gen, regex, color palettes and the lot.

I built Cordon because I didn’t trust what my agents were doing with those tools.

The two products solve different problems. Agent Toolbelt is the capability. Cordon is the oversight on top of it. Running them stacked is the setup I use on my own machine, and it’s the one I’d recommend to anyone shipping agents past the prototype stage.

Both are free at this scale and Agent Toolbelt has a thousand call free tier. Cordon has no paid tier exposed today, so if you’ve already got Agent Toolbelt wired up, adding Cordon is a ten-minute config change.

Try it