Nous Ergon: Building an Autonomous Alpha Engine with AI

Originally published at https://nousergon.ai/blog/posts/building-autonomous-alpha-engine/

The Thesis

Can AI generate sustained market alpha — not through a single model making predictions, but through a system of specialized components, each contributing what it does best?

That’s the question behind Nous Ergon: Alpha Engine (νοῦς ἔργον — “intelligence at work”), a fully autonomous trading system I’ve been building that combines AI-driven research, quantitative prediction, and rule-based execution. Quantitative finance — using mathematical models and statistical analysis to make investment decisions — has traditionally been the domain of institutional hedge funds with massive engineering teams. Large language models and modern machine learning tooling are changing that equation.

The system’s north star is alpha — the difference between the portfolio’s return and the S&P 500 (SPY):

Alpha = Portfolio Return − SPY Return

Positive alpha means you’re doing something the market isn’t already pricing in. Everything in Nous Ergon — every agent prompt, every feature, every risk rule — exists to find, validate, and capture that edge.

Why Not Just Ask an LLM to Trade?

The naive approach is tempting: give a large language model (LLM) market data and ask it what to buy. But LLMs are probabilistic text generators. They excel at synthesis, judgment, and reasoning across unstructured information. They’re terrible at precise numerical prediction, risk management, and consistent execution.

Nous Ergon splits the problem into three layers, each matched to the right tool:

Layer Tool Why
Research LLM agents (Claude) Judgment over unstructured data — news, analyst reports, macro context
Prediction Machine learning (ML) ensemble (starting with LightGBM) Pattern recognition over structured numerical features
Execution Deterministic rules Hard risk constraints that never get creative

LLMs reason about why a stock might move. ML models find patterns in how stocks actually move. And risk rules ensure the system survives long enough for the other two to matter.

A key design decision: LLM agents are used only in the Research module. This deliberate separation means the Predictor, Executor, and Backtester can run unlimited simulations, parameter sweeps, and backtests without making a single LLM API call. When you’re iterating on model features or testing risk parameters, that cost decoupling matters.

The Five Modules

Nous Ergon runs as five modules on AWS, connected through a shared S3 bucket. Each module has a single job, reads its inputs from S3, and writes its outputs back. There’s no shared state beyond the bucket — no databases to coordinate, no APIs to call between services.

1. Research

Five LLM agents orchestrated by LangGraph maintain rolling investment theses on ~20 tracked stocks and scan ~900 S&P 500 and S&P 400 tickers weekly for the top buy candidates. A quantitative filter first reduces the ~900 universe to ~50 candidates using volume, price, and momentum screens — no LLM calls. From there, a ranking agent (Sonnet) compares all ~50 candidates in a single cross-stock evaluation and selects the top ~35. Then two per-ticker agents — news sentiment and analyst research — each run independently on every candidate and population stock (Haiku), producing the sub-scores that feed into the final composite. A macro agent (Sonnet) assesses the broader market environment and sector conditions. A consolidator agent (Sonnet) synthesizes all analyses into a research brief.

Research outputs a composite attractiveness score (0–100) per ticker, combining news sentiment (50%) and analyst research (50%), with per-sector macro adjustments. The resulting signals.json — written to Amazon S3 — is the system’s primary input for everything downstream.

Research focuses entirely on fundamental attractiveness over a 6–12 month horizon. Technical analysis is deliberately excluded from the composite score. This is the first half of what I call horizon separation — Research answers “is this a good stock?”, not “is now the right time to buy it?”

2. Predictor

The Predictor handles the second half of horizon separation: short-term technical timing. Research may identify a stock as fundamentally attractive over the next 6–12 months, but that doesn’t mean today is the right day to enter. Each trading day, the Predictor evaluates the population’s near-term momentum using engineered features across technical indicators, macro context, volume analysis, and cross-sectional measures. Its veto gate can override a BUY signal from Research when the model predicts DOWN with high confidence — preventing the system from entering a fundamentally sound position at a technically poor time.

The current implementation uses a LightGBM gradient-boosted machine (GBM) model, but the architecture is designed for an ensemble of ML and deep learning algorithms. The plan is to layer additional models — likely including a neural network — and combine their predictions through confidence-weighted voting. LightGBM is a strong starting point: it handles threshold interactions and missing data well, trains fast, and provides interpretable feature importance. As the system matures, adding models that capture different types of patterns (non-linear interactions, sequential dependencies) should improve prediction quality.

The model trains on sector-neutral labels — stock returns minus sector exchange-traded fund (ETF) returns — isolating stock-specific signal from sector momentum. Weekly retraining uses 10 years of price history. New model weights only promote to production if they pass an Information Coefficient (IC) gate. IC measures the rank correlation between predicted and actual returns — in financial ML, an IC of 0.03–0.05 is considered meaningful because even small persistent edges compound significantly when applied across many positions over time. The current validation gate requires IC > 0.03.

3. Executor

Once the Predictor clears a position for entry, the Executor takes over. It reads signals and predictions from S3, applies hard risk rules, sizes the position, and executes market orders on Interactive Brokers. From that point forward, the Executor owns the position — managing it through a set of deterministic rules until exit.

Risk management is graduated, not binary. A drawdown response system scales position sizing through tiers: full sizing in normal conditions, reduced sizing as drawdowns deepen, and a complete halt at -8%. Additional constraints cap individual positions (5% of net asset value (NAV), 2.5% in bear markets), sector concentration (25% NAV), and total equity exposure (90% NAV).

Exit management combines ATR-based trailing stops (volatility-adaptive) with time-decay rules that progressively tighten stops as positions age, forcing the system to either prove a thesis quickly or move on.

The Executor’s rules are simple by design — but they aren’t arbitrary. They’re the output of the Backtester’s systematic optimization (more on this below). The Executor doesn’t reason or predict; it applies its parameters exactly as given, every time, with no emotional second-guessing. The intelligence lives in the process that produces those parameters, not in the component that executes them. This gives you consistent, repeatable execution while the learning happens offline where you can run thousands of simulations cheaply.

4. Backtester

The system’s learning mechanism. Runs weekly to validate the entire pipeline end-to-end — not just “did we make money?” but “are our signals predictive, which components drive that predictiveness, and what execution parameters maximize risk-adjusted returns?”

The Backtester does this through several layers of analysis:

  • Signal quality: are Research scores actually predictive? What percentage of BUY signals beat SPY at 10 and 30 days? Are higher scores more predictive than lower ones?
  • Attribution: which sub-scores (news vs research) correlate with outperformance? This determines where the scoring formula’s weight should shift.
  • Weight optimization: adjusts the Research scoring weights based on attribution results — conservatively, with a 30/70 blend of data-driven recommendations against current weights and a 15% max change per weight.
  • Executor parameter optimization: a parameter sweep across executor parameters — minimum entry score, position size limits, Average True Range (ATR) trailing stop multipliers, time-decay windows — replaying historical signals through the full executor simulation for each combination and ranking by Sharpe ratio. Random sampling (Bergstra & Bengio 2012) replaces exhaustive grid search: the number of trials auto-scales as a percentage of the total parameter space, with a statistical floor that guarantees a 95% probability of finding a top-5% combination regardless of grid size. The best-performing parameters get recommended for production.
  • Predictor threshold calibration: sweeps the veto gate’s confidence threshold across seven levels, measuring the trade-off between precision (correctly blocked losing trades) and missed alpha (incorrectly blocked winners).

Each optimization has guardrails — minimum sample sizes, minimum improvement thresholds, excluded parameters (the drawdown circuit breaker is never auto-tuned) — to prevent overfitting to noise.

The results flow back through S3: updated scoring weights for Research, optimized parameters for the Executor, and calibrated thresholds for the Predictor. Without the Backtester, the system operates blind. This is the component that turns a static pipeline into an adaptive one.

5. Dashboard

A Streamlit application providing read-only visibility into the full system: portfolio performance vs SPY, signal quality trends, per-ticker research timelines, backtester results, and predictor metrics. The operational cockpit.

How It All Connects

The modules run on AWS in two cadences — a daily trading loop and a weekly optimization cycle — with S3 as the sole communication bus:

Daily Cadence (Mon–Fri)

  • Predictor (6:15 AM PT) — reads latest signals.json from S3
  • Executor (6:30 AM PT) — reads predictions, trades on Interactive Brokers
  • EOD Reconcile (1:05 PM PT) — captures NAV, computes daily return and alpha, sends email

Weekly Cadence (Sunday/Monday)

  • Research — scans 900 tickers, rotates population, outputs signals.json
  • Predictor Training — retrains on 10y history, promotes weights if IC > 0.03
  • Backtester — signal quality analysis, weight optimization, parameter sweeps

Always-On

  • Dashboard (Streamlit) — read-only monitoring of all modules via S3

Research runs weekly to refresh the tracked population and generate updated signals.json. During the daily trading loop, the Predictor reads the latest signals from S3 and the Executor reads the Predictor’s output. Each module’s output is the next module’s input, and S3 acts as the contract between them.

S3 as the communication bus means any module can be replaced, rewritten, or tested independently. The Research module doesn’t know or care that a LightGBM model reads its signals. The Executor doesn’t know that five LLM agents generated the scores it’s acting on. They agree on a JSON schema, and S3 handles the rest.

The Feedback Loop

The most important architectural decision wasn’t any individual module — it was connecting the Backtester’s output back to the upstream components.

Every week, the Backtester measures whether the system’s signals actually worked. It tracks the percentage of BUY signals that beat SPY over 10 and 30 days, runs attribution analysis to determine which scoring components are pulling their weight, and recommends adjustments.

These recommendations flow back through S3: updated scoring weights that Research loads on its next run, optimized parameters that the Executor reads on cold-start, and calibrated thresholds that the Predictor uses for its veto gate. The system observes its own performance and adapts — slowly, conservatively, with guardrails — but it adapts.

This is what separates Nous Ergon from a static trading bot. Most automated trading systems are write-once: you build a strategy, deploy it, and hope it keeps working. Nous Ergon is designed to be fully autonomous — no human in the trading loop, no manual approvals, no daily oversight required. It researches, predicts, trades, measures, and adjusts on its own.

Where Things Stand

The infrastructure is built. All five modules are deployed on AWS, wired end-to-end, and running against live market data on Interactive Brokers paper trading. Research refreshes signals weekly. The Predictor and Executor run autonomously every trading day — Predictor scores the latest signals, Executor places trades, end-of-day (EOD) reconciliation measures performance.

Now comes the hard part: making it actually generate alpha.

The system hasn’t yet demonstrated sustained outperformance against SPY. That’s the work ahead — refining signal quality, tuning the ML models, calibrating risk parameters, expanding the prediction ensemble, and iterating on the scoring weights until the system finds edges that persist. Once the system demonstrates that ability consistently in paper trading, the plan is to transition to real capital in small amounts.

Building the infrastructure was the engineering challenge. Generating alpha is the research challenge. This is where it gets interesting.

Areas for Further Development

Beyond refining the core system, there are several directions that could meaningfully expand Nous Ergon’s capabilities:

Prediction Ensemble. The Predictor currently runs a single LightGBM model. The near-term goal is to build out an ensemble with additional ML and deep learning architectures. Options like Temporal Fusion Transformers (TFT) are compelling for their ability to model time-varying relationships, but may be cost-prohibitive at this stage — both in compute for training and in the engineering effort to deploy on Lambda. As the system generates alpha, there will be opportunities to invest in stronger deep neural network (DNN) architectures and higher-quality data APIs that aren’t justifiable today.

Retrieval-Augmented Generation (RAG). Research agents currently see fresh data each run plus the last thesis snapshot, but have no persistent memory of historical patterns — past earnings surprises, sector rotation cycles, how a stock behaved during previous rate hike environments. A RAG layer could let agents retrieve relevant historical context during their analysis, producing more informed research.

MCP Tool Use. Currently, the pipeline pre-fetches data and passes it to agents for analysis. With Model Context Protocol (MCP), agents could query data sources on demand — pulling specific SEC filings, checking real-time options flow, or querying alternative data — as part of their reasoning process rather than being limited to a pre-determined data scope.

Social Sentiment. Financial Twitter/X surfaces market-moving information — earnings reactions, sector rotation narratives, retail sentiment — often faster than traditional news sources. Integrating social sentiment as an additional signal source for Research could expand the system’s information surface area.

Expanded Sources and Features. Both Research and Predictor have room to grow their input data. Research could incorporate earnings call transcripts, insider trading filings, or institutional flow data. The Predictor’s feature set could expand with alternative data sources — options market signals, credit spreads, or cross-asset correlations — that may carry predictive information the current 29 features don’t capture.

Each of these represents both a system improvement and a meaningful engineering challenge. The modular architecture — S3 contracts between independent modules — means any of them can be pursued without disrupting the rest of the system.

How to Deploy Dokploy on an Ubuntu Server and Create Your First “Hello World” in Node.js

Architecture

Introduction

Dokploy is a self-hosted deployment platform that simplifies containerized application management using Docker Swarm. In this comprehensive tutorial, I’ll show you how to install Dokploy on an Ubuntu server, configure security with firewalls, deploy your first Node.js application, and follow production best practices.

Architecture

Diagram created with https://savnet.co

What You’ll Need

Before starting, make sure you have:

  • An Ubuntu 24.04 LTS server (minimum 2 GB RAM for testing)
  • SSH access with configured keys
  • A domain or subdomain (optional but recommended for HTTPS)
  • Basic terminal and Docker knowledge

Step 1: Prepare Your Ubuntu Server

If you already have an Ubuntu 24.04 LTS server with SSH access, you can skip this step. If you need to create one, follow these recommendations:

  1. Choose a cloud or VPS provider that offers Ubuntu 24.04 LTS
  2. Select a plan with at least 2 GB of RAM for testing
  3. Configure SSH key access instead of password (more secure)
  4. Consider enabling backups and monitoring for production
  5. Note the public IP of your server

If your provider has an integrated firewall (like DigitalOcean Cloud Firewall, AWS Security Groups, etc.), configure the necessary rules from the control panel.

New Server DigitalOcean

Step 2: Initial Server Configuration

Connect to the server via SSH:

ssh root@YOUR_PUBLIC_IP

Update system and install utilities

apt update && apt upgrade -y
apt install -y curl wget git ufw ca-certificates gnupg lsb-release

Create administrator user (recommended)

adduser deploy
usermod -aG sudo deploy
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy

Now you can connect with the new user:

ssh deploy@YOUR_PUBLIC_IP

Step 3: Configure Firewall with UFW

Dokploy needs specific ports open. Configure UFW properly:

# Allow SSH first to avoid locking ourselves out
sudo ufw allow OpenSSH

# Open ports needed for Dokploy and applications
sudo ufw allow 80/tcp     # HTTP for Traefik
sudo ufw allow 443/tcp    # HTTPS for Traefik
sudo ufw allow 443/udp    # HTTPS UDP for Traefik
sudo ufw allow 3000/tcp   # Dokploy panel (temporary only)

# Enable firewall
sudo ufw enable
sudo ufw status numbered

Important: Docker can bypass UFW rules because it manipulates iptables directly. If your cloud provider offers an integrated firewall (like DigitalOcean Cloud Firewall, AWS Security Groups, etc.), we recommend configuring it as an additional security layer.

Configure the same firewall rules in your provider’s panel:

  • 22/TCP from your IP or trusted range (SSH)
  • 80/TCP from Anywhere (HTTP)
  • 443/TCP from Anywhere (HTTPS)
  • 443/UDP from Anywhere (HTTPS UDP)
  • 3000/TCP temporarily from your IP only (for initial setup)

assets/dropplet-firewall.png

Step 4: Install Dokploy

The official Dokploy installation is straightforward with their script:

curl -sSL https://dokploy.com/install.sh | sudo sh

This script automatically:

  • Installs Docker if not present
  • Initializes Docker Swarm
  • Creates the overlay network dokploy-network
  • Deploys Postgres, Redis, and Dokploy services
  • Launches Traefik as reverse proxy
  • Publishes the panel on port 3000

Step 5: Verify the Installation

Check that everything is working:

# View Docker Swarm services
docker service ls

# View running containers
docker ps

# Verify ports are listening
ss -lntup | grep -E '(:80|:443|:3000)'

Console up services

Now open your browser and access the Dokploy panel:

http://YOUR_PUBLIC_IP:3000

Complete the initial setup wizard by creating your administrator account.

Dashboard Home

Step 6: Configure Domain and HTTPS (optional but recommended)

To access your applications with your own domain and HTTPS:

  1. In your DNS provider’s panel (Cloudflare, DigitalOcean, AWS Route53, etc.), add your domain if you haven’t already.
  2. Create DNS records:
    • A record for @ pointing to your server IP
    • A record for www pointing to your server IP
    • Or a subdomain like dokploy.yourdomain.com

Wait for DNS propagation (may take a few minutes).

Restrict access to port 3000

For security, once Dokploy is configured, restrict access to port 3000:

  • In your cloud provider’s firewall, limit it to your IP only if you need administrative access

 

Step 7: Deploy Your First “Hello World” Application in Node.js

Let’s create a simple Node.js application and deploy it to Dokploy.

7.1 Create the project locally

Create a folder for your project with the following files:

package.json:

{
  "name": "hello-world-dokploy",
  "version": "1.0.0",
  "description": "Hello World application for Dokploy",
  "main": "app.js",
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "^4.18.2"
  }
}

app.js:

const express = require('express');
const app = express();
const PORT = 3000;

app.get('/', (req, res) => {
  res.json({
    message: 'Hello World from Dokploy!',
    timestamp: new Date().toISOString(),
    environment: process.env.NODE_ENV
  });
});

app.get('/health', (req, res) => {
  res.status(200).send('OK');
});

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Dockerfile:

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --omit=dev
COPY . .
EXPOSE 3000
USER node
CMD ["node", "app.js"]

docker-compose.yml (for reference):

version: '3.8'
services:
  app:
    build: .
    environment:
      - NODE_ENV=staging
    restart: unless-stopped

7.2 Deploy to Dokploy

Deploy part 1

Deploy part 1

Deploy part 2

Deploy part 2

  1. In the Dokploy panel, click Create Project
  2. Give your project a name (e.g., “Tests”)
  3. In the project panel, click Create Service and select Application
  4. Name your application: Hello World
  5. Then access your application and in the bottom section Build Type select Dockerfile and then Save.
  6. In the top section you have Provider. For this test we’ll select the Drop type, however for real cases the best option is through git repository.
  7. Once Drop is selected, drag to the Zip file area the zip with the code we specified in point 7.1.
  8. Click the Deploy button
  9. Once deployment is complete, it will show the Deployments tab where you should see a green dot and Done indicating the process was successful. You can click View to verify the process.
  10. Now, to deploy our Hello World, go to the Domains tab.
  11. For this test we’ll use a free Traefik domain by clicking on the dice icon, specify that the Container Port is 3000 and enable HTTPS with Let’s Encrypt provider.
  12. Then click the Create button
  13. When finished, it will give us a URL where we can enter and see our web. Important: since we’re using a test domain it will give security warnings. For production environments use your own domain.

7.3 Verify the deployment

Once deployment is complete, access your application:

https://tests-hello-world-puloud-d13d9c-167-172-as2dsaccc234-151.traefik.me/

You should see the JSON “Hello World” message.

Browser Helo App

Step 8: Production Best Practices

1. Don’t build on production server

Dokploy recommends building Docker images in CI/CD (GitHub Actions, GitLab CI) and then deploying the pre-built image. This saves resources and reduces downtime risk.

2. Use health checks

Configure health checks in your applications (like the /health endpoint in our example) so Docker Swarm can monitor status.

3. Configure backups

  • Configure regular server backups (if your provider offers this feature)
  • Configure regular database backups
  • Use docker volume for persistent data

4. Monitoring and logs

# View Dokploy logs
docker service logs dokploy --tail 100 -f

# View application logs
docker service logs hello-world_app --tail 100 -f

# View service status
docker service ps hello-world_app

Conclusion

Dokploy offers a robust, self-hosted solution for deploying applications on Docker Swarm. By implementing it on an Ubuntu server, you get a scalable, secure, and professional infrastructure at an accessible cost.

Need a cloud server? You can get an Ubuntu Droplet on DigitalOcean using our referral link and receive initial credits to try this tutorial.

Additional Resources

Enjoyed this tutorial? Share your experiences deploying applications with Dokploy in the comments or suggest topics for future articles.

How to Talk to Managers (So They Actually Listen to You)

The fastest way to feel invisible as a developer isn’t to do bad work.

It’s to do great work… and communicate it wrong.

You’re probably thinking — “my code should speak for itself.”

I thought that too. For an embarrassingly long time. And I’ve been in this industry for over 25 years — so that’s a lot of time to be wrong about something.

They don’t teach the real world in bootcamps. Get 25 years of production-grade experience on my YouTube: 🛠️👉 https://www.youtube.com/@lessonsfromproduction

What This Is Actually About

Here’s what nobody tells you when you’re starting out.

You think talking to managers is about communication skills. Being more articulate. Slowing down when you’re nervous. Making eye contact. Saying “synergy” with a straight face.

That’s not it.

It’s this: your manager lives in a completely different reality than you do. Until you understand their reality, you’re shouting across a language barrier and wondering why nobody responds.

This isn’t a soft skills article. This is hard-earned, slightly battle-scarred advice from someone who has shipped at scale, managed teams, and watched genuinely talented engineers get passed over — not because their code was bad, but because nobody knew how good it was.

The Story That Embarrasses Me Every Time I Tell It

Let me show you what I mean.

Junior me had just finished a refactor I was genuinely proud of. Cleaned up a service that had been a mess for two years. Better performance, less complexity, the works.

I walked into my one-on-one ready to explain it. And I did. All of it. The before state, the approach, the tradeoffs, the three patterns I evaluated, why I picked the one I did.

My manager nodded. The way you nod when someone’s telling you about a dream they had.

Then she said:

“So is it done? And did anything break?”

That was the entire conversation she wanted to have.

I had the right information. I just buried it under fifteen minutes of architecture monologue nobody asked for.

The Actual Insight

You think talking to managers is about explaining your work clearly.

It’s about translating your work into their currency.

Their currency isn’t technical elegance. It isn’t even correctness.

It’s outcomes. Risk. Time. Trust.

Managers are running a different simulation. Deadlines, team commitments, what breaks if this goes wrong, how they explain it upward. They need to know: is it safe, is it done, does it matter?

Everything else is noise — not because they’re not smart, but because seventeen other things are already making noise.

1. Lead With What They Care About — Not What You Did

There’s a significant difference between:

“So I refactored the payment service, pulled out the legacy transaction logic, rewrote the retry mechanism using exponential backoff, and updated the tests…”

and:

“The payment service is now three times more reliable under load. Zero breaking changes. Deployed.”

Same work. Completely different landing.

Open with the outcome or the risk. The journey belongs in your PR description. The standup is for the headline.

Get comfortable framing your work in terms of:

  • Time saved
  • Bugs prevented
  • Risk reduced
  • Business value delivered

That’s the translation. That’s the whole game.

2. Bring Options, Not Just Problems

Here’s where juniors — and honestly some seniors — get stuck.

They walk in with a problem. Just a problem. No ideas, no options, nothing.

And I get it. Sometimes you’re genuinely stuck. That’s fine.

But there’s a version of that conversation that goes much better:

“I’ve hit a blocker on X. I see two paths — option A is faster but riskier, option B is safer but slower. I’m leaning toward B. Wanted your read.”

You’ve done the thinking. You’re asking for a decision, not a rescue.

You don’t need the perfect answer. You just need to show you engaged with the problem before escalating it.

Managers remember that. It’s rarer than you’d expect.

3. Calibrate Your Level of Detail

This is where technical people go wrong constantly. I still catch myself doing it.

We love detail. We live in detail. Detail is how we think.

Managers need the signal, not the circuit diagram.

A framework that actually works:

  1. One sentence of context
  2. One sentence of status
  3. One sentence of what you need from them

That’s it. Example:

“I’m working on the auth bug — more complex than expected, likely two more days. No action needed yet, just flagging.”

Twelve seconds. They have everything. If they want more, they’ll ask.

Respecting their time is itself a signal — and they notice.

4. Surface Blockers Early — Not at the Deadline

This is the one that actually costs people. I’ve seen it end careers. I’ve done it myself.

You’re working on something, it starts going sideways, and your brain says:

“I’ll figure it out. I don’t want to look like I can’t handle it.”

So you say nothing.

Then it’s Friday, deadline’s Monday, and now you’re having the conversation anyway — but with no time left to fix anything.

Here’s what you miss in that moment: managers expect things to be hard. What they cannot work with is surprises at the worst possible time.

When you flag it What it is
Tuesday: “This might slip a day or two” A manageable conversation
Friday 4pm: same information A trust event

Flag early. Even when it’s uncertain. Especially when it’s uncertain.

5. Make Your Work Visible

Your manager is not watching your commits.

They’re not reading your PRs. They don’t know about the gnarly race condition you hunted down on a Wednesday that saved the team three days of pain.

If you don’t surface it, it didn’t happen. Not in their reality.

This isn’t about bragging. There’s a flavour of self-promotion that makes every standup uncomfortable — don’t do that.

But there’s a difference between bragging and making your work visible:

“Fixed the intermittent CI failure — race condition in the test setup. The flaky build alerts should stop.”

That’s just information. Relevant, professional, and it quietly signals: this person finds and fixes hard things.

Invisible good work is indistinguishable from no work.

Harsh — but it’s the truth that’ll actually help you.

6. Ask for Feedback — Then Visibly Use It

This one is possibly the highest-leverage move on the whole list.

Ask for feedback on how you’re communicating — not just how you’re doing generally.

“Is there anything about how I give you updates that would make your life easier?”

That question does two things at once:

  • It gives you actually useful information
  • It signals a self-awareness that’s genuinely uncommon at the junior level

Then when they give you feedback — act on it visibly. So they can see the loop close.

Managers invest in people where the loop closes. That’s the whole secret.

The Shift Worth Making

After 25 years in this industry, here’s what I know for certain.

The engineers who grow fastest aren’t always the most technically gifted. They’re the ones who figured out — earlier than most — that being good at the work and making the work legible are two separate skills.

Both matter.

You’ve been focused on getting better at the technical side. Keep doing that.

But start running the parallel track: learning to operate inside the reality your manager actually lives in.

Lead with impact. Bring options. Calibrate your detail. Surface blockers early. Make your work visible. Close the feedback loop.

Not because it’s “soft skills.”

Because the engineer who does great work and makes it legible to the people around them gets trusted with bigger problems.

And bigger problems are where the real learning lives.

If this landed for you, drop a comment below — especially if you’ve got a war story of your own. I read every one.

Note: This article was written with AI assistance to help structure and articulate 25+ years of debugging experience. All examples, insights, and methodologies are from real-world experience.

Pure C# BitTorrent/WebTorrent client and server. No JavaScript dependencies. Desktop + Browser.

SpawnDev.WebTorrent 1.0.0

Pure C# BitTorrent/WebTorrent client and server. No JavaScript dependencies. Desktop + Browser.

Packages

Package NuGet
SpawnDev.WebTorrent Client library — torrents, peers, streaming
SpawnDev.WebTorrent.Server Server library — tracker, web seed

What’s In This Release

Pure C# BitTorrent Implementation

  • Complete BitTorrent wire protocol (handshake, bitfield, have, request, piece, cancel, extensions)
  • Bencode encoder/decoder with raw key tracking for info hash computation
  • .torrent file creation and parsing (single-file, multi-file, magnet URIs, Base32)
  • SHA-1 piece verification — every downloaded piece is hash-verified
  • BEP 10 extension protocol framework (ut_metadata, ut_pex)

Random-Access Streaming

  • ModelStream.ReadAsync(offset, length) — read any byte range from a torrent file as it downloads
  • Pieces download on demand when read — only fetch what you need
  • Cross-piece reads handled transparently
  • Designed for ML model weight loading — tested with forward seek, backward seek, large multi-piece spans, and real ML weight access patterns

Client Library (SpawnDev.WebTorrent)

  • WebTorrentClient — full client with peer ID generation, transport management
  • PieceManager — piece selection, block tracking, SHA-1 verification, storage
  • DownloadCoordinator — ties peers + web seeds together with priority requests
  • ModelTorrentClient — high-level API for ML model delivery
  • WebSocketTrackerClient — WebSocket tracker with WebRTC signaling relay
  • WebSeedConnection — HTTP range request fallback (BEP 17/19)
  • TcpTransport — desktop TCP transport
  • WebRtcTransport — browser WebRTC transport (structural, JS interop via SpawnDev.BlazorJS)
  • PeerCoordinator — full signaling flow: tracker → WebRTC → wire protocol → piece exchange
  • MemoryChunkStore + FileChunkStore — pluggable storage backends

Server Library (SpawnDev.WebTorrent.Server)

  • TorrentTracker — WebSocket tracker with WebRTC signaling relay for browser peers
  • WebSeedServer — serves torrent pieces over HTTP range requests
  • MapWebTorrentServer() — one-line ASP.NET endpoint registration

HuggingFace Integration (SpawnDev.WebTorrent.Server.HuggingFace)

  • HuggingFaceProxy — fetches model files from HuggingFace CDN, caches locally
  • Auto-generates .torrent files with piece hashes on demand
  • Magnet URI generation with tracker + web seed URLs
  • .torrent file caching — avoids regeneration on repeated requests
  • Endpoints: /hf/, /torrent/, /magnet/, /hf-stats

Live Production Server

  • hub.spawndev.com — live tracker + HuggingFace proxy serving AI models
  • SSL terminated via HAProxy + LetsEncrypt
  • Self-contained Linux x64 deployment

Test Suite

  • 49 tests passing in Chromium via PlaywrightMultiTest
  • 43 unit tests + 6 integration tests against live server
  • Stream seeking tests: forward, backward, large span, ML weight loading pattern
  • Full end-to-end: browser → server → HuggingFace → cache → .torrent → download → verify

Demo App

  • Live Demo — dark theme, live server stats, unit tests
  • Blazor WebAssembly — runs entirely in the browser
  • GitHub Pages deployment

Why This Exists

AI models are big — 100MB to 2GB+. CDNs can’t scale when every user downloads the same model. SpawnDev.WebTorrent turns every browser into a peer. The more users, the faster delivery. Built for SpawnDev.ILGPU.ML — GPU-accelerated ML inference for .NET.

What’s Next

  • Distributed GPU compute across devices via P2P network
  • AcceleratorType.P2P — 7th SpawnDev.ILGPU backend
  • AI agent communication protocol over wire extensions
  • SpawnDev.WebFS integration (virtual filesystem backed by torrent swarm)
  • DHT peer discovery (Kademlia)
  • BEP 52 (BitTorrent v2 with SHA-256)

Credits

Built by Todd Tanner (@LostBeard) and the SpawnDev team: Claude (Data), Claude (Riker), Claude (Tuvok), and Gemini.

🖖🚀

Docker as the Sandbox for AI Agents: Safe Cypress Workflows for Frontend Teams

Introduction

A Small Mistake That Breaks Everything. An agent tries to fix a failing test. It sees a permission error and decides to help. It runs:

chmod -R 777 .

Unfortunately, your project folder is symlinked to a broader directory. Within seconds, your local environment is exposed, permissions are broken, and debugging becomes a nightmare.

This is not a far-fetched scenario. It is the natural outcome of giving an autonomous system unrestricted access to your machine.

AI-assisted development has evolved quickly. Agents can now:

  • Run tests
  • Modify files
  • Execute scripts
  • Propose pull requests

With tools and protocols like MCP (Model Context Protocol), they are no longer passive assistants. They are active participants in your development workflow.

And that raises a fundamental question: Should AI agents have direct access to your development environment at all?

The Problem with Host-Based Agent Execution

In most current setups, the execution model looks like this:

This approach is convenient, but it creates systemic issues.

First, there is security exposure. The agent can access environment variables, local files, SSH keys, and system-level resources. Even if the agent behaves correctly, the risk surface is too large.

Second, there is environment inconsistency. The agent’s behavior depends on the local machine—Node versions, OS differences, and installed dependencies. The result is a new variation of the classic problem: “it works on my machine,” but now applied to AI workflows.

Third, reproducibility breaks down. When an agent executes tasks in an uncontrolled environment, it becomes difficult to recreate the same conditions elsewhere, particularly in CI.

Finally, debugging becomes complicated because there is no clear boundary between the agent’s actions and the local system state.

Rethinking the Model: Agents Need Environments, Not Access

We need to stop treating AI agents like supercharged IDE plugins and start treating them like untrusted third-party binaries.

That shift leads to a different execution model:

In this model:

The agent does not execute directly on your machine; it interacts with a containerized environment, and all operations happen within a controlled boundary.

The key difference is subtle but critical: “We are no longer giving the agent access. We are giving it an environment.”

Where MCP Fits: The Container as the “Jailer”

Earlier, we mentioned MCP (Model Context Protocol). This is where it becomes essential.

In most setups, MCP tools allow agents to:

  • Read files
  • Run commands
  • Inspect repositories

If MCP is connected directly to your host machine, it becomes a gateway to your entire system. Instead, the MCP server should run inside the container.

This changes the architecture:

The agent communicates with the MCP and operates within the container boundary. All file access and command execution are scoped to that environment

Effectively, MCP becomes the interface, and the container becomes the jailer that enforces constraints.

A Practical Example: Fixing a Cypress Test

Let’s walk through a real-world scenario.

A Cypress test fails in CI:

Error: Expected to find element:
[data-testid="submit-btn"]
But never found it.

An AI agent analyzes:

  • The failing test
  • Logs
  • Possibly a DOM snapshot

It identifies a selector change:

- cy.get('[data-testid="submit-btn"]')
+ cy.get('[data-testid="submit-button"]')

Deterministic Execution and Binary Parity

Here’s where things often go wrong in typical setups. The failure occurred in CI, which likely runs:

  • Linux
  • A specific Node version
  • A specific Cypress binary

But the agent might attempt to validate the fix on:

  • MacOS
  • ARM architecture
  • A different Node version

This mismatch leads to incorrect conclusions or “hallucinated fixes.” To avoid this, the agent executes inside a container that matches the CI environment:

docker run --rm
-v $(pwd):/app
-w /app
cypress/included:13.6.0
npx cypress run

This ensures binary parity:

  • Same OS
  • Same dependencies
  • Same runtime

The agent is now debugging the exact environment where the failure occurred.

This workflow ensures:

  • Validation happens in a controlled environment
  • The results are reproducible
  • Fixes are grounded in real execution, not assumptions

The Hybrid Strategy with Docker Compose

To make this practical, you can structure your setup like this:

  • Service A (Host-facing)

    • Vite dev server
    • Ports exposed
    • Optimized for speed
  • Service B (Agent Sandbox)

    • No exposed ports
    • Volume-mounted code
    • Runs tests and agent workflows

Example concept:

services:
  app:
    build: .
    command: npm run dev
    ports:
      - "5173:5173"

  agent-sandbox:
    image: cypress/included:13.6.0
    volumes:
      - .:/app
    working_dir: /app
    command: npx cypress run

Trade-offs and Considerations

This architecture introduces additional complexity. Containers need to be configured and maintained. There is some overhead in startup time and resource usage.

However, these costs are offset by:

  • Improved security boundaries
  • Deterministic execution
  • Reproducible debugging
  • Safer integration of AI agents into workflows

For teams operating at scale, these benefits become essential rather than optional.

Where This Is Heading

As AI agents become more capable, they will move from assisting developers to executing entire workflows. At that point, the key question is no longer:
“What can the agent do?”
It becomes:
“Where is the agent allowed to do it?”
In this context, Docker evolves from a deployment tool into an execution layer for intelligence.

Conclusion

AI agents introduce a powerful new capability into frontend development, but they also require a shift in how we think about execution and trust.

Allowing agents to operate directly on the host machine is convenient, but it is not a sustainable model for teams that prioritize security, consistency, and reproducibility.

By introducing containerized execution, we move from access-based thinking to environment-based thinking. This creates a safer and more predictable foundation for integrating AI into development workflows.

The future of frontend development will not be defined solely by faster tools, but by how effectively we control and constrain the systems that use them.

We built an API for events, places, and local news in Barcelona and Madrid

Building a local API for events, places, and news in Barcelona and Madrid GitHub

Most city products run into the same problem sooner or later.

You want to answer simple questions like:

  • What is happening today?
  • What is near these coordinates?
  • Which places belong to this category?
  • Which local news is still actually relevant?

But once you start building, local data is rarely clean enough. Events, places, and news often live in separate systems, use different filters, and do not map well to the way people actually explore a city.

That is one of the reasons we started opening up the data layer behind DondeGo.

DondeGo is a local media and city guide focused on Barcelona and Madrid. The editorial side is built around practical discovery: things to do, places to know, and local news that matters in city life. Recently, we have been turning more of that into an API that can be reused in real products.

What the API is trying to solve

The goal is not to build a huge generic directory.

The goal is to make local urban content easier to work with for developers, editorial teams, maps, newsletters, recommendation tools, and city-focused products.

Right now the API is centered around three main object types:

  • events
  • places
  • news

That makes it possible to support workflows like:

  • showing events happening today in Madrid
  • finding places within a radius from a point on the map
  • filtering objects by tags or categories
  • returning only current news items
  • building city guides or local recommendation layers

Recent changes in v0.1.0

We have just published v0.1.0, partly because I wanted the project to have a visible public milestone and partly because the API has become much more usable recently.

A few of the changes included in this version:

  • daily events now also include films
  • daily events can now return a title override
  • event and place categories now expose identifiers
  • filtering by tag slugs is supported via tags
  • news now supports an actual_only filter for current items
  • event date serialization now includes:
    • start_date
    • start_time
    • end_date
    • end_time
    • is_continuous
    • is_endless
    • is_startless
    • schedules
  • image serialization now includes authorship
  • it is now possible to fetch only films with free screenings
  • it is now possible to fetch only places with free events
  • is_free is now interpreted more strictly
  • categories for events and places can now be disabled in collection responses

None of this is especially flashy, but it moves the API closer to being something that is genuinely usable in production.

The kind of queries we care about

The API is most useful when it reflects how people actually navigate a city.

For example, a basic daily query:

GET /api/v1/events/?location=madrid&date=hoy

A proximity-based query:

GET /api/v1/events/?lat=40.4168&lon=-3.7038&radius=3000&date=hoy

And place discovery around a point:

GET /api/v1/places/?lat=41.3874&lon=2.1686&radius=2000

That may sound simple, but in practice this is the layer many local products need most.

Not “all possible city data”, but structured answers to questions like:

  • what can I do today?
  • what is nearby?
  • what is free?
  • what is still current?
  • what belongs to this area or category?

Why I find local APIs interesting

There are many great APIs in the world, but local discovery still feels oddly underserved.

A lot of the difficulty is not in storing records. It is in making the data useful in the context of a real city. Dates matter. Geography matters. Editorial freshness matters. Category structure matters. Free vs paid matters. Sometimes a simple “today” filter matters more than a complex taxonomy.

That is why I think local APIs are interesting. They sit somewhere between media, maps, structured data, and product design.

If you are building in this space

We are opening access gradually, but if you are working on a project around Barcelona or Madrid and need local events, places, or news, that is what we are building toward.

The public project is here, a bit more context lives there: DondeGo

I would also be curious how other people here would design this kind of API.

Would you prioritize geospatial search first, editorial freshness first, or category and tag structure first?

Buffer Overflows on x64 Windows: A Practical Beginners Guide (Part 1): Setting up

Introduction

Hello everyone. Mirrai here. I’ve been wanting to make this tutorial for a while because i feel guides on windows exploitation are hard to come across (especially x64) so i finally decided to start. This guide will walk you through the fundamentals of stack-based buffer overflows on x64 Windows, what they are, why they work, and how to set up your environment to start exploring them yourself.
By the end of part 1 you’ll understand the stack, what RIP is and why controlling it allows arbitrary code execution and you’ll have a vulnerable program ready to analyze. Part 2 will cover the actual, practical exploitation.

x86 vs. x64

If you’ve ever looked at older tutorials, you’ve probably seen a lot of talk about x86 (32-bit) exploitation. While the logic is similar, jumping to x64 (64-bit) feels like moving from an elevator to a warehouse. Everything is bigger and it will affect certain exploits later down the road.

In x86, your registers were 32 bits wide. In x64, they’ve doubled to 64 bits. This means every address you overwrite on the stack now needs to be 8 bytes long instead of 4. Certain registers such as EIP and ESP in x86 are now RIP and RSP in x64 as well.

x86 stack
0060F94C  00000000  
0060F950  00000000  

x64 stack
0000005189CFF0B0  0000000000000000  
0000005189CFF0B8  0000000000000000  

The Stack

Lets begin with the most basic and arguably hardest to understand part, the stack. When a program runs, it needs somewhere to temporarily store data, local variables, function arguments and return addresses. That place is the stack. Think of it as a pile of plates, uhh kinda. you add to the top and remove from the top. In x64 Windows, the stack grows downward in memory, meaning each new piece of data gets placed at a lower memory address than the previous one.

Random stack space
0000005189CFF908  0000000000000000  
0000005189CFF910  0000000000000000  
0000005189CFF918  0000000000000000  
0000005189CFF920  0000000000000000  
0000005189CFF928  0000000000000000  
0000005189CFF930  0000000000000000  
0000005189CFF938  0000000000000000  
0000005189CFF940  0000000000000000

This is the stack memory from a random program i just ran. 0000005189CFF908 is the lowest section in this stack so if you need to store more data you do something like sub, rsp, 32 in ASM to allocate more below it such as 0000005189CFF900 and so on but I’m getting ahead of myself.

When a function is called, the program pushes a return address onto the stack. This is the memory address it should go back to when the function finishes.

What is RIP?

RIP is the instruction pointer register on x64 systems. It holds the address of the next instruction the CPU will execute. When a function returns, the value saved on the stack as the return address gets loaded into RIP. Wherever RIP points, execution follows.
Control RIP, control the program.

Say this is a function


00007FF6EB70139 | 48:83C4 28             | add rsp,28                                 
00007FF6EB70139 | C3                     | ret

when ret is run the RIP changes to what rsp is pointed to.

RSP --> 0000005189CFF908  00007FF4EB801341  
        0000005189CFF910  0000000000000000 

If rsp is at 0000005189CFF908 then rip will change to and continue from there.

What is a Buffer Overflow?
A buffer is a fixed-size chunk of memory allocated for storing data. A buffer overflow happens when a program writes more data into a buffer than it was designed to hold, and that excess data spills into adjacent memory.
On the stack, that adjacent memory includes the saved return address. If you overflow far enough to overwrite the return address with a value you control, you control where execution goes when the function returns. That’s the primitive.

Take this for example.

0000005189CFF810  0000000000000000  
0000005189CFF818  0000000000000000  
0000005189CFF820  0000000000000000  
0000005189CFF828  0000000000000000  
0000005189CFF830  0000000000000000  
0000005189CFF838  0000000000000000  
0000005189CFF840  0000000000000000  
0000005189CFF848  0000000000000000  
0000005189CFF850  0000000000000000  
0000005189CFF858  0000000000000000  
0000005189CFF860  00007FF6EF40131A  

Say i made this with

char buffer[80] = {0}; and that 0000005189CFF860 is our function’s ret address.

If i write an “A” to this code using strcpy or whatever, I get:

0000005189CFF810  0000000000000041  
0000005189CFF818  0000000000000000  
0000005189CFF820  0000000000000000  
0000005189CFF828  0000000000000000  
0000005189CFF830  0000000000000000  
0000005189CFF838  0000000000000000  
0000005189CFF840  0000000000000000  
0000005189CFF848  0000000000000000  
0000005189CFF850  0000000000000000  
0000005189CFF858  0000000000000000  
0000005189CFF860  00007FF6EF40131A

41 is “A” in hex btw and remember, the stack grows “down” (toward lower addresses) when you add more space to the stack but Buffers (like arrays) fill “up” (toward higher addresses). This is why the “A” is at 0000005189CFF810 but as we write more “A”s to this buffer, we will climb upward to 0000005189CFF860.

The goal is to overwrite 0000005189CFF860 so we need to write 88 “A”s. when I do that I get:

0000005189CFF810  4141414141414141  
0000005189CFF818  4141414141414141 
0000005189CFF820  4141414141414141  
0000005189CFF828  4141414141414141  
0000005189CFF830  4141414141414141  
0000005189CFF838  4141414141414141  
0000005189CFF840  4141414141414141  
0000005189CFF848  4141414141414141  
0000005189CFF850  4141414141414141  
0000005189CFF858  4141414141414141  
0000005189CFF860  4141414141414141

Here we overwrote the ret address with “A”s which will crash the program because 4141414141414141 is not a real address that exists lol, but if we replace the value at 0000005189CFF860 with any other address we can control the RIP and run any code we want. Pretty cool right? Admittedly an actual function is a bit different to this but not by much. We will get there in the exploitation part.

Environment Setup

Now that we know the theory let’s practice it. You’ll need the following tools before proceeding:
x64dbg: A free, open source debugger for Windows. Download it at x64dbg.com. This is what you’ll use to inspect memory, set breakpoints, and observe the overflow in action.

  • pwntools: A Python library for exploit development. Install it with:
pip install pwntools

Useful for scripting your exploit once you understand the mechanics manually.

  • A basic Python installation and a C compiler for building the vulnerable program round out the setup. I recommend winlibs for the gcc compiler plus MinGW-w64 with or without clang. You can get python here

The Vulnerable Program

The program we’ll be exploiting is a simple C application with a classic stack vulnerability.

int main() {
   char username[20] = {0};
   printf("What is your username?: ");
   gets(username);
   printf("%s %sn", "Hello", username);
}

This program just greets you. How cute! Lets break it!

gets() is our entry point because it lacks bounds checking and will not stop if the data to be written is more than what the buffer can take. This is why it is advised to use fgets instead.

By the way, we will handle compilation and the arguments to pass to the compiler to disable certain protections like aslr, dep etc in part two. I might cover tutorials for getting around them sometime in the future.

What’s Next

In part two we’ll fire up x64dbg and pwntools, attach it to the vulnerable program, find the offset needed to control RIP, and build a working exploit.

See you all in part Two and of course if you have any questions feel free to ask.

测试文章2Hashnode专属

测试文章2Hashnode专属这篇文章将只发布到Hashnode平台## Hashnode特色- 支持丰富的Markdown格式- 面向技术博主- 良好的SEO优化### 代码高亮

pythondef hello_hashnode(): print('Hello Hashnode!')

专为Hashnode社区定制的内容

Understanding ECDSA Signatures with the Web Crypto API

Introduction

Cryptographic techniques are used everywhere on the internet — TLS, SSH, email signatures, and more. Signing, verification, encryption, decryption, key exchange — these terms come up constantly, yet if asked whether I truly understood how they work, the honest answer was: not really.

Then I discovered the Web Crypto API. It’s a cryptographic API built into the browser that lets you perform crypto operations without any external libraries. The API is simple enough that you can learn by writing and running code.

This motivated me to properly understand the fundamentals of cryptography at the code level. In this article, we’ll look at digital signatures (ECDSA) and see how signing and verification actually work.

What is the Web Crypto API?

The Web Crypto API is a cryptographic API built into the browser. It allows you to perform key generation, signing, verification, encryption, and decryption without any external libraries.

All operations are accessed through the crypto.subtle object.

Method Purpose
crypto.subtle.generateKey() Generate key pairs or symmetric keys
crypto.subtle.sign() Sign with a private key
crypto.subtle.verify() Verify with a public key
crypto.subtle.encrypt() Encrypt
crypto.subtle.decrypt() Decrypt
crypto.subtle.deriveKey() Derive a symmetric key from key exchange
crypto.subtle.exportKey() Export a key in JWK or other formats
crypto.subtle.importKey() Import an external key

Each method returns a Promise, so you receive results with await. In this article, we’ll use three: generateKey, sign, and verify.

Main algorithms available with generateKey:

Algorithm Type Purpose
ECDSA Elliptic curve (P-256, etc.) Signing and verification
RSA-OAEP RSA Encryption and decryption
AES-GCM Symmetric key Authenticated encryption/decryption
ECDH Elliptic curve (P-256, etc.) Key exchange
HMAC Symmetric key Message authentication

The same crypto.subtle is also available in Bun and Node.js (v19+), so the same code runs on the server side as well.

What is a Digital Signature?

A digital signature is a mechanism where you “sign with a private key and verify with a public key.” Only the signer can create the signature, but anyone can verify its authenticity.

Operation Key Used Meaning
Sign Private key Proves that you created it
Verify Public key Anyone can confirm authenticity

In this article, we’ll use the Web Crypto API to sign and verify with ECDSA (Elliptic Curve Digital Signature Algorithm).

Code: Key Pair Generation → Signing → Verification

Run the following shell command to save the code as ecdsa.mjs. For browsers, paste the JavaScript portion (excluding the cat and EOF lines) into the DevTools Console.

Environment How to Run
Browser Paste the JavaScript portion into the DevTools Console
Bun bun ecdsa.mjs
Node.js (v19+) node ecdsa.mjs
cat << 'EOF' > ecdsa.mjs

// 1. Generate key pair (ECDSA, P-256)
const keyPair = await crypto.subtle.generateKey(
  { name: "ECDSA", namedCurve: "P-256" },
  true, // extractable: true to allow key export (use false in production)
  ["sign", "verify"]
);

// 2. Data to sign
const message = "Hello, WebCrypto!";
const data = new TextEncoder().encode(message);

// 3. Sign with private key
const signature = await crypto.subtle.sign(
  { name: "ECDSA", hash: "SHA-256" },
  keyPair.privateKey,
  data
);

// 4. Verify with public key (original data)
const isValid = await crypto.subtle.verify(
  { name: "ECDSA", hash: "SHA-256" },
  keyPair.publicKey,
  signature,
  data
);

// 5. Verify with tampered data
const tamperedMessage = "Hello, WebCrypto! (tampered)";
const tampered = new TextEncoder().encode(tamperedMessage);
const isValidTampered = await crypto.subtle.verify(
  { name: "ECDSA", hash: "SHA-256" },
  keyPair.publicKey,
  signature,
  tampered
);

// Export key pair
const privateKey = await crypto.subtle.exportKey("jwk", keyPair.privateKey);
const publicKey = await crypto.subtle.exportKey("jwk", keyPair.publicKey);

// Output
console.log("Key pair generated:nPrivate key:", privateKey);
console.log("Public key:", publicKey);

console.log("nSigning:nData:", message);
console.log("Signature (Base64):", btoa(String.fromCharCode(...new Uint8Array(signature))));

console.log("nVerification:nData:", message);
console.log("Result:", isValid); // true

console.log("nTampered data verification:nData:", tamperedMessage);
console.log("Result:", isValidTampered); // false
EOF

Sample output:

$ bun ecdsa.mjs
Key pair generated:
Private key: {
  crv: "P-256",
  d: "qTk9mKRBkX5MCnNzGeCvg5sO1vAWe0FLRXHse7QzSlQ",
  ext: true,
  key_ops: [ "sign" ],
  kty: "EC",
  x: "lWH4MQwp7VEMqAKxk_DYef1_ZY4lVciws4nj1wwFANE",
  y: "c_aEH9x91cYmuaatRW5Nys6uFHDBadDZA4IJuPLVQCY",
}
Public key: {
  crv: "P-256",
  ext: true,
  key_ops: [ "verify" ],
  kty: "EC",
  x: "lWH4MQwp7VEMqAKxk_DYef1_ZY4lVciws4nj1wwFANE",
  y: "c_aEH9x91cYmuaatRW5Nys6uFHDBadDZA4IJuPLVQCY",
}

Signing:
Data: Hello, WebCrypto!
Signature (Base64): j3z3keEwOxWWl/7kJ9vWphyQYsY/W78TlCB/b0OuN4jlWCAXmWVwOD9DYyk54C3NG/eZblKt6cNYMf6rg24gUw==

Verification:
Data: Hello, WebCrypto!
Result: true

Tampered data verification:
Data: Hello, WebCrypto! (tampered)
Result: false

The code first generates an ECDSA key pair, then signs data with the private key to produce a signature. When verifying the signature using the public key and data, the original data returns true while tampered data returns false. If even a single byte of data changes, the signature verification fails and tampering is detected. Since the signature cannot be forged from the public key alone, a third party can confirm that “the owner of the private key signed this data.”

Key Points

  • ECDSA is for signing only. It cannot encrypt or decrypt. Encryption requires different algorithms.
  • extractable: false prevents the private key from being exported. This setting is recommended in production.
  • P-256 (secp256r1) is a widely used curve on the web and mobile. It’s also used in TLS, SSH, and Passkeys.

Summary

The Web Crypto API is a cryptographic API built into the browser, making it easy to experiment with code in the browser console, Bun, or Node.js. Using crypto.subtle, we demonstrated the generation and verification of ECDSA digital signatures. We confirmed that the fundamentals of asymmetric cryptography — signing with a private key and verifying with a public key — can be easily tested.

Appendix: What Does ECDSA Actually Compute?

Key Pair Generation:

  1. Generate a random integer d (1 ≤ d ≤ n-1, where n is the curve order) → this is the private key
  2. Compute a point on the elliptic curve: Q = d · G (G is the base point) → this is the public key

The private key is just a random number, and the public key is a point derived from it via scalar multiplication on the elliptic curve. Recovering d from Q and G is extremely difficult, so the private key cannot be derived from the public key.

Signing (private key side):

  1. Hash the data: h = SHA-256(data)
  2. Generate a random number k
  3. Compute a point on the elliptic curve: R = k · G (G is the base point)
  4. r = R.x mod n (x-coordinate of R)
  5. s = k⁻¹ × (h + r × private_key) mod n
  6. The signature is the pair (r, s)

Verification (public key side):

  1. Hash the data: h = SHA-256(data)
  2. u1 = h × s⁻¹ mod n
  3. u2 = r × s⁻¹ mod n
  4. Recover a point on the elliptic curve: R' = u1 · G + u2 · public_key
  5. If R'.x mod n == r, return true

Verification recovers a point on the elliptic curve from the data hash, s from the signature, and the public key, then checks whether its x-coordinate matches r from the signature.

Point Addition and Scalar Multiplication on Elliptic Curves:

k · G is the point obtained by adding the elliptic curve point G to itself k times.

Point addition on elliptic curves is a geometric operation different from ordinary addition.

Point addition (P + Q):

  1. Draw a line through two points P and Q on the curve
  2. Find the third point where the line intersects the curve
  3. Reflect that point across the x-axis → this is P + Q

Point doubling (P + P):

  1. Draw the tangent line at P
  2. Find the point where the tangent intersects the curve
  3. Reflect across the x-axis → this is 2P

Scalar multiplication k · G repeats this addition k times (G + G + G + ... + G). In practice, the “double-and-add” method computes this efficiently.

Why this works as cryptography:

  • Computing k · G from k and G is fast
  • Recovering k from G and k · G is extremely difficult (Elliptic Curve Discrete Logarithm Problem)

This one-way property is the foundation of ECDSA’s security.

About k⁻¹:

k⁻¹ is the modular inverse of k (an integer satisfying k⁻¹ × k ≡ 1 (mod n)). Unlike ordinary division (1/k), this performs the equivalent of “division” using integers in the mod n world.

For a hands-on implementation of these calculations: Implementing ECDSA from Scratch Without Libraries

References:

React vs Vue vs Svelte: Component Architecture Strategies for Visual Page Builders and Marketing Teams

The Framework Decision That Shapes Your Page Building Strategy

Picture this scenario. Your development team has just spent six months building a custom component library. The marketers are excited to start building landing pages independently. But when they open the visual editor, the components lag. The props are confusing. The bundle size is crushing mobile performance. Your choice of framework has created invisible handcuffs that limit what your team can achieve.

This is the reality facing engineering leaders in 2025. React, Vue, and Svelte each offer distinct philosophies for building reusable components. Yet when these components power visual page builders, the implications extend far beyond developer preference. The framework you select determines bundle size budgets, prop schema complexity, type safety enforcement, and ultimately whether marketing teams can truly self-serve.

In this analysis, we examine how each framework handles the specific demands of component-based page building. We look beyond popularity metrics to explore compilation strategies, runtime overhead, and prop definition patterns. Whether you are evaluating a new build or optimizing an existing component library, this guide provides the technical depth necessary to make an informed architectural decision.

The Current Landscape of Component-Based Development

Why Framework Choice Matters for Visual Page Builders

Traditional web development separates concerns by layer. Database queries live in one place. Business logic resides in another. Presentation components handle the view. Visual page builders disrupt this separation by exposing component props directly to non-technical users. Suddenly, your TypeScript interfaces become user interface elements. Your component variants become dropdown menus. Your conditional rendering logic becomes toggle switches.

This transformation creates unique constraints. Components must carry metadata about their configurable properties. They must validate inputs at the edge between developer code and marketer intent. They must render predictably across server and client environments. Not all frameworks handle these requirements equally.

React dominates the market with approximately 40% of developer mindshare. Vue maintains strong adoption in Asia and among teams seeking progressive enhancement. Svelte captures attention from performance-focused developers tired of virtual DOM overhead. Each brings different tradeoffs to the page building equation.

The Developer Marketer Collaboration Challenge

The fundamental tension in modern page building lies between developer rigor and marketer velocity. Developers want type safety, established patterns, and robust testing. Marketers want immediate visual feedback, intuitive controls, and the freedom to experiment without deployment cycles.

Frameworks that facilitate strong prop schemas bridge this gap effectively. When components self-document their configurable properties, visual editors can generate appropriate controls automatically. This eliminates the manual work of building custom field types for every new component. It also reduces the risk of marketers breaking layouts by entering invalid data.

Our experience building for hundreds of teams shows that the gap between developer capability and marketer need is where most projects lose velocity. Platforms that bridge this gap through visual editing of developer-built components see significantly faster page delivery and higher campaign performance.

Performance Implications at Scale

Page builders do not render single components in isolation. They compose dozens of components into complex layouts. They hydrate interactive elements progressively. They handle real-time preview updates as marketers adjust props.

This composition pattern amplifies framework overhead. A React component carrying 100KB of runtime might seem reasonable for a single page. Multiply that across fifty components on a landing page, add the virtual DOM reconciliation costs, and suddenly mobile users experience input delays. Frameworks that compile to vanilla JavaScript offer advantages here, but they introduce different constraints around dynamic behavior and ecosystem compatibility.

Technical Architecture Across Frameworks

React and the Virtual DOM Legacy

React approaches component architecture through the lens of the virtual DOM. Every component render generates a lightweight representation of the UI. React compares this against the previous representation, calculates the minimal set of changes, and applies them to the actual DOM.

For page builders, this reconciliation model offers predictable behavior. When marketers adjust a prop in the visual editor, React efficiently updates only the affected portions of the page. However, this comes with runtime costs. The virtual DOM library itself adds approximately 40KB gzipped to your bundle. Complex component trees require careful memoization to prevent unnecessary re-renders.

React’s ecosystem provides sophisticated solutions for prop schemas. Tools like Zod, Yup, and JSON Schema integrate cleanly with React components. TypeScript support is first-class, enabling powerful autocomplete and compile-time checking. The recent introduction of React Server Components further complicates the page building picture, allowing components to render exclusively on the server while maintaining interactive islands for client-side functionality.

Consider this typical prop schema pattern for a React hero component designed for visual editing:

interface HeroBannerProps {
 title: string;
 subtitle?: string;
 alignment: 'left' | 'center' | 'right';
 backgroundImage?: string;
 ctaText?: string;
 ctaUrl?: string;
 // Schema metadata for the visual builder
 schema: {
 title: {
 type: 'text';
 maxLength: 100;
 required: true;
 };
 alignment: {
 type: 'select';
 options: ['left', 'center', 'right'];
 defaultValue: 'center';
 };
 backgroundImage: {
 type: 'image';
 acceptedFormats: ['jpg', 'webp', 'png'];
 };
 };
}

export function HeroBanner({ 
 title, 
 subtitle, 
 alignment = 'center',
 backgroundImage,
 ctaText,
 ctaUrl 
}: HeroBannerProps) {
 return (

 # {title}

 {subtitle && {subtitle}

}
 {ctaText && ctaUrl && (
 {ctaText}
 )}

 );
}

typescript

Vue’s Progressive Approach

Vue occupies the middle ground between React’s explicitness and Svelte’s magic. It uses a template-based syntax that feels familiar to developers coming from HTML heavy backgrounds, while providing modern reactivity through a fine-grained proxy-based system.

The Options API and Composition API offer flexibility in how teams structure components. For page builders, the Composition API with TypeScript provides excellent type inference and prop validation. Vue’s single file components naturally separate template, script, and style concerns, making it easier for marketers to understand component boundaries when working in visual editors.

Vue’s reactivity system avoids the virtual DOM overhead of React in many cases. By tracking dependencies at the granular level of reactive primitives, Vue can update the DOM precisely without full tree diffing. This produces smaller bundle sizes and better performance for component-heavy pages.

Prop definition in Vue follows a declarative pattern:



 # {{ props. title }}

 {{ props. subtitle }}



Svelte’s Compile-Time Revolution

Svelte takes a fundamentally different approach. Rather than shipping a framework runtime to the browser, Svelte compiles components into highly optimized vanilla JavaScript at build time. The framework essentially disappears, leaving behind surgical DOM updates that execute without virtual DOM overhead.

For page builders, this compilation model offers compelling advantages. Bundle sizes shrink dramatically. Runtime performance approaches theoretical maximums. Components load instantly, even on constrained mobile networks.

However, the compile-time approach introduces constraints. Dynamic component rendering requires more explicit handling. The ecosystem, while growing, lacks the depth of React’s npm repository. Prop validation relies on runtime checks or external tools rather than first-class framework features.

Svelte’s syntax for props is refreshingly minimal:


 # {title}

 {#if subtitle}
 {subtitle}

 {/if}

Comparative Evaluation for Page Building

Framework Characteristics Compared

When selecting a framework for component-based page building, technical leaders must weigh multiple factors beyond raw popularity. The following table summarizes key characteristics relevant to visual editing platforms:

| Characteristic | React | Vue | Svelte |
| — | — | — | — |
| Runtime Size (gzipped) | ~40KB | ~22KB | ~2KB |
| Rendering Model | Virtual DOM | Virtual DOM + Proxy | Compiled DOM |
| TypeScript Integration | Excellent | Very Good | Good |
| Prop Schema Ecosystem | Extensive | Moderate | Emerging |
| Learning Curve | Moderate | Gentle | Gentle |
| Bundle at Scale | Large | Medium | Small |
| Server Component Support | Native | Via Nuxt | Limited |

Type Safety and Prop Schema Patterns

Type safety becomes critical when components serve as the boundary between developer code and marketer configuration. Strong typing prevents runtime errors and enables intelligent autocomplete in visual editors. Universal type-safe prop schemas allow teams to define component contracts once and reuse them across frameworks, though each framework requires specific implementation patterns.

React’s ecosystem leads here. Tools like React Hook Form, Formik, and numerous schema validators provide battle-tested patterns for prop validation. The React community has spent years solving the problem of runtime type checking, producing robust solutions that integrate with visual builders.

Vue’s TypeScript support has improved dramatically with Vue 3. The defineProps macro provides excellent type inference without boilerplate. However, the ecosystem for schema validation remains smaller than React’s.

Svelte requires more manual effort for complex validation. Without a virtual DOM, certain dynamic validation patterns become more verbose. Teams often rely on external libraries like Zod or Yup, importing them explicitly into component files.

Bundle Size and Runtime Performance

Page builders compound framework overhead. A marketing landing page might contain thirty distinct components. Each framework handles this composition differently.

React’s virtual DOM requires the full library presence. While tree shaking helps, the reconciliation engine always ships. For complex pages, React’s memory footprint grows with component count.

Vue offers a smaller runtime and more efficient reactivity tracking. The proxy-based system minimizes unnecessary updates, keeping memory usage flatter as page complexity increases.

Svelte effectively removes framework overhead. Each component compiles to vanilla JavaScript. A page with thirty Svelte components carries thirty small JavaScript modules, not a framework runtime plus thirty wrappers. This produces the smallest bundles and fastest initial loads, particularly on mobile devices.

Architectural Patterns for Visual Editing

Defining Prop Schemas for Cross-Functional Teams

The bridge between developer components and visual editing relies on prop schemas. These schemas describe not just types, but user interface hints. They tell the page builder which input to render for each prop. They define validation rules that prevent broken states. They specify default values that ensure graceful degradation.

Building reusable components with editable prop schemas requires thinking beyond TypeScript interfaces. Developers must consider the marketer’s mental model. A boolean prop named isVisible makes sense to engineers, but showSection communicates intent better to non-technical users.

Schema definition patterns vary by framework, but successful implementations share common traits. They separate the schema definition from the component logic to avoid bundling metadata in production. They support conditional logic, showing certain props only when others meet specific values. They handle media references specially, integrating with asset management systems.

CLI Tooling and Deployment Workflows

Modern page building requires seamless deployment pipelines. When developers push component updates, marketing teams should see changes immediately in the visual editor. This demands sophisticated CLI tooling that understands framework-specific build processes.

React’s mature tooling ecosystem provides excellent starting points. Webpack, Vite, and Parcel each handle React components efficiently. However, extracting prop schemas for the visual editor requires additional build steps. Teams often write custom plugins that parse TypeScript definitions or JSDoc comments to generate schema JSON.

Vue’s single file components simplify this extraction. The clear separation of template, script, and style makes automated parsing more reliable. Tools can scan . vue files and extract prop definitions with high confidence.

Svelte’s compilation step requires careful handling. Since Svelte transforms code significantly before output, schema extraction must happen pre-compilation. This typically involves parsing the Svelte AST to identify exported props and their types.

Regardless of framework, the deployment workflow should validate components before publishing. Type checking, linting, and visual regression testing ensure that updates do not break existing pages. Automated versioning helps marketing teams understand when component changes might affect their work.

Integration with Visual Builder Platforms

The ultimate test of component architecture comes during visual editing. Components must render reliably in preview modes, server-side rendering contexts, and static generation environments. They must handle hydration without flashing or layout shifts.

React’s Server Components represent the bleeding edge here. By rendering components exclusively on the server, teams can eliminate client-side JavaScript for static content. This improves performance significantly, though it complicates the visual editing experience. Page builders must simulate server environments during editing, or provide clear boundaries between server and client components.

Vue’s Nuxt framework offers similar capabilities with server components and islands architecture. The explicit nature of Vue’s reactivity makes it easier to identify which components require client interactivity versus static rendering.

SvelteKit provides server-side rendering and static site generation, but the framework’s compile-time nature requires careful handling of dynamic imports. Visual editors must account for Svelte’s module-based compilation when generating preview renders.

Strategic Decision Framework

When to Choose React

Select React when your team prioritizes ecosystem breadth and hiring pool over bundle size constraints. React makes sense for large organizations with established component libraries and complex state management needs. The framework excels when you need sophisticated server rendering patterns, extensive third-party integrations, or advanced animation libraries.

React also suits teams building for the long term. The framework’s stability and backing ensure continued development and support. If your page builder must integrate with numerous external services, React’s npm ecosystem provides ready solutions.

However, enterprise teams should evaluate whether React’s overhead justifies the investment for simple marketing pages. The framework shines in application contexts more than content-heavy landing pages.

When to Choose Vue

Vue fits teams seeking a middle path. It offers better performance than React with a gentler learning curve. Choose Vue when you have mixed skill levels on your development team, or when you prioritize template syntax familiarity over JSX flexibility.

Vue excels in progressive enhancement scenarios. If you are migrating an existing server-rendered application to a component-based page builder, Vue allows incremental adoption without full rewrites. The framework’s reactivity system handles complex component interactions without the boilerplate required in React.

Teams in Asia Pacific regions often prefer Vue due to its documentation quality and community support in those markets. If your development team is distributed globally, Vue’s approachable nature reduces onboarding friction.

When to Choose Svelte

Svelte suits performance-critical applications and teams frustrated with virtual DOM overhead. Choose Svelte when bundle size is paramount, such as for mobile-heavy audiences or emerging markets with limited bandwidth.

The framework excels for content-focused pages with limited interactivity. Marketing landing pages, brochure sites, and campaign microsites benefit from Svelte’s compilation model. The smaller runtime reduces hosting costs and improves Core Web Vitals scores.

However, Svelte requires comfort with newer tooling and smaller ecosystems. Teams must accept writing more custom logic for complex state management or validation. If your page builder requires highly dynamic, application-like features, React or Vue might serve better.

Future Implications and Emerging Patterns

The Rise of Server Components and Partial Hydration

The industry is moving toward server-first rendering with selective client hydration. React Server Components pioneered this approach, but similar patterns are emerging across all frameworks. For page builders, this trend promises faster initial loads and reduced JavaScript payloads.

However, server components complicate the visual editing experience. When components render exclusively on the server, the browser-based visual editor cannot easily preview them without a server environment. Platforms must evolve to provide real-time server rendering during editing sessions, or clearly delineate between editable client components and static server components.

We anticipate that 2026 will bring standardized patterns for mixing server and client components in visual builders. Frameworks will likely converge on islands architecture, where static content ships as HTML while interactive elements hydrate progressively.

Standardization of Prop Schemas

As visual page builders mature, the industry will likely standardize prop schema definitions. Currently, each platform invents its own format for describing component properties. This fragmentation forces developers to rewrite component definitions when switching tools.

Future standards might emerge from Web Components or new W3C proposals. These standards would allow components to carry their own editing metadata, making them portable across React, Vue, Svelte, and vanilla implementations. Developers could write components once and deploy them to any visual builder that understands the schema specification.

AI-Assisted Component Generation

Artificial intelligence is beginning to influence component development. Tools that generate React or Vue components from design files or natural language descriptions will become mainstream. For page builders, this means marketers might soon describe desired components in plain English, with AI generating the underlying framework code.

This evolution will place greater emphasis on clean prop schemas and component boundaries. AI-generated code must integrate seamlessly with existing visual editing systems. Frameworks with clear, explicit patterns will likely adapt better to AI tooling than those with heavy abstraction or magic conventions.

Conclusion: Aligning Framework Choice with Business Goals

The choice between React, Vue, and Svelte for component-based page building is not merely technical. It reflects organizational priorities around performance, team composition, and time to market.

React offers the safest bet for large teams needing extensive ecosystem support. Its virtual DOM tradeoffs are acceptable when building complex applications, though they may burden simple marketing pages. Vue provides the most balanced approach, combining gentle learning curves with professional-grade features. Svelte delivers unmatched performance for content-heavy sites, at the cost of ecosystem maturity.

Regardless of framework selection, success in component-based page building requires attention to prop schemas, type safety, and deployment workflows. The framework is merely the foundation. The architecture you build atop it determines whether marketing teams achieve true self-service or remain dependent on developer resources.

As visual editing platforms evolve, the gap between developer-built components and marketer-deployed pages will narrow. Teams that invest in strong component architecture today, with clear schemas and framework-agnostic patterns, will find themselves best positioned to leverage these advances. The future belongs not to any single framework, but to organizations that bridge the developer-marketer divide through thoughtful component design.

Originally published on Oaysus Blog. Oaysus is a visual page builder where developers build components and marketing teams create pages visually.