<?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>Ramón Chancay: Web, Mobile &amp; AI Engineering</title>
    <link>https://www.ramonchancay.me/blog</link>
    <description>Technical writing on AI agents, LLM engineering, React Native and native iOS, by Ramón Chancay.</description>
    <language>en</language>
    <copyright>Ramón Chancay</copyright>
    <lastBuildDate>Sun, 13 Sep 2026 12:00:00 GMT</lastBuildDate>
    <atom:link href="https://www.ramonchancay.me/rss.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>What you can only do in native: WidgetKit, Live Activities, App Intents, StoreKit 2, Foundation Models and watchOS</title>
      <link>https://www.ramonchancay.me/blog/what-you-can-only-do-in-native-ios</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/what-you-can-only-do-in-native-ios</guid>
      <pubDate>Sun, 13 Sep 2026 12:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>The iOS capabilities React Native does not offer and that justify Swift to a client: WidgetKit, Live Activities, App Intents and Siri, StoreKit 2, the on-device language model with Foundation Models and watchOS. Series close and sales argument.

Widgets, Live Activities, App Intents and watchOS complications run outside your process, rendered by the system from SwiftUI. There is no JavaScript runtime there; the part of the app the user sees without opening it is written only in Swift. StoreKit 2 and Foundation Models are Swift APIs with async/await that offer, without a server, what used to require one: purchase verification on the device and a local language model with structured output. The sales argument is not &quot;native is better&quot;: it is a list of system surfaces where the product can live, with the cost of each one. The client chooses which ones are worth the price; you have to be able to build them.</description>
      <category>Mobile</category>
      <category>WidgetKit</category>
      <category>Live Activities</category>
      <category>App Intents</category>
      <category>StoreKit 2</category>
      <category>Foundation Models</category>
      <category>watchOS</category>
      <category>iOS</category>
    </item>
    <item>
      <title>iOS testing and tooling for seniors: Swift Testing, snapshots, mocks, Instruments and build times</title>
      <link>https://www.ramonchancay.me/blog/ios-testing-and-tooling-for-seniors</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/ios-testing-and-tooling-for-seniors</guid>
      <pubDate>Sat, 12 Sep 2026 12:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>How to test an iOS app coming from Jest and Testing Library: Swift Testing versus XCTest, snapshot testing, mocks via protocols and URLProtocol without jest.mock, XCUITest versus Maestro, Instruments, SwiftLint and modularizing to cut build times.

Swift Testing (@Test, #expect, parameterized tests) is the framework for new code; XCTest is still required for UI and performance tests. The two coexist in the same target. Without jest.mock, mocks come in by design: one protocol per dependency with a fake implementation, and URLProtocol to intercept the network without touching the client. Snapshots compare rendered images, and they are the cheapest test for a SwiftUI view. Build times come down with architecture: small packages, explicit types in long expressions, and Tuist with caching. Instruments tells you where the app&apos;s time goes; the compiler, with flags, tells you where the build&apos;s time goes.</description>
      <category>Mobile</category>
      <category>Swift Testing</category>
      <category>XCTest</category>
      <category>Snapshot testing</category>
      <category>Maestro</category>
      <category>Instruments</category>
      <category>SwiftLint</category>
      <category>iOS</category>
    </item>
    <item>
      <title>Build, signing and the App Store: what EAS hid from you</title>
      <link>https://www.ramonchancay.me/blog/build-signing-app-store-what-eas-hid-from-you</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/build-signing-app-store-what-eas-hid-from-you</guid>
      <pubDate>Fri, 11 Sep 2026 12:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>What EAS Build did for you and is now yours: certificates, provisioning profiles and entitlements explained once, Fastlane match for team signing, TestFlight, the App Review guidelines that reject the most, the Privacy Manifest and Tuist.

Signing is three things: a certificate (who you are, with the private key on your Mac), an App ID with capabilities (what the app can do) and a provisioning profile (the union of the two with the device list, for development, or without it, for the store). Entitlements are the copy of those capabilities inside the binary, and they must match the profile. Fastlane match stores certificates and profiles encrypted in a git repository and installs them on any machine or CI with one command. It is what EAS did on its servers, and it is the right way to sign as a team. Tuist generates the Xcode project from a Swift file: the .pbxproj leaves git and merge conflicts disappear. Together with packages, it is the difference between a team that is afraid to add files and one that is not.</description>
      <category>Mobile</category>
      <category>Xcode</category>
      <category>App Store</category>
      <category>Code signing</category>
      <category>Fastlane</category>
      <category>TestFlight</category>
      <category>Tuist</category>
      <category>Privacy Manifest</category>
    </item>
    <item>
      <title>UI, theming and animations in SwiftUI without NativeWind or Reanimated</title>
      <link>https://www.ramonchancay.me/blog/swiftui-ui-theming-animations-without-nativewind-reanimated</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/swiftui-ui-theming-animations-without-nativewind-reanimated</guid>
      <pubDate>Thu, 10 Sep 2026 12:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>How to do theming, animations and accessibility in SwiftUI coming from NativeWind and Reanimated: design tokens in the Asset Catalog, Dynamic Type as the default, SF Symbols, withAnimation, matchedGeometryEffect, PhaseAnimator, native gestures and Liquid Glass as a day-one API.

Design tokens go in the Asset Catalog (colors with light and dark variants, plus high contrast) and in extensions of Color, Font and ShapeStyle. There are no utility classes; there are custom modifiers and component styles (ButtonStyle, LabelStyle). An animation in SwiftUI is not a value you interpolate: it is an instruction that a state change should animate. withAnimation, .animation(_:value:), matchedGeometryEffect and PhaseAnimator cover what Reanimated did with shared values and worklets, with no UI thread to manage. Dynamic Type, VoiceOver and prefers-reduced-motion work on their own if you use system text styles and do not fix sizes. Every .font(.system(size: 14)) and every .frame(height: 44) breaks them a little.</description>
      <category>Mobile</category>
      <category>SwiftUI</category>
      <category>Animations</category>
      <category>Theming</category>
      <category>Accessibility</category>
      <category>Dynamic Type</category>
      <category>SF Symbols</category>
      <category>Liquid Glass</category>
    </item>
    <item>
      <title>From the expo-* ecosystem to Apple frameworks: the equivalence table by domain</title>
      <link>https://www.ramonchancay.me/blog/from-the-expo-ecosystem-to-apple-frameworks</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/from-the-expo-ecosystem-to-apple-frameworks</guid>
      <pubDate>Wed, 09 Sep 2026 12:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>Which Apple framework replaces each expo-* package when you move from React Native to native iOS: camera, location, authentication, payments, notifications, files and more, with the Info.plist permissions and the silent crash everyone hits once.

Almost every expo-* package is a wrapper around an Apple framework: AVFoundation, CoreLocation, UserNotifications, StoreKit, AuthenticationServices, PhotosUI. Learning the framework means learning what the package was hiding, including its design decisions. Every permission has two parts: a usage key in Info.plist with the text the user sees, and a request call at runtime. If the key is missing, the app closes with no dialog and no readable error; it is the silent crash of the first week. For what Apple does not cover (third-party analytics, Google maps, certain SDKs), Swift Package Manager has most of the official SDKs. What does not exist on SPM is a warning: a mature alternative probably does not exist either.</description>
      <category>Mobile</category>
      <category>Expo</category>
      <category>iOS</category>
      <category>Swift</category>
      <category>Apple frameworks</category>
      <category>Permissions</category>
      <category>Info.plist</category>
      <category>React Native</category>
    </item>
    <item>
      <title>iOS persistence: SwiftData, GRDB and Keychain, and why Realm is no longer an option</title>
      <link>https://www.ramonchancay.me/blog/ios-persistence-swiftdata-grdb-keychain</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/ios-persistence-swiftdata-grdb-keychain</guid>
      <pubDate>Tue, 08 Sep 2026 12:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>How to choose between SwiftData, GRDB and Core Data to persist data in an iOS app, why Realm stopped being an option, why the Keychain survives uninstalling, and how to design offline-first and sync with CloudKit.

SwiftData for small, Apple-only apps with views that use @Query; GRDB when there are real queries, migrations that will evolve, or sync with conflicts; Core Data only if you inherit it. Realm is discontinued and should not go into a new project. The Keychain is not wiped when the app is uninstalled. A token stored there comes back if the user reinstalls; you have to detect the first launch and clear it on purpose. Offline-first is an architecture, not a library: local writes first, a queue of pending changes, and sync with explicit conflict resolution. CloudKit gives it to you almost for free between the same user&apos;s devices; with your own backend, you write it.</description>
      <category>Mobile</category>
      <category>SwiftData</category>
      <category>GRDB</category>
      <category>Core Data</category>
      <category>Keychain</category>
      <category>CloudKit</category>
      <category>Offline-first</category>
      <category>iOS</category>
    </item>
    <item>
      <title>Sensitive data out of the prompt: a TypeScript library written in 39 minutes from its specification</title>
      <link>https://www.ramonchancay.me/blog/scrubbing-pii-before-the-llm-sanitype</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/scrubbing-pii-before-the-llm-sanitype</guid>
      <pubDate>Mon, 07 Sep 2026 12:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>sanitype redacts, masks or drops sensitive data inside the process before a payload leaves for an LLM, a log sink or a third party. How it is implemented in TypeScript.

It combines two strategies: field-path rules for what you already know is sensitive, and detectors over free text for the email someone pasted into a notes field. By default an object goes in and an object with the same structure comes out. Every call returns a report of what was touched, where and by which detector, and that report never contains the original value. The repository was written the other way round: first SPEC, ARCHITECTURE, ROADMAP and COMPARISON, then the code. 39 minutes passed between the specification commit and the implementation commit.</description>
      <category>AI</category>
      <category>TypeScript</category>
      <category>PII</category>
      <category>LLM</category>
      <category>Security</category>
      <category>Spec-Driven Development</category>
    </item>
    <item>
      <title>Swift concurrency: goodbye event loop. async let, TaskGroup, actors and Swift 6</title>
      <link>https://www.ramonchancay.me/blog/swift-concurrency-goodbye-event-loop</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/swift-concurrency-goodbye-event-loop</guid>
      <pubDate>Mon, 07 Sep 2026 12:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>Swift Concurrency for people coming from JavaScript: why there is no event loop, how async let and TaskGroup replace Promise.all, what actors and @MainActor are, what Sendable requires and why in Swift 6 a data race is a compile error.

There is no event loop: await suspends the function and frees the thread, and the continuation may run on another thread. async let and TaskGroup are your Promise.all, but with real parallelism and cancellation that flows down to the children. An actor is a class whose state is only touched from inside, one call at a time. @MainActor is the actor of the main thread, where the UI lives. Sendable is the mark that a value can cross between actors safely. With Swift 6 strict mode, sharing mutable data between tasks without protection does not compile. The errors are noisy at first and worth every minute: they remove the hardest class of bug to reproduce.</description>
      <category>Mobile</category>
      <category>Swift</category>
      <category>Concurrency</category>
      <category>async/await</category>
      <category>Actors</category>
      <category>Swift 6</category>
      <category>Sendable</category>
      <category>iOS</category>
    </item>
    <item>
      <title>Data in Swift: what TanStack Query did for you and how to build your cache layer</title>
      <link>https://www.ramonchancay.me/blog/data-in-swift-what-tanstack-query-did-for-you</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/data-in-swift-what-tanstack-query-did-for-you</guid>
      <pubDate>Sun, 06 Sep 2026 12:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>There is no TanStack Query in Swift. How to design the data layer of an iOS app with URLSession and Codable, a repository that emits with AsyncStream, SwiftData or GRDB as the local cache, invalidation and supabase-swift.

URLSession with async/await and Codable covers transport; you don&apos;t need Alamofire for a normal REST API. What&apos;s missing is everything TanStack Query put on top: cache, staleness, deduplication and refresh. The pattern that works is &quot;the local database is the cache&quot;: the repository writes what it fetches from the network into SwiftData or GRDB, and the view observes the database, not the network. That way offline, refresh and consistency between screens come out of the same design; request deduplication is a ten-line actor. Invalidation is modeled explicitly: a timestamp per collection and a policy per use case. It is less automatic than staleTime and easier to reason about when something shows up stale.</description>
      <category>Mobile</category>
      <category>Swift</category>
      <category>URLSession</category>
      <category>AsyncStream</category>
      <category>SwiftData</category>
      <category>GRDB</category>
      <category>TanStack Query</category>
      <category>Supabase</category>
    </item>
    <item>
      <title>Spec-Driven Development in React Native: an MVP that lists the world&apos;s earthquakes, specified before writing code</title>
      <link>https://www.ramonchancay.me/blog/spec-driven-development-react-native-earthquake-app</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/spec-driven-development-react-native-earthquake-app</guid>
      <pubDate>Sat, 05 Sep 2026 12:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>I applied Spec-Driven Development to a React Native MVP that lists the world&apos;s earthquakes: the specification decides what goes in the list, and its acceptance criteria become the tests.

SDD separates three artifacts: the specification (what and why), the technical plan (how) and the tasks. The agent implements against them instead of against a loose prompt. The real value is not the document: it is that ambiguities get flagged and resolved in writing before the agent chooses for you. Which earthquakes make the list, in what order, and what happens offline are product decisions, not code decisions. Acceptance criteria are written as a table of cases, and that same table becomes the test. If the row is not in the table, the behavior is undefined.</description>
      <category>AI</category>
      <category>Spec-Driven Development</category>
      <category>React Native</category>
      <category>Expo</category>
      <category>Coding agents</category>
      <category>MVP</category>
    </item>
    <item>
      <title>SwiftUI navigation without file-based routing: NavigationStack, a Route enum and deep links</title>
      <link>https://www.ramonchancay.me/blog/swiftui-navigation-without-file-based-routing</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/swiftui-navigation-without-file-based-routing</guid>
      <pubDate>Sat, 05 Sep 2026 12:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>How to move mentally from Expo Router to SwiftUI: NavigationStack with a Route enum, the Coordinator pattern, deep links and Universal Links, and what swift-navigation adds when sheets and alerts become state.

In SwiftUI, navigation is an array of values (NavigationPath or [Route]) that you control. An enum Route with associated values replaces Expo Router&apos;s app/ folder, and navigationDestination(for:) replaces the files. A deep link is a pure function URL -&gt; [Route]. Since the stack is state, opening a link means assigning that array; there is no need to &quot;navigate&quot; step by step. Sheets, alerts and confirmations are state too (an optional that presents when it is not nil). swift-navigation formalizes this with one destination enum per screen so a view can never show two things at once.</description>
      <category>Mobile</category>
      <category>SwiftUI</category>
      <category>Navigation</category>
      <category>NavigationStack</category>
      <category>Expo Router</category>
      <category>Deep links</category>
      <category>Universal Links</category>
    </item>
    <item>
      <title>State and architecture in SwiftUI: from Zustand and Redux to @Observable and TCA</title>
      <link>https://www.ramonchancay.me/blog/state-and-architecture-from-zustand-to-observable-and-tca</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/state-and-architecture-from-zustand-to-observable-and-tca</guid>
      <pubDate>Fri, 04 Sep 2026 12:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>How to manage state in SwiftUI coming from Zustand and Redux: MVVM with @Observable as the default, when TCA justifies its learning curve, dependency injection with swift-dependencies or Factory, and why to modularize into packages from day one.

An @Observable class is the replacement for a Zustand store: shared state, per-property observation and no selectors. One ViewModel per screen built on that class is the default that scales without ceremony. TCA (The Composable Architecture) is Redux with effects, cancellation and exhaustive tests built in. It pays off when the team is large, the domain is complex and you are going to test every transition; for a mid-sized app, its learning curve costs more than it returns. Inject dependencies from day one with swift-dependencies or Factory, and split the app into SPM packages by layer. Both habits are cheap at the start and very expensive to add later.</description>
      <category>Mobile</category>
      <category>SwiftUI</category>
      <category>Architecture</category>
      <category>TCA</category>
      <category>Observable</category>
      <category>Zustand</category>
      <category>Redux</category>
      <category>Swift Package Manager</category>
    </item>
    <item>
      <title>Voice agent that books appointments: ElevenLabs, Cal.com and a backend that never lets the model guess</title>
      <link>https://www.ramonchancay.me/blog/voice-agent-books-appointments-elevenlabs-calcom</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/voice-agent-books-appointments-elevenlabs-calcom</guid>
      <pubDate>Fri, 04 Sep 2026 12:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>How I built a voice agent that books 30-minute appointments with ElevenLabs and Cal.com, and why the Fastify backend is what keeps the model from inventing times.

The backend hands the agent at most three options already phrased for speech, and booking happens with an optionId (opt_1), never with a date the model writes itself. Booking is the only irreversible action: it requires explicit confirmation, blocks interruptions while it runs, and is idempotent per conversation. Tool errors come back as HTTP 200 with a sentence the agent can read out loud. A 5xx would leave the model improvising in the middle of a call.</description>
      <category>AI</category>
      <category>ElevenLabs</category>
      <category>Voice agents</category>
      <category>Cal.com</category>
      <category>Fastify</category>
      <category>Tool calling</category>
      <category>AI</category>
    </item>
    <item>
      <title>SwiftUI is not React, even if it looks like it: body, identity, modifiers and layout</title>
      <link>https://www.ramonchancay.me/blog/swiftui-is-not-react</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/swiftui-is-not-react</guid>
      <pubDate>Thu, 03 Sep 2026 12:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>SwiftUI explained for people coming from React: what body is compared to render, why a view&apos;s identity matters, how modifier order changes the result, the layout system without flexbox and the map from hooks to property wrappers.

A SwiftUI view is a cheap struct that describes the UI; it is created and destroyed constantly. State does not live in the struct but in a store that SwiftUI associates with the view&apos;s identity, which is why identity (structural, or explicit with id) decides what is kept and what is reset. Each modifier wraps the view in another view. .padding().background() and .background().padding() are two different trees with two different results; there is no stylesheet applied at the end. Layout is a three-step negotiation: the parent proposes a size, the child decides its own, the parent positions it. flex: 1 does not exist; Spacer, frame(maxWidth:) and layoutPriority do.</description>
      <category>Mobile</category>
      <category>SwiftUI</category>
      <category>React</category>
      <category>React Native</category>
      <category>iOS</category>
      <category>Layout</category>
      <category>Property wrappers</category>
    </item>
    <item>
      <title>Swift for people who already master TypeScript: the five differences that change how you design</title>
      <link>https://www.ramonchancay.me/blog/swift-for-typescript-developers</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/swift-for-typescript-developers</guid>
      <pubDate>Wed, 02 Sep 2026 12:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>Swift explained for TypeScript developers: value vs reference semantics, optionals as part of the type system, enums with associated data, protocols with extensions, and Codable instead of zod.

In Swift, structs are copied and classes are shared. Almost your entire data model should be struct; what you solved in TypeScript with spread and disciplined immutability, the compiler guarantees here. An optional is not a value that can be null: it is a distinct type the compiler forces you to unwrap. Enums with associated data replace discriminated unions and are stricter than them. Protocols with extensions and retroactive conformances replace structural interfaces. Codable covers half of what zod did (the shape of the data, with errors that point at the field); the other half, the domain rules, is written as validated types.</description>
      <category>Mobile</category>
      <category>Swift</category>
      <category>TypeScript</category>
      <category>iOS</category>
      <category>React Native</category>
      <category>Codable</category>
      <category>Types</category>
    </item>
    <item>
      <title>Why a senior React Native developer should learn native in 2026</title>
      <link>https://www.ramonchancay.me/blog/why-a-senior-react-native-dev-should-learn-native</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/why-a-senior-react-native-dev-should-learn-native</guid>
      <pubDate>Tue, 01 Sep 2026 12:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>When native iOS beats React Native (widgets, day-one APIs, on-device ML), when it loses (two platforms, OTA, team) and why learning Swift adds to your toolkit instead of replacing it.

Native wins where React Native does not reach or reaches late: widgets and Live Activities, the APIs Apple announces in June and requires in September, on-device ML models and the performance of the most demanding screens. React Native wins where the business needs it: two platforms with one team, OTA updates without going through App Review and a JavaScript hiring market that Swift does not have. For a senior the decision is not &quot;which one&quot;, it is &quot;when&quot;. Knowing native makes you a better React Native engineer (modules, crashes, the New Architecture) and lets you sell the project that really does need Swift.</description>
      <category>Mobile</category>
      <category>React Native</category>
      <category>Swift</category>
      <category>SwiftUI</category>
      <category>iOS</category>
      <category>Expo</category>
      <category>Mobile architecture</category>
    </item>
    <item>
      <title>The hard limits of an autonomous agent: what keeps it from disaster</title>
      <link>https://www.ramonchancay.me/blog/hard-limits-autonomous-agent</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/hard-limits-autonomous-agent</guid>
      <pubDate>Mon, 31 Aug 2026 15:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>The limits that separate an experiment from an agent you&apos;d leave running: iteration and token caps treated as a budget, command and path allowlists, a kill switch, idempotency and observability.

A limit asked for in the prompt is a preference; a hard limit is code that runs after the model has decided. Everything that matters—what commands it runs, where it writes, how much it spends, when it stops—goes in the program, not in the instructions. The four that aren&apos;t optional: a per-run budget (iterations, tokens and time), a command allowlist with no shell, a path allowlist resolved with realpath, and a kill switch someone else can flip without deploying anything. The hard part isn&apos;t the code: it&apos;s making the PRs worth reviewing. That doesn&apos;t depend on the agent, it depends on how clear your tickets are and how good your test suite is.</description>
      <category>AI</category>
      <category>Agents</category>
      <category>Agent loop</category>
      <category>Limits</category>
      <category>Security</category>
      <category>Observability</category>
      <category>AI</category>
    </item>
    <item>
      <title>What an agent harness is (and when not to use one): deepagents vs LangGraph</title>
      <link>https://www.ramonchancay.me/blog/what-is-an-agent-harness-deepagents-langgraph</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/what-is-an-agent-harness-deepagents-langgraph</guid>
      <pubDate>Fri, 07 Aug 2026 12:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>What an agent harness is, what deepagents adds on top of create_agent and LangGraph, its four layers (environment, context, delegation, steering), and the table for deciding when to drop down to LangGraph.

Agent = model + harness. The harness is all the code that isn&apos;t the model: loop, tools, filesystem, sandbox, context summarization, subagents, and approvals. deepagents is a pre-assembled harness; LangGraph is the runtime it runs on. They are three levels of the same stack: LangGraph (you draw the graph), create_agent (a tool loop with middleware), and deepagents (that loop plus four layers: execution environment, context management, delegation, and steering). Use the harness when the task is open-ended and the model has to decide the next step. Drop down to LangGraph when you decide the order of the steps, you need typed state, or every transition has to be explicit.</description>
      <category>AI</category>
      <category>deepagents</category>
      <category>Agent harness</category>
      <category>LangGraph</category>
      <category>LangChain</category>
      <category>Agents</category>
      <category>AI</category>
    </item>
    <item>
      <title>Data annotation jobs: what labeling and reviewing AI responses involves</title>
      <link>https://www.ramonchancay.me/blog/data-annotation-jobs-reviewing-ai-responses</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/data-annotation-jobs-reviewing-ai-responses</guid>
      <pubDate>Sat, 25 Jul 2026 12:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>What data annotation jobs and AI response review are: the tasks involved, how they feed model training (RLHF), and how annotator quality is measured.

Annotating data means producing the correct answer a model needs to learn: labels, transcriptions, reference answers, and comparisons between outputs. Reviewing AI responses means fact-checking, scoring against a rubric, and picking the better of two responses with a written justification; those preferences train the model. Platforms measure you with control tasks and inter-annotator agreement; judgment and specialization (code, math, law) are worth more than speed.</description>
      <category>AI</category>
      <category>Data annotation</category>
      <category>RLHF</category>
      <category>Data labeling</category>
      <category>Model evaluation</category>
      <category>Remote work</category>
    </item>
    <item>
      <title>Orca: the ADE for orchestrating a fleet of coding agents in parallel</title>
      <link>https://www.ramonchancay.me/blog/orca-parallel-coding-agent-fleet</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/orca-parallel-coding-agent-fleet</guid>
      <pubDate>Mon, 06 Jul 2026 12:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>I tried Orca, the open-source Agent Development Environment (ADE), to run a fleet of coding agents in parallel—Claude Code, Codex and more, each in its own isolated worktree.

Orca runs any CLI agent (Claude Code, Codex, Gemini, Grok, Cursor CLI, OpenCode and 25+ more) in parallel, each in its own isolated worktree, on your own subscription. It has a CLI (orca worktree, orca terminal) and an orchestration layer with tasks, dispatches and decision gates to coordinate the fleet from a coordinator agent. It runs on macOS, Windows and Linux, plus iOS and Android apps to monitor and steer agents from your phone. Install via brew or AUR. They ship daily.</description>
      <category>AI</category>
      <category>Orca</category>
      <category>Coding agents</category>
      <category>ADE</category>
      <category>Worktrees</category>
      <category>Orchestration</category>
    </item>
    <item>
      <title>From a Jira ticket to a pull request: the end-to-end autonomous system</title>
      <link>https://www.ramonchancay.me/blog/from-a-jira-ticket-to-a-pull-request</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/from-a-jira-ticket-to-a-pull-request</guid>
      <pubDate>Thu, 02 Jul 2026 15:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>How I turned the agent into an autonomous system: a Jira ticket triggers it (webhook or polling), a queue with persistent state processes it exactly once, and it returns a PR and a Jira comment.

The trigger doesn&apos;t run the agent: it only enqueues. A Jira webhook gives low latency but drops events; JQL polling is slow but loses nothing. Use the webhook to react fast and polling as the net that recovers what the webhook missed. The property that holds everything together is idempotency: process each ticket exactly once. You get it with persistent state in a database and an atomic claim, not with an in-memory flag that&apos;s lost when the process restarts. Each ticket runs in its own ephemeral workspace (the worktree from the previous post) and the output is a PR plus a Jira comment with the link. A person still decides the merge.</description>
      <category>AI</category>
      <category>Agents</category>
      <category>Agent loop</category>
      <category>Jira</category>
      <category>Automation</category>
      <category>Pull request</category>
      <category>AI</category>
    </item>
    <item>
      <title>The agent inside a real repo: isolating tasks with git worktree and shipping a PR</title>
      <link>https://www.ramonchancay.me/blog/agent-in-a-real-repo</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/agent-in-a-real-repo</guid>
      <pubDate>Tue, 30 Jun 2026 21:30:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>How to put an agent loop to work on a real repository: isolate each task with git worktree, reproduce the bug with a test, run the whole suite, and ship a PR instead of a merge.

Don&apos;t let the agent run on your working copy. Give each task its own isolated copy with git worktree and a branch: a failed attempt is a branch you delete, and two tasks at once don&apos;t step on each other. The flow that works for bugs is writing the failing test first (red), fixing until it&apos;s green, then running the WHOLE suite at the end to catch regressions. The new test passing isn&apos;t enough. The agent&apos;s output is a PR, not a merge. It proposes a diff on its own branch; a person reviews and integrates it. Isolating the work and shipping it as a PR is what lets you leave it running unsupervised.</description>
      <category>AI</category>
      <category>Agents</category>
      <category>Agent loop</category>
      <category>Git worktree</category>
      <category>Pull request</category>
      <category>AI</category>
    </item>
    <item>
      <title>From generating files to using tools: a code agent&apos;s ReAct loop</title>
      <link>https://www.ramonchancay.me/blog/react-loop-agent-tools</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/react-loop-agent-tools</guid>
      <pubDate>Tue, 30 Jun 2026 19:30:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>The shift from generating the whole function to an agent that reads, searches, edits, and runs with tools: the ReAct loop and why the agent builds its own context instead of receiving it in the prompt.

The shift is from &quot;generate the whole function&quot; to &quot;read, search, edit, run&quot;. The model stops emitting the solution and starts emitting tool calls; the program runs them and hands the result back. The agent builds its own context. Instead of you cramming the repo into the prompt, it reads and searches only what it needs, when it needs it. The context is the result of its observations, not something you preload. It&apos;s the ReAct loop: reason, act, observe. The same loop, evaluator, and sandbox from earlier posts, but the action is no longer single—it&apos;s a choice among several tools—and that choice is what makes it feel like a real code agent.</description>
      <category>AI</category>
      <category>Agents</category>
      <category>Agent loop</category>
      <category>ReAct</category>
      <category>Tool calling</category>
      <category>AI</category>
    </item>
    <item>
      <title>Who writes the agent&apos;s tests: three ways to define what&apos;s correct</title>
      <link>https://www.ramonchancay.me/blog/who-writes-the-tests-agent-evaluator</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/who-writes-the-tests-agent-evaluator</guid>
      <pubDate>Tue, 30 Jun 2026 18:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>Who writes an agent&apos;s tests defines what counts as correct. Three levels of input: spec and tests by hand, examples the agent turns into tests, or the repo&apos;s suite as the evaluator.

An agent&apos;s real input isn&apos;t describing the solution, but defining how a correct one is recognized. That piece—the tests, a check, another model—is what the series calls the evaluator, and an agent doesn&apos;t optimize for doing the right thing: it optimizes for passing it. There are three levels depending on who writes the evaluator: you by hand (spec + tests), the agent from examples you give it, or the repo&apos;s existing suite. The higher the level, the less work per task and the less control over the criterion. The higher the level, the higher what you have to review climbs. At level 2 you review the tests the agent wrote itself, not just its code: an agent that defines its own criterion can set itself a lax one and go green without being right.</description>
      <category>AI</category>
      <category>Agents</category>
      <category>Agent loop</category>
      <category>Evaluator</category>
      <category>Tests</category>
      <category>AI</category>
    </item>
    <item>
      <title>The sandbox in an agent loop: from the model&apos;s text to a reliable observation</title>
      <link>https://www.ramonchancay.me/blog/sandbox-running-llm-generated-code</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/sandbox-running-llm-generated-code</guid>
      <pubDate>Tue, 30 Jun 2026 16:30:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>The sandbox in an agent loop turns the model&apos;s text into a reliable observation: extract the code from the prose, check that it parses, run it with a timeout, and isolate the process.

The sandbox is the loop&apos;s run step, but its job is to turn the model&apos;s text into a reliable observation. It goes through four phases: entry, preparation, execution, and isolation. Extract the code from the prose, check that it parses, run it with a timeout and in a separate process with a clean environment. Watch out: the timeout only stops code that takes too long—not code that consumes too much—and a subprocess is not a security boundary. What moves the needle most is classification: having the sandbox return a status (not a boolean) and keep stdout and stderr separate, so each failure feeds back to the model as a distinct message.</description>
      <category>AI</category>
      <category>Agents</category>
      <category>Sandbox</category>
      <category>Code execution</category>
      <category>Agent loop</category>
      <category>LLM</category>
    </item>
    <item>
      <title>The simplest loop that works: a write-test-fix agent, step by step</title>
      <link>https://www.ramonchancay.me/blog/write-test-fix-agent-loop</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/write-test-fix-agent-loop</guid>
      <pubDate>Tue, 30 Jun 2026 15:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>The simplest agent that works: spec and tests by hand, the model writes the code, it runs, and the failure feeds back as context. A complete write-test-fix loop in code that runs.

The write-test-fix loop is the minimal agent: spec and tests written by hand, the model generates the code, it runs, and the test failure feeds back as context for the next attempt. The stopping condition comes from the tests: green and it stops, red and it retries, up to an iteration cap. The model doesn&apos;t need to &quot;decide&quot; it&apos;s done. Feedback is everything. A test that only says &quot;failed&quot; fixes nothing; one that says &quot;expected [9,5,25] and got [null,0,0]&quot; gives the model exactly what it needs to fix it.</description>
      <category>AI</category>
      <category>Agents</category>
      <category>Agent loop</category>
      <category>write-test-fix</category>
      <category>Code generation</category>
      <category>AI</category>
    </item>
    <item>
      <title>What an agent loop is (and isn&apos;t): state, action, stop</title>
      <link>https://www.ramonchancay.me/blog/what-is-an-agent-loop</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/what-is-an-agent-loop</guid>
      <pubDate>Tue, 30 Jun 2026 00:58:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>What an agent loop is and isn&apos;t: its three components—state, action, and stopping condition—and why &apos;build me an app&apos; is input for a chat, not an agent.

An agent loop has three pieces: a state (what the agent knows so far), an action (what it does each turn), and a stopping condition (when it decides it&apos;s done). The difference from a chat isn&apos;t the model, it&apos;s who runs the cycle: in a chat you run it (read, run, ask again); in an agent the program does. &quot;Build me an app&quot; is a prompt for a chat and a goal for an agent. Same sentence, two different architectures.</description>
      <category>AI</category>
      <category>Agents</category>
      <category>Agent loop</category>
      <category>LLM</category>
      <category>Agent architecture</category>
      <category>AI</category>
    </item>
    <item>
      <title>Model routing with OpenRouter and DeepSeek to cut costs without losing quality</title>
      <link>https://www.ramonchancay.me/blog/model-routing-openrouter-deepseek</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/model-routing-openrouter-deepseek</guid>
      <pubDate>Mon, 29 Jun 2026 12:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>How I routed models in production with OpenRouter, using DeepSeek for the bulk of traffic and premium models only where it matters, cutting LLM costs without degrading quality.

Send the bulk of traffic (classify, extract, summarize) to a cheap model like DeepSeek and reserve premium (Claude) only for the hard cases. Validate the cheap model&apos;s output and escalate to premium only when it fails. Clean the JSON (fences, trailing commas) before giving up, so you don&apos;t escalate unnecessarily. Measure everything by model and feature. With volume and tasks of uneven difficulty, savings land around 80-90%.</description>
      <category>AI</category>
      <category>OpenRouter</category>
      <category>DeepSeek</category>
      <category>LLM</category>
      <category>Cost optimization</category>
      <category>Model routing</category>
    </item>
    <item>
      <title>I automated a news site with n8n: from 20 RSS feeds to WordPress drafts</title>
      <link>https://www.ramonchancay.me/blog/news-pipeline-n8n-rss-wordpress</link>
      <guid isPermaLink="true">https://www.ramonchancay.me/blog/news-pipeline-n8n-rss-wordpress</guid>
      <pubDate>Mon, 29 Jun 2026 12:00:00 GMT</pubDate>
      <dc:creator>Ramón Chancay</dc:creator>
      <description>How I built an n8n pipeline that reads 20 RSS feeds, deduplicates with Postgres, curates and rewrites with tiered AI, and leaves the article as a WordPress draft with an image and a Telegram alert.

Deduplicate before spending: hash the link and let Postgres reject repeats with ON CONFLICT DO NOTHING. That way you don&apos;t scrape or rewrite the same story twice. Use a cheap model to score relevance from 0 to 100 before rewriting: only what clears the threshold reaches the expensive model. Premium spend is the exception, not the rule. Leave everything as a draft, not published. The automation proposes; you approve. And alert yourself on Telegram so you review in time.</description>
      <category>Automation</category>
      <category>n8n</category>
      <category>Automation</category>
      <category>WordPress</category>
      <category>RSS</category>
      <category>LLM</category>
      <category>OpenRouter</category>
    </item>
  </channel>
</rss>
