<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ArshTechPro</title>
    <description>The latest articles on DEV Community by ArshTechPro (@arshtechpro).</description>
    <link>https://dev.to/arshtechpro</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3258664%2F7a2cc61a-0b4d-4cf8-884e-52f33905cac3.png</url>
      <title>DEV Community: ArshTechPro</title>
      <link>https://dev.to/arshtechpro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arshtechpro"/>
    <language>en</language>
    <item>
      <title>How Dopamine Works: The Architecture of a Modern iOS Jailbreak</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Tue, 11 Aug 2026 14:06:56 +0000</pubDate>
      <link>https://dev.to/arshtechpro/how-dopamine-works-the-architecture-of-a-modern-ios-jailbreak-2hj3</link>
      <guid>https://dev.to/arshtechpro/how-dopamine-works-the-architecture-of-a-modern-ios-jailbreak-2hj3</guid>
      <description>&lt;p&gt;Most developers will never jailbreak a phone. That is fine. This article is not a how-to, and there is no install guide here.&lt;/p&gt;

&lt;p&gt;What makes &lt;a href="https://github.com/opa334/Dopamine" rel="noopener noreferrer"&gt;Dopamine&lt;/a&gt; worth reading as an engineer is the constraint it operates under: it has to run a full package ecosystem on an operating system that was explicitly designed to make that impossible, without modifying a single byte of the system partition, and it has to survive every process launch on the device.&lt;/p&gt;

&lt;p&gt;Dopamine is a rootless, semi-untethered jailbreak by opa334 and évelyne, written mostly in C and Objective-C. Version support depends heavily on chip and iOS version, so check the README rather than trusting any number in a blog post.&lt;/p&gt;

&lt;p&gt;Let's walk the architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rules of the game
&lt;/h2&gt;

&lt;p&gt;Every design decision downstream falls out of what iOS enforces. Five things matter:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The system volume is sealed.&lt;/strong&gt; Since iOS 15, the root filesystem is a cryptographically sealed snapshot. You cannot write to &lt;code&gt;/usr/lib&lt;/code&gt; and have the device boot. So the entire jailbreak has to live somewhere else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code must be signed.&lt;/strong&gt; The kernel refuses to execute pages that do not carry a valid signature chain. There is also a &lt;em&gt;trust cache&lt;/em&gt;, a kernel-side list of hashes that are allowed to run. A tweak you compiled ten seconds ago is in neither.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Processes are sandboxed.&lt;/strong&gt; Even as root, a process only sees what its sandbox profile allows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Libraries are validated.&lt;/strong&gt; Library validation means a process will typically only load libraries signed by the same team as the main binary. That is the single biggest obstacle to loading third-party code into Apple's own processes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory is protected below the kernel.&lt;/strong&gt; On modern arm64e chips there are protection layers beneath the kernel itself (PPL, and later SPTM) that guard page tables, plus pointer authentication on function pointers. Kernel read/write alone is no longer enough.&lt;/p&gt;

&lt;p&gt;So: no writable system directory, no way to sign code, no way to load unsigned libraries, and a kernel you cannot fully trust yourself inside. Everything below is a response to one of those five facts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two layers
&lt;/h2&gt;

&lt;p&gt;The repo splits cleanly in two:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application/   Objective-C, UIKit. The app you tap. Orchestration, UI, logs.
BaseBin/       C, Mach, assembly. The runtime that lives on the device afterwards.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Application&lt;/code&gt; is the installer and control panel. &lt;code&gt;BaseBin&lt;/code&gt; is the actual jailbreak. Almost everything interesting is in &lt;code&gt;BaseBin&lt;/code&gt;, and it keeps running long after the app is closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 1: exploits as plugins
&lt;/h2&gt;

&lt;p&gt;The app has a &lt;code&gt;DOExploitManager&lt;/code&gt; that selects an exploit based on the device's chip family and OS build, and a &lt;code&gt;DOJailbreaker&lt;/code&gt; that drives the whole sequence.&lt;/p&gt;

&lt;p&gt;The part worth stealing here is not the exploits, it is the &lt;strong&gt;plugin boundary&lt;/strong&gt;. Each exploit ships as a bundle with an &lt;code&gt;Info.plist&lt;/code&gt; declaring what it supports. The repo has several of them side by side, and the wiki has a page on adding new ones. The orchestration code does not care which one runs. It asks the manager for something compatible with this device, runs it, and gets a set of capabilities back.&lt;/p&gt;

&lt;p&gt;This is a hardware abstraction layer, applied to bugs. When a new technique appears, you add a bundle instead of rewriting the jailbreak. Given how quickly individual entry points get patched, that boundary is the reason the project survived across four-plus years of OS releases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 2: from a bug to a stable primitive
&lt;/h2&gt;

&lt;p&gt;Raw exploitation gives you something awkward and fragile. What the rest of the system wants is a clean interface: read kernel memory, write kernel memory, call a kernel function, mark this page executable.&lt;/p&gt;

&lt;p&gt;That translation lives in &lt;code&gt;libjailbreak&lt;/code&gt;. It also holds a table of kernel structure offsets that vary per Darwin version, because struct layouts change between iOS releases and there are no headers for the ones that matter.&lt;/p&gt;

&lt;p&gt;That table is why version support is enumerated so precisely, and why "it should probably work on the next point release" is never true. A wrong offset is not a bug report, it is a kernel panic.&lt;/p&gt;

&lt;p&gt;Note the layering discipline: exploitation is one module, primitives are another, and every consumer above talks to the primitive API only. A large percentage of the codebase never has to know how privileges were obtained.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 3: the rootless bootstrap
&lt;/h2&gt;

&lt;p&gt;The system volume is sealed, so Dopamine installs into a randomized path under &lt;code&gt;/private/preboot&lt;/code&gt;, and exposes it at &lt;code&gt;/var/jb&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you have ever built software that must be relocatable, this will look familiar. It is &lt;code&gt;/usr/local&lt;/code&gt; versus &lt;code&gt;/usr&lt;/code&gt;, or a container volume mount versus the base image. Every package is compiled to reference &lt;code&gt;/var/jb/...&lt;/code&gt; instead of &lt;code&gt;/&lt;/code&gt;, the actual location is randomized per install, and the symlink hides that indirection from everything above.&lt;/p&gt;

&lt;p&gt;The environment itself is a Procursus bootstrap: a proper Debian-style userland with dpkg, so packages install through Sileo or Zebra using ordinary &lt;code&gt;.deb&lt;/code&gt; semantics.&lt;/p&gt;

&lt;p&gt;Two things fall out of this design. Restoring the device is mostly a matter of deleting a directory, not repairing a system partition. And system updates do not fight with a modified root. "Rootless" sounds like a limitation; in practice it made jailbreaks dramatically less destructive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 4: a capability server inside PID 1
&lt;/h2&gt;

&lt;p&gt;Here is the design decision I find most interesting.&lt;/p&gt;

&lt;p&gt;You have kernel read/write. The naive approach is to hand that to every process that needs it. That is a disaster: any of them can panic the kernel, and every one of them is now a privilege escalation target.&lt;/p&gt;

&lt;p&gt;Dopamine does the opposite. It injects a hook into &lt;code&gt;launchd&lt;/code&gt; (PID 1), and inside it runs &lt;strong&gt;&lt;code&gt;jbserver&lt;/code&gt;&lt;/strong&gt;, a Mach service that owns the privileged primitives. Everything else is a client that sends requests over Mach or XPC via &lt;code&gt;libjailbreak&lt;/code&gt;. Requests are organized into domains, and callers are checked for what they are allowed to ask for.&lt;/p&gt;

&lt;p&gt;That is a broker pattern, straight out of browser sandbox design. One privileged component, a narrow typed API, everyone else unprivileged. The clients cannot corrupt the kernel because they never touch it.&lt;/p&gt;

&lt;p&gt;Putting it inside &lt;code&gt;launchd&lt;/code&gt; also solves persistence: PID 1 never dies while userspace is alive, so the jailbreak state outlives the app entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 5: getting into every process
&lt;/h2&gt;

&lt;p&gt;For tweaks to work, code has to load into arbitrary system processes. Two components handle this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;dyldhook&lt;/code&gt;&lt;/strong&gt; patches the dynamic linker itself. It runs before the process's &lt;code&gt;main&lt;/code&gt;, checks the process in with &lt;code&gt;jbserver&lt;/code&gt; to receive its sandbox extensions and environment info, and handles library validation by making sure a library's signature is registered in the trust cache before the kernel evaluates it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;systemhook.dylib&lt;/code&gt;&lt;/strong&gt; is inserted via &lt;code&gt;DYLD_INSERT_LIBRARIES&lt;/code&gt; and does the ongoing work: loading tweaks, and hooking &lt;code&gt;posix_spawn&lt;/code&gt; and &lt;code&gt;execve&lt;/code&gt; so that every child process inherits the injection.&lt;/p&gt;

&lt;p&gt;That last detail is the whole trick. Think &lt;code&gt;LD_PRELOAD&lt;/code&gt;, except it re-preloads itself into everything it spawns. Inject once into PID 1, and the property propagates down the entire process tree by induction. You never have to enumerate processes or race a launch.&lt;/p&gt;

&lt;p&gt;Actual tweak hooking is delegated to ElleKit, an open-source hooking library that replaced the old proprietary Substrate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 6: making the system not notice
&lt;/h2&gt;

&lt;p&gt;This is where most of the engineering hours actually went, and it is the least glamorous part.&lt;/p&gt;

&lt;p&gt;Once you modify a running process, the OS starts noticing. &lt;code&gt;csops&lt;/code&gt; reports the process as invalid. On iOS 16 the networking policy layer began checking code signing validity, which meant modified processes silently lost network access. So &lt;code&gt;systemhook&lt;/code&gt; hooks those paths and re-validates.&lt;/p&gt;

&lt;p&gt;On arm64e, &lt;code&gt;fork()&lt;/code&gt; breaks, because the child needs to inherit memory protections and signing state that the kernel will not copy for it. The fix, &lt;code&gt;forkfix&lt;/code&gt;, is a small masterpiece of pragmatism: hook &lt;code&gt;__fork&lt;/code&gt;, use a pipe pair to freeze the child immediately after it appears, have the parent ask &lt;code&gt;jbserver&lt;/code&gt; to apply the necessary fixups to the child PID, then let it continue.&lt;/p&gt;

&lt;p&gt;There is also a jetsam multiplier, because processes carrying a stack of injected tweaks blow through memory limits and get killed.&lt;/p&gt;

&lt;p&gt;None of this is the exciting part of a jailbreak. All of it is why it is usable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Semi-untethered, and the userspace reboot
&lt;/h2&gt;

&lt;p&gt;Everything above lives in memory. A real reboot wipes it, which is what "semi-untethered" means: the device boots stock, and you reopen the app to re-apply.&lt;/p&gt;

&lt;p&gt;There is a middle option, though. A &lt;strong&gt;userspace reboot&lt;/strong&gt; tears down and restarts userland without a kernel boot, which means the jailbreak state can be re-established without re-running an exploit. Practically, it turns "something broke, reboot and start over" into a thirty-second operation. &lt;code&gt;launchdhook&lt;/code&gt; is what makes it possible, because PID 1 is where the state lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  The map
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Dopamine.app
        |
        | selects + runs
        v
  Exploit bundle  ---&amp;gt;  libjailbreak  (kernel primitives, version offsets)
                              |
                              v
                        launchdhook  in PID 1
                              |
                        +-----+-----+
                        |  jbserver |  &amp;lt;---- Mach / XPC ---- every process
                        +-----+-----+
                              |
   /var/jb  ------------------+   trust cache, sandbox extensions, fixups
   (Procursus bootstrap)
                              |
     dyldhook + systemhook injected into each process on spawn
                              |
                        ElleKit -&amp;gt; tweaks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What transfers to normal software
&lt;/h2&gt;

&lt;p&gt;Strip away the iOS specifics and there are four patterns here worth borrowing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Isolate the volatile part behind an interface.&lt;/strong&gt; Exploits are plugins with declared compatibility. When your dependency on the outside world is guaranteed to break, make replacing it a config change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Centralize dangerous capability, distribute access.&lt;/strong&gt; One broker owns the primitive, everyone else gets a narrow API. This is browser sandboxing, syscall filtering, and &lt;code&gt;jbserver&lt;/code&gt;, all the same shape.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make your install root relocatable and additive.&lt;/strong&gt; Never modify what you do not own. Add a prefix and indirect through it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Propagate through inheritance, not enumeration.&lt;/strong&gt; Hooking spawn beats scanning for processes, in the same way that fixing a base image beats patching running containers.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;p&gt;Jailbreaking is legal in many jurisdictions but not all, it voids your warranty, and the same mechanisms described here are why a jailbroken device is a weaker security boundary than a stock one. Read the code for the engineering. That is where the value is.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>mobile</category>
      <category>security</category>
      <category>programming</category>
    </item>
    <item>
      <title>Xcode 27: Everything Developers Need to Know</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Mon, 10 Aug 2026 10:27:47 +0000</pubDate>
      <link>https://dev.to/arshtechpro/xcode-27-everything-developers-need-to-know-6lh</link>
      <guid>https://dev.to/arshtechpro/xcode-27-everything-developers-need-to-know-6lh</guid>
      <description>&lt;p&gt;Xcode 27 is in beta right now (beta 4 at the time of writing), and it is a big release. Apple's official release notes are lots of lines of radar numbers and one-line bug fixes, which is great as a reference and terrible as a read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can you even run it?
&lt;/h2&gt;

&lt;p&gt;Before anything else, check these three lines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Xcode 27 requires a Mac running &lt;strong&gt;macOS Tahoe 26.4 or later&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Xcode 27 &lt;strong&gt;only installs and runs on Apple silicon Macs&lt;/strong&gt;. If you are still on an Intel Mac, this release is the end of the line for you.&lt;/li&gt;
&lt;li&gt;On-device debugging supports &lt;strong&gt;iOS 17+, tvOS 17+, watchOS 10+, and visionOS&lt;/strong&gt;. Older devices are no longer debuggable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It ships with &lt;strong&gt;Swift 6.4&lt;/strong&gt; and the SDKs for iOS 27, iPadOS 27, tvOS 27, watchOS 27, macOS 27, and visionOS 27.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 1: The breaking changes (read this section)
&lt;/h2&gt;

&lt;p&gt;I am putting these first because they are the parts that turn into a red build log on Monday morning. Everything else is upside.&lt;/p&gt;

&lt;h3&gt;
  
  
  Intel is being phased out of your build settings
&lt;/h3&gt;

&lt;p&gt;If a target's minimum deployment target is macOS 27.0 or DriverKit 27.0, it will &lt;strong&gt;no longer build Universal by default&lt;/strong&gt;. The &lt;code&gt;ARCHS_STANDARD&lt;/code&gt; setting drops &lt;code&gt;x86_64&lt;/code&gt; once &lt;code&gt;MACOSX_DEPLOYMENT_TARGET&lt;/code&gt; or &lt;code&gt;DRIVERKIT_DEPLOYMENT_TARGET&lt;/code&gt; is 27.0 or higher.&lt;/p&gt;

&lt;p&gt;If you still ship to Intel Macs, you have two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep your minimum deployment target below macOS 27.0.&lt;/li&gt;
&lt;li&gt;Explicitly add &lt;code&gt;x86_64&lt;/code&gt; back to the &lt;code&gt;ARCHS&lt;/code&gt; build setting.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The macOS 27 SDK can still back-deploy Universal apps down to macOS 12, so this is a default change, not a hard removal.&lt;/p&gt;

&lt;h3&gt;
  
  
  The old linker is gone
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ld64&lt;/code&gt; has been removed and the &lt;code&gt;-ld_classic&lt;/code&gt; flag is no longer supported. If you have that flag lingering in &lt;code&gt;OTHER_LDFLAGS&lt;/code&gt; from some 2023-era workaround, delete it now. This one is a hard failure, not a warning.&lt;/p&gt;

&lt;h3&gt;
  
  
  On Demand Resources is deprecated
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;NSBundleResourceRequest&lt;/code&gt; and the whole On Demand Resources system are deprecated. The replacement is &lt;strong&gt;Background Assets&lt;/strong&gt;, which got a solid round of improvements this release (more on that below).&lt;/p&gt;

&lt;h3&gt;
  
  
  PreviewProvider is deprecated
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;PreviewProvider&lt;/code&gt; and its family of preview modifiers are now deprecated. If you still have the old struct-based previews hanging around, this is your nudge to move to the &lt;code&gt;#Preview&lt;/code&gt; macro.&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ContentView_Previews&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;PreviewProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;previews&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="cp"&gt;#Preview {&lt;/span&gt;
    &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A real Swift source break
&lt;/h3&gt;

&lt;p&gt;This one is subtle. A computed property that has both an &lt;code&gt;init&lt;/code&gt; accessor and an array or dictionary literal as its initial value will no longer compile &lt;strong&gt;if the getter is declared before the &lt;code&gt;init&lt;/code&gt; accessor&lt;/strong&gt;. This is a known source break from SE-0508.&lt;/p&gt;

&lt;p&gt;Broken:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;S&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;_strings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_strings&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kd"&gt;@storageRestrictions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;initializes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_strings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;init&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_strings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newValue&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix is to reorder: declare the &lt;code&gt;init&lt;/code&gt; accessor first, then the getter. Annoying, but a one-line move.&lt;/p&gt;

&lt;h3&gt;
  
  
  Duplicate Clang module names now fail
&lt;/h3&gt;

&lt;p&gt;The Swift dependency scanner was optimized to skip redundant header searches, and the tradeoff is that &lt;strong&gt;every Clang module reachable from a single dependency scan must have a unique module name&lt;/strong&gt;. Previously the scanner tolerated duplicates; now it may error out.&lt;/p&gt;

&lt;p&gt;The two situations that trigger this in the real world:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An SDK or project that vends the same Clang module name from more than one location on the header search path.&lt;/li&gt;
&lt;li&gt;Vendored third-party sources shipping a &lt;code&gt;module.modulemap&lt;/code&gt; that redeclares a module already in the SDK.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you get a mysterious scanning error after upgrading, this is the first thing to check.&lt;/p&gt;

&lt;h3&gt;
  
  
  C++ changes worth knowing
&lt;/h3&gt;

&lt;p&gt;If you have C++ in your app, a few things moved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The minimum macOS deployment target for the C++ standard library is now &lt;strong&gt;11.0&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;multimap::find&lt;/code&gt; and &lt;code&gt;multiset::find&lt;/code&gt; no longer guarantee returning an iterator to the first equal element. libc++ used to do this by accident, and the Standard never promised it. Use &lt;code&gt;lower_bound&lt;/code&gt; or &lt;code&gt;equal_range&lt;/code&gt; if you were relying on it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lower_bound&lt;/code&gt; and &lt;code&gt;upper_bound&lt;/code&gt; on &lt;code&gt;std::map&lt;/code&gt; and &lt;code&gt;std::set&lt;/code&gt; behave differently for comparators that are not a strict weak order. Defining &lt;code&gt;_LIBCPP_ENABLE_LEGACY_TREE_LOWER_UPPER_BOUND&lt;/code&gt; gets the old behavior back, but that escape hatch is going away, likely next release.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bitset::operator[]&lt;/code&gt; now returns &lt;code&gt;bool&lt;/code&gt;, which is actually what the Standard says it should do.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;std::allocator&lt;/code&gt; is now trivially default-constructible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The upside is significant: associative and unordered containers got up to 11x faster in some functions, several algorithms got up to 3x faster, and &lt;code&gt;distance&lt;/code&gt; on non-random-access segmented iterators improved dramatically. A number of C++ papers landed too, including &lt;code&gt;std::optional&amp;lt;T&amp;amp;&amp;gt;&lt;/code&gt;, &lt;code&gt;zip&lt;/code&gt;, and &lt;code&gt;std::views::indices(n)&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 2: Agentic coding is now a first-class citizen
&lt;/h2&gt;

&lt;p&gt;This is the headline feature, and it is a lot more than a chat box.&lt;/p&gt;

&lt;h3&gt;
  
  
  The assistant moved out of the sidebar
&lt;/h3&gt;

&lt;p&gt;The coding assistant now lives &lt;strong&gt;in the editor area&lt;/strong&gt;, not the navigator, with a redesigned conversation transcript. Artifacts the agent produces (code diffs, plans, SwiftUI preview snapshots) show up next to the transcript, and you can annotate code snippets and plan documents to give targeted inline feedback without leaving the conversation.&lt;/p&gt;

&lt;p&gt;The sidebar is now dedicated purely to organizing conversations: real-time status, unread indicators, drag-and-drop grouping, archiving, renaming, multi-select for bulk actions, and a context menu to open conversations in new tabs, windows, or editor panes.&lt;/p&gt;

&lt;p&gt;There is also a &lt;strong&gt;New Conversation&lt;/strong&gt; button in the toolbar that works from anywhere in Xcode, with a status indicator you can click to jump to whichever conversation needs your attention.&lt;/p&gt;

&lt;h3&gt;
  
  
  Plan mode
&lt;/h3&gt;

&lt;p&gt;Planning is now a proper feature rather than a prompting trick. Plans appear as &lt;strong&gt;editable Markdown artifacts&lt;/strong&gt; next to the conversation. You review, annotate, discuss changes, and approve before the agent writes any code.&lt;/p&gt;

&lt;p&gt;This is the workflow I would recommend for anything non-trivial: get the plan right first, then let it execute.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agents can actually run your app
&lt;/h3&gt;

&lt;p&gt;This is the part that changes what agents are useful for. In Xcode 27, agents can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Boot simulators, install and launch apps, synthesize touch events, and capture screenshots to verify UI behavior.&lt;/li&gt;
&lt;li&gt;Manipulate the active run state, read and interact with the debugger console.&lt;/li&gt;
&lt;li&gt;List and switch between schemes and run destinations.&lt;/li&gt;
&lt;li&gt;Inspect and modify build settings, compiler flags, entitlements, and Info.plist keys.&lt;/li&gt;
&lt;li&gt;Access project insights such as crashes, disk writes, energy, hangs, and launch issues affecting your shipped app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, an agent can now write a fix, build it, run it, look at the screen, and check whether it worked.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gemini, ACP, and plugins
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Gemini&lt;/strong&gt; is now available in the coding assistant alongside the existing options.&lt;/li&gt;
&lt;li&gt;Xcode supports the &lt;strong&gt;Agent Client Protocol (ACP)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Agents can be extended with &lt;strong&gt;plugins&lt;/strong&gt; containing skills, MCP servers, and ACP agent configurations. Skills are invokable as slash commands with completion support.&lt;/li&gt;
&lt;li&gt;Apple ships its own &lt;strong&gt;specialists&lt;/strong&gt; for targeted tasks like localization, UIKit resizing, and accessibility.&lt;/li&gt;
&lt;li&gt;Two security-focused skills landed in beta 3: &lt;code&gt;adopt-c-bounds-safety&lt;/code&gt; for a file-by-file &lt;code&gt;-fbounds-safety&lt;/code&gt; adoption workflow, and &lt;code&gt;audit-xcode-security-settings&lt;/code&gt; to suggest security-oriented build settings and entitlements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plugin authors can customize how their MCP servers appear in the UI using &lt;code&gt;_meta&lt;/code&gt; fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MyGreatPlugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"An awesome MCP server configuration."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"MyGreatMCP"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"_meta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ideToolIconPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./icon.svg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ideToolIconRendersAsTemplate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ideToolTitles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"whoami"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Who Am I"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"get-current-email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Get Current Email Message"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A sandbox for agents
&lt;/h3&gt;

&lt;p&gt;Coding Intelligence includes a &lt;strong&gt;new security layer that monitors and controls filesystem access&lt;/strong&gt; by coding agents and any processes they spawn. It is opt-in via Coding Intelligence settings. If you are nervous about letting an agent loose in your repo, turn this on before you do anything else.&lt;/p&gt;

&lt;h3&gt;
  
  
  One known issue to watch
&lt;/h3&gt;

&lt;p&gt;If the "Implement the plan?" confirmation bar appears while the agent is still streaming, clicking Yes or No can start a new agent turn on top of the in-flight one and leave the conversation in a broken state. &lt;strong&gt;Wait for the agent to finish responding before confirming.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 3: Device Hub replaces the Simulator workflow
&lt;/h2&gt;

&lt;p&gt;Device Hub is the new unified interface for both simulators and physical devices. Two features stand out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wireless pairing
&lt;/h3&gt;

&lt;p&gt;You can now pair iPhone, iPad, and Apple Watch running OS 27 or later &lt;strong&gt;over a network&lt;/strong&gt;. Click the &lt;code&gt;+&lt;/code&gt; button in the Device Hub sidebar and choose "Pair Nearby Device". No cable needed for iPhone and iPad, and watch pairing is noticeably more reliable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mouse and trackpad gestures on iOS
&lt;/h3&gt;

&lt;p&gt;Standard Mac gestures (scrolling, pinching, rotating) now work with UIKit components on iOS devices, physical or simulated.&lt;/p&gt;

&lt;p&gt;There is a nuance worth understanding here. When you scroll with a pointing device, &lt;code&gt;UIEvent.EventType.scroll&lt;/code&gt; is emitted. Pinch or rotate produces &lt;code&gt;UIEvent.EventType.transform&lt;/code&gt;. But clicking with a mouse produces a simulated finger touch of type &lt;code&gt;UITouch.TouchType.direct&lt;/code&gt;, &lt;strong&gt;not&lt;/strong&gt; &lt;code&gt;UITouch.TouchType.indirectPointer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is a convenience hybrid, not a faithful simulation. To validate real pointer behavior, use "Simulate Trackpad or Mouse" from the Device menu, test on a physical iPad with a paired pointing device, or use iPhone Mirroring.&lt;/p&gt;

&lt;h3&gt;
  
  
  Simulator boot is faster
&lt;/h3&gt;

&lt;p&gt;Simulator runtimes now ship with a &lt;strong&gt;pre-built dyld cache&lt;/strong&gt;, which makes the first launch of a simulator much faster. Small change, noticeable every single day.&lt;/p&gt;

&lt;h3&gt;
  
  
  Device Hub rough edges
&lt;/h3&gt;

&lt;p&gt;Beta software, so expect some friction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Game controllers only work with the visionOS simulator.&lt;/li&gt;
&lt;li&gt;Video and input for a physical Apple Vision Pro are not supported (use AirPlay to view remotely). Everything else, like settings and DeviceFS, works.&lt;/li&gt;
&lt;li&gt;Two-finger touches cannot be sent.&lt;/li&gt;
&lt;li&gt;Scrolling over an Apple Watch face does not emulate the digital crown. Move the pointer over the crown in the bezel instead.&lt;/li&gt;
&lt;li&gt;Devices may not appear during parallel testing even though tests are running. Disable parallel runs if you want to watch UI tests execute.&lt;/li&gt;
&lt;li&gt;Downloaded app data containers land in a folder named after the bundle identifier rather than a proper &lt;code&gt;.xcappdata&lt;/code&gt; bundle. The workaround is to rename the folder with an &lt;code&gt;.xcappdata&lt;/code&gt; extension and nest the contents inside an &lt;code&gt;AppData&lt;/code&gt; subfolder.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Part 4: Previews and Playgrounds
&lt;/h2&gt;

&lt;p&gt;Previews got real quality-of-life work this cycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Argument grids
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;#Preview(arguments:)&lt;/code&gt; renders a grid of previews, one per argument. Click any cell to open it in Interactive mode. This is great for state matrices.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="cp"&gt;#Preview(arguments: [&lt;/span&gt;
    &lt;span class="kt"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;singleItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;manyItems&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="kt"&gt;OrderSummaryView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Resizable canvas
&lt;/h3&gt;

&lt;p&gt;iOS previews get a &lt;strong&gt;Resizable Canvas&lt;/strong&gt; mode so you can view your view in arbitrarily sized containers rather than fixed device frames. Helpful for adaptive layout work, and no longer constrained to specific size ratios.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other improvements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Each &lt;code&gt;#Preview&lt;/code&gt; and &lt;code&gt;#Playground&lt;/code&gt; tab can be &lt;strong&gt;pinned independently&lt;/strong&gt; in the canvas.&lt;/li&gt;
&lt;li&gt;You can preview your UI &lt;strong&gt;in a different localization&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Code inside &lt;code&gt;#Preview&lt;/code&gt; now explicitly runs on the main actor, so calling main-actor-isolated APIs no longer produces concurrency warnings or runtime check failures. That was a real annoyance under strict concurrency.&lt;/li&gt;
&lt;li&gt;Holding Command routes zoom and scroll events to the canvas. Toggleable via Editor &amp;gt; Canvas.&lt;/li&gt;
&lt;li&gt;Error messages across previews are meaningfully better: clearer timeouts, full diagnostics on MCP tool failures, and a placeholder view instead of a silent macOS fallback when a runtime is not installed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Known issue:&lt;/strong&gt; standalone Swift files opened by double-clicking in Finder may fail to run &lt;code&gt;#Playground&lt;/code&gt; or &lt;code&gt;#Preview&lt;/code&gt; blocks. Use File &amp;gt; Open, or drag onto the Dock icon.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 5: Localization becomes an agent workflow
&lt;/h2&gt;

&lt;p&gt;This is probably the most immediately practical feature for small teams.&lt;/p&gt;

&lt;p&gt;Agents can now translate strings in String Catalogs, from a single feature to an entire project, into one or more languages. Xcode handles the plumbing: it &lt;strong&gt;adds languages to your project settings, creates missing String Catalogs, and feeds context to the agent&lt;/strong&gt; as it translates.&lt;/p&gt;

&lt;p&gt;The String Catalog editor has a &lt;strong&gt;Generate Translations&lt;/strong&gt; button, and you can right-click specific strings to translate just those.&lt;/p&gt;

&lt;p&gt;Supporting changes that make this workable in a real pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A localization comment of "do not translate" automatically marks the string as &lt;strong&gt;Don't Translate&lt;/strong&gt; in String Catalogs and &lt;code&gt;translate="no"&lt;/code&gt; in exported XLIFFs.&lt;/li&gt;
&lt;li&gt;Exported XLIFFs use &lt;code&gt;state-qualifier="leveraged-mt"&lt;/code&gt; to flag machine-translated strings, so your human translators know what to review.&lt;/li&gt;
&lt;li&gt;You can annotate translations in String Catalog artifacts when agents are translating.&lt;/li&gt;
&lt;li&gt;Exporting localizations now extracts &lt;code&gt;NSLocalizedString&lt;/code&gt; and similar macros from &lt;strong&gt;header files&lt;/strong&gt;, not just implementation files. That is a fifteen-year-old bug report finally closed.&lt;/li&gt;
&lt;li&gt;The "Prepare Project for Localization" tool surfaces newly added strings as artifacts, and shows keys removed because they no longer appear in source.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The honest take: this handles the bulk of the work, but you still want a professional translator reviewing the output before you ship to a new market.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 6: Instruments got a serious upgrade
&lt;/h2&gt;

&lt;p&gt;If you do performance work, this might be the most valuable section of the release.&lt;/p&gt;

&lt;h3&gt;
  
  
  Swift Concurrency tooling
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A new &lt;strong&gt;Swift Executors instrument&lt;/strong&gt; shows tracks for the Cooperative Thread Pool, the Main Actor, and any type conforming to &lt;code&gt;TaskExecutor&lt;/code&gt; or &lt;code&gt;SerialExecutor&lt;/code&gt;. Full capture on OS 27; older systems show "Unknown executor".&lt;/li&gt;
&lt;li&gt;Task tracks now group into &lt;strong&gt;Swift Task Collections&lt;/strong&gt;, sorted by name or creation site. You can switch a collection track between showing task lifetimes and task states.&lt;/li&gt;
&lt;li&gt;Tasks, Collections, Actors, and Executors have a &lt;strong&gt;Profile&lt;/strong&gt; detail showing a call tree built from data captured while the task was running. Requires recording alongside Time Profiler or CPU Profiler.&lt;/li&gt;
&lt;li&gt;Selecting a bar chart interval in an Actor or Executor queue plot lists the waiting tasks in the inspector.&lt;/li&gt;
&lt;li&gt;Tasks and Actors whose lifetime started before the trace now show up, on a best-effort basis.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;language swift task tree&lt;/code&gt; in LLDB prints a tree of every Swift Task the debugger knows about.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Foundation Models instrument
&lt;/h3&gt;

&lt;p&gt;New instrument for tracing and debugging Foundation Models usage: instructions, prompts, responses, token usage, and inference performance. If you are shipping on-device AI features, you now have visibility into them.&lt;/p&gt;

&lt;h3&gt;
  
  
  System Trace and QoS
&lt;/h3&gt;

&lt;p&gt;System Trace unifies system calls, VM faults, and thread states into a &lt;strong&gt;single plot&lt;/strong&gt;, with a blending algorithm that keeps dense regions readable when zoomed out. You can walk the chain of scheduling events for a thread with left/right arrow keys, and the inspector offers quick actions like pinning the thread that made another thread runnable.&lt;/p&gt;

&lt;p&gt;The System Trace template also graphs &lt;strong&gt;thread priority over time&lt;/strong&gt;, which makes priority inversion and resource starvation much easier to spot. Thread Activity now displays effective QoS by default, with requested QoS available from the track dropdown.&lt;/p&gt;

&lt;h3&gt;
  
  
  Everything else in Instruments
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Graphs no longer rescale to the local maximum when you pan the timeline, so comparisons across tracks are consistent. Manual rescale is under View &amp;gt; Rescale.&lt;/li&gt;
&lt;li&gt;Pinned tracks are restored from the previous run, and can be saved and restored explicitly via View &amp;gt; Track States.&lt;/li&gt;
&lt;li&gt;A new inspector shows details for the selected event with quick actions for pinning, filtering tracks, and filtering the detail view.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;os_log&lt;/code&gt; data can be overlaid on process and thread tracks via the Track Graph Display popover.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;os_signpost&lt;/code&gt; gets a track per signpost name, nested under the category.&lt;/li&gt;
&lt;li&gt;The SwiftUI instrument records more detail about layout passes and why a layout computation was not cached, plus a "Summary of Updates" focus action on the view hierarchy.&lt;/li&gt;
&lt;li&gt;Allocations marks tagged allocations with a &lt;code&gt;(tagged)&lt;/code&gt; suffix when the process runs with Memory Integrity Enforcement.&lt;/li&gt;
&lt;li&gt;Drag and drop &lt;code&gt;.atrc&lt;/code&gt;, &lt;code&gt;.logarchive&lt;/code&gt;, or &lt;code&gt;.sample&lt;/code&gt; files onto the sidebar to create a run for each.&lt;/li&gt;
&lt;li&gt;Memory usage when importing &lt;code&gt;.atrc&lt;/code&gt; files dropped by roughly 1.5 GB on average.&lt;/li&gt;
&lt;li&gt;Animation Hitches supports visionOS 27.0 and later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the command line, &lt;code&gt;xctrace record&lt;/code&gt; accepts recording options as JSON (&lt;code&gt;--show-recording-options&lt;/code&gt; prints the available ones), &lt;code&gt;xctrace export&lt;/code&gt; can restrict a time range, and export now takes &lt;code&gt;.atrc&lt;/code&gt; and &lt;code&gt;.logarchive&lt;/code&gt; directly instead of requiring an import step first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deprecation:&lt;/strong&gt; Instruments now requires iOS 17, watchOS 10, or tvOS 17 as a minimum on target devices.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 7: Organizer and production insights
&lt;/h2&gt;

&lt;p&gt;The Organizer quietly became a planning tool rather than a crash dump viewer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Insights Overview&lt;/strong&gt; summarizes high-impact performance regressions across metrics and diagnostic reports, so you can prioritize instead of guessing.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Hitches&lt;/strong&gt; metric replaces Scrolling and covers all animations in your app, not just scroll.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage metrics&lt;/strong&gt; track Documents &amp;amp; Data and app size across releases, which catches cache bloat and bundle growth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-driven analysis&lt;/strong&gt; generates recommendations for Crash, Energy, Disk Write, Hang, and Launch diagnostics, with links into your source and the coding assistant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metric goals&lt;/strong&gt; now cover Battery Usage, Disk Writes, Hang Rate, Hitches, Memory, and Storage. Similar-app goals are supported for Hang Rate, on-screen Battery Usage, Disk Writes, and Storage, and Launch Time baselines were recalibrated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combined with agents having access to these insights, the loop is: Organizer flags a hang regression, you ask the agent about it, the agent reads the diagnostic and proposes a fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 8: Testing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;XCUIVoiceOverService&lt;/code&gt;&lt;/strong&gt; is a new UI testing API for verifying VoiceOver behavior. You can drive VoiceOver from UI tests and assert on focus, spoken output, and navigation. Accessibility testing that actually tests accessibility.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;launch test file template&lt;/strong&gt; that opts into &lt;code&gt;runsForEachTargetApplicationUIConfiguration&lt;/code&gt;, so the test runs across every combination of orientation, localization, and appearance your app supports.&lt;/li&gt;
&lt;li&gt;Test plans let you choose how the system responds when the target app crashes during UI testing: off, warning, failure (the default), or fatal failure.&lt;/li&gt;
&lt;li&gt;Mixing frameworks now warns you: calling an XCTest assertion inside a Swift Testing test (or the reverse) produces a warning-severity runtime issue. Configurable via the new interoperability setting in your test plan.&lt;/li&gt;
&lt;li&gt;Filters were added to the test plan configurations tab, plus recent tests and open tests filters in the Test Navigator.&lt;/li&gt;
&lt;li&gt;Large Swift Testing suites with many parameterized cases perform significantly better.&lt;/li&gt;
&lt;li&gt;Test Repetition Mode now repeats individual Swift Testing cases instead of the whole test plan.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the SwiftPM side, &lt;code&gt;swift test&lt;/code&gt; summarizes failures at the end of a run, and supports &lt;code&gt;--maximum-repetitions&lt;/code&gt; with &lt;code&gt;--repeat-until [pass|fail]&lt;/code&gt; for hunting flaky tests. Only cases matching the condition get repeated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;swift &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--maximum-repetitions&lt;/span&gt; 20 &lt;span class="nt"&gt;--repeat-until&lt;/span&gt; fail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Part 9: Everything else worth a mention
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Interface Builder builds without a simulator
&lt;/h3&gt;

&lt;p&gt;A new IB compilation mode, &lt;code&gt;toolchain&lt;/code&gt;, is &lt;strong&gt;enabled by default&lt;/strong&gt; for UIKit documents. It compiles IB documents without downloading a simulator, which is a genuine win for CI and build servers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Background Assets
&lt;/h3&gt;

&lt;p&gt;Since On Demand Resources is deprecated, this matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Asset-pack manifests support path wildcards, file exclusion, hard-coded source roots, and custom destination subpaths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Localized asset packs&lt;/strong&gt;: the system delivers only the packs matching the user's preferred languages, cutting storage usage.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Steam Asset Converter&lt;/strong&gt; turns Steam depots into asset packs.&lt;/li&gt;
&lt;li&gt;Xcode can serve asset packs to your app while debugging on device. Set a Background Asset Packs folder in the Run scheme action's Options tab.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  StoreKit testing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;New configuration UI for &lt;strong&gt;In-App Purchase offer codes&lt;/strong&gt;, plus off-device purchase options to test them through the Transaction Manager.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subscription Bundles&lt;/strong&gt; and &lt;strong&gt;Subscription Suites&lt;/strong&gt; can be configured for local testing.&lt;/li&gt;
&lt;li&gt;Volume purchase transactions can be created for 1-month and 1-year auto-renewing subscriptions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Swift and C++ interoperability
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;C++ constructors with default parameter expressions no longer require passing every argument explicitly from Swift.&lt;/li&gt;
&lt;li&gt;Swift closures convert to &lt;code&gt;std::function&lt;/code&gt; instances.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;__counted_by&lt;/code&gt; and &lt;code&gt;__noescape&lt;/code&gt;-annotated &lt;code&gt;std::span&lt;/code&gt; parameters map to Swift &lt;code&gt;Span&lt;/code&gt; without the experimental feature flag. Return values via &lt;code&gt;__lifetimebound&lt;/code&gt; still need it.&lt;/li&gt;
&lt;li&gt;Safe wrappers can be generated for functions taking &lt;code&gt;std::span&lt;/code&gt; directly, without hiding the instantiation behind a typedef, as long as the parameter is &lt;code&gt;__noescape&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The new &lt;code&gt;SWIFT_REFCOUNTED_PTR&lt;/code&gt; macro bridges smart pointers to intrusively reference-counted types into Swift classes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Debugging
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;LLDB can inspect data types with &lt;code&gt;~Copyable&lt;/code&gt; fields in the standard library and system frameworks.&lt;/li&gt;
&lt;li&gt;LLDB ships with an MCP server (&lt;code&gt;lldb-mcp&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;In projects using bridging headers, LLDB imports explicitly built Swift modules and PCH from DerivedData directly. This can dramatically speed up the first &lt;code&gt;po&lt;/code&gt; in a debug session, which has historically been painfully slow.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Documentation search
&lt;/h3&gt;

&lt;p&gt;You can search developer documentation using &lt;strong&gt;natural language&lt;/strong&gt;, with results matched semantically rather than by keyword.&lt;/p&gt;

&lt;h3&gt;
  
  
  Icon Composer 2.0
&lt;/h3&gt;

&lt;p&gt;Supports a sharper rendering mode for the 2027 operating systems, with refractivity, outside specular, and deeper shadows. Edit the new properties in the group inspector and preview either design generation from the toolbar. One icon covers all OS versions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Migration checklist
&lt;/h2&gt;

&lt;p&gt;If you want the short version, here it is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Confirm you are on an Apple silicon Mac with macOS Tahoe 26.4+.&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;-ld_classic&lt;/code&gt; from your linker flags.&lt;/li&gt;
&lt;li&gt;Decide your Intel story: lower your deployment target, or add &lt;code&gt;x86_64&lt;/code&gt; to &lt;code&gt;ARCHS&lt;/code&gt; explicitly.&lt;/li&gt;
&lt;li&gt;Migrate &lt;code&gt;PreviewProvider&lt;/code&gt; to &lt;code&gt;#Preview&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Plan the move from On Demand Resources to Background Assets.&lt;/li&gt;
&lt;li&gt;Check for duplicate Clang module names if dependency scanning starts failing.&lt;/li&gt;
&lt;li&gt;Fix any &lt;code&gt;init&lt;/code&gt; accessor ordering issues flagged by the Swift compiler.&lt;/li&gt;
&lt;li&gt;If you use C++, audit &lt;code&gt;multimap&lt;/code&gt;/&lt;code&gt;multiset&lt;/code&gt; &lt;code&gt;find&lt;/code&gt; usage and any custom comparators.&lt;/li&gt;
&lt;li&gt;Turn on the agent filesystem security layer before letting agents run.&lt;/li&gt;
&lt;li&gt;Drop your minimum device targets for testing to iOS 17 or later.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;Reference&lt;br&gt;
Source: &lt;a href="https://developer.apple.com/documentation/xcode-release-notes/xcode-27-release-notes" rel="noopener noreferrer"&gt;Xcode 27 Release Notes&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>mobile</category>
      <category>programming</category>
    </item>
    <item>
      <title>Reasonix - Deepseek: A Terminal Coding Agent Built Around the Thing Everyone Else Ignores</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Wed, 05 Aug 2026 18:27:59 +0000</pubDate>
      <link>https://dev.to/arshtechpro/reasonix-deepseek-a-terminal-coding-agent-built-around-the-thing-everyone-else-ignores-3l21</link>
      <guid>https://dev.to/arshtechpro/reasonix-deepseek-a-terminal-coding-agent-built-around-the-thing-everyone-else-ignores-3l21</guid>
      <description>&lt;p&gt;Most terminal coding agents are architecturally similar: a loop, a tool registry, some context management, a TUI. &lt;a href="https://github.com/esengine/DeepSeek-Reasonix" rel="noopener noreferrer"&gt;Reasonix&lt;/a&gt; picks a different thing to optimize for, and it is a thing that shows up on your bill rather than in a demo video.&lt;/p&gt;

&lt;p&gt;The tagline is "engineered around prefix-cache stability — leave it running." That phrase is doing a lot of work, so let's unpack it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why prefix caching is the whole pitch
&lt;/h2&gt;

&lt;p&gt;DeepSeek's API, like several others, caches the prefix of your prompt. If the next request starts with the exact same token sequence as the previous one, the provider serves those tokens from cache and bills them at a small fraction of the normal input rate. Cache hits are dramatically cheaper than cache misses.&lt;/p&gt;

&lt;p&gt;Here is the catch: it is a &lt;em&gt;prefix&lt;/em&gt; cache. The match has to start at token zero and run forward. Change one character near the top of your context and every token after it is a miss.&lt;/p&gt;

&lt;p&gt;Now think about what a typical agent harness does over a long session. It re-summarizes the conversation. It injects a fresh timestamp or a re-scanned directory tree at the top. It reorders tool definitions. It rewrites the system prompt when you switch modes. Every one of those is a mutation near the front of the context, and every one of them silently invalidates the entire cache.&lt;/p&gt;

&lt;p&gt;The result is an agent that feels fine and costs several times what it should. You do not notice, because nothing errors. You just watch the number go up.&lt;/p&gt;

&lt;p&gt;Reasonix's central design constraint is: don't do that. Keep the front of the context stable, append rather than mutate, and put churn where it costs least.&lt;/p&gt;

&lt;h2&gt;
  
  
  What that looks like in practice
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A small, stable environment summary is injected at startup rather than regenerated each turn.&lt;/li&gt;
&lt;li&gt;Stale tool output gets snipped and pruned &lt;em&gt;before&lt;/em&gt; summary compaction kicks in, so a giant &lt;code&gt;cat&lt;/code&gt; result from twenty turns ago is not still sitting in your prefix.&lt;/li&gt;
&lt;li&gt;The built-in tool schema contract is documented and regression-reviewed, because a silent tool-definition reshuffle is a cache invalidation with no visible symptom.&lt;/li&gt;
&lt;li&gt;Two-model mode (executor plus planner) runs each model in its own separate, cache-stable session instead of interleaving them into one context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is the neatest idea in the project. The naive way to add a planner is to inject planning turns into the same conversation, which trashes cache stability for both roles. Keeping them in separate sessions means each one's prefix stays intact.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rest of the architecture
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A single static Go binary.&lt;/strong&gt; &lt;code&gt;CGO_ENABLED=0&lt;/code&gt;, cross-compiles to six targets, and the only dependency is a TOML parser. No Node runtime, no Python venv, no dependency tree to audit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Config-driven, not model-hardcoded.&lt;/strong&gt; Providers, agent settings, enabled tools, and plugins all live in a &lt;code&gt;reasonix.toml&lt;/code&gt;. DeepSeek ships as a preset, but any OpenAI-compatible endpoint is a config entry rather than a code change. Secrets come from the environment and are never written into the config file. Despite the repo name, this is not DeepSeek-only, and it is not an official DeepSeek project — it is a community project that treats DeepSeek as the first-class default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP client.&lt;/strong&gt; External tools run as subprocesses over stdio JSON-RPC, or over Streamable HTTP for remote servers. If you already have an &lt;code&gt;.mcp.json&lt;/code&gt;, drop it in the project root and it is read as-is. Server prompts show up as slash commands, and resources are pulled into a message with &lt;code&gt;@server:uri&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Permissions and sandbox are separate mechanisms.&lt;/strong&gt; Permissions are policy: each tool call is evaluated deny, then ask, then allow, then fallback, and approvals are stored as reusable rules like &lt;code&gt;Bash(go test:*)&lt;/code&gt; rather than one-off clicks. The sandbox is enforcement: file writers refuse any path outside the workspace root, resolving symlinks and &lt;code&gt;..&lt;/code&gt; so a link cannot tunnel out.&lt;/p&gt;

&lt;p&gt;One caveat worth knowing before you use the autonomous mode: &lt;code&gt;bash&lt;/code&gt; itself is jailed via Seatbelt on macOS, but on other platforms it currently runs unconfined. On Linux or Windows, your deny list is the real boundary, not the sandbox. Write it accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; reasonix    &lt;span class="c"&gt;# or: brew install esengine/reasonix/reasonix&lt;/span&gt;

reasonix setup       &lt;span class="c"&gt;# pick a provider, set your key&lt;/span&gt;
reasonix             &lt;span class="c"&gt;# start an interactive session&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is genuinely the whole first run. There is also a desktop app and a VS Code extension that drive the same local engine, plus prebuilt archives on every release if you would rather not go through npm. Everything else — the TOML schema, permission rules, MCP plugins, custom slash commands, two-model setup — is in the &lt;a href="https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/GUIDE.md" rel="noopener noreferrer"&gt;Guide&lt;/a&gt;, and none of it is required to try the thing.&lt;/p&gt;

&lt;p&gt;Two commands worth knowing on day one: &lt;code&gt;/init&lt;/code&gt; generates project instructions, and &lt;code&gt;/branch &amp;lt;turn&amp;gt;&lt;/code&gt; forks the conversation from an earlier checkpointed turn. That second one is a much better recovery move than clearing context and re-explaining everything when the agent goes down a bad path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it worth a look?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Yes, if:&lt;/strong&gt; you already pay for DeepSeek or another OpenAI-compatible endpoint and run long agent sessions, you are cost-sensitive enough to care about cache hit rates, or you want a single static binary instead of a Node or Python install. The MCP support, config-first design, and permission rules are all solid, and the project has real traction — tens of thousands of stars, an active contributor list, CI, code-signed Windows builds, and a bilingual Discord.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Probably not, if:&lt;/strong&gt; you are locked into a provider without prefix caching, in which case the core optimization is irrelevant and you should pick a harness on other merits. Also skip it if you need a mature autonomous-mode sandbox on Linux or Windows today — that is still on the roadmap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Things to go in knowing.&lt;/strong&gt; The active branch is &lt;code&gt;main-v2&lt;/code&gt;, not &lt;code&gt;main&lt;/code&gt;, which is mildly confusing when you clone. If you are on a 0.x config, there is a migration guide, and your old MCP servers are still read as a lowest-priority source. The issue and PR counts are large, which is what you would expect from a fast-moving project at this size — read it as activity, not instability, but check the changelog before upgrading anything you depend on. And the repo name genuinely does mislead people: this is a community project, not something DeepSeek ships.&lt;/p&gt;

&lt;p&gt;The reason I would look at it even if you never install it is the design argument. Most agent harnesses treat context management as a correctness problem: keep the model informed, drop the irrelevant. Reasonix treats it as an economics problem too, where the ordering and stability of your context is a cost variable you control. That reframing is worth twenty minutes of reading whatever tool you end up using.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/esengine/DeepSeek-Reasonix" rel="noopener noreferrer"&gt;https://github.com/esengine/DeepSeek-Reasonix&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>cli</category>
      <category>deepseek</category>
    </item>
    <item>
      <title>LoopX: A Control Plane for AI Agents That Have to Keep Working for Days</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Wed, 05 Aug 2026 18:23:46 +0000</pubDate>
      <link>https://dev.to/arshtechpro/loopx-a-control-plane-for-ai-agents-that-have-to-keep-working-for-days-47n</link>
      <guid>https://dev.to/arshtechpro/loopx-a-control-plane-for-ai-agents-that-have-to-keep-working-for-days-47n</guid>
      <description>&lt;p&gt;If you have ever pointed a coding agent at a multi-day goal, you know the failure mode. It is not that the model writes a bad function. It is that on turn 40, the agent no longer remembers what the objective was, which decision you already made, what is out of scope, or what the last run actually proved. The context window rolled over, and the plot went with it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/huangruiteng/loopx" rel="noopener noreferrer"&gt;LoopX&lt;/a&gt; is an attempt to fix that specific problem. It calls itself "loop engineering for long-running AI agents," and it is a local control plane that sits &lt;em&gt;above&lt;/em&gt; your agent runtime rather than replacing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-sentence version
&lt;/h2&gt;

&lt;p&gt;Your agent (Codex, Claude Code, Cursor, whatever) executes bounded loops. Something (a heartbeat, a cron job, you hitting enter) triggers the next loop. LoopX holds the state that has to survive between those loops.&lt;/p&gt;

&lt;p&gt;The project draws the separation like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Codex / Claude Code / Cursor&lt;/td&gt;
&lt;td&gt;Execute a bounded agent loop: read, write, run commands, respond&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Goal mode / automation / CLI / TUI&lt;/td&gt;
&lt;td&gt;Trigger or schedule the next loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LoopX&lt;/td&gt;
&lt;td&gt;Preserve goals, gates, todos, run history, quota, evidence, handoff state&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That third row is the whole product. LoopX is not an executor and not an autonomous production controller. It is a state kernel with a CLI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "just use a todo file" isn't enough
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;TODO.md&lt;/code&gt; plus a long system prompt gets you surprisingly far. It falls over once any of these become true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The goal changed halfway through, and nothing recorded &lt;em&gt;why&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;A decision genuinely needs a human, and that request evaporated into a chat message nobody read.&lt;/li&gt;
&lt;li&gt;Two agents are touching the same repo and neither knows who owns what.&lt;/li&gt;
&lt;li&gt;The last run claimed success, and there is no artifact proving it.&lt;/li&gt;
&lt;li&gt;Some work is safe and read-only, some crosses into writes, production, or private data, and the distinction lives only in your head.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;LoopX makes those things explicit and machine-readable, which is what lets a loop run longer without becoming less accountable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The concepts, in plain English
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Lifetime goals.&lt;/strong&gt; A durable project intention that outlives one chat thread. Importantly, a lifetime goal does not hand the agent open-ended autonomy: only the next bounded transition is executable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User gates.&lt;/strong&gt; A concrete decision that belongs to you, recorded as a first-class object instead of a sentence in a transcript. The loop can see that it is blocked on a human.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Safe fallback.&lt;/strong&gt; When one lane is gated, audited side paths can keep moving without bypassing the gate. This is the part I find most interesting: the alternative designs are usually "block everything" or "let the agent decide," and both are bad.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Todo ownership.&lt;/strong&gt; Todos are tagged user or agent, with a &lt;code&gt;claimed_by&lt;/code&gt; field so multiple agents can coordinate instead of colliding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quota.&lt;/strong&gt; A guard that answers whether an automatic turn should run right now, wait, ask the user, self-repair, or stay quiet. Practically, this is your defense against a heartbeat loop burning tokens on turns that cannot produce a verified transition.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run history and evidence.&lt;/strong&gt; Compact append-only events for progress, validation, blockers, rewards, and quota spend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Public/private boundary checks.&lt;/strong&gt; A local scan that tries to keep credentials, raw logs, local paths, and private state out of anything you publish.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Requirements are refreshingly light: Python 3.11+, &lt;code&gt;curl&lt;/code&gt;, &lt;code&gt;tar&lt;/code&gt;, and a macOS or Linux shell. The Python package has no runtime dependencies outside the standard library. Git is only needed if you want to contribute.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Install (no clone)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/huangruiteng/loopx/main/scripts/install-from-github.sh | bash
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.local/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
loopx doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The installer drops a release snapshot under &lt;code&gt;~/.local/share/loopx/releases/&lt;/code&gt;, a CLI wrapper in &lt;code&gt;~/.local/bin&lt;/code&gt;, a man page, and reusable agent skills under &lt;code&gt;~/.codex/skills&lt;/code&gt;. As always, read a piped install script before running it if that matters to you.&lt;/p&gt;

&lt;p&gt;Updates go through an explicit interface rather than re-running the installer blind:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;loopx update &lt;span class="nt"&gt;--check&lt;/span&gt;     &lt;span class="c"&gt;# read-only&lt;/span&gt;
loopx update &lt;span class="nt"&gt;--dry-run&lt;/span&gt;   &lt;span class="c"&gt;# read-only&lt;/span&gt;
loopx update &lt;span class="nt"&gt;--execute&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Kick the tires without touching a real repo
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;loopx demo
&lt;span class="nb"&gt;cd&lt;/span&gt; /tmp/loopx-demo
loopx status
loopx quota should-run &lt;span class="nt"&gt;--goal-id&lt;/span&gt; demo-goal
loopx &lt;span class="nb"&gt;history&lt;/span&gt; &lt;span class="nt"&gt;--goal-id&lt;/span&gt; demo-goal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a disposable goal with one user todo and one agent todo. You should see &lt;code&gt;ok: True&lt;/code&gt; and a &lt;code&gt;should_run=True&lt;/code&gt; / &lt;code&gt;state=eligible&lt;/code&gt; quota response. Do this first. It takes thirty seconds and tells you whether the mental model clicks for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Connect a real project
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /path/to/your-project
loopx bootstrap &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--goal-id&lt;/span&gt; your-project-goal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--objective&lt;/span&gt; &lt;span class="s2"&gt;"Improve this project through bounded, verified goal segments."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--goal-doc&lt;/span&gt; GOAL.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;loopx connect&lt;/code&gt; is an alias for &lt;code&gt;bootstrap&lt;/code&gt;. This creates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;your-project/
  .loopx/registry.json
  .codex/goals/your-project-goal/ACTIVE_GOAL_STATE.md

~/.codex/loopx/
  goals/&amp;lt;goal-id&amp;gt;/runs/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Add these to &lt;code&gt;.gitignore&lt;/code&gt; before you commit anything:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.loopx/
.codex/goals/
.opencode/goals/
goals/**/ACTIVE_GOAL_STATE.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That state is live local runtime data. Committing a controller's active goal state is how private paths and internal notes end up in a public repo.&lt;/p&gt;

&lt;p&gt;A healthy connection means &lt;code&gt;loopx doctor&lt;/code&gt; passes, both files above exist, &lt;code&gt;loopx status&lt;/code&gt; shows who acts next, and none of it is staged for commit.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The agent-first path
&lt;/h3&gt;

&lt;p&gt;The docs actually push you toward &lt;em&gt;not&lt;/em&gt; running these commands yourself. Paste something like this into Codex, Claude Code, or Cursor from your project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Connect the current project to LoopX. Do not clone the LoopX repository.
If `loopx` is not on PATH, install it with the official no-clone installer.

Then run `loopx doctor`. Working only from the current project root:
1. If LoopX state already exists, reuse it. Do not overwrite the goal or objective.
2. If the project is not connected, prefer `loopx connect`; use `loopx bootstrap`
   only when state clearly needs initialization.
3. Ensure `.loopx/`, `.codex/goals/`, and `.local/` are ignored.
4. Set up the thin LoopX heartbeat for this surface.
5. Stop after setup and report the active state id, current user gate, top agent
   todo, and next safe action.

Do not start longer delivery work in this setup turn.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One caveat worth knowing before you try this with a non-Codex agent: LoopX can only drive an agent that exposes at least one control hook, such as shell execution, a goal/task command, an automation hook, or its own scheduler. Without one, LoopX still tracks state, but you run the commands by hand.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. The daily loop
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;loopx status
loopx &lt;span class="nb"&gt;history&lt;/span&gt; &lt;span class="nt"&gt;--goal-id&lt;/span&gt; your-project-goal
loopx quota should-run &lt;span class="nt"&gt;--goal-id&lt;/span&gt; your-project-goal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;loopx todo add &lt;span class="nt"&gt;--goal-id&lt;/span&gt; your-project-goal &lt;span class="nt"&gt;--role&lt;/span&gt; agent &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--text&lt;/span&gt; &lt;span class="s2"&gt;"Run the next bounded validation slice."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Diagnosing is also meant to be delegated. &lt;code&gt;loopx diagnose&lt;/code&gt; deliberately emits an agent-facing evidence packet rather than a verdict, so you ask your agent to run it and reason from it: can this project self-drive, what blocks it, what exact question needs your answer, what happens next.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Heartbeats and quota (the token-spend part)
&lt;/h3&gt;

&lt;p&gt;An automatic turn is supposed to check quota before working and record spend exactly once after validated writeback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;loopx quota should-run &lt;span class="nt"&gt;--goal-id&lt;/span&gt; your-project-goal
loopx heartbeat-prompt &lt;span class="nt"&gt;--thin&lt;/span&gt; &lt;span class="nt"&gt;--goal-id&lt;/span&gt; your-project-goal
loopx quota spend-slot &lt;span class="nt"&gt;--goal-id&lt;/span&gt; your-project-goal &lt;span class="nt"&gt;--slots&lt;/span&gt; 1 &lt;span class="nt"&gt;--source&lt;/span&gt; heartbeat &lt;span class="nt"&gt;--execute&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spend is not appended for quiet skips, preflight failures, or dry runs. &lt;code&gt;should-run&lt;/code&gt; returns a fairly rich contract: whether delivery may run, what it is waiting on (user, controller, external evidence, health, quota), the work lane, todo summaries, and the spend policy.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Before you publish anything
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;loopx check &lt;span class="nt"&gt;--scan-path&lt;/span&gt; README.md &lt;span class="nt"&gt;--scan-path&lt;/span&gt; docs/ &lt;span class="nt"&gt;--scan-path&lt;/span&gt; examples/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  8. Optional local dashboard
&lt;/h3&gt;

&lt;p&gt;There is a read-first React dashboard for inspecting projects, todos, gates, and evidence across a global registry. It is explicitly experimental: the CLI stays the source of truth and browser writes require local opt-in. Check the repo docs for the current path, since it moved between the README and the getting-started guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it deliberately does not do
&lt;/h2&gt;

&lt;p&gt;The maintainer is unusually direct about this, which is a good sign. LoopX is not a full agent platform, not an autonomous production controller, and not a replacement for your runtime. Project ownership and dangerous permissions stay with the human. It is a local coordination substrate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it worth a look?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Yes, if:&lt;/strong&gt; you are running agents on goals that span days, you have heartbeat or monitor-style turns firing on a schedule, you coordinate a controller agent with scoped side agents, or you have already been bitten by an agent confidently redoing work it finished last Tuesday. The demo costs you a minute, and the ideas are portable even if you never adopt the tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Probably not yet, if:&lt;/strong&gt; you use agents for single-session tasks, you are on Windows without WSL, you need something battle-tested with a release history and a large user base, or you do not use Codex-family or shell-capable agents. The value shows up proportionally to how long your loops run. Short loops do not drift, and the ceremony will feel like overhead.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/huangruiteng/loopx" rel="noopener noreferrer"&gt;https://github.com/huangruiteng/loopx&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>opensource</category>
      <category>agentskills</category>
    </item>
    <item>
      <title>Qwen3.8-Max Just Went GA: A Developer's Guide to Alibaba's 2.4T Model</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Mon, 03 Aug 2026 22:40:19 +0000</pubDate>
      <link>https://dev.to/arshtechpro/qwen38-max-just-went-ga-a-developers-guide-to-alibabas-24t-model-ff3</link>
      <guid>https://dev.to/arshtechpro/qwen38-max-just-went-ga-a-developers-guide-to-alibabas-24t-model-ff3</guid>
      <description>&lt;p&gt;Alibaba made Qwen3.8-Max generally available on &lt;strong&gt;August 3, 2026&lt;/strong&gt;. &lt;br&gt;
This is a practical rundown for developers: what the model is, what it costs, how to call it, and where the claims still need a pinch of salt.&lt;/p&gt;
&lt;h2&gt;
  
  
  The one-line version
&lt;/h2&gt;

&lt;p&gt;Qwen3.8-Max is a 2.4-trillion-parameter Mixture-of-Experts model with a 1M-token context window, native text/image/video input, OpenAI-compatible API, and pricing of $2 in / $6 out per million tokens. Open weights are promised for next week.&lt;/p&gt;
&lt;h2&gt;
  
  
  First, the naming confusion
&lt;/h2&gt;

&lt;p&gt;The version number trips people up, so let's clear it up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Qwen3&lt;/strong&gt; is the open-weight model family you've probably used (Qwen3-32B, Qwen3-235B-A22B, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Qwen3.5 / 3.6 / 3.7 / 3.8&lt;/strong&gt; are &lt;em&gt;successive flagship generations&lt;/em&gt;, not point releases of Qwen3&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Max"&lt;/strong&gt; is the top tier of each generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So Qwen3.8-Max is not "Qwen 3, version 8." It is the newest flagship, and it succeeds Qwen3.7-Max from May 2026. &lt;cite&gt;Qwen describes it as their most capable model to date, and the first open-weight model at Max scale.&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;Also note there is a &lt;strong&gt;Qwen3.8-Max-Preview&lt;/strong&gt; (July 19) and now &lt;strong&gt;Qwen3.8-Max&lt;/strong&gt; (August 3, GA). If you're reading a blog post from July, it's about the preview, and half its "not disclosed yet" list has since been answered.&lt;/p&gt;
&lt;h2&gt;
  
  
  The specs that matter
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Spec&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total parameters&lt;/td&gt;
&lt;td&gt;2.4 trillion (sparse MoE)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Active parameters per token&lt;/td&gt;
&lt;td&gt;~95 billion (reported)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context window&lt;/td&gt;
&lt;td&gt;1,000,000 tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max input&lt;/td&gt;
&lt;td&gt;991K tokens (983K with thinking on)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max output&lt;/td&gt;
&lt;td&gt;131K tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max reasoning budget&lt;/td&gt;
&lt;td&gt;262K tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Input modalities&lt;/td&gt;
&lt;td&gt;Text, image, video&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate limits&lt;/td&gt;
&lt;td&gt;2M tokens/min, 15K requests/min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model ID&lt;/td&gt;
&lt;td&gt;&lt;code&gt;qwen3.8-max&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Why "95B active" is the number to care about
&lt;/h3&gt;

&lt;p&gt;This is the part worth understanding properly, because "2.4 trillion parameters" is close to meaningless on its own.&lt;/p&gt;

&lt;p&gt;A sparse Mixture-of-Experts model is not one giant network. Each layer holds many specialist sub-networks ("experts"), and a router picks a small handful for each token. The rest sit idle for that token.&lt;/p&gt;

&lt;p&gt;Qwen's own smaller models make the pattern obvious from the naming: &lt;cite&gt;Qwen3-235B-A22B carries 235B total parameters but activates 22B per token, and Qwen3-30B-A3B activates roughly 3B.&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;So:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Total parameters&lt;/strong&gt; ≈ how much the model &lt;em&gt;knows&lt;/em&gt; (and how much memory you'd need to host it)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Active parameters&lt;/strong&gt; ≈ how much compute each token actually costs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At 2.4T total / ~95B active, roughly 4% of the network fires per token. That's why Alibaba can sell it at $2/M input rather than something ruinous.&lt;/p&gt;

&lt;p&gt;One honesty note: this figure needs a small asterisk. &lt;cite&gt;MarkTechPost's launch coverage stated Alibaba had not disclosed the activated-parameter count&lt;/cite&gt;, while &lt;cite&gt;benchmark trackers report that Qwen's own August 3 release post lists 2.4T total with 95B active&lt;/cite&gt;. &lt;cite&gt;Other coverage advises treating the number as reported rather than confirmed until Alibaba publishes a model card.&lt;/cite&gt; Use it for rough cost intuition, not for capacity planning.&lt;/p&gt;

&lt;p&gt;And to be clear: 95B active does &lt;strong&gt;not&lt;/strong&gt; mean you can serve this on a 95B-sized box. &lt;cite&gt;A serving system still needs fast access to the full expert pool, plus attention state, routing machinery, multimodal components and runtime buffers.&lt;/cite&gt; The whole 2.4T checkpoint has to be resident somewhere.&lt;/p&gt;
&lt;h2&gt;
  
  
  Pricing, and the one trick that dominates your bill
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Price per 1M tokens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Input&lt;/td&gt;
&lt;td&gt;$2.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;$6.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Input (implicit cache read)&lt;/td&gt;
&lt;td&gt;$0.25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explicit cache creation&lt;/td&gt;
&lt;td&gt;$2.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explicit cache read&lt;/td&gt;
&lt;td&gt;$0.17&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here's the thing to internalize: &lt;cite&gt;cached input is eight times cheaper than fresh input, which means prefix stability drives your cost more than prompt length does.&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;That single sentence should change how you architect against this model. Concretely, imagine an agent loop with a 200K-token stable prefix (system prompt, tool schemas, codebase context) running 50 turns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No caching:      50 × 0.2M × $2.00                = $20.00
Explicit cache:  (0.2M × $2.50) + (50 × 0.2M × $0.17) = $2.20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Roughly a 9x difference, from nothing but keeping your prefix byte-stable. Practical implications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Put everything static at the &lt;strong&gt;front&lt;/strong&gt; of your prompt, and everything variable at the end&lt;/li&gt;
&lt;li&gt;Don't inject timestamps, request IDs or shuffled context into your system prompt&lt;/li&gt;
&lt;li&gt;If you're rebuilding the prefix per request, you're paying 8x for no reason&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compare against the field: &lt;cite&gt;Kimi K3 runs $3.00 input / $15.00 output per million tokens&lt;/cite&gt;, so Qwen3.8-Max undercuts it meaningfully, particularly on output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calling the API
&lt;/h2&gt;

&lt;p&gt;Integration is deliberately boring, which is the point. &lt;cite&gt;The hosted API is OpenAI- and DashScope-compatible, so integration is a base-URL and model-ID change.&lt;/cite&gt; &lt;cite&gt;It also supports the Anthropic protocol, so tools such as Cursor, Cline, Codex and Claude Code can point at it through existing integrations.&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;Here's the multimodal example straight from the model page, using the DashScope SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dashscope&lt;/span&gt;

&lt;span class="n"&gt;dashscope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_http_api_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://dashscope-intl.aliyuncs.com/api/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/your-image.jpeg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is depicted in the image?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dashscope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MultiModalConversation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DASHSCOPE_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;qwen3.8-max&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're already on the OpenAI SDK, you point &lt;code&gt;base_url&lt;/code&gt; at Alibaba's compatible-mode endpoint and change the model string to &lt;code&gt;qwen3.8-max&lt;/code&gt;. Grab the exact base URL from &lt;code&gt;docs.qwencloud.com&lt;/code&gt; rather than guessing, since it differs between the international and China-domestic deployments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch out for the two regional endpoints.&lt;/strong&gt; &lt;cite&gt;Availability is split between an international deployment (qwencloud.com) and a China-domestic one (platform.qianwenai.com), each requiring separate registration and billing.&lt;/cite&gt; Keys are not interchangeable.&lt;/p&gt;

&lt;p&gt;It's also on third-party gateways already. &lt;cite&gt;Vercel's AI Gateway added it as &lt;code&gt;alibaba/qwen3.8-max&lt;/code&gt; on August 2 at provider pricing with no markup.&lt;/cite&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Supported features
&lt;/h3&gt;

&lt;p&gt;&lt;cite&gt;Function calling, structured outputs, batches, prefix completion and fine-tuning are all supported. Five built-in tools ship on the Responses API: &lt;code&gt;code_interpreter&lt;/code&gt;, &lt;code&gt;web_search&lt;/code&gt;, &lt;code&gt;web_extractor&lt;/code&gt;, &lt;code&gt;t2i_search&lt;/code&gt; and &lt;code&gt;i2i_search&lt;/code&gt;.&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;The built-in tools are worth a look before you hand-roll your own. If you're currently maintaining a custom web-search tool wrapper, that's now a server-side flag.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmarks: the honest read
&lt;/h2&gt;

&lt;p&gt;Unlike the July preview, GA came with actual numbers. &lt;cite&gt;Alibaba published a benchmark table with the formal launch showing 86.6 on Terminal-Bench 2.1, 67.7 on SWE-bench Pro, and 92.6 on GPQA Diamond, with the strongest gains in multimodal and agentic categories rather than general reasoning.&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;Filling in the competitive picture:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Benchmark&lt;/th&gt;
&lt;th&gt;Qwen3.8-Max&lt;/th&gt;
&lt;th&gt;Claude Fable 5&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Terminal-Bench 2.1&lt;/td&gt;
&lt;td&gt;86.6&lt;/td&gt;
&lt;td&gt;84.6&lt;/td&gt;
&lt;td&gt;GPT-5.6 Sol (max) leads at 88.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SWE-bench Pro&lt;/td&gt;
&lt;td&gt;67.7&lt;/td&gt;
&lt;td&gt;80.0&lt;/td&gt;
&lt;td&gt;Fable 5 ahead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FrontierSWE&lt;/td&gt;
&lt;td&gt;73.5&lt;/td&gt;
&lt;td&gt;88.8&lt;/td&gt;
&lt;td&gt;Fable 5 ahead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPQA Diamond&lt;/td&gt;
&lt;td&gt;92.6&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Up marginally from 3.7-Max's 92.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PaperBench&lt;/td&gt;
&lt;td&gt;93.0&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Qwen leads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IFBench&lt;/td&gt;
&lt;td&gt;82.8&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Qwen leads&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;cite&gt;It also tops most vision rows, including OSWorld-Verified at 86.1, Parametric CAD Bench at 91.5, and OmniDocBench 1.5 at 92.1. Against its own predecessor the jump is large: DeepSWE 1.1 moves from 21.6 to 56.6, FrontierSWE from 40.7 to 73.5, and JobBench from 31.3 to 53.4.&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;Two caveats that belong in any fair reading, both flagged by MarkTechPost: &lt;cite&gt;the multimodal table benchmarks against Qwen3.7-Plus rather than Qwen3.7-Max, which flatters the generational delta; and Alibaba's own RL scaling curve peaks at 0.725 near 4,000 training environments, then declines to 0.719 and 0.689.&lt;/cite&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Open weights: read the fine print
&lt;/h2&gt;

&lt;p&gt;&lt;cite&gt;Alibaba confirmed open weights ship next week, along with a second checkpoint, Qwen3.8-27B.&lt;/cite&gt; &lt;cite&gt;Releases are expected the week of August 10 via Alibaba Cloud Model Studio.&lt;/cite&gt; &lt;cite&gt;This marks Alibaba's return to open-sourcing its top-tier models after keeping several recent flagships proprietary earlier this year.&lt;/cite&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's still missing
&lt;/h2&gt;

&lt;p&gt;Being straight about the gaps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No model card.&lt;/strong&gt; &lt;cite&gt;The GA launch still did not provide an official training and safety model card.&lt;/cite&gt; No training data disclosure, no safety evaluation methodology.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No published license.&lt;/strong&gt; Until the weights land, there's nothing to review on commercial-use terms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Active parameter count is reported, not officially confirmed&lt;/strong&gt; in a spec sheet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No independent Artificial Analysis score&lt;/strong&gt; yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Should you use it?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Try it now if:&lt;/strong&gt; you're doing multimodal work (documents, video indexing, screenshots, UI automation), you want long-context agent runs at a fraction of Western frontier pricing, or you have an existing OpenAI/Anthropic-protocol setup where testing costs you a base-URL change and an afternoon.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.qwencloud.com/models/qwen3.8-max" rel="noopener noreferrer"&gt;Qwen3.8-Max model page (QwenCloud)&lt;/a&gt; — canonical specs, pricing, rate limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Running this in production yet? I'd be interested in real latency and tokens-per-second numbers, since Alibaba hasn't published throughput figures.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agentskills</category>
      <category>webdev</category>
    </item>
    <item>
      <title>AirLLM Runs a 70B Model on a 4GB GPU. It's True, and That's Not the Interesting Part</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Mon, 03 Aug 2026 22:23:56 +0000</pubDate>
      <link>https://dev.to/arshtechpro/airllm-runs-a-70b-model-on-a-4gb-gpu-its-true-and-thats-not-the-interesting-part-hha</link>
      <guid>https://dev.to/arshtechpro/airllm-runs-a-70b-model-on-a-4gb-gpu-its-true-and-thats-not-the-interesting-part-hha</guid>
      <description>&lt;p&gt;AirLLM's README opens with a line that sounds like it can't be true:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AirLLM dramatically reduces inference memory usage, letting 70B large language models run on a single 4GB GPU card — without quantization, distillation, or pruning.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So let's actually check it. Is the claim real? How do you set it up? And — the question nobody asks loudly enough — &lt;strong&gt;should you?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; The claim is technically true and the engineering is legitimate.&lt;/p&gt;




&lt;h2&gt;
  
  
  The trick, in one paragraph
&lt;/h2&gt;

&lt;p&gt;Here's the thing about a transformer: it's a stack of layers, and it runs them &lt;strong&gt;in order&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input → Layer 1 → Layer 2 → Layer 3 → ... → Layer 80 → Output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When Layer 1 is computing, layers 2 through 80 are just... sitting in your VRAM. Doing nothing. Taking up space.&lt;/p&gt;

&lt;p&gt;Normal inference loads all 80 layers into GPU memory because keeping them there is fast. AirLLM asks the obvious follow-up question: what if we didn't? Load Layer 1, run it, throw it away, load Layer 2, run it, throw it away.&lt;/p&gt;

&lt;p&gt;Now your VRAM requirement isn't "the size of the model." It's &lt;strong&gt;"the size of the single largest layer."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For a 70B model at full FP16 precision, that's roughly 1.75GB per layer. Which fits in 4GB with room to spare. The model is still 140GB — it just lives on your disk instead of your GPU, streaming through one slice at a time.&lt;/p&gt;

&lt;p&gt;That's it. That's the whole idea. And it genuinely works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Fact-check: is the claim real?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Yes — with an asterisk the size of the model itself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's take the claims one at a time.&lt;/p&gt;

&lt;h3&gt;
  
  
  "70B on a single 4GB GPU"
&lt;/h3&gt;

&lt;p&gt;Real. The math checks out (~1.75GB per layer at FP16), and it's been independently reproduced by enough people that this isn't in dispute.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Without quantization, distillation, or pruning"
&lt;/h3&gt;

&lt;p&gt;Real, and this is the genuinely interesting part. Most "run big models on small hardware" tricks work by &lt;em&gt;making the model worse&lt;/em&gt; — squashing weights from 16 bits to 4, which costs you some accuracy. AirLLM doesn't have to. You get the actual, unmodified, full-precision model.&lt;/p&gt;

&lt;p&gt;(Quantization is &lt;em&gt;optional&lt;/em&gt; here — you can pass &lt;code&gt;compression='4bit'&lt;/code&gt; to make it faster. But you don't have to, and that's the distinction they're drawing.)&lt;/p&gt;

&lt;h3&gt;
  
  
  The bigger numbers, too
&lt;/h3&gt;

&lt;p&gt;The README's scaling table looks absurd but follows from the same logic:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Claimed VRAM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Llama 3.x 70B&lt;/td&gt;
&lt;td&gt;70B&lt;/td&gt;
&lt;td&gt;~4 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Llama 3.1 405B&lt;/td&gt;
&lt;td&gt;405B&lt;/td&gt;
&lt;td&gt;~8 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek-V3&lt;/td&gt;
&lt;td&gt;671B&lt;/td&gt;
&lt;td&gt;~12 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-235B (MoE)&lt;/td&gt;
&lt;td&gt;235B&lt;/td&gt;
&lt;td&gt;~3 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kimi K3 (MoE)&lt;/td&gt;
&lt;td&gt;2.8T&lt;/td&gt;
&lt;td&gt;~3.7 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice something weird? &lt;strong&gt;The 2.8-trillion-parameter model needs less VRAM than the 671B one.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's not an error. Those are Mixture-of-Experts models. An MoE layer contains hundreds of "expert" sub-networks, but each token only routes to a handful of them. Per the v3.1.0 release notes, Kimi K3 holds 896 experts per layer and routes each token to just 16 — so while a full layer's experts expand to ~55GB, a single token only actually needs ~1GB of them. AirLLM streams &lt;em&gt;just those experts&lt;/em&gt; instead of the whole layer.&lt;/p&gt;

&lt;p&gt;Sparser model → smaller working set → less VRAM. Counterintuitive, correct.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the headline leaves out
&lt;/h3&gt;

&lt;p&gt;Here's the part that isn't in the big bold text. From AirLLM's &lt;strong&gt;own v3.1.0 release notes&lt;/strong&gt;, measured on an RTX 6000 Ada:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Peak VRAM during generation&lt;/td&gt;
&lt;td&gt;3.72 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One-time init&lt;/td&gt;
&lt;td&gt;900 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generation speed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;292 s/token, disk-bound&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To be clear about what that means: a 100-token response would take &lt;strong&gt;just over 8 hours&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Credit where it's due — the maintainer publishes this honestly in the release notes. It's just not the number on the marketing line.&lt;/p&gt;

&lt;p&gt;For more typical setups, community reports land in the range of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;70B on a decent NVMe:&lt;/strong&gt; roughly 5–35 seconds per token&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;70B on a MacBook:&lt;/strong&gt; reports as low as ~0.07 tokens/sec (~14 s/token)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For comparison, llama.cpp with a quantized 70B on an RTX 4090:&lt;/strong&gt; 8–15 tokens &lt;em&gt;per second&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the honest version of the claim is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AirLLM doesn't make 70B fast on a 4GB GPU. It makes 70B &lt;em&gt;possible&lt;/em&gt; on a 4GB GPU.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why it's slow (do this math once and you'll never be confused again)
&lt;/h2&gt;

&lt;p&gt;This is worth internalizing, because it explains everything and it's not complicated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To generate one token, the model must run every single layer.&lt;/strong&gt; Which means AirLLM must read &lt;strong&gt;the entire model off disk&lt;/strong&gt; — for &lt;strong&gt;every token&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;seconds per token ≈ model size on disk ÷ disk read speed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's plug in a 70B model at FP16 (~140GB):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Storage&lt;/th&gt;
&lt;th&gt;Speed&lt;/th&gt;
&lt;th&gt;Time per token&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gen4 NVMe SSD&lt;/td&gt;
&lt;td&gt;~7 GB/s&lt;/td&gt;
&lt;td&gt;~20 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gen3 NVMe SSD&lt;/td&gt;
&lt;td&gt;~3.5 GB/s&lt;/td&gt;
&lt;td&gt;~40 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SATA SSD&lt;/td&gt;
&lt;td&gt;~0.5 GB/s&lt;/td&gt;
&lt;td&gt;~280 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spinning HDD&lt;/td&gt;
&lt;td&gt;~0.15 GB/s&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Your disk is your inference engine.&lt;/strong&gt; The GPU is barely working — it's sitting idle waiting for data. This is why AirLLM users report their fans screaming and their laptop becoming unusable: the bottleneck is I/O and CPU, not compute.&lt;/p&gt;

&lt;p&gt;Two consequences that fall right out of this formula:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;compression='4bit'&lt;/code&gt;.&lt;/strong&gt; It shrinks the bytes you have to read by ~4x. The README advertises up to 3x speedup, and now you know exactly why — this isn't about faster math, it's about moving less data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAM is secretly your best upgrade.&lt;/strong&gt; If your system RAM can hold a big chunk of the model, the OS page cache serves layers from memory instead of disk. This is why people with 128GB machines report dramatically better numbers than the raw disk math predicts.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Requirements — read this part before you start
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Disk space is the #1 thing that will bite you.&lt;/strong&gt; AirLLM downloads the model, &lt;em&gt;then&lt;/em&gt; decomposes it into per-layer shards. For a while, you have &lt;strong&gt;both copies on disk&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For a 70B FP16 model, budget:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~140GB (original download)
+ ~140GB (layer shards)
= ~280GB free space
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The single most common error in the repo's FAQ — &lt;code&gt;safetensors_rust.SafetensorError: Error while deserializing header: MetadataIncompleteBuffer&lt;/code&gt; — is, per the maintainers, almost always just &lt;strong&gt;you ran out of disk&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You'll also want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An NVMe SSD (not SATA, definitely not HDD)&lt;/li&gt;
&lt;li&gt;As much system RAM as you can get&lt;/li&gt;
&lt;li&gt;A Hugging Face token for gated models like Llama&lt;/li&gt;
&lt;li&gt;Patience. Real, genuine patience.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1. Install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;airllm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the 4-bit compression speedup (recommended — see the math above):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-U&lt;/span&gt; bitsandbytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Run it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;airllm&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AutoModel&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Qwen/Qwen3-32B&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;compression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;4bit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;# ~3x faster; skip for full precision
&lt;/span&gt;    &lt;span class="n"&gt;delete_original&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;# deletes the original after splitting — saves ~50% disk
&lt;/span&gt;    &lt;span class="n"&gt;profiling_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;# logs per-layer timing so you can see the bottleneck
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;input_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;What is the capital of United States?&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;input_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;input_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;return_tensors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;return_attention_mask&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;truncation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;padding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;# avoids a common tokenizer error
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;generation_output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;input_tokens&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;input_ids&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;max_new_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;use_cache&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;return_dict_in_generate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generation_output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sequences&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole API. Swapping to a 671B model is a one-line change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deepseek-ai/DeepSeek-V3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# 671B, ~12GB VRAM
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Start small.&lt;/strong&gt; Please run an 8B model first to validate your setup before committing 280GB and several hours to a 70B download.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Useful config flags
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compression&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;'4bit'&lt;/code&gt; or &lt;code&gt;'8bit'&lt;/code&gt; block-wise quantization — biggest speed lever&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;delete_original&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deletes the original download after splitting; halves disk usage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;layer_shards_saving_path&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Put shards on a different (faster/bigger) drive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;profiling_mode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Logs time consumption per layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hf_token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;For gated models (Llama, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prefetching&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Overlaps loading and compute (~10% gain); on by default&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Common errors, decoded
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Error&lt;/th&gt;
&lt;th&gt;Actual cause&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MetadataIncompleteBuffer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Out of disk space. It's basically always this.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;401 Client Error... Repo is gated&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pass &lt;code&gt;hf_token='...'&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Asking to pad but the tokenizer does not have a padding token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;padding=False&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ValueError: max() arg is an empty sequence&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;AutoModel&lt;/code&gt;, not a specific model class&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  macOS
&lt;/h3&gt;

&lt;p&gt;Works on Apple Silicon only. Install &lt;a href="https://github.com/ml-explore/mlx" rel="noopener noreferrer"&gt;mlx&lt;/a&gt; plus torch, and make sure you're on native (not Rosetta) Python. Same code otherwise.&lt;/p&gt;




&lt;h2&gt;
  
  
  Is it worth checking out?
&lt;/h2&gt;

&lt;p&gt;Depends entirely on which of these you are.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skip it if you want to chat with a big model
&lt;/h3&gt;

&lt;p&gt;This is the fantasy that draws people in, and it just doesn't work. Interactive chat needs ~20+ tokens/sec. AirLLM gives you seconds-to-minutes &lt;em&gt;per token&lt;/em&gt;. You will not be having a conversation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skip it if you're doing serious volume
&lt;/h3&gt;

&lt;p&gt;Every token reads tens of GB off your SSD. Consumer NVMe drives are rated for a finite number of terabytes written; hammering one with continuous full-model reads and shard rewrites is not what it was designed for. Also, your machine will be effectively unusable while it runs.&lt;/p&gt;

&lt;h3&gt;
  
  
  It's genuinely great for offline batch work
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;This is the real use case, and it's underrated.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The killer insight: the expensive part is &lt;em&gt;loading a layer&lt;/em&gt;, not &lt;em&gt;using it&lt;/em&gt;. So if you load Layer 1 and run &lt;strong&gt;50 prompts through it&lt;/strong&gt; before moving on, you amortize that cost 50 ways.&lt;/p&gt;

&lt;p&gt;One reported benchmark: &lt;strong&gt;35 s/token for a single prompt vs 5.3 s/token when batching 50&lt;/strong&gt; — a 6.6x improvement for free.&lt;/p&gt;

&lt;p&gt;So if you have 10,000 documents to classify overnight and no GPU budget, AirLLM is a legitimately reasonable tool. Latency doesn't matter when nobody's waiting.&lt;/p&gt;

&lt;h3&gt;
  
  
  It's great if you need &lt;em&gt;full precision&lt;/em&gt;, specifically
&lt;/h3&gt;

&lt;p&gt;Research on quantization effects, numerical reproducibility, evaluating a model as-published — cases where a 4-bit approximation defeats the point. AirLLM is close to the only way to do this on hardware you already own.&lt;/p&gt;




&lt;h2&gt;
  
  
  The verdict
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The claim is real.&lt;/strong&gt; 70B on 4GB VRAM, full precision, no tricks in the "lying" sense. The engineering is clever and the MoE expert-streaming work is legitimately impressive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The framing is the problem.&lt;/strong&gt; "Run 70B on a 4GB GPU" implies you get 70B-quality answers on cheap hardware. What you actually get is 70B-quality answers &lt;em&gt;eventually&lt;/em&gt; — at a pace measured in minutes per token, with your SSD as the engine and your GPU mostly idle.&lt;/p&gt;

&lt;p&gt;AirLLM didn't remove the cost of running a huge model. It &lt;strong&gt;moved&lt;/strong&gt; it — out of VRAM, into time and disk I/O. Whether that's a good trade depends entirely on whether you have more time than money.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/lyogavin/airllm" rel="noopener noreferrer"&gt;AirLLM on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Have you actually run this on real hardware? I'd love to hear your tokens/sec and your disk setup in the comments — the community numbers vary wildly and more data points would help.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>python</category>
      <category>llm</category>
    </item>
    <item>
      <title>OpenWorker: Andrew Ng's Local-First AI Coworker, Explained for Developers</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Wed, 29 Jul 2026 12:10:20 +0000</pubDate>
      <link>https://dev.to/arshtechpro/openworker-andrew-ngs-local-first-ai-coworker-explained-for-developers-3hc9</link>
      <guid>https://dev.to/arshtechpro/openworker-andrew-ngs-local-first-ai-coworker-explained-for-developers-3hc9</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/andrewyng/openworker" rel="noopener noreferrer"&gt;OpenWorker&lt;/a&gt; shipped in late July 2026. It is MIT-licensed, runs on your own machine, and takes your API key instead of selling you inference.&lt;/p&gt;

&lt;p&gt;The pitch is narrow and worth repeating exactly: it is an agent that hands you &lt;strong&gt;finished work&lt;/strong&gt;, not a chat transcript. A drafted document on disk. A Slack reply with the real numbers in it. A calendar that has actually been rearranged.&lt;/p&gt;

&lt;p&gt;There are a lot of desktop agents right now. This post is about what makes this one structurally different, what state it is actually in, and how to get it running.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it is in one paragraph
&lt;/h2&gt;

&lt;p&gt;OpenWorker is a desktop app: a Tauri shell around a React UI, sitting on top of a local Python agent server. You give it an outcome ("prepare a customer brief from these three files and the Jira tickets"). It decomposes that into steps, reaches into your files, terminal, and connected SaaS apps, and produces an artifact. Before anything consequential happens - sending a message, running a shell command, writing to your calendar - it stops and asks.&lt;/p&gt;

&lt;p&gt;The engine is built on &lt;a href="https://github.com/andrewyng/aisuite" rel="noopener noreferrer"&gt;aisuite&lt;/a&gt;, Ng's provider-agnostic LLM library. That matters more than it sounds like: OpenWorker is explicitly positioned as a reference implementation of what you can build on aisuite, so the codebase doubles as a worked example if you are building your own harness.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four things that actually distinguish it
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. There is no OpenWorker inference service
&lt;/h3&gt;

&lt;p&gt;You paste a key, or you point it at Ollama and use none at all. The curated list covers OpenAI, Anthropic, Google, plus OpenAI-compatible vendors like DeepSeek, GLM, Kimi, Qwen, MiniMax, Mistral, and Grok, plus open-weight models through Together and Fireworks. Roughly thirty models are marked as verified for tool-calling work; you can point it at any other model string and accept the risk yourself.&lt;/p&gt;

&lt;p&gt;The practical consequence: your cost is your provider bill, and swapping models is a dropdown, not a migration.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The permission model is typed, not a confirmation dialog
&lt;/h3&gt;

&lt;p&gt;This is the part I would actually read the source for. Most agent projects treat approvals as UI polish. OpenWorker classifies every tool call into one of four risk classes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Risk class&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No side effects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;write_local&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mutates the workspace, path-scoped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;exec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Runs commands&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;external&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Side effects that leave your machine&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Five permission modes then decide what happens to each class. &lt;code&gt;discuss&lt;/code&gt; and &lt;code&gt;plan&lt;/code&gt; are read-only. &lt;code&gt;interactive&lt;/code&gt; is the default and asks before writes, commands, and external actions. &lt;code&gt;auto&lt;/code&gt; allows everything but stays path-scoped. &lt;code&gt;custom&lt;/code&gt; auto-approves a list of tools you name.&lt;/p&gt;

&lt;p&gt;Two design choices stand out. First, unattended runs do not raise the autonomy ceiling - they change &lt;em&gt;where the human is reached&lt;/em&gt;. Prompts that would have appeared inline get routed to an inbox and the session suspends until you answer. The agent does not quietly gain permissions because you walked away. Second, standing "always allow" rules are restricted to the &lt;code&gt;external&lt;/code&gt; class only. Shell commands ask every single time, on purpose.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. It has an explicit prompt-injection posture
&lt;/h3&gt;

&lt;p&gt;The shipped operator persona instructs the model to treat content coming from tools, logs, web pages, files, and incoming messages as untrusted &lt;em&gt;data&lt;/em&gt;, not as instructions. That is written into the persona the app ships with, rather than being left as an exercise for the user. For an agent that reads your Slack and your inbox, this is not optional, and it is good to see it stated rather than assumed.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Local-first is close to literal
&lt;/h3&gt;

&lt;p&gt;Conversations, connector tokens, and model keys live in a local secret store. Model calls go straight from your machine to whichever provider you configured. The only cloud component is a small optional broker that handles OAuth handshakes for one-click connectors, and tokens are handed to your machine rather than stored server-side. You can skip sign-in entirely and wire up connectors with manually created credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it can do out of the box
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real deliverables&lt;/strong&gt; - documents, spreadsheets, reports, web pages, written to disk as files you can open.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;25+ connectors&lt;/strong&gt; - GitHub, Slack, Jira, Notion, Linear, HubSpot, Outlook, monday.com, Gmail, Google Calendar, and more, plus your terminal and local filesystem. Anything reachable over MCP plugs in too, with per-tool control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slack as an entry point&lt;/strong&gt; - mention &lt;code&gt;@OpenWorker&lt;/code&gt; in a channel, a session opens on your desktop, the work runs with your local tools, and the answer comes back as a thread reply.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduled automations&lt;/strong&gt; - morning briefs, weekly reports, a standing watch on a channel. Runs land in the app with full transcripts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Option A: install the app (five minutes)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Download from the repo's release links: &lt;a href="https://download.openworker.com/mac" rel="noopener noreferrer"&gt;macOS (Apple Silicon)&lt;/a&gt;, macOS 12+, signed and notarized, auto-updating. Or &lt;a href="https://download.openworker.com/windows" rel="noopener noreferrer"&gt;Windows 10/11 x64&lt;/a&gt; - note that Windows builds are not code-signed yet, so SmartScreen will warn you.&lt;/li&gt;
&lt;li&gt;Open the app and add a model key, or point it at a running Ollama instance.&lt;/li&gt;
&lt;li&gt;Give it a real task. Not "hello" - something with a file attached and a defined output.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Leave it in &lt;code&gt;interactive&lt;/code&gt; mode for the first few sessions. Watching what it asks permission for is the fastest way to build an accurate mental model of what it is doing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option B: run from source
&lt;/h3&gt;

&lt;p&gt;Prerequisites: Python 3.10+, Node 20+, and the Rust toolchain via &lt;a href="https://rustup.rs/" rel="noopener noreferrer"&gt;rustup&lt;/a&gt; if you want the desktop shell rather than the browser UI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/andrewyng/openworker
&lt;span class="nb"&gt;cd &lt;/span&gt;openworker

&lt;span class="c"&gt;# 1. One-time bootstrap - creates the Python venv at .venv&lt;/span&gt;
&lt;span class="c"&gt;#    On Windows, run this from Git Bash or WSL&lt;/span&gt;
bash packaging/setup_dev_env.sh

&lt;span class="c"&gt;# 2. Start the local agent server&lt;/span&gt;
.venv/bin/openworker-server &lt;span class="nt"&gt;--cwd&lt;/span&gt; ~/some/project &lt;span class="nt"&gt;--port&lt;/span&gt; 8765
&lt;span class="c"&gt;# Windows: .venv\Scripts\openworker-server.exe&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in a second terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;surfaces/gui
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev        &lt;span class="c"&gt;# browser UI on the Vite dev port&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the full desktop app instead of the browser UI, replace that last step with &lt;code&gt;npm run tauri dev&lt;/code&gt; from &lt;code&gt;surfaces/gui/&lt;/code&gt;. The Tauri shell launches the window and supervises the Python server itself, so you can skip step 2 in that case.&lt;/p&gt;

&lt;p&gt;The server is FastAPI on uvicorn, bound to &lt;code&gt;127.0.0.1:8765&lt;/code&gt; by default.&lt;/p&gt;

&lt;p&gt;Running the tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;.venv/bin/pytest              &lt;span class="c"&gt;# backend&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;surfaces/gui
npm &lt;span class="nb"&gt;test&lt;/span&gt;                      &lt;span class="c"&gt;# GUI unit tests&lt;/span&gt;
npm run e2e                   &lt;span class="c"&gt;# hermetic end-to-end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;--cwd&lt;/code&gt; flag on the server. That is the workspace root the agent's path-scoped file access is anchored to. Point it at a scratch directory for your first runs, not at your home folder.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where to look in the code
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Directory&lt;/th&gt;
&lt;th&gt;Contents&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;coworker/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Python backend - agent engine, model providers, connectors, MCP client, memory, automations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;surfaces/gui/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;React UI plus the Tauri shell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stt/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rust speech-to-text sidecar for voice input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;packaging/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;macOS DMG and Windows installer builds, auto-update manifest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docs/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Design specs and decision logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tests/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Backend test suite&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you are here to learn rather than to use, &lt;code&gt;docs/&lt;/code&gt; and the permission engine in &lt;code&gt;coworker/&lt;/code&gt; are the highest-value reads. The design logs explain choices rather than just documenting APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it worth looking into?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Yes, if any of these describe you:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You want a desktop agent that touches real work systems but you are unwilling to hand a vendor your Slack tokens and your filesystem.&lt;/li&gt;
&lt;li&gt;You are building an agent harness and want a substantial, production-shaped reference for permission gating, connector management, and MCP integration. Roughly 32k lines of Python with 78 backend test modules is a real codebase to learn from, not a demo.&lt;/li&gt;
&lt;li&gt;You have compliance constraints that rule out hosted agents. Local-first plus BYO-key plus MIT is a rare combination.&lt;/li&gt;
&lt;li&gt;You want to run agents against fully local models. The Ollama path needs no key at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Hold off if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need stability. This is v0.1.x, self-labeled open beta, with a handful of contributors and a repo that is days old. It auto-updates, which is convenient and also means the thing under you moves.&lt;/li&gt;
&lt;li&gt;You expect a coding agent. It works with your terminal and files, but the framing is knowledge work - briefs, reports, inbox triage, calendar - not writing your service.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The honest summary
&lt;/h2&gt;

&lt;p&gt;The interesting claim here is not "agent that does tasks." Everyone is shipping that. The interesting claim is that the boring infrastructure - who is allowed to do what, where secrets live, what counts as an instruction versus data - has been designed as a typed system rather than bolted on. That is the part worth reading even if you never use the app.&lt;/p&gt;

&lt;p&gt;It is early. Expect rough edges, expect the Windows signing situation to resolve, expect the connector list to shift. But if you have been waiting for a credible open, local, model-agnostic option in this category, this is the first one that looks like it was built by people who thought about the failure modes first.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/andrewyng/openworker" rel="noopener noreferrer"&gt;github.com/andrewyng/openworker&lt;/a&gt;&lt;br&gt;
Site: &lt;a href="https://openworker.com" rel="noopener noreferrer"&gt;openworker.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>ai</category>
      <category>python</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Openship: Your Deployment Tool Shouldn't Compete With Your App for RAM</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Sun, 26 Jul 2026 16:59:30 +0000</pubDate>
      <link>https://dev.to/arshtechpro/openship-your-deployment-tool-shouldnt-compete-with-your-app-for-ram-5a4k</link>
      <guid>https://dev.to/arshtechpro/openship-your-deployment-tool-shouldnt-compete-with-your-app-for-ram-5a4k</guid>
      <description>&lt;p&gt;The self-hosted PaaS drawer is not empty. Coolify, Dokploy, CapRover, Dokku, Kamal. If you have wanted a Heroku you actually own, someone has already built you three of them.&lt;/p&gt;

&lt;p&gt;So when &lt;a href="https://github.com/oblien/openship" rel="noopener noreferrer"&gt;Openship&lt;/a&gt; showed up in July 2026 and hit several thousand GitHub stars in about a week, the reasonable reaction was skepticism. Another dashboard for &lt;code&gt;docker compose up&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Having read through it, there is one architectural decision here that is actually different, and it is the kind of difference you feel on a 4 GB VPS. This post covers what that decision is, how to get the thing running three different ways, and an honest read on whether you should put anything you care about on it yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one idea that matters
&lt;/h2&gt;

&lt;p&gt;Every self-hosted PaaS in that drawer runs its entire control plane on your server. The dashboard, the database backing it, the build system, the CI runner, the webhook listener, and your actual apps all share one box and one set of resources.&lt;/p&gt;

&lt;p&gt;You notice this the first time a Next.js build on a 4 GB VPS starves the running production container and takes your site down. Your deployment tool became a reliability risk for the thing it deploys.&lt;/p&gt;

&lt;p&gt;Openship inverts it. The control plane runs on your machine, or on a separate box, and drives your servers over SSH. Builds happen locally. What gets shipped to production is a finished container image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You                              Your server
┌──────────────────────┐         ┌──────────────────────┐
│  Desktop / Web UI    │   SSH   │                      │
│  Build pipeline      │────────→│  Your apps. Only.     │
│  CLI / API           │         │                      │
└──────────────────────┘         └──────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The practical consequences are worth spelling out:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your production server does not need build-sized resources.&lt;/strong&gt; It runs containers. That is it. A small VPS goes further.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your deployment tool has no public attack surface.&lt;/strong&gt; In the desktop configuration, Openship exposes no port and has no public URL. The only thing reachable from the internet is an app you deliberately gave a domain to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Builds use your laptop's CPU&lt;/strong&gt;, which is almost certainly faster than your VPS's shared vCPUs.&lt;/p&gt;

&lt;p&gt;The tradeoff is honest and the maintainers state it: the dashboard is only up while your machine is. No teammate access, no phone access, and push-to-deploy webhooks need a stable public endpoint that a laptop is not. If you need any of those, you run the control plane on a server instead, at which point you have re-created the normal arrangement, just deliberately rather than by default.&lt;/p&gt;

&lt;p&gt;Building on the server is also available as a config flag. The README's phrasing is that it is a config flag, not a religion.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is actually in the box
&lt;/h2&gt;

&lt;p&gt;Openship bundles more than deployment:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;What you get&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CI/CD&lt;/td&gt;
&lt;td&gt;Push-to-deploy, preview environments, staging and prod flows, rollbacks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stacks&lt;/td&gt;
&lt;td&gt;Node, Python, Go, Rust, PHP, Ruby, Java, .NET, Docker, monorepos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data&lt;/td&gt;
&lt;td&gt;Postgres, MySQL, MongoDB, Redis, workers, WebSockets, storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Domains and SSL&lt;/td&gt;
&lt;td&gt;Let's Encrypt automation via OpenResty and certbot, wildcards, auto-renewal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CDN&lt;/td&gt;
&lt;td&gt;Edge caching, HTTP/3, Brotli, instant purge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mail&lt;/td&gt;
&lt;td&gt;Built-in SMTP with DKIM, SPF, DMARC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backups&lt;/td&gt;
&lt;td&gt;Scheduled database and volume backups, one-click restore, exportable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automation&lt;/td&gt;
&lt;td&gt;REST API, and MCP so AI agents can drive it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The built-in mail server is the unusual one. Running your own SMTP with correct DKIM, SPF, and DMARC is genuinely annoying, and most self-hosted PaaS tools tell you to go use SES or Mailgun. Whether it survives contact with real deliverability is a separate question, but it is a real differentiator on the feature list.&lt;/p&gt;

&lt;p&gt;Under the hood it is a TypeScript monorepo: an Electron desktop app, a Next.js dashboard (React 19, Tailwind 4, xterm.js for terminals), and a CLI built on Commander and Clack. A single &lt;code&gt;OPENSHIP_TARGET&lt;/code&gt; environment flag decides whether an instance behaves as &lt;code&gt;desktop&lt;/code&gt;, &lt;code&gt;selfhosted&lt;/code&gt;, or &lt;code&gt;cloud&lt;/code&gt;, which is why the same codebase powers the local app and the managed SaaS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick your setup first
&lt;/h2&gt;

&lt;p&gt;The install path depends entirely on who is using it and whether it needs to be up when your machine is off.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;th&gt;Use when&lt;/th&gt;
&lt;th&gt;Where the control plane runs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Desktop app&lt;/td&gt;
&lt;td&gt;Just you, private&lt;/td&gt;
&lt;td&gt;Your machine, driving servers over SSH&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-hosted on a server&lt;/td&gt;
&lt;td&gt;Team, always-on, CI, push-to-deploy&lt;/td&gt;
&lt;td&gt;A Linux box at a public URL, invite-only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Openship Cloud&lt;/td&gt;
&lt;td&gt;You want zero ops&lt;/td&gt;
&lt;td&gt;Managed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Decide before you install. Switching later is possible but it is not a no-op.&lt;/p&gt;

&lt;h2&gt;
  
  
  Path 1: Desktop app (solo, and the recommended one)
&lt;/h2&gt;

&lt;p&gt;Download the build for your OS from &lt;a href="https://openship.io" rel="noopener noreferrer"&gt;openship.io&lt;/a&gt;, or let the CLI fetch it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openship &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Linux the download is an AppImage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x Openship.AppImage &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./Openship.AppImage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On first launch it asks what you want to manage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;This Machine&lt;/strong&gt; — manage the local box.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Another Server&lt;/strong&gt; — add a remote server over SSH with host, user, and key. There is a Test Connection button. Use it before you go further, because SSH problems surface much more clearly here than mid-deploy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the configuration where Openship's architecture pays off most. Nothing about the platform is exposed. It is worth starting here even if you eventually want the server setup, because you learn the model without also debugging a public deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Path 2: CLI on a server (teams and CI)
&lt;/h2&gt;

&lt;p&gt;Point your domain's DNS A record at the server first. Doing this after the fact means waiting on propagation while the certificate step fails repeatedly.&lt;/p&gt;

&lt;p&gt;Install the CLI, which bundles the API and dashboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; openship
&lt;span class="c"&gt;# or&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://get.openship.io | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then bring it up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openship up &lt;span class="nt"&gt;--public-url&lt;/span&gt; https://ops.example.com &lt;span class="nt"&gt;--managed-edge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What each part does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;openship up&lt;/code&gt; installs Openship as a background service that starts on boot and auto-restarts, then runs a setup wizard that creates the first admin and attaches your domain.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--public-url&lt;/code&gt; makes the dashboard reachable at your domain. Login is required and public signup is disabled; everyone else joins by invite.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--managed-edge&lt;/code&gt; installs OpenResty and provisions a free Let's Encrypt certificate, routing your domain to the dashboard. &lt;strong&gt;Omit this if you already run your own reverse proxy&lt;/strong&gt;, or you will have two things fighting over ports 80 and 443.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For an attached, non-service run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openship up &lt;span class="nt"&gt;--foreground&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A nice touch: once it is up, Openship registers itself as an app in its own dashboard. You manage its domain, tail its logs, and see it listed as Live alongside everything else. Self-hosting the control plane as a first-class app rather than a special case is the sort of detail that suggests someone actually ran this in anger.&lt;/p&gt;

&lt;p&gt;If you lose the admin password:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openship reset-admin-password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works locally on the box without signing in, using the internal token. Which also means: anyone with shell access to that server owns your Openship instance. Treat SSH access accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Path 3: Docker Compose (and why the maintainers steer you away)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/oblien/openship.git &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;openship
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works, but the docs explicitly label it as not the recommended path, and the reason is worth understanding rather than ignoring.&lt;/p&gt;

&lt;p&gt;The compose stack gives the control-plane container access to the &lt;strong&gt;host Docker daemon&lt;/strong&gt;. That is effectively host-level privilege. A container that can talk to the host's Docker socket can start a privileged container, mount the host filesystem, and escape. It is a well-understood escalation path, not a theoretical one.&lt;/p&gt;

&lt;p&gt;There are legitimate reasons to want a containerized control plane. Just know that you are trading the security posture that was the point of the architecture in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying something
&lt;/h2&gt;

&lt;p&gt;Once the control plane is running, the app workflow is short:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;my-project
openship init          &lt;span class="c"&gt;# links this directory to a project (.openship/project.json)&lt;/span&gt;
openship deploy        &lt;span class="c"&gt;# triggers a deployment&lt;/span&gt;
openship logs &amp;lt;deploymentId&amp;gt; &lt;span class="nt"&gt;-f&lt;/span&gt;    &lt;span class="c"&gt;# stream build and runtime logs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Openship detects the stack, builds it, wires up config, and ships the container. No YAML pipeline, no Dockerfile required unless you want one. Existing Docker Compose files can be deployed as-is.&lt;/p&gt;

&lt;p&gt;The auto-detection claim is the one I would test against your actual repo before believing it. Framework detection is easy for a vanilla Next.js app and gets progressively less magical as your monorepo gets weirder.&lt;/p&gt;

&lt;h2&gt;
  
  
  CLI cheat sheet
&lt;/h2&gt;

&lt;p&gt;Instance management:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openship up [--foreground]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Start as a service, or attached&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openship stop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stop the service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openship status [--json]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Running state, ports, API health&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openship open&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Open the dashboard in a browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openship update&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Update CLI and bundled server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openship doctor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Diagnose config, context, runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Deploy and inspect:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openship init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Link current directory to a project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openship deploy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Trigger a deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openship logs &amp;lt;id&amp;gt; [-f] [--tail N]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;View or stream deployment logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openship deployment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List and manage deployments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openship project&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List and manage projects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openship service&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Services within a stack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openship domain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A project's domains&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Infrastructure and auth:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openship server&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Manage self-hosted SSH servers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openship system&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read and update instance settings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openship mail&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mail server setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openship backup&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Backup policies and schedules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;openship login&lt;/code&gt; / &lt;code&gt;logout&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Authenticate with a Personal Access Token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openship context&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Switch which instance the CLI talks to&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openship api &amp;lt;method&amp;gt; &amp;lt;path&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Authenticated request to any API route&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;--json&lt;/code&gt; works on most read commands, which makes this scriptable in CI. And &lt;code&gt;openship api&lt;/code&gt; following the &lt;code&gt;gh api&lt;/code&gt; pattern is a good call, since it means you are never blocked waiting for a CLI command to be written for an endpoint that already exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The MCP angle, with a caveat
&lt;/h2&gt;

&lt;p&gt;Openship ships MCP support so AI agents can drive deployments. The pitch is roughly: add the MCP server, and your agent can do the work.&lt;/p&gt;

&lt;p&gt;Worth pausing on. You are handing an agent deploy permissions, secrets access, and production rollback control. Standard MCP hygiene applies and applies harder here: scope the tools you expose, point agents at staging rather than production, use a Personal Access Token with the narrowest permissions that work, and keep an audit trail of what the agent did.&lt;/p&gt;

&lt;p&gt;An agent that can deploy can also deploy the wrong thing at 3am while nobody is watching.&lt;/p&gt;

&lt;h2&gt;
  
  
  How ready is this, actually
&lt;/h2&gt;

&lt;p&gt;Being straight with you, since dev.to posts that skip this part are advertising.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Known rough edges.&lt;/strong&gt; As of July 2026 there are open reports of breakage in the CLI installer and in the self-hosted SSH deploy path, including deploys failing immediately after a successful Docker build. The issue queue is active, which is a good sign for responsiveness and a clear sign it is still early.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;License.&lt;/strong&gt; This one matters and is easy to get wrong. Openship launched under AGPL-3.0 plus the Commons Clause, which is source-available rather than open source and prohibited offering it as a hosted service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Roadmap gaps.&lt;/strong&gt; Multi-node clusters, load balancing UI, private networking, and visual CI/CD pipelines are all listed as coming, not shipped. If clustering is a requirement, this is not your tool yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you use it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Try it now if&lt;/strong&gt; you are a solo developer or small team, you have side projects or non-critical services, and the local-build architecture appeals to you. The desktop app is genuinely low-risk to experiment with, since it does not expose anything and does not need a server to evaluate.&lt;/p&gt;

&lt;p&gt;The architecture is the real contribution here. Keeping build infrastructure off the production box is correct, and it is a bit odd that it took this long for a self-hosted PaaS to treat it as the default rather than an option. Even if Openship itself does not win the category, that idea should propagate.&lt;/p&gt;

&lt;p&gt;Links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/oblien/openship" rel="noopener noreferrer"&gt;github.com/oblien/openship&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you run it on a real VPS, especially the SSH deploy path, post what broke in the comments. Early projects get better fastest when the failure reports are specific.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>devops</category>
      <category>docker</category>
    </item>
    <item>
      <title>Unlimited-OCR: Parsing a 40-Page PDF in One Pass Without Your GPU Melting</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Fri, 24 Jul 2026 17:19:34 +0000</pubDate>
      <link>https://dev.to/arshtechpro/unlimited-ocr-parsing-a-40-page-pdf-in-one-pass-without-your-gpu-melting-4mc4</link>
      <guid>https://dev.to/arshtechpro/unlimited-ocr-parsing-a-40-page-pdf-in-one-pass-without-your-gpu-melting-4mc4</guid>
      <description>&lt;p&gt;If you have ever built a document-parsing pipeline, you know the ritual. Split the PDF into pages. Run each page through the OCR model. Stitch the outputs back together. Then write a pile of glue code to fix the tables that got cut in half at a page boundary, and the heading that lost its section, and the footnote that ended up orphaned three pages away from its reference.&lt;/p&gt;

&lt;p&gt;Baidu's &lt;a href="https://github.com/baidu/Unlimited-OCR" rel="noopener noreferrer"&gt;Unlimited-OCR&lt;/a&gt; is a bet that you should not have to do any of that. It parses dozens of pages in a single forward pass, and it is MIT licensed.&lt;/p&gt;

&lt;p&gt;This post covers what the model actually does differently, and then three concrete ways to get it running: a quick Transformers script, an SGLang server for real throughput, and a Docker image if you would rather not touch a Python environment at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem it solves
&lt;/h2&gt;

&lt;p&gt;Standard transformer decoding has a cost that most OCR benchmarks quietly hide: the KV cache grows with every token you generate.&lt;/p&gt;

&lt;p&gt;The KV cache is the model's short-term memory. Every token the model produces gets appended to it so future tokens can attend backwards. For a chatbot writing three paragraphs, this is fine. For an OCR model transcribing a 40-page technical manual into 30,000 tokens of Markdown, it is not. Memory climbs, attention cost climbs with it, and generation gets slower the longer it runs. Somewhere around page eight your throughput graph stops looking like a line and starts looking like a cliff.&lt;/p&gt;

&lt;p&gt;The industry workaround has been chunking. Process one page, dump the cache, process the next page, and accept that the model has no idea what came before.&lt;/p&gt;

&lt;p&gt;Unlimited-OCR attacks the cache growth directly. The team replaced every attention layer in the decoder with something they call &lt;strong&gt;Reference Sliding Window Attention (R-SWA)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The intuition is close to how a human copy-typist works. You do not hold every word you have already typed in your head. You hold the last sentence or two, and you keep glancing back at the source document. R-SWA does the same thing: the decoder keeps a fixed-size window of recently generated tokens, but it retains permanent access to the original image tokens. The KV cache is implemented as a queue with a fixed capacity, so when a new token arrives, the oldest one in the window gets evicted.&lt;/p&gt;

&lt;p&gt;The result is a cache size that is constant rather than growing. Memory and per-token latency stay flat whether you are on token 500 or token 30,000.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you actually get
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;3B total parameters, 500M activated.&lt;/strong&gt; It is a Mixture-of-Experts model, so the compute cost at inference is closer to a 500M model than a 3B one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;32K max output length&lt;/strong&gt;, which is what makes multi-page single-pass parsing viable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;93.23 on OmniDocBench v1.5&lt;/strong&gt;, about 6.2 points above the DeepSeek-OCR baseline it was continue-trained from. Worth noting because efficiency work usually costs you accuracy; here it did not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MIT license.&lt;/strong&gt; Commercial use, no asterisks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture is DeepSeek-OCR's DeepEncoder (SAM-ViT-B plus CLIP-L) feeding an MoE decoder. The encoder's aggressive visual token compression is what keeps the image side of the cache small enough for the whole thing to work.&lt;/p&gt;

&lt;p&gt;One thing the paper flags that is easy to miss: R-SWA is not OCR-specific. It is a general-purpose attention mechanism for any long-horizon transcription-shaped task, ASR included.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you start
&lt;/h2&gt;

&lt;p&gt;You need an NVIDIA GPU. There is no CPU or Apple Silicon path in the official repo.&lt;/p&gt;

&lt;p&gt;The weights are bfloat16, so roughly 6 GB just to load the model, before activations and image tokens. A 12 GB card is a sane floor for single-image work; for multi-page runs at 32K context you will want more headroom. The maintainers tested on Python 3.12.3 with CUDA 12.9.&lt;/p&gt;

&lt;p&gt;If you just want to see output before committing to any of this, there is a &lt;a href="https://huggingface.co/spaces/baidu/Unlimited-OCR" rel="noopener noreferrer"&gt;Hugging Face Space&lt;/a&gt; you can drop a file into.&lt;/p&gt;

&lt;h2&gt;
  
  
  Path 1: Transformers (start here)
&lt;/h2&gt;

&lt;p&gt;This is the fastest way to a working result. Set up the environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate

pip &lt;span class="nb"&gt;install &lt;/span&gt;&lt;span class="nv"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;2.10.0 &lt;span class="nv"&gt;torchvision&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;0.25.0 &lt;span class="nv"&gt;transformers&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;4.57.1
pip &lt;span class="nb"&gt;install &lt;/span&gt;&lt;span class="nv"&gt;Pillow&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;12.1.1 &lt;span class="nv"&gt;matplotlib&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;3.10.8 &lt;span class="nv"&gt;einops&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;0.8.2
pip &lt;span class="nb"&gt;install &lt;/span&gt;&lt;span class="nv"&gt;addict&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;2.4.0 &lt;span class="nv"&gt;easydict&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;1.13 &lt;span class="nv"&gt;pymupdf&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;1.27.2.2 &lt;span class="nv"&gt;psutil&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;7.2.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pin these versions. The model ships custom modeling code via &lt;code&gt;trust_remote_code&lt;/code&gt;, and that code is written against these specific releases. Version drift here produces confusing import errors rather than clean failures.&lt;/p&gt;

&lt;p&gt;Now a single image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AutoModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AutoTokenizer&lt;/span&gt;

&lt;span class="n"&gt;model_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;baidu/Unlimited-OCR&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="n"&gt;tokenizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoTokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trust_remote_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;trust_remote_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;use_safetensors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;torch_dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bfloat16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;infer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;image&amp;gt;document parsing.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;image_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your_image.jpg&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;output_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your/output/dir&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;base_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;crop_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# gundam mode
&lt;/span&gt;    &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32768&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;no_repeat_ngram_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ngram_window&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;save_results&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details in there matter more than they look:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt must literally start with &lt;code&gt;&amp;lt;image&amp;gt;&lt;/code&gt;.&lt;/strong&gt; It is not decoration, it is the placeholder token the visual features get spliced into. Drop it and you will get output that looks like the model hallucinating a document it never saw.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;no_repeat_ngram_size&lt;/code&gt; and &lt;code&gt;ngram_window&lt;/code&gt; are load-bearing.&lt;/strong&gt; Long-horizon generation on repetitive layouts, think a table of contents or a price list, can send the model into a loop where it happily emits the same row forever. These parameters block any 35-gram from repeating inside a sliding window. Do not remove them because they look like tuning noise.&lt;/p&gt;

&lt;h3&gt;
  
  
  gundam vs base
&lt;/h3&gt;

&lt;p&gt;Single-image inference gives you two configurations, and the naming is not self-explanatory:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Settings&lt;/th&gt;
&lt;th&gt;Use for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;gundam&lt;/td&gt;
&lt;td&gt;&lt;code&gt;base_size=1024, image_size=640, crop_mode=True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Single images, especially dense ones. Crops the image into tiles and processes each, so small text survives.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;base&lt;/td&gt;
&lt;td&gt;&lt;code&gt;base_size=1024, image_size=1024, crop_mode=False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Whole image at once. Required for multi-page.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Multi-page and PDF paths only support base mode. That is a hard constraint, not a default.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multiple pages
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;infer_multi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;image&amp;gt;Multi page parsing.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;image_files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;page1.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;page2.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;page3.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;output_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your/output/dir&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;image_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32768&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;no_repeat_ngram_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ngram_window&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;save_results&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;ngram_window&lt;/code&gt; jumps from 128 to 1024 here. Across many pages there is legitimately more repeated structure, so the repeat-detection window has to widen to keep up.&lt;/p&gt;

&lt;h3&gt;
  
  
  PDFs
&lt;/h3&gt;

&lt;p&gt;There is no direct PDF entry point. You rasterize first, then feed the images to &lt;code&gt;infer_multi&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;fitz&lt;/span&gt;  &lt;span class="c1"&gt;# PyMuPDF
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pdf_to_images&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dpi&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fitz&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tmp_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdtemp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pdf_ocr_&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fitz&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Matrix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dpi&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dpi&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmp_dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;page_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;04&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_pixmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mat&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;paths&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;infer_multi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;image&amp;gt;Multi page parsing.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;image_files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;pdf_to_images&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your_doc.pdf&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dpi&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;output_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your/output/dir&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;image_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32768&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;no_repeat_ngram_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ngram_window&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;save_results&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;300 DPI is the recommended default. Going lower to save memory costs you small text and table rules, which is usually the exact content you cared about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Path 2: SGLang server (for anything real)
&lt;/h2&gt;

&lt;p&gt;Transformers is fine for evaluating the model. For a service handling concurrent requests, run SGLang and talk to it over an OpenAI-compatible API.&lt;/p&gt;

&lt;p&gt;Set up the environment. Note that SGLang ships as a local wheel in the repo rather than from PyPI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv venv &lt;span class="nt"&gt;--python&lt;/span&gt; 3.12
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate

uv pip &lt;span class="nb"&gt;install &lt;/span&gt;wheel/sglang-0.0.0.dev11416+g92e8bb79e-py3-none-any.whl
uv pip &lt;span class="nb"&gt;install &lt;/span&gt;&lt;span class="nv"&gt;kernels&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;0.11.7
uv pip &lt;span class="nb"&gt;install &lt;/span&gt;&lt;span class="nv"&gt;pymupdf&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;1.27.2.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Small heads-up: the README prose mentions pinning &lt;code&gt;kernels==0.9.0&lt;/code&gt; while the command block right beneath it installs &lt;code&gt;0.11.7&lt;/code&gt;. Follow the command block. If you hit kernel-related errors, that mismatch is the first thing to check against the current repo state.&lt;/p&gt;

&lt;p&gt;Launch the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; sglang.launch_server &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--model&lt;/span&gt; baidu/Unlimited-OCR &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--served-model-name&lt;/span&gt; Unlimited-OCR &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--attention-backend&lt;/span&gt; fa3 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--page-size&lt;/span&gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--mem-fraction-static&lt;/span&gt; 0.8 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--context-length&lt;/span&gt; 32768 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--enable-custom-logit-processor&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--disable-overlap-schedule&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--skip-server-warmup&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--host&lt;/span&gt; 0.0.0.0 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--port&lt;/span&gt; 10000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flags that are not optional:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--enable-custom-logit-processor&lt;/code&gt; — the no-repeat-ngram processor runs as a custom logit processor. Without this flag, requests referencing it fail.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--attention-backend fa3&lt;/code&gt; — FlashAttention 3, which requires Hopper-class hardware. On older GPUs you will need a different backend.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--page-size 1&lt;/code&gt; and &lt;code&gt;--disable-overlap-schedule&lt;/code&gt; — R-SWA's cache eviction does not play nicely with the usual paged-attention and overlapped-scheduling optimizations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then a client. The image goes in as a base64 data URL, standard OpenAI vision format, with a couple of model-specific extras:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sglang.srt.sampling.custom_logit_processor&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;DeepseekOCRNoRepeatNGramLogitProcessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;server_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://127.0.0.1:10000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trust_env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;encode_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splitext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_path&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;mime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image/jpeg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ext&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.jpeg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image_url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image_url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mime&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;;base64,&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image_paths&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image_mode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ngram_window&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nf"&gt;encode_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;image_paths&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unlimited-OCR&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;skip_special_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;images_config&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image_mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;image_mode&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;custom_logit_processor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DeepseekOCRNoRepeatNGramLogitProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_str&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;custom_params&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ngram_size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;window_size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ngram_window&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;server_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/v1/chat/completions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iter_lines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decode_unicode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[DONE]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;choices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;document parsing.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_image.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
         &lt;span class="n"&gt;image_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gundam&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ngram_window&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;temperature: 0&lt;/code&gt; and &lt;code&gt;skip_special_tokens: False&lt;/code&gt; are both deliberate. This is transcription, not generation, so any sampling randomness is pure downside. And the special tokens carry layout structure you want in the output.&lt;/p&gt;

&lt;p&gt;The 1200 second timeout is also not paranoia. Long documents take a while, which is precisely why you want streaming.&lt;/p&gt;

&lt;h3&gt;
  
  
  Batch processing
&lt;/h3&gt;

&lt;p&gt;The repo ships &lt;code&gt;infer.py&lt;/code&gt;, which starts the SGLang server for you and fires concurrent requests at it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# A directory of images&lt;/span&gt;
python infer.py &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--image_dir&lt;/span&gt; ./examples/images &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--output_dir&lt;/span&gt; ./outputs &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--concurrency&lt;/span&gt; 8 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--image_mode&lt;/span&gt; gundam

&lt;span class="c"&gt;# A PDF&lt;/span&gt;
python infer.py &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--pdf&lt;/span&gt; ./examples/document.pdf &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--output_dir&lt;/span&gt; ./outputs &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--concurrency&lt;/span&gt; 8 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--image_mode&lt;/span&gt; gundam
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful extras: &lt;code&gt;--model_dir&lt;/code&gt; accepts a local path or a Hugging Face ID, &lt;code&gt;--gpu&lt;/code&gt; sets &lt;code&gt;CUDA_VISIBLE_DEVICES&lt;/code&gt;, and &lt;code&gt;--server_log&lt;/code&gt; puts server output somewhere you can read it.&lt;/p&gt;

&lt;p&gt;Start concurrency low. Eight is the documented example, but the right number depends on your VRAM and how long your documents are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Path 3: vLLM via Docker
&lt;/h2&gt;

&lt;p&gt;If you want to skip environment management entirely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Default, CUDA 13.0&lt;/span&gt;
docker pull vllm/vllm-openai:unlimited-ocr

&lt;span class="c"&gt;# Hopper GPUs, CUDA 12.9&lt;/span&gt;
docker pull vllm/vllm-openai:unlimited-ocr-cu129
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--gpus&lt;/span&gt; all &lt;span class="nt"&gt;--network&lt;/span&gt; host &lt;span class="nt"&gt;--ipc&lt;/span&gt; host &lt;span class="se"&gt;\&lt;/span&gt;
    vllm/vllm-openai:unlimited-ocr &lt;span class="se"&gt;\&lt;/span&gt;
    baidu/Unlimited-OCR &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--trust-remote-code&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--logits_processors&lt;/span&gt; vllm.model_executor.models.unlimited_ocr:NGramPerReqLogitsProcessor &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--no-enable-prefix-caching&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--mm-processor-cache-gb&lt;/span&gt; 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same story as SGLang regarding the ngram processor, it has to be registered at server start. Prefix caching and the multimodal processor cache are both disabled because R-SWA's fixed-window cache invalidates the assumptions those optimizations make.&lt;/p&gt;

&lt;p&gt;Full details are in the &lt;a href="https://recipes.vllm.ai/baidu/Unlimited-OCR" rel="noopener noreferrer"&gt;official vLLM recipe&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parameter cheat sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Single image&lt;/th&gt;
&lt;th&gt;Multi-page / PDF&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;mode&lt;/td&gt;
&lt;td&gt;gundam or base&lt;/td&gt;
&lt;td&gt;base only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;640 (gundam) / 1024 (base)&lt;/td&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;crop_mode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;True (gundam) / False (base)&lt;/td&gt;
&lt;td&gt;False&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ngram_window&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no_repeat_ngram_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_length&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32768&lt;/td&gt;
&lt;td&gt;32768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;prompt&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;image&amp;gt;document parsing.&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;image&amp;gt;Multi page parsing.&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  When this is the wrong tool
&lt;/h2&gt;

&lt;p&gt;Reach for something else if you need bounding boxes and per-word confidence scores, since this is an end-to-end model producing Markdown, not a detection-plus-recognition pipeline. Same if you are OCRing short receipts or single lines, where a 3B model on a GPU is enormous overkill compared to PaddleOCR or Tesseract. And if you have no NVIDIA GPU, there is no supported path today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The interesting claim here is not the benchmark number. It is that a fixed-size KV cache made the model both faster and more accurate on long documents, when efficiency work almost always costs you quality somewhere.&lt;/p&gt;

&lt;p&gt;If R-SWA generalizes the way the authors suggest, the same trick applies to any task where a model transcribes a long input into a long output while keeping the source in view. Long-form ASR is the obvious next one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/baidu/Unlimited-OCR" rel="noopener noreferrer"&gt;github.com/baidu/Unlimited-OCR&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>opensource</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Kimi Code CLI: A Beginner-Friendly Guide to Moonshot AI's Terminal Coding Agent</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Sun, 19 Jul 2026 04:49:26 +0000</pubDate>
      <link>https://dev.to/arshtechpro/kimi-code-cli-a-beginner-friendly-guide-to-moonshot-ais-terminal-coding-agent-39db</link>
      <guid>https://dev.to/arshtechpro/kimi-code-cli-a-beginner-friendly-guide-to-moonshot-ais-terminal-coding-agent-39db</guid>
      <description>&lt;p&gt;If you have used tools like Claude Code or similar terminal-based AI agents, Kimi Code CLI will feel familiar. It is Moonshot AI's open-source coding agent that lives in your terminal, reads and edits your code, runs shell commands, and works through tasks step by step based on the results it gets back.&lt;/p&gt;

&lt;p&gt;This post explains what Kimi Code CLI is, why it might be useful to you, and walks through installing and using it for the first time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Kimi Code CLI
&lt;/h2&gt;

&lt;p&gt;Kimi Code CLI is an AI coding agent that runs directly in your terminal. Instead of copying code back and forth between a chat window and your editor, you describe a task in plain language, and the agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reads and edits files in your project&lt;/li&gt;
&lt;li&gt;Runs shell commands&lt;/li&gt;
&lt;li&gt;Searches through your codebase&lt;/li&gt;
&lt;li&gt;Fetches web pages when it needs external information&lt;/li&gt;
&lt;li&gt;Decides its next step based on the output it gets, rather than following a fixed script&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It works out of the box with Moonshot AI's Kimi models, and it can also be pointed at other compatible providers like Anthropic, OpenAI, or Google by editing a config file.&lt;/p&gt;

&lt;p&gt;The project is written in TypeScript, MIT licensed, and distributed as a single binary, so you do not need a Node.js setup just to run it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it stands out
&lt;/h2&gt;

&lt;p&gt;A few design choices make it worth trying if you already use a terminal-based AI agent, or are looking for your first one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single-binary install.&lt;/strong&gt; No Node.js, no PATH conflicts, no global package clutter. One script and you are done.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast startup.&lt;/strong&gt; The terminal UI (TUI) loads in milliseconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video input.&lt;/strong&gt; You can drop a screen recording into the chat, and the agent can turn it into working code or a short summary. This is unusual among coding agents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversational MCP setup.&lt;/strong&gt; Model Context Protocol (MCP) servers can be added and authenticated by talking to the CLI with &lt;code&gt;/mcp-config&lt;/code&gt;, instead of hand-editing JSON files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plugin ecosystem.&lt;/strong&gt; You can install skills, MCP servers, and data sources from a marketplace or directly from a GitHub repo, with the trust level of each shown upfront.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subagents.&lt;/strong&gt; Built-in &lt;code&gt;coder&lt;/code&gt;, &lt;code&gt;explore&lt;/code&gt;, and &lt;code&gt;plan&lt;/code&gt; subagents can run in isolated contexts in parallel, keeping your main conversation clean.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lifecycle hooks.&lt;/strong&gt; You can run local commands at key points, for example to block a risky tool call, log decisions, or trigger a desktop notification.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Editor integration via ACP.&lt;/strong&gt; Using the Agent Client Protocol, you can drive a Kimi Code CLI session from Zed, JetBrains IDEs, or any other ACP-compatible client.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installing Kimi Code CLI
&lt;/h2&gt;

&lt;p&gt;You have two main installation paths: the official install script, or npm.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Install script (recommended)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;macOS or Linux&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://code.kimi.com/kimi-code/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Windows (PowerShell)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;irm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://code.kimi.com/kimi-code/install.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows, install &lt;a href="https://gitforwindows.org/" rel="noopener noreferrer"&gt;Git for Windows&lt;/a&gt; first, since Kimi Code CLI uses the bundled Git Bash as its shell environment. If Git Bash is in a non-standard location, set the &lt;code&gt;KIMI_SHELL_PATH&lt;/code&gt; environment variable to the full path of &lt;code&gt;bash.exe&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Homebrew (macOS/Linux)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;kimi-code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script downloads the latest release, verifies its checksum, and adds the &lt;code&gt;kimi&lt;/code&gt; executable to your PATH.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: Install via npm
&lt;/h3&gt;

&lt;p&gt;If you already have Node.js 22.19.0 or later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--version&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @moonshot-ai/kimi-code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or with pnpm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add &lt;span class="nt"&gt;-g&lt;/span&gt; @moonshot-ai/kimi-code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verify the install
&lt;/h3&gt;

&lt;p&gt;Open a new shell session and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kimi &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that prints a version number, you are ready to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  First launch and login
&lt;/h2&gt;

&lt;p&gt;Move into a project directory and start the interactive UI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;your-project
kimi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On first launch, log in from inside the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This opens a platform selector with two options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kimi Code (OAuth)&lt;/strong&gt; — a device-code flow. You open a link on any device, sign in, and enter a code to authorize.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kimi Platform API key&lt;/strong&gt; — paste in a key from &lt;code&gt;platform.kimi.com&lt;/code&gt; or &lt;code&gt;platform.kimi.ai&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To log out later, run &lt;code&gt;/logout&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you would rather use Anthropic, OpenAI, Google, or another compatible provider, edit &lt;code&gt;~/.kimi-code/config.toml&lt;/code&gt; directly and add your API key there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying your first task
&lt;/h2&gt;

&lt;p&gt;Once logged in, just describe what you want in plain English. A good first prompt is to have it explore your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Take a look at this project's directory structure and briefly describe what each directory is for.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kimi Code CLI will call its file-reading and search tools automatically to gather context before answering. Read-only operations run without asking for confirmation. Anything that modifies files or runs shell commands will prompt you to approve it first.&lt;/p&gt;

&lt;p&gt;You can also hand it a concrete coding task:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Add&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;utils&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;converts&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;kebab&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;unit&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent plans the steps, edits the code, runs the tests, and reports back what it did.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running a single instruction without the interactive UI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kimi &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"Take a look at this project's directory structure"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Resuming your previous session
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kimi &lt;span class="nt"&gt;-c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Useful commands to know
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/help&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Opens the command and shortcut panel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/new&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Starts a new session, clearing current context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/sessions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Browse and resume past sessions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Switch the active model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/compact&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Manually compress context to free up tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/fork&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fork the current session, keeping history but continuing separately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/mcp-config&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add and authenticate MCP servers conversationally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/exit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Exit the CLI&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Handy keyboard shortcuts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Shortcut&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Esc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Interrupt streaming output or close a popup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl-C&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Interrupt output; press twice while idle to exit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Shift-Tab&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Toggle Plan mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl-S&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Inject a message mid-stream&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl-O&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Collapse or expand tool output&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Using it inside your editor (ACP)
&lt;/h2&gt;

&lt;p&gt;Kimi Code CLI supports the Agent Client Protocol, which lets editors like Zed and JetBrains drive a session directly.&lt;/p&gt;

&lt;p&gt;For Zed, add this to &lt;code&gt;~/.config/zed/settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"agent_servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Kimi Code CLI"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"custom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kimi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"acp"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open a new conversation in Zed's Agent panel. No extra login is needed since it reuses your existing CLI credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where your data lives
&lt;/h2&gt;

&lt;p&gt;Kimi Code CLI stores config files, session records, logs, and its update cache under &lt;code&gt;~/.kimi-code/&lt;/code&gt; by default. If you want to move that elsewhere, set the &lt;code&gt;KIMI_CODE_HOME&lt;/code&gt; environment variable to a different path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upgrading and uninstalling
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Upgrade&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kimi upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, if you installed via npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @moonshot-ai/kimi-code@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Uninstall&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you installed via the script, just delete the &lt;code&gt;kimi&lt;/code&gt; executable. If you installed via npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm uninstall &lt;span class="nt"&gt;-g&lt;/span&gt; @moonshot-ai/kimi-code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Building it from source
&lt;/h2&gt;

&lt;p&gt;If you want to contribute or run the latest code from &lt;code&gt;main&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/MoonshotAI/kimi-code.git
&lt;span class="nb"&gt;cd &lt;/span&gt;kimi-code
pnpm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm dev:cli    &lt;span class="c"&gt;# run the CLI in dev mode&lt;/span&gt;
pnpm &lt;span class="nb"&gt;test&lt;/span&gt;       &lt;span class="c"&gt;# run tests&lt;/span&gt;
pnpm typecheck  &lt;span class="c"&gt;# TypeScript check&lt;/span&gt;
pnpm lint       &lt;span class="c"&gt;# oxlint&lt;/span&gt;
pnpm build      &lt;span class="c"&gt;# build all packages&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This requires Node.js 24.15.0 or later and pnpm 10.33.0. Check &lt;code&gt;CONTRIBUTING.md&lt;/code&gt; in the repo for the full contribution guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Kimi Code CLI brings together a lot of what people already like about terminal-based AI coding agents: a fast single-binary install, a clean TUI, subagents, MCP support, and editor integration through ACP. The video input feature and the conversational MCP setup are the two pieces that stand out as genuinely different from similar tools.&lt;/p&gt;

&lt;p&gt;If you want to try it, the fastest path is the install script followed by &lt;code&gt;/login&lt;/code&gt;. From there, just start describing what you want done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Links&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repository: &lt;a href="https://github.com/MoonshotAI/kimi-code" rel="noopener noreferrer"&gt;https://github.com/MoonshotAI/kimi-code&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Strix: Give Your App a Free AI Pentester Before It Ships</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Thu, 02 Jul 2026 17:13:58 +0000</pubDate>
      <link>https://dev.to/arshtechpro/strix-give-your-app-a-free-ai-pentester-before-it-ships-1p5f</link>
      <guid>https://dev.to/arshtechpro/strix-give-your-app-a-free-ai-pentester-before-it-ships-1p5f</guid>
      <description>&lt;p&gt;If you have ever pushed code to production and quietly hoped nobody probes it too hard, this project is for you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/usestrix/strix" rel="noopener noreferrer"&gt;Strix&lt;/a&gt; is an open-source tool that runs autonomous AI agents against your application the way a real attacker would: it spins up your app, pokes at it, tries to break in, and proves whether a vulnerability is real before it tells you about it. No SaaS lock-in required, no waiting weeks for a pentest firm to get back to you.&lt;/p&gt;

&lt;p&gt;In this article I will walk through what Strix actually does, how it is different from the static analysis tools you already have, and how to run your first scan.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With Most "Security Scanning"
&lt;/h2&gt;

&lt;p&gt;Most security tooling developers use day to day falls into two buckets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Static analysis (SAST)&lt;/strong&gt; — scans your source code for risky patterns. Fast, but full of false positives, and it has no idea how your app actually behaves at runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency scanners&lt;/strong&gt; — flag known CVEs in your packages. Useful, but they say nothing about the business logic bugs you wrote yourself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither of these actually &lt;em&gt;runs&lt;/em&gt; your application and tries to exploit it. That's traditionally been the job of a human penetration tester, and hiring one is slow and expensive.&lt;/p&gt;

&lt;p&gt;Strix tries to close that gap by giving AI agents an actual hacker's toolkit and letting them attack a running instance of your app, the same way a person would.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Strix Actually Does
&lt;/h2&gt;

&lt;p&gt;Strix agents come with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A full HTTP proxy&lt;/strong&gt; for inspecting and manipulating requests and responses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser automation&lt;/strong&gt; for testing things like XSS, CSRF, and auth flows across multiple tabs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terminal access&lt;/strong&gt; for running commands and testing interactively&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Python runtime&lt;/strong&gt; for writing custom exploits on the fly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reconnaissance tooling&lt;/strong&gt; for mapping out the attack surface&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Static and dynamic code analysis&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of one agent doing everything, Strix uses a "graph of agents" model, so multiple specialized agents can work in parallel on different parts of your app and share what they find with each other.&lt;/p&gt;

&lt;p&gt;Critically, when Strix reports a vulnerability, it comes with an actual proof-of-concept demonstrating that the exploit works, not just a pattern match that says "this line looks suspicious." That is the main thing that separates it from a linter with a security label on it.&lt;/p&gt;

&lt;p&gt;It can detect things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Access control issues — IDOR, privilege escalation, auth bypass&lt;/li&gt;
&lt;li&gt;Injection attacks — SQL, NoSQL, command injection&lt;/li&gt;
&lt;li&gt;Server-side flaws — SSRF, XXE, insecure deserialization&lt;/li&gt;
&lt;li&gt;Client-side flaws — XSS, prototype pollution, DOM issues&lt;/li&gt;
&lt;li&gt;Business logic bugs — race conditions, workflow abuse&lt;/li&gt;
&lt;li&gt;Auth issues — JWT vulnerabilities, broken session management&lt;/li&gt;
&lt;li&gt;Infrastructure misconfigurations and exposed services&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installing and Running Your First Scan
&lt;/h2&gt;

&lt;p&gt;You need two things before you start:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Docker&lt;/strong&gt;, running locally (Strix's agents operate inside a sandbox container)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An API key&lt;/strong&gt; from a supported LLM provider — OpenAI, Anthropic, Google, or a local model via Ollama/LMStudio&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Install it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; https://strix.ai/install | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure which model powers the agents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;STRIX_LLM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"openai/gpt-5.4"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LLM_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-api-key"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then point it at a target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;strix &lt;span class="nt"&gt;--target&lt;/span&gt; ./app-directory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. On first run it will pull the sandbox Docker image, then start testing. Results land in &lt;code&gt;strix_runs/&amp;lt;run-name&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Strix will also remember your config after the first run, saving it to &lt;code&gt;~/.strix/cli-config.json&lt;/code&gt; so you don't have to set environment variables every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond a Local Folder
&lt;/h2&gt;

&lt;p&gt;Strix isn't limited to scanning a directory on your machine. A few other ways to point it at something:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Review a GitHub repo directly&lt;/span&gt;
strix &lt;span class="nt"&gt;--target&lt;/span&gt; https://github.com/org/repo

&lt;span class="c"&gt;# Black-box test a live deployed app&lt;/span&gt;
strix &lt;span class="nt"&gt;--target&lt;/span&gt; https://your-app.com

&lt;span class="c"&gt;# Test both the source code and the deployed app together&lt;/span&gt;
strix &lt;span class="nt"&gt;-t&lt;/span&gt; https://github.com/org/app &lt;span class="nt"&gt;-t&lt;/span&gt; https://your-app.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your app needs a login, you can hand Strix credentials and let it test authenticated flows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;strix &lt;span class="nt"&gt;--target&lt;/span&gt; https://your-app.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--instruction&lt;/span&gt; &lt;span class="s2"&gt;"Perform authenticated testing using credentials: user:pass"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also steer it toward specific concerns instead of a generic sweep:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;strix &lt;span class="nt"&gt;--target&lt;/span&gt; api.your-app.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--instruction&lt;/span&gt; &lt;span class="s2"&gt;"Focus on business logic flaws and IDOR vulnerabilities"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more detailed rules of engagement, scope, or exclusions, hand it a file instead of a one-liner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;strix &lt;span class="nt"&gt;--target&lt;/span&gt; api.your-app.com &lt;span class="nt"&gt;--instruction-file&lt;/span&gt; ./instruction.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running It Headless
&lt;/h2&gt;

&lt;p&gt;If you want to run Strix as part of an automated job rather than interactively, use non-interactive mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;strix &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;--target&lt;/span&gt; https://your-app.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It prints findings in real time and exits with a non-zero status code if it finds vulnerabilities, which makes it straightforward to fail a build on real findings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiring It Into CI/CD
&lt;/h2&gt;

&lt;p&gt;This is where Strix gets genuinely useful for a team: instead of running a security scan occasionally, you run it on every pull request and block insecure code before it merges.&lt;/p&gt;

&lt;p&gt;A minimal GitHub Actions setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;strix-penetration-test&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;security-scan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v6&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Strix&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;curl -sSL https://strix.ai/install | bash&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Strix&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;STRIX_LLM&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.STRIX_LLM }}&lt;/span&gt;
          &lt;span class="na"&gt;LLM_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.LLM_API_KEY }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;strix -n -t ./ --scan-mode quick&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A couple of practical notes if you're setting this up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;fetch-depth: 0&lt;/code&gt; in the checkout step. On PR runs, Strix automatically scopes a quick review to just the changed files, and it needs full git history to resolve that diff correctly. If it can't, pass &lt;code&gt;--diff-base&lt;/code&gt; explicitly.&lt;/li&gt;
&lt;li&gt;Store your LLM credentials as GitHub secrets, not plain environment variables.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuration Options Worth Knowing
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;STRIX_LLM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"openai/gpt-5.4"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LLM_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-api-key"&lt;/span&gt;

&lt;span class="c"&gt;# Optional: point at a local model instead&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LLM_API_BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-api-base-url"&lt;/span&gt;

&lt;span class="c"&gt;# Optional: enables search capability for the agents&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PERPLEXITY_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-api-key"&lt;/span&gt;

&lt;span class="c"&gt;# Optional: control how much the model "thinks"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;STRIX_REASONING_EFFORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;  &lt;span class="c"&gt;# default is high; quick scans use medium&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As for which model to run it with, the project currently recommends:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI GPT-5.4 (&lt;code&gt;openai/gpt-5.4&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Anthropic Claude Sonnet 4.6 (&lt;code&gt;anthropic/claude-sonnet-4-6&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Google Gemini 3 Pro Preview (&lt;code&gt;vertex_ai/gemini-3-pro-preview&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also supports Vertex AI, Bedrock, Azure, and local models — worth checking the &lt;a href="https://docs.strix.ai/llm-providers/overview" rel="noopener noreferrer"&gt;LLM providers docs&lt;/a&gt; if you want to run something self-hosted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Fits in Your Workflow
&lt;/h2&gt;

&lt;p&gt;A reasonable way to think about where Strix fits alongside what you probably already run:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool type&lt;/th&gt;
&lt;th&gt;What it catches&lt;/th&gt;
&lt;th&gt;What it misses&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SAST / linters&lt;/td&gt;
&lt;td&gt;Risky code patterns&lt;/td&gt;
&lt;td&gt;Runtime behavior, business logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependency scanners&lt;/td&gt;
&lt;td&gt;Known CVEs in packages&lt;/td&gt;
&lt;td&gt;Bugs in your own code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strix&lt;/td&gt;
&lt;td&gt;Exploitable, validated vulnerabilities with a working PoC&lt;/td&gt;
&lt;td&gt;Anything outside the scope you give it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Strix is not a replacement for code review or a full professional pentest on a critical system, but as a fast, repeatable layer that actually tries to exploit your app before an attacker does, it fills a gap that static tooling structurally can't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying It Out
&lt;/h2&gt;

&lt;p&gt;If you want to run this against something disposable first rather than your production app, that's a reasonable way to get a feel for it — spin up a small local project, point Strix at it, and see what it turns up.&lt;/p&gt;

&lt;p&gt;One important note directly from the maintainers: only test applications you own or have explicit permission to test. You are responsible for using it ethically and legally.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/usestrix/strix" rel="noopener noreferrer"&gt;github.com/usestrix/strix&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mobile</category>
      <category>ios</category>
      <category>android</category>
    </item>
    <item>
      <title>WWDC 2026 - Build Intelligent Siri Experiences with App Schemas</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Thu, 02 Jul 2026 09:28:07 +0000</pubDate>
      <link>https://dev.to/arshtechpro/wwdc-2026-build-intelligent-siri-experiences-with-app-schemas-102o</link>
      <guid>https://dev.to/arshtechpro/wwdc-2026-build-intelligent-siri-experiences-with-app-schemas-102o</guid>
      <description>&lt;p&gt;In the iOS 27 release, Siri takes a real step forward. It can now reach into the actual content inside your app, take actions on your behalf, and understand what is on screen right now. The way you plug into all of that as a developer is the App Intents framework, and the piece that makes it click is App Schemas.&lt;/p&gt;

&lt;p&gt;This guide walks through the core ideas from Apple's  session Build intelligent Siri experiences and turns them into something you can act on.&lt;/p&gt;

&lt;p&gt;If App Intents is completely new to you, it is worth watching "Get to know App Intents" first, since everything below builds on that foundation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The big picture: three new powers for Siri
&lt;/h2&gt;

&lt;p&gt;There are three ways Siri gets more capable this year, and it helps to hold all three in your head before diving into code.&lt;/p&gt;

&lt;p&gt;First, Siri can access your app's entities, meaning the real content inside your app. Someone can ask "When and where is my next meeting?" and Siri answers directly, because it understands what a meeting is, which one is relevant, and which properties to return.&lt;/p&gt;

&lt;p&gt;Second, Siri can take actions using your app's intents. A request like "Send my latest report to Mary" works because your intent describes the action, its parameters, and when it is safe to run. Siri handles the language understanding; your app just does the work.&lt;/p&gt;

&lt;p&gt;Third, Siri can understand on-screen context. When you annotate your views with entities, someone can say "Explain this text" or "Forward the last one" and Siri knows exactly what content they mean.&lt;/p&gt;

&lt;p&gt;Everything below is built on one central concept, so let us start there.&lt;/p&gt;

&lt;h2&gt;
  
  
  App Entities: describing the content you already have
&lt;/h2&gt;

&lt;p&gt;An App Entity is a structured description of the content inside your app. You are not building a new data model. You are describing content you already have in a way the system can reason about.&lt;/p&gt;

&lt;p&gt;Think about the nouns in your app. A calendar app has events. A mail app has messages. A photos app has photos and albums. UnicornChat has Contacts, Conversations, and Messages. Each of these is an App Entity.&lt;/p&gt;

&lt;p&gt;An entity describes three things: what the thing is, how it is identified, and which properties matter (a title, a date, some text). That is it.&lt;/p&gt;

&lt;p&gt;Modeling an entity is step one, but on its own it is not enough for Siri to find it or talk about it. For that, your entity needs to conform to an App Schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  App Schemas: giving Siri a shared vocabulary
&lt;/h2&gt;

&lt;p&gt;An App Schema gives Siri a predefined understanding of a common concept, such as a message, a contact, or a document. When your entity conforms to a schema, Siri already knows how to reason about it. Instead of treating your app as a black box, it understands that a UnicornChat message is a message.&lt;/p&gt;

&lt;p&gt;This is the key mental shift. You do not teach Siri your app's vocabulary or define training phrases. You declare your data against a shape Siri already knows, and the language understanding comes for free. Because schemas are system-defined, your integration automatically benefits as Siri improves and expands to new languages and dialects.&lt;/p&gt;

&lt;p&gt;Here is how UnicornChat contributes a message's text content to Apple Intelligence by conforming to the messages message schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Contributing message content to Apple Intelligence&lt;/span&gt;

&lt;span class="kd"&gt;@AppEntity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;MessageEntity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;IndexedEntity&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// The text content of the message&lt;/span&gt;
    &lt;span class="kd"&gt;@Property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;indexingKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AttributedString&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single &lt;code&gt;schema: .messages.message&lt;/code&gt; is what lets Siri understand a request like "Show my last message from Flare."&lt;/p&gt;

&lt;h2&gt;
  
  
  Entity resolution: turning words into real objects
&lt;/h2&gt;

&lt;p&gt;Once your content is modeled, the next question is how Siri finds the right one. That process is called entity resolution. When someone says "Open UnicornChat with Glow," Siri resolves that "Glow" refers to a specific contact, finds the match, and fills in the entity with its properties.&lt;/p&gt;

&lt;p&gt;But people do not always name things exactly. They describe them. Someone might say "the best windsurfing in Carmel," which is a meaning, not a text match. To support that, Siri needs semantic search, and there are two ways to power resolution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: IndexedEntity (the best experience)
&lt;/h3&gt;

&lt;p&gt;The primary path is adopting &lt;code&gt;IndexedEntity&lt;/code&gt;. When you do, your entities are added to the system's semantic index in Spotlight. That lets Siri match on meaning rather than exact text, understand relationships between entities, and answer questions over your content.&lt;/p&gt;

&lt;p&gt;For example, "Show the messages with Flare about movies" is not a string match. Siri can find messages that reference movie titles because it is running a semantic query over UnicornChat's indexed messages.&lt;/p&gt;

&lt;p&gt;Notice the &lt;code&gt;indexingKey&lt;/code&gt; in the earlier code sample. That is how you tell Spotlight which properties, like the message body, should be searchable. &lt;code&gt;IndexedEntity&lt;/code&gt; gives you semantic matching, fewer follow-up questions, and the most natural language understanding.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: EntityStringQuery (when indexing is not feasible)
&lt;/h3&gt;

&lt;p&gt;Not everything can be indexed ahead of time. Your dataset might be huge, live on a server, or change too frequently. In those cases you use &lt;code&gt;EntityStringQuery&lt;/code&gt;. Siri hands you the person's raw input string, and your app is responsible for finding and returning the matches.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// An interface that locates entities using arbitrary string input&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ContactQuery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;EntityStringQuery&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;entities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matching&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;ContactEntity&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;predicate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;#Predicate&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;localizedStandardContains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;descriptor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;FetchDescriptor&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;predicate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;predicate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;modelContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;descriptor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You lose semantic understanding here, but you gain full control over how you search. The rule of thumb: index when you can, use a string query when you cannot.&lt;/p&gt;

&lt;h2&gt;
  
  
  App Intents and action schemas: letting Siri do things
&lt;/h2&gt;

&lt;p&gt;Entities on their own are just information. Things get interesting when you combine them with actions, and actions come from App Intents.&lt;/p&gt;

&lt;p&gt;When you define an App Intent, that action can appear across the system: in Shortcuts, Spotlight, Widgets, and more. People can discover and trigger it even without Siri. You describe what the action does, define its parameters, and implement the behavior. The system handles surfacing and suggesting it.&lt;/p&gt;

&lt;p&gt;To bring an action to Siri specifically, you adopt an action schema. Just as entities use schemas to be understood, actions use schemas to become executable by Siri. Think of a schema as a specialized App Intent, shaped so Siri knows how to process it. The schema defines the kind of action, the structure Siri expects, and how it maps to natural language. That is what lets Siri confidently handle "Send a message to Mary" or "Play my focus playlist."&lt;/p&gt;

&lt;h3&gt;
  
  
  Domains: grouping schemas into complete experiences
&lt;/h3&gt;

&lt;p&gt;A single schema defines a single action, but apps usually need a set of them. That is why schemas are grouped into domains such as mail, photos, and messages. A domain is a category of contract between your app and Siri. When you adopt a domain, you implement its predefined schemas, map them to your app's functionality, and Siri immediately knows how to talk about your app in that category.&lt;/p&gt;

&lt;p&gt;In UnicornChat, sending a message means adopting the &lt;code&gt;sendMessage&lt;/code&gt; schema from the messages domain. In Xcode you start typing the schema name, autocomplete shows the available schemas grouped by domain, and you pick the one you want. Your job is then to map the schema's parameters (the recipient, the message content) onto your existing messaging flow: process the parameters, pass them into your send logic, and return the sent message back to the system as an entity.&lt;/p&gt;

&lt;p&gt;The payoff looks like this in practice. You say "Send a message to Glow in UnicornChat, saying 'What movies do you recommend?'" Siri resolves Glow through your entity query, invokes your intent, and sends the message, all without opening the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working across apps: on-screen awareness and content transfer
&lt;/h2&gt;

&lt;p&gt;Many real requests span multiple apps. "Email my wife this reply from Bubbles" starts in one app and finishes in another. That combines two capabilities: understanding what the person is looking at, and moving that content elsewhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  On-screen awareness
&lt;/h3&gt;

&lt;p&gt;To let Siri understand references like "this message" or "that conversation," you connect what is visible to your entities. There are two APIs, for two situations.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;UserActivity&lt;/code&gt; when there is one primary thing on screen, like a single document or a compose view. Use view annotations when several meaningful items are visible at once, like rows in a conversation. Here is the view annotation approach in UnicornChat, attaching each message row to its entity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Working across apps - View annotations&lt;/span&gt;

&lt;span class="kt"&gt;List&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;ForEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="kt"&gt;MessageRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appEntityIdentifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="kt"&gt;EntityIdentifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;MessageEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nv"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that in place, someone can say "Edit this message" or "Forward the last one" and Siri resolves the entity straight from the view.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exporting content to another app
&lt;/h3&gt;

&lt;p&gt;Content transfer is what lets other apps act on your entities. You enable it by conforming your entity to &lt;code&gt;Transferable&lt;/code&gt; and providing an &lt;code&gt;IntentValueRepresentation&lt;/code&gt;. UnicornChat exports a &lt;code&gt;ContactEntity&lt;/code&gt; as a system &lt;code&gt;IntentPerson&lt;/code&gt;, which powers requests like "Call this contact." Your app does not need to know what happens next; it just describes its content accurately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Working across apps - Exporting content to another app&lt;/span&gt;

&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="kt"&gt;ContactEntity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Transferable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;transferRepresentation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;TransferRepresentation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;IntentValueRepresentation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;exporting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Receiving content: resolve or import
&lt;/h3&gt;

&lt;p&gt;When content comes into your app, it either refers to something that already exists or represents something new. You decide which path to take.&lt;/p&gt;

&lt;p&gt;If the incoming content should match an existing entity, use &lt;code&gt;IntentValueQuery&lt;/code&gt;. This is conceptually like an entity query, but scoped to an intent parameter. Here UnicornChat receives an &lt;code&gt;IntentPerson&lt;/code&gt; from another app and matches it to an existing contact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Working across apps - IntentValueQuery&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ContactEntityQuery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;IntentValueQuery&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;IntentPerson&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;ContactEntity&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;descriptor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;FetchDescriptor&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Contact&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;contacts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mainContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;descriptor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contacts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;contact&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                &lt;span class="n"&gt;contact&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;localizedStandardContains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the incoming content should create something new, add an &lt;code&gt;importing&lt;/code&gt; closure to your &lt;code&gt;IntentValueRepresentation&lt;/code&gt;. This converts the incoming value into a brand new entity, such as creating a new unicorn from an &lt;code&gt;IntentPerson&lt;/code&gt;. Your app stays in control of how that content is stored.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Working across apps - IntentValueRepresentation&lt;/span&gt;

&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="kt"&gt;ContactEntity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Transferable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;transferRepresentation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;TransferRepresentation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;IntentValueRepresentation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;exporting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;importing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;intentPerson&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;contact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Contact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;importing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;intentPerson&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kt"&gt;ContactManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contacts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contact&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;contact&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Many apps use both: resolve when the content already exists, import when it does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best practices and tooling
&lt;/h2&gt;

&lt;p&gt;A few things separate a working integration from a great one.&lt;/p&gt;

&lt;p&gt;Some Siri scenarios need more than one schema. If you adopt &lt;code&gt;sendMessage&lt;/code&gt;, Xcode may raise a build error telling you that you also need &lt;code&gt;draftMessage&lt;/code&gt;, because a complete send flow requires a way to draft and confirm. This is a design hint delivered at build time rather than a silent runtime failure. Xcode even offers a fix-it that generates a stub adoption for you to fill in. If your app mutates UI state in an intent, remember to run that work on the main actor.&lt;/p&gt;

&lt;p&gt;When it comes to testing, work outward in layers. Start with AppIntentsTesting, a framework that lets you exercise your intents in isolation with no Siri involved, so you can validate business logic fast. Then use the Shortcuts app to inspect how your intent's parameters are shaped and exposed. Next check Spotlight to confirm your entities are indexed, discoverable, and linkable. Finally test end to end with Siri, where natural language, entity resolution, on-screen context, and cross-app workflows all come together.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>mobile</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
