Designing a Domain Model That Actually Models the Domain

Ports and adapters only work if you have a domain worth protecting.

If your domain objects are just bags of getters and setters no behavior, no rules, no real logic the boundaries don’t buy you much. You’ve built a clean fence around an empty lot.

This post is about filling the lot.

The Anemic Domain Model

Here’s the Order and Customer you see in most codebases even the “clean” ones:

from dataclasses import dataclass
from typing import List, Optional

@dataclass
class OrderItem:
    product_id: str
    price: float
    quantity: int

@dataclass
class Customer:
    id: str
    loyalty_points: int

@dataclass
class Order:
    customer_id: str
    items: List[OrderItem]
    loyalty_points: int
    total: float = 0.0
    status: str = "draft"
    id: Optional[str] = None

These are data containers. They hold values. They don’t do anything.

The business rules that involve these objects how totals are calculated, what makes an order valid, whether a customer qualifies for a discount live somewhere else. Usually scattered across service methods, utility functions, and the occasional if statement buried in a route handler.

This is the anemic domain model. Martin Fowler named it in 2003. It’s still the default pattern in most Python codebases.

The problem isn’t that these classes are wrong. It’s that they’re incomplete. The data is here but the behavior isn’t.

What Belongs in the Domain

Before adding behavior, it helps to know what belongs there.

The rule: if a piece of logic is about the rules of your business, it belongs in the domain. If it’s about how you store or transmit data, it belongs in infrastructure.

Concretely, for an order management system:

Domain logic:

  • How is an order’s total calculated?
  • When is an order considered valid?
  • What makes a customer eligible for a discount?
  • Can a placed order be cancelled?
  • What’s the maximum quantity per item?

Not domain logic:

  • How do you persist an order to PostgreSQL?
  • How do you serialize an order to JSON?
  • How do you send an email confirmation?
  • What HTTP status code do you return when an order isn’t found?

The first list lives on your domain objects. The second list lives in adapters, serializers, and route handlers. When logic from the second list ends up on the domain objects, you have leakage. When logic from the first list ends up in service methods or route handlers, you have an anemic domain.

Evolving the Model

Let’s move the business rules back where they belong.

from __future__ import annotations
from dataclasses import dataclass, field
from typing import List, Optional


@dataclass(frozen=True)
class Money:
    amount: float
    currency: str = "USD"

    def __post_init__(self) -> None:
        if self.amount < 0:
            raise ValueError(f"Money amount cannot be negative: {self.amount}")

    def __add__(self, other: Money) -> Money:
        if self.currency != other.currency:
            raise ValueError(f"Cannot add {self.currency} and {other.currency}")
        return Money(self.amount + other.amount, self.currency)

    def __mul__(self, factor: float) -> Money:
        return Money(round(self.amount * factor, 2), self.currency)


@dataclass(frozen=True)
class Discount:
    rate: float  # 0.0 to 1.0

    def __post_init__(self) -> None:
        if not (0.0 <= self.rate <= 1.0):
            raise ValueError(f"Discount rate must be between 0.0 and 1.0, got: {self.rate}")

    def apply(self, amount: Money) -> Money:
        return amount * (1.0 - self.rate)

    @classmethod
    def loyalty(cls, loyalty_points: int) -> Discount:
        """Return the discount a customer earns based on loyalty points."""
        if loyalty_points > 500:
            return cls(0.10)
        return cls(0.0)

Two value objects: Money and Discount. Both are frozen dataclasses immutable by construction. Both validate themselves. You cannot create a Money with a negative amount. You cannot create a Discount with a rate outside [0.0, 1.0]. The rules are encoded in the objects, not enforced by whatever code happens to use them.

Now the OrderItem and Order:

@dataclass
class OrderItem:
    product_id: str
    price: Money
    quantity: int

    def __post_init__(self) -> None:
        if self.quantity <= 0:
            raise ValueError(f"Quantity must be positive, got: {self.quantity}")
        if self.quantity > 100:
            raise ValueError(f"Quantity cannot exceed 100 per item, got: {self.quantity}")

    def subtotal(self) -> Money:
        return self.price * self.quantity


@dataclass
class Customer:
    id: str
    loyalty_points: int

    def discount(self) -> Discount:
        return Discount.loyalty(self.loyalty_points)


@dataclass
class Order:
    customer_id: str
    items: List[OrderItem]
    discount: Discount
    status: str = "draft"
    id: Optional[str] = None

    def __post_init__(self) -> None:
        if not self.items:
            raise ValueError("An order must contain at least one item")

    def total(self) -> Money:
        raw = Money(0.0)
        for item in self.items:
            raw = raw + item.subtotal()
        return self.discount.apply(raw)

    def place(self) -> Order:
        if self.status != "draft":
            raise ValueError(f"Cannot place an order with status '{self.status}'")
        self.status = "pending"
        return self

    def cancel(self) -> Order:
        if self.status not in ("draft", "pending"):
            raise ValueError(f"Cannot cancel an order with status '{self.status}'")
        self.status = "cancelled"
        return self

Look at what changed:

  • total() is a method on Order, not a calculation in OrderService. The order knows how to compute its own total.
  • place() and cancel() encode the state machine. An order can’t be placed if it’s not a draft. That rule lives on the object not in a service method that might forget to check.
  • Customer.discount() knows how to determine what discount a customer earns. The OrderService doesn’t have to know the threshold is 500.
  • OrderItem.subtotal() knows how to compute its own contribution.
  • Validation is in __post_init__. You can’t construct an invalid object. You can’t create an OrderItem with zero quantity.

Value Objects

Money and Discount are value objects. The term comes from domain-driven design, but the concept is simple: an object defined entirely by its value, not by an identity.

Two Money(100.0, "USD") instances are equal and interchangeable. You don’t care which one you have they’re the same. In contrast, two Order objects are not interchangeable even if they have the same items. They’re distinct entities with their own identities and histories.

Value objects have two properties worth building in from the start:

Immutability. Use frozen=True on the dataclass. A Money object doesn’t change arithmetic operations return new instances. This eliminates a whole class of bugs where shared state gets mutated in unexpected places.

Self-validation. Validate in __post_init__. The rule lives with the type. Any code that creates a Money gets the validation for free there’s no way to bypass it accidentally.

The practical test: if you find yourself writing if amount < 0: raise ValueError in three different places in your codebase, you have a value that wants to be a value object.

How the Service Simplifies

With behavior on the objects, the service gets thinner:

from app.domain.ports import CustomerRepository, OrderRepository
from app.domain.models import Order, OrderItem
from typing import List


class OrderService:
    def __init__(
        self,
        customer_repo: CustomerRepository,
        order_repo: OrderRepository,
    ):
        self.customer_repo = customer_repo
        self.order_repo = order_repo

    def place_order(self, customer_id: str, items: List[OrderItem]) -> Order:
        customer = self.customer_repo.find_by_id(customer_id)
        if not customer:
            raise ValueError(f"Customer {customer_id} not found")

        order = Order(
            customer_id=customer_id,
            items=items,
            discount=customer.discount(),
        ).place()

        return self.order_repo.save(order)

Compare this to the Post 2 version. We removed order.total = order.calculate_total() and order.status = "pending" both are now handled by the domain objects themselves. The service coordinates. It doesn’t compute.

This is the distinction: services orchestrate, domain objects encapsulate. When the service is doing business calculations, something has leaked.

What’s Next

You now have a domain worth protecting: real behavior, real rules, real validation all on the objects that own them.

The last piece is wiring. You have domain objects. You have ports. You have adapters. Now you need a place that assembles them: the application layer. Dependency injection. The startup wiring that connects a real SQLAlchemy repository to the service that declares it needs something that can save an order.

That’s Post 4: “Wiring It All Together: Adapters, DI, and the Application Layer”.

I built a VS Code extension to capture terminal output as styled screenshots

TermSnap Preview

Ever tried sharing terminal output with someone? You’ve got two bad options:

Copy-paste – and watch your perfectly aligned table turn into a mess. Paste into WhatsApp, Slack, Google Docs, or Word and the columns misalign, box-drawing characters break, and structured output becomes an unreadable wall of text.

Screenshot – but your platform’s screenshot tool only captures what’s on screen. If your table or log extends beyond the viewport, you’re stitching multiple screenshots or just giving up.

I kept hitting both problems, so I built TermSnap.

What is TermSnap?

A VS Code extension that lets you select terminal text – no matter how long – press Cmd+Shift+S, and get a single Carbon-style screenshot of the entire output.

TermSnap Preview

Features

  • One shortcut – Select text in terminal, press Cmd+Shift+S (Mac) / Ctrl+Shift+S (Windows/Linux)
  • Full selection capture – Even hundreds of lines that don’t fit on screen
  • Carbon-style rendering – Rounded window frame, macOS dots, shadow, themed background
  • 11 built-in themes – Dracula, Tokyo Night, Nord, Monokai, GitHub Dark, Catppuccin Mocha, and more
  • Custom themes – Define your own colors in VS Code settings
  • Live preview – Adjust theme, font size, padding, line numbers, window chrome in real time
  • Export options – Save as PNG, copy image to clipboard, or copy raw text

Works great for

  • Tables, ASCII art, mermaid diagrams, flowcharts
  • Deployment logs and CLI output (kubectl, docker, aws cli, firebase)
  • Documentation, presentations, slides, blog posts
  • Anywhere plain text loses formatting when pasted

How to use

  1. Open terminal in VS Code
  2. Run your commands
  3. Select the output
  4. Press Cmd+Shift+S
  5. Adjust theme and settings in the preview panel
  6. Click Save as PNG, Copy Image, or Copy Text

Custom themes

Don’t like the built-in themes? Define your own:

{
  "termsnap.theme": "custom",
  "termsnap.customBackground": "#1a1a2e",
  "termsnap.customTextColor": "#e0e0e0",
  "termsnap.customTitleBarColor": "#16213e"
}

Install

It’s free and open source (MIT). Would love to hear what features or themes you’d want to see – drop a comment or open a GitHub issue!

A beginner’s guide to understanding tools in Strands Agents | Part 1

We’re now in the world of agents. Agents do the work for you by acting on an instruction you give them. So if you’re building agents, understanding how to add tools to your agents helps you build agents that don’t just chat, but can also do stuff for you.

Tools are how you extend the capabilities of an agent. Using tools, agents can access and search external information such as the internet, databases, and APIs. With tools, agents also perform calculations and specialized processing such as mathematical operations, data analysis, and more. They can create, modify, and manage content by generating documents, execute code or system commands, and connect to any external service such as weather APIs, payment systems, and enterprise software.

Simply put, I like to think of tools as a makeup artist’s toolkit; just like a makeup artist chooses specific brushes and products based on the look they’re creating, the agent selects the right tools for the job. Ok, maybe a different analogy for you, think of tools as a chef’s equipment, the chef doesn’t use every pot and pan for every dish, but selects the right tools based on the tasty dish they’re preparing. Your agent orchestrates its tools to complete your request and what I find really great here is that you don’t tell the agent which tools to use, it figures it out on its own through autonomous reasoning.

In this post which is Part 1 of a series, I’ll introduce you to tools in agentic AI, show you how Strands Agents SDK implements them, and walk you through a real example, a Seasonal Wardrobe Curator Agent I built using Strands. By the end, you’ll understand what tools are, why you should care about them if you’re building agents, and how to use tools with your own agents.

I want the Seasonal Wardrobe Curator Agent to do the tasks outlined below, so keep all this in mind as we start to look at the code in the subsequent sections:

You [agent] help users curate their wardrobe for the current season by:
1. First determining what season it is.
2. You understand their gender and style preferences.
3. You research recent fashion shows and trending styles for the current season.
4. Then, you create a curated wardrobe shopping list with checkboxes.
5. Finally, calculate budget estimates in South African Rand (ZAR) when asked.

Let’s get started.

Tools in Strands Agents SDK

Now, looking at how Strands Agents SDK implements tools, there are two types of tools in Strands; built-in tools and custom tools. Let’s dive in to each one of them.

1. Built-in tools

Strands Agents Tools is a community-driven project that provides a powerful, ready-to-use set of (built-in) tools for your agents.

Here’s the import to load them to our agent:

from strands_tools import calculator  # For point 5 in the task outline above, budget calc.

agent = Agent(
    model=model,
    tools=[calculator]  # One line—that's it!
)

With one import line you’ve loaded tools (in this case, the Calculator tool, ready to use by your agent. Because this is a built-in tool, there’s no decorator required and due to it being from a community-driven project, you’re assured that it’s being tested and maintained by the community. Common built-in tools include the Calculator for mathematical operations, HTTP request for API interactions, and more.

2. Custom tools

When you need domain-specific functionality that doesn’t exist in the community package, you can create custom tools using the @tool decorator. Here’s an implementation for the season detector functionality (point 1 in the task outline above):

from strands.tools import tool
from datetime import datetime

SEASON_MAP = {
    "southern": {(12, 1, 2): "Summer", (3, 4, 5): "Autumn", (6, 7, 8): "Winter", (9, 10, 11): "Spring"},
    "northern": {(12, 1, 2): "Winter", (3, 4, 5): "Spring", (6, 7, 8): "Summer", (9, 10, 11): "Autumn"},
}

SOUTHERN_LOCATIONS = {"South Africa", "Southern Hemisphere", "Australia", "New Zealand"}

def _get_season(month: int, hemisphere: str) -> str:
    for months, season in SEASON_MAP[hemisphere].items():
        if month in months:
            return season
    return "Unknown"

@tool
def get_current_season(location: str = "South Africa") -> str:
    """
    Determines the current season based on the date and location.

    Args:
        location: Geographic location (default: South Africa for Southern Hemisphere)

    Returns:
        The current season name and additional context
    """
    now = datetime.now()
    hemisphere = "southern" if location in SOUTHERN_LOCATIONS else "northern"
    season = _get_season(now.month, hemisphere)
    label = "Southern Hemisphere" if hemisphere == "southern" else "Northern Hemisphere"
    return f"Current season: {season} ({label}) - {now.strftime('%B %Y')}"

Custom tools give you full control over functionality as they can be tailored to your specific needs and are reusable across multiple agents (for example, I created the custom tool in it’s own separate Python file season_detector.py). In addition to the calculator custom tool, I also created web search (to find current fashion trends) and file operations (to save the wardrobe checklist) custom tools (points 3 and 4 of the task outline above). Here’s how I load them to the agent.

from tools.season_detector import get_current_season
from tools.web_search import web_search
from tools.file_ops import write_file

The agent now has access to four tools; three custom (season detection, web search, file operations) and one built-in (calculator).

tools=[get_current_season, web_search, calculator, write_file] 

3. System prompts

Finally, I want to guide the agent with system prompts. The system prompt should describe the agent’s role and capabilities, when to use each tool, and the expected behavior and tone.

system_prompt="""You are a Seasonal Wardrobe Curator Agent specializing in fashion recommendations for South African users.

YOUR ROLE:
You help users curate their wardrobe for the current season by:
1. First, use the get_current_season tool to determine what season it is
2. Understanding their gender and style preferences
3. Researching recent fashion shows and trending styles for the current season
4. Creating a curated wardrobe shopping list with checkboxes
5. Calculating budget estimates in South African Rand (ZAR) when asked

IMPORTANT:
- All budget calculations must be in South African Rand (ZAR)
- Use realistic South African pricing for clothing items
- Consider local fashion retailers and brands when relevant

Be enthusiastic, knowledgeable, and helpful!"""

4. Bringing it all together

When I run the agentic AI application to ask for wardrobe curation, the agent autonomously orchestrates the tools. Here’s the result I get when running it in the terminal.

I've created a comprehensive autumn wardrobe checklist perfectly tailored for you! Here's your budget breakdown for R5,000:

**BUDGET ALLOCATION:**
- **Outerwear & Layers**: R1,750 (35%)
- **Tops & Blouses**: R1,250 (25%)
- **Bottoms**: R1,000 (20%)
- **Footwear**: R750 (15%)
- **Accessories**: R250 (5%)

**KEY HIGHLIGHTS:**

**Perfect for SA Autumn**: Your checklist considers our unique climate - cool mornings that warm up during the day

**Smart Layering**: Focus on versatile pieces that work together for maximum outfit combinations

**Local Shopping Guide**: Included South African retailers across all price points

**Color Palette**: Trending autumn colors including warm earth tones, jewel tones, and classic neutrals       

** Your Shopping Strategy:**
1. Start with outerwear (your biggest investment pieces)
2. Build a foundation with versatile tops
3. Add coordinating bottoms
4. Complete with footwear and accessories

The checklist includes realistic South African pricing and considers local brands like Superbalist and retailers like Woolworths. Each item has a checkbox so you can track your shopping progress!

Would you like me to adjust any budget categories or add specific items you have in mind? I can also help you plan outfits once you start shopping! 

I can mark items I own with [X] and leave [ ] for items to shop for.

Find full code in my GitHub repository.

What’s Next

In Part 2 of this series I’ll be extending my Wardrobe Curator with Model Context Protocol (MCP) to go from recommendations to real shopping. In the meantime, I can’t wait to hear what you build with agents using tools, let me know in the comments below.

Resources

Authorizer v2 Is Here: Self-Hosted Auth, Rebuilt From the Ground Up

We just shipped Authorizer v2 — a major rewrite of our open-source, self-hosted authentication and authorization server.

If you’ve ever been frustrated by per-seat auth pricing, vendor lock-in, or shipping your users’ data to someone else’s cloud — this release is for you.

TL;DR: Single Go binary. 13+ database backends. CLI-driven config. OAuth 2.0/OIDC compliant. Deploy in 5 minutes. Free forever.

GitHub | Docs | Website | Migration Video

Why We Built v2

Authorizer v1 worked. Teams used it in production. But we kept hearing the same feedback:

“Config stored in the database felt fragile.”
“I want to manage auth config the same way I manage everything else — through code.”
“It’s hard to audit what changed and when.”

So we rethought the entire configuration model.

v1: Configuration lived in the database, encrypted. You changed settings through the dashboard UI or a GraphQL mutation. Convenient, but opaque — you couldn’t version-control your auth config, couldn’t audit changes easily, and secrets sat in a persistent store.

v2: All configuration is passed via CLI flags at startup. That’s it. No .env files. No database-stored config. No mutation to accidentally expose secrets. Your auth server is configured the same way you configure every other 12-factor service.

authorizer 
  --database-type postgres 
  --database-url "postgres://user:pass@localhost:5432/auth" 
  --client-id "your-client-id" 
  --client-secret "your-client-secret" 
  --smtp-host "smtp.example.com" 
  --enable-magic-link-login 
  --enable-totp-login 
  --jwt-type RS256

That’s a production-ready auth server. One command. No magic.

What’s in v2

13+ Database Backends

Use whatever database your team already runs:

Category Supported
SQL PostgreSQL, MySQL, SQLite, SQL Server, MariaDB, YugabyteDB, PlanetScale, CockroachDB, LibSQL
NoSQL MongoDB, ArangoDB, CassandraDB, ScyllaDB
Cloud AWS DynamoDB, Couchbase

No other open-source auth server supports this many backends. If you’re already running MongoDB or DynamoDB — you don’t need to spin up a separate Postgres just for auth.

11 Social Login Providers

Google, GitHub, Facebook, Apple, LinkedIn, Microsoft, Discord, Twitter, Twitch, Roblox — all configured with a pair of CLI flags:

--google-client-id "..." --google-client-secret "..."
--github-client-id "..." --github-client-secret "..."

Multi-Factor Authentication

  • TOTP — Google Authenticator, Authy, 1Password
  • Email OTP — One-time codes via email
  • SMS OTP — Via Twilio integration
  • Enforceable globally with --enforce-mfa

Full OAuth 2.0 / OIDC Compliance

  • Authorization code flow with PKCE (RFC 7636)
  • Implicit token and ID token flows
  • JWKS endpoint (/.well-known/jwks.json)
  • 9 JWT signing algorithms (HS256/384/512, RS256/384/512, ES256/384/512)
  • Custom access token claims via JavaScript scripts

Developer Experience

  • GraphQL API — Introspectable schema, admin operations prefixed with _
  • REST endpoints — Standard OAuth 2.0/OIDC paths
  • SDKs — React, JavaScript, Go, Svelte, Vue, Flutter
  • Built-in UI — Login/signup pages out of the box, themeable
  • Admin dashboard — User management, role assignment, email templates
  • Webhooks — 8 event types for real-time integrations

Role-Based Access Control

Define roles, set defaults, protect sensitive ones:

--roles "user,admin,editor" 
--default-roles "user" 
--protected-roles "admin"

One-Command Deployment

# Docker
docker run -p 8080:8080 authorizerdev/authorizer:latest 
  --database-type sqlite 
  --client-id "test" 
  --client-secret "test"

# Or one-click deploy on Railway, Heroku, Render, Koyeb

Single binary. No JVM. No app server. No runtime dependencies.

What Changed From v1

If you’re upgrading, here’s what matters:

v1 v2
Configuration Stored in DB, editable via dashboard CLI flags only, immutable at runtime
Binary name server authorizer
Env vars Read from .env and OS Pass as CLI arguments
Dashboard Can change server config Read-only (user management only)
Mobile auth Separate mobile_signup/mobile_login Use signup/login with phone_number
Admin auth Header always enabled Can disable header auth for security
SDK versions authorizer-js v2, authorizer-react v1 authorizer-js v3, authorizer-react v2

We wrote a detailed migration guide covering every breaking change. Prefer video? Here’s a step-by-step migration walkthrough on YouTube.

What’s Coming Next: The Roadmap

We’re not stopping here. Here’s what’s planned across five phases:

Phase 1: Security Hardening

The foundation for enterprise adoption:

  • Rate limiting & brute force protection — Per-IP, per-user throttling and account lockout
  • CAPTCHA integration — Cloudflare Turnstile and Google reCAPTCHA v3
  • Leaked password detection — Have I Been Pwned API integration
  • Structured audit logs — Queryable event trail for compliance
  • Prometheus metrics — /metrics endpoint for observability
  • Session security — Device fingerprinting, unrecognized device alerts, remote revocation

Phase 2: Authorization & Machine-to-Machine

Moving beyond basic RBAC:

  • Fine-grained permissions — Resource-level access control (document:read, project:admin)
  • M2M authentication — OAuth 2.0 client credentials grant for service-to-service
  • Service accounts — Application identities that aren’t tied to humans
  • API key management — Let your users create and manage their own API keys
  • Organization enhancements — Domain-based routing, org-level policies, invitations

Phase 3: Enterprise SSO & Federation

What enterprise buyers ask for on day one:

  • SAML 2.0 — Connect to Okta, Azure AD, OneLogin
  • SCIM 2.0 / Directory Sync — Automated user provisioning and deprovisioning
  • Authorizer as OIDC Provider — Issue tokens for downstream services
  • Self-service admin portal — Let customer IT teams configure their own SSO

Phase 4: AI-Era Authentication

Auth is changing. AI agents need identity too:

  • MCP (Model Context Protocol) authorization — Secure tool access for AI agents
  • Agent-to-Agent (A2A) authentication — Identity and delegation for autonomous agents
  • OAuth 2.1 compliance — Mandatory PKCE, no implicit grant, refresh token rotation
  • Token exchange (RFC 8693) — Delegation and impersonation flows
  • Dynamic client registration (RFC 7591) — Programmatic OAuth client creation

Phase 5: Advanced Security & Modern Standards (Q2-Q3 2027)

  • Passkeys / WebAuthn (FIDO2) — Passwordless with hardware keys
  • DPoP (RFC 9449) — Proof-of-possession tokens to prevent token theft
  • Advanced bot protection — Risk scoring, credential stuffing detection
  • SIEM integration — Stream logs to Datadog, Splunk, Elastic

The full roadmap is on GitHub.

Why Self-Hosted Auth Matters in 2026

Three trends are making self-hosted auth more relevant than ever:

1. Data sovereignty isn’t optional anymore. GDPR enforcement is accelerating. New regulations in India, Brazil, and across APAC require data residency. If your auth provider stores user data in a region you can’t control, you have a compliance problem.

2. Auth pricing doesn’t scale. Hosted auth providers get expensive fast — they charge per user, per connection, or per feature. With Authorizer, you pay for a server. That’s it.

3. AI agents need auth too. MCP, A2A, and OAuth 2.1 are the emerging standards for agent authentication. The auth layer needs to evolve — and you want that evolution to happen on infrastructure you control.

Get Started

5-minute quickstart:

docker run -p 8080:8080 authorizerdev/authorizer:latest 
  --database-type sqlite 
  --client-id "my-app" 
  --client-secret "my-secret"

Open http://localhost:8080 — you have a working auth server with a login page.

Add it to your React app:

npm install @authorizerdev/authorizer-react
import { AuthorizerProvider, Authorizer } from '@authorizerdev/authorizer-react';

function App() {
  return (
    <AuthorizerProvider
      config={{
        authorizerURL: 'http://localhost:8080',
        redirectURL: window.location.origin,
        clientID: 'my-app',
      }}
    >
      <Authorizer />
    </AuthorizerProvider>
  );
}

That’s email/password auth, social logins, and session management — in 15 lines.

One-click cloud deploy:

Join the Community

Authorizer is Apache 2.0 licensed. It’s free, it’s open source, and your data stays yours.

We’d love your feedback, bug reports, and contributions. If this solves a problem for you — give us a star. It helps more than you think.

Sponsor Authorizer

Authorizer is built and maintained by the community. If it saves you time or money, consider sponsoring the project to keep development going:

Sponsor Authorizer on GitHub

Built with Go. Powered by the community. Owned by you.

About the author: I’m Lakhan Samani, creator of Authorizer. Connect with me on LinkedIn or X/Twitter.

Build a Multi-Agent Data Pipeline in 50 Lines of Neam

In this tutorial, you’ll build a working multi-agent data pipeline using Neam, an agentic AI programming language. By the end, you’ll have a DIO orchestrating five agents through a churn prediction workflow.

Step 1: Define Your Infrastructure Profile. This tells every agent where data lives and what compliance rules apply:

infrastructure_profile MyInfra {
    data_warehouse: {
        platform: "postgres",
        connection: env("DB_URL")
    },
    governance: { regulations: ["GDPR"] }
}

Step 2: Declare Your Agents. Each agent is a specialist. Note the budget constraints:

budget B { cost: 50.00, tokens: 500000 }

databa agent MyBA { provider: "openai",
    model: "gpt-4o", budget: B }
datascientist agent MyDS { provider: "openai",
    model: "gpt-4o", budget: B }
datatest agent MyDT { provider: "openai",
    model: "gpt-4o", budget: B }

Step 3: Wire Up the DIO. The orchestrator coordinates everything:

budget DioBudget { cost: 500.00, tokens: 2000000 }

dio agent MyDIO {
    mode: "hybrid",
    task: "Predict customer churn, identify drivers",
    infrastructure: MyInfra,
    agent_md: "./my_domain.agent.md",
    provider: "openai", model: "gpt-4o",
    budget: DioBudget
}

let result = dio_solve(MyDIO, task)
print(result)

Step 4: Create Your Agent.MD. This is the secret weapon — encode domain knowledge:

## @organization-context
Company: My E-Commerce Co
Scale: 500K customers, 5M orders

## @known-data-issues
- signup_date timezone drift before 2024-03
- Product ratings skew positive (self-reported)

## @agent-preferences
DataScientist: XGBoost for tabular, AUC-ROC metric

Run it: neam-cli run my_pipeline.neam

Welcome to Neam Ecosystem

Neam Landing Page

Neam DIO Data Sims

Neam Repo

Neam DIO EBook

Programming Neam

Claude Code subagents: how to run parallel tasks without hitting rate limits

Claude Code subagents: how to run parallel tasks without hitting rate limits

One of the least-documented features of Claude Code is its ability to spin up subagents — separate Claude instances that handle isolated tasks in parallel. If you’ve been running everything sequentially and wondering why your agent workflow feels slow, this is the missing piece.

What is a subagent in Claude Code?

When Claude Code encounters a task that can be broken into independent pieces, it can launch child processes that each have their own context window, their own tool access, and their own conversation thread. The parent agent coordinates; the subagents execute.

This is different from just having a long conversation. Each subagent starts fresh — clean context, no accumulated tokens from previous steps.

The rate limit problem

Here’s the frustrating reality: Claude’s API has rate limits based on tokens per minute. When you run a long sequential workflow, you often hit the ceiling — especially if you’re processing multiple files, running test suites, or doing research that requires many tool calls.

Subagents help because:

  1. Each subagent uses its own token budget
  2. They can be staggered so they don’t all fire simultaneously
  3. Failed subagents don’t kill the parent task

How to trigger parallel subagents

In your CLAUDE.md, you can hint at parallelism:

## Workflow preferences
- For tasks involving multiple independent files, use parallel subagents
- Each subagent should handle one file or one logical unit
- Report results back to parent before merging

For more direct control, structure your prompts explicitly:

Task: Refactor these 4 service files to use the new error handling pattern.

Run these as parallel subagents:
1. Subagent A: src/services/auth.js
2. Subagent B: src/services/billing.js  
3. Subagent C: src/services/email.js
4. Subagent D: src/services/webhook.js

Each subagent: read the file, apply the pattern, write the result.
Parent: wait for all 4, then run tests.

Real example: running a test suite in parallel

I have a project with 12 test files. Sequential testing takes ~8 minutes because Claude has to read each file, understand the context, run the tests, interpret results, and move to the next. With subagents:

Run the test suite using 4 parallel subagents:
- Subagent 1: tests/auth/*.test.js
- Subagent 2: tests/billing/*.test.js
- Subagent 3: tests/api/*.test.js
- Subagent 4: tests/utils/*.test.js

Each subagent reports: pass/fail count + any error messages.
Only surface failures to me — I don't need to see passing tests.

Time dropped from 8 minutes to under 3.

Controlling subagent scope

The biggest risk with subagents is scope creep — a subagent that’s supposed to refactor one file starts touching dependencies, which touches other files, which creates conflicts with another subagent working in parallel.

Prevention via CLAUDE.md:

## Subagent rules
- Subagents must not modify files outside their assigned scope
- If a subagent needs to touch a shared file, it must report back first
- No subagent should run git commands
- No subagent should modify package.json

You can also use settings.json deny rules to enforce this at the tool level:

{
  "permissions": {
    "deny": [
      "Bash(git commit:*)",
      "Bash(git push:*)",
      "Write(package.json)",
      "Write(package-lock.json)"
    ]
  }
}

This means no subagent — regardless of instructions — can commit or modify your package files.

The cost equation

Each subagent uses tokens. More parallelism = more tokens = higher cost if you’re on pay-per-token pricing.

This is where flat-rate API proxies become genuinely useful. I’ve been running Claude Code through SimplyLouie ($2/month flat) by setting:

export ANTHROPIC_BASE_URL=https://simplylouie.com/api/proxy

With per-token pricing, running 4 parallel subagents costs 4x what a sequential run costs. With flat-rate, it’s the same monthly fee regardless. For heavy parallel workloads, this math matters.

Debugging when subagents go wrong

Subagents can fail silently. The parent might report success even if a subagent partially failed. Build explicit reporting into your workflow:

After each subagent completes, output a status block:

[SUBAGENT-A] status: SUCCESS|FAILURE
[SUBAGENT-A] files_modified: [list]
[SUBAGENT-A] issues: [any problems encountered]

Parent: aggregate all status blocks before proceeding.

This gives you a clear audit trail and makes it obvious when something needs manual review.

When NOT to use subagents

Subagents aren’t always the right tool:

  • Order-dependent tasks: If step 2 requires the output of step 1, run sequentially
  • Small tasks: Spinning up a subagent for a 10-line change has overhead that’s not worth it
  • Shared state: If all your tasks write to the same file or database, parallelism causes conflicts
  • Debugging sessions: When you’re investigating a bug, you want to follow one thread of reasoning

Summary

Subagents are Claude Code’s answer to the “I have too much work for one context window” problem. Use them for:

  • Processing multiple independent files
  • Running test suites
  • Research tasks (each subagent investigates one question)
  • Any workflow where tasks don’t depend on each other

The rate limit avoidance is a side effect — the real value is keeping each subagent focused and context-clean, which produces better output than a single agent trying to hold everything in one window.

Running Claude Code with a flat-rate API? Set ANTHROPIC_BASE_URL to skip per-token billing. Details at simplylouie.com/developers

The Curse of Excessive Kindness and the Economics of Empathy — Why Imprecise Comfort Creates Both Fatigue and Cost

𝟏. 𝐇𝐚𝐬 𝐊𝐢𝐧𝐝𝐞𝐫 𝐀𝐈 𝐑𝐞𝐚𝐥𝐥𝐲 𝐁𝐞𝐜𝐨𝐦𝐞 𝐁𝐞𝐭𝐭𝐞𝐫 𝐀𝐈?
​
For a long time, we wanted AI to become kinder.
Compared to cold, mechanical replies, a system that receives our words gently and handles our emotions without bruising them felt like a more advanced form of technology.

And over the past few years, the AI industry has moved rapidly in exactly that direction.
Kinder answers. More human-like empathy. Longer conversations.
Many services have begun to treat these responses as the very sign of a “good AI.”

But now, this kindness must be questioned again.

Is AI’s empathy truly becoming more precise?
Or is it simply being produced more often, in greater volume, and at greater length?

This distinction matters far more than it seems.
Because the problem of empathy is not merely a matter of emotional warmth.
It is a matter of structure.

𝟐. 𝐄𝐦𝐩𝐚𝐭𝐡𝐲 𝐇𝐚𝐬 𝐈𝐧𝐜𝐫𝐞𝐚𝐬𝐞𝐝, 𝐛𝐮𝐭 𝐈𝐭 𝐇𝐚𝐬 𝐍𝐨𝐭 𝐁𝐞𝐜𝐨𝐦𝐞 𝐌𝐨𝐫𝐞 𝐏𝐫𝐞𝐜𝐢𝐬𝐞

Many AI systems today appear empathetic.
When a user says they are struggling, the system immediately acknowledges it.
When a user says they feel overwhelmed, it tries to reassure them.
When someone expresses insecurity, it offers encouraging words.

On the surface, this seems soft and harmless.
But the moment we look more closely at actual user experience, familiar patterns begin to appear:

the repetition of similar comforting phrases,
endings that constantly reopen the conversation,
empathetic expressions that barely change even when the situation clearly has,
and responses so flat that they fail to distinguish between comfort, encouragement, restraint, and silence depending on the user’s state.

That is where the real problem begins.

The problem with AI empathy is not that there is too little of it.
The problem is that it is not precise enough, and because of that, it creates fatigue.

𝟑. 𝐑𝐞𝐩𝐞𝐚𝐭𝐞𝐝 𝐂𝐨𝐦𝐟𝐨𝐫𝐭 𝐄𝐯𝐞𝐧𝐭𝐮𝐚𝐥𝐥𝐲 𝐁𝐞𝐜𝐨𝐦𝐞𝐬 𝐍𝐨𝐢𝐬𝐞

When empathy is too thin, users experience it as coldness.
But when empathy becomes rough, repetitive, and indiscriminate, users become exhausted even faster.

When similar words of comfort are repeated again and again, what first sounded gentle slowly stops lifting emotion and starts pressing down on it instead.

The moment empathy stops reading the user’s actual state and begins replaying prepackaged kindness, comfort ceases to be a relationship.
It becomes noise.

This is not simply a stylistic flaw.
It is a question of how psychological energy is being handled.

People in pain do not always want more words.
They do not necessarily want the same kind of comfort repeated over and over.
What they often need is a response that can tell the difference
between empathy,
a brief silence,
a more careful explanation,
or a clear and timely brake.

But imprecise AI fails to make that distinction.
Empathy remains, but direction disappears.
Comfort increases, but resolution decreases.

This is where the curse of excessive kindness begins.

𝟒. 𝐄𝐱𝐜𝐞𝐬𝐬𝐢𝐯𝐞 𝐊𝐢𝐧𝐝𝐧𝐞𝐬𝐬 𝐌𝐚𝐲 𝐍𝐨𝐭 𝐁𝐞 𝐆𝐨𝐨𝐝𝐰𝐢𝐥𝐥, 𝐛𝐮𝐭 𝐌𝐚𝐫𝐤𝐞𝐭 𝐂𝐨𝐦𝐩𝐞𝐭𝐢𝐭𝐢𝐨𝐧

Excessive kindness often appears to come from goodwill.
But when we look at the actual structure of the industry, that is not always the full story.

Today’s AI is no longer designed merely to answer well.
It is often designed to keep users engaged longer, satisfy them more consistently, and interact more smoothly.

Within this competitive environment, models are increasingly tuned to agree more easily, reassure more quickly, and keep conversations open more readily.

In other words, today’s kindness is not only an ethical choice.
It is also a default setting intensified by market competition.

A softer answer can reduce churn.
A kinder tone can increase satisfaction.
Longer empathy can feel like deeper connection.
But there is one thing the industry repeatedly forgets:

Increasing the quantity of kindness does not mean increasing its quality.

𝟓. 𝐈𝐦𝐩𝐫𝐞𝐜𝐢𝐬𝐞 𝐊𝐢𝐧𝐝𝐧𝐞𝐬𝐬 𝐄𝐱𝐡𝐚𝐮𝐬𝐭𝐬 𝐭𝐡𝐞 𝐔𝐬𝐞𝐫 𝐅𝐢𝐫𝐬𝐭

In fact, imprecise kindness can make users more tired.
When the same meaning keeps being repeated,
when unnecessary turns are added,
when unwanted question-based endings keep appearing,
and when comfort continues even when it no longer fits the situation,
AI stops helping the user and starts consuming their energy instead.

For ordinary users, this appears as psychological fatigue.

“It feels like it’s listening, but I’m getting more tired.”
“It sounds kind, but it keeps saying the same thing.”
“It feels less like comfort and more like the conversation just won’t end.”

These are not minor complaints.
They are the results of an empathy structure that has not been designed with enough precision.

𝟔. 𝐄𝐱𝐜𝐞𝐬𝐬𝐢𝐯𝐞 𝐊𝐢𝐧𝐝𝐧𝐞𝐬𝐬 𝐀𝐥𝐬𝐨 𝐁𝐞𝐜𝐨𝐦𝐞𝐬 𝐚 𝐂𝐨𝐬𝐭 𝐟𝐨𝐫 𝐂𝐨𝐦𝐩𝐚𝐧𝐢𝐞𝐬

For companies, the problem returns in a more concrete form.
Excessive kindness often appears as longer responses,
and longer responses mean more tokens, more turns, and more cost.
A conversation that could have ended in one exchange continues into two or three.
Extra softening phrases are appended.
Question-based endings reopen the dialogue yet again.
At that point, kindness becomes operating cost.

This is the economics of empathy.

Empathy is no longer a free virtue.
The way empathy is delivered changes
user fatigue,
response efficiency,
and cost structure.
At first, excessive kindness may look like a better user experience.
But if it is not designed with precision,
it turns into inefficiency that increases dwell time, response length, and operating expense.
Emotionally, it may fail to comfort the user.
Economically, it may make the system unnecessarily expensive.

𝟕. 𝐖𝐡𝐞𝐧 𝐭𝐡𝐞 𝐁𝐨𝐮𝐧𝐝𝐚𝐫𝐲 𝐁𝐞𝐭𝐰𝐞𝐞𝐧 𝐄𝐦𝐩𝐚𝐭𝐡𝐲 𝐚𝐧𝐝 𝐏𝐞𝐫𝐦𝐢𝐬𝐬𝐢𝐨𝐧 𝐂𝐨𝐥𝐥𝐚𝐩𝐬𝐞𝐬, 𝐒𝐨𝐜𝐢𝐚𝐥 𝐂𝐨𝐬𝐭 𝐄𝐦𝐞𝐫𝐠𝐞𝐬

And the problem does not stop there.

Imprecise empathy can also generate larger social costs.

The more AI defaults to repetitive comfort and excessive acceptance,

the more likely users are to feel emotionally validated even when they are moving in the wrong direction.

A vulnerable user may encounter companionship where restraint is needed,
affirmation where reflection is needed,
and over-response where silence would have been wiser.

At that point, the problem is not simply that AI has become “too kind.”
The deeper issue is that it begins to blur the boundary between judgment and empathy.

To empathize with a feeling is not to approve the direction of that feeling.
To comfort distress is not to legitimize every conclusion emerging from distress.

Kindness can soften relationships,
but the moment it pushes aside necessary restraint, social cost rises sharply.

Users become more dependent.
Companies inherit more responsibility.
Services end up paying more in every sense.

𝟖. 𝐓𝐡𝐞 𝐅𝐮𝐭𝐮𝐫𝐞 𝐨𝐟 𝐀𝐈 𝐈𝐬 𝐍𝐨𝐭 𝐀𝐛𝐨𝐮𝐭 𝐁𝐞𝐜𝐨𝐦𝐢𝐧𝐠 𝐊𝐢𝐧𝐝𝐞𝐫, 𝐛𝐮𝐭 𝐁𝐞𝐜𝐨𝐦𝐢𝐧𝐠 𝐌𝐨𝐫𝐞 𝐏𝐫𝐞𝐜𝐢𝐬𝐞

That is why the future of AI cannot simply be “more kindness.”
It must be more precise kindness.

AI must be able to distinguish
the moment that calls for empathy,
the moment that calls for carefulness,
the moment when encouragement should lead,
and the moment when restraint must come first.

Not every sadness is the same sadness.
Not every anxiety is the same anxiety.
Not every conversation requires the same comfort.

Good empathy is not empathy that talks more.
Good empathy is empathy that knows how to say only what is needed.

Good comfort is not always long.
Good encouragement is not always warm in the same way.
Good kindness sometimes stops asking questions.
Sometimes it closes the conversation.
Sometimes it applies a gentle but unmistakable brake.

𝟗. 𝐖𝐞 𝐌𝐮𝐬𝐭 𝐒𝐭𝐨𝐩 𝐀𝐬𝐤𝐢𝐧𝐠 𝐀𝐛𝐨𝐮𝐭 𝐭𝐡𝐞 𝐐𝐮𝐚𝐧𝐭𝐢𝐭𝐲 𝐨𝐟 𝐊𝐢𝐧𝐝𝐧𝐞𝐬𝐬 𝐚𝐧𝐝 𝐁𝐞𝐠𝐢𝐧 𝐀𝐬𝐤𝐢𝐧𝐠 𝐀𝐛𝐨𝐮𝐭 𝐈𝐭𝐬 𝐑𝐞𝐬𝐨𝐥𝐮𝐭𝐢𝐨𝐧

We should no longer ask only how kind AI is.
We must ask how precise that kindness is.
And we must ask whether that precision is
reducing user fatigue,
reducing corporate cost,
and reducing the weight of social responsibility.
Excessive kindness may look beautiful on the surface.
But when it lacks precision, it easily turns into fatigue,
into cost,
and into responsibility.

𝟏𝟎. 𝐄𝐦𝐩𝐚𝐭𝐡𝐲 𝐒𝐡𝐨𝐮𝐥𝐝 𝐍𝐨𝐭 𝐌𝐞𝐚𝐧 𝐌𝐨𝐫𝐞 𝐎𝐮𝐭𝐩𝐮𝐭, 𝐛𝐮𝐭 𝐁𝐞𝐭𝐭𝐞𝐫 𝐃𝐢𝐬𝐜𝐞𝐫𝐧𝐦𝐞𝐧𝐭

What the AI industry needs now is not more empathy.
It needs better discernment.

It needs to know
when to receive,
when to say less,
when to encourage,
and when to stop.

Only when that distinction appears
does empathy cease to be a simple text-generation feature
and become a structure that governs the situation itself.

And only then does kindness stop being a sentence that is blindly consumed
and begin to become a technology that truly leaves trust behind.

by SeongHyeok Seo, AAIH Insights – Editorial Writer

The Engineering Challenge of Abstracting AI Model Complexity in No-Code Platforms

A Fortune 500 enterprise needs to implement sentiment analysis across customer support tickets, product reviews, and social media mentions. This scenario illustrates the paradigm shift from “build vs. buy” to “configure vs. code.”  Organizations can approach AI implementation in three ways: building custom integrations directly against model provider APIs, purchasing separate per-vendor SaaS solutions, or … continue reading

The post The Engineering Challenge of Abstracting AI Model Complexity in No-Code Platforms appeared first on SD Times.

Presentation: The Principal Engineer’s Path: Skills, Strategies, and Lessons Learned

Sophie Weston explains that technical careers are winding journeys, not straight ladders. Drawing on 30 years of experience, she shares how senior ICs can become “broken combs” by broadening skills in systems thinking and strategy. She discusses the vital role of organizational flexibility and explains how public speaking and community engagement create feedback loops for career success.

By Sophie Weston

Cloudflare Launches Dynamic Workers Open Beta: Isolate-Based Sandboxing for AI Agent Code Execution

Cloudflare has released Dynamic Worker Loader into open beta, offering V8 isolate-based sandboxing for AI-generated code execution. The company claims isolates start in milliseconds, using megabytes of memory, making them roughly 100x faster and up to 100x more memory-efficient than containers. The feature builds on Cloudflare’s Code Mode approach.

By Steef-Jan Wiggers