I Built a Register-VM JavaScript Engine in Rust with opencode.ai — Beating QuickJS

Three weeks ago I signed up for opencode.ai’s $5 plan. I had this crazy idea: build an ES2023-compliant JavaScript engine in Rust. The result? pipa (枇杷) — a register-based VM that scores 1256 on the V8 benchmark, edging out QuickJS (1219).

The Stack

  • AI: opencode.ai ($5/mo first month) × DeepSeek-v4-Flash
  • Language: Rust (edition 2024)
  • Binary size: ~5.2 MB (including REPL, HTTP client, WebSocket)
  • Zero external deps for: Regex, JSON, Base64, BigInt, Fetch, WebSocket, SSE

Built Over 3 Weeks

Looking back at the opencode session logs, the earliest work on pipa started on April 28. Over the next 22 days, I worked through ~85 sessions. opencode tracked every diff — from the initial skeleton (lexer, parser, NaN-boxed value system, register VM, opcodes) through incremental refinements.

The first commit created the entire foundation: ~75,000 lines across 205 source files. Then came iterative improvements — peephole optimizer passes, inline cache tuning, GC pinning logic, WebSocket handshake fixes, benchmark harnesses, and optimization levels O0 through O3.

Why a Register VM?

Stack VMs need constant shuffle/dup. Register VMs name operands explicitly — fewer instructions, more optimization surface.

pipa’s instructions are u8 opcode + u16 register operands, variable-length (1/3/5/7/9/11 bytes). Fused EqJumpIf combines compare + branch. LoadInt8 r, imm8 loads small integers in 3 bytes. Compact Jump8 uses 2 bytes for near jumps. The peephole optimizer runs 3 passes, eliminating Move rX, rX and folding zero/one patterns.

NaN-Boxing: Everything in 64 Bits

0x7FF8_?TTT_?PPP_PPPP_PPPP_PPPP
         ^^^^ tag  ^^^^^^^^^^^^^ payload

Every JS value fits in one u64. A canonical NaN prefix + 4-bit tag + 47-bit payload encodes all 10 types. Type dispatch is a single shift+mask. Small integers avoid boxing entirely. Hot-path both_int() checks both operands with one bitmask — straight to i64 ops.

Zero-Dependency Builtins

  • Regex (src/regexp/): Layered execution — LiteralFast → FastClass → DFA → OptimizedNFA → FullNFA. The analyzer picks the fastest engine per pattern. No regex crate.
  • JSON: Hand-written recursive descent parser and serializer.
  • Fetch/WebSocket/SSE (src/http/): Rustls-based TLS, chunked transfer encoding, compression, WebSocket frame parsing — all from scratch.
  • BigInt, Base64, Unicode: Hand-rolled.

Generational GC

16 MB nursery, bump allocation, minor GC clears dead objects. If 10 consecutive minors find no garbage, it promotes to full GC with incremental marking and write barriers. Triggered every 16K allocations.

Performance Optimizations: What Actually Worked

Beyond the baseline compiler, pipa has a stack of optimizations that together make it faster than QuickJS:

1. Peephole Optimizer (3 Passes)

src/compiler/peephole.rs runs up to 3 passes over bytecode. Each pass locally transforms patterns:

  • Self-move: Move rX, rX → NOP
  • Zero/one folding: LoadInt rX, 0; Add rX, rY, rX → Move rX, rY
  • Divide-by-one: LoadInt rX, 1; Div rX, rY, rX → Move rX, rY
  • Jump threading: Jump → Jump chains resolved to the final target
  • Double negation: Not; Not → Move, same for Neg; Neg, BitNot; BitNot
  • Constant folding: LoadInt 3; LoadInt 4; Add → single LoadInt 7 (supports add/sub/mul/and/or/xor)
  • Branch on constant: LoadTrue; JumpIf rX → Jump (always taken), LoadFalse; JumpIf rX → NOP
  • LoadInt → LoadInt8 shrink: Integers in [-128, 127] that feed a Move get shortened to 3-byte LoadInt8

2. Fused Compare-Jump Instructions

Instead of computing a boolean to a register then branching, EqJumpIf, LtJumpIfNot, StrictEqJumpIf etc. (16 fused pairs) directly compare two values and conditionally jump in a single opcode. This eliminates intermediate register pressure and reduces instruction count on every comparison.

3. NaN-Boxing Fast Paths

both_int() in src/value.rs:194 uses a single bitmask operation to check whether both operands are tagged integers:

const TAG_MASK: u64 = QNAN_BASE | (0xF << TAG_SHIFT);
(a.0 & TAG_MASK) == INT_TAG_BITS && (b.0 & TAG_MASK) == INT_TAG_BITS

This is used 29 times in vm.rs across all arithmetic and comparison opcodes. When both operands are integers, the VM directly performs i64 arithmetic — no type dispatch, no ToNumber coercion. Similarly, both_raw_float() skips tagging for raw f64 pairs.

is_fast_int() in new_float() automatically converts f64 values that fit in 47 signed bits back to tagged ints, maximizing the chance future operations hit the fast path.

4. Shape + Inline Cache

src/object/shape.rs implements a V8-style hidden class tree. Each object points to a Shape that records property-name-to-offset mappings via transition chains (add property → new child shape).

src/compiler/ic.rs implements two-way polymorphic inline caches (IC_POLY=2). Each cache slot stores (shape_id, property_offset). On property access, the VM compares the object’s shape ID against cached slots — on hit, it reads directly from the cached offset, bypassing hash lookup entirely. For monomorphic hotspots (one shape), this is a single shape compare + pointer offset load.

5. Cached Raw Pointers in VM

The VM struct caches 7 raw pointers updated on each call frame push:
cached_code_ptr, cached_const_ptr, cached_registers_base, cached_registers_ptr, cached_ic_table_ptr, cached_upvalue_slot_ptr, cached_upvalues_len

Hot-path instruction handlers access bytecode, constants, and registers through these cached pointers rather than chasing through frames[frame_index] on every instruction. This eliminates pointer indirection in the inner loop (~9000 lines of execute_inner).

6. Opcode-Level Shortcuts

  • LoadInt8 r, imm8: Loads [-128, 127] in 3 bytes instead of 7 for full LoadInt
  • AddImm8 / SubImm8 / LteImm8: Arithmetic with small integer constants encoded directly in the instruction
  • Call0 / Call1 / Call2 / Call3: Specialized call opcodes that skip argument counting
  • Jump8 / JumpIf8: 2-byte jumps for near targets (saves 3 bytes per jump)
  • Micro function inlining (O2+): x => x.prop or x => x.a.b detected in codegen and inlined as GetNamedProp sequences, eliminating frame allocation

7. Generational GC with Incremental Marking

The GC (src/runtime/gc.rs, ~1550 lines) uses a 16 MB nursery with bump allocation. Minor GC is nearly free — scan roots, sweep white. If 10 consecutive minor GCs find nothing to collect, it promotes to full GC. Full GC uses incremental marking with configurable budget per step, avoiding long stop-the-world pauses. Write barriers protect black→white references during incremental marking.

8. Layered Regex Engine

The regex engine (src/regexp/) is the biggest single win — 3× faster than QuickJS on the V8 RegExp benchmark. It uses a five-tier execution strategy:

Mode When How
LiteralFast Pure literal string (no metacharacters) memcmp
FastClass Simple character class only Byte-by-byte FastClassMatcher
LiteralDFA/ClassDFA Simple patterns compilable to DFA Linear-time DFA execution
OptimizedNFA Medium complexity with optimizations Optimized NFA bytecode
FullNFA Complex patterns with backreferences Full backtracking NFA with pooled contexts

The pattern analyzer (analyze_pattern) classifies each regex at compile time and selects the cheapest engine that can handle it. Most real-world regexes hit the DFA or faster tiers.

test262 Coverage: What We Tried

Running the official ECMAScript test suite (test262) has been an ongoing effort. Here’s what’s in place:

test262 Runner

examples/test262_runner.rs (~500 lines) is a standalone test harness that:

  • Parses YAML frontmatter from test files (/*--- ... ---*/)
  • Handles test flags (onlyStrict, noStrict, raw, async, generated)
  • Resolves test includes (harness helper files like assert.js, sta.js)
  • Detects negative tests (expected failures with specific error types)
  • Tracks per-file pass/fail statistics with console output

test262.sh scripts the full flow: clone test262 from tc39, then invoke the runner.

Current Coverage: ~45%

The engine passes roughly 45% of the test262 suite. Major working areas:

  • Lexical grammar: Identifiers, keywords, Unicode escape sequences, line terminators
  • Expressions: Primary, left-hand-side, unary, binary, conditional, assignment
  • Statements: Block, variable/function declarations, if/switch/while/for/for-in/for-of, try/catch/finally, return/throw/break/continue
  • Built-in objects: Object, Array, String, Number, Boolean, Symbol, BigInt, Math, Date, RegExp, JSON, Map, Set, Promise, Proxy, Reflect, TypedArrays, Intl
  • Control flow: Aberrant behavior detection, early error handling
  • Modules: import/export, namespace objects

What’s Missing

The remaining 55% mostly falls into:

  • Annex B (browser extensions): __proto__, Object.prototype.__defineGetter__, RegExp.$1 etc.
  • Edge cases in temporal dead zone and class field initialization
  • Async generator finalization corner cases
  • Atomics/SharedArrayBuffer: The single-threaded model doesn’t support these yet
  • Intl.NumberFormat v3: Recent additions to the Intl spec

The MIR layer (src/compiler/mir.rs, ~460 lines) is designed but not yet connected to the main pipeline — once wired, it will enable better dead code elimination, type inference, and potentially SSA-based optimizations.

How opencode Helped

I’d never have built this without AI pair programming. The workflow — describe what I want, get working code, test, refine — collapsed months of work into 3 weeks.

The session timeline tells the story:

Period Focus
Week 1 (Apr 28–May 4) Core engine: lexer, parser, register VM, NaN-boxing, opcode encoding, basic builtins
Week 2 (May 5–11) Optimizations: peephole passes, shape system, inline caches, regex engine, bytecode serialization
Week 3 (May 13–20) HTTP stack: fetch, WebSocket, SSE, EventSource; process API; benchmarks; REPL polish

DeepSeek-v4-Flash understood Rust borrow semantics, generated correct unsafe NaN-boxing code, wired up TLS handshakes, and debugged GC edge cases. Without it, this project would have stayed a weekend thought experiment.

Try It

cargo install pipa-js
pipa script.js
pipa -compile input.js output.jsc   # pre-compile to bytecode
pipa -O3 script.js                   # max optimization

MIT licensed on GitHub. PRs and stars welcome.

The GPU Is the New Database

_

Twenty years ago, teams had no idea how to run databases at scale. They made every mistake possible before the patterns solidified. We are now in the same position with GPU infrastructure, making the same mistakes, faster.
Find more articles atThis site
_

In 2004, if you were running a web application at any meaningful scale,
your biggest infrastructure problem was the database. Not the application
servers, those were stateless, you could add more. The database was the
single stateful thing everything depended on, it didn’t scale horizontally,
it was expensive to run, and almost nobody knew how to operate it well.

Teams made every mistake. They put too much logic in the application and
not enough in the database. They put too much in the database and not
enough in the application. They didn’t index correctly. They didn’t cache
correctly. They scaled vertically until they couldn’t, then scrambled to
shard. They had no idea what their query plans looked like. They treated
the database as a black box until it stopped working, then learned the
hard way that it wasn’t.

Over the following decade, the patterns solidified. Connection pooling.
Read replicas. Query analysis. Proper indexing strategy. Cache layers.
The knowledge became common. The tools improved. Managed database services
abstracted most of the complexity. Today a competent team can run a
database at significant scale without extraordinary expertise.

We are now, in 2026, in the same position with GPU infrastructure. The
GPU is the new database, the expensive, stateful, poorly-understood
bottleneck that everything AI depends on, that doesn’t scale the way
people expect, that is being operated badly by the majority of teams
running it, and for which the patterns have not yet solidified.

The teams that figure this out first will have an infrastructure advantage
that is very difficult to close. The teams that don’t will spend the
next five years making the same mistakes everyone made with databases
in 2004, just faster and more expensively.

Why the GPU is not just a fast CPU

The first mistake most teams make with GPU infrastructure is treating
GPUs as very fast CPUs. They’re not. They’re a fundamentally different
computational model, and the mismatch between that model and how most
people use them is where most of the waste comes from.

A CPU is optimised for latency ,completing a single complex task as
quickly as possible. It has a small number of powerful cores, large
caches, sophisticated branch prediction, and out-of-order execution.
It’s good at sequential logic, conditional branching, and tasks where
each step depends on the result of the previous one.

A GPU is optimised for throughput, completing an enormous number of
simple tasks simultaneously. It has thousands of smaller, simpler cores.
It’s good at the same operation applied in parallel to a large amount
of data. It’s bad at anything sequential, anything with complex
branching, and anything where you need to move data back to the CPU
in the middle of computation.

The practical consequence: a GPU that is not batching work is a GPU
that is mostly idle. The most common pattern for teams deploying AI
inference in production, one request comes in, run the model, return
the result, wait for the next request, uses a small fraction of the
GPU’s actual capacity. The GPU’s utilisation number looks reasonable.
The GPU’s actual computational throughput is terrible.

This is the equivalent of a database that opens a new connection for
every query, executes it, and closes the connection. Technically
functional. Completely missing how the system should be used.

# What most teams do: one request, one inference
# GPU utilisation looks like 20-40%, but throughput is poor

async def handle_inference_request(prompt: str) -> str:
    result = model.generate(prompt)  # GPU mostly idle while waiting
    return result


# What should be happening: dynamic batching
# Multiple requests grouped and processed together

class InferenceBatcher:
    def __init__(self, model, max_batch_size: int = 32, max_wait_ms: int = 50):
        self.model = model
        self.max_batch_size = max_batch_size
        self.max_wait_ms = max_wait_ms
        self.queue: asyncio.Queue = asyncio.Queue()

    async def infer(self, prompt: str) -> str:
        future = asyncio.Future()
        await self.queue.put((prompt, future))
        return await future

    async def _batch_worker(self):
        while True:
            batch = []
            deadline = asyncio.get_event_loop().time() + (self.max_wait_ms / 1000)

            # Collect requests until batch is full or deadline passes
            while len(batch) < self.max_batch_size:
                timeout = deadline - asyncio.get_event_loop().time()
                if timeout <= 0:
                    break
                try:
                    item = await asyncio.wait_for(
                        self.queue.get(),
                        timeout=timeout
                    )
                    batch.append(item)
                except asyncio.TimeoutError:
                    break

            if not batch:
                continue

            prompts = [item[0] for item in batch]
            futures = [item[1] for item in batch]

            # Single GPU call processes all requests simultaneously
            results = self.model.generate_batch(prompts)

            for future, result in zip(futures, results):
                future.set_result(result)

Dynamic batching is the connection pooling of GPU inference. It is
not optional if you care about cost or throughput. It is also not
implemented by default in most hand-rolled inference deployments,
for the same reason that early web applications didn’t implement
connection pooling: teams didn’t know they needed it until they
hit the wall.

The memory hierarchy nobody teaches you

GPU memory is not like CPU memory. Understanding the difference is
the difference between a system that works and one that doesn’t, and
between inference costs that are manageable and ones that are not.

A GPU has its own on-device memory ,VRAM. VRAM is fast, finite,
and expensive. A GPU with 80GB of VRAM is a very expensive GPU.
The model you’re running must fit in VRAM. If it doesn’t fit, you
can use techniques like quantization to make it smaller, or you can
distribute it across multiple GPUs, but you cannot simply overflow
to system RAM without taking a catastrophic performance hit. The
bandwidth between CPU RAM and GPU VRAM is orders of magnitude slower
than VRAM bandwidth. When you hear about models being “quantized
to 4-bit,” this is why 4-bit quantization halves the memory
footprint roughly, which is the difference between fitting on one
GPU and not fitting on one GPU.

Within the GPU itself, there is a memory hierarchy that determines
how fast computation runs. The KV cache, the cached attention
computation for the tokens already processed in a conversation
lives in VRAM and grows with sequence length. Managing KV cache
is one of the most consequential performance decisions in LLM serving,
and most teams don’t think about it at all until they start hitting
out-of-memory errors on long contexts.

# KV cache management: what happens without it
# Each new token regenerates attention for the entire context
# Cost is O(n²) in sequence length

# What vLLM and similar systems do differently:
# PagedAttention manages KV cache in fixed-size blocks
# like virtual memory paging in an OS

# This allows:
# 1. Sharing KV cache between requests with the same prefix
# 2. Better memory utilisation (no internal fragmentation)
# 3. Handling variable-length sequences without pre-allocating
#    worst-case memory

from vllm import LLM, SamplingParams

# vLLM handles KV cache management automatically
# This is not a minor optimisation — it's 2-4x throughput improvement
# on typical workloads versus naive implementations

llm = LLM(
    model="meta-llama/Llama-3-8b-instruct",
    gpu_memory_utilization=0.90,    # Leave 10% headroom
    max_model_len=8192,             # Maximum sequence length
    enable_prefix_caching=True,     # Cache common prefixes (system prompts)
    tensor_parallel_size=1,         # Number of GPUs for this model
)

sampling_params = SamplingParams(
    temperature=0.7,
    max_tokens=512,
)

# Prefix caching means your system prompt is computed once
# and cached for all subsequent requests — significant for
# long system prompts used with every inference
outputs = llm.generate(prompts, sampling_params)

Most teams serving LLMs in production are not using PagedAttention.
They’re using naive inference implementations that waste fifty to
seventy percent of their GPU memory to fragmentation and redundant
computation. The cost difference is not marginal.

The scaling question everyone asks wrong

When a team’s AI infrastructure starts struggling under load, the
first question is almost always: “should we add more GPUs?”

This is the wrong question asked at the wrong time, for the same
reason that “should we add more database servers” was the wrong
first question when a database was struggling in 2008. The right
question is: “why are we using our current GPUs so inefficiently?”

GPU utilisation that is below sixty percent is almost always a
batching problem. Requests are not being grouped efficiently before
hitting the GPU. You can add more GPUs and halve your utilisation
number, which means you now have twice the infrastructure running at
thirty percent capacity instead of one set running at sixty. You’ve
doubled your cost and solved nothing.

GPU utilisation that is high but latency is still bad is almost
always a model sizing problem. The model is too large for the request
volume being served. A smaller quantized model, or a different
architecture, may serve your request latency requirements at a
fraction of the compute cost.

# Measuring what actually matters before deciding to scale

import time
import psutil
from prometheus_client import Histogram, Gauge, Counter

# These metrics tell you where the problem actually is

GPU_UTILISATION = Gauge(
    'gpu_utilisation_percent',
    'GPU compute utilisation',
    ['device_id']
)

GPU_MEMORY_USED = Gauge(
    'gpu_memory_used_bytes',
    'GPU VRAM in use',
    ['device_id']
)

BATCH_SIZE = Histogram(
    'inference_batch_size',
    'Number of requests processed per batch',
    buckets=[1, 2, 4, 8, 16, 32, 64]
)

TOKENS_PER_SECOND = Histogram(
    'inference_tokens_per_second',
    'Throughput of inference in tokens per second',
    buckets=[10, 25, 50, 100, 200, 400, 800]
)

TIME_TO_FIRST_TOKEN = Histogram(
    'inference_ttft_seconds',
    'Time from request to first token generated',
    buckets=[.05, .1, .25, .5, 1, 2, 5]
)

REQUEST_QUEUE_DEPTH = Gauge(
    'inference_queue_depth',
    'Number of requests waiting for GPU'
)


class InstrumentedInferenceServer:
    async def infer(self, prompts: list[str]) -> list[str]:
        BATCH_SIZE.observe(len(prompts))
        REQUEST_QUEUE_DEPTH.set(self.queue.qsize())

        start = time.perf_counter()
        results = await self._run_inference(prompts)
        duration = time.perf_counter() - start

        total_tokens = sum(len(r.split()) for r in results)
        TOKENS_PER_SECOND.observe(total_tokens / duration)

        return results

When you can see batch sizes, queue depth, tokens per second, and
time-to-first-token alongside GPU utilisation and VRAM usage, the
question of “do we need more GPUs” almost answers itself. Usually
the answer is “no, we need to batch better” or “no, we need to use
a smaller model” and scaling turns out to be unnecessary.

The cold start problem nobody planned for

Databases take seconds to start. GPU inference servers take minutes.

A database that restarts unexpectedly is back within thirty seconds
in most cases. An LLM inference server that restarts needs to load
model weights from storage into VRAM before it can serve any requests.
A 70B parameter model stored in 4-bit quantization is roughly 35GB.
Loading 35GB from network storage into VRAM, at typical cloud storage
bandwidth, takes several minutes under good conditions.

This changes incident dynamics entirely. A database blip is a brief
interruption. A GPU server blip is a several-minute outage for every
affected instance. Autoscaling, which works well for stateless
application servers and adequately for databases, works badly for
GPU inference because new instances take so long to become ready.

The teams that have worked this out run warm pools ,GPU instances
with models already loaded, sitting idle, waiting for traffic that
hasn’t arrived yet. This feels wasteful. It’s the only way to handle
traffic spikes without minutes-long latency blowouts.

# Kubernetes deployment with warm pool strategy
# Minimum replicas keep instances warm even at low traffic
# This costs money. The alternative is cold start latency.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: llm-inference
spec:
  replicas: 3  # Never scale below this. These are your warm pool.
  template:
    spec:
      containers:
      - name: inference-server
        image: myteam/inference:latest
        resources:
          limits:
            nvidia.com/gpu: 1
          requests:
            nvidia.com/gpu: 1
        readinessProbe:
          httpGet:
            path: /health
            port: 8080
          # Model loading takes time. This probe must not pass
          # until the model is fully loaded in VRAM.
          initialDelaySeconds: 180   # 3 minutes minimum
          periodSeconds: 10
          failureThreshold: 30       # 5 more minutes of retries
        lifecycle:
          preStop:
            exec:
              # Drain in-flight requests before shutdown
              command: ["/bin/sh", "-c", "sleep 30"]

---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: llm-inference-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: llm-inference
  minReplicas: 3    # Warm pool floor
  maxReplicas: 10
  metrics:
  - type: External
    external:
      metric:
        name: inference_queue_depth
      target:
        type: AverageValue
        averageValue: "5"  # Scale when queue exceeds 5 requests per replica
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
      - type: Pods
        value: 2
        periodSeconds: 120   # Add 2 pods max every 2 minutes
                             # Fast enough to respond, slow enough
                             # to not over-provision during spikes
    scaleDown:
      stabilizationWindowSeconds: 600  # Wait 10 minutes before scaling down
                                       # Cold start cost makes yo-yo scaling
                                       # extremely expensive

The scaleDown stabilization window is long deliberately. The cold start
cost is so high that scaling down and back up in response to a brief
traffic dip is more expensive than just keeping the instances running.
This is counterintuitive if you’re coming from stateless web services.
It’s the operational reality of GPU infrastructure.

The cost model is upside down

Database costs scale with data volume and query complexity. You pay
more as your data grows and your queries get more complex.

GPU costs scale with time. You pay for every second a GPU exists,
whether it’s serving requests or not. An idle GPU costs the same as
a busy GPU.

This inverts the normal infrastructure economics. With stateless
application servers, idle capacity is cheap, you can scale to zero
when traffic drops and pay nothing. With GPU inference, scaling to
zero means cold starts when traffic returns. The minimum viable
capacity for a production inference service is not zero, it’s
whatever your warm pool needs to be, which is determined by your
acceptable cold start latency and your traffic spike patterns.

The teams that have made peace with this have stopped thinking about
GPU cost as a variable cost that tracks usage and started thinking
about it as a fixed cost that buys capacity. The question is not
“how do we pay less for GPU when traffic is low?” The question is
“what is the right amount of always-on capacity, and how do we make
sure we use it efficiently?”

Efficient use means high batch fill rates, high token throughput per
GPU-hour, low idle time. The metrics above are the inputs to this
calculation. Without them you’re guessing about whether your
infrastructure is sized correctly.

The pattern that’s emerging

The teams operating GPU infrastructure well in 2026 look, in their
operational discipline, a lot like the teams that operated databases
well in 2012 after enough people had been burned that the patterns
were starting to solidify.

They treat GPU utilisation as a lagging indicator and token throughput
as the leading one. They instrument everything: batch sizes, queue
depth, time-to-first-token, VRAM usage, KV cache hit rates. They
size their warm pools based on measured traffic patterns rather
than intuition. They run the smallest model that meets their quality
bar, not the largest model they can afford, because smaller models
batched efficiently outperform larger models batched poorly on
almost every practical metric.

They’ve also accepted something that takes a while to accept: that
the right abstraction for GPU infrastructure is not “fast compute”
but “throughput capacity.” The question is not “how fast can this
machine process one request?” GPUs are fast at that regardless.
The question is “how many requests per dollar can this infrastructure
handle at acceptable latency?” That question requires different
metrics, different architecture, and a different mental model than
the one most teams bring from their experience with CPU infrastructure.

The database analogy runs deeper than it looks. In 2004, the teams
that treated the database as a black box, put data in, get data
out, add more RAM when it’s slow, eventually hit walls that their
architecture couldn’t get past. The teams that understood what was
happening inside the database query plans, index usage, lock
contention, buffer pool behaviour built things that scaled.

The GPU is not a black box. It has a memory hierarchy, a batching
model, a cost structure, and performance characteristics that reward
understanding and punish ignorance in the same way the database did.

The patterns are forming. The teams learning them now will have the
same advantage in five years that database-literate engineers had in 2015.

The mistakes are happening right now, at scale, expensively.
Most of them are the same mistakes. Most of them are avoidable.

Per-User OAuth for AI Agents: Why It Matters and What to Look For

Per-user OAuth flow for AI agents

Per-user OAuth flow for AI agents

AI agents are crossing a line that traditional software never had to. They read your Slack, draft your emails, push code, update your CRM, and pay your invoices. To do that, they need keys to systems that belong to specific people. Not the application. Not the company. The person.

That is the entire reason per-user OAuth exists in the agent context, and it is the difference between a side project and something a customer will trust with their Gmail account.

This article breaks down what per-user OAuth means for AI agents, why shared credentials fall apart at scale, what the emerging standards look like, and the exact checklist to use when picking a platform to handle it. We will also show how Composio approaches each of these problems so you do not have to assemble the stack yourself.

The problem with the way most teams start

Most agent prototypes start with a single API key in an environment variable. It works for one developer, on one machine, for one demo. The moment a real user shows up, the model breaks.

API keys identify the calling application, not the user behind the action. Every request from the agent looks the same to the downstream service. There is no concept of consent, no scoping per user, no way to revoke one person’s access without invalidating everyone, and no audit trail that says “this action happened because Sarah asked the agent to do it.”

This becomes a real security problem fast. If the agent code accidentally passes the wrong user identifier, or an attacker tricks the agent into requesting data for a user who did not authorize it, the agent has no protocol-level defense. This is the classic confused deputy problem, and it scales horribly with autonomous systems that chain dozens of tool calls per task.

Composio’s own guide on AI agent infrastructure calls this hitting the “Authentication Wall.” It is the moment a promising prototype stops being promising.

What per-user OAuth actually solves

Per-user OAuth flips the model. Instead of the agent holding one master credential, each user grants the agent a scoped, revocable token tied to their own account. The agent acts with that user’s identity, within the limits that user approved, for as long as that user allows.

Concretely, this gives you:

Explicit consent. The user sees what the agent is asking for and approves it. No assumed access.

Scoped permissions. The token can be limited to read-only access on a specific resource rather than full account control. If the agent only needs to read, that is all it gets.

Short lifetimes. Access tokens expire in minutes to an hour. Refresh tokens rotate. A leaked token is dangerous for a short window, not forever.

Selective revocation. Revoking one user’s grant does not break the agent for everyone else.

Identity in the audit log. Every action traces back to a specific user, which is what SOC 2, HIPAA, and ISO 27001 actually require.

Multi-tenant isolation. Each user’s tokens live in their own bucket, encrypted at rest. A bug in one tenant’s workflow does not expose another tenant’s data.

Multi-tenant token isolation across users

That last one matters more than people think. Composio’s user and session model is built around exactly this idea: a user is an identifier from your app, every connection lives under that user’s ID, and connections are fully isolated between users. The same agent code can serve thousands of users without any of them ever touching each other’s data.

The standards that are actually shaping this

The protocol layer is moving fast. There are three things worth knowing.

OAuth 2.1 with mandatory PKCE

OAuth 2.1 is the current best-practice consolidation of OAuth 2.0. It makes PKCE (Proof Key for Code Exchange) mandatory and removes older, less secure flows. PKCE matters specifically for agents because most agents are public clients running in environments where you cannot reliably hide a client secret. PKCE prevents an attacker from intercepting an authorization code mid-flow.

If a platform you are evaluating does not enforce PKCE, that is a red flag.

The MCP Authorization spec

The Model Context Protocol authorization specification formalized OAuth as its standard in 2025. It mandates OAuth 2.1 with PKCE, requires Authorization Server Metadata discovery via RFC 8414, and supports Dynamic Client Registration via RFC 7591. The November 2025 update added step-up authorization, letting clients request additional scopes only when an operation actually requires them, rather than over-permissioning the initial token.

The spec also had a real-world security crisis to address. In late 2025, security researchers at Obsidian Security disclosed one-click account takeover vulnerabilities in remote MCP servers from several well-known organizations. The root cause: many MCP servers were implemented as OAuth proxies using a single static client_id to talk to the upstream SaaS authorization server. Once any user consented for that shared client_id, the SaaS auth server cached the decision. An attacker could then complete the MCP-layer consent themselves, send a crafted authorization link to a victim, and the upstream server would skip the consent prompt entirely because it had seen that client_id before. The authorization code would be issued to the attacker’s redirect URI.

The fix is per-client identity and strict consent handling at the proxy layer. Composio’s Tool Router gives each session a secure, user-scoped MCP URL rather than a shared endpoint, which sidesteps this class of attack structurally.

The IETF “On-Behalf-Of” draft for AI agents

There is an active IETF draft, draft-oauth-ai-agents-on-behalf-of-user, that extends OAuth specifically for agent delegation. It adds a requested_actor parameter so the consent screen shows the agent’s identity (not just the app), and an actor_token parameter so the agent authenticates itself when exchanging the authorization code.

The result is an access token that documents the full delegation chain: the user delegated to this client application, which delegated to this specific agent. That chain is what makes after-the-fact auditing possible.

The draft is at revision 02 as of August 2025 and has not been adopted by the working group yet, but it points clearly at where the protocol layer is headed. Multi-hop delegation (agent A calling agent B on the same user’s behalf) is still an open problem in the spec.

Production architecture: where teams fail

Getting an access token is the easy part. Operating at production scale is where most homegrown OAuth implementations collapse. Five things tend to go wrong.

Token storage. Tokens have to be encrypted at rest, isolated per tenant, never logged, and never placed in LLM context. The last point is non-obvious and critical: if you put a refresh token in the prompt, a prompt injection attack can exfiltrate it. The pattern to use is brokered credentials, where the LLM never sees the token at all and a separate service makes the actual API call.

Brokered credentials pattern

Composio’s secure infrastructure guide explains this pattern directly: the LLM asks Composio to perform an action, Composio calls the upstream API with the stored credential, and the token never enters the model’s context window.

Refresh handling. Refresh tokens need proactive coordination. Waiting for a 401 error and then refreshing creates race conditions, cascading retries, and unstable background jobs. Composio handles refresh automatically and only marks a connection as EXPIRED after multiple refresh attempts have failed, according to its authentication docs.

Scope discipline. Composio requests sensible default scopes for each toolkit but lets you override them via custom auth configs. Tightening scopes shrinks the blast radius if something goes wrong. Most APIs still have coarse-grained scopes, which means even with discipline, agents tend to be over-permissioned. The mitigation is short token lifetimes and per-tool scoping where the API supports it.

Branding and consent screens. When users hit an OAuth consent screen that says “Composio wants to access your Gmail” instead of “YourProduct wants to access your Gmail,” conversion drops and trust takes a hit. Composio’s white-labeling support lets you bring your own OAuth app credentials so the consent screen shows your brand. Use managed apps for prototyping and your own credentials for production.

Multi-account per user. Some users connect a personal Gmail and a work Gmail. The platform needs to model that without forcing them to share a user ID across both. Composio handles this with the connected account ID layered under the user ID, so a single user can have multiple accounts on the same toolkit, as documented in the users and sessions guide.

What to look for in a per-user OAuth platform for agents

If you are evaluating providers, these are the non-negotiables:

  1. Per-user token isolation with encryption at rest and no cross-tenant leakage
  2. OAuth 2.1 with mandatory PKCE for all OAuth flows
  3. Automatic, proactive token refresh that does not depend on the client
  4. Short-lived access tokens with rotating refresh tokens
  5. Granular scope configuration so each integration uses least privilege
  6. Brokered credentials so the LLM never sees raw tokens
  7. Selective per-user revocation without affecting other users
  8. Audit trail on the delegation chain for compliance
  9. White-label OAuth consent screens for production trust
  10. Step-up authorization to request new scopes only when needed
  11. Multi-account per user for personal and work accounts on the same app
  12. MCP-native deployment so any MCP-compatible client can use the same auth layer
  13. SOC 2 Type 2 and ISO 27001 compliance as table stakes for enterprise customers
  14. SDK support across major frameworks (LangChain, CrewAI, OpenAI Agents SDK, Claude Agent SDK, Mastra, Vercel AI SDK, LlamaIndex)

Composio covers every item on that list today. It supports 500+ toolkits, handles OAuth end-to-end with user-scoped tokens, is SOC 2 Type 2 and ISO 27001 compliant, and works as both a direct SDK and an MCP server. The full feature breakdown is on the AgentAuth product page and the comparison guide.

What this looks like in code

Per-user OAuth with Composio compresses into a handful of lines. The pattern below follows the current SDK as documented in Composio’s authenticating tools guide.

First, install the SDK and set your API key:

pip install composio
export COMPOSIO_API_KEY=your_api_key_here

To trigger the OAuth flow for a specific user, use the hosted Connect Link pattern. This returns a redirect URL the user opens in their browser to complete authentication:

from composio import Composio

composio = Composio(api_key="your_api_key")

# Use the "AUTH CONFIG ID" from your Composio dashboard
auth_config_id = "your_auth_config_id"

# Use a unique identifier for each user in your application
user_id = "user_1349_129_12"

connection_request = composio.connected_accounts.link(
    user_id=user_id,
    auth_config_id=auth_config_id,
    callback_url="https://your-app.com/callback"
)

redirect_url = connection_request.redirect_url
print(f"Visit: {redirect_url} to authenticate your account")

After the user completes the flow, Composio stores the tokens, links them to that user ID, and handles refresh automatically. The agent code never touches the token directly.

To fetch tools scoped to a specific user, pass that same user ID:

from composio import Composio

composio = Composio(api_key="your_api_key")

user_id = "user_1349_129_12"

# Tools are automatically scoped to this user's connected accounts
tools = composio.tools.get(user_id=user_id, toolkits=["GITHUB", "GMAIL"])

Two users running the same workflow get two different sets of credentials applied transparently:

tools_user_1 = composio.tools.get(user_id="user_1", toolkits=["GITHUB"])
tools_user_2 = composio.tools.get(user_id="user_2", toolkits=["GITHUB"])

# Each set of tools uses the respective user's credentials
# when invoked by an agent

That is the whole point of per-user OAuth: the auth layer disappears into the platform, and the agent code reads like it is talking to a single user even when it is serving thousands.

For a full working example with an LLM framework, see the framework-specific quickstart guides in Composio’s documentation.

The takeaway

Per-user OAuth is not a feature you bolt onto an agent product later. It is the foundation that decides whether your agents can serve real customers at all. Shared API keys cap your ceiling at a single-user demo. Per-user OAuth opens the door to multi-tenant production deployment, enterprise compliance, and the kind of trust required to handle a customer’s inbox, calendar, or revenue pipeline.

The protocol layer is still evolving. OAuth 2.1, the MCP authorization spec, and the IETF on-behalf-of draft are all converging on the same answer: explicit user consent, scoped delegation, audited token lifecycle, and isolation per user. Build for that model now and you will not need to retrofit later.

If you would rather skip the months of building it yourself, start with Composio or read the auth-to-action guide for the full architecture. The shortest path from prototype to production is using a platform that already solved this.

You Got Your Whole Genome Sequenced. Now What?

Pixel art character staring at overwhelming genome data on a glowing monitor in a cozy home office at night

Published May 20, 2026 on matbanik.info

You paid somewhere between $300 and $500 for whole genome sequencing. Maybe Nebula, maybe Dante, maybe one of the newer providers that keep undercutting each other. You got a folder. Inside that folder: a VCF file with 4.86 million rows of variant data and a dashboard that shows you maybe 1% of what’s in there.

The dashboard answers their questions. Ancestry breakdown. A handful of trait associations. Maybe a carrier screening if you’re lucky. But your questions — the ones that actually matter to you — don’t have a button.

“What are my CYP enzyme metabolizer phenotypes?” No button.

“Which variants did I inherit from my father versus my mother?” Definitely no button.

“What does ClinVar say about that variant my doctor flagged last week?” Not even close.

This post is about closing that gap. I’ll show why it exists, what it looks like when it’s gone, and how to set it up yourself — locally, without uploading your genome anywhere.

Pixel art character staring at overwhelming genome data on a glowing monitor in a cozy home office at night

The “Now What?” Problem

The consumer whole-genome sequencing market has a strange asymmetry. Getting sequenced is easy. Thirty-times coverage for under $500 in 2026. The price curve has been falling for years and it’s still falling.

But interpretation hasn’t kept up.

You get a VCF file with millions of variant rows. Each row is a position in your genome that differs from the reference. Some of those differences matter a lot. Most don’t. And the consumer platforms that sold you the test? They show you a curated slice — pre-built reports with pre-built answers.

That’s fine for the questions they anticipated. It’s useless for the ones they didn’t.

Here’s what gets lost in the average consumer dashboard:

Non-SNP variation. Structural variants, copy number variations, mitochondrial DNA. Most consumer platforms skip these entirely or treat them as secondary.

Family-aware queries. If you’ve sequenced your parents too, you’re sitting on a trio — three genomes that can tell you which variants came from whom. Zero consumer platforms offer trio analysis.

Flexible evidence lookup. You read about a variant on a research paper. You want to know if you carry it, what ClinVar says, what the population frequency is across ancestry groups. The dashboard doesn’t have a “look up an arbitrary rsID” button.

The 23andMe bankruptcy in March 2025 made this concrete. Within 24 hours of the Chapter 11 filing, the site saw 1.5 million visits — a 526% spike. The data-deletion page got 376,000 hits on day one, 480,000 on day two. People realized their most personal dataset was sitting on someone else’s server, and they wanted it back.

The demand for local-first analysis isn’t hypothetical. It’s measured in deletion-page clicks.

What It Looks Like When You Close the Gap

The stack I’ve been running has three components:

GeneChat-MCP handles local VCF queries. It reads your variant files directly — nothing leaves your machine. You can ask about specific genes, scan for known pathogenic variants, compare inheritance patterns across family members.

OpenCRAVAT-MCP connects to cloud annotation databases, but only sends rsIDs (the public identifiers for known variants, like rs4988235 for lactase persistence). Your actual genotype stays local. What comes back: population frequencies, functional predictions, protein interaction data, regulatory annotations — the context that makes a variant meaningful.

Pomera handles session notes. When you’re working through complex queries, you want persistent context.

All three run inside your IDE through the Model Context Protocol. Antigravity, Codex, Claude Code — anything that supports MCP. You ask in natural language. The IDE routes the query to the right tool. You get grounded answers from your actual files.

No uploads. No subscription tiers. No waiting for a report.

Pixel art character relaxed at a clean IDE workspace with organized genome query results and a hovering robot companion
Same desk, different story. The data is organized, queryable, and entirely local.

Four Examples from a Real Genome

I’ve run all of these queries on real data. Full reports — with complete tables, source databases, and version stamps — are available in the examples directory on GitHub. Here’s what they found.

Drug Metabolism (Pharmacogenomics)

Query: “What are my CYP enzyme metabolizer phenotypes for drug metabolism?”

Gene Likely Phenotype Key Finding
CYP2C19 Normal Metabolizer No *2, *3, or *17 alleles
CYP2D6 Intermediate Metabolizer Heterozygous *4 carrier (*1/*4)
CYP2C9 Normal Metabolizer No *2 or *3 alleles
CYP3A5 Non-expresser (*3/*3) Common European genotype

CYP2D6 matters most here. It metabolizes roughly 25% of all prescribed drugs — codeine, tramadol, tamoxifen, many antidepressants, several beta-blockers. An intermediate metabolizer status means reduced enzyme activity. Codeine won’t convert to morphine as efficiently. Some antidepressants may need dose adjustments.

This is factual genotype data, not a prescription. But it’s exactly the kind of information worth discussing with a prescriber before they write a script for tramadol.

Over a hundred FDA drug labels reference pharmacogenomic biomarkers. Your prescriber may not know your metabolizer status. You can bring this information to them.

→ Full report: Pharmacogenomics Profile

Pixel art RPG potion shop reimagined as a pharmacogenomics lab with enzyme icons glowing green and amber
Four enzymes, four results. The amber one — CYP2D6 — processes a quarter of all prescribed drugs.

What You Inherited from Whom (Trio Analysis)

Query: “For these well-known variants, which parent did I inherit them from?”

Three VCF files. Three genomes. Standard trio logic: if you’re heterozygous and one parent carries the variant while the other doesn’t, you know which side it came from.

Variant Gene Inheritance
rs1801131 MTHFR (A1298C) Paternal — father is het, mother is wildtype
rs17822931 ABCC11 (earwax type) Maternal — mother is het, father is wildtype
rs4988235 MCM6 (lactase) Both parents — one allele from each
rs1050450 GPX1 (antioxidant) Not inherited — mother carries it, subject is wildtype

That last row is the one people don’t expect. Your parent carries a variant. You didn’t inherit it. That’s a question you can answer with trio analysis and literally nothing else available to consumers.

→ Full report: Trio Inheritance Analysis

Pixel art family of three around a table with glowing genome orbs and luminous inheritance threads connecting them
Three genomes, three orbs. The threads of light trace what was inherited — and what wasn’t.

ClinVar Genome Scan

Query: “Scan my genome for ClinVar pathogenic variants.”

ClinVar is NIH’s database of clinically relevant variants — the ones linked to diseases, drug responses, or other phenotypes. A full scan against 4.86 million variants found:

  • 100 pathogenic variants across 41 genes
  • 25 drug-response variants

That sounds alarming until you dig in. Most entries flagged as “pathogenic” have conflicting classifications. One lab calls it pathogenic, another calls it benign, a third says uncertain significance. The database captures this disagreement, which is actually valuable — it tells you where the science is still unsettled.

A handful of variants had consistent pathogenic classifications across multiple submitters. Those are worth reviewing with a genetic counselor. The rest are noise, or at least noise until more evidence accumulates.

→ Full report: ClinVar Variant Scan

Deep Variant Annotation (OpenCRAVAT)

Query: “Give me a deep annotation of rs4988235.”

This is where the rsID-only cloud query earns its keep. A single call — annotate_rsid("rs4988235") — returned over 150 annotation fields for the lactase persistence variant:

  • CADD score: Functional impact prediction
  • Population frequencies across seven ancestry groups: 60.2% in Europeans, 0.3% in East Asians (this variant enabled dairy farming in northern Europe — its geographic distribution tells a 10,000-year story)
  • 56 protein interactors: The broader molecular network
  • Regulatory element data: Where this variant sits in the genome’s control architecture

Getting this normally requires a bioinformatics pipeline — downloading databases, running annotation tools, parsing output formats. Here it’s one function call that sends only the rsID, not your genotype.

→ Full report: OpenCRAVAT Deep Annotation

Two additional reports — trait associations and polygenic risk scores — are available in the GitHub examples directory.

Clone, Configure, Query

What You Need

  • Your VCF file(s) from any whole-genome sequencing provider
  • An agentic IDE that supports MCP (Antigravity, Codex, Claude Code)
  • Python 3.10+ and conda (for GeneChat-MCP)
  • About 2GB of disk space for annotation databases (ClinVar, SnpEff, GWAS Catalog, PGS models)
  • Optional: an OpenCRAVAT cloud account (free tier) for deep annotation

Architecture

Your IDE (chat)
  Antigravity / Codex / Claude Code
       │                │
       ▼                ▼
  ┌──────────┐   ┌──────────────┐
  │ GeneChat │   │  OpenCRAVAT  │
  │   MCP    │   │     MCP      │
  │ (local)  │   │ (cloud API)  │
  └────┬─────┘   └──────┬───────┘
       │                │
       ▼                │ rsIDs only
  ┌──────────┐          │ (no genome data)
  │ Your VCF │          ▼
  │  files   │     CADD, REVEL,
  │ (local)  │     gnomAD, BioGRID
  └──────────┘

Setup

I’m not going to reproduce the README here. If you can configure an MCP server in your IDE, you can follow the instructions in the repo.

Clone it: github.com/matbanik/agentic-genomics

The setup has three pieces: GeneChat-MCP (local VCF querying), OpenCRAVAT-MCP (cloud annotation), and your IDE’s MCP configuration. The README walks through each one.

Two things that trip people up:

VCF indexing. Your VCF files need to be indexed with tabix before the first query. GeneChat expects .vcf.gz + .vcf.gz.tbi pairs. If the index is missing, queries will fail silently or throw cryptic errors. The repo documents this, but it’s the single most common setup issue.

Contig format mismatch. Some sequencing providers use chr1, chr2, chr3 prefixes. Others use bare 1, 2, 3. If your VCF uses one format and the reference databases expect the other, variant lookups will miss. The repo handles the conversion, but it’s worth knowing why a query might return “not found” when you know the variant is there.

Trio analysis? Same setup, more genomes. Register each family member’s VCF file and query across all of them.

What You Can Ask

These are natural-language prompts you can type directly into your IDE. The agent routes each one to the right MCP tool automatically.

What is my CYP2D6 metabolizer status?

Scan my genome for ClinVar pathogenic variants

Which of these variants did I inherit from my mother?

Calculate my BMI polygenic risk score

Give me a deep annotation of rs4988235

What GWAS associations exist for caffeine metabolism?

→ All six example reports on GitHub

The Landscape

You’re Not Alone

This space is moving fast. A few projects worth knowing about.

ClawBio came out of the UK AI Agent Hackathon at Imperial College. It’s a Python CLI and library — not an MCP server, so the architecture differs, but the goal overlaps. They’ve built two tools I haven’t seen elsewhere: gwas-lookup federates queries across nine GWAS databases simultaneously, and clinpgx pulls from PharmGKB, CPIC guidelines, and FDA label annotations in one call. Complementary work, different interface paradigm.

Academic signals are emerging too. A paper in Briefings in Bioinformatics formalized the MCPmed framework for medical AI agents. EMBL has BioContextAI in development. cBioPortal — the cancer genomics database — now has an MCP interface. IBM Research presented related work at ISMB.

On the open-source side: Bio-MCP provides general bioinformatics tool access, gget-mcp wraps the gget library for gene/protein queries, and IGV-MCP connects to the Integrative Genomics Viewer for visualization.

The pattern is clear. Genomic data is becoming queryable through conversational interfaces. The question is whether that happens on your machine or someone else’s.

What This Isn’t

I want to be direct about boundaries.

This does not diagnose disease. A ClinVar “pathogenic” flag is not a diagnosis — it’s a database entry reflecting submitted evidence, often with conflicting interpretations.

This does not recommend treatments. A CYP2D6 intermediate metabolizer status is a genotype fact. What to do about it is a clinical decision that depends on your full medical context, other medications, and your prescriber’s judgment.

This does not provide nutritional advice. Your MTHFR status does not tell you what supplements to take.

Star-allele calls — CYP2D6 *1/*4, CYP2C19 *1/*1 — are factual genotype data. They describe what variants you carry. Translating that into action requires a human with clinical training and your complete picture.

If you find something concerning, discuss it with a genetic counselor or your prescriber. That’s not a hedge; it’s how this works.

Source databases are named and versioned in each query output: PharmVar for star allele definitions, CPIC for genotype-phenotype mappings, ClinVar for clinical variant classifications, gnomAD for population frequencies.

Closing

Your genome is the most personal dataset you’ll ever own. Right now, most of it sits in a folder you’ve never opened — or on a server you can’t control.

The tools to change that are free, open, and run on your machine.

Pixel art character standing on a sunset hilltop holding a glowing genome shard, with a robot companion, turned away from a distant cloud fortress
Your data. Your machine. Your questions.

If you get stuck during setup, open an issue on GitHub. That’s what it’s there for.

Resources

⚠️ Disclaimer: This is a factual genotype report, not medical advice. Discuss actionable findings with your prescriber or genetic counselor. Source databases: PharmVar, CPIC, ClinVar, gnomAD — versions stated per query.

Originally published on matbanik.info. Cross-posted with ❤️ to Dev.to.

Zero to Full-Stack in 6 Months: The Izzy Way…

TL;DR

  • This post is adapted from my 2024 YouTube video, “Learned To Code In 6 Months!“. Since the original recording, I have evolved as a developer and expanded my technology stack beyond the initial scope discussed here.

  • Dedicated 6 months to studying full-stack development, Monday through Friday, 6:30 AM to 4:00 PM.

  • Managed distractions using the Pomodoro technique and physically removing my phone from the workspace.

  • Learned almost entirely via free YouTube tutorials, avoiding “tutorial hell” by actively building.

  • Built a complex first project (“Nissan Experience“) to force hands-on learning.

  • Leveraged AI (Gemini) as a mentor for concept breakdown, alongside real-world guidance from Dorian (@doriandevelops).

  • Maintained mental health through strict weekend breaks, attending NYC Code & Coffee meetups, and heavy weightlifting.

_

Greetings from the future. This is Izzy, writing to you from the year 2026. The following article is a time capsule detailing exactly how I learned full-stack development in just six months back in 2024.
_

My name is Izzy, and over the past six months, I executed a dedicated roadmap to learn full-stack development. This is not a heavily edited, polished fairy tale. It is an objective breakdown of my schedule, the technologies I adopted, the struggles I faced, and the strategies that kept me disciplined.

1. The Study Schedule and Discipline

Creating a routine puts discipline on autopilot. My standard study schedule was Monday through Friday, 6:30 AM to 4:00 PM. On the three days a week I went to the gym focusing heavily on progressive overload for my chest, shoulders, triceps, and legs, I concluded my study sessions at 3:00 PM.

Rigid Study Schedules

  • Pros: Automates decision-making; guarantees a high volume of deep work; sets clear boundaries between “work” and “life.”
  • Cons: Can induce severe guilt when life inevitably disrupts the routine; high risk of burnout if breaks are not strictly enforced.
  • Potential Pitfalls: Confusing “hours logged” with “actual learning.” Staring at a screen for 9 hours is useless without active coding.
  • Conclusion: A rigid schedule is necessary, but it must be paired with mandatory time off. I absolutely do not study on weekends, forcing myself to step away and reset.

2. Environment Architecture: Beating Distractions

To combat context switching, I removed my phone entirely, placing it across the room and relying on an Apple Watch strictly for critical alerts. For desktop distractions, I implemented the Pomodoro method using an always on top window timer.

The Pomodoro Technique

  • Pros: Forces regular breaks to prevent cognitive fatigue; gamifies deep work; prevents endless scrolling.
  • Cons: Can arbitrarily interrupt a deep “flow state” right when a complex bug is being solved.
  • Potential Pitfalls: Becoming a slave to the timer.
  • Conclusion: The structure is invaluable for maintaining focus over an 8-hour day, provided you allow flexibility when deep in the zone. Listening to synth-wave Lo-Fi music (like Lo-Fi Girl) also served to anchor my focus without the distraction of lyrics.

3. Financial Reality

Transparency is critical. I am able to study full-time because of an agreement with my wife. For three years, I financially supported us while she completed an intense physician assistant program. Once she secured her role as a PA, we flipped the script, allowing me to quit my job and pursue software engineering full-time.

4. Resource Allocation and “The Izzy Stack”

I learned 99.9% of my foundation through free YouTube tutorials, starting with HTML, CSS, and a comprehensive “50 Projects in 50 Days” vanilla JavaScript course.

Fast forward to 2026, my learning path solidified into what I proudly call…

The Izzy Stack:

  • Frontend: React, TypeScript, Tailwind.
  • Backend: Java, Spring Boot, PSQL, Supabase.

Relying on Free Video Tutorials

  • Pros: Zero financial barrier to entry; diverse range of instructors and teaching styles.
  • Cons: Information is often fragmented, outdated, or lacks enterprise-level best practices.
  • Potential Pitfalls: Falling into “Tutorial Hell,” where you passively consume content without writing your own code. If a 2-hour tutorial takes you exactly 2 hours to finish, you did not learn anything.
  • Conclusion: Tutorials are only valid if you constantly pause, break the code, and rebuild it yourself.

5. Mentorship and AI as a Learning Tool

Learning in a vacuum is dangerous. I reached out to Dorian (@DorianDevelops), who provided actionable, game-changing direction: stop doing tutorials, start building a full-stack project, and network immediately. I also utilized Gemini as a daily tutor to explain complex topics.

Using AI for Code Learning

  • Pros: On-demand, 24/7 explanations of granular concepts; excellent at breaking down documentation.
  • Cons: AI frequently hallucinates or suggests deprecated syntax; it can become a crutch.
  • Potential Pitfalls: Copying and pasting AI-generated code without understanding it, leading to a brittle codebase and failed technical interviews.
  • Conclusion: AI should be treated strictly as a mentor, not an employee. Never implement code you cannot personally explain from scratch.

6. Project-Driven Learning: The (“Nissan Experience“)

Following Dorian’s advice, I built a full-stack application as my first major project a tribute platform to Nissan vehicles featuring Stripe integration and a relational database.

Building a Full-Stack App as Project #1

  • Pros: Forces a holistic understanding of how APIs, databases, and client interfaces interact; generates a highly impressive resume asset.
  • Cons: The learning curve is brutal. Concepts like environment variables, Git workflows, and API security hit all at once.
  • Potential Pitfalls: Quitting out of sheer frustration when connecting the database or setting up authentication.
  • Conclusion: It took me three months and involved deep frustration, particularly with JavaScript logic, but the trial by fire approach accelerates true engineering comprehension better than building ten simple calculator apps.

7. Community and Next Steps

Coding in isolation breeds anxiety, especially given current market conditions. Finding a local community is non-negotiable. I consistently attend Code & Coffee in NYC, which has been instrumental in networking with data scientists, product managers, and fellow engineers.

Moving forward, I am limiting myself to three high-complexity projects this year, diving deeper into TypeScript, AWS, and eventually LeetCode. The market is tough for juniors, but the only controllable variable is the daily consistency of showing up.

🛠️ Let’s Connect!

PasteCheck v1.3 — what I improved after launching and getting real users

A few days ago I launched PasteCheck — a free tool
to paste code and see errors highlighted instantly.

After launch I found real gaps and shipped v1.3.
Here’s what changed:

Python indentation monitor
Now flags mixed tabs and spaces as an error, and
inconsistent indentation style across the file as
a warning.

CSS property validator
Embedded CSS inside HTML now checked for misspelled
properties. Catches things like colour instead of
color, font-weigh instead of font-weight —
with “did you mean X?” suggestions.

Aria-label support
Previously flagged inputs with aria-label or
aria-labelledby as unlabelled. Fixed — those are
now correctly recognised as labelled.

Language detection confidence UI
Short snippets under 5 lines now show a subtle ?
badge next to the detected language so users know
detection may be approximate.

All shipped in a single session, tested, live.

Try it: https://www.pastecheck.co.uk

What would you want to see in v2?

DeepSeek V4 on Huawei’s Ascend 950: A Real Stress Test for China’s AI Chip Ecosystem

In April 2026, DeepSeek released V4 — a 1.6 trillion parameter MoE model — and for the first time, the technical report listed Huawei’s Ascend NPU alongside NVIDIA in its validated hardware list. This is the story of what that means for the actual supply chain, the bottlenecks that remain, and where this is heading.

The Validation That Changed Everything

When DeepSeek released V4 on April 24, 2026, most of the attention went to the model’s benchmark scores matching GPT-5 and Claude Opus. But a quieter — and arguably more consequential — event was buried in the fine print:

DeepSeek V4 is the first top-tier model to fully validate inference on Huawei’s Ascend 950PR chip.

This wasn’t a “it compiles” checkbox validation. The DeepSeek team:

  • Rewrote 200+ core CUDA operators for Huawei’s CANN Next framework
  • Ran 100,000+ test cases for precision alignment
  • Invested approximately 30 person-years of engineering effort
  • Delayed the product launch by over 2 months specifically to complete the port
  • The initial port ran at 1/35th of target performance — they optimized back to parity

The results on the 950PR are genuine:

Metric Improvement vs. NVIDIA H20
FP4 compute 2.87x faster (1.56 PFLOPS vs ~0.5)
MoE inference speed 1.5-1.73x (general), up to 1.96x (RL rollout)
Multi-modal generation +60% faster
HBM capacity 112GB vs 96GB

A caveat: the H20 is NVIDIA’s “China special” — deliberately crippled by export controls. This doesn’t mean 950PR beats H100 or B200. But it does mean that for inference workloads on Chinese soil, domestic hardware is now a credible alternative, not a consolation prize.

The Chip That Does It: Ascend 950’s Dual-Architecture Strategy

What makes the 950 interesting isn’t just the raw spec sheet — it’s the architectural cleverness. Huawei realized that LLM inference has two fundamentally different phases, and designed two separate chips sharing the same die:

Variant Phase Memory Bandwidth Shipping
950PR Prefill + Recommendation HiBL 1.0 (128GB) 1.6 TB/s ✅ Now (mass production since March 2026)
950DT Decode + Training HiZQ 2.0 (144GB) 4 TB/s Q4 2026

Prefill (reading the entire input + computing KV cache) is compute-bound — it needs raw FLOPs, not memory bandwidth. A cheaper HBM works fine. Decode (generating one token at a time) is memory-bandwidth-bound — the bottleneck is how fast you can feed weights to the compute units. Here, 4 TB/s bandwidth makes a real difference.

The 950DT’s 4 TB/s HiZQ 2.0 memory puts it in the same league as NVIDIA’s H200 (141GB / 4.8 TB/s). It won’t be available until Q4 2026, but that’s when the training-side gap starts to close.

The Self-Developed HBM Bet

HBM accounts for roughly 50% of an AI chip’s cost. Huawei’s decision to develop its own HBM — HiBL (low-cost/Budget Line) and HiZQ (high-performance) — isn’t just about supply chain security. It enables customization that off-the-shelf HBM can’t provide.

The local HBM supply chain is making real progress:

Milestone Status Timeline
CXMT (长鑫存储) HBM3 samples delivered to Huawei ✅ Done
CXMT Shanghai packaging fab 🟡 Construction End of 2026
CXMT HBM3E development 🟡 In progress Target 2027
CXMT HBM3 mass production ❌ Not started — no volume orders yet Delayed

The bottleneck: CXMT’s HBM3 is still in testing. Raw materials only support sample runs, not mass production. The Huawei alliance is also working with Fujian Jinhua (福建晋华) and Wuhan Xinxin (武汉新芯) as secondary foundries, but these are supplementary capacity, not primary sources.

The pragmatic reality: HiBL 1.0 and HiZQ 2.0 are likely “self-developed” at the packaging and controller level, not at the DRAM die level. Huawei takes available DRAM dies, packages them with proprietary 2.5D stacking, and adds custom controllers. This is why HiBL 1.0’s 1.6 TB/s bandwidth is achievable — it’s bounded by the dies they can source, not by their design ambition.

The Five Bottlenecks That Limit Delivery

HBM gets the headlines, but it’s not the only constraint. Here are all five, ranked by severity:

🔴 1. Advanced Manufacturing (SMIC)

The hardest bottleneck. SMIC’s N+2 (equivalent to 7nm, using DUV multipatterning since EUV is unavailable) has a monthly capacity of approximately 35,000-38,000 12-inch wafers. At ~92% yield, that translates to roughly 750,000 Ascend 950 chips per year.

750K sounds like a lot, but it serves the entire Chinese AI market. NVIDIA ships millions of H100/B200 units annually. The capacity gap is orders of magnitude.

SMIC plans to double capacity to 70,000 wafers/month during 2026, but without EUV, each generation becomes exponentially harder. The 950DT uses the same N+2 process. The absolute ceiling of domestic advanced manufacturing will remain the binding constraint through at least 2028.

🟡 2. Advanced Packaging

Ascend 950 requires 2.5D Chiplet packaging (2 compute dies + 2 I/O dies + HBM). This isn’t a “nice to have” — without it, you can’t assemble the chip.

Supplier Status
JCET (长电科技) — Dongguan HBM base Running at full capacity
Tongfu Micro (通富微电) — SJ1/SJ lines Fully loaded, emergency expansion
QuLiang Electronics (渠梁电子) Accelerating expansion

Packaging capacity is the tightest short-term bottleneck. New capacity from JCET and Tongfu’s expansion won’t meaningfully add supply until 2027. This is why “advanced packaging stocks” are the hottest semiconductor theme on China’s A-share market in 2026.

🟡 3. Interconnect: Making 8,192 Cards Work as One Computer

The Atlas 950 SuperNode (8,192 cards, 160 cabinets, 1,000 square meters) requires a new interconnect protocol — Lingqu 2.0 / UnifiedBus. The predecessor (Lingqu 1.0) was validated on 384-card Atlas 900 systems (300+ deployed). Scaling from 384 to 8,192 is a leap in complexity:

  • Full optical interconnect between cabinets
  • 16 PB/s total bandwidth (10x global internet peak traffic)
  • All-liquid cooling at MW-scale per cluster

This is a 2026 Q4 delivery. The engineering risk is real, but Huawei’s track record with Lingqu 1.0 (proven at scale) suggests this is a schedule risk, not a technology risk.

🟢 4. Software Ecosystem (CANN)

CANN was fully open-sourced in December 2025. DeepSeek V4’s successful port is the single biggest validation event to date. But the developer count gap is stark: ~87,000 CANN developers vs. ~3 million CUDA developers.

Huawei’s strategy is “CUDA-to-CANN automated conversion tools” combined with PyTorch compatibility layers. This works for standard model architectures. Edge cases still require manual operator rewriting — the same 30 person-years of work that DeepSeek invested.

For large enterprises with dedicated ML teams, this is doable. For smaller teams, it’s a barrier.

🟢 5. Cooling and Power

Per-chip TDP is ~310W. At supernode scale, total power draw is in megawatts. Full liquid cooling is mandatory, and green power alignment adds infrastructure complexity. This is solvable — the technology exists — but deployment speed varies across data center operators.

The Long-Term Outlook: An Honest Assessment

The trajectory is real

Huawei has a clear 3-generation roadmap:

Generation FP8 FP4 Memory BW Expected
950 (PR + DT) 1 PFLOPS 2 PFLOPS 4 TB/s 2026
960 2 PFLOPS 4 PFLOPS ~8 TB/s Q4 2027
970 4 PFLOPS 8 PFLOPS ~12-16 TB/s Q4 2028

Each generation roughly doubles specs. Revenue hit $12 billion in 2026 (up 60% from $7.5B in 2025). The business is scaling.

But it needs context

Dimension 2026 Reality 2028 Target
Supply-demand gap 🔴 Severe (750K chips vs 2-3x demand) 🟡 Improving but not balanced
Performance vs. NVIDIA 🟡 950PR ≈ H200 (which is last gen for NVIDIA) 🟡 960 ≈ 70-80% of 2027 NVIDIA
Process node 🔴 7nm (no EUV) 🔴 Still 7nm — Chiplet mitigates but can’t eliminate
Market share (China inference) ~20% 40-50% (projected)

The honest assessment: Ascend will not “catch up” to NVIDIA in absolute terms. The process gap (7nm DUV vs 3nm EUV+) is physical and cannot be willed away. But it doesn’t need to catch up. The Chinese AI chip market is structurally bifurcating:

Ascend takes ~50% of domestic demand + NVIDIA holds the high end through H20 and smuggled/cloud-accessible H100 + Other domestic players (Cambricon, Moore Threads, Biren) split the remainder

For anyone building AI products for the Chinese market: this is not a question of “whether to switch.” It’s “when to switch.” For anyone building for global markets: unaffected — continue with CUDA.

Two technology worlds are solidifying: CUDA World and CANN World.

What DeepSeek V4 on Ascend 950 really proved

Before April 2026, Huawei could say “our chips work.” After April 2026, DeepSeek proved it with a 1.6T-parameter model, real production traffic, and actual cost numbers. The credibility gap is closed.

The remaining bottlenecks are all physical or temporal: more chips, more packaging lines, more fab capacity, more time for the ecosystem to mature. None of these have a quick fix. But they also don’t depend on any single breakthrough — they’re a production scaling problem, and production scaling responds to money and time.

China’s AI chip ecosystem just passed its most important stress test. The bottlenecks that remain are hard, but they’re the kind of hard that follows linear progress curves — not the binary win/lose of “can this even work.”

How Strong Is “Strong”? Password Entropy in Plain English

Every password generator tells you the password is “strong.” Very few tell you how strong, or what that actually means in practice.

The answer is entropy — measured in bits.

What entropy bits mean

Entropy is the number of equally likely possibilities an attacker has to try. The formula:

entropy = log2(pool_size ^ length) = length × log2(pool_size)

A password drawn from 72 characters (lowercase + uppercase + digits + common symbols) at 12 characters long has:

12 × log2(72) = 12 × 6.17 = ~74 bits

74 bits means 2^74 possible passwords — about 18 quadrillion. At a billion guesses per second (a fast GPU cracker), that’s ~585 years.

How length beats complexity

Most people think adding symbols makes a password strong. Length does more:

Password Entropy
P@ssw0rd (8 chars, mixed) ~52 bits
correcthorsebatterystaple (25 lowercase) ~117 bits
kR#9mXqL2p!v (12 chars, full set) ~74 bits

A 25-character lowercase passphrase is stronger than a 12-character mixed-case symbol-heavy password. Length compounds.

The minimum worth using in 2026

  • Account passwords: 16+ characters, mixed set, generated randomly (not chosen by you)
  • API keys / secrets: 32+ characters, full character set
  • Passphrases: 4+ random words from a large wordlist (diceware-style)

The rule of thumb: anything under 60 bits is crackable with modern hardware in a reasonable timeframe.

QTNest’s password generator lets you set length, character set, and quantity — and shows entropy for each generated password.

  • Passphrases: 4+ random words from a large wordlist (diceware-style)

The rule of thumb: anything under 60 bits is crackable with modern hardware in a reasonable timeframe.

QTNest’s password generator lets you set length, character set, and quantity — and shows entropy for each generated password.

Precision Mechatronics: Mitigating Step-Pulse Resonance and Thermal Dissipation in Micro-Stepping Hardware Controllers

When building automated kinetic maintenance hardware for delicate micro-mechanical systems (like luxury watch calibers), most engineers stop at the basic motor loop. They spin up a generic permanent magnet DC motor or a cheap stepper running on a continuous digitalWrite() delay toggle.

However, when you audit the physical implementation under an oscilloscope and a thermal imaging camera, two critical systemic failures emerge: low-frequency step-pulse resonance and localized thermal dissipation (Joule heating).

If your micro-controller is outputting un-calibrated, jagged square waves, you aren’t just rotating a mechanical weight; you are inducing micro-vibrations that travel directly up the balance pivots, potentially magnifying mainspring bridle friction and accelerating component fatigue.

Here is how we can resolve these electromagnetic and mechanical bottlenecks at the firmware and driver level.

  1. The Bottleneck: Harmonic Resonance & Chopper Noise
    Standard full-step or half-step drive profiles deliver discrete, aggressive current injections to the motor coils. This square-wave switching profile creates severe current ripple, leading to rotor overshoot and harmonic oscillations. In mechanical timekeeping maintenance, these structural micro-shocks act as a destructive energy transfer pattern.

To achieve fluid, analog-like kinetic sweeping, we must bypass basic PWM blocks and implement a 1/32 Micro-stepping Sinusoidal Wave Matrix. By blending the Phase A and Phase B coil currents into a shifting sine/cosine vector, the rotor steps smoothly across intermediate magnetic poles.

[Full-Step Profile] –> [Square Wave Input] –> Aggressive Rotor Snapping –> Mechanical Shock
[1/32 Micro-stepping] –> [Sinusoidal Matrix] –> Fluid Continuous Sweeping –> Zero Resonance
However, running micro-stepping at high frequencies introduces another problem: current decay. During the “off” time of the PWM cycle, the back-electromotive force (back-EMF) from the motor coils must be dissipated. If your driver is locked in a static “Fast Decay” or “Slow Decay” mode, you will experience either severe current distortion or excessive audible whine.

By deploying advanced smart-chopping hardware architectures (like SpreadCycle or StealthChop topologies), we dynamically mix fast and slow decay cycles on the fly, ensuring a perfect sinusoidal current profile regardless of internal coil impedance variations.

  1. The Code: Deterministic ISR Timer Arrays
    To feed a constant, jitter-free pulse train to your stepper drivers without blocking your main application loop (which might be handling Wi-Fi handshakes, OLED rendering, or JSON API requests), you must rely on deterministic hardware timers and Interrupt Service Routines (ISRs).

The following production-grade C architecture demonstrates how to map specific mechanical target profiles—derived from an analytical data matrix—into localized, non-blocking step-pulse timing calculations:

  1. The Thermal Constraint: Protecting the Mainspring CoreWhen running micro-stepping loops inside enclosed spaces, Joule heating ($P = I^2R$) is your absolute worst enemy. Traditional stepper drivers feed full operating current to the motor phases even when the system is completely stationary.This static current generates localized ambient heat signatures that can easily spike the internal environment by 10°C to 15°C.For an analog timekeeping instrument, localized thermal escalation causes immediate changes in the viscosity of the micro-lubricants (such as Moebius 9010/HP1300 synthetics) applied to the gear train and escapement. The oil thins out, migrates away from critical friction points, and causes accelerated component wear.To counter this, your firmware must execute Dynamic Current Scaling:Acceleration Phase: Peak current allocation ($I_{run}$) to overcome rotor inertia.Cruising Phase: Nominal current allocation ($I_{cruise} approx 70%$) to maintain stable step vectors.Standby Phase: Dropping static current to a baseline holding threshold ($I_{hold} approx 10% – 20%$) during relaxation windows, entirely eliminating thermal dissipation.For engineers and material scientists looking to cross-examine our empirical thermal imaging logs, flux-gate sensor tracking data, and structural magnetic shield attenuation curves under extreme desk environments, you can audit our comprehensive open engineering white paper:Data Repository: Anti-Magnetic Engineering & Thermal Attenuation Brief PDF4. Hardware RealizationIf you prefer to deploy an out-of-the-box physical setup that already implements these multi-phase deterministic timers, dynamic current scaling, and high-permeability Mu-metal shielding cages natively within a premium chassis, we designed a desktop dual-chamber isolation vault executing these exact parameters.You can explore our full industrial architecture blueprints, circuit routing layouts, and chassis build specifications here:Hardware Specification: AuraWinder Double Watch Winder Engineering SeriesHow are you handling motor vibration and thermal isolation in your current hardware builds? Are you using dedicated motion co-processors or running timing calculations directly on the main MCU? Drop your thoughts, PCB schematics, or register configurations in the comments below!

A Fact A Day, an autonomous Podcast as my entry 4 Hermes Agent Challenge

This is a submission for the Hermes Agent Challenge

What I Built

“A Fact A Day” is an autonomous, daily educational podcast that summarizes current scientific and technological breakthroughs in approximately 60 seconds.

The project solves a concrete problem: knowledge is everywhere, but hard to consume. People who want to stay up-to-date don’t have time to read long articles or listen to hour-long podcasts. A Fact A Day delivers exactly one piece of information per day — well-researched, well-told, in one minute.

The special feature: The entire production process is automated and runs once daily via cron job:

  1. Research current breakthroughs from trusted sources (MIT Tech Review, Ars Technica, Wired, NYT Tech)
  2. AI-assisted script creation with strict structure (strong hook → information → open question)
  3. Speech synthesis with Stepfun TTS (female voice, excited emotion)
  4. Automatic audio editing and merging into a finished MP3
  5. Publication on the archive page

Demo

https://www.redpandamonium.de/afactaday/

Code

https://github.com/tkdmatze/afactaday/

My Tech Stack

  • Hermes Agent – orchestration, cron job, skill system, Telegram integration
  • Stepfun API – LLM (step) for script creation, TTS (stepaudio-2.5-tts) for speech synthesis
  • Python – main script (generate_lecture.py) with requests, pydub, feedparser
  • defuddle-cli – token-efficient HTML extraction from news sources
  • ffmpeg – merging TTS audio snippets into a seamless MP3
  • Astro – archive page (https://www.redpandamonium.de/afactaday/)
  • nginx + rsync – deployment to my own servers
  • Telegram Bot API – delivery of the finished episode

How I Used Hermes Agent

Hermes Agent is the backbone of the entire system — without it, the podcast wouldn’t work.

  1. Skill System as Reusable Building Blocks

The entire production process is implemented as a Skill (aura-lectures) that can be easily called and configured:

hermes run /aura-lectures --topic "quantum computing"

The skill encapsulates all dependencies: RSS feeds, HTML extraction, LLM prompting, TTS synthesis, audio merging. This makes the code maintainable and extensible.

  1. Cron Job for Full Automation

Via cronjob(action=’create’), the skill is automatically executed every weekday at 08:30 UTC — without human intervention:

Registered in Hermes Cron
ID: XXXXXX
Schedule: 30 8 * * 1-5 (weekdays)
Command: hermes run /aura-lectures
Delivery: Telegram channel

  1. Terminal Tools for the Processing Chain

The Python script uses subprocess calls for external tools:

  • feedparser to collect RSS feeds
  • defuddle-cli to clean HTML (retains only actual article content)
  • ffmpeg to merge TTS audio snippets
  • requests for all API calls (Stepfun Chat + TTS)

noteworthy

What makes this experiment/project special for me is how much Hermes Agent handles completely autonomously in the background — things that typically require explicit setup, configuration, or manual steps. May that was, because i did experiments before, and so hermes knows exactly where to store files, which are my preferred libraries and which design to use.

  • Skill scaffolding: Hermes created the complete skill structure (SKILL.md, scripts/generate_lecture.py) — no manual file creation needed.
  • RSS feed management: The skill automatically discovers and parses multiple RSS feeds (MIT Tech Review, Ars Technica, Wired, NYT Tech), extracts article URLs, and fetches full content — all without any external RSS-to-email service.
  • HTML cleaning: Using defuddle-cli, Hermes automatically strips headers, footers, and navigation from article pages, reducing token consumption by 5–10× before sending to the LLM — a step most podcast automations skip entirely.
  • Script quality enforcement: The LLM prompt is automatically structured to guarantee the exact format: strong hook → information → open-ended question, with strict word count (120 ± 15 words). No post-processing or manual editing required.
  • TTS chunking: When the generated script exceeds Stepfun’s 1000-character limit per request, Hermes automatically splits it at sentence boundaries, synthesizes each chunk separately, and merges them back together — transparently.
  • Audio normalization: The tts_synthesize function handles rate limiting, retries, and temp file cleanup automatically. The final MP3 is produced with optimal bitrate (-q:a 2) via ffmpeg without manual ffmpeg command crafting.
  • Telegram delivery: The finished MP3 is sent as a native voice message.
    — Hermes detects the file path and handles the upload automatically, no bot token management or manual API calls needed.
  • Archive page generation: After each episode, Hermes can automatically copy the MP3 to the server’s audio folder and update the Astro page’s episode list — the entire “publish to website” step is fully automatable.
  • Cron job registration: The recurring job is created with a single cronjob(action=’create’) call, complete with natural-language description, weekday schedule, and Telegram delivery target — no crontab editing or systemd service files.
  • Memory persistence: All decisions are stored in MEMORY.md and USER.md, so the skill works immediately on any new Hermes instance without re-running setup steps.

In short: from “run the skill” to “episode delivered and published” — zero manual intervention. The entire pipeline is self-contained, robust, and fully documented within the skill system.