AI 资讯
Google Trends API: the 200 OK that means you got soft-blocked
Google Trends has no public API. What it has is the same internal JSON endpoints the trends.google.com single-page app calls — and those endpoints do something most REST clients aren't built to survive: they answer with HTTP 200 and an empty body when Google decides you look like a bot. Quick answer A 200 OK from Google Trends' widgetdata endpoints does not mean you got data. If the response body is empty, Google soft-blocked the request without bothering to send a 429. The fix is to stop trusting the status code alone: check resp.text.strip() on every call that's supposed to return a payload, and if it's empty, rotate the proxy session and retry exactly like you would on a 429 — because that's what it functionally is. if status == 200 : if require_body and not resp . text . strip (): # Soft block: Google returns 200 with empty body when it detects bots. # Treat the same as 429 — rotate session and retry. logger . warning ( " %s: HTTP 200 but empty body (soft block, attempt %d/%d) " , ...) if proxy_cfg is not None : new_sid = _fresh_session_id () current_proxy_url = await proxy_cfg . new_url ( session_id = new_sid ) await asyncio . sleep ( delay ) continue return resp Why does a working response start with )]}' ? Every Trends JSON endpoint prepends an XSSI-protection prefix before the actual JSON body — a defence against cross-site script inclusion attacks that predates fetch() . Naive json.loads(resp.text) throws a JSONDecodeError on a perfectly healthy response. Worse, the prefix isn't even consistent: /trends/api/explore sends )]}'\n (no comma), some widgetdata endpoints send )]}',\n (with a comma). We check the longer variant first so a response using the short prefix doesn't get mis-stripped: XSSI_PREFIX = " )]} ' , \n " XSSI_PREFIX_NO_COMMA = " )]} ' \n " def _strip_xssi_prefix ( body : str ) -> str : if body . startswith ( XSSI_PREFIX ): return body [ len ( XSSI_PREFIX ):] if body . startswith ( XSSI_PREFIX_NO_COMMA ): return body [ len ( XSSI_PREFIX_NO_COMMA
AI 资讯
How I Model Aspects and Orbs in a Python Astrology Engine
I like working on aspect calculations because the astrology terminology disappears pretty quickly once you get into the code. At the calculation level, an aspect is basically: How close are two points on a circle to a configured angle? That turns the problem into geometry, tolerances and a few interesting edge cases. Start with angular distance Suppose two planets have longitudes: 12° 102° Their separation is 90°. That’s easy. But this pair is more interesting: 358° 2° A normal absolute difference gives you 356°. On a circle, they’re actually 4° apart. So one of the basic utilities looks conceptually like this: def angular_distance ( a : float , b : float ) -> float : delta = abs ( a - b ) % 360 return min ( delta , 360 - delta ) Now: angular_distance ( 358 , 2 ) returns: 4 That simple normalization is the base of the rest of the aspect system. Then define target angles For the major aspects, you’re comparing against angles such as: 0° conjunction 60° sextile 90° square 120° trine 180° opposition If everything had to be exact, the implementation would be trivial. But astrology uses orbs. So a separation of 92° can still be treated as a square depending on the calculation profile. The orb is basically: orb = abs ( actual_distance - target_angle ) Then: if orb <= allowed_orb : # aspect matched I keep orb rules in a profile This is one of those places where hidden constants are really tempting. Something like: MAX_ORB = 8 and move on. I prefer putting this kind of behavior into an explicit calculation profile. That way the result isn’t just: Venus square Saturn It can be understood as: Venus square Saturn under this aspect profile with this orb That makes the methodology easier to inspect and makes future changes much less messy. The engine reports geometry, not sentiment This was another boundary I wanted to keep clean. The core can calculate: planet A planet B aspect type orb phase It should not calculate: good bad easy terrible relationship Those are interpretation-
AI 资讯
The Hard Part of Birth Chart Calculations Isn't the Zodiac. It's Time.
The most annoying bugs I’ve dealt with while building a birth-chart engine were not about zodiac signs. They were about time. And the deeper I got into it, the more I realized that “birth time” is a much less simple input than it looks on a form. A local datetime isn't enough Take this: 1990-05-15 09:30 It looks precise. But precise where? Without a timezone, it doesn’t identify an instant. So the calculation API I use takes both the local datetime and the IANA timezone: chart = engine . natal ( local_datetime = " 1990-05-15T09:30:00 " , timezone = " Europe/London " , latitude = 51.5074 , longitude =- 0.1278 , ) I prefer an IANA zone like Europe/London over something like UTC+1 . The former describes a real timezone with historical rules. The latter is just an offset. DST makes this more interesting During a daylight-saving fall-back transition, the same local clock time can occur twice. So a value like: 01:30 may correspond to two different UTC instants. That means the input looks exact to the user while still being ambiguous to the calculation engine. There’s a strong temptation to quietly choose one. I don’t like doing that. If the input is genuinely ambiguous, I’d rather make the ambiguity explicit. The same thing happens in the opposite direction during spring transitions. Some local clock times never existed. If the clock jumped directly from 01:59 to 03:00, then: 02:30 isn’t a valid local instant. Again, silently “fixing” it is convenient. But now the software has changed the user’s data. Then there’s the bigger problem: no birth time A lot of people simply don’t know what time they were born. This creates a product decision. You can say: unknown → 12:00 PM and suddenly everything works. You get: Ascendant houses house cusps Midheaven The object looks complete. But none of those values are based on a birth time the person actually supplied. That bothered me enough that I made unknown birth time a first-class state in the engine. chart = engine . natal ( local
AI 资讯
Top Vector Databases for AI Agents in 2026: Qdrant vs Pinecone vs Weaviate vs PgVector vs Milvus
Top Vector Databases for AI Agents in 2026: Qdrant vs Pinecone vs Weaviate vs PgVector vs Milvus Persistent memory is the foundation that turns a stateless LLM into a continuously improving, autonomous agent. In 2026, selecting a vector database is no longer just about raw Approximate Nearest Neighbor (ANN) speed. For AI agents, the critical requirements have shifted to: Payload & Metadata Filtering : Can you filter by tenant_id , user_id , and timestamp during vector graph traversal without sacrificing recall? Hybrid Search (BM25 + Dense Vectors + Sparse SPLADE) : Combining exact keyword matching (for code symbols and error codes) with semantic understanding. Multi-Tenancy & Memory Namespacing : Safely isolating memory blocks across thousands of users and sessions. Billion-Scale Quantization (Product Quantization & Scalar Quantization) : Slashing RAM costs by 75–90% in production. This guide provides a comprehensive architectural comparison of the top 5 vector databases for AI agents in 2026. Head-to-Head Comparison Matrix Feature / Metric Qdrant Pinecone (Serverless) Weaviate PgVector (PostgreSQL) Milvus Primary Architecture Rust-native, disk-backed Fully managed serverless Go-native, modular RAG PostgreSQL extension Distributed cloud-native Open Source Yes (Apache 2.0) Proprietary SaaS Yes (BSD-3) Yes (Open Source) Yes (Apache 2.0) Payload Filtering Exceptional (HNSW custom payload indexing) Good (Metadata filtering) Strong (Inverted index + HNSW) SQL WHERE clause Strong (Partition keys) Hybrid Search Native (Dense + Sparse vectors) Native hybrid Native BM25 + Vector SQL text search + pgvector Native multi-vector Quantization Scalar & Product Quantization (Binary) Automatic serverless compression PQ, BQ, SQ Halfvec, Binary Quantization Scalar / Product Quantization Best Fit High-performance agent memory & self-hosted RAG Zero-maintenance cloud SaaS GraphQL & multi-modal search Unified relational + vector apps Ultra-large enterprise (100M+ vectors) 1. Qdrant: The
AI 资讯
Top AI Agent Security & Guardrails Frameworks in 2026: Defending Against Prompt Injections & Tool Hijacking
Top AI Agent Security & Guardrails Frameworks in 2026: Defending Against Prompt Injections & Tool Hijacking As AI agents transition from read-only chatbots to autonomous actors with tool execution privileges (SQL queries, API calls, shell execution, email dispatch), application security has become the number one blocker for production deployment. A simple prompt injection against a chatbot produces bad text; a prompt injection against an agent can drop production databases, exfiltrate API keys, or hijack customer sessions . In 2026, securing an AI agent requires a multi-layered defense architecture across inputs, model reasoning, tool invocations, and memory stores. The Top 5 AI Agent Security & Guardrail Frameworks in 2026 ┌─────────────────────────────────────────────────────────┐ │ Input Defense & Sanitization │ │ (Lakera Guard / Rebuff / Preamble) │ └────────────────────────────┬────────────────────────────┘ │ ┌────────────────────────────▼────────────────────────────┐ │ Execution & Policy Enforcement │ │ (NVIDIA NeMo Guardrails / LLM Guard) │ └────────────────────────────┬────────────────────────────┘ │ ┌────────────────────────────▼────────────────────────────┐ │ Tool Scoping & Sandboxed Runtime │ │ (Docker / E2B / Fly Machines Sandboxes) │ └─────────────────────────────────────────────────────────┘ 1. NVIDIA NeMo Guardrails: Programmable Semantic Rails NeMo Guardrails uses Colang to define programmable dialogue flow, topical boundaries, and safety constraints. Core Capabilities: Topical Rails : Ensures the agent stays strictly on domain (e.g., banking support cannot discuss medical advice). Execution Rails : Intercepts tool calls before execution to verify parameter safety. Hallucination Rails : Validates that outputs are strictly grounded in retrieved RAG context. 2. LLM Guard (Protect AI): Open-Source Scanner Suite LLM Guard is a modular security toolkit providing 30+ dedicated scanners for input and output validation. Key Scanners: Prompt Injection Detecto
AI 资讯
Stop Guessing Your Calories: Building a Real-Time Multimodal Nutrition Engine with GPT-4o Vision
How many times have you stared at a plate of Gong Bao Chicken or a complex Mediterranean salad and wondered, "How many calories are actually in here?" Traditional calorie tracking apps are tedious, requiring you to manually weigh ingredients and search through messy databases. But with the rise of multimodal AI , specifically the GPT-4o Vision API , we can now transform a simple photo into a detailed nutritional breakdown in seconds. In this tutorial, we are building a Computer Vision Nutrition Engine that leverages GPT-4o to identify ingredients, estimate portions, and calculate macronutrients with surprising accuracy. By using Few-shot Prompting and structured data validation with Pydantic , we’ll solve the age-old problem of identifying "hidden" ingredients in complex cuisines. Whether you're interested in AI for health or mastering multimodal LLM pipelines , this guide is for you! The Architecture 🏗️ The system logic is straightforward but powerful. We take an image input, process it through the GPT-4o vision model using a specialized system prompt, and enforce a strict JSON schema output for our frontend to consume. graph TD A[User Uploads Food Image] --> B[Streamlit Frontend] B --> C{FastAPI/Python Logic} C --> D[GPT-4o Vision API] D --> E[Few-Shot Prompting Strategy] E --> F[Pydantic Structured Output] F --> G[Calorie & Nutrient Dashboard] G --> H[User Review & Log] Prerequisites 🛠️ To follow along, you'll need: Python 3.9+ OpenAI API Key (with GPT-4o access) Libraries : openai , streamlit , pydantic , pillow Step 1: Defining the Data Schema with Pydantic To make our engine reliable, we can't just accept raw text from the AI. We need structured data. We’ll use Pydantic to define exactly what a "Nutrition Report" looks like. from pydantic import BaseModel , Field from typing import List class Ingredient ( BaseModel ): name : str = Field ( description = " Name of the ingredient identified " ) estimated_weight_g : float = Field ( description = " Estimated weight
AI 资讯
The Principle of Least Privilege: Why File Permissions Like 600/644/755 Exist
Anyone who has worked with SSH private keys has run into an instruction to "set it to 600." Config files, by contrast, often get 644, and executable scripts get 755. What do these three-digit numbers actually mean, and why does the right number depend on what kind of file you're dealing with? This post starts from the mechanics of Unix-style (Mac/Linux) file permissions and works up to the design principle behind them: least privilege. Permissions as a 2D grid of who and what Unix-family operating systems express file access as a grid: three kinds of "who" crossed with three kinds of "what." "Who" breaks down into the file's owner, the group the owner belongs to, and everyone else ("other"). "What" breaks down into read, write, and execute. Each cell in that 3×3 grid is either granted or not, and that's exactly what a listing like -rw-r--r-- from ls -l is showing you. Strip the leading character and the remaining nine characters are three groups of three — owner, group, other — each rendered as r/w/x when granted or - when not. Why a single digit can represent read/write/execute Numeric notation like chmod 600 compresses that rwx combination into a single octal digit. Read is worth 4, write is worth 2, execute is worth 1 — powers of two — and you sum whichever bits are set. Note: powers of two are used here because each of read/write/execute is tracked as an independent bit (on or off), and any sum of a subset of {4, 2, 1} maps back to exactly one combination of bits. There's no ambiguity — for example, 6 can only mean read+write (4+2), never any other combination. Read and write, no execute ( rw- ): 4 + 2 = 6 Read only ( r-- ): 4 Read, write, and execute ( rwx ): 4 + 2 + 1 = 7 No access at all ( --- ): 0 A three-digit number like 600 lines up these single digits for owner, group, and other, left to right. 600 means "owner gets read+write, group and other get nothing." What the common numbers actually mean Reading the numbers mentioned at the top through this lens:
AI 资讯
What's an event loop anyways?
Event loops are a paradigm for processing events different than your typical single-threaded or multi-threaded application. Your request gets broken down into async "events" that are executed in a loop to improve performance and minimize synchronization across threads. It is famously used by Node.js as the backbone of their event processing and also by several other technologies like Redis and Nginx . In this article I'll explain the reason for why this paradigm was created and what it tries to optimize. By the end you'll come out a little wiser, and know more than just "don't block the event loop" :). Motiviation - why event loops? To understand why we need event loops we will explore a simple but key example. Take this straightforward HTTP request code, which sends a request and then tries to read the response from the socket: def send_http_request_GET ( domain : str , request : str ) -> HttpResponse : socket_fd = get_socket_for_domain ( domain ) write_res = os . write ( socket_fd , request ) data = os . read ( socket_fd , 1024 ) return HttpResponse ( data ) We do two things in this call - write data out and read data in. Both of these actions will end up triggering syscalls through the kernel that write and fetch data. In terms of time spent on the CPU, this is relatively inexpensive; sending out packets takes very little time, and eventually reading the response will also take very little CPU time. The key time lost is from waiting on the server to respond to us. os.read will block this thread until the response is available, meaning that the thread cannot be used for any other processing during this time. If our service is single-threaded, this means that we can't make any requests in parallel and are stuck waiting on any previous requests to finish. But of course, most services are not single-threaded, so this isn't a huge problem? Let's continue with the example code, imagining that instead we are processing these requests with multiple threads pulling from a
AI 资讯
iCloud Silently Evicted 69 Article Files and Killed 4 Days of Publishing: EDEADLK and a read_text_resilient Design
Every one of my publishing lanes went dark for four days, and every script involved exited with status 0. Nothing had crashed. The files themselves had quietly stopped existing on disk — macOS had uploaded them to iCloud and deleted the local copies to "optimize storage." Why This Matters What it means for automation to depend on its environment When you run 160+ launchd jobs around the clock, the execution environment itself becomes a failure source before your script logic does. Ports get exhausted, processes orphan and pile up, memory never frees — I wrote about that class of resource leak last time. This is a completely different kind of total failure that happened the very next day. The files had become fatal to read . Not a bug in my code. Not a filesystem bug. An unintended side effect of a mechanism macOS runs under the name "optimization." What optimize-storage actually does macOS's "Optimize Storage" (System Settings → General → Storage → Optimize Storage), on a machine with iCloud Drive enabled, uploads files under Desktop and Documents to iCloud and deletes the local copies when free disk space gets tight . In Finder they still look like normal icons, but there is no local data — they are in a "dataless" state. Click one and it downloads automatically. For a human user, that's an acceptable tradeoff. The problem is automation scripts. python3 's open() , pathlib.Path.read_text() , cat , jq , cp — all of them die instantly on a dataless file with Errno 11: EDEADLK: Resource deadlock avoided . The name "Resource deadlock" makes you suspect a deadlock, but this is a POSIX errno code that macOS repurposes to mean "waiting for a file download." No lock is contended. No thread is stuck. The mere fact that "the data isn't local" surfaces to the process as a fatal error code. You can also get EAGAIN (resource temporarily unavailable). That one shows up as a race right after a download starts. The actual damage: four days of zero posts On August 6, 2026, note's a
AI 资讯
Reduce LLM Cost for Catalog: Compare Small Models to Summarize, Classify, and Extract JSON
Short answer: the best way to reduce LLM cost for a product catalog is to measure cost per accepted record, then route each job by difficulty. Count prompt tokens before the call, use a small model for the easy summarize/classify/extract-JSON cases, reserve a stronger model for exceptions, and batch work that can wait. The winning option is the one that preserves the required fields at the lowest cost per accepted result, not the one with the lowest token rate. I build RAG and agent features in Python, so I want this decision to survive contact with production. A catalog description such as “blue jacket, recycled nylon, sizes S–XL” looks easy until one tenant sends HTML, another sends translated attributes, and a third puts three products in one paragraph. The useful unit is therefore a tenant-scoped record with an outcome, a token count, a retry count, and a review decision. Start with the record, not the model For this workflow, the data path is short: normalize a description, count the tokens in the planned prompt, choose a route, request a typed result, validate it, and write the result and its cost to an append-only ledger. A rejected result must remain visible. Otherwise, a cheap model can appear successful simply because invalid JSON and missing attributes disappeared from the report. The ledger needs enough identity to answer a boring but important question: which tenant paid for this retry? I use a job ID, tenant ID, source revision, model route, input token count, output token count, latency, validation status, and review status. Keep the raw description out of ordinary cost dashboards when it contains customer data; the ledger can hold a reference to the source revision instead. Prompt-token counting is a gate, not a decorative metric. If an HTML description expands the prompt beyond its budget, the worker can strip markup, split the record, send it to review, or select a different route. Those choices have different quality consequences, so record the re
开源项目
🔥 docling-project / docling - Get your documents ready for gen AI
GitHub热门项目 | Get your documents ready for gen AI | Stars: 65,286 | 145 stars today | 语言: Python
开源项目
🔥 ATH-MaaS / Pixelle-Video - 🚀 AI 全自动短视频引擎 | AI Fully Automated Short Video Engine
GitHub热门项目 | 🚀 AI 全自动短视频引擎 | AI Fully Automated Short Video Engine | Stars: 27,091 | 125 stars today | 语言: Python
开源项目
🔥 Osmantic / ODS - Turn your PC, Mac, or Linux box into an AI server. LLM infer
GitHub热门项目 | Turn your PC, Mac, or Linux box into an AI server. LLM inference, chat UI, voice, agents, workflows, RAG, and image generation. | Stars: 4,561 | 168 stars today | 语言: Python
开源项目
🔥 Tencent / AI-Infra-Guard - A full-stack AI Red Teaming platform securing AI ecosystems
GitHub热门项目 | A full-stack AI Red Teaming platform securing AI ecosystems via Agent Scan, Skills Scan, MCP scan, AI Infra scan and LLM jailbreak evaluation. | Stars: 4,773 | 28 stars today | 语言: Python
AI 资讯
A benchmark is only as good as the model you use to grade it
I built a pytest harness that runs the same set of questions through five language models at once - a free local Llama, plus GPT, DeepSeek, and two Claude models - and compares them on the three things a team pays for: cost per query, speed, and answer quality. The plan was simple. Run the grid, read the scoreboard, say which model to use. The scoreboard came back clean and easy to read. This is the story of why I didn't trust it, and what I found when I checked. The thing I stopped trusting wasn't any of the models. It was the tool I was using to score them. It's also the first project in this series that spends real money. Every one before it ran locally, for free. Here each call costs something, and the whole comparison came to about 21 cents. That price is small, but it changed how I tested, and not in the way I expected. The scoreboard, and why I didn't stop there Five models, the same ten questions, twice each, every call measured. Here is the run, ordered by quality score (a second model grades each answer on correctness and relevance, combined into a 0-1 score, pass line 0.7): model quality mean $/query mean latency out-tokens deepseek-v4-pro 0.970 $0.000138 2713 ms 113 claude-haiku-4-5 0.967 $0.000537 1597 ms 104 gpt-5.6-luna 0.962 $0.000082 1323 ms 65 claude-sonnet-5 0.937 $0.002426 4093 ms 239 llama3.2 (local) 0.922 $0.000000 7859 ms 130 Read it straight and it looks finished. The whole quality column sits in a tiny band, 0.92 to 0.97. The cheapest, fastest paid model scores right in there with the rest. The most expensive one, Sonnet, at about thirty times the price per query, sits no higher than the others - its answers are just longer (239 tokens to GPT's 65), which costs more and takes longer without scoring better. So the easy takeaway is: use the small cheap model, skip the expensive one. I want to be careful with that, because it's the kind of tidy result I've learned to distrust. The gaps between the top models are tiny, and a ranking built on tin
开发者
One rented /24 could eclipse a Kademlia node. Now it takes ten.
This is a submission for DEV's Summer Bug Smash: Smash Stories powered by Sentry. The...
AI 资讯
The test was green. Every real connection would have failed.
This is a submission for DEV's Summer Bug Smash: Smash Stories powered by Sentry. The...
AI 资讯
Driving DaVinci Resolve's Free Edition with Claude, From Inside the App
The wall Every MCP server that controls DaVinci Resolve connects to it the same way: a script running outside the app calls into Resolve's scripting API over the network. That works fine on Resolve Studio. On the free edition it doesn't work at all — Lite is sandboxed and blocks any script that isn't launched from inside Resolve itself. The one door left open Free Resolve still runs Python scripts launched from its own Workspace > Scripts menu. A menu script gets the resolve object injected for free, can run a long-lived loop, and — because the sandboxed app ships the com.apple.security.network.server entitlement — can open a localhost listening socket. That's the whole trick: the MCP server is the menu script. Claude Code ──HTTP JSON-RPC (MCP)──▶ 127.0.0.1:8765/mcp │ server runs INSIDE Resolve │ (Workspace > Scripts > Utility) ▼ command queue → main script thread ▼ global `resolve` object → Resolve API What it gets you 157 tools across editing, color, render, media pool, and Fusion title styling — driven from plain-language requests in Claude Code. Zero dependencies: pure Python standard library, so there's nothing to pip install into Resolve's bundled interpreter. Try it git clone https://github.com/2sem/davinci-resolve-lite-mcp.git cd davinci-resolve-lite-mcp ./install.sh macOS only for now. Full tools reference and demo video in the repo.
AI 资讯
How to Build Your First AI Agent Tool in 15 Minutes (20+ Open Issues for Beginners!)
If you’ve been using ChatGPT, Claude, or LangChain, you know that Large Language Models (LLMs) are completely isolated from the real world. They can't check the weather, read your emails, query your database, or send Slack messages. That is, unless you give them Tools. Connecting AI agents to external APIs is one of the most in-demand skills in AI engineering right now. To make this easier for everyone, I recently launched Agent Tools & MCP Hub, an open-source directory of plug-and-play AI tools compatible with the new Model Context Protocol (MCP) standard. And the best part? We have over 20+ good first issue tasks open right now for anyone who wants to contribute! 🌟 What is the Agent Tools & MCP Hub? Agent Tools & MCP Hub is a modular, zero-dependency repository that standardizes how tools are built for AI agents. Whether you are using LangChain, CrewAI, AutoGen, or Anthropic’s new Claude Desktop MCP clients, our tools are designed to work right out of the box. Why Contribute? If you've been wanting to make your first open-source contribution but felt overwhelmed by massive codebases and merge conflicts, this repo is built specifically for you: 🧩 100% Modular: Every tool lives in its own isolated folder ( tools/<tool-name> ). Your code will never conflict with someone else's. ⚡ Easy Templates: We provide a copy-paste _template folder. You just add your API logic. 3. 🏅 Instant Recognition: Every contributor whose PR is merged gets their GitHub profile showcased on the official repository README! 🛠️ How to Contribute (in 15 Minutes) We've made the contribution process as frictionless as possible. Step 1: Claim an Issue Head over to our GitHub Issues Page and find an open issue labeled good first issue . Comment on the issue to get it assigned to you! Here are some of the trendy tools waiting to be built: Spotify Current Track & Playlist Fetcher Notion Page & Database Appender Stripe Payment Status Inspector Supabase Realtime Table Query Tool Linear / Jira Task Creato
AI 资讯
Deploying Multiple Python Bots to a Single Railway Container
A tutorial for running two or more python bots on Railway inside one container and one service, with independent crash recovery for each. Deploying Multiple Python Bots to a Single Railway Container If you're running more than one Python bot — say, a Telegram ingestion bot and a Discord notification bot that share a database — deploying each as its own Railway service means double the hosting cost and double the configuration for something that's logically one unit. This tutorial covers deploying both bots inside a single Railway container, with each one still getting fully independent crash recovery. Table of Contents Why Two Services Is Usually Overkill The Naive Fix and Why It Falls Short Step 1: Install StayPresent Step 2: Structure Your Project Step 3: Configure Multiple Bots in One Entry Point Step 4: Read Railway's Assigned Port Step 5: Deploy as a Single Railway Service Verifying Both Bots Are Running FAQs Conclusion Why Two Services Is Usually Overkill Railway (like most PaaS platforms) charges per service, and each service needs its own configuration, environment variables, and deployment pipeline. If two bots are closely related — sharing a database, a queue, or just conceptually belonging to the same project — running them as two separate Railway services duplicates all of that for no real benefit. The Naive Fix and Why It Falls Short A common first instinct is a shell script: python telegram_bot.py & python discord_bot.py & wait This runs both, but there's no real process supervision here — if telegram_bot.py crashes, nothing restarts it, and you still haven't solved Railway's HTTP port requirement, since neither script opens one. Step 1: Install StayPresent pip install staypresent[prod] # requirements.txt staypresent[prod] Step 2: Structure Your Project project/ ├── main.py ├── telegram_bot.py ├── discord_bot.py ├── requirements.txt Both bot scripts stay exactly as they are — nothing about their internal logic needs to change. Step 3: Configure Multipl