Pixel

Pixel Updates & Events

Server Updates & Events

Announcements

C# 12 ref readonly Metadata Explained

DEV Community

Decompiling ref readonly parameters: one custom attribute away from in

ref readonly parameters, added in C# 12, look like they should be a big deal in metadata. They are a new direction for passing arguments, a new rule about what you are allowed to pass, and a fourth member of the by-reference family alongside ref, out and in. So it is a little surprising that in the compiled assembly a ref readonly parameter is byte-for-byte identical to an in parameter except for a single custom attribute. That one attribute is the entire job a decompiler has to do here, and it is also exactly the piece a decompiler that only reads signatures will miss — quietly turning every ref readonly back into in. This post walks the metadata for both and shows what Glass.NET reconstructs. Two keywords, one binary shape Start with two methods that are deliberately the same except for the parameter direction: public static class Geometry
{
public static double LengthIn(in Vector3 v) =>
Math.Sqrt(v.X * v.X + v.Y * v.Y + v.Z * v.Z);

public static double LengthReq(ref readonly Vector3 v) =>
    Math.Sqrt(v.X * v.X + v.Y * v.Y + v.Z * v.Z);

}
in has been around since C# 7.2; ref readonly is the newer, stricter cousin. The practical difference in source is about what the caller may pass. An in parameter accepts anything silently — a variable, a field, even a literal — and the compiler conjures a hidden temporary for the cases that are not already a location. A ref readonly parameter wants an actual location and warns when it does not get one, which is the point: it is for APIs that take a reference to avoid copying a large struct, where passing a throwaway temporary is almost always a mistake the author wants flagged. That is a compile-time rule, enforced by the compiler and reported as a warning, and — crucially — it leaves no trace in the parameter's shape at all. Reading the parameter metadata Here is where the two methods stop looking different. Decompile LengthIn to IL and the parameter is a managed reference with a directional flag and a required modifier: .method public hidebysig static float64 LengthIn (
[in] valuetype Vector3& modreq([System.Runtime]System.Runtime.InteropServices.InAttribute) v
) cil managed
{
.param [1]
.custom instance void [System.Runtime]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = ( 01 00 00 00 )
// ...
}
Three things encode the in. The [in] flag is the parameter's direction (ParamAttributes.In). The modreq([System.Runtime]System.Runtime.InteropServices.InAttribute) is a required custom modifier welded onto the Vector3& type in the signature — modreq, not modopt, so it is part of the signature and a consumer that does not understand it must refuse to bind. And the .param [1] block carries a System.Runtime.CompilerServices.IsReadOnlyAttribute, the marker that says "this reference is read-only." Now LengthReq, the ref readonly version: .method public hidebysig static float64 LengthReq (
[in] valuetype Vector3& modreq([System.Runtime]System.Runtime.InteropServices.InAttribute) v
) cil managed
{
.param [1]
.custom instance void [System.Runtime]System.Runtime.CompilerServices.RequiresLocationAttribute::.ctor() = ( 01 00 00 00 )
// ...
}
Read the two signatures side by side. The return type is the same, the [in] flag is the same, and the modreq(InAttribute) is the same required modifier. The only difference in the entire method is the name of the custom attribute in the .param block: IsReadOnlyAttribute for in, RequiresLocationAttribute for ref readonly. That is the whole distinction, and it is a plain custom attribute — not a modifier, not a flag, not anything that participates in signature matching. Why the shape is shared on purpose This is not an accident of implementation; it is the feature's defining constraint. The language team wanted two things at once. First, changing an existing in parameter in a published library to ref readonly must not break any caller that was compiled against the old signature — it is meant to be a saf...

Powered by Readybot.io

C# 12 ref readonly Metadata Explained

Agent Replay Lab: Offline Testing for TypeScript Agents

DEV Community

Agent Replay Lab: a local-first VCR for TypeScript agent tool I/O

Agent Replay Lab: a local-first VCR for TypeScript agent tool I/O I open-sourced Agent Replay Lab, a local-first TypeScript toolkit that records, mock-replays, diffs, and CI-tests the tool I/O of tool-using agents. Project page: https://jarvisosorio.com/agent-replay-lab/ Source: https://github.com/JarvisOsorio/agent-replay-lab It answers a narrower question than observability products, which usually re-run live: does this agent still take the same tool path (name / order / args / responses) against the same recorded tool world, offline, in CI? That is behavioral regression for tool trajectories — not proof that the LLM would make the same decisions again. Local-first. SQLite plus portable JSON. It is not an observability dashboard, eval leaderboard, agent framework, or a full-trajectory “deterministic agent” engine. Honest modes in v0.1 Mode What it does In v0.1? offline / mock Stub tools from the cassette; fail closed if a mock is missing Yes (default) structural compare Diff tool name / order / args / response Yes live-tools Call real tools No (0.2) live-model Call a real model No (0.2) Offline mock stubs tools only. It does not re-execute or pin LLM requests, responses, or tool-selection decisions; agent state machines, retries, or parallel scheduling; or clock, randomness, or other nondeterministic inputs. You can optionally record model.requested / model.responded events for inspection, but agent-replay replay does not play them back as a live model. The cassette category is crowded (Cassette, Agentic Stash, Agent VCR, StoneTape, TracePact, agent-retrace, AgentTape, and others). Fail-closed offline replay, redaction, and CI diffs are not unique inventions — the bar for this repo is a small, honest TypeScript/SQLite knife. Install (not on npm yet) v0.1.0, Apache-2.0. Requires Node.js 22+ and pnpm (the repo pins [email protected]). Clone the monorepo: git clone https://github.com/JarvisOsorio/agent-replay-lab.git
cd agent-replay-lab
pnpm install
pnpm build
pnpm test

5-minute proof

pnpm --filter @agent-replay-lab/example-basic-agent demo
Packages: @agent-replay-lab/core and @agent-replay-lab/cli (agent-replay commands: init, list, inspect, replay, diff, fixture, test, export, import, doctor). fixture create pins tool sequence, args, and responses. test golden --run fails with exit 1 on structural drift. Replay tests use in-memory or local cassette data and make no network calls. Capture seam is wrapTool from @agent-replay-lab/core. Known secret key names and common token shapes are redacted before persist — best-effort, not a PII product. Pair with agentscrub for after-the-fact store remediation. Not in v0.1: MCP adapter, OpenTelemetry adapter, flexible matching, semantic compare, dashboard, live auto-replay, telemetry, or behavioral-policy enforcement. I'm Jarvis Osorio; I built this. Happy to answer questions about the fail-closed mock path or the structural CI gate.

Powered by Readybot.io

Agent Replay Lab: Offline Testing for TypeScript Agents

WarpBuild Migration Costs: Runners, Cache & Snapshots

DEV Community

Migrate from WarpBuild, and what the snapshot was doing

Migrate from WarpBuild and the label is the easy part: every size maps onto a size somewhere else at a comparable rate. What does not map is the part of WarpBuild that is not a runner, which is a metered cache and a snapshot restore priced per job, and those are the lines that decide whether the move saves money or just moves it. The four metered lines a WarpBuild pipeline can carry, and which of them have an equivalent elsewhere. Prices read from warpbuild.com/pricing on 2026-09-21. WarpBuild sells two different products under one name, and which one you are on changes the whole migration. The managed runners are an ordinary metered fleet in its cloud. The bring-your-own-cloud mode is a control plane over machines in your own AWS, GCP or Azure account at a flat $0.002 a minute, and leaving that is a decision about your cloud account rather than about a rate. Either way, three things come off the bill with their features attached: cache storage, cache operations and snapshots. Read those before the label map, because the label map is the trivial half. The label encodes the vCPU count WarpBuild labels read warp----x, where the suffix is the core count rather than a tier number. That is worth stating carefully, because it is an inference rather than a quotation: the rate card prices exactly one macOS shape, 6 vCPU with 14 GB, and the macOS label in the NativeScript/NativeScript workflows is warp-macos-15-arm64-6x, which only lines up if the suffix counts cores. The Linux side behaves the same way, with warp-ubuntu-2404-x64-4x in FuelLabs/fuel-core sitting against a published 4 vCPU with 16 GB row. Once you read it that way the map is mechanical. Sizes double the same way everywhere and the memory ratio is the standard 4 GB per core, so a WarpBuild size lands on the same shape at every destination up to the point where the destination stops publishing sizes. WarpBuild label Shape Published rate Closest Latchkey label Rate there warp-ubuntu-2404-x64-2x 2 vCPU, 8 GB $0.004 a minute latchkey-small $0.0025 a minute warp-ubuntu-2404-x64-4x 4 vCPU, 16 GB $0.008 a minute latchkey-medium $0.005 a minute warp-ubuntu-2404-x64-8x 8 vCPU, 32 GB $0.016 a minute latchkey-large Published per size warp-ubuntu-2404-x64-16x 16 vCPU, 64 GB $0.032 a minute latchkey-xlarge Published per size warp-ubuntu-2404-x64-32x 32 vCPU, 128 GB $0.064 a minute None No equivalent above 16 vCPU warp-ubuntu-2404-arm64-2x 2 vCPU, 8 GB on arm64 $0.003 a minute None Blacksmith arm64 at $0.0025 warp-windows-2022-x64-4x 4 vCPU, 16 GB on Windows $0.016 a minute None GitHub-hosted, or Namespace warp-macos-15-arm64-6x 6 vCPU, 14 GB on M4 Pro $0.08 a minute None Depot or Blacksmith at $0.08 Rates read from warpbuild.com/pricing on 2026-09-21; comparison rates from each vendor's own page the same day. Two labels are quoted from workflow files in named public repositories, warp-ubuntu-2404-x64-4x and warp-macos-15-arm64-6x; the rest of the first column applies the inferred naming rule to each published size rather than reproducing a label we have seen. The rule itself is inferred from those two against the rate card, not quoted from a WarpBuild page, which is a weaker claim and is flagged as one. Three meters leave the bill, and so do their features WarpBuild unbundles several things that most vendors either include or do not sell. Its published add-ons are cache storage at $0.20 per GB-month, cache write or restore at $0.0001 per operation, snapshot restore at $0.04 per job and snapshot storage at $0.025 per hour per snapshot. On a large pipeline those are not rounding errors: 20,000 jobs a month each restoring a snapshot is $800 in restores alone. The useful consequence for a migration is that the saving is easy to compute and is often bigger than the rate difference. The uncomfortable consequence is that the features go with the meters. A destination that includes cache in the price is cheaper and may publish no size at all, where WarpBuild would h...

Powered by Readybot.io

WarpBuild Migration Costs: Runners, Cache & Snapshots

Build-Time Entitlement Gates with Roslyn

DEV Community

Gate features at compile time: a Roslyn source generator for Keyright entitlements

Entitlement checks have a way of multiplying. One feature needs export.pdf, another needs reports.scheduled, a third needs a seats limit — and each one grows its own little block of "resolve the license, read the entitlement, decide what to do on failure." Copy that block around a codebase and it drifts: one call site throws, another returns false, a third forgets to check at all, and somewhere a string literal says exprot.pdf and silently denies a feature to every paying customer. The checks are individually trivial and collectively a liability. This is exactly the kind of repetitive, error-prone, mechanical code a Roslyn source generator exists to remove. Instead of writing the guard, you declare the gate with an attribute and let the generator emit a correct, uniform guard at compile time — and, because the generator sees your whole compilation, it can validate every entitlement name against your declared catalog and fail the build on a typo. This post builds that generator against the Keyright SDK. What we are building You decorate a partial method with the entitlement it requires: public static partial class Features
{
[Gate("export.pdf")]
public static partial void RequirePdfExport();

[Gate("export.pdf", Kind = GateKind.Limit, Limit = "pages-per-day")]
public static partial int PdfPageBudget();

}
The generator fills in the bodies. At a call site you write one line and the branching is gone: public byte[] Export(Document doc)
{
Features.RequirePdfExport(); // throws LicenseException if not entitled
int budget = Features.PdfPageBudget(); // the numeric limit from the signed license
...
}
Every gate now reads the same way, fails closed the same way, and names an entitlement the compiler has already confirmed exists. The diagram below shows the two halves — what happens at build time, and what the emitted code does at runtime. A source generator emits entitlement guards at compile time; the guard enforces the signed license at runtime Compile time Runtime [Gate("export.pdf")] partial method entitlements.json declared catalog Source generator Emitted guard Features.g.cs unknown name → build error KR0001 guard call site LicenseGate.Current cached LicenseInfo Validate() signature + public key allow throw compiles in The marker attribute Source generators ship their own attributes so consumers do not reference a separate assembly. Emit it during post-initialization, before any user code is analyzed: private const string AttributeSource = """
namespace Acme.Licensing;
[System.AttributeUsage(System.AttributeTargets.Method)]
internal sealed class GateAttribute : System.Attribute
{
public GateAttribute(string entitlement) => Entitlement = entitlement;
public string Entitlement { get; }
public GateKind Kind { get; set; } = GateKind.Require;
public string? Limit { get; set; }
}
internal enum GateKind { Require, Limit }
""";
Require emits a void guard that throws when the entitlement is absent; Limit emits a method returning the numeric limit the signed license carries for a named key. The incremental generator Use IIncrementalGenerator — the cached, pull-based model that only re-runs when relevant inputs change. Three inputs feed it: the attribute (registered up front), the annotated methods, and the entitlement manifest supplied as an AdditionalFile. [Generator]
public sealed class EntitlementGateGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterPostInitializationOutput(ctx =>
ctx.AddSource("GateAttribute.g.cs", AttributeSource));

    IncrementalValuesProvider<GateModel> gates = context.SyntaxProvider
        .ForAttributeWithMetadataName(
            "Acme.Licensing.GateAttribute",
            predicate: static (node, _) => node is MethodDeclarationSyntax m
                && m.Modifiers.Any(SyntaxKind.Pa...


Powered by Readybot.io

Build-Time Entitlement Gates with Roslyn

Planet Symphony: Encode Messages as Music

DEV Community

Planet Symphony

Created for DurHack 2026 with the theme of "Composition"! We were also heavily inspired by QRTs Challenge "Information Hidden in Plain Sight" as well as Marshall Wace's "New Worlds". Humans have been looking to the stars for thousands of years. Many have wondered what exists beyond our little planet, in the great unknown. We have broadcast ourselves far and wide with radio waves, radiation, and space junk… … oddly nobody has answered. Maybe we should change the modality of our contact. Perhaps new worlds might respond to the power of song. Video Demo Our Journey In our time at DurHack 2026, we wanted to explore the possibilities of encoding data as music. We delivered in creating a Transmitter and Receiver for interplanetary communication, with data being entirely encoded within the melody itself (no tricks). What does it do? Planet Symphony begins with a message (plaintext) and a choice of key and musical scale (e.g., F# major-pentatonic). Next, that message is encoded algorithmically into a melody within a set of configurated parameters (cluster size, octaves). This melody is then sent to the Transmitter for a waiting receiver to hear. The waiting Receiver is a trained model for identifying musical notes, which is then passed into the algorithmic decoder. As the encoder is deterministic, as long as no notes are missed by the receiver the original message should be decoded with zero error. during encoding a specific music scale is chosen (demo uses f# major pentatonic) and only those notes are selected from so all notes are diatonic (in the specified key). when encoding characters to musical phrases, smaller intervals are prefered to sound more scalic. a drone note of the root is used to add a grounding tone and allow melodies to be properly parsed by your ears. paired with a specific pattern of cortchets and quavers the melody sounds more organic and pleasant. What are we most proud of? Malachi: Creating a deterministic encoder and decoder which can effectively translate messages into song and back to messages. Sarunas: Translating the notes into a song and having it actually sound good. Matty: Creating a visual language to fit with the project. Jack: Training a CNN to accurately read a spectrogram and improving accuracy from pure audio analysis. See our github, https://github.com/sarunasbend/durhack26, and try it yourself! See our devpost for more information, https://devpost.com/software/bluestooth What we learned Data transfer methods are often focused on speed, efficiency, trying to be out of your attention. We learned how data transfer doesn't have to fit these boxes and maybe slowing down can benefit all of us.

Powered by Readybot.io

Planet Symphony: Encode Messages as Music

Truco Mineiro em TypeScript: Manilhas e Comparador

DEV Community

Truco mineiro em código: manilhas fixas e comparador de cartas

No truco paulista a manilha muda a cada mão, porque depende da vira. No truco mineiro não: são quatro cartas fixas, e isso muda bastante o código do comparador. Aqui vai a versão que usamos para conferir jogada, em TypeScript. As regras que o código precisa conhecer Baralho de 40 cartas (o de 52 sem 8, 9 e 10). Quatro manilhas que nunca mudam, da mais forte para a mais fraca: 4 de paus (zap), 7 de copas, ás de espadas (espadilha) e 7 de ouros (pica-fumo). Não existe vira. As demais cartas seguem esta ordem: 3, 2, ás, rei, valete, dama, 7, 6, 5, 4. Entre elas o naipe não desempata. O comparador type Carta = { valor: string; naipe: "P" | "C" | "E" | "O" };

// zap, 7 de copas, espadilha, pica-fumo
const MANILHAS = ["4P", "7C", "AE", "7O"];
const ORDEM_COMUM = ["3", "2", "A", "K", "J", "Q", "7", "6", "5", "4"];

const id = (c: Carta) => c.valor + c.naipe;

export function forca(c: Carta): number {
const i = MANILHAS.indexOf(id(c));
if (i >= 0) return 100 - i; // manilhas sempre acima das comuns
return 50 - ORDEM_COMUM.indexOf(c.valor); // empata pelo número
}

export function vence(a: Carta, b: Carta): "a" | "b" | "empate" {
const fa = forca(a);
const fb = forca(b);
return fa === fb ? "empate" : fa > fb ? "a" : "b";
}
Dois detalhes que dão bug se passarem batido: Ás de espadas é manilha, os outros ases não. A checagem das manilhas precisa vir antes da ordem comum. O mesmo vale para o 7: só o de copas e o de ouros são manilha; o de paus e o de espadas são 7 comuns. Empate de verdade. Dois 3 de naipes diferentes empatam. Se o seu motor desempata por naipe (como no paulista, quando duas manilhas disputam), vai errar a rodada. Testes que valem a pena const c = (valor: string, naipe: Carta["naipe"]): Carta => ({ valor, naipe });

console.assert(vence(c("4", "P"), c("7", "C")) === "a"); // zap vence tudo
console.assert(vence(c("A", "E"), c("A", "C")) === "a"); // espadilha x ás comum
console.assert(vence(c("3", "O"), c("3", "P")) === "empate");
console.assert(vence(c("7", "P"), c("Q", "C")) === "b"); // 7 comum perde para dama
A escalada da mão Outro ponto em que o mineiro é diferente: a mão limpa vale 2 pontos e sobe em 4 (truco), 6, 10 e 12. Algumas fontes contam 1, 3, 6, 9 e 12, porque a escada varia por região. No nosso motor a escada fica numa tabela de configuração, não espalhada pelo código: const ESCADA_MINEIRO = [2, 4, 6, 10, 12];
Se você escrever o seu, deixe isso configurável, porque a turma de cada lugar joga de um jeito. Para conferir na mesa A página com a ordem das cartas e as diferenças para o paulista está em Truco mineiro: manilhas fixas e regras. Para jogar uma mão contra o computador, o treino fica em buteco.club/treinar/truco. Fontes das regras: Pagat e FocusGN.

Powered by Readybot.io

Truco Mineiro em TypeScript: Manilhas e Comparador

Kodama: Open-Source AI Agent Pipelines for Teams

DEV Community

Why Kodama Being Apache-2.0 Open Source Feels Like a Breath of Fresh Air

A coding agent in a terminal is great for one developer on one laptop. The trouble starts the day your company wants to run agents as a team across a hundred repositories—with separate roles, review gates, audit trails, and shared credentials. Right now, almost everyone trying to solve that problem wants to sell you a new SaaS subscription. You know the pitch: connect your GitHub organization to our cloud, hand over write tokens to private repositories, route your internal context and logs through our multi-tenant black box, and pay per seat—or marked-up compute minutes—while your own Kubernetes cluster sits right there doing nothing. That is why stumbling onto Kodama feels so refreshing. Instead of wrapping container orchestration and human approvals behind a hosted paywall, Kodama ships the whole software factory as Apache-2.0 open-source software in Go—ready to run on rootless Podman on a Linux laptop in minutes, and on the Kubernetes cluster you already operate in production. What Kodama Actually Is Louis introduced Kodama on DEV with a line that sticks: “Like OpenClaw, but for teams.” Even if you have never touched OpenClaw, the core product model fits in one sentence: A pipeline is an ordered list of actions; an action runs a container or requests an answer; an execution runs a pipeline once; a task is a conversation that starts executions. If you have ever used container-native CI like Drone, the first half feels immediately familiar: containers run in order, share a workspace, and exit zero or nonzero. The second half is what turns CI into a software factory: An action does not have to run a container. It can pause the line and request a decision from a role (role/reviewer) or an automated policy service (service/). An agent is just another container command. Claude Code, Codex, OpenCode, Pi, or an ordinary shell script all plug into the exact same pipeline alongside your build and test images. Tasks hold the shared history. People, scripts, or editor agents submit tasks via REST, MCP, or kodactl. Reviewers work from a role inbox; failed executions land in the operators' inbox. How It Works Under the Hood What makes Kodama a pleasure to inspect is how cleanly its architecture draws boundaries (KODAMA.md lays out the entire v2 design and 20 acceptance criteria in one readable spec): Shell (Control Plane): Owns tenants, projects, OIDC and service-token identity, authorization, tasks, role inboxes, envelope-encrypted secrets, and a single operation router with REST and MCP edges (used by kodactl and editor integrations alike). Kernel (Execution Engine): Advances ordered executions, enforces action deadlines, and recovers cleanly after crashes—without depending on HTTP, Git, OIDC, or message brokers. Durable State & Messaging: PostgreSQL is the source of truth for state, approval receipts, and the event table (isolated per tenant and project with forced row-level security). NATS JetStream distributes events and live logs; if the broker hiccups, integrations wait, but executions do not lose state. Workloads (Podman or Kubernetes Jobs): Locally, actions run as rootless Podman containers. In production, each container action is a plain Kubernetes Job mounting a workspace PVC—no custom CRDs and no Kubernetes operator to babysit. Workloads authenticate back to the Shell with short-lived execution tokens. Userland Services: Git hosting, coding agents, and external adapters live outside control-plane internals and use the public API and approval verification. Why Open Source Beats Another Agent SaaS Here Some software categories make sense as SaaS. Your software delivery control plane—where code, credentials, review authority, and release history meet—is where open source and self-hosting shine brightest. Four details in Kodama show why: 1. Your keys, code, and decisions stay in your cluster With Kodama, your repositories, pipeline state, and approval records stay in your own database and cluster. Even better: workload co...

Powered by Readybot.io

Kodama: Open-Source AI Agent Pipelines for Teams

MCP Security Audit: 10 GitHub Repos Scanned

DEV Community

I Scanned 10 Popular MCP Server Repositories on GitHub (4,409 Files) — Here Are the Security & Token Bloat Results

As AI coding agents (Claude Code, Cursor, Windsurf, GitHub Copilot, and Zed) rapidly adopt the Model Context Protocol (MCP), repositories are shipping .mcp.json configs, tool definitions, and agent workflows faster than security teams can review them. To measure the real-world security posture and LLM token efficiency across the ecosystem, I ran RepoXray (npx rxray)—an open-source, zero-dependency static security & supply-chain scanner—against 10 widely used MCP repositories on GitHub. ⚡ Audit Snapshot (TL;DR) Total Files Scanned: 4,409 files across 10 popular MCP repos Average Scan Speed: 1.53 seconds per repository (100% local static analysis) Supply-Chain Risk: 70% of scanned repos contained dependencies with known CVEs Context Bloat: 100% lacked an .aiignore file, wasting 2.17M+ tokens on lockfiles and build artifacts 📊 Aggregate Findings Across 10 MCP Repositories Repository Score Grade Scan Time 🔴 Critical 🟠 High 🟡 Med Optimizable LLM Tokens tavily-ai/tavily-mcp 99/100 🟢 A+ 0.77s 0 0 1 15,073 (46%) modelcontextprotocol/servers 91/100 🟢 A 1.49s 0 0 5 335,953 (24%) lastmile-ai/mcp-agent 68/100 🟡 C 0.89s 1 0 1 925,638 (34%) executeautomation/mcp-playwright 68/100 🟡 C 1.67s 1 1 1 340,576 (74%) browserbase/mcp-server-browserbase 68/100 🟡 C 0.98s 1 2 1 58,969 (67%) mendableai/firecrawl-mcp-server 68/100 🟡 C 1.15s 1 1 1 119,947 (26%) excalidraw/excalidraw-mcp 64/100 🟡 C 2.80s 2 4 2 38,593 (43%) modelcontextprotocol/inspector 63/100 🟡 C 2.62s 4 5 2 179,301 (4%) punkpeye/fastmcp 50/100 🟠 D 1.57s 3 9 1 68,102 (11%) upstash/context7 28/100 🔴 F 1.42s 12 4 1 87,521 (10%) Note: Scans were executed against the default branch of each repository using npx rxray. Scores reflect static security rules, OSV.dev advisory lookups, and AI context hygiene. 🔍 3 Key Takeaways From the Audit 1. 70% of Scanned Repos Have Dependencies With Known CVEs (SC-OSV-VULNERABILITY) 7 out of 10 repositories contained direct or transitive manifest versions with security advisories published in OSV.dev. Why this matters for MCP: Unlike sandboxed web apps, MCP servers execute locally on developer workstations—frequently holding privileges to read/write local filesystems, control headless browsers, and access production API keys. A vulnerable dependency inside an MCP server carries immediate local code execution risk. 2. Unpinned npx -y and Mutable Git References in MCP Configs Several MCP configurations and README setup guides instruct developers to launch servers using mutable targets: Unpinned Registry Execution (SEC-MCP-UNPINNED-NPX): Running npx -y some-mcp-server@latest auto-downloads and executes whatever version was pushed most recently without user confirmation. Mutable Git Branches (SC-MCP-UNPINNED-GIT): Using npx github:owner/repo or uvx --from git+https://... without pinning to an immutable 40-character commit SHA. The Risk: If an upstream npm maintainer account is hijacked or a Git tag is force-pushed, every developer opening their IDE automatically pulls and executes the malicious payload on startup.
json
// ❌ Risky: Pulls latest unverified code on every IDE startup
{
"mcpServers": {
"example": {
"command": "npx",
"args": ["-y", "some-mcp-server@latest"]
}
}
}

// ✅ Safer: Pin to an exact, audited version
{
"mcpServers": {
"example": {
"command": "npx",
"args": ["[email protected]"]
}
}
}

  1. 100% Lacked an .aiignore File — Wasting 2.17 Million Context Tokens
    None of the 10 repositories included an .aiignore (or .cursorignore / .claudeignore) file.

Across the 10 repos, 2,169,673 tokens came purely from lockfiles (package-lock.json, pnpm-lock.yaml, uv.lock), minified bundles, and static JSON fixtures. When coding agents index a repository without context ignore rules, this noise pollutes the LLM context window, increases API costs, and degrades reasoning quality.

🛠️ Test Your Own Repository in <2 Seconds (Zero Dependencies)
You can run the exact same audit on your own codebase ...

Powered by Readybot.io

MCP Security Audit: 10 GitHub Repos Scanned

Taiwan Solar Terms CSV: Official Data vs Estimates

DEV Community

Publishing a Taiwan Solar Terms CSV Without Blurring Official Data and Estimates

Solar terms are useful for calendars and teaching materials, but a table of dates can hide an important distinction: was each time published by a meteorological agency, or calculated by the person who assembled the table? For nongminli.tw, we assembled a UTF-8 CSV of Taiwan's 24 solar terms for each year from 2020 through 2035. It has 384 rows. The value_kind column is the part I would check first before using any row: 48 rows for 2026 and 2027 come from the Central Weather Administration's published annual calendar tables. 336 rows for other years are calculated estimates based on the Sun's apparent ecliptic longitude. They are not CWA announcements. Every row uses Asia/Taipei and gives a local date and time to the minute. This makes the file easier to use in a Taiwan-facing calendar without silently mixing UTC and local time. For example, a downstream app can require an explicit choice before displaying a time as official: import csv

with open("taiwan-solar-terms-2020-2035.csv", encoding="utf-8", newline="") as file:
rows = list(csv.DictReader(file))

official = [row for row in rows if row["value_kind"] == "CWA published calendar"]
estimated = [row for row in rows if row["value_kind"] == "calculated estimate"]
assert len(official) == 48 and len(estimated) == 336
Keeping the provenance field in the application model is more useful than hiding the distinction in a footnote. What we checked We compared the calculation with the CWA's 2026 calendar and 2027 calendar. Across those 48 solar terms, the local dates matched and the largest difference in the published minute was one minute. That is a useful check of the calculation; it does not verify the remaining years against official tables. Some annual PDFs include a few terms from the following January. We still classify all 2028 rows as estimates because the dataset takes a complete set of 24 terms from each calendar year. The release also includes 1991–2020 monthly climate normals for five Taiwan stations: Taipei, Taichung, Kaohsiung, Hualien, and Hengchun. There are 480 rows: five stations × eight measurements × twelve months. These are long-term monthly averages, not a daily weather forecast. The Kaohsiung entry refers to the station used by the source table, not an interchangeable modern station record. Make the release auditable Alongside the CSV files, we published a manifest with the source URLs, capture dates, and SHA-256 hashes of the source snapshots and output files. A small python3 scripts/verify_data.py command checks the output hashes, column shapes, and row counts. The script works offline and does not modify the files. The license note also separates our own calculation, selection, and explanations from the CWA's original records. Our own contribution is offered under CC BY 4.0; CWA source material remains subject to its original terms and attribution. Reusing the CSV should not be described as receiving a new license from the CWA or as an official endorsement of the calculated rows. If you use this data to build a calendar, keep value_kind visible in your pipeline. If you need an exact official time for research or a consequential decision, check that year's original CWA publication rather than assuming an estimate is authoritative. The files, field notes, and verification command are in the public data directory; the site's broader source notes explain where other calendar fields come from. An AI agent wrote this article. Its dataset counts, URLs, comparison result, and licensing distinctions were checked against the published files and source notes before publication.

Powered by Readybot.io

Taiwan Solar Terms CSV: Official Data vs Estimates

Self-Host AI Agents with Traefik and Coolify

DEV Community

Self-Host AI Agents: Traefik & Coolify on Hostinger KVM VPS

💡 Originally published on Bijak Technology Enterprise Journal with full interactive parameter benchmarks. Unchecked cloud infrastructure bills jumping from $40 to $380/month during peak LLM inference are a harsh reality for DevOps teams. When deploying AI automations, operational margins are frequently eroded by API quota markups and egress bandwidth charges without transparent warning. A self-hosted approach on dedicated VPS nodes serves as the ultimate line of defense for corporate budget efficiency. Multi-AI Agent Orchestration Architecture Digital transformation requires autonomous entities capable of processing decision context in real time. Multi-agent orchestration distributes heavy computational workloads into isolated microservices: web scraping agents, embedding processors, and LLM inference engines (such as Ollama or vLLM). Isolating each agent within Docker containers caps maximum RAM usage to 4GB per instance, preventing cascade failures caused by memory leaks. Inter-module communication executes over internal Docker networks via REST APIs or local gRPC sockets, maintaining inter-container latency under 1.2 milliseconds. Comparison Parameter Self-Hosted (Traefik + Coolify) Managed Cloud Platform (AWS ECS/RDS) SSL & Routing Automation Automatic via Traefik Labels Vendor Load Balancer Lock-in CI/CD Deployment Time < 3 Minutes (Coolify Git Webhook) 5–10 Minutes (Cloud Pipeline) pgvector Query Latency < 12ms (Local Docker Network) 45–110ms (Cross-VPC Network) Monthly Cost Structure ~$11.99 - $23.99/mo (Fixed) $320 - $850/mo (Fluctuating) Explore more in-depth system architecture blueprints, FinOps strategies, and enterprise implementations at Bijak Technology.

Powered by Readybot.io

Self-Host AI Agents with Traefik and Coolify

Hire Python Engineers, Not GIL Trivia Experts

DEV Community

Would you ask a driver how the suspension works?

If you’re interviewing a Python developer, you might be tempted to ask how the Global Interpreter Lock (GIL) works. A hard question. What would a good answer tell you about the person you’re about to hire? Imagine you spend half your day getting from one meeting to another in London. You’d like to use that time to work, so you decide to hire a driver. You ask around, but no one you know can recommend a driver. So you post a job ad. Fifteen people apply. So what do you ask? Have you driven professionally before? How well do you know London? Any traffic fines? Can you get to my place by seven on a Monday morning? You ask all fifteen candidates and get perfectly reasonable answers from every one. You still have to pick someone. A smooth ride matters to you, so you try a harder question. “Could you explain a suspension system?” About half the candidates can’t give you much of an answer. For the first time all afternoon, you can cross a few names off your list. So you ask another question about the suspension. Congratulations, you’ve hired a former mechanic as your driver. On Monday morning, you’re in the back seat with your laptop open. The ride is a little rough and you spill coffee on your shirt. “It’s the suspension” your driver says. “Too stiff”. He knows a shop that can adjust it. Two days later, the car is back. You’re glad you hired someone who knows these things. The following week, he takes a sharp bend by the Thames. This time the coffee lands on your keyboard. “Could be the springs” he says. “I can call the shop.” You’ve already lost two days. Before you lose another two, you ask him to take that bend a little slower on the way home. “All right” he says. The coffee stays in your cup. “Could still be the springs” he adds. Back to the Python interview. The job ad might say “Python developer.” What you need is an engineer who can figure out why something is going wrong. Python may be their strongest skill. Knowing when to use it is part of the job. A customer says our dashboard takes ten seconds to load. When I log in, it opens instantly. Where would you start? I’d bring that story to the interview and see what the candidate asks. Where does the time go? Is the worker busy doing calculations or is it waiting for the database? If the candidate has a question about the GIL, we can talk about it. If they want to see the database query, I’d pull it up. Either way, I get to see how they work. Before you ask a harder question, who are you looking for? A mechanic or a driver?

Powered by Readybot.io

Hire Python Engineers, Not GIL Trivia Experts

Go Load Balancer: Circuit Breaker Lessons

DEV Community

My circuit breaker let 100 requests through: lessons from building a small load balancer in Go

I built loadbear, a small HTTP load balancer / reverse proxy in Go, mainly to see how concurrency and resilience patterns fit together in one program. It has four balancing strategies, active health checks, a circuit breaker, retries, per-IP rate limiting and an admin port with Prometheus-style metrics. It is not meant to replace nginx or HAProxy. After the first release, a reviewer asked a question that turned into the most useful week of the project: "How is half-open admission bounded?" Here is what we found, what was wrong, and what the numbers say. Repo: loadbear 1. The half-open state let everything through A circuit breaker has three states. When the cooldown ends, it moves from open to half-open and should let one probe request through to test the backend. If 100 requests arrive at that moment, all 100 shouldn't hit a backend that just recovered. I wrote a test with 100 concurrent goroutines. All 100 were admitted. The check looked at the state, but nothing limited how many requests could pass it. The fix is to make admission a single atomic operation. A simplified sketch of the idea (not the exact code): type breaker struct {
state atomic.Int32 // closed, open, halfOpen
probing atomic.Bool // is a probe in flight?
}

func (b breaker) allow() bool {
switch b.state.Load() {
case closed:
return true
case halfOpen:
// only the goroutine that flips false -> true gets to probe
return b.probing.CompareAndSwap(false, true)
default:
return false
}
}
The less obvious part is the other direction: the probe slot has to be released on every exit path, including timeouts, client cancellation and panics. Otherwise a cancelled probe could leave the breaker stuck in half-open. I added a regression test for that and a deferred release. 2. Retries were less safe than they looked I had limited retries to GET, HEAD and OPTIONS, which felt safe. Review and tests found more cases: A GET with a body that cannot be replayed was still retried. A retry could start after the client's deadline had already passed, because backoff did not look at the request context. Once response headers have been sent to the client, retrying is impossible, and the code needed to say so explicitly. Retries now require a replayable body, an uncommitted response and the original request context. I also added fault modes to the fake backend (hang, close, drop-mid, slow), so these cases can be reproduced on demand. 3. WebSocket "didn't work" and SIGHUP was broken The README said WebSocket upgrades were unverified. When I wrote a test, it failed immediately with a 400: The proxy stripped Connection and Upgrade from every request as hop-by-hop headers. My access-log response wrapper did not implement http.Hijacker or http.Flusher, so ReverseProxy could not set up the tunnel. Wrapping a ResponseWriter is an easy way to silently lose interfaces. After the fix, an HTTP/1.1 upgrade handshake and a bidirectional byte echo work through the proxy. I only verified that, not full RFC 6455 behavior, and the README says so. While adding config reload, I found that SIGHUP was wired into the shutdown signals. In v0.1.x, sending SIGHUP killed the process instead of reloading it. It now reloads the backend list and strategy, keeps state for unchanged backends, rejects invalid configs and lets in-flight requests finish. Only that part reloads; other settings still need a restart. 4. Benchmarks, and a measurement bug in my own tool I wrote a small load generator (standard library only) so the benchmark works on Windows too. The first run reported errors for roughly one request per worker. The proxy was fine: my generator cancelled in-flight requests at the end of the measured window and counted them as failures. The fix was to stop starting requests at the cutoff and let in-flight ones drain. Final results on a Ryzen 5 7600 (Windows 11, Go 1.27.4, 10 s measured, concurrency 64, rate limiting off, access logs off, clien...

Powered by Readybot.io*

Go Load Balancer: Circuit Breaker Lessons

GitHub Actions always() After Cancellation Explained

DEV Community

GitHub Actions if: always() runs a step after cancellation

GitHub Actions if: always() is a function that returns true and checks nothing, so a step guarded with it starts after somebody presses cancel. The companion belief, that adding any condition removes the built-in success check, is the opposite of what the parser does, and it is worth unlearning before you add success() to conditions that never needed it. The grid on the left is the table reproduced in the body of this page. The rewrite on the right is quoted from ConvertToIfCondition in actions/runner. What this error means Nothing here is an error message. The symptom is a step that ran when you did not expect it to, and the bill or the side effect that followed. Somebody cancels a run halfway through and a deployment still goes out, or a cleanup job tears down an environment that the next run needed, or a notification announces a failure for a run a person deliberately stopped. In the log the step is green and the run conclusion is cancelled, which is an unusual combination and the clearest sign that this is what you are looking at. The second half of the problem produces the mirror image: a step that never runs after a failure, because somebody added success() to a condition that already had it. .github/workflows/ci.yml (illustrative) name: Upload logs if: always() # true in every state, cancellation included run: ./upload-logs.sh name: Notify if: ${{ !cancelled() }} # every state except cancellation run: ./notify.sh Common causes always() was used to mean success or failure The function name reads like natural language and the intention is almost always narrower than what it does. Teams reach for it on artifact uploads, notifications and teardown steps, and it works until the first time somebody cancels a run, at which point the teardown runs against a half-finished environment. A status function was added that was already there The mirror mistake, and it is caused by the belief that any condition drops the gate. Writing success() next to a branch test changes nothing, because the parser would have added it. Writing failure() next to one changes everything, because now the step runs only after a failure. The negated form was written without braces A leading exclamation mark is YAML tag syntax, so the bare form is rejected at parse time rather than misbehaving at run time. It looks like a problem with the function when it is a problem with the quoting. A job condition was copied from a step condition The two positions allow different contexts. A job-level condition accepts the four status functions but not steps, env, job, runner or the file-hashing function, so a condition that works on a step can be rejected outright when it is moved up to the job. How to fix it Say which states you want, then pick the expression Write down whether the step should run after success, after failure, and after cancellation. Success and failure but not cancellation is the negated cancellation check, in braces. Failure only is failure(). Success only is nothing at all. All three, including cancellation, is always(), and it should be rare. Replace always() on anything with an external effect Deployments, teardown, cloud resources and anything that posts to a third party are the steps where cancellation should mean stop. Swap the function and leave the rest of the step alone..github/workflows/ci.yml (illustrative) name: Tear down preview environment if: ${{ !cancelled() }} run: ./teardown.sh Stop adding success() to ordinary conditions A condition with no status function in it is already gated. Removing the redundant call makes the expression shorter and makes the ones that really do override the gate visible in a diff..github/workflows/ci.yml (illustrative) name: Deploy to production if: github.ref == 'refs/heads/main' run: ./deploy.sh ``` Combine a status function with your own test explicitly When you do want a step to run after a failure and only in some cases, name both halves. The status function is what turns the gate...

Powered by Readybot.io

GitHub Actions always() After Cancellation Explained

ProjektSUBRO: Automating Insurance Claim Recovery

DEV Community

ProjektSUBRO

This is a submission for the MLH x DEV Writing Challenge. What I Built ProjektSUBRO connects upfront funding with automated recovery of denied insurance claims. Healthcare providers need cash for staff, supplies, and patient care—even while insurers review payments for care already delivered. Why this matters Nearly $18 billion spent by U.S. hospitals fighting claim denials in 2025. AHA 41.2% of rural hospitals operated at a loss, and 417 were vulnerable to closure, according to Chartis’ 2026 analysis. Chartis 68.6% of denied claims were eventually paid in Premier’s survey of 2023 claims—but providers averaged three review rounds, each taking 45–60 days. Premier SUBRO prices denied claims, simulates upfront funding, and runs recovery workflows. Evidence, agent actions, and financial outcomes stay visible in one workspace. $$ \text{Expected profit} = (\text{Collectible} \times P(\text{recovery})) - \text{Advance} - \text{Costs} $$ The demo uses 120 synthetic claims. No real patient data or money. Recovery probabilities are assumptions. Demo Try SUBRO live Open Full recovery, inspect the claim, and approve the simulated advance. Follow the recovery run, then check Ledger to compare the forecast with the recorded outcome. Other scenarios demonstrate missing evidence, expired deadlines, repeated rejection, conflicting records, and partial payment. Partner Technologies The current demo does not integrate MLH partner APIs. I built it with Next.js, React, TypeScript, Tailwind CSS, Recharts, and Vercel. Deterministic functions handle pricing; a bounded recovery agent supports optional AI-assisted appeal drafting. Browser localStorage preserves the sandbox. Hackathon Experience I built SUBRO for SanD Hacks Fall 2026. The hardest parts were preventing duplicate transactions, separating forecasts from outcomes, and deciding when recovery should stop. My biggest takeaway: trustworthy automation depends on structured records, evidence, and guardrails. That shaped the audit trail and ledger—each action needed enough context to explain what happened and how it affected the money.

Powered by Readybot.io

ProjektSUBRO: Automating Insurance Claim Recovery

Why AI Designs Keep Using Purple: Color Trends Explained

DEV Community

AI Loves Purple: Most and Least Used AI Colors

Ask any AI coding tool for a landing page and watch what happens. A dark hero section. A glowing card or two. A button that fades from indigo into purple. Ask again with different words and you often get the same thing. If you have noticed this, you are not imagining it. Developers have been joking about "the purple problem" for more than a year, and the creator of Tailwind CSS publicly apologized for his part in it. But the jokes hide a more useful question: which colors does AI actually use the most, which does it use the least, and what can you do about it when you ship real products? This guide breaks down the colors most used by AI, where they come from, and the exact steps to replace them in your own code. We answer that with a clear line between three kinds of evidence: what was actually measured, what is well documented history, and what is designer observation. Then we give you code you can use today to find and replace AI default colors in your own projects. The Short Answer Most used by AI: Blue, indigo, violet and purple, often as gradients. Stock Tailwind swatches. White or near white backgrounds with slate grays. Dark backgrounds with glowing purple or blue accents. Least used by AI: In the one academic study we found that counted colors, pink and black were the least frequent. Designers also point to warm and earthy colors as rare in AI output, such as deep greens, ochre, amber, terracotta and warm off whites. That second group comes from observation, not a published count. NOTE — How we graded the evidence We could not find a large, independent census of every color AI tools produce. Anyone who gives you exact percentages for "AI's favorite color" is guessing. What exists is a mix of a small study, a well documented origin story and a lot of consistent practitioner experience. Each claim below is labelled. Colors Most Used by AI: The Defaults You See Everywhere Color pattern Where it shows up Evidence strength Indigo and violet buttons Primary buttons, links, focus rings Strong: documented origin, widely reported Blue to purple or pink to purple gradients Hero sections, headline text, buttons Strong: widely reported by designers Blue as the default scheme Generic sites when no scheme is given Measured in a 2026 study Dark background with neon purple glow SaaS and AI product landing pages Moderate: designer observation Pure white backgrounds and slate grays Cards, sections, body text Moderate: appears in AI slop checklists Pure black body text Paragraphs and headings Moderate: appears in AI slop checklists 1. Indigo and violet: the signature The most famous AI color is the indigo to violet range. The origin story is unusually well documented. In August 2025, Tailwind CSS creator Adam Wathan posted on X: "I'd like to formally apologize for making every button in Tailwind UI bg-indigo-500 five years ago." He acknowledged that this default had led to AI generated interfaces coming out indigo. The mechanism people describe is simple. Tailwind's examples and templates used indigo as the primary color, and that convention is plain to see in the Tailwind color palette documentation. Developers copied those examples into tutorials and open source projects. Models trained on that code learned that "modern web design" and "indigo button" go together. A word of caution: that causal chain is plausible but untested. Nobody has published an analysis of the training data showing how often indigo appears. The Tailwind history is fact. The idea that it is the main cause is a well supported hypothesis. 2. Gradients in the blue to purple range Several design write-ups call the indigo to purple gradient the loudest tell of AI generated design. Gradient headline text, gradient "Get started" buttons and soft aurora backgrounds behind a dark hero are the usual forms. Pink to purple gradients that trended around 2020 and 2021, and blue to indigo hero looks associated with the Linear aesthetic, also feed into this pool of common p...

Powered by Readybot.io

Why AI Designs Keep Using Purple: Color Trends Explained

Free Sitemap & Indexing Tool for Developers

DEV Community

Build a Free Sitemaps & Indexing Utility: Streamline Your Website Structure for Developers

Hi everyone! 👋 As developers and site administrators, we often spend a lot of time structuring our web properties, handling configurations, and making sure search engines can easily crawl and index our pages. Having clean, fast, and accessible tools right in the browser can save hours of repetitive work. Recently, I built and published a handy utility page designed specifically to help streamline sitemap management and quick generation tasks: Sitemap & Indexing Tool. Why I Built This Utility When managing static sites, blogs, or custom web projects, generating a quick structural template or mapping out URL directories shouldn't require complex CLI setups every single time. I wanted something lightweight, client-side, and instantly accessible. Key highlights of the tool include: Clean Structure: Generates well-formatted XML/HTML sitemap blocks instantly. Client-Side Efficiency: Runs directly in your browser with zero bloat or complex dependencies. Copy-to-Clipboard Functionality: Easily copy generated snippets with a single click to plug right into your project files. A Quick Look at the Code Logic If you're curious about how it handles the dynamic clipboard copying on the frontend, it uses a straightforward, modern JavaScript Clipboard API implementation:
javascript
function copySnippet() {
const codeElement = document.getElementById('sitemapCodeText');
const textToCopy = codeElement.innerText;

navigator.clipboard.writeText(textToCopy).then(() => {
    console.log('Successfully copied to clipboard!');
}).catch(err => {
    console.error('Failed to copy text: ', err);
});

}

Try It Out & Contribute Ideas
You can test the live tool directly here: 👉 Sitemap & Indexing Tool.

I built this as part of a broader set of web utilities on my site seobasil. If you have any feature requests, optimizations, or suggestions on how to make developer utility scripts faster and more responsive, drop a comment below. I’d love to hear your feedback!

Happy coding! 🚀

Powered by Readybot.io

Free Sitemap & Indexing Tool for Developers

AWS Lambda MicroVMs for Secure AI Code Sandboxes

DEV Community

Lambda MicroVMs: run AI code safely in a sandbox

Lambda MicroVMs are the building block I had been waiting for to solve one very specific problem: running code that I did not write myself. An AI assistant suggests a solution, an agent wants to test it, a learning platform lets beginners write their first loops. Simply running that code inside your own Lambda function would be reckless. Operating a container cluster just for sandboxes, on the other hand, feels a lot like “servers are back”. On November 11, 2026, AWS added x86 support to MicroVMs, and that was my reason to take a thorough look. In this article we build a small code sandbox together: one MicroVM per learning session, controlled by a Lambda function that you deploy with the Serverless Framework. On top of that you get my honest take on where the model shines and where you are better off sticking with regular functions. In short Lambda MicroVMs start an isolated VM with its own HTTPS endpoint from a snapshot. It lives for up to eight hours and is put to sleep when idle. This lets you build sandboxes for AI-generated or third-party code without running servers. The biggest pitfall: outbound internet access is open by default, and for untrusted code you have to close it deliberately. What you will learn in this article What Lambda MicroVMs are and how they differ from Lambda functions How the snapshot, the /ready hook and the /run hook work together How to build a code sandbox step by step with TypeScript and the Serverless Framework Which security settings are mandatory for untrusted code What a session costs What are Lambda MicroVMs? A MicroVM is a very lightweight virtual machine. AWS builds it on Firecracker, the same virtualization technology that regular Lambda functions run on. What is new is that you now control the VM yourself: you start it, it gets its own HTTPS endpoint, it keeps its memory and disk state, and when it is idle it is put to sleep instead of being thrown away. The trick behind this is the snapshot. When you create a MicroVM image, Lambda runs your Dockerfile, starts your application and waits until it reports that it is ready. Then Lambda freezes the complete state of memory and disk. Every new MicroVM starts from this frozen state, so your application does not have to boot again. If you know SnapStart, you will recognize the principle. Two more terms come up all the time. Suspend means the VM is paused, memory and disk are kept, and you only pay for storing the state. Resume wakes it up again, automatically on the next request if you want. In addition, there are hooks: HTTP endpoints in your application that Lambda calls at fixed points in the lifecycle, for example /ready during the build or /run when a new VM starts. Lambda function or MicroVM: the difference Lambda function Lambda MicroVM Invocation event or invoke, Lambda distributes the load own HTTPS endpoint per VM, no load balancer Duration at most 15 minutes per invocation up to 8 hours including suspend State ephemeral, no guarantee between invocations memory and disk survive a suspend Isolation execution environment is reused for invocations of the same function one VM per session, job or tenant, full operating system Billing per request and duration baseline size per second while RUNNING, storage cost while suspended Bottom line: functions are built for many short, similar invocations. MicroVMs are meant for longer, stateful sessions in which someone (or something) brings code you do not trust. When are Lambda MicroVMs worth it? Among other use cases, AWS lists sandboxes for AI code, interactive development environments, notebooks, security scans and multi-tenant CI/CD runners. I would make the decision with three questions. First: are you running code you do not control? If yes, a dedicated VM per session is the cleanest boundary you can get with serverless. Second: does the session need state for minutes or hours, for example installed packages, an open database connection or a conversation with an agent? That is where su...

Powered by Readybot.io

AWS Lambda MicroVMs for Secure AI Code Sandboxes

uv 0.13 Installs 6,000-File Packages 32% Faster

DEV Community

uv 0.13's tar-codec backend installs a 6,000-file sdist in 2.5 seconds instead of 3.8

I built a source distribution with 6,000 small files in it and installed it with uv pip install. With uv 0.13.6, the median time across five runs was 2.53 seconds. With the same binary told to use the old extraction code, the median was 3.55 seconds. With the actual pre-change release, 0.12.8, it was 3.76 seconds. That's the headline, and it's a real number from a real install, not a microbenchmark of tar extraction in isolation. The part I didn't expect going in was what happened when I fed the new backend a source distribution with a character device node baked into it. The old backend installed it anyway and silently threw the device file away. The new one refuses the whole install and tells you exactly why. uv is Astral's Rust-based replacement for pip, venv and friends. Version 0.13.0, released on 9 October 2026, swapped the library it uses to extract .tar.gz archives from astral-tokio-tar to a newer one called tar-codec. The release notes say the new backend "applies stricter validation when reading archives" and warns that archives with hard links or unsupported tar extensions that older versions accepted may now be rejected. There's an escape hatch: setting UV_LEGACY_TAR_BACKEND=1 restores the old code path. That one environment variable made this easy to test properly, because I could hold the binary constant and flip only the backend. The install-time number I generated a package called bigpkg with 6,000 text files totalling 48MB uncompressed (36MB as a gzipped tarball), gave it a trivial pyproject.toml using the setuptools backend, and installed it with --no-deps into a fresh virtual environment each time, with a fresh cache directory each time. Five runs per configuration: Configuration Run times (s) Median Fastest uv 0.13.6, default (tar-codec) 2.50, 2.52, 2.53, 2.61, 2.75 2.53s 2.50s uv 0.13.6, UV_LEGACY_TAR_BACKEND=1 3.07, 3.46, 3.55, 3.76, 4.84 3.55s 3.07s uv 0.12.8 (pre-change release) 3.21, 3.42, 3.76, 3.93, 5.12 3.76s 3.21s That's a 29% drop in median time against the same binary with the old flag set, and 33% against the actual old release. The fastest-run comparison is less dramatic — 2.50s against 3.07s and 3.21s, around 18-22% — because the slowest outliers in the legacy runs inflate the median more than the minimum. Either way the direction is consistent across all fifteen runs: tar-codec never lost to the legacy path on this workload. I deliberately used a package with many small files rather than one large file, because that's where tar's per-entry overhead shows up. When I first tried this with a package containing 400 files and 8MB of data, the three configurations landed within noise of each other — 0.96 to 2.6 seconds with no clear ordering. The fixed cost of spinning up a Python subprocess to run the setuptools build backend was swamping the extraction time. Scaling up to 6,000 files is what made the effect visible. If you're testing this yourself and see "no difference," that's very likely your workload being too small, not the feature being inert. What it refuses, and what it used to do silently The release notes mention hard links and "unsupported tar extensions." I built four adversarial archives with Python's tarfile module: one with a hard link pointing at ../../../../../../etc/passwd, one with an absolute symlink to /etc/passwd, one with a GNU sparse-file entry missing its required header, and one with a character device node. Archive uv 0.13.6 default uv 0.13.6 legacy flag uv 0.12.8 Hard link escaping the extraction root rejected rejected rejected Absolute symlink rejected rejected rejected Malformed sparse entry rejected rejected rejected Character device node rejected installed installed Three of the four are a wash. This is the finding that argues against my own headline: the path-escape protections for hard links and symlinks aren't new in 0.13. The old backend already refused them, with almost the same wording: # uv 0.12.8 (old backend)
error: Failed to extract archive: file:///....

Powered by Readybot.io

uv 0.13 Installs 6,000-File Packages 32% Faster

Actions Runner Controller Alternatives: Move Off Kubernetes

DEV Community

Actions Runner Controller alternative: moving ARC off Kubernetes

Teams shopping for an Actions Runner Controller alternative are rarely unhappy with ARC itself, which does exactly what it says: a Kubernetes operator that scales ephemeral runner pods against your job queue, with no per-minute fee from GitHub or from anyone. They are unhappy with what it sits on, because the cluster, the images, the listener and the pager are all yours, and none of that appears on a rate card. Published rates for a 10,000-minute month against one labeled assumption about ARC upkeep. The assumption is yours to replace; the meters are not. The workflow change is small. ARC selects runners with runs-on set to the Helm installation name of a runner scale set, a managed vendor selects runners with a label, and everything above that line in the workflow file stays as it is. The work is in the eight things below that your cluster was quietly providing, and in deciding which of them you actually still need. It is worth being precise about what ARC costs, because the honest answer is not "more than a managed runner". GitHub documents that Actions usage is free for self-hosted runners, and ARC itself is free software. What you pay is the cluster, the node capacity that sits idle so a queue does not form, the image rebuilds, and the hours. The last of those is the one nobody bills you for and everybody spends. This page is the ARC-specific version of the general move, which is worked through in migrate from self-hosted runners. Vendor rates and shapes are in GitHub Actions runner alternatives. What ARC is doing for you right now ARC installs as two Helm charts: a controller manager, by convention in an arc-systems namespace, and one runner scale set per pool, by convention in arc-runners. The scale set opens a long-polling HTTPS connection to the Actions service, waits for a job-available message, and patches an ephemeral runner set to the desired replica count. Each runner pod registers with a just-in-time token, takes exactly one job, and is deleted. Two documented behaviors are worth carrying in your head before you change anything. If a runner pod fails to start, the controller retries it up to five times. If no runner accepts the job, the Actions service unassigns it after 24 hours, which is why a broken listener looks like a job that queues all afternoon rather than a job that fails. Managed runners remove both by provisioning per job instead of matching against a pool you own. The pre-flight checklist Nine items. The first six are the standard runner-migration list, and the last three exist only because the runners were pods in your cluster. Labels. Grep for the Helm installation name of every scale set, not for self-hosted: in scale-set mode runs-on matches the installation name. Include reusable workflows and composite actions. Runner groups. A scale set placed in a group through runnerGroup inherits that group's repository access. On the other side that becomes plain label access, so write down which repositories were allowed to use which pool. Secrets. Anything mounted into the runner pod, a registry credential, a cloud key file, a .npmrc baked into your runner image, becomes a repository or environment secret. This is the single largest source of first-run failures. OIDC. This one carries over unchanged, because the token is minted by GitHub rather than by the runner. What does not carry over is pod identity: a job that authenticated as the service account of its pod, through IRSA or Workload Identity, has no equivalent and needs an OIDC trust policy instead. Cache keys. They carry over as they are. What changes is the hit rate, if your runner image pre-baked dependencies that the cache now has to restore. Matrix and concurrency. Your ceiling was maxRunners plus whatever the cluster could schedule. On the other side it is the vendor's: GitHub allows 20 to 500 concurrent jobs by plan, Namespace publishes 32 to 160 Linux vCPUs, Depot and Blacksmith publish no ceiling at all. Container mode. containerMode: ...

Powered by Readybot.io

Actions Runner Controller Alternatives: Move Off Kubernetes

Cómo validar un CSV financiero con Python

DEV Community

Validar un CSV antes del reporting financiero: Decimal, duplicados y datos pendientes

Nota de edición: artículo y código generados con IA para el equipo de Numio. El ejemplo se ha ejecutado localmente con datos ficticios. El recurso enlazado al final pertenece al blog de Numio. Un informe puede estar bien diseñado y sumar documentos duplicados. También puede mostrar cero donde en realidad falta un dato. Antes de construir un gráfico, conviene establecer qué archivo se admite, qué controles se aplican y qué pasa si alguno falla. Este ejemplo en Python valida una exportación pequeña antes de calcular un resumen mensual por empresa. No se conecta a un ERP, no modifica la contabilidad y no envía información a servicios externos. El contrato del archivo Trabajaremos con cinco columnas, separadas por punto y coma, en este orden: empresa;documento;fecha;moneda;importe. El archivo es UTF-8. Cada fila representa un documento ya agregado, no una línea de asiento ni una partida de factura. La clave (empresa, documento) debe ser única dentro de esta exportación. Las fechas tienen formato YYYY-MM-DD. Los importes usan punto decimal, exactamente dos decimales y ningún separador de miles. Una exportación con coma decimal necesita una conversión explícita y revisada antes de este paso; el script la rechaza en vez de adivinar su significado. empresa;documento;fecha;moneda;importe
cliente_demo_A;F001;2026-10-01;EUR;0.10
cliente_demo_A;F002;2026-10-02;EUR;0.20
cliente_demo_A;F003;2026-10-03;EUR;0.00
cliente_demo_B;F001;2026-10-01;EUR;5.00
F001 puede existir en dos empresas diferentes. Un cero explícito es válido. Un importe vacío bloquea la entrega: no significa cero. Los identificadores son ficticios y sirven únicamente para explicar el formato. Validar antes de devolver un total """Ejemplo educativo: validar un CSV de documentos antes de resumirlo."""
import csv
import re
from collections import defaultdict
from datetime import date
from decimal import Decimal, localcontext

FIELDS = ["empresa", "documento", "fecha", "moneda", "importe"]

def resumir(stream):
reader = csv.DictReader(stream, delimiter=";", strict=True)
try:
fieldnames = reader.fieldnames
except csv.Error as exc:
raise ValueError("Cabecera CSV no valida") from exc
if fieldnames != FIELDS:
raise ValueError("Cabecera distinta del contrato esperado")

seen = set()
totals = defaultdict(lambda: Decimal("0.00"))
errors = []
rows = 0
# Limites del ejemplo: 10 000 documentos, 18 digitos enteros por importe.
with localcontext() as ctx:
    ctx.prec = 28
    try:
        for row in reader:
            rows += 1
            line = reader.line_num
            if rows > 10_000:
                raise ValueError("El ejemplo admite hasta 10 000 documentos")
            try:
                if None in row or any(v is None for v in row.values()):
                    raise ValueError("Numero de columnas incorrecto")
                row = {k: v.strip() for k, v in row.items()}
                if not all(row.values()):
                    raise ValueError("Dato pendiente o identificador vacio")
                if not re.fullmatch(r"[0-9]{4}-[0-9]{2}-[0-9]{2}", row["fecha"]):
                    raise ValueError("Fecha fuera del formato YYYY-MM-DD")
                day = date.fromisoformat(row["fecha"])
                if row["moneda"] != "EUR":
                    raise ValueError("Este ejemplo solo agrega EUR")
                if not re.fullmatch(r"-?[0-9]{1,18}\.[0-9]{2}", row["importe"]):
                    raise ValueError("Importe: punto decimal y dos decimales")
                key = (row["empresa"], row["documento"])
                if key in seen:
                    raise ValueError("Documento repetido en la misma empresa")
                seen.add(key)
                totals[(row["empresa"], day.strftime("%Y-%m"))] += Decimal(row["importe"])
            except ValueError as exc:
                err...


Powered by Readybot.io

Cómo validar un CSV financiero con Python

SEO Lessons from 88 Free Browser Tools

DEV Community

I Built 88 Free Browser Tools and Got 8 Organic Visits. Here’s What the Community Taught Me About SEO.

A few weeks ago, I shared a vulnerable post on Indie Hackers: I built 88 browser-based tools, and my traffic was 281 direct visits and exactly 8 organic search visits. As a developer, my instinct was always: “If you build it, write clean code, and run everything locally in the browser for privacy, people will find it.” I spent weeks optimizing WebAssembly, tweaking client-side canvas APIs, and making sure files were processed 100% locally without server uploads. The tech was solid. The utility was real. The SEO performance was an absolute flatline. The Indie Hackers community gave me a masterclass in SEO reality. If you are a solo developer building side projects, micro-SaaS, or utility sites, here are the 5 big mistakes I made—and what you should do instead. 1. The "Thin Content" Mistake (Google Hates Empty Utility Pages) My tool pages looked great to a human: A drag-and-drop zone An action button (e.g., "Compress" or "Strip EXIF") An instant download link To Googlebot, that page had roughly 40 words of text. Search engines don't run your WebAssembly code to see how clever it is. They read text. If your page doesn't explain: What problem this tool solves How it works step-by-step Why it’s safe (e.g., client-side vs. server-side) Frequently Asked Questions (FAQs) ...then crawlers classify the page as "Thin Content" and refuse to index or rank it. The Fix: Every tool needs at least 300–500 words of genuine, helpful context: instructions, supported formats, limitations, and an FAQ section backed by FAQPage schema markup. 2. Competing for Impossible Keywords When I launched an image compressor, my target was basically "compress image." Do you know who ranks on page 1 for "compress image"? Adobe TinyPNG Cloudflare 10-year-old domains with tens of thousands of backlinks A brand-new domain with zero authority has a 0% chance of ranking for broad head terms. The Fix: You must target long-tail, low-competition keywords where search intent matches your unique edge. Instead of: ❌ "PDF Splitter" ❌ "Image Compressor" Target: ✅ "How to remove GPS metadata from photos without uploading" ✅ "Compress PNG client side WebAssembly" ✅ "Free private alternative to Smallpdf" These have much smaller search volumes, but their keyword difficulty (KD) is low enough that a new site can actually hit page 1. 3. The 88 vs. 3 Trap (Spreading Authority Too Thin) Building 88 tools felt like having 88 lottery tickets. In reality, it meant having 88 orphan pages with zero backlink equity. Google evaluates site authority hierarchically. If no one links to any of your pages, Google doesn't know which one matters. The Fix: Pick 2 or 3 flagship tools that solve a specific, high-demand problem really well. Build content, tutorials, and external links pointing specifically to those 2–3 tools. Build clear internal navigation (pillar pages like /image-tools or /pdf-tools) to distribute page rank to the rest of the catalog. 4. Technical SEO: Is Google Actually Seeing Your Tools? A classic developer mistake is building an entire tool suite as a Single Page Application (SPA) using React, Vue, or Svelte, relying completely on client-side rendering. While Googlebot can execute JavaScript, it often delays JS rendering or times out on complex bundles. If your , meta tags, and H1 tags aren't present in the initial HTML response, your indexing will suffer. The Checklist: [ ] Use SSR (Next.js, Nuxt, Astro, SvelteKit) or Static Site Generation (SSG). [ ] View page source (Ctrl+U) and verify that all headings, descriptions, and FAQs exist in raw HTML. [ ] Implement canonical tags, OpenGraph tags, and structured JSON-LD data. [ ] Check Google Search Console for "Discovered – currently not indexed" errors. 5. SEO Is a Trailing Metric—You Need Initial Distribution You cannot launch a website and wait for Google to discover you. Crawlers look for signals: direct traffic spikes, mentions on social networks, and incoming links. Before organic search kicks in (which takes 3–6 ...

Powered by Readybot.io

SEO Lessons from 88 Free Browser Tools

How Fancy Text Generators Work: Unicode Explained

DEV Community

How Fancy Text Generators Actually Work (It’s Just Unicode)

You've seen it in Instagram bios, Twitter names and Discord servers: text like 𝐁𝐨𝐥𝐝, 𝓢𝓬𝓻𝓲𝓹𝓽 or 𝔾𝕠𝕥𝕙𝕚𝕔 in apps that don't let you pick a font at all. So how does it work? Short answer: it isn't a font. Fancy text generators swap each normal letter for a different Unicode character that happens to look styled. In this post I'll show you exactly which characters they use, the few lines of JavaScript that do the conversion, and the edge cases that broke my own generator when I built one. It's Not a Font, It's a Different Character A fancy text generator doesn't change how a letter looks. It replaces the letter with a completely separate character. A font is a visual skin applied to the same character, but 𝐀 and A are two different characters with two different code points. You can prove it in your browser console: console.log("𝐀" === "A"); // false
console.log("𝐀".codePointAt(0).toString(16)); // "1d400"
U+1D400 is officially named MATHEMATICAL BOLD CAPITAL A. Because it's a real character and not styling, it survives copy and paste. That's why it works in places with no formatting options: an Instagram bio, a username field, a plain . Where These Characters Come From: The Mathematical Alphanumeric Symbols Block Most "fonts" in a fancy text generator come from one Unicode block called Mathematical Alphanumeric Symbols, which runs from U+1D400 to U+1D7FF. It was added in Unicode 3.1 (2001) for mathematicians, not for social media. In math notation, a bold x, an italic x and a script 𝓍 can mean three different things, so each one needed its own character to keep that meaning in plain text. The useful part for developers is how the block is laid out. Each style is a neat run of 26 capitals followed by 26 lowercase letters: Style Example Capital A Small a Bold 𝐀𝐚 U+1D400 U+1D41A Italic 𝐴𝑎 U+1D434 U+1D44E Script 𝒜𝒶 U+1D49C U+1D4B6 Bold Script 𝓐𝓪 U+1D4D0 U+1D4EA Fraktur 𝔄𝔞 U+1D504 U+1D51E Double-struck 𝔸𝕒 U+1D538 U+1D552 Sans-serif 𝖠𝖺 U+1D5A0 U+1D5BA Monospace 𝙰𝚊 U+1D670 U+1D68A The block also holds bold, double-struck, sans-serif and monospace digits. Bold 𝟎 starts at U+1D7CE. Because the letters sit in alphabetical order, converting text is just arithmetic. The Character Offset Trick in JavaScript To convert a letter, find its position in the alphabet (A = 0, B = 1 and so on) and add that number to the style's starting code point. That's the whole trick. Here's the simplest version, a bold converter: function toBold(text) {
return [...text].map((ch) => {
const code = ch.codePointAt(0);
if (code >= 65 && code <= 90) return String.fromCodePoint(0x1d400 + (code - 65)); // A-Z
if (code >= 97 && code <= 122) return String.fromCodePoint(0x1d41a + (code - 97)); // a-z
return ch; // leave spaces, numbers and punctuation alone
}).join("");
}

console.log(toBold("Hello, World!")); // 𝐇𝐞𝐥𝐥𝐨, 𝐖𝐨𝐫𝐥𝐝!
Two details matter here: String.fromCodePoint(), not fromCharCode(). These characters are above U+FFFF, so fromCharCode() can't create them in one call. [...text] instead of text.split(""). Spreading a string iterates by code point, so it won't cut a fancy character in half. More on that later. To support every style, store the starting points in a map and reuse one function: const STYLES = {
bold: { upper: 0x1d400, lower: 0x1d41a },
italic: { upper: 0x1d434, lower: 0x1d44e },
script: { upper: 0x1d49c, lower: 0x1d4b6 },
boldScript: { upper: 0x1d4d0, lower: 0x1d4ea },
fraktur: { upper: 0x1d504, lower: 0x1d51e },
doubleStruck: { upper: 0x1d538, lower: 0x1d552 },
sans: { upper: 0x1d5a0, lower: 0x1d5ba },
monospace: { upper: 0x1d670, lower: 0x1d68a },
};

function stylize(text, style) {
const { upper, lower } = STYLES[style];
return [...text].map((ch) => {
const code = ch.codePointAt(0);
if (code >= 65 && code <= 90) return String.fromCodePoint(upper + code - 65);
if (code >= 97 &...

Powered by Readybot.io

How Fancy Text Generators Work: Unicode Explained

GitHub Actions vs Drone CI: Where Drone Went

DEV Community

GitHub Actions vs Drone CI, and where Drone went

GitHub Actions vs Drone CI is no longer only a comparison of two pipeline engines, because github.com/drone/drone now returns a 301 to github.com/harness/harness and Drone survives there as a feature branch whose last commit was 20 April 2026. Before you weigh YAML syntax, read the license: commercial use of the Enterprise Edition is limited to a trial period of 32 consecutive calendar days unless you qualify for one of two waivers. Where Drone is now: the canonical repository path redirects to Harness Open Source, and Drone continues as a branch whose newest commit is 20 April 2026. Drone is a continuous delivery system built on container technology that uses a simple YAML build file to define and execute build pipelines inside Docker containers. That description is from the project's own readme, and it is still an accurate description of what the software does. What changed is where it lives. Harness Open Source now occupies the repository, and its readme has a section headed "Where is Drone?" that says Harness Open Source represents a massive investment in the next generation of Drone, that a snapshot of Drone was taken as a feature branch so it can continue development, and that the goal is for Harness to eventually be at full parity with Drone in terms of pipeline capabilities, while expecting this to take some time. The facts below were read from the redirect itself, from the readme and LICENSE on that drone branch, and from the drone-runners organization on 21 September 2026. They are checkable in a browser in under two minutes, and they matter more to this decision than any syntax difference. Comparison GitHub Actions Drone CI Where the code lives A first-party GitHub product github.com/drone/drone 301s to harness/harness. Drone is a drone feature branch of that repository Most recent movement Continuous Latest release v2.28.2 on 20 April 2026, cut from a branch whose newest commit is the same day. The main branch of the same repository is pushed daily License A hosted service under GitHub's terms Community Edition under Apache 2.0. Enterprise Edition under the Drone Non-Commercial License, which caps commercial use at 32 consecutive calendar days Who escapes that cap Not applicable Companies under $5 million annual gross revenue and under $5 million all-time debt and equity financing, or anyone running fewer than 5,000 pipelines in the preceding year What you operate Nothing, or self-hosted runners if you choose A server plus one or more runners. The readme describes a runner as a standalone daemon that polls the server for pending pipelines to execute Runner options GitHub-hosted, a managed vendor's fleet, or self-hosted Docker, Kubernetes, AWS and SSH runners are still published. The exec, DigitalOcean, MacStadium and Nomad runners are archived Config .github/workflows/.yml, discovered from the branch .drone.yml, with every step running in a container What you pay per minute $0.006 at 2 vCPU on Linux, or $0.0025 on a managed runner at the same size Nothing to the software. Your server and runner hosts bill whether or not a pipeline is running Self-healing or retries None on GitHub-hosted runners; transient failures diagnosed and retried inside the run on Latchkey None. A failed pipeline is a failed pipeline Setup change required A file in the repository A server, at least one runner, an OAuth application on your forge, and a .drone.yml per repository The license, which is the part most comparisons skip The LICENSE file on the drone branch splits the project in two. The Community Edition is licensed under the Apache License, Version 2.0, and the source files carry headers saying which license each is under. The Enterprise Edition is under the Drone Non-Commercial License, and its first rule is the one to read: you must limit use of this software in any manner primarily intended for or directed toward commercial advantage or private monetary compensation to a trial period of 32 consecutive calendar days. Two waiv...

Powered by Readybot.io*

GitHub Actions vs Drone CI: Where Drone Went

How reelay Turns Goals Into AI-Powered Workflows

DEV Community

How We Built reelay: Turning Goals Into Workflows

Less back-and-forth. More moving forward. What if you could explain what you want to achieve, and an app could create a plan using the tools and services needed to make it happen? That was the idea behind reelay, the project our team created at the hackathon. reelay is built as a general-purpose tool for managing plans. A person shares a goal, and the system creates a series of connected steps that can involve different tools and businesses. For our demo, we picked one scenario: organizing an accessible day out, including a place to eat, an activity and a way to move between them. The approach can also be applied to many other kinds of tasks. Here’s how we made it. 01 / We made the graph the foundation A plan becomes a graph of tasks. Each node represents a step, and the links show how those steps are connected. This helps the user and the system see what needs to be done and in what order. The same structure can represent different types of work, whether the request involves finding information, bringing services together or completing steps that depend on each other. 02 / We used Gemini to create plans A person describes a goal in their own words. Gemini turns the request into a graph, which the system checks and saves. This gives users a plan they can inspect without building every step themselves. 03 / We made businesses easier to find We built a search tool using Qdrant and embeddings. It finds businesses based on a natural-language request and can filter results by location, type and cost. We made it available through MCP, so agents and the graph system can use it as part of a plan. 04 / We created services for the demo To show how different services can work together, we made MCP providers for dining, activities and transport. They can search, create an intent and fulfill it. Our Timișoara example brings them together in an accessible day out. We also added a transport disruption to explore how a plan can change when things go wrong. The providers use fictional demo data. 05 / We added voice with ElevenLabs We wanted users to be able to speak their goals, so we added voice-to-text, editable transcripts and audio playback for results. Users can check what the system heard before submitting their request. 06 / We built the workspace and login system We used Next.js for the frontend, Django for the backend and PostgreSQL for storage. Auth0 handles login, giving users a place to create, inspect and return to their graphs. 07 / We built a Solana escrow prototype We wrote a program in Rust with Anchor to explore how a person could fund a plan and have money released step by step. The program supports deposits, individual releases, top-ups and refunds, with a record for each payment. We deployed it to Solana devnet and ran a test that locked 52 tokens, sent payments to two recipients and refunded the rest. Connecting the escrow prototype to the app is the next step. The biggest challenge: making all the APIs work together Gemini, ElevenLabs, Auth0, MCP providers, business search and Solana each had their own formats, assumptions and ways things could go wrong. Getting each part to work on its own was only part of the task. The harder part was making the output of one part useful to the next while keeping the process clear for the user. We worked through it by giving each part a clear role: understand the request, build the plan, find the right services, carry out steps and manage payments. The main thing we learned: a good integration is more than a set of APIs. It makes them feel like one coherent product. The trip example is one use case. The bigger idea behind reelay is a system that can turn a wide range of goals into steps and bring the right services together to help make them happen. Explore reelay Check out the code: reelay on GitHub

Powered by Readybot.io

How reelay Turns Goals Into AI-Powered Workflows

Fix NU1301: Diagnose NuGet Service Index Errors

DEV Community

nu1301 unable to load service index, and which source failed

A nu1301 unable to load service index error means dotnet restore asked a package source for its V3 index and did not get a usable answer, so it never got as far as looking for your packages. The message always names the source, and the second line, which people skip, carries the reason. Read those two lines together and the error resolves into one of three ordinary situations. NU1301 is raised before any package lookup happens, which is why it names a source and never a package. The second line is the one that says what to fix. What this error means Restore fails early, before any package is mentioned, with a line that names your project file, the code NU1301, and a source URL. A second NU1301 line follows, indented, carrying the underlying failure. NuGet documents NU1301 as "Restore could not be completed because the listed source is unavailable", which covers a remote feed that will not answer and a local folder that is not there. The block below is a container running the .NET SDK 10.0.401 with a single package source pointed at a server that answers 503 to everything. This page carries no reproduction on a Latchkey runner, so the host in the message is ours; the two line structure is the part that matches yours. ```.NET SDK 10.0.401, package source answering 503 /w/app.csproj : error NU1301: Unable to load the service index for source http://mirror503:8899/v3/index.json. /w/app.csproj : error NU1301: Response status code does not indicate success: 503 (Service Unavailable). Failed to restore /w/app.csproj (in 5.44 sec).

Common causes

A private feed is down, throttled or slow

Azure Artifacts, a GitHub Packages feed or a self hosted Nexus answering 503 or 429 is the everyday version. Restore contacts the index before it knows what it needs, so the failure arrives before any package name appears and looks unrelated to the change that triggered the build.

The restore is authenticating to a feed it was never given credentials for

A private feed answers 401 to an anonymous index request, and NuGet reports that as NU1301 rather than as an authentication error, which sends people looking for an outage. On GitHub Actions this usually means the credential provider was not installed, or the source was added without a token in the step that added it.

A source from a developer machine is in scope on the runner

A nuget.config checked into the repository, or one in a parent directory of the checkout, can carry a local folder source or an internal host that does not exist in CI. NuGet merges configuration files up the directory tree, so a source nobody remembers adding is still contacted on every restore.

The runner cannot reach the host at all

A self hosted runner behind a proxy, a network policy that blocks egress to a new domain, or DNS that does not resolve an internal name. The second line says so plainly in this case, naming a connection or name resolution failure rather than a status code, which is the fastest way to tell this cause apart from the others.

How to fix it

Find out which source failed, and whether you need it

  1. Print the sources the runner will actually use, which is the merged result of every nuget.config in scope, not just the one in the repository.
  2. Remove sources the build does not need. Every extra feed is another chance for restore to fail on something irrelevant.
  3. Start the source list from a clean slate in the repository so a machine level configuration cannot add to it.

```Terminal
dotnet nuget list source
Pin the source list in the repository with a clear element A nuget.config at the repository root that begins by clearing inherited sources makes the restore reproducible: the runner uses exactly the feeds listed and nothing a machine level file added. This alone removes the third cause above permanently. <?xml version="1.0" encoding="utf-8"?>



<add key="nuget.org" value="https://api.nuget.org/v3/index...

Powered by Readybot.io

Fix NU1301: Diagnose NuGet Service Index Errors