2026-02-27 21:14:45
If you are building a web application for the Bangladeshi market, integrating bKash is an absolute necessity. As the largest mobile financial service (MFS) in Bangladesh, bKash is the go-to payment method for millions of users.
However, integrating the bKash API using Python has historically been a headache. Developers often have to write boilerplate code, manually handle token expiration, and deal with raw HTTP requests. Whether you are using Django, Flask, or FastAPI, you just want a clean, Pythonic way to accept payments.
That’s where pybkash comes in. pybkash is a modern, fully-typed Python SDK for the bKash payment gateway that handles all the heavy lifting for you. It covers the entire bKash API surface and uniquely supports both synchronous (Django/Flask) and asynchronous (FastAPI) operations out of the box.
In this tutorial, we will walk through how to easily integrate the bKash payment gateway into your Python application using pybkash.
Before we start writing code, you will need:
username, password, app_key, and app_secret. You can get these by registering on the bKash Developer Portal.Installing the package is straightforward. Open your terminal and run:
pip install pybkash
To communicate with bKash, you need to generate an authentication token. Traditionally, you would have to request a token and manage its lifecycle. With pybkash, you just pass your credentials into a Token object, and the Client handles the rest automatically.
from pybkash import Client, Token
# 1. Initialize the Token with your bKash credentials
token = Token(
username="your_username",
password="your_password",
app_key="your_app_key",
app_secret="your_app_secret",
sandbox=True # Important: Set to False when moving to production!
)
# 2. Create the bKash Client
client = Client(token)
Note: Always keep your credentials safe using environment variables (e.g., using python-dotenv). Never hardcode them in your production codebase!
The bKash checkout process follows a redirect-based flow: Create → Execute → Query. First, you create a payment intent on your server. bKash will return a secure URL. You then redirect your customer to this URL to enter their bKash number, OTP, and PIN.
Here is how you create that payment URL:
# 3. Create the payment intent
payment = client.create_payment(
callback_url="https://yoursite.com/api/bkash/callback", # Where bKash sends the user after paying
payer_reference="CUSTOMER_001", # Optional: Pre-populates the bKash number on the checkout page
amount=1000 # The amount to charge in BDT
)
print(f"Payment ID: {payment.payment_id}")
print(f"Redirect User To: {payment.bkash_url}")
What happens here?
payment.payment_id: Store this ID in your database. You will need it to verify the payment later.payment.bkash_url: Return this URL to your frontend or issue an HTTP redirect so the user can complete the payment.After the user completes, fails, or cancels the transaction on the bKash page, bKash redirects the user back to the base callback_url you provided in Step 3, appending several query parameters.
For example, a successful redirection looks like this:https://yoursite.com/api/bkash/callback?paymentID=TR0011...&status=success&signature=...
These query parameters act as a signal indicating that the user has finished interacting with the bKash page.
⚠️ Crucial Security Warning: The callback redirection alone should not be treated as final confirmation of a successful transaction, as malicious users can manipulate URLs. You must execute the payment server-side to actually deduct the funds and securely verify the final state.
When the user hits your callback endpoint, extract the status and paymentID from the URL query parameters, and execute the payment:
# 4. Extract query parameters from your web framework's request object
status_from_url = "success" # Extracted from ?status=success
payment_id_from_url = "payment_id_received_from_query_params"
if status_from_url == "success":
# 5. Execute the payment server-side to finalize the transaction
execution = client.execute_payment(payment_id_from_url)
# 6. Verify if the execution was successful
if execution.is_complete():
print(f"Payment successful! Transaction ID (TrxID): {execution.trx_id}")
# TODO: Update your database, mark the order as paid, and send a receipt!
else:
print(f"Execution failed. Status: {execution.status}")
else:
print(f"User cancelled or failed the payment. Status: {status_from_url}")
And that's it! You've successfully integrated bKash into your Python application.
If you are building a high-performance web API using FastAPI or Starlette, blocking synchronous calls can slow down your application. pybkash was built with modern Python in mind and provides first-class async support.
You can simply import the asynchronous client and await your payment methods for non-blocking I/O:
from pybkash import AsyncClient, Token
import asyncio
async def process_bkash_payment():
token = Token(
username="your_username",
password="your_password",
app_key="your_app_key",
app_secret="your_app_secret",
sandbox=True
)
# Initialize the Async client
async_client = AsyncClient(token)
# Create payment asynchronously
payment = await async_client.create_payment(
callback_url="https://yoursite.com/api/bkash/callback",
amount=500
)
print(f"Redirect to: {payment.bkash_url}")
# Run the async function
asyncio.run(process_bkash_payment())
Integrating the bKash payment gateway in Python doesn't have to be complicated. By using the pybkash SDK, you can implement secure, production-ready payment flows in just a few lines of code, whether you are using Django, Flask, or FastAPI.
Beyond basic payments, pybkash also supports advanced features like Agreement Creation (Tokenized Checkout), Refunds, and Transaction Searching.
This tutorial covered the standard checkout flow, but there is much more! For an in-depth look at all available methods, return types, and operational rules, check out the Detailed Usage Documentation directly in the repository.
If this guide helped you save time, please consider dropping a star (⭐) on the pybkash GitHub Repository.
If you find any bugs, have feature requests, or want to contribute to making the best bKash Python SDK even better, feel free to open an issue or submit a pull request!
Happy coding! 🚀
2026-02-27 21:14:39
Let's play a little game. You're a developer who just received a task:
"Take this Java class and rewrite it in Kotlin — keeping the exact same behavior."
Here's the Java class you've been handed:
public class AlunoJava {
private String codigo;
private String nome;
private int numero = 0;
private String texto = "EscolaX";
public AlunoJava(String codigo, String nome) {
this.codigo = codigo;
this.nome = nome;
}
}
Take a moment. How would you write this in Kotlin? 🤔
Before jumping to Kotlin, let's break down what this Java class actually contains:
codigo and nome) that are set via the constructornumero = 0 and texto = "EscolaX")codigo and nome as parameters and assigns them using this
Simple enough. Now let's talk about how Kotlin handles each of these.
In Java, you write:
public class AlunoJava { ... }
In Kotlin, classes are public by default, so you don't need the public keyword:
class AlunoKotlin { ... }
✅ No public needed — less boilerplate from the start.
This is where Kotlin really shines. In Java, the constructor is a separate block:
public AlunoJava(String codigo, String nome) {
this.codigo = codigo;
this.nome = nome;
}
In Kotlin, you can declare the primary constructor directly in the class header. And if you add val or var to the parameters, Kotlin automatically creates the fields and assigns them — no need for this.x = x at all:
class AlunoKotlin(private val nome: String, private val codigo: String) {
// nome and codigo are already declared AND assigned here!
}
✅ The constructor declaration, field declaration, and assignment all happen in one line.
You might be wondering: why val instead of var?
val → immutable (like final in Java) — value can't be reassigned after initializationvar → mutable — value can be changed laterSince the original Java class doesn't reassign codigo or nome after construction, val is the more faithful and idiomatic translation.
In Java:
private int numero = 0;
private String texto = "EscolaX";
In Kotlin, these go inside the class body, and the types are inferred automatically:
private var numero = 0 // Kotlin infers Int
private var texto = "EscolaX" // Kotlin infers String
✅ We use var here because these fields may be changed later (they weren't final in Java).
Putting it all together:
class AlunoKotlin(private val nome: String, private val codigo: String) {
private var numero = 0
private var texto = "EscolaX"
}
Compare side by side:
Java (17 lines):
public class AlunoJava {
private String codigo;
private String nome;
private int numero = 0;
private String texto = "EscolaX";
public AlunoJava(String codigo, String nome) {
this.codigo = codigo;
this.nome = nome;
}
}
Kotlin (3 lines):
class AlunoKotlin(private val nome: String, private val codigo: String) {
private var numero = 0
private var texto = "EscolaX"
}
Same behavior. A fraction of the code. 🎯
| Java concept | Kotlin equivalent |
|---|---|
public class |
class (public by default) |
| Separate constructor block | Primary constructor in class header |
this.x = x assignment |
val/var in constructor parameters |
private String x |
private val x: String |
Type before name (String x) |
Type after name (x: String) |
final field |
val |
| Mutable field | var |
Kotlin was designed to eliminate Java boilerplate without sacrificing clarity. Once you get used to the primary constructor syntax and the val/var distinction, you'll find that most simple Java classes translate to Kotlin naturally — and end up much shorter.
Have you started using Kotlin in any of your projects? Drop a comment below! 👇
2026-02-27 21:14:01
Part 2 of a series on using Claude Code as a production runtime. Originally published on everyrow.io.
Our marketing pipeline scans 18 community sources, enriches threads with full content, classifies opportunities with a 20-question rubric, generates draft forum responses, and creates a pull request - every weekday at 08:00 UTC. The whole pipeline definition is not e.g. Python's functions with some workflow manager and executor like Prefect or Dagster (which are both cool), but - yeah, you guessed it - a markdown file in plain English, written by my boss.
I don't mean my boss specified it and an engineer implemented it. I mean he opened SKILL.md in his editor and typed the pipeline in English. Or more precisely - in the light of this series - he asked Claude Code to write it together with him. It's a markdown file that says things like "spawn 18 scanners in background" and "after phase 1, do phase 2." It's not a formal task DAG and isn't specified in code. And it all runs inside Claude Code, as described in our first post from the series. This post is about a generic comparison of such systems, while we will see specific instances of that in the subsequent posts.
We're not going to pretend this is better than Prefect or Dagster. For a lot of workloads, it's worse. But "a lot" isn't "all," and we think the tradeoff space is genuinely interesting. Here is a somewhat naive comparison:
| Prefect / Dagster | Claude Code | |
|---|---|---|
| Task definition | Python functions, objects, decorators, ... | Markdown files |
| DAG | Explicit dependency graph | "after scanning, enrich" |
| Workers | Containerized functions | Subagents with their own context windows |
| Retry logic | @task(retries=3) |
"if Python enrichment fails, try WebFetch instead" |
| Adding a new integration | Install plugin, configure IO manager, write config schema | "read from BigQuery" |
| Scaffolding | Specific decorators, YAML, definitions.py, webserver config, user code, ... |
Markdown files |
| Deployment | webserver, usercode containers, UIs, DB, ... | one (cron)job as per Part 1 of this series |
| Monitoring | Dashboards, metrics, alerts, orchestration UIs | none? |
| Who writes it | Software engineer | anyone, in English |
| Debugging | Stack traces, breakpoints | Absolutely horrendous |
I am not gonna pretend this comparison is not skewed towards Claude Code, it heavily is, as it's not a full replacement of these. Dagster is giving you stuff like sensors, queues, concurrency, work pools and what not, and if you need those, then go for it. What we are covering here is mostly the job runtime and basic orchestration (which could still be plugged into frameworks like this to benefit from both worlds).
I want to write something like "it's all markdown files", which is a little bit of an exaggeration, but not much! The whole setup is one skill (the orchestrator), a handful of subagent definitions, and some Python libraries for the mechanical stuff. Compare that to e.g. Dagster scaffolding. Dagster is pretty opinionated here and you really want to do things the way it wants you to - definitions.py, YAML config, webserver, user code server, and if you want to read from GCS, the right IO manager plugin configured through Dagster's abstraction layer instead of just... asking Claude to use gsutil. It's all legitimate infrastructure for production workloads. If tomorrow we need to read from BigQuery, we write "query BigQuery for the last 7 days of page analytics" in the skill file and Claude figures out the bq command or the MCP tool or whatever's available (setting those + permissions is still some annoying boilerplate though).
The pipeline is one skill that orchestrates six phases. Most of the heavy lifting is fanned out to subagents running in parallel. We will get into the details of the pipeline in a separate post, but just to give you an idea of what we're talking about:
Phase 1: Scan
├── Python search script → produces shards
├── 18 scanner subagents (one per source: reddit, hubspot, shopify, ...)
└── N search-scanner subagents (one per shard)
↓ poll filesystem for .json / .error files
Phase 2: Enrich
└── Python enrichment (fetch full thread content, WebFetch fallback)
↓
Phase 3: Classify
└── N classifier subagents (one per enriched file, 20 questions, score 1-5)
↓ poll filesystem again
Phase 4: Propose
└── proposer-{product} subagents (one per product with score 4-5 hits)
↓
Phase 5: Report
└── markdown report with metrics, top opportunities, draft responses
↓
Phase 6: Git
└── branch, commit, push, open PR
The orchestrator - the main Claude Code process - reads the skill, spawns the subagents via the Task tool, and coordinates them. The subagents write results to disk. The orchestrator polls for output files rather than collecting agent output directly (we'll get to why in the filesystem section below). The "dependency graph" is just document order: phase 2 comes after phase 1 because it's written after phase 1.
Here's one aspect that the comparison table doesn't capture: Claude Code is accidentally resilient in ways that traditional orchestrators are not.
When a Python script hits an unexpected error, it crashes. By default, the state is lost, you go to some logging tool or something and try to find the bug. If you are lucky, you fix the bug, but often you're not sure if it's hard to reproduce (reddit blocks IPs from GCP), you re-run the whole thing, and hope. Orchestrators are trying to help you by giving you e.g. retries mechanisms, which are good, but far from ideal when dealing with unknown unknowns, i.e. how many retries do you need, what the backoff period is, on what type of errors should you try to retry and so on.
When Claude Code hits an error, it reads the error message and decides what to do. A library isn't installed in the container? It runs apt-get install (scary, but awesome). An API returns an unexpected format? It adapts the parsing. The enrichment script returns fewer results than expected? The pipeline instruction says "use WebFetch for the failed URLs" - and it does, for just the ones that failed, preserving everything that already worked.
This is not magic. It's just that the "retry logic" has access to the same reasoning that wrote the original attempt. It can distinguish between "the server is down, try again" and "this approach won't work, try a different one," in a way traditional retries cannot.
And the state preservation is a great feature on its own. When running locally and phase 3 of our pipeline fails, phases 1 and 2's results are still on disk and in the conversation context. If we're running interactively, we can --resume and say "phase 2 worked fine, start from phase 3 and here's what went wrong." The agent just remembers everything - no checkpoint files, no serialization, no cache key configuration.
Prefect and Dagster have caching, and it's a real feature. But getting it right is real engineering work: hash the inputs properly for the cache key, make sure the task-level cache interacts correctly with the flow-level cache, handle the case where a cached task succeeds but the next task fails, deciding where the cache is stored... We've been through this, and sometimes it's just not worth the effort.
This is a real excerpt from our pipeline definition:
## Phase 1: Scan
### Step 1b: Run Domain Scanners
Spawn all 18 domain scanners in background.
Track each task_id with its source name.
Each: Task (subagent_type: scanner, run_in_background: true): "Scan {source}"
That's the DAG basically: "Spawn 18 things. Track them." Claude Code reads this, spawns 18 subagents, and tracks the task IDs. The "dependency graph" is the document order: Phase 2 comes after Phase 1 because it's written after Phase 1, as... any human naturally works and thinks.
Running it is what we covered in the Part 1. You pass "execute scan-and-classify skill" as a prompt and it runs. Again, you don't have to think about deployments and flags and if you should use deploy() or serve() or anything, just CLI command.
We do have specialized agents that do specific jobs. Agents are spawned with their own context, so the context of the main orchestrating agent doesn't explode. Each subagent is a markdown file with YAML frontmatter:
---
name: scanner
description: Scan a community source for marketing opportunities.
tools: Bash, Read, Write, Glob, Grep
model: sonnet
permissionMode: bypassPermissions
---
We have 23 of these agent definitions. Scanners, classifiers, proposers, graphics generators, dataset finders, SEO analyzers. Each one is a markdown file describing what the agent should do, what tools it has, and what model to use.
One of the design principles is putting mechanics in code and letting Claude make judgments. Specifically, it's this separation of concerns:
lib/scanners/reddit.py → Fetches posts, parses JSON, handles rate limits
.claude/agents/scanner.md → Reads posts, decides "is this a real data problem?"
This is not a strict separation - it's totally fine that the agents are writing some code. But if it's something that can be reused and standardized, it's good to add it, but it's still quite wasteful resource-wise to let agents do everything like scanning API endpoints and stuff.
This is yet another part where running inside Claude Code shines - it can develop and improve itself while running in production. No, really, let that sink in for a second, because you cannot just gloss over it: the development and the runtime blend together. You tell it to run the scanner for a site and it tells you it can't because X, but presents you with a workaround for X that it can incorporate into the skill or lib for future runs. When it encounters that the environment has changed - like an API schema on the remote is different - it can self-correct during the runtime, and even commit that as an improvement for any further runs.
Here's where it gets ugly. In Prefect, it's the orchestrator backend that manages dependencies. Claude is of course able to get state of agents natively, and it worked fine for like 4 agents. When we scaled to 18, the orchestrator's context window filled up with all the returned output, as it seems to be a limitation that Claude cannot get the state without also parsing the output. Claude started forgetting earlier results and producing incomplete reports.
The fix: run_in_background: true + filesystem polling. The orchestrator's context went from O(n * output_size) to O(n * filename). The agents write their results to disk and the orchestrator only reads file paths. Specifically, when a scanner agent finishes, it writes data/scans/reddit/2026-02-17-run1.json. If it fails, it writes data/scans/reddit/2026-02-17-run1.error. The orchestrator polls:
while [ $ELAPSED -lt $TIMEOUT ]; do
SUCCESS=$(ls -1 data/scans/*/${TODAY}-run*.json 2>/dev/null | wc -l)
ERRORS=$(ls -1 data/scans/*/${TODAY}-run*.error 2>/dev/null | wc -l)
if [ "$((SUCCESS + ERRORS))" -ge "$EXPECTED" ]; then break; fi
sleep 10
done
.json means success. .error means failure. ls is the health check. This is not elegant.
Agents will run forever if you let them, and given how imprecise and informal this setup is, you need to add some limits to it. There is nothing especially interesting here, but for completeness, we implemented that on four layers:
Layer 1: max_turns per agent. A hard limit on API round-trips. When a news-finder hits 30 turns, Claude Code stops it and returns whatever it has. We tuned these empirically - 30 was too few for news-finder, 20 was right for dataset-finder.
Layer 2: Wall-clock cap per phase. 10 minutes. If a batch of agents hasn't finished, move on with whatever completed. Mark the stragglers as "timeout" in the report.
Layer 3: Bash timeout 10800. After 3 hours, a second Claude wakes up to salvage partial results (see Post 1).
Layer 4: activeDeadlineSeconds. And finally the hard limit enforced by Kubernetes, set to 4 hours in our case.
I want to be honest about this. Debugging a Claude Code pipeline is pretty subpar. There are no breakpoints or stack traces. When a subagent fails silently, you see nothing - you just notice a .error file appeared, if you remembered to implement .error files in the first place (we didn't).
And as there's no formal verification of any of it - no tests for the pipeline, no type checking on the DAG and so on, you interact with the system through Claude Code because a human genuinely cannot handle the throughput of many parallel scanners writing enriched JSON. It's vibecoding at its finest.
Also, the --dangerously-skip-permissions? The flag is named that for a reason, and there's no way to guarantee that Claude Code won't - to paraphrase a famous Haskell tutorial - go outside and scratch your car with a potato. We run it in an ephemeral container with limited credentials to reduce the blast radius. But if you're someone who needs formal guarantees about what your code will do at runtime, this approach should give you hives. We do acknowledge this, and are fully aware of the associated risks and tradeoffs, and given what this does, the stakes are low.
And of course, writing pipelines in English produces some quirks you'd never see in a traditional codebase. Here are three picks from ours.
The one-sentence retry policy. This is the entire fallback logic for when enrichment fails:
If Python enrichment returns fewer opportunities than scanned,
use WebFetch for the failed URLs.
Add successful results with "enrichment_method": "webfetch".
Log URLs that fail both methods.
In Prefect, that's a custom retry handler with conditional logic. Here it's a paragraph and Claude figures it out.
The anti-coding instruction. The classifier agent's instructions include this:
At no point should you Write() a Python script. If you think you
need one, it's because you misunderstood these instructions.
We added this after a classifier tried to write a sentiment analysis script instead of just... reading the thread and thinking about it.
The "never fully fail" rule. The last section of the skill file:
- Scanner fails: Log failure, continue with others
- Python enrichment fails: Try WebFetch fallback, then continue
- Classifier fails: Log failure, continue with other sources
- Proposer fails: Log failure, keep intermediate files
Never fail the entire skill due to individual component failures.
Always produce a pipeline report.
That last line is doing a lot of work. Even a completely botched run produces a report saying "everything broke" - which is still more useful than a silent crash.
I genuinely enjoy nerding about subagent orchestration and filesystem-based message buses as much as anyone. But everyrow.io is a startup that needs to survive. The cool architecture means nothing if it doesn't change something out in the real world behind the boundaries of the company. Our pipeline generates mostly reports, and also needs some state like what URLs it has already seen and so on. A natural thing for this would be to use a database, define a schema, set up credentials, ... Well, we use GitHub as a store (together with LFS). Every pipeline run creates a PR with a markdown report and necessary state files like a seen.txt with already listed URLs. Our non-technical person opens it, reads the results, expands a draft response, tweaks a sentence, and responds to an opportunity. Or they open the news pipeline PR, pick the better graphic from two variations, downloads the PNG, and shares it. GitHub is the database, the UI, and the delivery mechanism.
This bridges the gap between what an AI can do and a human can do - neither on their own is as good as both together. The AI produces 80% of the work, the human fixes the last hardest 20% and takes action, and together they ship something neither could individually.
The flexibility matters more than the reliability here. When the classifier catches that a Reddit post is actually a competitor's marketing campaign, that's judgment no @task(retries=3) gives you. When news breaks about tariffs and the pipeline routes to QuantGov for regulatory data, that's not something you hard-code in a DAG. And when your boss can read the pipeline definition and say "add Snowflake to the sources" and it just works because the instruction is in English - that's the point.
We build everyrow.io - forecast, score, classify, or research every row of a dataset. This pipeline is how we find the people who need it.
2026-02-27 21:13:15
Every face analysis tool on the internet works the same way: upload your photo to a server, wait for results, hope they don't store your image.
I wanted to build something different — an AI face analyzer that runs entirely in the browser. No server processing, no cloud uploads, no data collection. Your photos never leave your device.
Here's how I built RealSmile, what I learned, and why browser-based AI is the future for privacy-sensitive tools.
Most face analysis apps follow this flow:
This creates three problems:
What if the AI model ran on the user's device instead?
User uploads photo → Browser loads ML model → Processing on user's CPU/GPU → Results displayed
No round trip. No server. No privacy concerns. And crucially — no compute costs for me.
Here's what powers RealSmile:
The key insight: face-api.js models are ~5MB total and load from a CDN (jsdelivr). Once loaded, all processing happens in the browser's JavaScript engine.
The biggest UX challenge is model loading. 5MB is nothing for a server, but noticeable for a first-time user. Here's how I handle it:
let modelsLoaded = false
let modelsLoading = false
async function ensureModels() {
if (modelsLoaded) return // Already loaded, instant
if (modelsLoading) {
// Another call is loading, wait for it
while (modelsLoading) {
await new Promise(r => setTimeout(r, 200))
}
return
}
modelsLoading = true
const MODEL_URL = 'https://cdn.jsdelivr.net/npm/@vladmandic/face-api/model'
await Promise.all([
faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL),
faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL),
faceapi.nets.faceExpressionNet.loadFromUri(MODEL_URL),
])
modelsLoaded = true
modelsLoading = false
}
Key decisions:
One of RealSmile's features is detecting whether a smile is genuine. This is based on the Duchenne smile — a 150-year-old discovery that genuine smiles activate muscles around the eyes (orbicularis oculi), not just the mouth.
const detection = await faceapi
.detectSingleFace(canvas, new faceapi.TinyFaceDetectorOptions({
inputSize: 512,
scoreThreshold: 0.3
}))
.withFaceLandmarks()
.withFaceExpressions()
const expressions = detection.expressions
const happyScore = expressions.happy || 0
const isGenuine = happyScore > 0.6
The expression model returns probability scores for 7 emotions. A high "happy" score combined with landmark analysis around the eye region gives a reasonable approximation of Duchenne smile detection.
The more interesting technical challenge was measuring facial proportions against the golden ratio (φ = 1.618).
With 68 facial landmarks, you can calculate ratios between key measurements:
const PHI = 1.618
// Key landmark points
const faceTop = landmarks[19] // Between eyebrows
const chin = landmarks[8] // Bottom of chin
const leftFace = landmarks[0] // Left jawline
const rightFace = landmarks[16] // Right jawline
const faceHeight = distance(faceTop, chin)
const faceWidth = distance(leftFace, rightFace)
// How close is this ratio to the golden ratio?
const ratio = faceHeight / faceWidth
const score = Math.max(0, (1 - Math.abs(ratio - PHI) / PHI)) * 100
I measure 6 ratios total: face height/width, eye spacing, mouth/nose ratio, nose width, eye width, and nose shape. The overall score is an average of how close each ratio is to its ideal phi-based value.
The architecture that makes this free and private also makes it embeddable. Since there's no server dependency, anyone can add the analyzer to their site with one line of code:
<iframe
src="https://realsmile.online/embed/smile"
width="380"
height="400"
style="border:none;border-radius:16px;"
loading="lazy"
></iframe>
The iframe loads a compact version of the analyzer. Models load from jsdelivr's CDN (not my server). Processing happens in the visitor's browser. My server just serves a static page.
This means even if 10,000 sites embed the widget, my hosting costs stay near zero.
Check out all 4 embeddable widgets at realsmile.online/widget.
On a modern laptop:
On mobile (iPhone 13):
Perfectly usable on both platforms without any server infrastructure.
Here's what it costs to run:
| Component | Cost |
|---|---|
| Vercel hosting (static pages) | $0 (free tier) |
| jsdelivr CDN (model files) | $0 (free) |
| AI processing | $0 (runs on user's device) |
| Domain | ~$12/year |
| Total | ~$1/month |
Compare that to running TensorFlow Serving on a GPU instance: $50-500/month depending on traffic. The browser-based approach is essentially free at any scale.
It's not all sunshine:
For this use case — entertainment-grade face analysis — the tradeoffs are worth it. For medical imaging or security applications, you'd want server-side processing.
If you're building privacy-sensitive tools, consider browser-based ML. The models are good enough for many use cases, the cost savings are dramatic, and users genuinely appreciate knowing their data stays on their device.
Happy to answer questions in the comments!
2026-02-27 21:12:17
Every AI innovation cycle feels familiar. First comes excitement, then the confusing stories and heated takes, and only later does the community start separating promise from reality. OpenClaw landed right in that phase. For many people, it represents a leap forward because it brings an AI agent into the user’s own environment, connected to everyday messaging channels and tools. For others, it became a warning sign, because when something can touch integrations, credentials, and automations, a single mistake can carry a high cost. That’s why the topic feels new and, at the same time, full of contradictions. Both can be true.
What OpenClaw is and why it caught so much attention
OpenClaw can be understood as an AI assistant with agent-like behavior. Instead of only answering questions, it can interact through chat channels and trigger actions via integrations and routines. Its most distinctive idea is local execution, which gives users a stronger sense of control over data, context, and customization. That directly matches a common frustration among people using AI for work and study: relying on external platforms for everything, with limited visibility into how data flows and where it ends up.
The reason OpenClaw is gaining traction is straightforward. It turns conversation into the primary interface. You talk to the agent in a chat, and it begins to perform tasks, organize information, call tools, and return results in the same place. That is what many people picture when they hear the word agent: something that doesn’t just understand, but acts.
What it’s for in practice: from assistant to process operator
At the most basic level, it centralizes quick questions, reminders, and lightweight lookups. The real value shows up when OpenClaw becomes an operational layer. In that scenario, it acts as a bridge between conversation and execution. It can mediate automations, retrieve context from connected sources, organize routines, and trigger actions that previously required opening multiple tabs and switching across systems.
This is where both the benefit and the risk are born. A genuinely useful agent needs access. Access to integrations, access to channels, and often access to tokens and secrets for authentication. What enables capability also increases the blast radius of any failure. In other words, OpenClaw isn’t risky because it is “AI,” but because it can become a privileged hub if configured carelessly.
What it needs to run and why that matters for security
From a technical standpoint, OpenClaw typically requires a modern runtime environment, up-to-date dependencies, and command-line installation. People may run it on a personal machine, a home server, or a virtual machine. Where it runs is not just about convenience, it is part of the risk model.
The most important requirement, and the one that often gets overlooked, is credential handling. An agent connected to channels and tools must store and use API keys, login tokens, and integration secrets. If those secrets are exposed through weak storage, overly broad permissions, or leaked logs, the agent becomes a shortcut into other accounts and systems. The real requirement is not only installing it, but operating it with discipline: where secrets live, how they are rotated, how access is controlled, who can administer the gateway, and how you reduce the overall attack surface.
Vulnerabilities: why an agent flaw is often more severe
When a vulnerability appears in a typical app, the impact may be limited to that app. With an agent, the story changes. An agent is an intermediary that talks to users and to tools. If a flaw enables session theft, unintended execution, or workflow hijacking, an attacker can inherit capabilities that would normally require several separate steps.
In agent ecosystems, two patterns appear repeatedly. The first is authentication, session, and origin-validation issues, where a stolen token or a flawed flow opens doors that should remain closed. The second is exposure of the control plane or gateway, often due to misconfiguration or insecure defaults when deployed in open environments. Even when fixes arrive quickly, risk remains because many deployments are not updated promptly or users do not realize they are publicly reachable.
The key idea is this: agents are not automatically insecure, but they concentrate power, and concentrated power amplifies the consequences of mistakes.
Security debates and why contradictions keep happening
A lot of the intense debate around OpenClaw comes from unfair comparisons. On one side, people treat local execution as an automatic guarantee of privacy and security. On the other, people treat every vulnerability as proof that the project is unusable. Both interpretations oversimplify the reality.
Local execution can help a lot, but it does not solve everything. If you expose the agent interface to the internet, use weak credentials, run with broad permissions, or store secrets poorly, the risk climbs anyway. And the opposite is also true. A vulnerability that is disclosed and patched does not necessarily mean the tool is “dead.” It means the tool requires an operational mindset closer to critical services, not casual apps.
Another source of contradiction is the community effect. The more popular a tool becomes, the more plugins, skills, and automations appear. That accelerates value, but also attracts abuse. Supply chain risk becomes real. Third-party plugins can be malicious, updates can introduce compromised dependencies, and large communities raise the incentive for scams and social engineering. With an agent, installing an extension is not a harmless act. It may be equivalent to granting operational access.
Security needs: what you should demand before you trust it
To treat OpenClaw with maturity, think like an operations team. Start with least privilege. The agent should not have access to everything, only what it needs. Then isolate the environment. Running in a segmented setup with controlled networking reduces impact if something goes wrong. Next is secret management. Tokens and keys should be stored appropriately, with restrictive permissions, no leakage into logs, and a rotation plan. Then comes patching. In fast-moving projects, updates are not optional. Finally, plugin caution. Extensions should be treated as code with real risk, requiring review, validation, and a preference for trusted sources.
Even for personal use, you can apply the same logic in a simplified way. Avoid public exposure, avoid installing random extensions for convenience, separate accounts, limit permissions, and keep versions current. The extra effort is not bureaucracy, it is the price of letting an agent act on your behalf.
Conclusion: OpenClaw is not just a new tool, it is a shift in responsibility
OpenClaw represents an important shift in how we use AI. It does not only respond, it can execute. And once execution enters the picture, security stops being a footnote and becomes part of the product, the user experience, and the user’s responsibility. The contradictions around it make sense because the ecosystem is still maturing and because many people want immediate autonomy without the operational cost that comes with it.
The balanced view is this. OpenClaw can be a powerful piece for productivity and automation, especially for those who want more control and customization. But it needs to be treated as a sensitive component, with governance, limits, and best practices from day one. From that angle, the debate moves away from hype versus panic and becomes what it should be: learning how to build trust through security.
2026-02-27 21:12:00
In 1972, Donella Meadows and her colleagues at MIT published The Limits to Growth, a systems dynamics model that simulated the interaction between population, industrialization, pollution, food production, and resource depletion. The book was controversial, criticized by economists and celebrated by environmentalists. But regardless of whether its specific predictions proved accurate, it introduced millions of people to a way of thinking that remains profoundly underutilized: systems thinking.
A system is a set of interconnected elements organized to achieve a purpose. Your body is a system. A company is a system. An economy is a system. The distinguishing feature of a system, the thing that makes it more than just a collection of parts, is the relationships between those parts. And the most important relationships in any system are feedback loops.
A reinforcing feedback loop, sometimes called a positive feedback loop, amplifies whatever is happening. When a change in one direction triggers further change in the same direction, you have a reinforcing loop. Money in a savings account earns interest, which increases the balance, which earns more interest. A popular product attracts more users, which makes it more useful, which attracts more users.
Reinforcing loops are the engines behind exponential growth. They are also the engines behind exponential collapse. A bank run is a reinforcing loop: fear of bank failure causes withdrawals, which bring the bank closer to failure, which causes more fear. The same structural dynamic that creates virtuous cycles creates vicious cycles. The math is identical. Only the direction differs.
The human brain is poorly equipped to intuit exponential change. We think linearly by default, which causes us to consistently underestimate the power of reinforcing loops in both directions. Early growth seems disappointingly slow because our linear intuition expects constant progress. Late growth seems impossibly fast because we failed to anticipate the acceleration. Understanding how systematic thinking frameworks improve decision quality is the first step toward correcting this cognitive limitation.
A balancing feedback loop, sometimes called a negative feedback loop, counteracts change. When a change in one direction triggers forces that push back in the opposite direction, you have a balancing loop. Your thermostat is a balancing loop: when the temperature rises above the set point, the air conditioner activates to push it back down. When it falls below the set point, the heater activates.
Balancing loops are everywhere. Hunger drives eating, which reduces hunger. Competition drives down prices, which reduces competition. Debt creates pressure to repay, which reduces debt. These loops maintain stability, which is valuable when the stable state is desirable and frustrating when it is not.
Every system that persists over time contains balancing loops. Without them, reinforcing loops would drive the system to infinity or zero in short order. The dynamic behavior of any system emerges from the interaction between its reinforcing and balancing loops. Understanding which loops dominate at any given time is the key to understanding the system's behavior.
Delays are the most underappreciated element of systems. A delay is simply the time gap between an action and its consequence. You exercise today, but the health benefits appear months later. You overeat today, but the weight gain materializes gradually. A company hires engineers today, but their productivity contribution begins months after onboarding.
Delays make systems oscillate. Consider the classic example of a shower with delayed temperature response. You turn the handle toward hot. Nothing happens. You turn it further. Still nothing. Then suddenly the water is scalding. You overcorrect toward cold. Nothing happens. You turn it further. Then the water is freezing. The oscillation is not caused by incompetence. It is caused by the delay between action and feedback.
This same dynamic plays out in business, economics, and personal decisions. The delay between hiring and productivity leads to cycles of overhiring and layoffs. The delay between interest rate changes and economic effects leads to cycles of monetary tightening and loosening. The delay between lifestyle choices and health consequences leads to chronic diseases that could have been prevented by earlier action.
When multiple actors share a common resource, each has an individual incentive to use more of it, but the collective effect of everyone doing so is resource depletion. The reinforcing loop of individual benefit overwhelms the balancing loop of collective sustainability. Fisheries collapse, aquifers drain, and shared budgets deplete because the feedback from individual gain is immediate while the feedback from collective loss is delayed.
When a system experiences a problem, there are often two types of solutions: symptomatic and fundamental. Symptomatic solutions provide quick relief but do not address the root cause. Fundamental solutions take longer but resolve the underlying issue. The trap occurs because the quick symptomatic fix reduces the urgency to pursue the fundamental solution. Over time, the system becomes dependent on the symptomatic fix while the fundamental problem grows worse.
Painkillers for chronic pain, debt for chronic overspending, and caffeine for chronic sleep deprivation are all examples of shifting the burden. Each provides immediate relief that delays the fundamental correction. The greatest strategic thinkers and decision-makers are those who resist the allure of symptomatic fixes and invest in fundamental solutions, even when the payoff is delayed.
When two parties each try to gain advantage over the other through competitive action, the result is often an escalation spiral. Each action by one party triggers a larger response from the other. Arms races, price wars, and social media arguments all follow this pattern. The reinforcing loop of competitive response drives both parties to invest increasing resources for diminishing or negative returns.
Before intervening in any complex situation, take time to map the relevant feedback loops and delays. What are the reinforcing loops that might amplify your action? What balancing loops might counteract it? What delays might cause you to over- or under-correct? This mapping does not need to be mathematically precise to be valuable. Even a rough sketch of the system's structure will reveal dynamics that linear thinking would miss.
Donella Meadows identified a hierarchy of leverage points in systems, places where a small intervention can produce large effects. The most powerful leverage points are not the obvious ones. Changing a parameter like a tax rate is a low-leverage intervention. Changing the rules of the system, the information flows, or the goals of the system is far more powerful.
When the consequences of your actions are delayed, you must resist the temptation to overcorrect. If you have made a change and have not yet seen results, the worst response is usually to make another change. Give the original intervention time to work through the system's delays before concluding it has failed.
In a system with multiple interconnected feedback loops, any intervention will produce consequences beyond the intended effect. A policy to reduce traffic congestion by building more roads may actually increase congestion by encouraging more driving. A decision to boost profits by cutting training budgets may reduce long-term productivity. Systems thinking means asking not just what is the direct effect but also what are the secondary and tertiary effects?
Building the practice of structured systems analysis into your decision-making process allows you to anticipate these dynamics rather than being surprised by them.
Thinking in systems requires a fundamental shift from linear causation to circular causation, from isolated events to patterns of behavior, from blame to structural analysis. When something goes wrong in a system, the natural human response is to find someone to blame. The systems thinker asks instead: what structural dynamics produced this outcome, and how can the structure be changed to produce a different outcome?
This is not an abstract academic exercise. It is the most practical approach to understanding and influencing the complex systems in which we all live and work. The manager who understands feedback loops will manage differently than one who thinks in straight lines. The investor who understands system dynamics will invest differently than one who extrapolates trends linearly. The person who understands the delays in their own life systems will make decisions with a patience and foresight that produces consistently better long-term outcomes.
Systems are everywhere. The question is not whether you live in systems but whether you see them clearly enough to navigate them wisely.