<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://www.fbeeper.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.fbeeper.com/" rel="alternate" type="text/html" /><updated>2026-06-04T10:01:00+00:00</updated><id>https://www.fbeeper.com/feed.xml</id><title type="html">fbeeper</title><author><name>Ferran Poveda</name></author><entry><title type="html">Exploration Got Cheap.Human Review Did Not.</title><link href="https://www.fbeeper.com/agentkitten/2026/06/04/Exploration-Got-Cheap-Human-Review-Did-Not/" rel="alternate" type="text/html" title="Exploration Got Cheap.Human Review Did Not." /><published>2026-06-04T00:00:00+00:00</published><updated>2026-06-04T00:00:00+00:00</updated><id>https://www.fbeeper.com/agentkitten/2026/06/04/Exploration-Got-Cheap-Human-Review-Did-Not</id><content type="html" xml:base="https://www.fbeeper.com/agentkitten/2026/06/04/Exploration-Got-Cheap-Human-Review-Did-Not/"><![CDATA[<p>For as long as I’ve worked in the field, software architectures that last (the ones you want to, not the ones you <em>have</em> to live with) often come from knowing the bird’s eye view. However, when the calendar is tight, exploration gets squeezed. So we know how to commit to <em>a</em> direction quite early. We build plans with what’s known while trying to prepare for what isn’t. We break it into steps, and commit to discover the rest of the shape through delivery. This can still be good discipline (arguably is more agile!), and we make it work.</p>

<p>However, the tax on exploration has dropped. AI has made it cheaper (setting aside the cost of tokens): I can generate a grand version, <em>see</em> for myself how it holds, and push it further in the time I have.</p>

<p>It certainly does not remove the human part. You can fake it for social media, but the initiative, judgement, and critical thinking are still ours. We decide what’s done, and whether the shape is right.</p>

<p>And the same goes at the other end: once a concept is deeply explored, breaking it into deliverable pieces (even when that means big refactors) got cheaper too.</p>

<p>It is tempting to brush that last bit and conclude the whole software-engineering job got cheaper and we can just ship the big thing.</p>

<p>It didn’t. One part at the center didn’t move at all.</p>

<h2 id="the-part-that-didnt-move">The part that didn’t move</h2>

<p>Even code reviewing split in two.</p>

<p>A serious chunk got cheaper: AI reviews code better and better. They are fast and tireless. They can catch many things we’d be embarrassed to ship (or even show another human).</p>

<p>So, this is raising the floor of what’s worth your time. But… it does not shorten the human’s afternoon. And it certainly does not transfer ownership.</p>

<p>That is the part that didn’t get cheaper. A person understanding your change and standing behind it, gated by human attention. That’s as scarce as it’s ever been.</p>

<p>The trap is letting the cheapness of exploration leak: You explore big, it works, and you try to deliver the exploration artifact.</p>

<p>But <strong>the output of exploration is understanding, not a merge.</strong></p>

<h2 id="why-chewable-isnt-negotiable">Why chewable isn’t negotiable</h2>

<p>Assume the “cheap” review already happened. The developer ran the machine over their own change and fixed what it found. What’s left, is the human part.</p>

<p>Today, I still struggle with how casually large PRs are accepted. A Swift pull request north of 2000 lines is routinely treated as a normal thing to ask another engineer to review. It is not.</p>

<p>At that size, you can’t review it without extreme guidance from the author. And even with it… you drift.</p>

<p>When attention thins out, you start pattern-matching, you validate the shape and miss the substance: Understanding it.</p>

<p><strong>Reviewers are not a gate for a LGTM.</strong></p>

<p>They are there to find what you missed. The blind spots. The assumptions. The places where understanding was incomplete. We are there help each other grow (or fail less spectacularly… that is, if we let our reviewers be effective).</p>

<p>Tests count too. A 2000 line PR that is “only 800 lines of feature code” is still a 2000-line PR. It’s so tempting to wave off size (I’ve done it myself!) when “most of it is tests.” But tests stay to subtly encode assumptions, and they become a template for the next change. Skim them in review and you won’t save time… you will have shipped fake peace of mind for the future.</p>

<h2 id="a-small-but-illustrative-example">A small but illustrative example</h2>

<p><a href="https://github.com/fbeeper/agentkitten">AgentKitten</a>, my new Swift library for building AI agents, is meant to be agnostic to inference providers. Pick an inference model provider and swap it at “no cost”. That promise only holds if adding one provider is tractable. So, adding OpenAI as a new provider was a test of the architecture, not just a feature.</p>

<p>I explored the whole thing first, exactly as the new economics invite: I worked with a coding agent for a complete OpenAI provider modeled on the existing ones. It came back <em>fast</em>. One rather large change covering the whole surface: text, tools, structured output, token counting, compaction. Of course it compiled and tests passed. And it worked on every manual test I threw at it.</p>

<p>As exploration, a success: in an afternoon I had an end-to-end answer to “what does this provider look like across the whole surface?”</p>

<p>As delivery, unmergeable: Long stretches were a near word-for-word clone of the existing Anthropic provider with the nouns swapped. A clone inherits assumptions faster than you can inspect them. I couldn’t review it without drifting. As expected, I closed it.</p>

<p><strong>Code I cannot faithfully review is one I cannot own.</strong></p>

<p>I knew that the provider surface is meant to be more modular. I designed the API boundary for this. A provider isn’t one all-or-nothing conformance. It is a set of responsibilities. Some stacked, some not. Each gated by its own seam: text inference, tool calls, structured inference, and history compaction.</p>

<p>You should be able to stop at any feature and still have a complete and honest provider. A text-only inference provider isn’t half-finished, it’s finished if it says “I don’t do tools” out loud.</p>

<p>That “large but cheap” exploration is now meant to be broken down in smaller bits, a process that is also become relatively cheap.</p>

<p>The angle for cutting this one was obvious to me in this case: cut PRs to fulfill the 4 main responsibilities of a provider. I broke the work down as separate and stacked branches in git worktrees. One capability per branch, each built on the last.</p>

<p>With this I:</p>

<ul>
  <li>Made my reviews feasible. I could read a change end to end without drifting off.</li>
  <li>Proved a partially-featured provider can ship and fully work.</li>
</ul>

<p>And, it made the AI reviews tangibly better too. A focused diff gets a focused review, the model started catching things that actually mattered. With both of us so much more focused, I was able to improve a bunch of things along the way.</p>

<p>By the end, the work stopped being a clone exactly where cloning was ineffective (or plain wrong). But the exploration stayed valuable without forcing reviews to inherit its size.</p>

<h2 id="tldr">TL:DR;</h2>

<p>Software Engineering exploration got cheaper. A chunk of review process did too. A human understanding changes and standing behind it… did not.</p>

<p><strong>Explore big and fast</strong>. Push architecture further than you otherwise would.</p>

<p><strong>Deliver it small and honest</strong>. Broken down into chewable pieces a person (and the model) can actually understand and own.</p>

<p>Large exploration and small delivery are not contradictory. They are the same workflow.</p>]]></content><author><name>Ferran Poveda</name></author><category term="agentkitten" /><category term="agentkitten" /><category term="AI" /><category term="coding" /><category term="agent" /><summary type="html"><![CDATA[Software Engineering exploration got cheaper. A chunk of review process did too. A human understanding changes and standing behind it... did not.]]></summary></entry><entry><title type="html">Who Owns the AI Agent Loop?</title><link href="https://www.fbeeper.com/agentkitten/2026/05/13/Who-Owns-the-AI-Agent-Loop/" rel="alternate" type="text/html" title="Who Owns the AI Agent Loop?" /><published>2026-05-13T00:00:00+00:00</published><updated>2026-05-13T00:00:00+00:00</updated><id>https://www.fbeeper.com/agentkitten/2026/05/13/Who-Owns-the-AI-Agent-Loop</id><content type="html" xml:base="https://www.fbeeper.com/agentkitten/2026/05/13/Who-Owns-the-AI-Agent-Loop/"><![CDATA[<p><strong>Who Owns the AI Agent Loop?</strong> is not particularly a new question.</p>

<p>If you’ve been building features with LLMs over the last few years you’ve likely used frameworks like LangChain/LangGraph, Vercel AI SDK, LlamaIndex, Google ADK, etc. Alternatively, you may be deeply exposed to chatbots, coding agents, and/or assistants. All of the above, roughly circling around the same idea, are flavors of the <strong>AI Agent loop being a mix of large language models and software scaffolding</strong> (typically called a <em>harness</em>).</p>

<p>What’s surprising is that for Swift and Apple platform developers, a solid version of the harness-building toolset doesn’t seem to exist yet. Working on some exploratory features, I kept having to relearn and rebuild the same scaffolding every time I wanted to try a different provider. The kind of thing a framework exists to solve. And since a clean Swift abstraction of it didn’t already exist, I built mine:</p>

<p><strong><a href="https://github.com/fbeeper/agentkitten">AgentKitten</a></strong> <br />
<strong>A Swift package for building provider-agnostic AI agents on Apple platforms.</strong></p>

<hr />

<h2 id="the-agent---harness-spectrum">The Agent - Harness Spectrum</h2>

<p><em>### Feel free to skip this section if already proficient in Agents and Harnesses. ###</em></p>

<p>There has been a popular definition doing its rounds for a while:</p>

<blockquote>
  <p><em>Agent = Model + Harness</em></p>
</blockquote>

<p>This captures something really useful. However, I think there is a more nuanced spectrum to grasp.</p>

<p>Early language models generated text. Then, models were trained to request tool execution: to say “call this function with these arguments” rather than just describing what should happen. That may be clearest split of responsibility between Agent and Harness. But harnesses went further, mostly compensating: managing memory the model couldn’t maintain, adding validation loops for outputs it couldn’t reliably verify, imposing step limits for when it may not be able to recognize it was stuck. And many of today’s models have internalized much of that: planning, self-correction, knowing when a task is done. So the harness has shifted its responsibilities.</p>

<p>What surely stays in the harness isn’t just what the model can’t do, but what the model should not self-govern regardless of capability: the policies that define what it’s allowed to do, the approval gates that keep a human in consequential decisions, the trace that makes the whole thing auditable, etc.</p>

<p>The harness’ boundary keeps shifting as models improve. Models are internalizing capabilities that required explicit harness scaffolding just a year ago. It is plausible that a significant portion of what today’s harnesses provide will be absorbed by the models, by protocols, or by higher-order orchestration. But what probably stays is whatever the model shouldn’t self-govern regardless of capability.</p>

<p>An on-device model (tight context window, narrower general-purpose reasoning, running locally with privacy guarantees by design) will likely continue to need more active help from the harness. For example, context compaction could be critical, and/or validation loops can compensate for less reliable self-correction.</p>

<p>A frontier cloud model (abundant context window, broader reasoning, remote endpoint) shifts the weight elsewhere. Cost per token matters, data exposure is a real concern, and observability of consequential actions may be key.</p>

<p>There is a world of harness primitives that may stay exactly the same either way: tools, policies, and traces. And other core primitives, although used differently, may also have a shared surface like validation loops and state. In any of these stable concepts, what really changes is the configuration and where the harness ends up doing most of its work.</p>

<hr />

<h2 id="what-agentkittens-abstraction-covers">What AgentKitten’s abstraction covers</h2>

<p>The two providers AgentKitten currently ships with are at opposite ends of the spectrum. Chosen not because they’re the only ones worth supporting, but because they make the clearest test of what the abstraction does and doesn’t cover:</p>

<ul>
  <li>Anthropic’s API is remote, stateless, with open history, and tool loop-delegating. You can rather easily work the history, and get tool calls mid-inference and everything after that is your code.</li>
  <li>Apple’s Foundation Models SDK is on-device, stateful, local, history and tool loop-owning. It keeps its transcript rather close to its chest, and dispatches tools through its session internally.</li>
</ul>

<p>The abstraction holds across both ends of that range reasonably well. Where it leaks, provider-specific behavior remains accessible rather than hidden behind forced generalization. That leakage is intentional, not a failure mode.</p>

<p>Also, AgentKitten isn’t a pipeline DSL. There are frameworks that compose model calls into step graphs well. This framework is focused instead on ongoing interactive sessions with state, effectively controlling tools, and a loop that persists for as many turns as the conversation requires.</p>

<hr />

<h2 id="what-agentkittens-loop-ownership-gives-you">What AgentKitten’s loop ownership gives you</h2>

<p>I have four immediate examples worth being specific about. Each one a problem the framework handles so you don’t have to rebuild it:</p>

<h3 id="1-tool-execution-policy">1. Tool execution policy</h3>

<p>The decision your app can make before anything runs.</p>

<ul>
  <li>Apple’s FoundationModels dispatches tools through its session internally. You implement the tool, the model decides to call it. It runs. There is no moment between the model’s decision and the execution where your app participates.</li>
  <li>Anthropic’s API hands you the tool call and trusts you to do whatever you want. But “whatever you want” is left entirely to you to implement from scratch.</li>
</ul>

<p><code class="language-plaintext highlighter-rouge">ToolExecutionPolicy</code> is that missing moment, made explicit by AgentKitten. Before any tool call executes, your policy sees the call and returns one of three decisions:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="kd">public</span> <span class="kd">protocol</span> <span class="kt">ToolExecutionPolicy</span><span class="p">:</span> <span class="kt">Sendable</span> <span class="p">{</span>
      <span class="kd">func</span> <span class="nf">resolve</span><span class="p">(</span>
          <span class="nv">call</span><span class="p">:</span> <span class="kt">PendingToolCall</span><span class="p">,</span>
          <span class="nv">context</span><span class="p">:</span> <span class="kt">ToolExecutionContext</span>
      <span class="p">)</span> <span class="k">async</span> <span class="o">-&gt;</span> <span class="kt">ToolExecutionDecision</span>
  <span class="p">}</span> 
  
  <span class="kd">public</span> <span class="kd">enum</span> <span class="kt">ToolExecutionDecision</span><span class="p">:</span> <span class="kt">Sendable</span><span class="p">,</span> <span class="kt">Equatable</span> <span class="p">{</span>
      <span class="k">case</span> <span class="n">execute</span>
      <span class="k">case</span> <span class="nf">deny</span><span class="p">(</span><span class="nv">reason</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span>
      <span class="k">case</span> <span class="n">requiresApproval</span>
  <span class="p">}</span>
</code></pre></div></div>

<p>Here, <code class="language-plaintext highlighter-rouge">.execute</code> is the silent path for background agents or specific calls that run unattended. Meanwhile, <code class="language-plaintext highlighter-rouge">.deny</code> lets the policy refuse a call and surface a reason back to the model. That way, it can try a different approach rather than failing silently. And <code class="language-plaintext highlighter-rouge">.requiresApproval</code> suspends the turn and emits a <code class="language-plaintext highlighter-rouge">toolApprovalRequired</code> event. The turn stays live. The model is waiting, until the caller resolves it.</p>

<p>The <code class="language-plaintext highlighter-rouge">PendingToolCall</code> given to your implementation carries something worth singling out: The <code class="language-plaintext highlighter-rouge">modelRationale</code>. This is the model’s own self-reported rationale for wanting to make this tool call. It is available as UX context. With it, you can avoid generic messaging but provide the model’s actual reasoning. The field is marked explicitly in the source documentation as “UX context only — may be nil, inaccurate, or adversarially supplied” which is the brutal truth. But for interactive approval flows it could be the difference between a bare permission dialog and one that gives the user something meaningful to respond to.</p>

<p>Lastly, the approval resolution sits on the session:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">for</span> <span class="k">try</span> <span class="k">await</span> <span class="n">event</span> <span class="k">in</span> <span class="n">turn</span><span class="o">.</span><span class="n">events</span> <span class="p">{</span>
      <span class="k">if</span> <span class="k">case</span> <span class="o">.</span><span class="nf">toolApprovalRequired</span><span class="p">(</span><span class="k">let</span> <span class="nv">call</span><span class="p">)</span> <span class="o">=</span> <span class="n">event</span><span class="o">.</span><span class="n">kind</span> <span class="p">{</span>
          <span class="k">let</span> <span class="nv">approved</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">showApprovalDialog</span><span class="p">(</span>
              <span class="nv">toolName</span><span class="p">:</span> <span class="n">call</span><span class="o">.</span><span class="n">name</span><span class="p">,</span>
              <span class="nv">rationale</span><span class="p">:</span> <span class="n">call</span><span class="o">.</span><span class="n">modelRationale</span>
          <span class="p">)</span>
          <span class="k">if</span> <span class="n">approved</span> <span class="p">{</span>
              <span class="k">try</span> <span class="k">await</span> <span class="n">session</span><span class="o">.</span><span class="nf">approve</span><span class="p">(</span><span class="nv">callID</span><span class="p">:</span> <span class="n">call</span><span class="o">.</span><span class="n">id</span><span class="p">)</span>
          <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
              <span class="k">try</span> <span class="k">await</span> <span class="n">session</span><span class="o">.</span><span class="nf">deny</span><span class="p">(</span><span class="nv">callID</span><span class="p">:</span> <span class="n">call</span><span class="o">.</span><span class="n">id</span><span class="p">,</span> <span class="nv">reason</span><span class="p">:</span> <span class="s">"User declined."</span><span class="p">)</span>
          <span class="p">}</span>
      <span class="p">}</span>
  <span class="p">}</span>
</code></pre></div></div>

<h3 id="2-per-turn-overrides">2. Per-turn overrides</h3>

<p>Controlling what the model can see and do one step at a time.</p>

<p>Let’s explain this through an example: It is well known some agent patterns may benefit from separating planning from execution. With this separation we can let the model reason without access tools with side effects. Then, open them back up once there’s a plan. <code class="language-plaintext highlighter-rouge">TurnOverrides</code> lets you change model, inference settings, or tool availability for a single turn without touching the agent or session as a whole:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">let</span> <span class="nv">planningOverrides</span> <span class="o">=</span> <span class="kt">TurnOverrides</span><span class="p">(</span>
      <span class="nv">toolSelection</span><span class="p">:</span> <span class="o">.</span><span class="nf">including</span><span class="p">([</span><span class="s">"read_file"</span><span class="p">,</span> <span class="s">"list_directory"</span><span class="p">])</span>
  <span class="p">)</span>
  <span class="k">let</span> <span class="nv">planTurn</span> <span class="o">=</span> <span class="k">try</span> <span class="k">await</span> <span class="n">session</span><span class="o">.</span><span class="nf">send</span><span class="p">(</span><span class="n">taskPrompt</span><span class="p">,</span> <span class="nv">turnOverrides</span><span class="p">:</span> <span class="n">planningOverrides</span><span class="p">)</span>
  <span class="k">let</span> <span class="nv">executionTurn</span> <span class="o">=</span> <span class="k">try</span> <span class="k">await</span> <span class="n">session</span><span class="o">.</span><span class="nf">send</span><span class="p">(</span><span class="s">"Execute the plan."</span><span class="p">)</span> <span class="c1">// Base Agent and Tool Behavior</span>
</code></pre></div></div>

<p>Because models can be swapped per turn, planning can run on a faster model while execution runs on a more capable one, all within a shared session history. That orchestration logic belongs to the application, not the provider.</p>

<p>The policy can also read typed custom environment values you’ve threaded through <code class="language-plaintext highlighter-rouge">TurnOverrides</code>. Which means things like your tool approval logic can branch on turn state without coupling your tool definitions to that state. A turn running in an elevated-trust context can auto-approve. The same agent running for a guest user can require confirmation for every write operation. The policy is the seam between the agent’s behavior and your app’s trust model.</p>

<h3 id="3-context-compaction">3. Context Compaction</h3>

<p>The on-device constraints aren’t a phase.</p>

<p>As of today, Apple Foundation Models have a smaller context window (4096 tokens) than a frontier cloud model (around 200k to 1M tokens). This isn’t a temporary problem, so multi-turn conversations within the same context will hit the limit faster than you’d want them to. Especially with tool results accumulating in history. Truncation loses context. Not doing multi-turn may make the features significantly worse. Summarization is simple, but it is well proven to work: Compress older turns, and even preserve recent ones for increased fidelity of recent context. And have the ability for it to fire timely and automatically at the right threshold, without the caller managing it per session.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">let</span> <span class="nv">behavior</span> <span class="o">=</span> <span class="kt">AgentBehavior</span><span class="p">(</span>
      <span class="nv">systemPrompt</span><span class="p">:</span> <span class="s">"You are a search assistant."</span><span class="p">,</span>
      <span class="n">defaultAutomaticCompactionPolicy</span><span class="p">:</span> <span class="o">.</span><span class="nf">enabled</span><span class="p">(</span>
          <span class="nv">trigger</span><span class="p">:</span> <span class="o">.</span><span class="nf">percentOfContextWindow</span><span class="p">(</span><span class="mf">0.5</span><span class="p">)</span> <span class="c1">// When 50% full</span>
      <span class="p">)</span>
  <span class="p">)</span>
</code></pre></div></div>

<p>Same exact configuration can work for all providers. The adapter handles the mechanics. The policy is yours. You can even define a different provider to summarize than those being used for inference.</p>

<p>Even if you were fully vibe-coding, I wouldn’t want to reimplement this support in every fresh agent harness you build per project. For context, getting that architecture right took five consecutive pull requests and some inversions of the ownership model.</p>

<h3 id="4-powerful-tool-hooks">4. Powerful Tool Hooks</h3>

<p>Beyond permissions, intercepting tool calls before and after execution opens up a range of use cases that execution policy alone can’t cover.</p>

<p>Privacy is the most fun example to talk about this one: Apple’s on-device inference and Private Cloud Compute offer a strong privacy guarantee by design. But the moment you’re outside that (calling a remote model) what reaches the inference endpoint becomes your responsibility.</p>

<p>Redaction and rehydration is a pattern tool hooks make possible. You can strip PII from the data before it reaches the model by replacing a user’s email with a placeholder the model can work with. Then, intercept tool calls before they execute to rehydrate those sentinels back to real values. And strip again on the way out if tool results contain sensitive data before they would otherwise be fed back to the model.</p>

<p><code class="language-plaintext highlighter-rouge">ToolHook</code> intercepts tool calls at both points.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="kd">public</span> <span class="kd">protocol</span> <span class="kt">ToolHook</span><span class="p">:</span> <span class="kt">Sendable</span> <span class="p">{</span>
      <span class="k">var</span> <span class="nv">name</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>
      <span class="k">var</span> <span class="nv">phases</span><span class="p">:</span> <span class="kt">Set</span><span class="o">&lt;</span><span class="kt">ToolHookPhase</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>

      <span class="kd">func</span> <span class="nf">beforeExecute</span><span class="p">(</span>
          <span class="n">_</span> <span class="nv">call</span><span class="p">:</span> <span class="kt">PendingToolCall</span><span class="p">,</span>
          <span class="nv">context</span><span class="p">:</span> <span class="kt">ToolExecutionContext</span>
      <span class="p">)</span> <span class="k">async</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">PendingToolCall</span>

      <span class="kd">func</span> <span class="nf">afterExecute</span><span class="p">(</span>
          <span class="n">_</span> <span class="nv">call</span><span class="p">:</span> <span class="kt">PendingToolCall</span><span class="p">,</span>
          <span class="nv">outcome</span><span class="p">:</span> <span class="kt">ToolCallOutcome</span><span class="p">,</span>
          <span class="nv">context</span><span class="p">:</span> <span class="kt">ToolExecutionContext</span>
      <span class="p">)</span> <span class="k">async</span> <span class="o">-&gt;</span> <span class="kt">ToolCallOutcome</span>
  <span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">beforeExecute</code> receives the pending call and allows to inspect and/or instrument it (arguments modified, placeholders swapped in, or the call cancelled outright). <code class="language-plaintext highlighter-rouge">afterExecute</code> allows inspection and instrumentation of the outcome (letting you redact or reshape the result before it feeds back to the model).</p>

<p>With proper redaction, the model never sees what it shouldn’t while the tools operate on real data.</p>

<p>Of course, this kind of privacy is only as good as your detection and replacement logic. A home-rolled redaction middleware would reasonably alarm a security expert, and I’m not claiming this solves the remote inference privacy problem in a comprehensive sense. But it does give you a principled interception point for simple, well-defined cases. Which is hopefully more than you have without AgentKitten.</p>

<hr />

<h2 id="the-part-that-makes-evaluation-possible-in-agentkitten">The part that makes evaluation possible in AgentKitten</h2>

<p>Every <code class="language-plaintext highlighter-rouge">AgentSession</code> keeps a live <code class="language-plaintext highlighter-rouge">AgentTrace</code>: An append-only structured record of every event. Turn starts/ends, resolved inference config, tool hook firings, compaction events, validation outcomes, errors. Each entry carries a turn identifier (aka invocation id), a monotonic timestamp, and a semantic kind.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">let</span> <span class="nv">entries</span> <span class="o">=</span> <span class="k">await</span> <span class="n">session</span><span class="o">.</span><span class="n">trace</span><span class="o">.</span><span class="nf">snapshot</span><span class="p">()</span>

  <span class="k">for</span> <span class="n">entry</span> <span class="k">in</span> <span class="n">entries</span> <span class="p">{</span>
      <span class="k">switch</span> <span class="n">entry</span><span class="o">.</span><span class="n">kind</span> <span class="p">{</span>
      <span class="k">case</span> <span class="o">.</span><span class="nf">turnStarted</span><span class="p">(</span><span class="k">let</span> <span class="nv">message</span><span class="p">):</span> <span class="c1">// ...</span>
      <span class="k">case</span> <span class="o">.</span><span class="nf">toolHookFired</span><span class="p">(</span><span class="k">let</span> <span class="nv">info</span><span class="p">):</span> <span class="c1">// ...</span>
      <span class="k">case</span> <span class="o">.</span><span class="nf">contextCompaction</span><span class="p">(</span><span class="k">let</span> <span class="nv">info</span><span class="p">):</span> <span class="c1">// ...</span>
      <span class="k">case</span> <span class="o">.</span><span class="nf">turnCompleted</span><span class="p">(</span><span class="k">let</span> <span class="nv">outcome</span><span class="p">):</span> <span class="c1">// ...</span>
      <span class="c1">// ...</span>
      <span class="p">}</span>
  <span class="p">}</span>
</code></pre></div></div>

<p>This is not a log but a structured record with a stable schema across providers, prompt changes, and time. That stability is what should make evaluation possible: compare runs, build test fixtures from real sessions, assert on tool call patterns, track whether a prompt change improved or regressed behavior.</p>

<p>If the scaffold regenerates this concept fresh every time, you can’t do any of that. The abstraction being stable enough to evaluate was the goal from the start, not a feature added later.</p>

<hr />

<h2 id="questions-i-expect">Questions I expect</h2>

<ul>
  <li>
    <p><strong>Isn’t this just a Swift <em>{Insert Famous Framework Name Here}</em> wannabe?</strong></p>

    <p>Yes, roughly. But there is good value on it being natively built for the platform in Swift, and possibly from being rather narrowly scoped.</p>
  </li>
  <li>
    <p><strong>Can’t a coding agent just generate this?</strong></p>

    <p>Yes. But you can’t effectively evaluate something that has a different shape every time you change it.</p>
  </li>
  <li>
    <p><strong>The abstraction leaks.</strong></p>

    <p>Possibly! It just tries to cover common cases cleanly and leave provider specifics for you to reach through with your custom implementation.</p>
  </li>
  <li>
    <p><strong>Shouldn’t the provider own the loop?</strong></p>

    <p>Sometimes. Use the underlying SDK/API if you’re building a single-provider chat feature. This is for when you need what the provider won’t give you, or want to stay nimble to migrate between them.</p>
  </li>
  <li>
    <p><strong>Providers will absorb it all.</strong></p>

    <p>Good! The durable part should be the app-specific control surface they won’t solve.</p>
  </li>
  <li>
    <p><strong>On-device constraints are a temporary phase.</strong></p>

    <p>On-device and frontier are in a permanent chase. I feel the gap shifts, but doesn’t really close.</p>
  </li>
  <li>
    <p><strong>No one’s asking for this.</strong></p>

    <p>Teams are reinventing it silently inside product codebases. Latent, not absent.</p>
  </li>
  <li>
    <p><strong>Show me production usage.</strong></p>

    <p>Not there yet.</p>
  </li>
</ul>

<hr />

<h2 id="where-it-stands">Where it stands</h2>

<p>While I flattened the repository history before making it public, the repository <strong>history of AgentKitten spanned more than 570 commits and 130 pull requests of rethinking the shape of things</strong>.</p>

<p>And, yes, it is built using AI coding tools, but steered carefully and deliberately. The design decisions are mine. The iteration was painfully real: The tool hook design went through at least two architectures. Context compaction was born as an overly-simplistic retrofit, but ended as an automated (and highly customizable) one. And I lost count of how many iterations I went through to get to the current configuration/overrides architecture.</p>

<p>Just being honest, “I iterated on this a lot” is a more accurate description than just “I designed it.”</p>

<p>It’s still early: the test coverage/quality is being improved, the provider surface will grow, and some API edges will shift before a 1.0. I’m very open to input and collaboration.</p>

<p>The repository is at <a href="https://github.com/fbeeper/agentkitten">https://github.com/fbeeper/agentkitten</a>.</p>

<p>Run <code class="language-plaintext highlighter-rouge">swift run Playground --help</code> to see examples of what it can do.</p>

<p>Looking forward for your input!</p>]]></content><author><name>Ferran Poveda</name></author><category term="agentkitten" /><category term="agentkitten" /><category term="AI" /><category term="agent" /><category term="harness" /><summary type="html"><![CDATA[Presenting AgentKitten: a Swift package for building provider-agnostic AI agents on Apple platforms.]]></summary></entry><entry><title type="html">Hello Again!</title><link href="https://www.fbeeper.com/general/2026/02/19/Hello-Again/" rel="alternate" type="text/html" title="Hello Again!" /><published>2026-02-19T00:00:00+00:00</published><updated>2026-02-19T00:00:00+00:00</updated><id>https://www.fbeeper.com/general/2026/02/19/Hello-Again</id><content type="html" xml:base="https://www.fbeeper.com/general/2026/02/19/Hello-Again/"><![CDATA[<p>It’s been a minute. Updating the site, archiving old posts, starting anew. Nothing else for today.</p>]]></content><author><name>Ferran Poveda</name></author><category term="general" /><category term="hello" /><summary type="html"><![CDATA[It's been a minute. Updating the site, archiving all old thoughts, starting anew.]]></summary></entry><entry><title type="html">Our Little Computer History Museum</title><link href="https://www.fbeeper.com/comphist/2020/11/15/Our-Little-Computer-History-Museum-at-Home/" rel="alternate" type="text/html" title="Our Little Computer History Museum" /><published>2020-11-15T12:59:00+00:00</published><updated>2020-11-15T12:59:00+00:00</updated><id>https://www.fbeeper.com/comphist/2020/11/15/Our-Little-Computer-History-Museum-at-Home</id><content type="html" xml:base="https://www.fbeeper.com/comphist/2020/11/15/Our-Little-Computer-History-Museum-at-Home/"><![CDATA[<p>Time flies! It was over ten years ago that I was giving my first Computer History course at my alma mater, the Universitat Autonoma de Barcelona.</p>

<p>Back then, I had (hoarded 🙈) a big box of old computer parts at home. From it, I salvaged a couple of old CPUs and memories to show to my students.</p>

<p>My budget at the time was ridiculously low. But, somehow, I ended up browsing eBay and bought a couple more things that I felt would be interesting to illustrate some concepts in class.</p>

<p>I spent no more than 20 or 30 euros. Still, these few items felt like an exotic treasure. I kept all the chips among bubble wraps, in static shielding bags, protected by hard plastic boxes (actually, old repurposed CD storage boxes,) and tucked away from the elements in an old shoebox.</p>

<p>Fast forward to 2018/19. While I never really increased my budget for it, I had now gathered a large enough collection of memories and CPUs.</p>

<p>My partner, Helena Vilaplana, was super excited about the collection but felt it was a shame to have it hidden in a drawer. So she championed this crafts project to build together a proper display for them.</p>

<div role="figure" aria-labelledby="caption">

<picture>


<img src="/public/img/2020-11-15-CompHist/HomeCompHistCollection.jpg" alt="Picture of a 3 (rows) x 6 (columns) arrangement of shadowbox frames on a white wall. Each shadowbox has a colorful background and a white frame. The background colors of each column are purple, blue, green, yellow, orange, and red. Colors vary in luminosity in each row, ranging from darker to lighter from top to bottom. Inside each shadowbox, one or more devices are suspended, centered in slightly different alignments." />

</picture>

<p id="caption" class="image-caption"><em>Welcome to our first computer history museum at home.<br />(All rights reserved © Ferran Poveda &amp; Helena Vilaplana)</em></p>
</div>

<p>Each one of these shadowboxes was painted by hand, and the devices were affixed in different ways, with materials and processes carefully chosen to both preserve the pieces for posterity and to display them in an artistic and exciting way.</p>]]></content><author><name>Ferran Poveda</name></author><category term="comphist" /><category term="computer history" /><category term="home" /><category term="design" /><summary type="html"><![CDATA[A craft project to artfully display tokens of computer history.]]></summary></entry><entry><title type="html">VoiceOver 102: Common Gestures</title><link href="https://www.fbeeper.com/accessibility/2020/06/18/VoiceOver-102-Common-Gestures/" rel="alternate" type="text/html" title="VoiceOver 102: Common Gestures" /><published>2020-06-18T00:00:00+00:00</published><updated>2020-06-18T00:00:00+00:00</updated><id>https://www.fbeeper.com/accessibility/2020/06/18/VoiceOver-102-Common-Gestures</id><content type="html" xml:base="https://www.fbeeper.com/accessibility/2020/06/18/VoiceOver-102-Common-Gestures/"><![CDATA[<p><a href="/2020/06/11/Accessibility-Audit-VoiceOver-101/">Three basic finger gestures</a> can get us quite far with VoiceOver. But adding a few more things to our tool-belt will help us becoming fluid VoiceOver users and/or auditors.</p>

<p>Here are the tools I would take to a desert island with me:</p>

<h2 id="accessibility-shortcut">Accessibility Shortcut</h2>

<p>On the first article, we activated VoiceOver with Siri. It feels modern, sure. But, to be honest, it is not particularly practical to me.</p>

<p>If we go to “Settings” → “Accessibility” → “Accessibility Shortcut,” we can select “VoiceOver” on that list.</p>

<p>With this setting, <strong>triple tapping</strong> the home or side button (depending on the device) will toggle VoiceOver. So convenient! Right?</p>

<h2 id="continuous-swipe-and-tap">Continuous Swipe (and Tap)</h2>

<p>Sooner or later, one may discover naturally (or accidentally 😅) that a <strong>single tap</strong> on the screen moves the focus to the accessible element touched.</p>

<p>We can actually <strong>continuously swipe</strong> around the UI. VoiceOver’s focus will swiftly follow our touch. And additional <strong>single taps</strong> will activate the element focused under our (uninterrupted) swipe.</p>

<div class="message">
⚠️ When purposefully leveraging vision or spatial memory, these gestures can be very powerful. However, I don't particularly like (or would recommend) to solely rely on these gestures as the main way to use/audit with VoiceOver. Depending on total or partial vision can make us inadvertently use queues that are not clear or available to others.
</div>

<h2 id="scroll">Scroll</h2>

<p>To scroll, we only need the <strong>3 finger swipe</strong>. Up, Down, Left, or Right.</p>

<p>That easy!</p>

<h2 id="two-finger-scrub">Two Finger Scrub</h2>

<p>With the gestures we learned so far, going back on navigation or dismissing a modal may be a hassle.</p>

<p><strong>Swiping a Z shape with two fingers</strong> anywhere on the screen will solve it. This gesture will perform those actions, and it will save us a lot of time. And it will often rescue us when feeling trapped too.</p>

<div class="message">
ℹ️ The direction (or shape) of this Z does not matter! It is only necessary to go back and forth thrice. But, while getting used to VoiceOver, remembering the Zorro can be a fun mnemotechnic technique.
</div>

<p>This gesture may not be supported in custom implementations. But it is so useful that it will be <em>really</em> expected by our users. Let’s not forget adding it to our self-auditing notebook when it does not work!</p>

<h2 id="practice-practice-practice">Practice, practice, practice</h2>

<p>We may be starting to question our memory skills to remember these gestures. And, to the best of my knowledge, there is only one way to become fluent and internalize these gestures: practice.</p>

<p>Just keep swiping!</p>]]></content><author><name>Ferran Poveda</name></author><category term="accessibility" /><category term="accessibility" /><category term="iOS" /><category term="VoiceOver" /><summary type="html"><![CDATA[Becoming a fluid user in VoiceOver is just a few gestures away.]]></summary></entry><entry><title type="html">VoiceOver 101: Basic Use &amp;amp; Audit</title><link href="https://www.fbeeper.com/accessibility/2020/06/11/Accessibility-Audit-VoiceOver-101/" rel="alternate" type="text/html" title="VoiceOver 101: Basic Use &amp;amp; Audit" /><published>2020-06-11T00:00:00+00:00</published><updated>2020-06-11T00:00:00+00:00</updated><id>https://www.fbeeper.com/accessibility/2020/06/11/Accessibility-Audit-VoiceOver-101</id><content type="html" xml:base="https://www.fbeeper.com/accessibility/2020/06/11/Accessibility-Audit-VoiceOver-101/"><![CDATA[<p>In short, VoiceOver is a screen reader provided by Apple in iOS, iPadOS, watchOS, macOS, and tvOS. It allows blind and low vision users to enjoy the software on these platforms.</p>

<p>Let’s use it!</p>

<p>Unlock an iPhone or iPad, open an (your?) app. Now, ask Siri to activate VoiceOver. Immetiatelly, VoiceOver focuses on a UI element on the screen and reads it out loud.</p>

<h2 id="navigation">Navigation</h2>

<p><strong>Swipe from left to right</strong> anywhere on the screen. This should move the focus to the next UI element. Again, VoiceOver reads it.</p>

<picture>

<source srcset="/public/img/2020-06-11-Swipe/DarkL2RSwipe.gif" media="(prefers-color-scheme: dark)" />


<img src="/public/img/2020-06-11-Swipe/LightL2RSwipe.gif" alt="Left to Right Swipe Animation" />

</picture>

<p>Now, <strong>swipe from right to left</strong>. We are back at the first item.</p>

<picture>

<source srcset="/public/img/2020-06-11-Swipe/DarkR2LSwipe.gif" media="(prefers-color-scheme: dark)" />


<img src="/public/img/2020-06-11-Swipe/LightR2LSwipe.gif" alt="Left to Right Swipe Animation" />

</picture>

<h2 id="interaction">Interaction</h2>

<p>Let’s level up. Swipe to a button you’d like to use, and <strong>double-tap</strong> anywhere on the screen. This gesture will activate the focused button.</p>

<picture>

<source srcset="/public/img/2020-06-11-Swipe/Dark1F2Tap.gif" media="(prefers-color-scheme: dark)" />


<img src="/public/img/2020-06-11-Swipe/Light1F2Tap.gif" alt="Left to Right Swipe Animation" />

</picture>

<p>We are almost experts at VoiceOver now. It is magical.</p>

<h2 id="immersion">Immersion</h2>

<p>We could stop here, but my experience tells me to give one extra bit: Without hesitation, let’s do a <strong>triple-finger triple-tap</strong> anywhere on the screen. (It’s as simple as it sounds: tap thrice with three fingers.)</p>

<picture>

<source srcset="/public/img/2020-06-11-Swipe/Dark3F3Tap.gif" media="(prefers-color-scheme: dark)" />


<img src="/public/img/2020-06-11-Swipe/Light3F3Tap.gif" alt="Left to Right Swipe Animation" />

</picture>

<p>Screen Curtain is now active. The screen went dark, but the screen and the app are still very much alive.</p>

<div class="message">
🧐 I would argue this is what we should have been seeing all along. Navigating our app with Screen Curtain will provide us with a very important perspective that we cannot miss.
</div>

<p>If we cannot use the app, it is likely our users won’t be able to use it either.</p>

<h2 id="audit-and-roadmap">Audit and Roadmap</h2>

<p>Using VoiceOver, we can learn some of the most fundamental concepts of accessibility. And we can also start exposing where we need to improve our apps.</p>

<p>With the four (4!) simple gestures in this article I have identified some of the most foundational accessibility issues in iOS apps. I hope you can too.</p>

<p><strong>I look to validate that all UI elements are reachable and properly described.</strong> In other words, I note all the things I cannot navigate consistently, as well as those that are not clearly described with the VoiceOver reading.</p>

<p><em>What is the issue? Where in the app? How important is it? How to reproduce?</em></p>

<p>Answering questions like this certainly helps to lay out a roadmap. While it may look over-simplistic, the results of this self-auditing can be a strong foundation towards making an app accessible.</p>

<p>Want more? The story continues in <a href="/accessibility/2020/06/18/VoiceOver-102-Common-Gestures/">VoiceOver 102</a>.</p>

<div class="message">
✍️ Edited on June 15th, 2020 with minor refinements.<br />
✍️ Edited on Sept 8th, 2020 with minor refinements.
</div>]]></content><author><name>Ferran Poveda</name></author><category term="accessibility" /><category term="accessibility" /><category term="iOS" /><category term="VoiceOver" /><summary type="html"><![CDATA[The most basic audit may be the most powerful one.]]></summary></entry><entry><title type="html">Accessibility &amp;amp; Competing Priorities</title><link href="https://www.fbeeper.com/accessibility/2020/05/25/Accessibility-Competing-Priorities/" rel="alternate" type="text/html" title="Accessibility &amp;amp; Competing Priorities" /><published>2020-05-25T00:00:00+00:00</published><updated>2020-05-25T00:00:00+00:00</updated><id>https://www.fbeeper.com/accessibility/2020/05/25/Accessibility-Competing-Priorities</id><content type="html" xml:base="https://www.fbeeper.com/accessibility/2020/05/25/Accessibility-Competing-Priorities/"><![CDATA[<p>At first, everything in iOS accessibility seemed quite simple to me. But I soon realized it can be difficult to make quick (significant) progress.</p>

<p>Often, there is just so much we can do with the allocated time and resources, and the scope and expectations of a project.</p>

<p>I have always thrived in small teams, and my typical day is composed by many competing priorities: fulfilling high ROI features for the business, fixing technical issues and/or debt, achieving my multiple personal goals, surviving to the existing workflows, creating new ones, documenting, testing, navigating the demands of users, resolving conflicts…</p>

<p>It is not straightforward to make the right choices to keep it all perfectly balanced. Nor one should feel like it must be.</p>

<h2 id="tomorrow-is-too-late-for-accessibility">Tomorrow is too late for accessibility</h2>

<p>When I first introduced accessibility to my tornado of competing priorities, I started to look around me for help. It was not particularly difficult to motivate people given my enthusiasm. But we all had our tornadoes to deal with, and introducing accessibility just made them more powerful.</p>

<p>Now, we all shared a looming feeling of “tomorrow is too late for accessibility.” But we did not know how to fulfill it effectively.</p>

<p>Although it will always be true that there is an urgent need to make our apps fully accessible, the reality is that declaring a problem “looming” can be a toxic way to live and deal with it. We weren’t going to be successful with that approach.</p>

<h2 id="break-it-down">Break it down</h2>

<p>I had to fail multiple times to then realize something that is not rocket science: To make all the accessibility work attainable, I just needed to come to terms with not trying to tackle everything at once.</p>

<p>Some things are not as blocking as I though. I did not need to persuade any stakeholders to drop it all for accessibility. I did not need to have every design and ticket filled with a comprehensive list of accessibility requirements.</p>

<p>There were ways to champion it and make significant headway.</p>

<p>I just needed a roadmap to be successful, and for that, I thought I needed guidance because I wasn’t an expert on accessibility.</p>

<h2 id="self-auditing">Self-auditing</h2>

<p>Thankfully, building a practical roadmap was possible in simpler terms than I initially envisioned.</p>

<p>While there is absolutely nothing ill-judged about expediting the process by recurring to experts, for indy developers, small teams, or even big teams under pressure, there may not be enough resources to do so.</p>

<p>Instead of recurring to external auditing I realized I was able to self-audit my work with the knowledge I had at every given time. I only needed to grow naturally, and try to improve constantly.</p>

<h2 id="external-feedback">External feedback</h2>

<p>Another key point is to seek for external feedback. It may come more slowly, but it usually has solved many of my self-auditing roadblocks and opened new paths to explore.</p>

<p>Big or small, some breakthroughs came to me from:</p>

<ul>
  <li>Taking any opportunity to listen to users who would like to discuss their accessibility experience.</li>
  <li>Talking to members of the iOS + Accessibility community.</li>
  <li>Paying close attention to Apple’s WWDC sessions and other online materials.</li>
  <li>And the accessibility labs and consultations at WWDC. Their first-hand experience is invaluable.</li>
</ul>

<p>Putting all this together, I have built and grown successful roadmaps.</p>

<p>For me, my key takeaways to build the best experience I can offer have been: being more comfortable with unknowns, trust existing knowledge, and keep iterating.</p>

<p>Comments or questions? Comment <a href="https://github.com/fbeeper/fbeeper.github.io/issues/5">here</a> or hit me @fbeeper. More to come on my next article about bridging the technical gaps to accessibility.</p>]]></content><author><name>Ferran Poveda</name></author><category term="accessibility" /><category term="accessibility" /><category term="iOS" /><category term="design" /><summary type="html"><![CDATA[Accessibility is easier to include in your workflow than it looks.]]></summary></entry><entry><title type="html">Global Accessibility Awareness Day</title><link href="https://www.fbeeper.com/accessibility/2020/05/21/Happy-Global-Accessibility-Awareness-Day/" rel="alternate" type="text/html" title="Global Accessibility Awareness Day" /><published>2020-05-21T00:00:00+00:00</published><updated>2020-05-21T00:00:00+00:00</updated><id>https://www.fbeeper.com/accessibility/2020/05/21/Happy-Global-Accessibility-Awareness-Day</id><content type="html" xml:base="https://www.fbeeper.com/accessibility/2020/05/21/Happy-Global-Accessibility-Awareness-Day/"><![CDATA[<p>Happy <a href="https://globalaccessibilityawarenessday.org/">Global Accessibility Awareness Day</a>!</p>

<p>I have been an accessibility advocate for a long time, but most of my conversations have happened in professional contexts and applied to specific projects. This blog was born, among other things, to provide a space for a larger conversation on this subject.</p>

<p>Today feels like a great day to start it.</p>

<p>Accessibility is becoming a trending topic in the iOS community, and that makes me really happy.</p>

<p>But it also adds up some pressure: anything I might want to say has probably already been discussed before.</p>

<p>However, I feel it is less about pure facts and unmovable truths and more about one’s journey. I would like to share with you my continuous process to learn and develop more accessible iOS apps, the roadblocks I found on the way, and some tips to hopefully make your trip a bit easier.</p>

<h2 id="we-all-start-somewhere">We All Start Somewhere</h2>

<p>Sometimes, when passionately defending accessibility (the classic “we need to have more empathy and fewer excuses”), advocates risk making people feel they are less for not having thought or implemented it already.</p>

<p>If something is not accessible in your app, it means there is a critical bug in it. But if that’s your case, don’t let shame paralyze you. We all start somewhere.</p>

<p>In this first post, I would like to focus on how accessibility applies to everyone, and also start breaking down the main obstacles we may find on our way as iOS developers.</p>

<h2 id="accessibility-is-for-all">Accessibility is for all</h2>

<p><a href="https://globalaccessibilityawarenessday.org/">One billion people</a>, about an eighth of the world’s population, receive and experience stimuli from the world in diverse visual, auditive, motor and/or cognitive ways.</p>

<p>Even more, we all have had or will have different accessibility needs throughout our lives, even if only circumstantially or temporarily.</p>

<p>Have you ever had your pupils dilated for an eye exam and tried to use your iPhone to find a ride-share? Do you know a family member who has developed presbyopia with age and needs a bigger font size to read your texts?</p>

<p>Accessibility comes in many shapes and forms. It is not for a few, it is for all of us.</p>

<h2 id="main-obstacles">Main Obstacles</h2>

<p>We are empathetic to people’s diverse needs and want to give everyone equal opportunities when using our apps.</p>

<p>Additionally, Apple has done an outstanding job to provide accessible platforms. Even without explicitly thinking about accessibility, building iOS apps from scratch with system controls allows our apps to be accessible <em>by default</em>.</p>

<p>So… Why isn’t my work perfectly accessible then?!</p>

<p>In my experience, even in the best-case scenario, there are lots of things to consider, including a few hugely underestimated life basics:</p>

<ul>
  <li>We don’t know what we don’t know.</li>
  <li>We always have tons of competing priorities.</li>
  <li>Even the smallest gaps take effort to bridge.</li>
  <li>Life, happens.</li>
</ul>

<p>So don’t feel bad if you haven’t nailed accessibility instinctively. Instead, join me taking steps towards ensuring your code is accessible <em>by default</em> too.</p>

<h2 id="we-dont-know-what-we-dont-know">We don’t know what we don’t know</h2>

<p>One of the main reasons we need to raise awareness about accessibility is because we are often oblivious to the gaps in our knowledge.</p>

<p>Looking back in time, I did not have any obvious need for accessibility features. For a while, I lived building apps being quite unaware of accessibility. Not that I did not care, or I did not have the time. I just simply did not know enough to properly click into this area.</p>

<p><em>Dramatized:</em> Where I had a pretty simple set of layouts and a few fonts, colors, and images, now I have a myriad of interesting concepts to wrap my head around and adapt in my workflow:</p>

<blockquote>
  <p>VoiceOver, Reduce transparency, On/Off labels, Grayscale, Audio descriptions, Word prediction, Mono audio, AssistiveTouch, Button shapes, Speak screen, Gliding cursor speed, Subtitles, Captioning, Closed captions, Switch control, Larger text, Speech, Increase contrast, Invert colors, Cursor color, Dictation, Siri, Bold text, Larger cursor, Zoom, Reduce motion, Hearing aids, Sticky keys, Gestures, Auto scanning, Safari reader, Guided access, Slow Keys, Touch accommodations.</p>

  <p>From WWDC’16 <a href="https://developer.apple.com/videos/play/wwdc2016/407/">Auditing Your Apps for Accessibility</a> <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup></p>
</blockquote>

<p>Thankfully, as a developer, I believe this dichotomy between fascinating and complex defines seme our most interesting challenges.</p>

<p>I will never be able to fully understand or represent people with consequential accessibility needs that are different than mine. But I stopped feeling bad for being late. Instead, I focused on doing my best to include everyone. I work hard to make my apps as accessible as possible and, honestly, it’s become one of my favorite parts of this work.</p>

<p>Want to share your experience or are looking for guidance? Let’s chat <a href="https://github.com/fbeeper/fbeeper.github.io/issues/4">here</a> or reach me @fbeeper.</p>

<p>See you in my <a href="/accessibility/2020/05/25/Accessibility-Competing-Priorities/">next article on accessibility &amp; competing priorities</a>!</p>

<hr />

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Actually, today there are many more concepts that didn’t exist or weren’t represented in that 2016 slide, like Display Accommodations, Voice Control, Dynamic Type, Zoom, Live Listen, and probably even more. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Ferran Poveda</name></author><category term="accessibility" /><category term="accessibility" /><category term="iOS" /><category term="design" /><summary type="html"><![CDATA[Starting a new journey by sharing my experience on accessibility.]]></summary></entry><entry><title type="html">Dark Mode Bauhaus</title><link href="https://www.fbeeper.com/design/2020/05/17/Dark-Mode-Bauhaus/" rel="alternate" type="text/html" title="Dark Mode Bauhaus" /><published>2020-05-17T00:00:00+00:00</published><updated>2020-05-17T00:00:00+00:00</updated><id>https://www.fbeeper.com/design/2020/05/17/Dark-Mode-Bauhaus</id><content type="html" xml:base="https://www.fbeeper.com/design/2020/05/17/Dark-Mode-Bauhaus/"><![CDATA[<p>When building this site I went for a minimalistic design. Inspired by Medium, an ornamented serif font helps make it feel less technical. The otherwise plain design keeps it modern. And to make it more personal, I added some bright colored geometrical decorations.</p>

<p>They are a homage to the hallmark of Bauhaus design:</p>

<p>Without getting into detail, Wassily Kandinsky, who taught at the Bauhaus, studied shapes and color and theorized about their properties and relationships. Each color and shape was associated with some psychological properties and got inherently connected. From <a href="https://www.documenta-bauhaus.de/images/docA_BI_7KandinskyW12015_132_04.jpg?w=1242">these studies</a> arose the well known yellow triangle, blue circle, and red square of the Bauhaus. <a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3769683/">Modern studies</a> deem Kandinsky’s findings not too accurate. Still, having <a href="https://www.getty.edu/research/exhibitions_events/exhibitions/bauhaus/new_artist/form_color/interactive/">tested</a> myself, my choices coincided with Kandinsky’s model. It does not make it correct, but it is serendipitous.</p>

<p>I feel this Bauhaus choice sharply (but beautifully) contrasts with the serif font that is <em>not</em> associated <em>at all</em> with the Bauhaus design. After all, are living an era of remixes.</p>

<h2 id="accessible-color">Accessible Color</h2>

<p>Now, combining my initial color picks with the existing text was not going to be a recipe for success. I cared about providing solid readability in <em>every</em> scenario. So, I had to ensure proper color contrast of the text over the graphics.</p>

<div role="figure" aria-labelledby="caption">

<picture>


<img src="/public/img/2020-05-17-First-Color-Choice.png" srcset="/public/img/2020-05-17-First-Color-Choice.png 1x, /public/img/2020-05-17-First-Color-Choice@2x.png 2x" alt="A bright yellow triangle, a bright blue circle, and a bright red circle. Illegible dark grey text &ldquo;fbeeper&rdquo; on top." title="Sample of my initial out-of-context color choices. The text is not easy to read." />

</picture>

<p id="caption" class="image-caption"><em>Sample of my initial out-of-context color choices. <br />The text is not easy to read.</em></p>
</div>

<p>From here, the closest colors that would satisfy legible contrast ratios did not work for me. So I kept tuning them until they did. That is, I liked the individual colors, they worked together, they had a 4.5+ contrast ratio with my font color, and they looked good on the site.</p>

<div role="figure" aria-labelledby="caption">

<picture>


<img src="/public/img/2020-05-17-Second-Color-Choice.png" srcset="/public/img/2020-05-17-Second-Color-Choice.png 1x, /public/img/2020-05-17-Second-Color-Choice@2x.png 2x" alt="A light yellow triangle, a light blue circle, and a light red circle. Legible dark grey text &ldquo;fbeeper&rdquo; on top." title="My light mode color choices (with proper contrast.)" />

</picture>

<p id="caption" class="image-caption"><em>My light mode color choices (with proper contrast.)</em></p>
</div>

<h2 id="dark-mode">Dark Mode</h2>

<p>To support dark mode, I had to play a game with more variables. The colors for the light background did not match the new dark environment even when having a theoretically correct contrast. Dark mode colors need a different luminosity to be easy on the eye:</p>

<div role="figure" aria-labelledby="caption">

<picture>


<img src="/public/img/2020-05-17-Third-Color-Choice.png" srcset="/public/img/2020-05-17-Third-Color-Choice.png 1x, /public/img/2020-05-17-Third-Color-Choice@2x.png 2x" alt="Dark mode shapes and colors: A dark yellow triangle, a dark blue circle, and a dark red circle. Legible white text &ldquo;fbeeper&rdquo; on top." title="My dark mode color choices." />

</picture>

<p id="caption" class="image-caption"><em>My dark mode color choices.</em></p>
</div>

<p>None of this is novel or mindblowing, but I felt it was a short and sweet sample to share what is behind the scenes when designing for accessibility and dark mode.</p>

<p>–</p>

<div class="message">
✍️ Edited on Sunday, Nov 15, 2020: Shortened article.
</div>]]></content><author><name>Ferran Poveda</name></author><category term="design" /><category term="design" /><category term="dark mode" /><summary type="html"><![CDATA[A little story about my Bauhaus-inspired decorations in Dark Mode.]]></summary></entry><entry><title type="html">Build a blog with Jekyll</title><link href="https://www.fbeeper.com/development/2020/05/07/Build-a-blog-with-Jekyll/" rel="alternate" type="text/html" title="Build a blog with Jekyll" /><published>2020-05-07T00:00:00+00:00</published><updated>2020-05-07T00:00:00+00:00</updated><id>https://www.fbeeper.com/development/2020/05/07/Build-a-blog-with-Jekyll</id><content type="html" xml:base="https://www.fbeeper.com/development/2020/05/07/Build-a-blog-with-Jekyll/"><![CDATA[<p>My goal was to build a fast and simple site. I wanted to offer good user experience and privacy. I was looking to move away from the controversial World Wide Web of 2020 (<a href="https://medium.com/@imho_ios">my old Medium blog</a><em>*cough*</em>).</p>

<p>A clean, static site offers me the opportunity to control all these things, checks all the boxes to be trendy, and pushes me to step out of my Swift and iOS comfort zone.</p>

<p>I would like to start this blog by giving explicit credit to all the sources that helped me create it.</p>

<p>The main building blocks include:</p>

<ul>
  <li><a href="https://jekyllrb.com">Jekyll</a> as the static site generator behind the scenes.</li>
  <li><a href="https://pages.github.com">GitHub Pages</a>, providing much more than hosting.</li>
  <li>And <a href="https://hyde.getpoole.com">Hyde</a> offered a solid foundation theme to build on.</li>
</ul>

<p>But many other tools, snippets, and docs helped me shape the site. Here is a quick reference:</p>

<ul>
  <li>Jekyll has some wonderful plugins (<a href="https://pages.github.com/versions/">supported</a> by GitHub Pages:)
    <ul>
      <li><a href="https://github.com/jekyll/jekyll-sitemap">jekyll-sitemap</a>, to improve SEO and accessibility with a site map.</li>
      <li><a href="https://github.com/jekyll/jekyll-seo-tag">jekyll-seo-tag</a>, to generate SEO metadata.</li>
      <li><a href="https://github.com/jekyll/jekyll-feed">jekyll-feed</a>, to provide a feed for Atom readers.</li>
      <li><a href="https://github.com/jekyll/jekyll-mentions">jekyll-mentions</a>, to simplify @ mentions.</li>
      <li><a href="https://github.com/jekyll/jekyll-gist">jekyll-gist</a>, to make Github Gist embeds as simple as they can be.</li>
    </ul>
  </li>
  <li>I also added other custom add-ons, thanks to:
    <ul>
      <li><a href="https://brianbunke.com/blog/2017/09/06/twitter-cards-on-jekyll">Twitter Cards on Jekyll</a>, by Brian Bunke + <a href="https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/summary">Twitter’s reference</a>.</li>
      <li><a href="https://carlosbecker.com/posts/jekyll-reading-time-without-plugins">Reading Time without Plugins</a>, by Calos Becker.</li>
      <li><a href="https://github.com/allejo/jekyll-anchor-headings">Jekyll Heading Anchors</a>, a great solution without plugins or JS.</li>
      <li>Plain link to share posts with <a href="https://developer.twitter.com/en/docs/twitter-for-websites/tweet-button/overview">Tweet Button</a>.</li>
      <li>Simple image captions following <a href="https://stackoverflow.com/a/43045614">this advice</a>.</li>
    </ul>
  </li>
  <li>The <a href="https://shopify.github.io/liquid/">Liquid</a> docs were excellent to help me get used to this template language used by Jekyll.</li>
  <li>And, last but not least, <a href="https://www.goatcounter.com">Goat Counter</a>, is powering simple and respectful analytics. See <a href="/licensing/#privacy">Privacy</a> for more info.</li>
</ul>

<p>Shifting gears to design and accessibility, these were highly valuable:</p>

<ul>
  <li>I got actionable feedback to improve accessibility from:
    <ul>
      <li><a href="https://developers.google.com/web/tools/lighthouse">Lighthouse</a> in Google Chrome.</li>
      <li><a href="https://wave.webaim.org">WAVE Web Accessibility Evaluation Tool</a>.</li>
      <li><a href="https://www.adascan.com/">ADAScan</a>.</li>
    </ul>
  </li>
  <li><a href="https://www.w3.org/TR/wai-aria-1.1/">W3C’s WAI-ARIA</a> was a useful reference to keep around.</li>
  <li><a href="https://a11yproject.com/posts/never-use-maximum-scale/">The A11Y Project</a> has some enlightening articles (at least for someone new to web accessibility.)</li>
  <li><a href="https://app.contrast-finder.org/">Contrast Finder</a> helped me build a proper color scheme.</li>
  <li><a href="https://github.com/adobe-fonts/source-serif-pro">Source Serif Pro</a> and <a href="https://github.com/adobe-fonts/source-code-pro">Source Code Pro</a> by Adobe (under the <a href="https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&amp;id=OFL">Open Font License</a>) found in and provided by <a href="https://fonts.google.com">Google Fonts</a>.</li>
  <li><a href="https://fontawesome.com">Font Awesome</a> supplies the icons all around.</li>
  <li>Syntax highlighting is heavily inspired by <a href="https://developer.apple.com/xcode/">Xcode 11</a>, and diff highlighting from <a href="https://github.com/desktop/desktop/issues/5027">Github</a>.</li>
  <li>I used <a href="https://css-tricks.com/the-shapes-of-css/">The Shapes of CSS</a> on CSS-Tricks for my decorative graphics.</li>
  <li><a href="https://developer.apple.com/videos/play/wwdc2019/511/">Supporting Dark Mode in Your Web Content</a> from Apple’s WWDC, showed me how to support dark mode and also exposed me to CSS variables.</li>
</ul>

<p>I may come back with more details in the future, so… stay tuned!</p>]]></content><author><name>Ferran Poveda</name></author><category term="development" /><category term="jekyll" /><category term="github" /><category term="web" /><summary type="html"><![CDATA[What made this site possible?]]></summary></entry></feed>