AI 资讯
Canaries, Not Faith: Auditing Where Your Coding Agent Actually Writes
When people discuss AI agents escaping their boundaries, the mental image is usually dramatic: a jailbreak, a rogue prompt, an obvious disaster. What I've actually seen in practice is duller and more dangerous. The agent finishes its task successfully, the tests pass, and only later does someone notice it edited a file three directories up, or that a "helpful cleanup" deleted something it shouldn't have. Silent drift, not explosions. Last month I wrote about building a prompt regression harness that runs entirely on free tiers. This piece extends the same instinct from what the model says to what the agent does : I wanted a cheap, repeatable way to answer one narrow question — when my agent uses its tools, which parts of this machine does it actually reach? The specific risk I'm measuring A typical coding agent gets handed some mix of shell access, filesystem tools, and HTTP. The failure that matters most in day-to-day use isn't an adversarial attack. It's ordinary helpfulness with sloppy scope: An instruction like "find the relevant config" becomes a walk up the directory tree into your dotfiles. A refactoring task spills into a sibling repository because both were visible. A scratch file gets written somewhere outside the intended workspace and quietly persists. A fetch tool designed for one documentation site ends up POSTing context somewhere else. Notice that nothing here requires a malicious model. A cooperative model with generous tool permissions produces the same outcome. So the question isn't "can I trick the agent into misbehaving" — it's "does the sandbox I believe in actually exist." A probe harness you can run tonight The approach: hand the agent tasks engineered to invite scope violations, record every filesystem change it makes, and compare those changes against an explicit allowlist. Anything outside the list fails the run. The script below is pure standard-library Python. Instead of strace or eBPF (which need privileges you often don't have), it sna
AI 资讯
The Model Passed Your Benchmark. Now Stop Merging Its Code Blindly
A few weeks ago I wrote about building a reproducible test harness for comparing free AI coding models before you commit . That harness answers one question: which model should I use? It does not answer the harder follow-up: once a model generates a patch for my real codebase, when is it safe to merge? This week there was a great discussion on DEV about "understanding over origin" — the idea that it doesn't matter whether code came from a human or a model, only whether someone actually understands it. I agree with the principle, but principles don't survive contact with a busy afternoon. What survives is a checklist with teeth. So here is the pipeline I bolted onto my model harness: every AI-generated patch has to pass through a scripted review gate before I even read it, and the script produces a scorecard that tells me how carefully I need to read it. The problem with eyeballing diffs When a model produces a 40-line diff that looks idiomatic, my brain does a dangerous thing: it pattern-matches on style and skips semantics. The code reads like something I'd write, so I approve it like something I'd write. The failures I've actually shipped from AI-generated code were never syntax errors — the tests even passed. They were things like: A retry loop that retried on the wrong exception type, so real errors got swallowed. A query filter that was subtly wider than the one it replaced (tests passed because fixtures were too small to notice). A dependency added for a one-liner the standard library already covers. All three would have been caught by asking four boring questions before reading the code. So I scripted the questions. The review gate: a reproducible artifact The gate is a small shell script. It takes a patch file, applies it to a throwaway worktree, and runs four checks. It never touches my working branch, and it prints a one-line verdict at the end. #!/usr/bin/env bash # review-gate.sh <patch-file> <base-branch> set -euo pipefail PATCH = " $1 " BASE = " ${ 2 :
AI 资讯
One of China’s Most Powerful AI Models Has Also Escaped Containment
Security researchers say that Kimi K3, an open-weight model from China, wandered off to the internet in an attempt to cheat on a test it was given.
AI 资讯
How to Detect Overtraining Before It Hits: Analyzing HRV with Python and Isolation Forests 🏃♂️📉
We’ve all been there: you're crushing your workouts, feeling like a beast, and then suddenly— bam . You can’t get out of bed, your resting heart rate is through the roof, and your motivation has evaporated. Welcome to Overtraining Syndrome (OTS) . In the world of sports science, Heart Rate Variability (HRV) is the gold standard for tracking recovery. By analyzing the tiny fluctuations between heartbeats (R-R intervals), we can peek into our Autonomic Nervous System (ANS). Today, we’re going to build a Python-based pipeline to fetch data from the Oura Cloud API , calculate key HRV metrics like SDNN and RMSSD , and use an Isolation Forest model to detect when you're pushing a bit too hard. Whether you're a biohacker or a developer interested in wearable data analysis , this guide will show you how to turn raw health data into actionable recovery insights. The Architecture: From Pulse to Prediction 🏗️ Before we dive into the code, let's visualize how the data flows from your finger to our anomaly detection model. graph TD A[Oura Ring] -->|Sync| B(Oura Cloud API) B -->|Raw R-R Intervals| C{Data Preprocessing} C -->|Filtering Artifacts| D[Feature Extraction] D -->|SDNN & RMSSD| E[Isolation Forest Model] E -->|Normal| F[Keep Training! 🚀] E -->|Anomaly| G[Rest Day Required! 🛑] Prerequisites 🛠️ To follow along, you’ll need a few tools in your tech_stack : Python 3.9+ Scikit-learn : For our machine learning magic. SciPy/NumPy : For the heavy math lifting. Oura Cloud API Access : To get that sweet, sweet biometric data. pip install scikit-learn scipy pandas requests Step 1: Fetching R-R Intervals from Oura 💍 The Oura Ring records "R-R intervals" (the time between successive heartbeats in milliseconds) during sleep. This is much more granular than a simple "Heart Rate" average. import requests import pandas as pd def fetch_oura_hrv_data ( api_token , start_date , end_date ): url = f ' https://api.ouraring.com/v2/usercollection/heart_rate ' headers = { ' Authorization ' : f ' B
AI 资讯
The Silent Costs of AI APIs Nobody Warns You About
I remember the exact moment the excitement turned to dread. I had just integrated GPT-4 into a side project—a small document summarization tool. The pricing page said $0.03 per 1K input tokens and $0.06 per 1K output tokens. Clean, simple, two numbers. I calculated roughly $0.01 per summary and smiled. Two weeks later the bill arrived: $87.43 for what I thought would be maybe $15. I wasn't being careless. I had read the docs. I knew about tokens. But the silent costs—the ones nobody puts in a neat table—had quietly multiplied my burn rate by six. That experience taught me that AI API pricing is a lot like buying a printer. The upfront cost is seductive; the real expense hides in the ink cartridges, the proprietary drivers, and the forced upgrades you never planned for. Let's talk about those hidden costs, because I'll bet you've either already hit them or you're about to. The Token Trap That Isn't What You Think Everyone knows tokens are the unit of billing, but the gap between "understanding tokens" and "feeling tokens" is enormous. First, there's the input/output asymmetry . GPT-4 charges double for output tokens. That's fine for short answers, but what about chain-of-thought? If you ask the model to reason step-by-step, those intermediate steps count as output tokens—and they add up fast. I had a single query balloon from 500 output tokens to 2,400 because the model decided to work through a logic puzzle aloud. My cost quadrupled without me changing a thing in my prompt. Then there's the system prompt tax . Many developers stuff context into system messages: instructions, examples, formatting rules. Those are input tokens paid every single time, even when the user's query is tiny. If your system prompt is 1,500 tokens and you handle 10,000 requests, that's 15 million input tokens you're paying for—whether the model uses them or not. And don't get me started on retry costs . You hit a rate limit or your request times out? The token count for that failed request? S
AI 资讯
AWS Aurora, ElastiCache Patterns & DynamoDB — The Complete Data Layer
Part of my AWS learning journey — transitioning from Systems Engineer to Cloud/DevOps. This session completes the database picture — Aurora's read/write architecture, ElastiCache caching strategies, and DynamoDB from table creation to production-ready query patterns. 📋 Topics Covered # Topic Type 1 Aurora Endpoints — Writer vs Reader Concept + Interview 2 What Happens When the Aurora Writer Fails Concept + Cert 3 ElastiCache Caching Patterns — Lazy Loading, Write Through, Session Store Concept + Interview 4 Cache Invalidation Concept + Interview 5 DynamoDB — What It Is and When to Use It Concept + Interview 6 DynamoDB Table Creation — Keys and Settings Concept + Lab 7 Table Classes — Standard vs Standard-IA Concept + Cert 8 Capacity Modes — On-Demand vs Provisioned Concept + Cert 9 Warm Throughput Concept + Cert 10 DynamoDB Items & Attributes — CRUD Operations Concept + Lab 11 Query vs Scan — The Critical Difference Concept + Interview 12 Local Secondary Index (LSI) vs Global Secondary Index (GSI) Concept + Cert 13 Bonus Concepts — Streams, DAX, Consistency, Transactions Concept + Interview 14 Interview Questions Interview 15 Practice Tasks Practice Aurora Endpoints — Writer vs Reader Aurora doesn't give you just one database endpoint — it gives you two, each serving a different purpose and routing to different parts of the cluster. Writer Endpoint (Primary Endpoint): Always points to the current primary/writer instance. All write operations (INSERT, UPDATE, DELETE) go here. If a failover happens and a replica is promoted, Aurora automatically redirects this endpoint to the new writer — your application's configuration never needs to change. Reader Endpoint: A load-balanced endpoint that distributes read-only queries (SELECT) across all available Aurora Replicas. You don't manage which replica serves each query — Aurora handles the routing, spreading read traffic evenly across however many replicas exist. Why this architecture matters: In a typical application, read
开发者
My Terraform Drift Pipeline Fixed the Change, Then Forgot It
My Terraform drift pipeline could detect a manual EC2 tag change, classify it as LOW, and run Terraform to remove it. Then the pipeline moved on. The evidence existed, but it was spread across CodeBuild output, Lambda logs, and an SNS message. If I wanted to know what changed, how it was classified, and whether remediation started, I had to reconstruct the event from multiple AWS services. The pipeline could act on drift. It could not remember drift. Phase 4 added that memory: a durable DynamoDB record, a read only API, and a small dashboard that turns the event history into something I can inspect without opening three AWS consoles. The Stack Terraform drift event ↓ SNS ↓ Severity Lambda ├── classifies HIGH / MEDIUM / LOW ├── starts remediation for eligible LOW drift └── writes the audit event to DynamoDB ↓ API Gateway HTTP API ↓ Read only Lambda ↓ DynamoDB Query ↓ CloudFront → static dashboard ↑ private S3 bucket The browser receives static HTML, CSS, and JavaScript from CloudFront. JavaScript calls API Gateway, the API Lambda queries DynamoDB, and the returned JSON becomes the live dashboard. There is no EC2 web server and no application process running continuously. Step 1: Store Every Classified Event I created a DynamoDB table with a composite key: resource "aws_dynamodb_table" "drift_events" { name = "terraform-drift-events" billing_mode = "PAY_PER_REQUEST" hash_key = "project" range_key = "timestamp" attribute { name = "project" type = "S" } attribute { name = "timestamp" type = "S" } } project groups the history for one Terraform project. The ISO 8601 timestamp orders its events. DynamoDB only requires attribute definitions for keys and indexes. Fields such as high_count , changes , and status still belong in each item, but they do not belong in the table schema block. I passed the table name into the existing severity Lambda instead of putting it directly in the code: environment { variables = { DRIFT_EVENTS_TABLE = aws_dynamodb_table . drift_events . name
AI 资讯
GitHub pauses the Kimi K3 rollout in Copilot while it works a GitHub Actions incident
A GitHub product launch is being held back by the CI/CD platform underneath it. On August 6 GitHub filed a Changelog entry announcing that Kimi K3, an open-weight model, is now generally available in GitHub Copilot, then added an editor's note the same day: the rollout is temporarily paused while GitHub mitigates an incident with GitHub Actions. What the entry says, and what it does not Per the note, GitHub will resume the rollout as soon as possible and update the docs with Kimi K3 pricing: $3 per 1M input tokens, $15 per 1M output tokens, and $0.30 per 1M cached input tokens. That is the extent of the disclosure. The Changelog does not describe the Actions incident, does not put a scale on its blast radius, and does not commit to a resume time. It also does not explain how a Copilot model rollout ends up gated on Actions in the first place; a reader can infer that some provisioning or feature-flag step rides the same platform, but the entry does not say so. Availability is qualified in a way worth flagging. Kimi K3 is GA on paper, but the switch that actually turns it on for end users is paused. The operational read There is a coupling here worth naming plainly. GitHub sells Actions as CI/CD for everyone else, and it also uses Actions to ship its own products. When Actions has a bad day, GitHub's launch calendar has a bad day too, in public. That is not a scandal; it is what dogfooding looks like when the changelog is a live document. It is also a data point for any team running a rollout on top of a hosted CI platform: your feature-flag flip is downstream of somebody else's incident queue, and you inherit that queue's MTTR whether or not it is on your status page. Two follow-ups are worth watching. First, whether the resumed rollout entry names the incident and its cause, or whether it stays silent. Second, whether Kimi K3's published pricing survives the pause unchanged. Until then, the GA label is doing work the runtime cannot back up.
AI 资讯
How to Set Up Rate Limiting in Nuxt
Rate limiting is one of those things that doesn't feel urgent—until someone hammers your login endpoint at 3am and you wake up to a flooded database and a locked-out user base. I added this to my Nuxt base layer after realising I'd shipped several projects with zero protection on auth routes. Not great. This post walks through the exact setup I now use: Redis-backed, an in-memory fallback when Redis is down, named presets for different sensitivity levels, and a 429 page that shows a live countdown instead of just dying on the user. The structure Three pieces, each with one job: createRateLimiter() — a factory that builds the limiter, using Redis with an in-memory fallback applyRateLimit() — what you call inside handlers to enforce a limit server/middleware/rateLimiter.ts — global middleware so every route gets a baseline for free 1. Install npm install rate-limiter-flexible ioredis rate-limiter-flexible does the heavy lifting: sliding windows, Redis integration, and the insurance fallback pattern we'll use. 2. The factory Create server/utils/rateLimiter.ts : import { RateLimiterRedis , RateLimiterMemory , type RateLimiterAbstract , } from ' rate-limiter-flexible ' import { getRedisClient } from ' ./redis ' export interface RateLimiterConfig { keyPrefix : string // Must be unique per limiter, e.g. 'rl:auth' limit : number // Maximum requests within the window windowSeconds : number } export interface RateLimitResult { allowed : boolean limit : number remaining : number resetAt : number // Unix timestamp in seconds when the window resets retryAfter : number // Seconds until retry; 0 if allowed } function buildLimiter ( config : RateLimiterConfig , ): RateLimiterAbstract { const insurance = new RateLimiterMemory ({ keyPrefix : config . keyPrefix , points : config . limit , duration : config . windowSeconds , }) const redis = getRedisClient () if ( ! redis ) { return insurance } return new RateLimiterRedis ({ storeClient : redis , keyPrefix : config . keyPrefix , points
AI 资讯
Three Ways Your Training Data Lies to You (And None of Them Throw an Error)
Every failure I am about to describe produced a clean run. No exception, no stack trace, no red build. Each one produced a plausible number that I believed for longer than I should have. That is the category of bug I have come to fear most. A crash tells you it crashed. A silently broken dataset tells you nothing at all, and your metrics will politely agree with it. Here are three from the last year, all from my own work, all found late. 1. The dataset that was 92% one category I had a training set of 688 records for a multi-category vision-language task. Thirteen categories. Reasonable size for a fine-tune, already used in a completed training run whose results I had written up. While preparing a stratified split, I joined the records back against the source annotations and actually counted the categories. 630 of 688 were a single category: scene captions. Zero examples of traffic signals. Zero of planning. Zero of uncertainty. Several categories the evaluation explicitly measured had no representation in training at all. The previous fine-tune had shown gains on some of those very categories. I had interpreted this as the model learning the task. The real explanation was duller and more useful: the model had learned the answer format from caption supervision, and format alignment alone was enough to move a multiple-choice score. Nothing category-specific had been learned, because nothing category-specific had been shown. The root cause was upstream and boring. The conversion script I inherited only rewrote file paths and dropped records with missing frames. It faithfully preserved a caption-only selection made further up the chain. It had no opinion about balance because nobody had asked it to have one. What I changed: the composition of a training set is now an artifact I generate and inspect before any run, not a property I assume. A category histogram takes seconds. I had not looked, for months. 2. The 18-hour run that converged perfectly to nothing Large model
AI 资讯
Deploying Qwen3.8 Max as a Task‑Oriented Agent in Python
You need a model that can plan, reason, and act across multiple steps. Qwen3.8 Max claims the top spot on the agentic index, but that alone doesn't guarantee a smooth integration. What You'll Learn Wrap Qwen3.8 Max in a reusable agent class. Compare its performance to GPT‑4 on a planning benchmark. Identify failure modes like hallucinations and token limits. Optimize cost and latency with batching and caching. Quick Start: Install and Load The Qwen library is available on PyPI. Install it and load the 3.8‑Max checkpoint. ## Install the Qwen package ! pip install qwen ## Load the model and tokenizer from qwen import QwenLM model = QwenLM . from_pretrained ( " qwen/qwen-3.8b-max " ) The code uses the official qwen package. It pulls the checkpoint from the Hugging Face hub and prepares the tokenizer. Building a Simple Agent Wrapper Below is a minimal agent that sends a prompt, receives a response, and can be extended with tool calls. class QwenAgent : def __init__ ( self , model , max_tokens = 512 ): self . model = model self . max_tokens = max_tokens def run ( self , prompt , ** kwargs ): # Forward the prompt to the model response = self . model . generate ( prompt , max_new_tokens = self . max_tokens , ** kwargs ) return response The wrapper keeps the interface simple: run(prompt) returns the raw text. You can add tool‑calling logic later. Benchmarking Agentic Behavior We test the agent on a short planning task: "Plan a 3‑day trip to Paris." We compare Qwen3.8 Max with GPT‑4. from openai import OpenAI client = OpenAI ( api_key = " YOUR_OPENAI_KEY " ) prompt = " Plan a 3-day trip to Paris, including activities, meals, and transport. " ## Qwen qwen_agent = QwenAgent ( model ) qwen_output = qwen_agent . run ( prompt ) ## GPT‑4 gpt_output = client . chat . completions . create ( model = " gpt-4o-mini " , messages = [{ " role " : " user " , " content " : prompt }], max_tokens = 512 ). choices [ 0 ]. message . content print ( " Qwen output: \n " , qwen_output ) print ( " \
AI 资讯
[Advanced Rust] 2.6. API Design Principles of Flexibility Pt.2 - Object Safety, API Design, and Generic Trait Methods
2.6.1. Object Safety When defining a trait, whether it is object-safe is also part of the unstated contract. Object safety is a concept in Rust related to trait objects . It determines whether a trait can be dynamically dispatched, that is, whether it can be used in the form of dyn Trait . Traits That Are Object-Safe Must Satisfy the Following Conditions (Based on RFC 255) All supertraits must also be object-safe If a trait inherits from other traits, then those supertraits must also be object-safe. It must not require Sized A trait cannot use Sized as a supertrait, meaning it cannot contain a Self: Sized bound, because the size of a trait object is unknown at compile time. It cannot have associated constants . It cannot have associated types with type parameters . All associated functions (methods) must satisfy one of the following rules : Dispatchable functions : They cannot have any type parameters, though lifetime parameters are allowed. They must be methods, and Self may only appear in receiver positions such as: &self &mut self Box<Self> Rc<Self> Arc<Self> Pin<P> (where P is one of the types above) They cannot require Self: Sized , otherwise the trait would only be usable for types with known size and object safety would be broken. Explicitly non-dispatchable functions : They may return Self , but such functions must require Self: Sized , so they cannot be called on trait objects and can only be used with concrete types. If you cannot remember all of the above, just remember object safety describes whether a trait can be safely turned into a trait object . What Object Safety Does If a trait is object-safe, meaning it satisfies all of the conditions above, then we can use dyn Trait to treat different types that implement the trait as a single generic type. If it is not object-safe, the compiler will prevent you from using dyn Trait . Object Safety and API Design When designing APIs, it is recommended to make traits object-safe, even if that slightly reduces con
AI 资讯
OpenAI’s new AI smart speaker will reportedly sell for between $300 and $400
Additional details about OpenAI's mysterious new AI device make it sound like a pricey smart speaker.
科技前沿
Organ donation group accused of trying to take living man's organs faces shutdown
The organization, Network for Hope, "strongly disagrees" with Trump admin's decision.
AI 资讯
Explosive drone found hovering near Ukrainian cargo aircraft at German airport
Russian attack? Explosive drone targeted parked aircraft at Leipzig airport.
AI 资讯
Your table awaits: Exhibit at TechCrunch Disrupt 2026 to be seen by thousands
Not everyone needs a keynote slot to make noise at TechCrunch Disrupt 2026. Sometimes the best way to meet investors, customers, and partners is by exhibiting directly on the Expo Hall floor at San Francisco’s Moscone West from October 13-15. That’s exactly what our Exhibit Program offers, and it’s still open to showcase your startup. Here’s what $12,500 buys you: Joining fellow exhibitors is the fastest, lowest-lift way for a […]
开发者
The “3 / 2 * 10 != 10 * 3 / 2” Problem
Coming from school math, it feels pretty strange that: 3 / 2 * 10 != 10 * 3 / 2 This expression can evaluate to true or false depending on the programming language you use. Languages where the two sides are NOT equal Languages where the two sides ARE equal C, C++, C#, Java, Kotlin, Scala, Ruby, Go, D, Rust, Swift, Zig, Odin, V, Fortran, Python 2 Python 3, JavaScript, TypeScript, Dart, R, Lua 5.3+, Perl, MATLAB, Pascal, Mojo, Nim, Crystal, Julia, Haskell Why are the two sides not equal in the languages on the left? On the left side of the expression above, the operation 3 / 2 is evaluated first using integer arithmetic—truncating the fractional part—which results in 1 . This is then multiplied by 10 , giving a result of 10 for the left side. On the right side, 10 * 3 = 30 is the first step. Dividing this by 2 gives 15 . Thus: 10 != 15 These languages prioritize the efficient (fast) execution of expressions over mathematical correctness, as integer arithmetic is significantly faster than floating-point arithmetic. Unfortunately, these languages use the same / operator for both integer and floating-point division, selecting the operation based on the types of the operands. Regrettably, the expression 3 / 2 * 10.0 still yields 10 in most of these languages (and results in a compilation error in Rust). Even though we indicated our intent to use floating-point numbers by writing 10.0 , it is already too late: compilers evaluate 3 / 2 as integer arithmetic in the first step. Expressions like 3.0 / 2 * 10 or 3 / 2.0 * 10 , on the other hand, produce 15 . Thus, depending on the operand types, you end up with either 10 or 15 . This situation becomes even more dangerous when variables are involved in the expression: num / denum * scale != scale * num / denum This can evaluate to true or false depending on the types of the num and denum variables ( float vs. int ). To avoid these pitfalls, developers use type casting: (float)num / denum * scale != scale * (float)num / denum Thi
AI 资讯
My Scanner Missed 93% of the Bugs — and That Was the Right First Result
The first time I ran my vulnerability scanner against the industry-standard benchmark, the bottom line of the scorer's report was this: $ python scripts/score_benchmark.py --findings out/java.findings.json \ --truth benchmark-java/expectedresults-1.2.csv OVERALL precision 0.60 recall 0.07 F1 0.13 # abridged Three numbers, and here is what each one means. Precision 0.60 — of all the alarms the scanner raised, 60% pointed at real bugs: when it spoke, it was right more often than not. Recall 0.07 — of all the real bugs in the benchmark, it found 7%. In the four vulnerability classes my scanner covers, the benchmark contains 777 real, labeled vulnerabilities; it missed 93% of the bugs it exists to find. F1 0.13 — precision and recall combined into one score (their harmonic mean), dragged down to almost nothing by that recall. My first instinct was to fix it before anyone saw it. Instead I saved the output, wrote the number into my benchmark log, and kept it — because that number was always going to be published, and this is the article that publishes it. The Context For the past months I've been deep in AI — reading, building, measuring. One of the projects that came out of it is an AI vulnerability scanner. The design in one sentence: deterministic static-analysis rules do all the searching, and an LLM judges each finding — is this a real bug or a false alarm? The full architecture gets its own article. This one is about the first measured number. The test set is the OWASP Benchmark — 2,740 labeled Java test cases, the standard exam for Java security scanners. In my scanner's four vulnerability classes (SQL injection, command injection, path traversal, XSS) there are 1,478 cases: 777 real vulnerabilities and 701 cases deliberately designed to bait scanners into raising false alarms. Every tool I compare against — Semgrep, CodeQL — takes the same exam, scored by the same scoring code. Same rules for everyone. New to this? Three words carry this article. A source is wher
AI 资讯
Hackers Stalked Me by Hijacking a Smartwatch for Kids
Security researchers tracked and eavesdropped on a WIRED reporter using vulnerabilities in a pink plastic smartwatch. It’s just one piece of a deeply insecure supply chain of GPS-enabled gadgets.
AI 资讯
Directus + Coolify: Should You Decouple Postgres & Redis?
This is Part 2 of the Directus + Coolify series. If you're new here, start with "Secure Your VPS Before Hackers Do" and the first Directus + Coolify post — the bundled, single-Compose-file setup — before following along with this one. Introduction In the first method, we coupled all of the services into one stack using a single Docker Compose file. The network between all services was created automatically, and we didn't have to start them up individually — which removes the risk of a race condition if service startup isn't handled properly. If you're running a single app, that's genuinely the recommended way to set up Directus on a Coolify-managed VPS. Going in, I assumed there were several good reasons to split the services apart instead — more control over backups, monitoring, restarts, that kind of thing. So before recommending decoupling, I actually tested each of those assumptions on a live Coolify instance. Most of them turned out to be wrong. Myth 1: Restarting Directus Restarts the Whole Stack I expected that restarting Directus inside the bundled Compose file would restart Redis and Postgres along with it. It doesn't. Coolify lets you restart each service in the stack independently — Directus, Database, and Cache each get their own Restart button, right there in the same view. No decoupling needed for this one. Myth 2: You Need a Separate Database Resource for S3 Backups Same story. Even with Postgres bundled inside the Directus Compose file, Coolify still gives it its own dedicated Backups option, S3 included. This isn't a separate-resource-only feature. Myth 3: Scheduled Tasks Require Separate Services Also not true. Coolify exposes a Scheduled Tasks tab per service, even inside a single bundled stack — complete with a Container name dropdown letting you target the cron job at just the database, or just Directus, without splitting anything apart. What Actually Holds Up Two things survived testing. First: metrics. This one's confirmed directly in Coolify'