OpenClaw Skills: Teaching Your AI Agent New Abilities

Your AI agent is only as capable as the tools it knows about. Out of the box, OpenClaw gives you a solid foundation — file operations, web search, browser control, messaging. But the real power comes from Skills: modular packages that teach your agent entirely new abilities.

Want your agent to post to X (Twitter)? Generate images via DALL-E? Transcribe audio? Manage GitHub issues? There’s a skill for that. And if there isn’t, you can build one in an afternoon.

This guide covers how Skills work, how to install them, how to configure them, and how to create your own.

What Is a Skill?

A Skill is a directory containing a single SKILL.md file. That file has two parts:

  • YAML frontmatter — metadata: name, description, gating rules (what binaries or API keys are required)
  • Instructions — the actual text injected into the agent’s system prompt, describing how to use the tool

When OpenClaw loads, it scans for eligible skills, filters them based on your environment and config, and injects the relevant instructions into the agent’s context. From that point on, your agent knows how to use that tool — no code changes, no restarts (for new sessions).

Skills follow the AgentSkills spec, which means they’re portable across compatible agent runtimes. A skill built for OpenClaw can also work with other Pi-compatible agents.

Where Skills Live

OpenClaw looks for skills in three places, in order of precedence:

  1. Workspace skills — <workspace>/skills/ (your agent’s workspace folder)
  2. Managed/local skills — ~/.openclaw/skills/
  3. Bundled skills — shipped with OpenClaw itself

If the same skill name appears in multiple locations, the higher-precedence copy wins. This means you can override a bundled skill by placing a modified version in your workspace — without touching the install.

In multi-agent setups, workspace skills are agent-specific (each agent has its own workspace), while ~/.openclaw/skills/ is shared across all agents on that machine.

Finding and Installing Skills from ClawHub

ClawHub is the public registry for OpenClaw skills. Browse it to discover what the community has built.

Installing a skill is one command:

# Install a skill into your workspace
clawhub install <skill-slug>

# Update all installed skills
clawhub update --all

# Sync (scan local skills + publish updates)
clawhub sync --all

By default, clawhub install drops the skill into ./skills/ under your current working directory (falling back to your configured workspace). OpenClaw picks it up as a workspace skill on the next new session.

Treat third-party skills as untrusted code. Read the SKILL.md before enabling — it gets injected into your agent’s system prompt, and malicious instructions could influence behavior. Prefer sandboxed runs for skills that touch sensitive data.

Skill Gating: What Gets Loaded and When

Not every skill is eligible on every machine. OpenClaw uses gating rules in the skill’s frontmatter to decide what loads:

---
name: openai-image-gen
description: "Batch-generate images via OpenAI Images API."
metadata: {"openclaw": {"requires": {"bins": ["node"], "env": ["OPENAI_API_KEY"]}}}
---

Gating options in metadata.openclaw.requires:

  • bins — all listed binaries must exist on PATH
  • anyBins — at least one must exist on PATH
  • env — env variable must be set (or provided via config)
  • config — a key in openclaw.json must be truthy (e.g. browser.enabled)

Other useful flags:

  • always: true — skip all other gates and always include this skill
  • os — limit to specific platforms (darwin, linux, win32)
  • primaryEnv — the main API key this skill needs (used for config UI)

If a skill’s requirements aren’t met, it silently drops out of the eligible set. Your agent won’t see broken skill instructions — it just won’t have that skill available.

Configuring Skills

Some skills need API keys or custom config. Set these in ~/.openclaw/openclaw.json under skills.entries:

{
  "skills": {
    "entries": {
      "openai-image-gen": {
        "enabled": true,
        "apiKey": { "source": "env", "provider": "default", "id": "OPENAI_API_KEY" }
      },
      "my-custom-skill": {
        "enabled": true,
        "env": {
          "MY_TOOL_API_KEY": "sk-..."
        },
        "config": {
          "endpoint": "https://api.myservice.com",
          "model": "fast"
        }
      }
    }
  }
}

Key rules:

  • enabled: false disables a skill even if it’s bundled and eligible
  • env values are injected only if the variable isn’t already in the environment
  • apiKey is a shorthand for the skill’s primary API key — supports plaintext or a SecretRef object
  • Skill names with hyphens must be quoted in JSON5

If you only want specific bundled skills to load, use skills.allowBundled as an allowlist. Workspace and managed skills are unaffected.

Per-Agent vs Shared Skills

When running multiple agents on one machine, skills placement determines scope:

  • Per-agent skills → <workspace>/skills/ — only that agent sees them
  • Shared skills → ~/.openclaw/skills/ — all agents on the machine see them
  • Extra shared folders → configure via skills.load.extraDirs in openclaw.json

This matters when you have a general-purpose agent and a specialized one. Your X-posting skill might live in the marketing agent’s workspace only — your DevOps agent doesn’t need to know about it.

How to Write Your Own Skill

Creating a skill is simpler than it sounds. You need one file:

skills/
  my-weather-skill/
    SKILL.md

A minimal SKILL.md:

---
name: my-weather-skill
description: Get current weather via wttr.in API. Use when the user asks about weather or temperature.
---

## my-weather-skill

To get weather for a location, run:

  curl -s "https://wttr.in/LOCATION?format=3"

Replace LOCATION with a city name or coordinates. Returns a one-line weather summary. Use the exec tool to run this command.

Drop this in <workspace>/skills/my-weather-skill/SKILL.md and start a new session. Your agent will now know how to fetch weather on request.

Advanced: Gating and Environment Injection

If your skill needs an API key or a specific binary, add gating metadata:

---
name: my-api-skill
description: Interact with MyService API for data lookups.
metadata: {"openclaw": {"requires": {"env": ["MY_API_KEY"]}, "primaryEnv": "MY_API_KEY"}}
---

## my-api-skill

You have access to the MyService API via the MY_API_KEY environment variable.

To query data:
  curl -H "Authorization: Bearer $MY_API_KEY" https://api.myservice.com/data

OpenClaw will only load this skill if MY_API_KEY is set — either in the environment or via skills.entries.my-api-skill.apiKey in your config.

Referencing the Skill Directory

Use {baseDir} in your skill instructions to reference the skill folder path. This is useful when your skill includes scripts or assets alongside SKILL.md:

Run the helper script at {baseDir}/run.sh to process files.

Skills vs Plugins: What’s the Difference?

Skills are pure instructions — text injected into the system prompt. They teach the agent how to use existing tools (CLI commands, APIs via curl, shell scripts). No code executes at load time. Zero runtime overhead.

Plugins are code modules that run alongside the Gateway. They can register new tools as first-class agent functions, add CLI commands, start background services, and hook into the Gateway’s RPC layer. Plugins can also ship skills — a plugin might register a new tool and include a skill that teaches the agent how to invoke it.

Rule of thumb: start with a skill. If the skill needs to do something a shell command can’t handle (complex auth flows, streaming APIs, event-driven behavior), graduate to a plugin.

Token Impact and Performance

Skills inject text into your system prompt. The overhead is deterministic:

  • Base overhead (when ≥1 skill is loaded): ~195 characters
  • Per skill: ~97 characters + the length of name + description + location

OpenClaw snapshots eligible skills when a session starts and reuses that list for subsequent turns. Changes to skills (adding, modifying, removing) take effect on the next new session. The skills watcher can hot-reload the snapshot mid-session when SKILL.md files change — configure this under skills.load.watch.

Keep skill descriptions concise. The description field is what triggers skill selection (the agent reads it to decide whether a skill applies). Clear, specific descriptions reduce noise in the system prompt and improve accuracy.

Real-World Examples: Skills in Action

X (Twitter) Posting Agent

The xurl skill teaches the agent how to use the xurl CLI for authenticated X API calls. Install it, configure your API keys, and your agent can draft and post tweets, reply to mentions, and search threads — all from a Slack DM.

GitHub Issue Automation

The gh-issues skill pairs with the gh CLI to let your agent fetch open issues, spawn sub-agents to fix bugs, and open PRs — automatically, on a schedule.

Content Pipeline

Skills can chain: a web_search skill feeds research into an openai-image-gen skill to generate cover art, then a custom Slack-posting skill delivers the finished post. Each skill handles one step; the agent orchestrates the flow.

Getting Started

If you’re new to OpenClaw, start with the complete OpenClaw overview. Once your agent is running:

  1. Browse ClawHub for skills relevant to your workflow
  2. Install one with clawhub install <slug>
  3. Add any required API keys to your config
  4. Start a new session — the agent now has the ability
  5. For custom tools, create a SKILL.md in <workspace>/skills/<name>/

The right skill stack turns your agent from a smart chatbot into a fully operational team member — one that knows how to use your tools, respects your environment, and picks up new abilities as your needs evolve.

Originally published at openclawplaybook.ai. Get The OpenClaw Playbook — $9.99

Building a development organization driven by generative artificial intelligence JAWS 2026 K1

1️⃣ Jeff Barr has driven the AWS news blog for 20 years. His long-term developer advocacy has built a feedback loop between product teams and builders. He continuously maintains the blog to gather developer feedback.
2️⃣ Narrative documents define AWS’s web services vision. He writes documents to ensure product-market fit before software development. Narrative documents are completed before development begins.
3️⃣ Barr has written 3,200 articles documenting service launches. Technical communication drives cloud infrastructure adoption and maintains enterprise transparency. He documents service releases through technical articles.
4️⃣ Barr has visited 14 countries to gather developer insights. Direct customer engagement identifies operational challenges and optimizes the product roadmap. He personally participates in developer events to refine the product roadmap.
5️⃣ Generative AI is changing the software development lifecycle. Internal projects utilize Gen AI to improve developer productivity and maintain safety standards. Generative AI is integrated into internal processes.
6️⃣ Launched Amazon S3 and AWS on March 14, 2006. Tracked milestones to help stakeholders measure the long-term growth of the cloud platform. Recorded key time points to evaluate platform development.
7️⃣ User groups facilitated knowledge exchange among builders. Connected stakeholders to ensure product goals were aligned. Supported the developer community to build scalable systems.
8️⃣ AI-generated images enhanced technical presentations. Included prompts to ensure transparency and reproducibility. Revealed AI involvement and provided input parameters.
9️⃣ Project Mantle identified the value of generative AI. Understood developer workflows to assist in tooling optimization analysis of internal projects to refine AI implementation.
1️⃣0️⃣ Generative AI and development tools evolved daily. Stayed abreast of current developments to maintain a competitive edge in software development. Monitored model releases to integrate emerging technologies.
1️⃣1️⃣ Technological shifts brought operational challenges. Identified skills gaps and conducted targeted training. Assess technical proficiency to address industry transformation.
1️⃣2️⃣ Rapidly changing industry trends. Identify the pace to help organizations adjust product cycles. Use the bullet train analogy to measure AI adoption readiness.
1️⃣3️⃣ Continuous learning mitigates the impact of model updates. Assess individual status to ensure the team keeps up with innovation. Conduct developer surveys to determine tool readiness.
1️⃣4️⃣ Amazon Bedrock provides infrastructure for running models. The platform simplifies AI application building. Use models and inference services through Bedrock.
1️⃣5️⃣ Customization features and safety guardrails protect data. Perform policy checks to ensure enterprise compliance. Apply security protocols to protect customer information.
1️⃣6️⃣ Agentic AI capabilities drive complex application development. Rapid iteration accelerates feature expansion. Utilize Bedrock tools to build autonomous systems.
1️⃣7️⃣ Model catalogs expanded to 277 models in December. Offer diverse options to drive platform adoption. Maintain infrastructure to support model growth.
1️⃣8️⃣ Inference jobs generate varying latency. Optimize GPU resources to improve cost efficiency. Manage computing resources to handle diverse workloads.
1️⃣9️⃣ Job isolation prevents interference between clients. Ensure independent execution to maintain system reliability. Isolate tenant workloads to protect AWS regions’ performance.
2️⃣0️⃣ Tiered service levels offer diverse pricing. Align speed and cost to optimize resource allocation. Choose service tiers based on latency requirements.
2️⃣1️⃣ Model lifecycles require automated resource reclamation. Decommission old models to reduce operational overhead. Monitor model activity to optimize region resources.
2️⃣2️⃣ Model lifecycle management reclaims computing resources from obsolete models. Automated resource allocation prevents waste. Decommission models not used by clients.
2️⃣3️⃣ Project Mantle refactors Bedrock infrastructure. Foundational architecture supports long-term scalability. A senior engineering team modernizes core systems.
2️⃣4️⃣ Traditional development cycles cannot meet market speeds. Align development velocity with market demands to enhance competitiveness. Evaluate development methodologies when behind schedule.
2️⃣5️⃣ Generative AI provides alternatives to manpower expansion. Maintain small teams to reduce communication overhead. Experiment with AI tools to generate productivity.
2️⃣6️⃣ Q Developer and Qiro tools accelerate code generation. Shorter development times demonstrate ROI. Utilize AI to assist in writing unit tests and technical documentation.
2️⃣7️⃣ Gen AI enabled Bedrock to launch within 76 days. Shorten time-to-market for rapid deployment. Utilize AI workflows to meet delivery schedules.
2️⃣8️⃣ Project Mantle supports the publication of 18 models on re:Invent. Infrastructure supports platform expansion. Expand backend systems to accommodate new products.
2️⃣9️⃣ AI integration boosted developer productivity by 20 times. Weekly commits increased from 2 to 40. AI tools were implemented to optimize individual output.
3️⃣0️⃣ Project organization is centralized in monorepo. Centralized code and documentation simplify knowledge management. A single repository structure is used to provide feedback to the model context.
3️⃣1️⃣ Local testing environments are built, removing external dependencies. Developers run full-stack tests on laptops. Local tests are set up to ensure software quality.
3️⃣2️⃣ Automated code generation generates thousands of tests. Code quality is verified through extensive testing. Unit tests are generated with the source code to maintain engineering standards.
3️⃣3️⃣ Future-proofing code requires an AI-friendly development cycle. Code that is easy for the model to understand facilitates iteration. The model is instructed to generate readable syntax.
3️⃣4️⃣ The development cycle achieves hourly code changes. Workflow decomposition accelerates problem-solving. Prompts are given to the model for specific changes.
3️⃣5️⃣ End-to-end tests follow AI code changes. Human review ensures the code safely enters production. Use automated testing to verify logic.
3️⃣6️⃣ Accumulate code for next-day production pushes. Shorten release intervals to deliver business value. Optimize CI/CD pipelines to handle high-frequency commits.
3️⃣7️⃣ Prompt engineering delivers value through specific tasks. Strongly typed languages ​​provide better model information. Use precise instructions to reduce development errors.
3️⃣8️⃣ Strongly typed languages ​​like Rust and Go improve model inference. Structured information ensures outcome quality. Choose languages ​​with clear syntax for AI-assisted development.
3️⃣9️⃣ Gen AI amplifies developer skills. Transform builders into high-performing engineers. Focus on skill acquisition by extending AI capabilities.
4️⃣0️⃣ Workflow separation includes search and fix elements. Isolate exploration and execution to improve prompt accuracy. Use independent instructions to locate logic problems.
4️⃣1️⃣ High-quality prompts build developer confidence. Increase the regularity of development cycles to expand productivity. 4️⃣2️⃣ AI tools produce code of equivalent
quality to human effort. Maintain industry-standard error rates to ensure system reliability. Leverage AI to expand code volume without compromising software quality.
4️⃣3️⃣ Larger code output increases the overall number of errors. A 20x increase in code volume can create downstream operational risks. Monitor critical errors generated in the production environment due to high speed.
4️⃣4️⃣ Traditional testing must be increased to match code volume. Strengthen unit tests and end-to-end tests to reduce risk. Require tools to actively build complete test suites.
4️⃣5️⃣ Test-to-production ratios must expand with AI output. Improve error detection capabilities to prevent faults from reaching customers. Implement smart testing to ensure safe high-speed development.
4️⃣6️⃣ Formal methods use mathematical techniques to prove program correctness. Minimize logical errors to ensure functional correctness. Invest in automated reasoning to find errors missed by traditional testing.
4️⃣7️⃣ Context management impacts model performance. Reduce excessive tools or information to avoid tracking difficulties. Frequently opening new sessions clears context and improves accuracy.
4️⃣8️⃣ Session summarization stores and restores model context. Preserving summary data ensures continuity without sacrificing performance. Use tools to load information only when needed.
4️⃣9️⃣ Loading information on demand prevents preloading overhead. Exercise judgment to ensure the model only receives the data it needs at the moment. Load specific documentation only when requested by the model.
5️⃣0️⃣ Pattern mimicking allows models to replicate modern code. Following updated module patterns ensures repository consistency. Hint at models to use existing patterns in other source code locations.
5️⃣1️⃣ Developer judgment is increasingly important in AI workflows. Long-term built skills ensure high-quality decision-making. Develop personal expertise to effectively guide and validate AI tool outputs.
5️⃣2️⃣ The software building environment is changing for both experienced and junior developers. Understanding career advancement paths helps organizations recruit effective builders. Focus on problem-solving and experience, not just language syntax.
5️⃣3️⃣ Senior developers provide experience and intuition beyond technical skills. Make informed and competent judgments in complex situations to ensure project success. Focus on judgment skills to drive product development.
5️⃣4️⃣ New developers need to be trained with complex systems. Develop judgment through experience to prepare for senior positions. Lead teams through informed decision-making processes.
5️⃣5️⃣ Prompt writing as a new language for expressing ideas. Learn syntax and structure to better communicate with models. Consider instruction writing a core competency in modern software development.
5️⃣6️⃣ Shift-left strategies: Automate QA and build processes. Improve efficiency early to maintain product quality. Ensure every commit passes all configuration tests.
5️⃣7️⃣ Downstream systems face pressure to accelerate output. A 20-fold increase in productivity will put pressure on code repositories and review workflows. Optimize internal processes to handle high-speed AI-assisted output.
5️⃣8️⃣ Code reviews must be scaled to match the merge rate. Avoid developers reviewing excessive code, causing operational bottlenecks. Invest resources to synchronize review speed with generation speed.
5️⃣9️⃣ CI/CD processes need to be triggered frequently to maintain value. Run automated builds to prevent system lag. Ensure the delivery pipeline is as fast as AI-assisted developers.
6️⃣0️⃣ Amdahl’s Law: Identify and optimize system bottlenecks in the architecture. The benefits of improving a single part are limited by the unimproved parts. Balance development speed with downstream processing capacity.
6️⃣1️⃣ Parallel agents generate code independently of developer ideas. They can work without coordination to accelerate the build phase. Foster autonomous agents to handle different layers of the system.
6️⃣2️⃣ Review workload increases fivefold when agents work in parallel. Increase personnel to handle code reviews to prevent delivery bottlenecks. Scalable review capabilities to match AI output.
6️⃣3️⃣ CI/CD systems must process merges and tests at 20x speed. Synchronize downstream processes and deployment speeds to ensure productivity gains. Optimize deployment pipelines to receive high-speed code.
6️⃣4️⃣ Non-developmental software lifecycles require speed optimization. Identify market opportunities involving improved review and CI/CD efficiency. Focus on process improvements to support rapid development.
6️⃣5️⃣ The BMW Group established a serverless CI/CD architecture. Handling 130,000 deployments daily to support 3,000 builders. Implementing a serverless pipeline to scale builds across AWS regions.
6️⃣6️⃣ Multi-region builds allow simultaneous deployments across different operating systems. Covering 1,300 microservices demonstrates enterprise scalability. Maintaining global team pipeline speed using a serverless architecture.
6️⃣7️⃣ Serverless CI/CD operates at the same speed as developers. Documenting architectural details provides best practices for stakeholders. Referencing technical blogs to learn implementation strategies.
6️⃣8️⃣ Decision-making processes must match code generation speed. Accelerating software development requires stakeholders to make decisions 20 times faster. Synchronizing leadership review with technical speed prevents operational bottlenecks.
6️⃣9️⃣ In-person collaboration improves communication within the AI ​​team. Allowing for instant consultation from the same physical location. Co-working optimizes knowledge transfer and problem-solving.
7️⃣0️⃣ Hacker houses represent a lifestyle optimized for maximum productivity. Co-working maximizes development focus. Assess team proximity to determine the best environment for high-productivity projects.
7️⃣1️⃣ Traditional agile models with daily stand-up meetings are becoming less efficient. Weekly meetings are too infrequent for high-speed AI workflows. Redesign development processes to support continuous coordination and rapid iteration.
7️⃣2️⃣ Two-pizza teams must evolve into smaller units. Maintaining one-pizza-size teams reduces communication overhead. Reducing team size increases individual control and functional ownership.
7️⃣3️⃣ As teams shrink, a broader skill set is needed. Each developer manages a wider scope of work and responsibilities. Encourage cross-disciplinary training to ensure builders can handle end-to-end development.
7️⃣4️⃣ Reference manuals and memorization define legacy programming models. Mastering syntax and operators ensures suitability. Beyond static facts, embrace experimental learning in AI environments.
7️⃣5️⃣ Model capabilities are discovered through hands-on exploration. There is no checklist of generative AI strengths and weaknesses. Directly test models to understand their specific utility for business cases.
7️⃣6️⃣ Hands-on exploration reveals model strengths and weaknesses. Directly try out models to find strengths for business cases. Hands-on experience provides a new understanding of limitations.
7️⃣7️⃣ The application philosophy shifts from long-term maintenance to short cycles. Rapidly build software to reduce the need for costly investments. Frequently rebuild applications to align with current customer issues.
7️⃣8️⃣ Disposable code simplifies the software development lifecycle. Long-term code maintenance is no longer enterprise best practice. Treat issue specifications and prompts as durable assets in development.
7️⃣9️⃣ Durable code remains important for microservices. Separate ephemeral application code from long-term infrastructure optimization. Focus engineering efforts on building reusable internal services.
8️⃣0️⃣ Data value increases as application lifespan shortens. Collecting and analyzing information provides an organization with a competitive advantage. Build general-purpose applications that reference and utilize high-value datasets.
8️⃣1️⃣ Generalist skills replace specialized roles in small teams. Adapt to new environments to ensure builders remain resource-sufficient. Develop broad skills to manage end-to-end project challenges.
8️⃣2️⃣ Next-generation skills focus on customer understanding and business cases. Identify user challenge details to drive product-market fit. Focus on stakeholder needs to build effective AI solutions.
8️⃣3️⃣ Communication skills require expressive writing developed for instructions. Use more vocabulary in prompts to produce better results. Practice reading and writing to become an effective communicator for AI tools.
8️⃣4️⃣ Prompt-based development requires high-density word input. Provide more context for models to produce accurate outputs. Write complete instructions to improve software development efficiency.
8️⃣5️⃣ Cooperative skills: Strengthen team connections through physical co-working. Working in the same location improves coordination and speed. Prioritize teamwork to maintain alignment with product goals.
8️⃣6️⃣ Concentration: Technical skills in high-speed environments. Maintain focus to ensure stability amidst tool changes. Allocate time for mental clarification to cope with technology transfer.
8️⃣7️⃣ 15 minutes of continuous learning daily to mitigate the risk of becoming obsolete. Acquire new facts or tool knowledge daily to ensure readiness. Schedule daily training to keep up with industry innovation.
8️⃣8️⃣ Automated code generation: Reduce the need for manual code review. Builders report that AI output meets expectations. Evaluate and validate workflows to identify key areas for human review.
8️⃣9️⃣ Multilingual model support: Remove the English language barrier in software development. Open development to more backgrounds to expand the global talent pool. Use local languages ​​to expand technical engagement and innovation.
9️⃣0️⃣ Prompt engineering uses Markdown and structured rules as its syntax. It shifts from formatted code to natural language guidance to maintain logical standards. Applying markup formats provides a clear sequence for AI tools.
9️⃣1️⃣ Defining problem statements replaces syntax as the primary bottleneck. Developers who communicate effectively gain a competitive advantage. Focus on verbal and written communication to drive technical outcomes.
9️⃣2️⃣ Learning and adaptation are core principles of engineering. Shifting from syntax to expressive instructions ensures long-term career growth. Embrace new methodologies to maintain productivity in changing environments.

https://www.youtube.com/watch?v=r9-ke0c7GsU
[JAWS DAYS 2026] K1 Keynote / Building a Development Organization Led by Generative AI

I Built a Production-Ready Biometric Auth Microservice. Here Are 5 Problems Nobody Warns You About.

Everyone talks about biometric authentication as the future of security. Fingerprint scanning, face recognition, liveness detection… it all sounds elegant in a conference talk or a product demo. Then you actually start building one, and reality shows up uninvited.

I built a biometric authentication microservice from scratch. Not a wrapper around someone else’s API. A standalone, production-ready, multi-tenant service with its own recognition pipeline, anti-spoofing layers, and encryption stack. The goal: offer it as a SaaS product that other applications can plug into.

This post is not a tutorial. I’m not going to walk you through code or tell you which libraries to use. Instead, I want to share 5 real problems I ran into during this process. These are things you won’t find in documentation, Stack Overflow answers, or “Getting Started with Biometrics” blog posts. You only learn them by building.

1. Liveness Detection is Harder Than Face Recognition

Here’s what most people assume: the hard part of biometric auth is recognizing a face. It’s not. Face recognition models are mature, well-documented, and surprisingly accurate even on modest hardware.

The actual hard part? Figuring out whether the face in front of the camera belongs to a living person.

Think about the attack surface for a moment. Someone holds up a photo of the target user. Someone plays a video on their phone screen. Someone prints a high-resolution image and curves it slightly to mimic facial contours. Someone uses a replay attack, feeding a previously captured video stream directly into the system.

Your face recognition model will happily match all of these. It doesn’t care if the face is alive or printed on a piece of paper. That’s not its job.

I treated anti-spoofing as a completely separate engineering layer. Face recognition and liveness detection run as independent steps in my pipeline. Each produces its own confidence score, and both must exceed their respective thresholds for the request to succeed. If either fails, the entire operation is rejected.

This separation was a deliberate architectural decision, not something I bolted on after the first demo. If you’re building any kind of biometric system and you’re treating liveness detection as a “nice to have” or an afterthought, stop and reconsider. It’s a harder problem than recognition itself, and it deserves first-class engineering attention from day one.

2. Why a Layered Architecture is Non-Negotiable

Biometric authentication is not a single feature. It’s a stack of concerns: face detection, recognition, liveness verification, encryption, tenant isolation, compliance logging, and more. Each of these has its own failure modes, its own security implications, and its own performance characteristics.

Early on, I made the decision to design the system as distinct, decoupled layers. Each layer has one job and a clear contract with the next. This is not microservices-for-the-sake-of-microservices. It is security-driven separation of concerns.

The layers, in broad strokes:

  • Input validation and preprocessing. Reject garbage before it reaches anything expensive.
  • Biometric processing. Recognition and liveness as separate sub-layers with independent scoring.
  • Encryption and secure storage. Handles the cryptographic lifecycle of biometric data.
  • Tenant isolation and access control. Ensures one customer’s data never touches another’s.
  • Audit and compliance logging. Records every operation for regulatory requirements.

Why does this matter? Because in a security product, a vulnerability in one layer should not cascade into others. If there’s a bug in how I handle image preprocessing, it should never give an attacker access to encrypted biometric templates. If a tenant isolation check fails, it should never bypass the encryption layer.

There’s a practical benefit too: compliance auditors can review each layer independently. Performance bottlenecks become visible per layer. And when you need to swap out a biometric model for a better one, you don’t have to rewrite your encryption logic.

If your auth system is a monolith where recognition, encryption, and tenant management are tangled together, you are one bug away from a full breach.

3. Encryption is a Lifecycle, Not a Feature

Let me start with the number one rule in cryptography: never roll your own encryption.

This isn’t a suggestion. It’s not a guideline you can bend when you’re feeling clever. Use proven, battle-tested, peer-reviewed cryptographic primitives. If someone tells you they wrote their own encryption algorithm, run.

This rule extends beyond algorithms. Key management, initialization vectors, padding schemes, modes of operation… for all of these, use what the cryptographic community has already built and vetted. The history of security is littered with smart engineers who thought they could do better.

Now, with that foundation in place, here’s the real challenge: biometric data is not a password. If a password leaks, you reset it. If biometric data leaks, the consequences are permanent. You cannot reset someone’s face.

So “encrypt everything” is not a strategy. It’s a starting point. The real questions are: encrypt what, where, when, and how?

I never store raw biometric data. What gets stored is a mathematically transformed, encrypted representation. The transformation is irreversible by design. Even if someone dumps the entire database, they cannot reconstruct the original biometric data from what they find. The goal is not just to encrypt data, but to make leaked data useless.

During comparison operations, decryption happens in a controlled manner: decrypt in memory, perform the match, wipe immediately. Each tenant operates within its own isolated encryption scope. Key management lives entirely separate from application logic.

The lesson: design your encryption strategy around the data lifecycle. Enrollment, storage, comparison, deletion. Each stage carries different risks and requires different protections. “One key encrypts everything” sounds simple because it is. And simple, in this context, means vulnerable.

4. Running ML Models When You Can’t Afford a GPU

Face recognition and anti-spoofing models ideally want GPU acceleration. Inference is faster, throughput is higher, everything feels smoother.

My reality: a VPS under $50/month. Single CPU. Minimal RAM. No GPU.

The easy answer is “you need a GPU.” The more interesting question is: how far can you go without one?

Further than you might think, as it turns out. But it requires deliberate engineering at every level.

Model optimization was the first lever. I evaluated smaller model variants and made conscious precision trade-offs. Not every use case needs the absolute highest accuracy. Choosing a model that’s 98.5% accurate but runs 3x faster on CPU versus one that’s 99.2% accurate but requires a GPU is a valid engineering decision. Context matters.

Memory management was the second. I keep models loaded in memory rather than reloading per request. I avoid redundant copies. Every megabyte counts when you have limited RAM, and a careless memory allocation can turn a working service into an OOM crash.

Request handling was the third. Biometric operations are computationally heavy, so I serialize concurrent requests rather than letting them compete for CPU. A queue with predictable latency is better than parallel execution that degrades unpredictably under load.

The last piece: graceful degradation. Under heavy load, the system reduces throughput rather than crashing. A slower response is always better than no response.

Tight constraints force better engineering decisions. When you have unlimited GPU budget, you can afford to be lazy about optimization. When every CPU cycle and every byte of RAM matters, you think harder about what’s actually necessary. A production-ready biometric auth service can absolutely run on a minimal VPS. You just can’t waste anything.

5. Privacy Regulations Shape Your Architecture

Biometric data is classified as “sensitive personal data” in most jurisdictions. GDPR in Europe, KVKK in Turkey, CCPA in California, and many more. Each comes with its own specific requirements, and none of them are optional.

The temptation is to say “we’ll handle compliance later.” This does not work. Privacy requirements are not features you add on top. They are architectural decisions that affect how you design the system from the ground up.

A few examples:

Data minimization. You should store only the absolute minimum data needed for the operation. If you don’t need to keep a raw image after enrollment, delete it. If you can perform verification with a compact representation instead of a full biometric template, use the compact one.

Consent management. Explicit user consent must be enforced at the API level, not just in the UI. The backend should refuse to process biometric data without a valid consent record.

Right to deletion. When a tenant leaves or a user requests data removal, all biometric data must be irreversibly deleted. Not soft-deleted. Not archived. Destroyed. This means your storage architecture must support true hard deletes.

Audit trail. Every biometric operation must be logged: who processed what data, when, and for what purpose. These logs must be tenant-isolated and tamper-resistant.

I designed all of this into the system from the beginning. Not because I enjoy compliance paperwork, but because retrofitting privacy into an existing architecture is a painful, expensive rewrite. If you’re building anything that handles biometric data, make privacy-by-design your starting principle. Your future self will thank you.

Closing Thoughts

Building a biometric authentication microservice taught me that this space has very little tolerance for shortcuts. “We’ll handle it later” is the most expensive sentence in security engineering.

The five challenges I described are not edge cases. They are core engineering problems that anyone building biometric systems will face:

  1. Liveness detection deserves first-class engineering attention.
  2. Layered architecture is survival, not a luxury.
  3. Encryption is a lifecycle that starts with one rule: never roll your own.
  4. Hardware constraints force better decisions, not worse products.
  5. Privacy compliance is an architectural choice, not a checkbox.

If you’re working on something similar, or considering building biometric capabilities into your product, I’d be happy to discuss approaches. Drop a comment or reach out directly.

And if there’s interest, I may write a follow-up diving deeper into one of these areas.

Filtering a DB using SQL Queries

While practicing SQL, I worked on a movie rental database and tried solving different types of queries using conditions, sorting,limits and pattern matching.

1.Movies with rental rate > $3

SELECT * FROM film WHERE rental_rate > 3;

2.Rental rate > $3 AND replacement cost < $20

SELECT * 
FROM film
WHERE rental_rate > 3 AND replacement_cost < 20;

3.Movies rated ‘PG’ OR rental rate = $0.99

SELECT * FROM film WHERE rating = 'PG' OR rental_rate = 0.99;

4.Top 10 movies by highest rental rate

SELECT * FROM film ORDER BY rental_rate DESC LIMIT 10;

5.Skip first 5, fetch next 3 (ascending rental rate)

SELECT * FROM film ORDER BY rental_rate ASC LIMIT 3 OFFSET 5;

6.First 5 movies sorted by title

SELECT * FROM film ORDER BY title ASC LIMIT 5;

7.Skip first 10, fetch next 3 (highest replacement cost)

SELECT * FROM film ORDER BY replacement_cost DESC LIMIT 3 OFFSET 10;

8.Top 5 movies with highest replacement cost (skip most expensive)

SELECT * FROM film ORDER BY replacement_cost DESC LIMIT 5 OFFSET 1;

9.Rental duration between 3 and 7 days

SELECT * FROM film WHERE rental_duration BETWEEN 3 AND 7;

10.Rentals between two dates

SELECT * FROM rental WHERE rental_date BETWEEN '2005-05-01' AND '2005-06-01';

11.Title starts with ‘A’ and ends with ‘e’

SELECT * FROM film WHERE title LIKE 'A%e';

12.Title starts with ‘A’ or ‘B’ and ends with ‘s’

SELECT * FROM film WHERE (title LIKE 'A%s' OR title LIKE 'B%s');

13.Title contains “Man”, “Men”, or “Woman”

SELECT * FROM film WHERE title LIKE '%Man%' OR title LIKE '%Men%' OR title LIKE '%Woman%';

14.Title starts with ‘The’

SELECT * FROM film WHERE title LIKE 'The%';

15.Title contains “Love” or “Hate”

SELECT * FROM film WHERE title LIKE '%Love%' OR title LIKE '%Hate%';

16.Titles ending with ‘er’, ‘or’, or ‘ar’ (top 5)

SELECT * FROM film WHERE title LIKE '%er' OR title LIKE '%or' OR title LIKE '%ar' LIMIT 5;

17.Movies released in 2006, rental rate 2.99 or 3.99, title starts with ‘S’

SELECT title, rental_rate, release_year FROM film WHERE release_year = 2006 AND rental_rate IN (2.99, 3.99) AND title LIKE 'S%' LIMIT 5;

18.Movies with rental rate 4.99 and replacement cost > 20

SELECT * FROM film WHERE rental_rate = 4.99 AND replacement_cost > 20;

19.Movies with rental rate 0.99 OR rating ‘PG-13’

SELECT * FROM film WHERE rental_rate = 0.99 OR rating = 'PG-13';

20.Movies with rating in (‘G’, ‘PG’, ‘PG-13’)

SELECT * FROM film WHERE rating IN ('G', 'PG', 'PG-13');

21.Movies with rental rate between $2 and $4

SELECT * FROM film WHERE rental_rate BETWEEN 2 AND 4;

22.Movies where special_features is NULL

SELECT * FROM film WHERE special_features IS NULL;

23.Customers without email

SELECT * FROM customer WHERE email IS NULL;

24.Movies with rental duration > 7 days

SELECT * FROM film WHERE rental_duration > 7;

25.Movies with rental rate 2.99 or 4.99, rating ‘R’, title contains “Love”

SELECT * FROM film WHERE rental_rate IN (2.99, 4.99) AND rating ='R' AND title LIKE '%Love%' LIMIT 10;

26.Titles containing % symbol

SELECT * FROM film WHERE title LIKE '%%%' ESCAPE '';

27.Titles containing underscore _

SELECT * FROM film WHERE title LIKE '%_%' ESCAPE '';

28.Titles containing digits

SELECT * FROM film WHERE title REGEXP '[0-9]';

29.Titles containing backslash ()

SELECT * FROM film WHERE title LIKE '%\\%';

30.10 customers after skipping 20 (sorted by last name)

SELECT * FROM customer ORDER BY last_name ASC LIMIT 10 OFFSET 20;

What I Learned

  • How to filter data using WHERE, AND, OR
  • Sorting with ORDER BY
  • Pagination(Page navigation) using LIMIT and OFFSET
  • Pattern matching with LIKE and REGEXP
  • Handling missing data using NULL

I Built Uilora – An Interactive UI Component Library for Modern Web Developers

Building modern interfaces often means rewriting the same UI patterns again and again. Dashboards, loaders, animations, layouts, and small interaction details take time even though every developer has built them before.

So I started building Uilora, an interactive UI component library focused on helping developers explore, test, and integrate UI components faster.

Instead of static documentation, Uilora lets developers see components in action and understand how they behave in real interfaces. The goal is to make UI development more practical by providing reusable building blocks that can be easily integrated into modern web projects.

Uilora includes components like dashboards, interactive UI elements, animations, loaders, and interface patterns commonly used in real products.

It’s built for developers who want to move faster without sacrificing clean design and reusable architecture.

I’m continuously expanding the library and experimenting with new UI patterns and developer-friendly interactions.

If you’re interested in modern UI development, component systems, or building faster frontend workflows, I’d love to hear your thoughts.

How to Connect Your AI Agent to Slack (Complete Setup Guide)

Your AI agent lives on your machine. Your team lives in Slack. The gap between those two is where most “AI at work” experiments die — an agent nobody can talk to is an agent nobody uses.

OpenClaw’s Slack integration bridges that gap. It connects your agent to Slack via either Socket Mode or HTTP Events API, giving your agent the ability to participate in DMs, channels, and threads like any other team member. Not a webhook that dumps text — a real conversational presence with access control, threading, reactions, and file handling.

This guide walks through the complete setup, from creating a Slack app to fine-tuning channel policies. By the end, your agent will be a Slack citizen.

Prerequisites

Before you start, you’ll need:

  • OpenClaw installed and running — if you haven’t done this yet, check out What is OpenClaw?
  • A Slack workspace where you have admin permissions (or can ask an admin to install apps)
  • 5-10 minutes — the setup is straightforward once you know the steps

Step 1: Create a Slack App

Head to api.slack.com/apps and click “Create New App”. Choose “From a manifest” — it’s faster than clicking through every permission screen manually.

Here’s a manifest that covers everything OpenClaw needs:

{
  "display_information": {
    "name": "OpenClaw",
    "description": "Slack connector for OpenClaw"
  },
  "features": {
    "bot_user": {
      "display_name": "OpenClaw",
      "always_online": false
    },
    "app_home": {
      "messages_tab_enabled": true,
      "messages_tab_read_only_enabled": false
    },
    "slash_commands": [
      {
        "command": "/openclaw",
        "description": "Send a message to OpenClaw",
        "should_escape": false
      }
    ]
  },
  "oauth_config": {
    "scopes": {
      "bot": [
        "chat:write",
        "channels:history",
        "channels:read",
        "groups:history",
        "im:history",
        "im:read",
        "im:write",
        "mpim:history",
        "mpim:read",
        "mpim:write",
        "users:read",
        "app_mentions:read",
        "assistant:write",
        "reactions:read",
        "reactions:write",
        "pins:read",
        "pins:write",
        "emoji:read",
        "commands",
        "files:read",
        "files:write"
      ]
    }
  },
  "settings": {
    "socket_mode_enabled": true,
    "event_subscriptions": {
      "bot_events": [
        "app_mention",
        "message.channels",
        "message.groups",
        "message.im",
        "message.mpim",
        "reaction_added",
        "reaction_removed",
        "member_joined_channel",
        "member_left_channel",
        "channel_rename",
        "pin_added",
        "pin_removed"
      ]
    }
  }
}

After creating the app, install it to your workspace. Slack will ask you to authorize the scopes — approve them. You’ll get a Bot Token (xoxb-...) on the install confirmation page. Save it.

Step 2: Get Your Tokens

You need two tokens for Socket Mode (the recommended approach):

  1. Bot Token (xoxb-...) — found under OAuth & Permissions after installing the app
  2. App Token (xapp-...) — go to Basic Information → App-Level Tokens, click “Generate Token”, name it anything, and add the connections:write scope

The Bot Token lets your agent send messages and read channels. The App Token opens the WebSocket connection that Slack uses to push events to your agent in real time.

Step 3: Configure OpenClaw

Add the Slack channel to your openclaw.json:

{
  "channels": {
    "slack": {
      "enabled": true,
      "mode": "socket",
      "appToken": "xapp-1-...",
      "botToken": "xoxb-..."
    }
  }
}

Or use environment variables if you prefer keeping tokens out of config files:

SLACK_APP_TOKEN=xapp-1-...
SLACK_BOT_TOKEN=xoxb-...

Config tokens take precedence over env vars. The env fallback only works for the default account.

Why Socket Mode?

Socket Mode is the default for good reason. It uses a persistent WebSocket connection — no public URL needed, no webhook endpoints to expose, no SSL certificates to manage. Your agent connects outbound to Slack, which means it works behind firewalls, on home networks, even on a Raspberry Pi. If you’re just getting started, use Socket Mode.

Step 4: Start the Gateway

openclaw gateway

That’s it. OpenClaw connects to Slack, establishes the WebSocket, and your agent is online. Send it a DM in Slack — you should see a response within seconds.

Controlling DM Access

By default, Slack DMs use pairing mode. This means when someone DMs your agent for the first time, they get a pairing code. You approve the pairing from the CLI:

openclaw pairing approve slack <code>

This is the most secure default — nobody talks to your agent without explicit approval. But you have other options:

  • pairing (default) — require explicit pairing approval per user
  • allowlist — only specific Slack user IDs can DM the agent
  • open — anyone in the workspace can DM (requires allowFrom: ["*"])
  • disabled — no DMs at all, channels only
{
  "channels": {
    "slack": {
      "enabled": true,
      "mode": "socket",
      "appToken": "xapp-...",
      "botToken": "xoxb-...",
      "dmPolicy": "allowlist",
      "allowFrom": ["U01ABC123", "U02DEF456"]
    }
  }
}

Group DMs (MPIMs) are disabled by default. Enable them with "dm": { "groupEnabled": true } if your team uses group chats.

Adding Channel Access

DMs are one-on-one. Channels are where teams actually work. To let your agent participate in Slack channels, configure the group policy:

{
  "channels": {
    "slack": {
      "groupPolicy": "allowlist",
      "channels": {
        "C0A4G5T2DLM": {
          "requireMention": true
        },
        "C0ACMC7HM18": {
          "requireMention": false,
          "users": ["U01ABC123"]
        }
      }
    }
  }
}

Key channel settings:

  • requireMention — when true, the agent only responds when explicitly @mentioned. This is the right default for most channels.
  • users — restrict which users the agent responds to in this channel.
  • allowBots — let the agent respond to other bots. Usually keep this off.
  • systemPrompt — inject a per-channel system prompt. Your agent can behave differently in #engineering vs #marketing.
  • skills — restrict which skills are available per channel for tighter access control.

HTTP Events API Mode (Alternative)

If you need a publicly accessible endpoint — maybe you’re running on a VPS with a domain — OpenClaw supports that too:

{
  "channels": {
    "slack": {
      "enabled": true,
      "mode": "http",
      "botToken": "xoxb-...",
      "signingSecret": "your-signing-secret",
      "webhookPath": "/slack/events"
    }
  }
}

In HTTP mode, set your webhook URL as the Request URL in three places in your Slack app settings: Event Subscriptions, Interactivity, and Slash Commands.

Threading and Sessions

OpenClaw maps Slack threads to isolated sessions so context stays clean:

  • DMs route as direct sessions — with default settings, DMs share your agent’s main session context
  • Channels create per-channel sessions — agent:<agentId>:slack:channel:<channelId>
  • Threads can spawn thread-scoped sub-sessions

Control reply threading behavior with replyToMode:

{
  "channels": {
    "slack": {
      "replyToMode": "all",
      "replyToModeByChatType": {
        "direct": "off",
        "channel": "all"
      }
    }
  }
}

Options: off, first, all.

Live Streaming Responses

OpenClaw supports Slack native text streaming — your agent’s responses appear token-by-token, like watching someone type in real time:

{
  "channels": {
    "slack": {
      "streaming": "partial",
      "nativeStreaming": true
    }
  }
}

This uses Slack’s Agents and AI Apps API. Requirements: enable Agents and AI Apps in your Slack app settings, ensure the assistant:write scope is granted, and a reply thread must be available.

Reactions and Acknowledgments

When someone sends a message, your agent can react with an emoji to acknowledge it’s processing:

{
  "channels": {
    "slack": {
      "ackReaction": "eyes"
    }
  }
}

The reaction appears immediately while the agent generates a response. Your agent can also add reactions as part of its workflow — acknowledging bug reports with 🔨, marking tasks complete with ✅, flagging urgent items with 🚨.

Multi-Account Setup

Running your agent across multiple Slack workspaces? OpenClaw supports named accounts:

{
  "channels": {
    "slack": {
      "enabled": true,
      "accounts": {
        "primary": {
          "mode": "socket",
          "appToken": "xapp-primary-...",
          "botToken": "xoxb-primary-...",
          "allowFrom": ["U01ABC123"]
        },
        "client-workspace": {
          "mode": "http",
          "botToken": "xoxb-client-...",
          "signingSecret": "client-secret",
          "webhookPath": "/slack/client-events"
        }
      }
    }
  }
}

Troubleshooting

Agent not responding in channels: Check groupPolicy → channel allowlist → requireMention setting → per-channel users allowlist. Run openclaw channels status --probe.

DMs being ignored: Verify dm.enabled is true, check dmPolicy, confirm pairing is approved (openclaw pairing list slack).

Socket Mode won’t connect: Double-check both tokens. Ensure Socket Mode is enabled in Slack app settings.

General debugging:

openclaw channels status --probe
openclaw logs --follow
openclaw doctor

What’s Next

Once Slack is connected, your agent is a team member. Explore further:

OpenClaw also supports Telegram, Discord, WhatsApp, Signal, iMessage, and more. Same agent, multiple surfaces.

Originally published at openclawplaybook.ai. Get The OpenClaw Playbook — $9.99

CA 36 – Isolation (ACID)

This one was about Isolation, basically what happens when two people try to use the same account at the same time.

I used two sessions (like opening two terminals).

First I checked data:

SELECT * FROM accounts;

Alice -> 1000
Bob -> 500

Now in Session 1:

BEGIN;

UPDATE accounts
SET balance = balance – 800
WHERE name = ‘Alice’;

I did not commit yet.

Now in Session 2, I tried:

SELECT * FROM accounts WHERE name = ‘Alice’;

It still shows 1000, not 200.
So uncommitted change is not visible → no dirty read.

Then in Session 2 I tried to update:

UPDATE accounts
SET balance = balance – 300
WHERE name = ‘Alice’;

This got stuck (waiting).
Because Session 1 is still holding the lock.

Then I commit in Session 1:

COMMIT;

Now Session 2 continues and updates based on new value.

So final balance becomes:
1000 – 800 – 300 = -100 (but this fails due to CHECK constraint)

So transaction fails and rollback happens.

Then I tried changing isolation level:

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

Default level, works like above (no dirty reads).

Then tried:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

Now if two transactions conflict, one will fail with error instead of waiting silently.

So what I understood:

Isolation makes sure one transaction does not mess with another
Uncommitted data is not visible
Database uses locks to control access
Prevents issues like dirty read and lost update

So even if two users try to send money at same time, DB keeps things safe.

Building a Modular ERP from Scratch with Flutter, Supabase, and Riverpod — Part 1

English Version 🇺🇸

Why I started this project

I decided to build a modular ERP as a portfolio project with two goals in mind:

  1. build something real, with actual business value
  2. learn professional software development in practice instead of studying isolated concepts with no real context

The project is called ERP Modular, and it is aimed at small businesses.

Right now, the focus is only on Module 1: Warehouse and Inventory.

The goal is not to jump into multiple modules at once. The goal is to build a solid foundation first and expand later with more confidence.

What I am building

In this first module, I want to support a real operational flow:

  • import NF-e XML files, the XML documents used by Brazil’s electronic invoice system
  • read barcodes
  • check invoice items against scanned products
  • manage inventory with movement history
  • generate conference PDFs
  • support multiple companies with Row Level Security

So this is not just a pretty UI project. It is a system designed around real business rules.

For readers outside Brazil: NF-e (“Nota Fiscal eletrônica”) is the standardized electronic invoice format widely used in the country.

Chosen stack

The stack I chose was:

  • Flutter for the frontend
  • Supabase for the backend
  • Riverpod for state management
  • Go Router for navigation
  • mobile_scanner for barcode scanning
  • xml for NF-e parsing

I chose Flutter because I wanted a single codebase that could eventually run on web, desktop, and mobile.

I chose Supabase because I wanted a real PostgreSQL database, built-in authentication, storage, and row-level security without having to maintain my own backend from day one.

The most important rule of the project

Before writing the first line of code, I defined one rule:

I did not want to repeat the beginner mistake of coding without direction and accumulating technical debt from the very first commit.

Because of that, my first session was entirely dedicated to planning.

I wrote no code at all. And that was intentional.

Session 1: planning before implementation

In the first session, I defined:

  • the scope of the current module
  • the technical stack
  • the layered architecture
  • the feature-based folder structure
  • Git rules
  • the initial database model
  • the OOP concepts and design patterns I wanted to apply consciously

The architecture looked like this:

lib/
  features/
    estoque/
      presentation/
      application/
      domain/
      infrastructure/
    notas/
      presentation/
      application/
      domain/
      infrastructure/
    conferencia/
      presentation/
      application/
      domain/
      infrastructure/
  core/
    services/

The idea was simple:

  • presentation displays
  • application orchestrates state
  • domain defines rules and contracts
  • infrastructure implements external access

I also established a few golden rules from the beginning:

  • no widget talks directly to the database
  • no provider knows where the data comes from
  • complex objects should be created through factory constructors
  • commits must follow Conventional Commits

That first session mattered because it forced me to think like someone building a real system, not just assembling screens.

Session 2: setup and infrastructure

In the second session, planning became real structure.

That was when I:

  • created the public GitHub repository
  • configured the project in Supabase
  • generated the Flutter project correctly
  • organized the layered folder structure
  • added supabase_flutter, flutter_riverpod, and go_router
  • initialized Supabase in the app
  • ran the application on Linux desktop

One detail that changed my mindset was the fact that the first important commit was not a feature.

It was .gitignore.

I protected credentials before almost anything else, including the local Supabase configuration file.

That may sound small, but it is not.

It was a technical decision to avoid leaking something sensitive into the repository history right at the beginning.

Another important part of this session was getting more intentional exposure to terminal commands and Git.

I had used Git before, but this was the moment when I started to better understand:

  • staging
  • the difference between local and remote
  • commit organization
  • the value of small commits
  • commit history visualization in VS Code Source Control

Seeing Supabase initialized and the app running for the first time was a simple milestone, but a very meaningful one.

Session 3: full authentication and the first real engineering moment

The third session was the most intense one so far.

In about two hours, I implemented the complete authentication feature while respecting the project’s layers:

  • domain
  • infrastructure
  • application
  • presentation

The final result was a real login flow with:

  • email and password authentication
  • form validation
  • loading and error states
  • display of the authenticated user’s name and role
  • working logout

But the most important part was not that the login worked.

It was how it was built.

1. The Usuario domain class

This was the first real domain entity in the project.

I consciously applied:

  • final fields
  • a constructor with required named parameters
  • factory Usuario.fromMap()
  • toMap()
  • equality and hashCode overrides

This was the moment when some OOP concepts finally started to make sense in practice.

For example:

  • immutability: an authenticated user should not suddenly become a different user during execution
  • encapsulation: the object controls its own state and its own creation

2. IAuthRepository

Instead of letting the UI or the state layer talk directly to the backend, I defined an abstract contract with the main authentication methods.

In other words, anything that depends on this repository knows what it does, but not how it does it.

3. SupabaseAuthRepository

This class isolated the concrete Supabase implementation.

That taught me an important distinction:

  • Supabase Auth handles credentials
  • business-related user data lives in the usuarios table

So the flow was not just “log in.”

It was:

  1. authenticate with Supabase Auth
  2. fetch the user’s name, role, and company data
  3. transform that raw data into a valid domain object

That was one of those moments when architecture stopped being a diagram and became code with clear responsibilities.

4. AuthState and AuthNotifier

For state management, I modeled the authentication scenarios explicitly:

  • initial
  • loading
  • authenticated
  • unauthenticated
  • error

That helped me understand why explicit state modeling avoids invalid combinations and makes UI logic simpler.

5. LoginScreen

The screen became responsible for observing state and displaying what made sense for each scenario.

No database logic.

No infrastructure logic.

No improper coupling.

That was exactly the kind of separation I wanted to practice in this project.

The mistake that taught me the most so far

Not everything worked on the first try.

In Session 3, I started implementing the authentication state using a Riverpod v2 pattern, but the project was already using Riverpod v3.

The result was a lot of errors at once in the editor.

It was frustrating for a few minutes, but it turned into one of the best lessons so far.

Because the problem was not simply “the code is broken.”

The problem was more professional than that:

I was using an outdated pattern for a different version of the library.

Fixing it required rewriting the implementation using Notifier and NotifierProvider, and that made the concept much clearer than if everything had worked immediately.

Another small but useful mistake also happened: I committed an empty interface file because I forgot to save it before running git add.

I fixed it with a fix commit and enabled autosave to prevent it from happening again.

What this project is actually teaching me

So far, the biggest gain is not only technical.

It is mindset.

I am starting to understand more clearly:

  • why architecture is not bureaucracy
  • why separating responsibilities reduces confusion
  • why small commits improve the way I think
  • why patterns make sense when they emerge as solutions to real problems
  • why documenting blockers is just as valuable as documenting wins

I am also seeing something important in practice:

good code does not appear ready-made.

It improves as the foundation, criteria, and understanding improve.

Next steps

The next focus of the project is to keep expanding the Warehouse and Inventory module according to the roadmap, moving toward product listing screens, product modeling, XML import, barcode-based checking, stock movements, and reports.

But the priority remains the same:

grow with consistency, not in a hurry.

Conclusion

This project is my way of studying software development more honestly.

Instead of only consuming theory, I am trying to build something real and use every step to better understand:

  • OOP
  • layered architecture
  • design patterns
  • Git
  • domain modeling
  • backend integration

It is still the beginning.

But it is the most solid beginning I have managed to build so far.

If you are also studying architecture, Flutter, or building a portfolio project with more intention than urgency, I will keep documenting this journey.

Versão em Português 🇧🇷

Por que comecei este projeto

Decidi construir um ERP modular como projeto de portfólio com dois objetivos em mente:

  1. construir algo real, com valor de negócio de verdade
  2. aprender desenvolvimento de software de forma prática, em vez de estudar conceitos isolados sem contexto real

O projeto se chama ERP Modular e é voltado para pequenos negócios.

Neste momento, o foco está somente no Módulo 1: Almoxarifado e Estoque.

A ideia não é sair construindo vários módulos de uma vez. A prioridade é criar uma base sólida primeiro e expandir depois com mais confiança.

O que estou construindo

Neste primeiro módulo, quero atender um fluxo operacional real:

  • importar arquivos XML de NF-e
  • ler códigos de barras
  • conferir itens da nota com os produtos escaneados
  • controlar o estoque com histórico de movimentações
  • gerar PDFs de conferência
  • suportar múltiplas empresas com Row Level Security

Ou seja, este não é apenas um projeto com uma interface bonita. É um sistema pensado em torno de regras de negócio reais.

Stack escolhida

A stack que defini foi:

  • Flutter no frontend
  • Supabase no backend
  • Riverpod para gerenciamento de estado
  • Go Router para navegação
  • mobile_scanner para leitura de códigos de barras
  • xml para parse de NF-e

Escolhi Flutter porque queria um único codebase com potencial para rodar em web, desktop e mobile.

Escolhi Supabase porque queria um banco PostgreSQL real, autenticação pronta, storage e segurança em nível de linha sem precisar manter um backend próprio desde o primeiro dia.

A regra mais importante do projeto

Antes de escrever a primeira linha de código, defini uma regra:

eu não queria repetir o erro de iniciante de sair codando sem direção e acumular dívida técnica desde o primeiro commit.

Por isso, minha primeira sessão foi inteiramente dedicada ao planejamento.

Eu não escrevi nenhuma linha de código. E isso foi intencional.

Sessão 1: planejamento antes da implementação

Na primeira sessão, defini:

  • o escopo do módulo atual
  • a stack técnica
  • a arquitetura em camadas
  • a estrutura de pastas por feature
  • as regras de Git
  • a modelagem inicial do banco
  • os conceitos de POO e os design patterns que eu queria aplicar conscientemente

A arquitetura ficou assim:

lib/
  features/
    estoque/
      presentation/
      application/
      domain/
      infrastructure/
    notas/
      presentation/
      application/
      domain/
      infrastructure/
    conferencia/
      presentation/
      application/
      domain/
      infrastructure/
  core/
    services/

A lógica era simples:

  • presentation exibe
  • application orquestra estado
  • domain define regras e contratos
  • infrastructure implementa o acesso externo

Também estabeleci algumas regras de ouro desde o começo:

  • nenhum widget fala diretamente com o banco de dados
  • nenhum provider sabe de onde os dados vêm
  • objetos complexos devem ser criados por factory constructors
  • os commits precisam seguir Conventional Commits

Essa primeira sessão foi importante porque me obrigou a pensar como alguém que está construindo um sistema real, e não apenas juntando telas.

Sessão 2: setup e infraestrutura

Na segunda sessão, o planejamento virou estrutura real.

Foi quando eu:

  • criei o repositório público no GitHub
  • configurei o projeto no Supabase
  • gerei o projeto Flutter corretamente
  • organizei a estrutura de pastas da arquitetura
  • adicionei supabase_flutter, flutter_riverpod e go_router
  • inicializei o Supabase no app
  • rodei a aplicação no Linux desktop

Um detalhe que mudou minha forma de pensar foi o fato de o primeiro commit importante não ter sido uma feature.

Foi o .gitignore.

Eu protegi as credenciais antes de quase qualquer outra coisa, incluindo o arquivo local de configuração do Supabase.

Isso pode parecer pequeno, mas não é.

Foi uma decisão técnica para evitar que algo sensível vazasse para o histórico do repositório logo no começo.

Outra parte importante dessa sessão foi ter um contato mais intencional com comandos de terminal e com Git.

Eu já tinha usado Git antes, mas esse foi o momento em que comecei a entender melhor:

  • staging
  • a diferença entre local e remoto
  • a organização dos commits
  • o valor de commits pequenos
  • a visualização do histórico de commits no Source Control do VS Code

Ver o Supabase inicializado e o app rodando pela primeira vez foi um marco simples, mas muito significativo.

Sessão 3: autenticação completa e o primeiro momento real de engenharia de software

A terceira sessão foi a mais intensa até agora.

Em cerca de duas horas, implementei a feature completa de autenticação respeitando as camadas do projeto:

  • domain
  • infrastructure
  • application
  • presentation

O resultado final foi um fluxo de login real com:

  • autenticação por e-mail e senha
  • validação de formulário
  • estados de carregamento e erro
  • exibição do nome e do papel do usuário autenticado
  • logout funcionando

Mas a parte mais importante não foi apenas o login funcionar.

Foi como ele foi construído.

1. A classe de domínio Usuario

Essa foi a primeira entidade de domínio real do projeto.

Nela, apliquei conscientemente:

  • campos final
  • construtor com parâmetros nomeados obrigatórios
  • factory Usuario.fromMap()
  • toMap()
  • sobrescrita de igualdade e hashCode

Esse foi o momento em que alguns conceitos de POO finalmente começaram a fazer sentido na prática.

Por exemplo:

  • imutabilidade: um usuário autenticado não deve simplesmente virar outro durante a execução
  • encapsulamento: o objeto controla o próprio estado e a própria criação

2. IAuthRepository

Em vez de deixar a UI ou a camada de estado falarem diretamente com o backend, defini um contrato abstrato com os principais métodos de autenticação.

Em outras palavras, qualquer parte que dependa desse repositório sabe o que ele faz, mas não como ele faz.

3. SupabaseAuthRepository

Essa classe isolou a implementação concreta com Supabase.

Isso me ensinou uma distinção importante:

  • o Supabase Auth cuida das credenciais
  • os dados de negócio do usuário ficam na tabela usuarios

Então o fluxo não era apenas “fazer login”.

Era:

  1. autenticar com o Supabase Auth
  2. buscar nome, papel e dados da empresa do usuário
  3. transformar esses dados brutos em um objeto de domínio válido

Esse foi um daqueles momentos em que a arquitetura deixou de ser um diagrama e virou código com responsabilidades claras.

4. AuthState e AuthNotifier

No gerenciamento de estado, modelei explicitamente os cenários da autenticação:

  • inicial
  • carregando
  • autenticado
  • não autenticado
  • erro

Isso me ajudou a entender por que uma modelagem explícita de estados evita combinações inválidas e simplifica a lógica da UI.

5. LoginScreen

A tela passou a ser responsável por observar o estado e exibir o que fazia sentido em cada cenário.

Sem lógica de banco de dados.

Sem lógica de infraestrutura.

Sem acoplamento indevido.

Esse era exatamente o tipo de separação que eu queria praticar neste projeto.

O erro que mais me ensinou até agora

Nem tudo funcionou de primeira.

Na Sessão 3, comecei implementando o estado da autenticação com um padrão de Riverpod v2, mas o projeto já estava usando Riverpod v3.

O resultado foi uma grande quantidade de erros de uma vez no editor.

Foi frustrante por alguns minutos, mas acabou se tornando uma das melhores lições até aqui.

Porque o problema não era simplesmente “o código está quebrado”.

O problema era mais profissional do que isso:

eu estava usando um padrão desatualizado para uma versão diferente da biblioteca.

Corrigir isso exigiu reescrever a implementação usando Notifier e NotifierProvider, e isso deixou o conceito muito mais claro do que se tudo tivesse funcionado de primeira.

Outro erro pequeno, mas útil, também aconteceu: comitei um arquivo de interface vazio porque esqueci de salvá-lo antes de executar git add.

Corrigi com um commit de fix e ativei o autosave para evitar que isso aconteça de novo.

O que este projeto está realmente me ensinando

Até aqui, o maior ganho não é apenas técnico.

É mentalidade.

Estou começando a entender com mais clareza:

  • por que arquitetura não é burocracia
  • por que separar responsabilidades reduz confusão
  • por que commits pequenos melhoram a forma como eu penso
  • por que patterns fazem sentido quando surgem como solução para problemas reais
  • por que documentar travamentos é tão valioso quanto documentar acertos

Também estou vendo algo importante na prática:

código bom não aparece pronto.

Ele melhora à medida que a base, os critérios e o entendimento evoluem.

Próximos passos

O próximo foco do projeto é continuar expandindo o módulo de Almoxarifado e Estoque de acordo com o cronograma, avançando para telas de listagem de produtos, modelagem de produto, importação de XML, conferência por código de barras, movimentações de estoque e relatórios.

Mas a prioridade continua a mesma:

crescer com consistência, não com pressa.

Conclusão

Este projeto é a minha forma de estudar desenvolvimento de software de maneira mais honesta.

Em vez de apenas consumir teoria, estou tentando construir algo real e usar cada etapa para entender melhor:

  • POO
  • arquitetura em camadas
  • design patterns
  • Git
  • modelagem de domínio
  • integração com backend

Ainda é só o começo.

Mas é o começo mais sólido que já consegui construir até aqui.

Se você também está estudando arquitetura, Flutter ou construindo um projeto de portfólio com mais intenção do que urgência, eu vou continuar documentando essa jornada.

Secure AI Agent Architecture

I’ve Started Writing an Open Book on Secure AI Agent Architecture

I’ve started writing an open book on the architecture of secure AI agents.

The goal is to build a practical engineering reference — not a collection of flashy demos, but a structured guide to production-grade agent systems: control planes, policy boundaries, tool execution, memory, observability, evaluations, approvals, and governance.

The first chapters are already live:

Repository: https://github.com/agent-axiom/agent-arch

There is a lot of excitement around agents, but far less shared engineering guidance on how to build them safely and operate them reliably in production. This project is my attempt to help close that gap.

I’d genuinely appreciate thoughtful feedback from the community:

  • what feels solid
  • what is missing
  • what seems debatable
  • what should be improved
  • what operational or security practices deserve more attention

If this topic is close to your work, I’d be glad to hear your critique, ideas, counterexamples, and contributions.

I Built a Mac App That Does One Thing: Let You Clean Your Keyboard

Every MacBook owner knows this moment. You grab a cloth to wipe your keyboard, and the second you touch a key, Spotlight opens. You brush the trackpad, the cursor flies across the screen. You wipe the function row and your volume changes, your brightness drops, your music skips. You’re trying to clean the machine, and the machine keeps interpreting your cleaning as commands.

It sounds trivial. It’s not.

This small frustration is the reason I built Mac Pause. And the process of building it taught me a lot about how macOS actually works under the surface.

There’s No “Cleaning Mode” for Your Mac

Think about it. Your Mac has Do Not Disturb, Focus modes, screen savers, sleep timers, and a dozen other states it can enter. But there’s no mode that says: “Hey, I’m wiping my keyboard right now. Please ignore everything for the next thirty seconds.”

The workarounds all have problems. You can shut down completely, but that’s overkill for a quick wipe. You can lock the screen, but keys and controls still register in ways you don’t expect. You can try to be careful, but “carefully cleaning a keyboard” kind of defeats the purpose.

What was missing was simple: a temporary, safe way to suspend all input on macOS so you can touch any surface without consequences.

The Concept Was Tiny on Purpose

Mac Pause was designed to be small. Not small as in “MVP I’ll expand later.” Small as in: this is the entire product.

The idea was a menu bar utility with one job. Start a cleaning session. Block keyboard and pointer input. Exit only when the user deliberately unlocks it. Include a failsafe timeout in case anything goes wrong.

That’s it. No settings panels with forty options. No subscription. No system tweaker ambitions. One sentence should explain what the app does. If it can’t, the scope is wrong.

I believed (and still do) that the best utilities are the ones that feel obvious once they exist. “Of course there should be a cleaning mode for your Mac.” That’s the reaction I was going for.

Then I Started Building It, and macOS Had Other Plans

The concept was straightforward. The implementation was not. macOS is a sophisticated operating system with deep security layers, and building an app that suppresses input touches several of them at once.

Permissions Are the First Wall

An app that wants to intercept and block keyboard and mouse input can’t just start doing it. macOS requires explicit user permission, specifically Accessibility and Input Monitoring access.

That’s entirely reasonable from a security standpoint. But from a product standpoint, it creates immediate friction. Your brand-new utility that does “one simple thing” now needs the user to open System Settings, find the right section, toggle the right switch, and potentially restart the app.

Permission handling isn’t a side feature. It’s a core part of the user experience. I learned this early. If the permission flow is confusing or feels aggressive, people won’t trust your app enough to grant access.

“Block Input” Isn’t Actually One Operation

At first glance, “pause keyboard and mouse” sounds like flipping a switch. On macOS, it’s more like intercepting a river at multiple points.

For a regular (non-kernel) app, the practical approach is event suppression: you intercept input events and prevent them from reaching the rest of the system. That means the app has to catch the right categories of events, suppress them consistently, and still preserve a reliable unlock path.

That last part was non-negotiable. A utility that blocks all input and then gets stuck has turned your laptop into a brick. Mac Pause needed both an intentional unlock mechanism and a hard timeout that would always restore control, no matter what.

The Function Row Didn’t Play by the Rules

Here’s where it got interesting. I had regular key blocking working. Mouse input was blocked. Everything seemed fine. Then I wiped the function row during a test and the volume changed.

Turns out, function keys on macOS (brightness, volume, media controls) don’t always travel through the same event pipeline as regular keyboard input. Some of them arrive as legacy system-defined events, which is a different path entirely. So “keyboard blocked” didn’t actually mean “all keyboard-looking input blocked.”

This is the kind of thing that makes system utilities tricky. The first 90% of the problem is solvable in a weekend. The last 10% is where your assumptions get tested by how the OS actually works.

How Do You Unlock Something That Blocks All Input?

This was a subtle design problem. The obvious answer is “press Escape to exit.” But if you’re cleaning your keyboard, you probably want to wipe the Escape key too. A single-key exit means there’s one key you can never touch during cleaning.

I landed on a hold-based chord: Right Shift + Escape. You hold both keys for a short duration to unlock.

That solved several problems at once. It was unlikely to trigger accidentally during cleaning. It let users clean the Escape key by itself (just don’t hold Right Shift at the same time). And the hold requirement made the action feel intentional, which matters for something safety-related.

A tap would have been too risky. People brush keys while cleaning. A deliberate hold is a different kind of input, and that distinction is the whole point.

The Setup Screen Almost Became the Problem

One of the more ironic bugs I hit wasn’t in the input blocker at all. It was in the permission setup flow.

At one point, the permission prompt behaved too much like a system-level overlay. It filled the screen. It was hard to dismiss. The very app designed to prevent your Mac from feeling “trapped” was trapping people during setup.

I rebuilt the setup flow into something calmer: a smaller utility window, clear guidance about what permissions were needed and where to find them, and honest messaging when macOS didn’t behave consistently (because sometimes the system doesn’t deep-link to the exact settings page you’d expect).

That redesign ended up improving the whole app. Honesty about system quirks turned out to be better UX than pretending everything would work perfectly every time.

Packaging Was the Invisible Make-or-Break

Here’s something that surprised me. macOS privacy permissions are sensitive to how your app is packaged and identified. Running a raw executable during development is not the same as running a properly bundled .app with a stable bundle identifier.

During development, I hit a confusing state where permissions appeared granted in System Settings, but the app still couldn’t suppress input correctly. The fix wasn’t a code change. The fix was proper packaging: a real .app bundle, a stable bundle ID, an app icon, and a clean zip for distribution.

It’s the kind of work nobody ever sees, but it’s the difference between a utility that works reliably and one that works “most of the time.”

What Came Out the Other Side

The final product is a small menu bar utility with a short, predictable flow:

Start a cleaning session. Wait through an arming countdown (so you have time to put down the mouse). Clean while everything is blocked. Exit by holding Right Shift + Escape, or let the automatic timeout handle it.

I also added a few things that turned out to matter more than expected. Partial lock modes (keyboard only, or pointer only) for people who just want to clean one surface. Solid backdrop modes for screen cleaning. Launch-at-login support. A visible overlay so you always know the app is active.

The app is distributed as a direct download, outside the App Store. That was a deliberate choice. For a small, focused utility, the App Store’s review process and sandboxing restrictions add friction without adding much value.

The Lesson That Keeps Repeating

Building Mac Pause reinforced something I’ve seen over and over with macOS utilities.

The headline feature is rarely the hard part. “Block input” took a fraction of the total effort. The real work was everything around it: permission flows, edge cases with function keys, unlock safety, packaging, and making the whole experience feel trustworthy enough that someone would grant an app control over their keyboard and mouse.

Small tools demand a level of polish that bigger apps can sometimes get away with skipping. When your entire product is one interaction, every rough edge is visible.

Mac Pause is the kind of app that should feel almost unnecessary once you’ve used it. Of course your Mac should have a cleaning mode. The fact that it didn’t was the gap, and filling it turned out to be a lot more interesting than I expected.

Try It

Mac Pause is open source. The full code is on GitHub: github.com/gloverola/macPause

If you find it useful, I’d really appreciate a star on the repo. It helps more people discover the project.

Installing from a release

Download the latest MacPause-<version>.zip from the releases page, then:

  1. Unzip the file.
  2. Open Mac Pause.app.
  3. Drag it into /Applications if you want to keep it installed.
  4. If macOS warns that the developer cannot be verified, right-click the app and choose Open.
  5. Grant Accessibility and Input Monitoring when prompted (you’ll find these under System Settings > Privacy & Security).
  6. Quit and reopen once after enabling Input Monitoring. macOS often needs a restart of the app before it recognizes that permission correctly.

If Gatekeeper still blocks the app after download, you can remove the quarantine flag in Terminal:

xattr -dr com.apple.quarantine "/Applications/Mac Pause.app"

Building from source

If you’d rather build it yourself:

./Scripts/build_app_bundle.sh

Then run the bundled app:

./Scripts/run_bundled_app.sh

Mac Pause requires macOS 13 or later.