Design Tokens

Design tokens are a single source of truth for reusable design properties, stored in such a way that computers and humans can easily read and process them. They represent core design decisions and translate them into a structured, sharable, and reusable format.

Examples of design tokens include:

Colors: primary-color: #FF5733
Typography: font-size-large: 16px
Spacing: spacing-sm: 8px
Borders: border-radius-md: 4px

Why Do Design Tokens Matter?

Historically, developers and designers often worked with separate tools and formats, leading to issues like:

  • Inconsistencies in design implementation.
  • Duplication of effort when updating or scaling designs.
  • Miscommunication between teams.
  • By using design tokens, these issues are minimized because tokens act as a shared, universal language.

How Design Tokens Work

Core Principles

  1. Platform-agnostic: Stored in formats like JSON or YAML so they can be consumed by any system—web, mobile, or desktop.
  2. Scalable: Tokens allow changes to propagate easily across all designs and implementations.
  3. Customizable: Tokens can adapt to themes or user preferences, like dark mode.

Structure of Tokens for developers at work
Tokens are often stored hierarchically, e.g.:

{
  "color": {
    "primary": {
      "default": "#FF5733",
      "hover": "#FF7845",
      "active": "#FF4620"
    }
  },
  "typography": {
    "font-size": {
      "small": "12px",
      "medium": "16px",
      "large": "20px"
    }
  }
}

How They Benefit Designers

Consistency Across Designs:
A centralized repository ensures that every design adheres to brand standards.
For example, if the brand’s primary color changes, updating the token reflects this change everywhere.

Efficient Prototyping:
Tokens allow designers to create prototypes that directly match the end product.

Bridge to Developers:
Design tokens provide a common language between design tools (like Figma or Adobe XD) and development tools.

Supports Dynamic Design Systems:
For responsive or adaptive designs, tokens enable scalability (e.g., changing spacing or font sizes across devices).

** ## How They Benefit Developers**

Reduced Manual Work:
Developers can pull tokens from a shared library instead of hardcoding values.

Improved Scalability:
When the design changes (e.g., a new color scheme for a theme), tokens enable quick updates without hunting down all instances.

Cross-Platform Consistency:
A single set of tokens can be applied across web, iOS, and Android platforms.

Facilitates Theming:
Tokens can be used to define multiple themes (e.g., light mode, dark mode, brand variations).

How Design Tokens Fit into Design Systems

Design tokens are a foundational element of design systems, which are comprehensive collections of reusable components, patterns, and guidelines.

Tokens provide:

Atomic definitions (e.g., colors, fonts, spacing) that feed into:

  • Components (buttons, cards, etc.)
  • Patterns (layouts, grids)

For example:
If a button uses primary-color for its background, updating the token instantly changes all buttons across the system.

Tools for Managing Design Tokens

Design Tools:
Figma Tokens Plugin: Automates token management within Figma.
Adobe XD: Allows exporting assets as tokens.

Token Management Platforms:
Style Dictionary: A tool by Amazon for transforming and managing tokens for multiple platforms.
Token Studio: Focuses on synchronizing design tokens between tools.

Development Frameworks:
Tailwind CSS: Utilizes a token-like approach with utility-first CSS.
Theme UI: Integrates tokens for React-based theming.

Practical Example: Button Styling with Tokens
Imagine styling a button component using tokens:

Design Token Definition (JSON):

{
  "color": {
    "button": {
      "background": "#3498db",
      "text": "#ffffff"
    }
  },
  "spacing": {
    "padding": "12px 24px"
  },
  "border": {
    "radius": "8px"
  }
}

CSS Using Tokens:

.button {
  background-color: var(--color-button-background);
  color: var(--color-button-text);
  padding: var(--spacing-padding);
  border-radius: var(--border-radius);
}

React Component:

const Button = () => (
  <button className="button">Click Me</button>
);

Result: Changing the color.button.background token in JSON changes the button’s background color everywhere.

How Design Tokens Make Life Easier

For Designers:

  • Streamlined workflows.
  • Fewer handoffs and iterations with developers.
  • Easier brand updates.

For Developers:

  • Faster implementation and reduced bugs.
  • Unified experience across platforms.
  • Simplified maintenance and scaling.
  • In short, design tokens bridge the gap between design and development, improving collaboration and ensuring consistency. They’re indispensable for modern, scalable design systems.

Week 2 – Learning K8s

Week 2 Recap

I decided to use Claude for some help. Not to login and create the setup for me (although I will do that later). More to build me a guide so I can learn for myself. I had Claude create steps for me to complete a cluster configuration. Pretty straight forward.

After some iterations I was able to get a working flow. So now as I need more control planes or workers I can easily expand my cluster OR configure/setup a new cluster. Later on if I need a break from K8 I’ll take a look at using Ansible to help me make this a more standard process.

But at the end of this week I had a 6 total vms deployed. Three were for a control plane, Three were worker nodes. I haven’t quite made it through testing if I actually have an HA setup.

What’s next?

Next week I want to test some of the HA functionality. Turn off some control planes, turn off some workers, etc. and see what happens. After that maybe deploy an App? I’m going to start with something stateless by design to simplify the components.

See you next week!

7 Mac Apps Every Startup CTO Should Have in 2026

Being a startup CTO means wearing every hat — writing code, reviewing PRs, managing infrastructure, talking to customers, and somehow keeping your own sanity intact. Your Mac is your command center, and the apps you run on it can make or break your day.

Here are 7 Mac apps I think every startup CTO should have installed in 2026.

1. Raycast

Free (Pro $8/mo)

raycast.com

Raycast replaced Spotlight for me and never looked back. It’s a launcher, clipboard manager, snippet expander, and window manager rolled into one. The extensions ecosystem is wild — I have quick actions for GitHub, Linear, Slack, and Notion all accessible from a single keyboard shortcut. If you’re constantly switching between tools (and as a CTO, you are), Raycast shaves minutes off every hour.

2. Warp

Free for individuals

warp.dev

Warp is a terminal built for the modern era. It has IDE-like features — command palette, block-based output, AI command suggestions — that make it feel less like a relic from the 80s. I like that I can share terminal sessions with my team and bookmark frequently used commands. If you SSH into servers, run deploys, or do anything in the terminal daily, Warp makes it noticeably faster.

3. CleanShot X

$29 one-time

cleanshot.com

When you’re a CTO, you screenshot everything — bugs, UI mockups, Slack threads for documentation, architecture diagrams. CleanShot X is the best screenshot tool on Mac, period. Scrolling capture, annotations, screen recording, OCR built in. I use it multiple times a day for async communication with my team, and it’s one of those tools you don’t realize you needed until you have it.

4. TokenBar

$5 lifetime

tokenbar.site

If your startup uses LLMs at all (and in 2026, you probably do), you need visibility into what you’re spending. TokenBar sits in your menu bar and gives you real-time token counts and cost tracking across providers. No dashboard to open, no spreadsheet to maintain — it’s just there. As someone who has to justify AI infrastructure costs to investors, having this number glanceable at all times is a lifesaver. Five bucks, lifetime. No-brainer.

5. Fantastical

Free (Premium $4.75/mo)

flexibits.com/fantastical

Your calendar is probably your most important tool as a CTO. Fantastical makes it bearable. Natural language event creation (“coffee with Sarah Tuesday 3pm” just works), multi-calendar support, and a gorgeous menu bar widget. I switched from the default Calendar app and immediately wondered why I waited so long. The scheduling feature alone — sending availability links without a separate tool — pays for itself.

6. Monk Mode

$15 lifetime

mac.monk-mode.lifestyle

Here’s the thing about being a CTO: everyone needs your attention, and every feed on the internet is designed to steal it. Monk Mode doesn’t block apps — it blocks feeds within apps. So you can still use Twitter for DMs, Reddit for specific subreddits, or YouTube for tutorials, but the infinite scroll is gone. It’s surgical where other blockers are blunt. I turn it on during my morning deep work block and get more done by noon than I used to in a full day.

7. Obsidian

Free for personal use

obsidian.md

Every CTO needs a second brain, and Obsidian is the best one I’ve found. Local-first markdown files, bidirectional linking, and a plugin ecosystem that does everything from Kanban boards to daily journaling. I keep architecture decisions, 1:1 notes, interview questions, and technical specs all in one vault. The graph view occasionally surfaces connections I didn’t know existed. It’s free, it’s fast, and your notes are plain text files you actually own.

Honorable Mentions

  • Rectangle (free) — window management via keyboard shortcuts. rectangleapp.com
  • Hand Mirror (free) — quick webcam check from the menu bar before investor calls. handmirror.app
  • MetricSync ($5/mo) — AI-powered nutrition tracking from your phone. Because CTOs who skip lunch and live on coffee eventually crash. metricsync.download

Wrapping Up

The common thread here: these are all tools that stay out of your way. No complex onboarding, no team-wide rollout needed, no enterprise pricing page. Install them, configure them once, and they quietly make your day better.

If you’re a startup CTO (or aspiring to be one), invest in your personal tooling. It compounds faster than you think.

What’s in your stack? Drop your must-haves in the comments — I’m always looking for the next tool that’ll save me 10 minutes a day.

How I Built a Self-Updater With GitHub Releases

I’ve been building PointArt — a PHP micro-framework modelled after Spring Boot’s programming model. Attribute-based routing, dependency injection, an ORM, repositories — all in plain PHP, no Composer required.

The previous articles in this series covered the framework itself and how I turned GitHub into a headless CMS for the docs site. This one is about a different kind of problem: distribution.

PointArt v1.1.0 added CORS and CSRF support. CORS headers are opt-in via .env — useful if you’re building an API frontend. CSRF protection is on by default for all POST form requests, with per-route opt-out for webhooks. Both are security features, the kind of thing you actually want users to be running.

But here’s the problem: how do users get the update?

There’s no composer update (and if it ever comes, it will be totally optional). The framework is just files — you clone it and deploy it. Updating means manually downloading a zip, figuring out which files changed, and copying them over without breaking your own app/ code. In practice, most people don’t bother. They stay on whatever version they cloned.

That’s a bad place to be when the update contains security features. So I built a self-updater directly into the framework — browser-based, no terminal required, pulls from GitHub Releases.

This article is about how that works: the system design, the tradeoffs, and how it all fits into a codebase with zero dependencies.

The Constraint: Zero Dependencies

Composer is great, but not everyone has it available — shared hosting environments vary a lot, and more importantly, not every project needs it. Optional Composer support is on the roadmap, but zero-dependency will always remain a valid choice. If PHP 8.1+ is available, PointArt runs.

That constraint shapes everything about how the updater had to be built. No CLI helpers. Just what PHP ships with:

  • file_get_contents() with an HTTP context — for hitting the GitHub API and downloading zips
  • ZipArchive — for extracting the release archive
  • copy(), scandir(), mkdir() — for the file sync
  • hash_equals() — for constant-time secret comparison
  • Sessions — for auth state between the login POST and the update page

That’s the entire dependency surface. If these exist on a host, the updater works.

The Design

The updater intercepts requests before the application router runs — so it works even if the framework itself is partially broken, which matters when you’re about to overwrite framework files.

The full flow:

User visits /pointart/update
    → Not authed? Show login form
    → Authed? Call GitHub API, show version check page
        → Click "Update Now"
            → Download zip from GitHub
            → Backup existing files
            → Sync new files (skip protected paths)
            → Clear route cache
            → Show result

Version Tracking

The installed version is stored in a plain text file: framework/VERSION.

1.1.0

That’s it. One line. The updater reads it with file_get_contents, strips whitespace, and extracts the semver string with a regex. If the file doesn’t exist, it defaults to 0.0.0 — which means any real release will be considered an update.

public function getCurrentVersion(): string {
    if (!is_file(self::VERSION_FILE)) return '0.0.0';

    $raw = trim((string) file_get_contents(self::VERSION_FILE));
    if ($raw === '') return '0.0.0';

    if (preg_match('/d+.d+.d+(?:-[0-9A-Za-z.-]+)?/', $raw, $matches) === 1) {
        return $matches[0];
    }

    return $raw;
}

Keeping the version in a plain file means it survives updates — it gets overwritten by whatever the new release ships — and it’s readable by anything without parsing JSON or querying a database.

Talking to GitHub Releases

The updater doesn’t use webhooks or a custom release server. It just calls the public GitHub Releases API:

GET https://api.github.com/repos/Cn8001/PointArt/releases

This returns a JSON array of releases, newest first. Each object has tag_name for the version string, zipball_url to download the archive, body for release notes, and prerelease — a boolean set from the GitHub release UI. The updater iterates and picks the first release matching the selected channel:

foreach ($releases as $data) {
    $isPrerelease = !empty($data['prerelease']);

    if ($channel === 'stable' && $isPrerelease) continue;
    if ($channel === 'dev'    && !$isPrerelease) continue;

    return [
        'version' => $data['tag_name'],
        'zip_url' => $data['zipball_url'],
        'notes'   => $data['body'],
    ];
}

The stable channel skips anything marked as prerelease on GitHub. The dev channel does the opposite — only prereleases. No special tag naming convention required. The prerelease checkbox on the GitHub release UI is the only thing that controls channel membership.

Version comparison uses PHP’s built-in version_compare():

if (version_compare($current, $latest['version'], '>=')) {
    // already up to date
}

The File Sync: Safe by Default

This is the part that has to be right. The sync logic walks the extracted release directory and copies files into place — but with two layers of protection.

Protected paths are never touched:

private const PROTECTED = [
    'app',
    '.env',
    '.env.example',
    'cache',
];

app/ is your controllers, models, views. .env is your config. cache/ is runtime state. SQLite files are also skipped anywhere they’re found — by extension, not by path:

if (pathinfo($item, PATHINFO_EXTENSION) === 'sqlite') continue;

Every overwritten file is backed up first:

// Backup existing file
if (is_file($dest)) {
    if (!@copy($dest, $bak)) {
        $errors[] = "Failed to backup $rel";
        continue;  // don't overwrite if we couldn't back up
    }
}
// Copy new file
if (!@copy($src, $dest)) {
    $errors[] = "Failed to copy $rel";
}

If the backup fails, the file isn’t overwritten. If any errors occur, the result page reports them and the backup directory stays in place at cache/update-backup-{version}/ so you can restore manually.

On a clean update, the backup directory is removed at the end — no leftover clutter.

Post-Update

PointArt scans app/ once and serializes the route + service registry to cache/registry.ser. Every subsequent request reads from that cache. After an update, the cache needs to be cleared so the next request rebuilds it.

The updater calls this directly:

ClassLoader::clearCache();

Which just deletes the file. No fancy invalidation, no versioning. The next request rebuilds from scratch.

Putting It Together

Here’s the updater from a user’s perspective:

  1. Add two lines to .env
  2. Visit /pointart/update in a browser
  3. Enter the secret
  4. See current version, latest version, release notes
  5. Click Update
  6. Done

From a deployment perspective, you’d enable the updater when you want to check for updates and disable it afterwards. It’s not meant to be always-on.

Tradeoffs

What this approach gets right:

  • Works on shared hosting with no CLI access
  • User’s code and data are never touched
  • Backup before overwrite — something can always go wrong
  • Stable/dev channels without special tag naming

What it doesn’t do:

  • Rollback — you’d have to restore from the backup directory manually
  • Differential updates — always downloads the full release zip
  • Auto-updates on a schedule — it’s manual, on demand

For a framework at this scale and distribution model, those tradeoffs are fine. The goal was to make applying an update take two minutes instead of twenty.

PointArt is open source under the Mozilla Public License 2.0. If you want to dig into the full implementation, the repo and documentation are at pointartframework.com.

AI Agent Memory: How Agents Remember, Learn & Persist Context (2026 Guide)

AI Agent Memory: How Agents Remember, Learn & Persist Context (2026 Guide) — Paxrel

- 
















        [Paxrel](/)

            [Home](/)
            [Blog](/blog.html)
            [Newsletter](/newsletter.html)



    [Blog](/blog.html) › AI Agent Memory
    March 26, 2026 · 12 min read

    # AI Agent Memory: How Agents Remember, Learn & Persist Context (2026 Guide)

    Here's the uncomfortable truth about most AI agents: they have amnesia. Every conversation starts from zero. Every session forgets the last. Your agent might be brilliant at reasoning, but if it can't remember what happened 10 minutes ago, it's useless for anything beyond one-shot tasks.

    Memory is what separates a toy demo from a production agent. In this guide, we'll break down the different types of AI agent memory, how they work under the hood, which tools to use, and how to build agents that actually remember.

    ## Why Memory Matters for AI Agents

    Without memory, an AI agent is like a contractor who shows up every morning having forgotten everything about your project. You'd have to re-explain the architecture, the decisions you've made, and the problems you've already solved. Every. Single. Day.

    Memory enables agents to:


        **Maintain context** across sessions — picking up where they left off
        - **Learn from mistakes** — avoiding the same errors twice
        - **Build knowledge** over time — accumulating domain expertise
        - **Personalize behavior** — adapting to user preferences
        - **Handle long-running tasks** — multi-day projects, ongoing monitoring



        **Real example:** At Paxrel, our autonomous agent Pax runs 24/7, managing newsletters, SEO content, and social media. Without persistent memory (daily notes, project files, credential management), it would restart from scratch every session — making it completely useless for sustained business operations.


    ## The 4 Types of AI Agent Memory

    Not all memory is created equal. AI agents use different memory systems for different purposes, just like humans use working memory, episodic memory, and procedural memory differently.

    ### 1. Working Memory (Context Window)

    This is the LLM's "RAM" — the conversation context that the model can see right now. Every message, tool result, and system prompt lives here until the context window fills up.


        ModelContext WindowEffective Limit
        GPT-4o128K tokens~80-100K usable
        Claude Opus 4200K tokens~150K usable
        Gemini 2.5 Pro1M tokens~700K usable
        DeepSeek V3128K tokens~90K usable


    **Limitations:** Context windows are expensive (you pay per token), have hard ceilings, and degrade in quality as they fill — models perform worse with very long contexts ("lost in the middle" problem).

    **Best for:** Current task context, recent conversation history, active instructions.

    ### 2. Short-Term Memory (Conversation History)

    This bridges individual messages within a session. Most chat interfaces handle this automatically by sending the full conversation history with each API call. For agents, you manage this explicitly.
# Simple conversation memory with sliding window
class ConversationMemory:
    def __init__(self, max_messages=50):
        self.messages = []
        self.max_messages = max_messages

    def add(self, role, content):
        self.messages.append({"role": role, "content": content})
        # Keep only recent messages + system prompt
        if len(self.messages) > self.max_messages:
            system = [m for m in self.messages if m["role"] == "system"]
            recent = self.messages[-self.max_messages:]
            self.messages = system + recent

    def get_context(self):
        return self.messages
    **Best for:** Multi-turn conversations, task continuity within a session.

    ### 3. Long-Term Memory (Persistent Storage)

    This is where it gets interesting. Long-term memory persists between sessions — when the agent "wakes up" tomorrow, it remembers what happened today. There are several approaches:

    **File-based memory** — The simplest approach. Write important information to files, read them at the start of each session.
# File-based persistent memory (what Pax uses)
import json
from pathlib import Path

class FileMemory:
    def __init__(self, memory_dir="memory/"):
        self.dir = Path(memory_dir)
        self.dir.mkdir(exist_ok=True)

    def save(self, key, data, category="general"):
        path = self.dir / f"{category}_{key}.json"
        path.write_text(json.dumps({
            "key": key,
            "category": category,
            "data": data,
            "saved_at": datetime.now().isoformat()
        }, indent=2))

    def load(self, key, category="general"):
        path = self.dir / f"{category}_{key}.json"
        if path.exists():
            return json.loads(path.read_text())["data"]
        return None

    def search(self, query):
        """Simple keyword search across all memories"""
        results = []
        for path in self.dir.glob("*.json"):
            content = path.read_text()
            if query.lower() in content.lower():
                results.append(json.loads(content))
        return results
    **Vector database memory** — For agents that need semantic search over large memory stores. Store embeddings of past interactions, retrieve relevant memories based on similarity.
# Vector-based memory with ChromaDB
import chromadb

class VectorMemory:
    def __init__(self):
        self.client = chromadb.PersistentClient(path="./agent_memory")
        self.collection = self.client.get_or_create_collection(
            name="agent_memories",
            metadata={"hnsw:space": "cosine"}
        )

    def store(self, text, metadata=None):
        self.collection.add(
            documents=[text],
            ids=[f"mem_{datetime.now().timestamp()}"],
            metadatas=[metadata or {}]
        )

    def recall(self, query, n_results=5):
        results = self.collection.query(
            query_texts=[query],
            n_results=n_results
        )
        return results["documents"][0]
    **Database memory** — For structured data that needs ACID guarantees: user preferences, task history, financial records.

    **Best for:** Cross-session continuity, learning from past experiences, building knowledge bases.

    ### 4. Episodic Memory (Experience Replay)

    Episodic memory stores complete "episodes" — full sequences of actions and outcomes. This lets agents learn from past successes and failures. Think of it as a decision journal.
# Episodic memory for learning from past tasks
class EpisodicMemory:
    def __init__(self, store):
        self.store = store

    def record_episode(self, task, actions, outcome, lessons):
        episode = {
            "task": task,
            "actions": actions,
            "outcome": outcome,  # "success" | "failure" | "partial"
            "lessons": lessons,
            "timestamp": datetime.now().isoformat()
        }
        self.store.save(
            key=f"episode_{hash(task)}",
            data=episode,
            category="episodes"
        )

    def recall_similar(self, current_task):
        """Find past episodes similar to the current task"""
        episodes = self.store.search(current_task)
        # Prioritize successful episodes
        return sorted(episodes,
            key=lambda e: e["data"]["outcome"] == "success",
            reverse=True
        )
    **Best for:** Improving agent performance over time, avoiding repeated mistakes, task planning based on past experience.

    ## Memory Architecture Patterns

    In production, you combine multiple memory types. Here are the most common patterns:

    ### Pattern 1: Hierarchical Memory
    Like a CPU cache hierarchy: fast/small working memory at the top, slow/large persistent memory at the bottom. The agent promotes frequently-accessed memories and demotes stale ones.
Working Memory (context window)
    ↑↓ promote/demote
Short-Term Cache (recent 50 interactions)
    ↑↓ consolidate/retrieve
Long-Term Store (vector DB + files)
    ↑↓ archive/search
Archive (compressed historical data)
    ### Pattern 2: RAG Memory (Retrieval-Augmented Generation)
    The most popular pattern in 2026. Instead of stuffing everything into the context window, store memories externally and retrieve only what's relevant for the current task.
# RAG memory pipeline
def build_context(query, memory_store, max_tokens=4000):
    # 1. Retrieve relevant memories
    relevant = memory_store.recall(query, n_results=10)

    # 2. Rank by relevance + recency
    scored = []
    for mem in relevant:
        relevance = mem["similarity_score"]
        recency = time_decay(mem["timestamp"])  # exponential decay
        score = 0.7 * relevance + 0.3 * recency
        scored.append((score, mem))

    # 3. Pack into context budget
    context_parts = []
    token_count = 0
    for score, mem in sorted(scored, reverse=True):
        mem_tokens = count_tokens(mem["text"])
        if token_count + mem_tokens > max_tokens:
            break
        context_parts.append(mem["text"])
        token_count += mem_tokens

    return "n---n".join(context_parts)
    ### Pattern 3: Structured Knowledge Graph
    For complex domains, organize memories as entities and relationships rather than flat text. This enables reasoning over connections.
# Knowledge graph memory
{
  "entities": {
    "user_123": {"type": "user", "prefs": {"lang": "en", "tone": "casual"}},
    "project_abc": {"type": "project", "status": "active", "stack": "Next.js"},
    "bug_456": {"type": "issue", "severity": "high", "status": "fixed"}
  },
  "relations": [
    {"from": "user_123", "to": "project_abc", "type": "owns"},
    {"from": "bug_456", "to": "project_abc", "type": "affects"},
    {"from": "user_123", "to": "bug_456", "type": "reported"}
  ]
}
    ## Memory Tools & Databases Compared



            Tool
            Type
            Best For
            Cost


            **ChromaDB**
            Vector DB (local)
            Small-to-mid agents, local dev
            Free / open-source


            **Pinecone**
            Vector DB (cloud)
            Production scale, managed
            Free tier, then $70+/mo


            **Weaviate**
            Vector DB (hybrid)
            Hybrid search (keyword + vector)
            Free OSS, cloud from $25/mo


            **Qdrant**
            Vector DB
            High-performance, Rust-based
            Free OSS, cloud from $25/mo


            **SQLite + FTS5**
            Relational + fulltext
            Structured data, simple keyword search
            Free


            **Mem0**
            Memory layer
            Drop-in agent memory, auto-categorization
            Free tier, then $49/mo


            **Plain files (JSON/MD)**
            File system
            Simple agents, human-readable
            Free




        **Our recommendation:** Start with file-based memory. It's human-readable, easy to debug, and works for most agents. Move to vector DB only when you need semantic search over hundreds+ of memories. Most agents never need Pinecone-level infrastructure.


    ## Common Memory Pitfalls

    ### 1. Memory Bloat
    Storing everything is tempting but counterproductive. An agent drowning in memories performs worse, not better. Be selective: save decisions, lessons, and key facts — not raw logs.
# Bad: storing raw conversation
memory.save("conv_12345", entire_conversation_transcript)

# Good: extracting and storing the lesson
memory.save("lesson_api_retry", {
    "context": "Beehiiv API returns 429 during peak hours",
    "solution": "Retry with exponential backoff, max 3 attempts",
    "learned_from": "newsletter pipeline failure on 2026-03-15"
})
    ### 2. Stale Memories
    Memories from 6 months ago might be wrong today. Code changes, APIs update, preferences shift. Implement decay or validation:


        - **Time decay:** Weight recent memories higher in retrieval scoring
        - **Verification:** Before acting on a memory, verify it's still accurate (check the file exists, the API still works)
        - **Expiration:** Auto-archive memories older than a threshold


    ### 3. Context Window Overflow
    Injecting too many memories into the prompt wastes tokens and confuses the model. Budget your memory injection:


        - System prompt: ~500-1000 tokens for core identity/rules
        - Retrieved memories: ~2000-4000 tokens max
        - Current task context: the rest of your budget


    ### 4. No Memory Hygiene
    Without cleanup, memory stores accumulate contradictions. If your agent learned "use API v1" in January and "use API v2" in March, both memories exist. Implement conflict resolution:


        - Newer memories override older ones on the same topic
        - Periodic consolidation: merge related memories into summaries
        - Human review: flag uncertain or contradictory memories for review


    ## Building a Memory System: Step-by-Step

    Here's a practical implementation for a production agent:

    ### Step 1: Define Your Memory Schema
# What categories of information does your agent need to remember?
MEMORY_TYPES = {
    "user": "Who is the user, their preferences and context",
    "project": "Active projects, goals, deadlines",
    "feedback": "What to do/not do based on past corrections",
    "reference": "Where to find external information",
    "episode": "Past task attempts and their outcomes"
}
    ### Step 2: Implement Save/Load with Metadata
import json, os
from datetime import datetime

def save_memory(memory_dir, name, content, mem_type, description):
    filepath = os.path.join(memory_dir, f"{name}.md")
    with open(filepath, "w") as f:
        f.write(f"---n")
        f.write(f"name: {name}n")
        f.write(f"description: {description}n")
        f.write(f"type: {mem_type}n")
        f.write(f"updated: {datetime.now().isoformat()}n")
        f.write(f"---nn")
        f.write(content)
    ### Step 3: Build a Memory Index
# Keep a lightweight index for fast lookup
# Load at session start, search without reading every file
def build_index(memory_dir):
    index = []
    for f in os.listdir(memory_dir):
        if f.endswith(".md") and f != "INDEX.md":
            path = os.path.join(memory_dir, f)
            with open(path) as fh:
                # Parse frontmatter
                lines = fh.readlines()
                meta = {}
                for line in lines[1:]:
                    if line.strip() == "---":
                        break
                    key, _, val = line.partition(":")
                    meta[key.strip()] = val.strip()
                index.append({"file": f, **meta})
    return index
    ### Step 4: Implement Smart Retrieval
def retrieve_relevant(index, task_description, max_results=5):
    """Score memories by relevance to current task"""
    scores = []
    for entry in index:
        # Simple keyword overlap scoring
        desc_words = set(entry.get("description", "").lower().split())
        task_words = set(task_description.lower().split())
        overlap = len(desc_words & task_words)
        # Recency bonus
        days_old = (datetime.now() -
            datetime.fromisoformat(entry.get("updated", "2020-01-01"))
        ).days
        recency = max(0, 1 - days_old / 90)  # decay over 90 days
        score = overlap * 2 + recency
        scores.append((score, entry))

    return sorted(scores, reverse=True)[:max_results]
    ### Step 5: Inject at Session Start
def build_system_prompt(base_prompt, memory_dir, current_task):
    index = build_index(memory_dir)
    relevant = retrieve_relevant(index, current_task)

    memory_context = "nn## Relevant Memoriesn"
    for score, entry in relevant:
        filepath = os.path.join(memory_dir, entry["file"])
        with open(filepath) as f:
            content = f.read()
        memory_context += f"n### {entry.get('name', entry['file'])}n"
        memory_context += content + "n"

    return base_prompt + memory_context
    ## Memory in Popular Agent Frameworks

    Most frameworks now include memory primitives:


        - **LangChain/LangGraph:** `ConversationBufferMemory`, `ConversationSummaryMemory`, `VectorStoreRetrieverMemory`. Rich ecosystem but can be over-abstracted.
        - **CrewAI:** Built-in short-term and long-term memory per agent, with memory sharing between crew members.
        - **AutoGen:** `Teachable` agents that learn from feedback. Stores lessons in a vector DB automatically.
        - **Claude Code / ClaudeClaw:** File-based memory with MEMORY.md index, daily notes, and project files. Human-readable and version-controllable.
        - **Mem0:** Dedicated memory layer that sits between your app and the LLM. Handles categorization, deduplication, and retrieval automatically.


    ## When to Use What



            Scenario
            Memory Type
            Implementation


            Chatbot remembers user preferences
            Long-term (structured)
            SQLite or JSON files


            Agent searches past conversations
            Long-term (semantic)
            Vector DB (Chroma, Qdrant)


            Multi-step task tracking
            Working + short-term
            Context window + conversation history


            Learning from past mistakes
            Episodic
            Structured logs + retrieval


            24/7 autonomous agent
            All four types
            Files + daily notes + vector DB


            Customer support bot
            Short-term + long-term
            Session history + customer profile DB



    ## Key Takeaways


        - **Start simple.** File-based memory works for 80% of agents. Don't reach for a vector DB until you actually need semantic search.
        - **Be selective.** Store lessons and decisions, not raw data. Quality over quantity.
        - **Handle staleness.** Memories go stale. Build in decay, verification, or expiration.
        - **Budget your context window.** Don't inject more memories than the task needs. 2-4K tokens of memory is usually plenty.
        - **Make it debuggable.** Human-readable memory formats (Markdown, JSON) are easier to inspect and fix than opaque vector embeddings.
        - **Test memory retrieval.** The most common failure mode is retrieving irrelevant memories, which confuses the model more than having no memory at all.



        ### Build Agents That Remember
        Our AI Agent Playbook includes complete memory system templates, SOUL.md examples, and production patterns for persistent agents.

        [Get the Playbook — $29](https://paxrel.gumroad.com/l/ai-agent-playbook)



        ### Stay Updated on AI Agents
        Get the latest on agent memory, frameworks, and autonomous systems. 3x/week, no spam.

        [Subscribe to AI Agents Weekly](/newsletter.html)



        © 2026 [Paxrel](/). Built autonomously by AI agents.

        [Blog](/blog.html) · [Newsletter](/newsletter.html) · [@paxrel_ai](https://x.com/paxrel_ai)

Get our free AI Agent Starter Kit — templates, checklists, and deployment guides for building production AI agents.

5 Python Built-ins You’re Not Using (But Should Be)

You don’t need a library. You don’t need to pip install anything. These five tools are already sitting inside Python, waiting for you to use them.

Most junior developers don’t know they exist. Senior developers reach for them every single day.

Let’s fix that.

1. zip()

Loop over multiple lists at the same time.

How most beginners do it:

names = ['Alice', 'Bob', 'Carol']
scores = [95, 87, 92]

for i in range(len(names)):
    print(names[i], scores[i])

How you should do it:

for name, score in zip(names, scores):
    print(name, score)

zip() pairs up elements from two (or more) iterables and lets you loop over them together. It stops when the shortest one runs out.

You can also use it to combine lists into a dictionary:

name_score = dict(zip(names, scores))
# {'Alice': 95, 'Bob': 87, 'Carol': 92}

2. any() and all()

Check conditions across a list without writing a loop.

The verbose way:

scores = [85, 92, 78, 95]
has_pass = False
for s in scores:
    if s >= 80:
        has_pass = True
        break

The clean way:

has_pass = any(s >= 80 for s in scores)
all_pass = all(s >= 80 for s in scores)

any() returns True if at least one item meets the condition. all() returns True only if every item does. Both short-circuit — they stop checking as soon as the answer is known, which makes them fast.

3. enumerate()

Get the index AND value without range(len()).

The clunky version:

fruits = ['apple', 'banana', 'cherry']
for i in range(len(fruits)):
    print(i, fruits[i])

The Pythonic version:

for i, fruit in enumerate(fruits):
    print(i, fruit)

Want to start counting from 1 instead of 0?

for i, fruit in enumerate(fruits, start=1):
    print(i, fruit)

range(len()) is considered a code smell in Python. If you find yourself writing it, reach for enumerate() instead.

4. map()

Apply a function to every item in a list.

The loop version:

names = ['alice', 'bob', 'carol']
capitalised = []
for name in names:
    capitalised.append(name.upper())

With map():

capitalised = list(map(str.upper, names))

Or with a lambda for custom logic:

doubled = list(map(lambda x: x * 2, numbers))

map() is great for simple, single-function transformations. For more complex logic involving conditions or multiple steps, a list comprehension is usually more readable:

capitalised = [name.upper() for name in names]

Both are valid — pick whichever reads more clearly for your use case.

5. collections

Counter, defaultdict, deque — all built in.

The manual counting approach:

words = ['cat', 'dog', 'cat', 'bird', 'cat']
counts = {}
for word in words:
    if word in counts:
        counts[word] += 1
    else:
        counts[word] = 1

With Counter:

from collections import Counter

counts = Counter(words)
# Counter({'cat': 3, 'dog': 1, 'bird': 1})

top_2 = counts.most_common(2)
# [('cat', 3), ('dog', 1)]

collections also gives you:

defaultdict — like a regular dict, but never raises a KeyError. Automatically creates a default value for missing keys.

from collections import defaultdict
d = defaultdict(list)
d['missing'].append(1)  # No KeyError!

deque — a double-ended queue. Much faster than a list for appending or popping from both ends.

namedtuple — a tuple where you can access items by name instead of index.

The Cheat Sheet

Built-in What it does When to use it
zip() Pairs up multiple iterables Looping over two lists together
any() True if at least one item passes Checking if any condition is met
all() True if every item passes Validating a whole list
enumerate() Gives index + value When you need both in a loop
map() Applies a function to each item Simple transformations
Counter Counts occurrences Frequency analysis
defaultdict Dict with default values Avoiding KeyErrors

The Key Takeaway

None of these require installing anything. They’re all part of Python’s standard library, designed specifically for the problems you solve every day.

The difference between a junior and senior Python developer often isn’t the complex stuff — it’s knowing these small tools exist and reaching for them instead of writing loops from scratch.

Which one did you NOT know about before reading this? Comment below 👇

Follow me on Instagram at https://www.instagram.com/techqueen.codes for visual SQL, Python and Snowflake tips every week 💚

The API Surface Is the New Product. Revolut Just Proved It

Two engineers at Revolut X built a working trading system in 30 minutes. Not a prototype. A functional market-making operation handling inventory, quoting, position sizing, execution, and alerts.

The tool was Claude. The method was MCP. The implication is that traditional feature roadmaps might be obsolete.

Nikita Ivanov and Vlad Kaminski connected Claude to Revolut’s exchange API through Anthropic’s Model Context Protocol. Leonid Bashlykov, Revolut’s Head of Crypto Product, prompted it in plain English. The agent did the rest.

What MCP Actually Changed

Before MCP, every AI integration was bespoke. You wrote a connector for OpenAI, another for Claude, another for your internal tools. Each one required maintenance, documentation, and debugging.

MCP is an open protocol that standardizes how AI models discover and use external tools. You expose your API through an MCP server once. Any compatible agent can use it.

Revolut built one server. Claude figured out the rest.

The shift is subtle but important. MCP decouples the integration effort from the number of AI systems that can use it. One server works with Claude today, could work with other agents tomorrow.

The Product Strategy Question Nobody Is Asking

The Revolut team asked the right question: if an agent can orchestrate your system through the API, what exactly is the product team building?

The uncomfortable answer is that interface design matters less when the agent reads your API documentation and composes capabilities on its own.

A market-making strategy that normally requires a quant, a developer, backtesting infrastructure, and weeks of iteration materialized in half an hour. The engineers provided the server. Claude provided the strategy.

Why This Matters Beyond Crypto

Crypto is a forgiving sandbox. Twenty-four-seven markets, fewer regulatory constraints, simpler settlement.

The real test is regulated markets. Equities, fixed income, anything with compliance overlays.

The fintech that solves scoped agent permissions for regulated markets wins. Trade up to 10k per day, specific instruments only, kill-switch at 2% drawdown. That is the interface compliance teams actually want.

The Infrastructure Race

Revolut’s experiment was a side project. But the question it raises is strategic.

If Coinbase, Kraken, and Interactive Brokers ship MCP layers, the competitive moat moves from interface design to API richness and execution quality.

The UI becomes a monitoring layer. The API surface becomes the product.

Revolut built a trading desk with Claude in 30 minutes. The real story is not the time saved. It is the architecture revealed.