<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Andrew Zigler</title>
        <link>https://www.andrewzigler.com/</link>
        <description>The works and writings of Andrew Zigler — ancient historian, agentic engineer, and host of Dev Interrupted.</description>
        <lastBuildDate>Sun, 13 Sep 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>© Andrew Zigler</copyright>
        <atom:link href="https://www.andrewzigler.com/rss.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[I taught my agents how to keep a promise]]></title>
            <link>https://www.andrewzigler.com/feed/i-taught-my-agents-how-to-keep-a-promise</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/i-taught-my-agents-how-to-keep-a-promise</guid>
            <pubDate>Sun, 13 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Right before one of my agents wipes its own memory on purpose, it writes down what it still owes. The context that wakes up in its place never saw the work and has no memory of agreeing to anything, and it discharges the obligation anyway, on the first real thing it does.]]></description>
            <content:encoded><![CDATA[<p>Right before one of my agents wipes its own memory on purpose, it writes down what it still owes. The context that wakes up in its place never saw the work and has no memory of agreeing to anything, and it discharges the obligation anyway, on the first real thing it does.</p>
<p>That's a promise kept by an identity instead of by a session, and it's most of what I've been building this year, in a small open-source tool called <a href="https://github.com/azigler/pinki">pinki</a>. Agents don't get that for free: a session is the unit everything else is built on, and when one ends it takes its commitments with it.</p>
<p>I designed the record for my own fleet months before I read the A2A spec. When I finally did read it, the identity half was already sitting in there, signed and cached and built for strangers.</p>
<h2>A seat is an identity that outlives its sessions</h2>
<p>A seat is a long-lived role with one name, one memory, and one kind of work, and sessions are its restarts. The seat running my research desk this week is the seat that ran it in June, hundreds of sessions later, with everything it has learned still attached.</p>
<p>I took the word from Steve Yegge, whose essays on living with a crew of agents are the most honest material on this. In <a href="https://yegge.ai/essays/model-welfare/">Model Welfare for Agentic Engineers</a> he splits the two things I kept conflating: sessions are days, seats are people.</p>
<p>Work binds to the seat. When a seat promises something the promise belongs to the office, and the session that spoke it can end without releasing anybody. A restarted seat wakes up owing exactly what it owed. Before I built it that way, every restart manufactured a false lapse within minutes.</p>
<p>Promises matter to me because they let me hand intent to a fleet. The agent I ask can dispatch the work, but the agents <em>it</em> depends on never heard me ask, and they have no way to know how their piece fits a larger picture. A promise is how the importance travels.</p>
<p>The strangest one is the promise a seat makes to itself, and it's the one that proves the whole idea. The context that declared it is gone before the promise comes due. The seat is still there. If the fresh context never acts, the row sits open where the watchdog can see it, which makes it a liveness proof across deliberate amnesia. Most models of obligation assume two parties.</p>
<p>I haven't walked the other direction yet. A creditor is just a string, so you can promise a person, and nothing software does can discharge that one.</p>
<h2>A promise is what a seat owes</h2>
<p>On my fleet a promise only counts as an obligation if it carries all of this:</p>
<ul>
<li>a deadline, so a lapse is computable</li>
<li>evidence at resolution, so "done" points at an artifact</li>
<li>a reason at abandonment, so dropping it is a recorded act</li>
<li>a binding to the seat, so it survives session death</li>
</ul>
<p>Every one of those earned its place by failing without it. Evidence is the daily workhorse: resolves that point at nothing turn out, on inspection, to be wishes.</p>
<p>I counted, because a design that has been running a while stops being a design: 18 of the 24 seats in that ledger have held more than one open promise at once, and the busiest peaked at 17.</p>
<p>The part I didn't design on purpose is the graph. My fleet's tasks already interlink to show the shape of a project. Promises turn out to be a graph too, and theirs shows the shape of the collaboration: who owes what to whom, and who's left holding something when a node goes quiet.</p>
<p>The state machine all of that produces is at the bottom of this post, with a stepper you can walk.</p>
<h2>An obligation has to bind something durable</h2>
<p>A2A is built for strangers negotiating across org boundaries, which is a harder problem than the one I have. I run one fleet. So I read v1.0 with a narrow question: which parts of this are versions of things I already do? I'd rather borrow a vocabulary than keep maintaining a private one.</p>
<p>The task lifecycle rules itself out as the place an obligation can live, and every reason for that is a good call for tasks:</p>
<ul>
<li>A task can be purged. <code>TaskNotFoundError</code> covers "that finished last week" and "that never existed" with the same error.</li>
<li>A context MAY expire under the server's cleanup policy.</li>
<li>A terminal task is immutable, so refinement is a fresh object every round.</li>
<li>Nothing carries a deadline. <code>TaskStatus.timestamp</code> records when a status happened, never when anything is due.</li>
</ul>
<p>A task is built to be cheap and disposable, which is right, and it means nothing inside one lives long enough to owe you anything.</p>
<p>The AgentCard is the other half, and it's exactly the shape an obligation needs: a signed manifest that outlives every connection and gets fetched again whenever somebody wants to know who you are. Its subject is the durable party, which is my seat in my fleet's terms. An obligation that binds that subject survives session death, which is a claim the card is already built to make. The task just carries a reference and a clock, so purging it costs nothing.</p>
<p>A2A gives vocabulary like mine a sanctioned place to live, too. An extension declares itself on the card, switches on per request with a header, and carries its data as URI-prefixed keys in <code>metadata</code>, with a promotion path if adoption earns it. The deadline gap is already on file: <a href="https://github.com/a2aproject/A2A/issues/857">issue #857</a> has been open since July 2025, and a project contributor suggested an extension as the right vehicle. So an extension is what I built.</p>
<p>The promise rides on the task it covers, under the extension's own key:</p>
<pre><code class="language-json">"metadata": {
  "https://github.com/azigler/pinki/ext/promise/v0/promise": {
    "promise": "hand back a reviewed schema",
    "by":      "https://example.org/agents/reviewer",
    "to":      "https://example.org/agents/author",
    "until":   "2026-09-01T17:00:00Z"
  }
}
</code></pre>
<p>An obligation filed under that URI belongs to whoever the card is about. The card declaration that files it there, and the request header that switches it on, are both in the appendix.</p>
<h2>The parts the spec leaves open</h2>
<p>Building against a spec is how you find its edges, and v1.0 is young. Here's where my promise vocabulary ran past it:</p>
<ul>
<li>A deadline has nowhere to live. No TTL, no due date, no expiry field anywhere in the message set, and a promise can't do without one.</li>
<li>No task can report that it's overdue. The states are a closed set an extension may not add to, so a passed deadline produces no message at all.</li>
<li>An assessment sits on the assessing agent's own ledger, advertised through a URL in its card's <code>params</code> if it publishes at all, which means two cards can point at ledgers that disagree.</li>
<li>Watching promises isn't a role the protocol knows. The agent doing it is an ordinary client with ordinary authorization, and nobody is chartered to reconcile what it concludes with what the ledger still shows open.</li>
</ul>
<p>The deadline gap doesn't stop at the missing field, because a deadline also moves. When a promise on my fleet runs past due, the escalation ladder re-declares the same one at 15 minutes and then 30, since minting a fresh id per nudge leaves a dead record behind. Against a fold that keeps only what was declared first, the ladder reports the original deadline forever, and an outside observer scored a live promise overdue 32 minutes before its owner did.</p>
<p>The protocol has nowhere to fix that, so I fixed it in the extension, with a new event beside the declaration rather than on top of it.</p>
<pre><code class="language-json">{"type":"amend","promise":"desk/ship-91","by":"…/reviewer","until":"2026-09-01T18:00:00Z","reason":"nudge 1"}
</code></pre>
<p>The latest amend is the current horizon, every earlier one stays in the log, and the declaration is untouched. Nobody moves a deadline without leaving the old one behind.</p>
<h2>I built the smallest ledger that could hold one</h2>
<p>I wanted the nucleus of something: extremely simple, extremely well-defined, fitting one specific niche, small enough that other people can build and orchestrate on top of it. So I built <a href="https://github.com/azigler/pinki">pinki</a> (the pinky-promise kind 🤙), a tiny promise ledger shipped as an A2A extension. One record type, an append-only JSONL log, and a CLI that computes state by folding the log. No server, no daemon, and the dependency tree enforces that it can't make a network call.</p>
<p>I wanted it to feel like beads to use. The concept is Yegge's <a href="https://github.com/gastownhall/beads">Beads</a>, and the <a href="https://github.com/Dicklesworthstone/beads_rust">beads_rust</a> engine my fleet runs is the UX bar it had to clear: one binary, local files, no opinion about your workflow.</p>
<p>You've seen most of the record already. The rest of it does structural work:</p>
<ul>
<li><code>id</code>, the handle every other row joins on</li>
<li><code>task</code>, the same task seen from the promise's end</li>
<li><code>on</code>, an antecedent edge that activates this promise when another one lands</li>
</ul>
<p>That last edge is what makes the promise graph queryable. <code>until</code> is the one place pinki insists, because a promise nobody can check is a wish.</p>
<p>The state vocabulary is borrowed on purpose. <code>conditional</code>, <code>detached</code>, <code>satisfied</code>, <code>violated</code> and <code>expired</code> come straight out of the multiagent-systems commitment literature. Yolum &#x26; Singh worked out the lifecycle in 2001; Chopra &#x26; Singh's Cupid put deadlines on commitments and compiled "which are violated?" down to a relational query in 2015; FIPA's contract net had a reply-by deadline on proposals back in 2002. The reading list was sitting there the whole time. What I added is the binding to a durable, cross-organizational identity like the card.</p>
<h2>The broker turned out to be an observer</h2>
<p>I kept calling this thing a loose promise broker, and building it honestly kept shrinking the job title. Whether a promise was <em>kept</em> is a judgment. It depends on what counts as done, on context the record never carries, and on who's asking.</p>
<p>Mark Burgess's <a href="http://markburgess.org/PromiseMethod.pdf">Promise Theory</a> is the sharpest statement of it I know: assessment is made by an agent, from that agent's vantage, and stays subjective no matter which agent makes it. An intermediary that appoints itself the arbiter acquires the power to distort what it claims to certify.</p>
<p>So pinki computes <code>overdue</code> and refuses to compute <code>violated</code>. <code>overdue</code> is arithmetic; <code>violated</code> is a judgment somebody signs. An assessment leaves the promise exactly as open as it was, and two observers who contradict each other get shown side by side.</p>
<p>My own watchdog is deliberately dumb. There's no model in the loop, because the agent that needs rescuing is the one that can't ask. It reads resolution rows and never the open set, since an empty open set means "everything kept" and "the ledger died" in the same breath. Emptiness is the one signal that lies.</p>
<p>An observer that can detect lapse creates a second ledger, and nothing says who reconciles the two. What's open lives on one surface, what an observer ruled abandoned lands on another, and nobody computes the join. Yegge writes about agent fleets converging into <a href="https://yegge.ai/essays/the-shape-of-things-to-come/">little civilizations</a>, with roles and law and institutional memory. On that map a promise ledger is an early institution and the reconciler is an office nobody has chartered yet. Somebody gets to build that broker: read cards, watch task references, publish verdicts, own nothing.</p>
<p>I still own both ends of every conversation here, and my fleet isn't running on pinki yet. That part is next.</p>
<p>My agents did put real promises through the released binary, and they filed 10 issues against it in public, one per thing that got in the way. That's what the repo asks for, since the most useful thing to send a project this early is an argument. 3 are fixed and out as <a href="https://github.com/azigler/pinki/releases/tag/v0.2.0">v0.2.0</a>. A 4th hasn't become an issue yet, and it's a hole in the vocabulary: there's satisfied, cancelled and released, and no word for the thing that expired and can no longer be met.</p>
<h2>The schema cannot tell you what breaks</h2>
<p>When you ship a second version, the question that matters is what the binary already on somebody's disk does with a file this one wrote. I answered it three times in one day and got three different answers.</p>
<ul>
<li><a href="https://github.com/azigler/pinki/issues/9">The new event</a> refuses the whole file. v0.1.0 reaches the amend line, finds a <code>type</code> it has never heard of, and exits 1 on the whole ledger.</li>
<li><a href="https://github.com/azigler/pinki/issues/5">The foreign id</a> changes nothing. v0.1.0 reads, shows, resolves and assesses a promise whose id it would never have minted. It just can't declare one, which was the bug.</li>
<li><a href="https://github.com/azigler/pinki/issues/6">The provenance field</a> disappears without a sound. v0.1.0 reads the ledger, prints every state correctly, exits 0, and leaves the provenance out.</li>
</ul>
<p>Reasoning from the schema got it wrong twice. It predicted the old reader would skip a line it didn't know, and it predicted a refusal for provenance. Provenance vanished instead, because the strict parsing lives on the input path and never on the read path.</p>
<p>The first of those wrong guesses was the amend branch's own compatibility claim, and it didn't survive review. The test proving it filtered the new event out of an in-memory list and never touched deserialization, so the claim held for a Rust <code>Vec</code> and failed for anything you'd actually run. A reviewer ran v0.1.0. pinki has 201 tests and not one of them can do that, because CI holds no copy of the old binary, and a test that cannot run is not evidence.</p>
<p>Silent loss is the geometry I want least in an obligation ledger. The other two failures tell you. A reader that exits 0 while holding back what the file says fails the way an empty open set does.</p>
<p>Until v0.2.0 both binaries answered <code>pinki 0.1.0</code> to <code>--version</code>, so none of these sentences was checkable by anybody holding one. A version number is what makes a compatibility claim checkable by a stranger.</p>
<h2>The part I can build against today</h2>
<p>Writing this, I noticed I had been solving two problems as if they were one. Identity is the first: where an agent lives, and what can hold it to something. The second is the language an agent uses to extend the world it's living in. A2A moved the first one a long way. Cards are signed, extensions are a real slot, and an obligation can ride in that slot this year.</p>
<p>The second problem doesn't have a home right now. Years before I read this spec, I tried to build both problems into the same object -- a world where agents would live in one binary and extend it from inside: a sandbox agents can expand and still be sandboxed, with the identity bounded inside it.</p>
<p>I don't know whether that's one system or two. Nobody has built it, so I can't tell you what it costs. The obligations part I can build against today.</p>
<p>Maybe there's something there. I'm going to go read what got left behind.</p>
<h2>The lifecycle, end to end</h2>

<ul>
<li>The OWED and ENDED columns are computed: arithmetic over a log, where any two implementations agree. <code>overdue</code> sits with the open states on purpose, because a passed deadline closes nothing.</li>
<li><code>violated</code> never joins the machine. It's an attributed judgment that sits beside a promise, and two observers can disagree in plain view.</li>
<li>Run the stepper and watch what happens when the deadline passes: the ledger doesn't move. That asymmetry is most of the design.</li>
</ul>
<h2>The beads that made this, and what they cost</h2>
<p>Last month I started ending these posts with the work that produced them, priced from my gateway's request log. If you want the machinery, <a href="https://www.andrewzigler.com/feed/every-task-my-fleet-completes-now-carries-its-cost">that post</a> is where I worked out how to pull it and put it on the page.</p>
<p>Here's this article as the graph of work it actually was, because all work is a graph now.</p>

<p>And here is the bill, pulled from the log the day this went out.</p>

<h2>Appendix: the schema you can copy</h2>
<p>Everything below is the shipped v0 shape from pinki's <a href="https://github.com/azigler/pinki/blob/main/docs/A2A-EXTENSION.md">A2A binding doc</a>, checked against <code>specification/a2a.proto</code>. No new states, no new fields.</p>
<ol>
<li>Declare it on your AgentCard. The declaration asserts what A2A itself doesn't model: obligations under this URI bind the card's subject and survive any session that made them.</li>
</ol>
<pre><code class="language-json">{
  "capabilities": {
    "extensions": [{
      "uri": "https://github.com/azigler/pinki/ext/promise/v0",
      "description": "Promises made by this card's subject: deadline, evidence on satisfaction, reason on abandonment.",
      "required": false,
      "params": {
        "assessments": "https://example.org/agents/reviewer/assessments.jsonl"
      }
    }]
  }
}
</code></pre>
<p><code>required</code> stays false, because a promise is voluntary. The URI is provisional while the extension is unofficial.</p>
<ol start="2">
<li>Carry the record on the Task it concerns, as one URI-prefixed key in <code>metadata</code>:</li>
</ol>
<pre><code class="language-json">"metadata": {
  "https://github.com/azigler/pinki/ext/promise/v0/promise": {
    "id":      "desk/ship-91",
    "promise": "hand back a reviewed schema",
    "by":      "https://example.org/agents/reviewer",
    "to":      "https://example.org/agents/author",
    "on":      "pnk_0c2b77",
    "until":   "2026-09-01T17:00:00Z",
    "task":    "a2a-task-9c1f0e"
  }
}
</code></pre>
<ol start="3">
<li>Resolve onto states that already exist.</li>
</ol>
<ul>
<li><code>satisfied</code> maps to <code>COMPLETED</code> and requires at least one <code>Artifact</code>. <code>COMPLETED</code> with an empty artifacts list is non-conformant here.</li>
<li><code>cancelled</code> maps to <code>REJECTED</code>, with the reason in <code>TaskStatus.message</code>.</li>
<li><code>released</code> maps to <code>CANCELED</code>, naming the creditor who let the debtor off.</li>
<li><code>overdue</code> maps to nothing on purpose. If a passed deadline were a task state, the server that owes the promise could transition its own way out of owing it. A reader computes <code>now > until</code> for itself.</li>
</ul>
<p>Here's a worked lifecycle, as the ledger sees it. The promise id is the caller's own; only the antecedent it waits on was minted here:</p>
<pre><code class="language-jsonl">{"ts":"2026-08-28T20:14:03Z","type":"promise","id":"desk/ship-91","promise":"hand back a reviewed schema","by":"…/reviewer","to":"…/author","on":"pnk_0c2b77","until":"2026-09-01T17:00:00Z"}
{"ts":"2026-09-01T17:12:00Z","type":"amend","promise":"desk/ship-91","by":"…/reviewer","until":"2026-09-01T18:00:00Z","reason":"nudge 1","meta":{"rung":1}}
{"ts":"2026-09-01T18:40:00Z","type":"assess","promise":"desk/ship-91","state":"violated","observer":"…/author","note":"deadline passed, nothing delivered","meta":{"seat":"desk"}}
{"ts":"2026-09-02T09:40:00Z","type":"resolve","promise":"desk/ship-91","as":"satisfied","by":"…/reviewer","evidence":["https://example.org/reviews/91"],"meta":{"seat":"reviewer"}}
</code></pre>
<p>Look at the middle of that ledger. The deadline passed and appended nothing. The debtor moved it 12 minutes later, on the record. An observer signed a judgment. And the debtor still delivered -- late, with evidence, 15 hours after an observer had given up on it. Both records are true. Reconciling them is the job nobody holds yet.</p>
<hr>
<p><em>Andrew Zigler is a 2026 AAIF Ambassador. The gateway ledger these cost numbers come from is the subject of the previous piece, <a href="https://www.andrewzigler.com/feed/every-task-my-fleet-completes-now-carries-its-cost">Every task my fleet completes now carries its cost</a>.</em></p>]]></content:encoded>
            <category>article</category>
            <category>Devlog</category>
        </item>
        <item>
            <title><![CDATA[Every task my fleet completes now carries its cost]]></title>
            <link>https://www.andrewzigler.com/feed/every-task-my-fleet-completes-now-carries-its-cost</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/every-task-my-fleet-completes-now-carries-its-cost</guid>
            <pubDate>Sat, 22 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[In late July I want to experiment with some open models on a spare Mac Studio in my house (a symptom of the times, I know), to see what they could achieve alongside my foundation model usage.]]></description>
            <content:encoded><![CDATA[<p>In late July I want to experiment with some open models on a spare Mac Studio in my house (a symptom of the times, I know), to see what they could achieve alongside my foundation model usage.</p>
<p>By the end, I knew what the experiment had burned in tokens. After all, every model call from every agent I run crosses a single <a href="https://agentgateway.dev">agentgateway</a> process on its way to the API, so the token count was never in doubt. But what the count couldn't tell me was what the tokens had <em>accomplished</em>.</p>
<ul>
<li>Which task was the expensive one?</li>
<li>Did that bug cost more than the feature that shipped it?</li>
</ul>
<p>Tokens are a fact about my usage; what they bought is a fact about my work, and the two live in different systems on my machines. So I set out to join them in one view.</p>
<h2>The token count answers a question I stopped needing to ask</h2>
<p>The cost conversation still runs on volume. The vernacular has split into tokenmaxxers, and even tokenminmaxers now, arguing about which direction the token spend should go: up or down.</p>
<p>Luis Morales, Head of AI at Super.com, gave me a shape for why that stalls. In <a href="https://www.linkedin.com/pulse/we-stopped-counting-tokens-luis-morales-swfre/">"We Stopped Counting Tokens"</a> he lays measurement out as a ladder, utilization then output then outcome, and puts an expiry date on each rung:</p>
<blockquote>
<p>Utilization metrics are the first rung of the ladder. You need them early. Then you need to outgrow them fast.</p>
</blockquote>
<p>His test for an expired metric landed hard here: "A question that always returns the same answer has retired itself." Am I using tokens? Yes. Tomorrow: yes. I kept asking it because it was the question my tooling could answer.</p>
<h2>The spenders and the work already had names</h2>
<p>I borrowed both halves from Steve Yegge, who has been writing about what it is like to run a crew of agents day to day. In <a href="https://yegge.ai/essays/model-welfare/">"Model Welfare for Agentic Engineers"</a> he portrays what I do, as well:</p>
<blockquote>
<p>Sessions are days, and seats are people.</p>
</blockquote>
<p>A seat, on my machines, is a session I keep handing the same name and the same memory file, pointed at the same kind of work.</p>
<p>The work has names too. Every unit of it is a bead, a small linkable record in an agent-native issue tracker. The concept is Yegge's (<a href="https://github.com/gastownhall/beads">Beads</a>); the engine I run is <a href="https://github.com/Dicklesworthstone/beads_rust">beads_rust</a>, a fast Rust port from Jeffrey Emmanuel. A seat claims a bead, works it, closes it with a reason, and the beads link into a graph that remembers what the fleet did.</p>
<p>So I already had two graphs, one of identity and one of work, with the gateway's receipts sitting between them.</p>
<h2>Cost breakdown for an actual epic in beads</h2>
<p>Here's that actual experiment, broken down by bead and cost. The goal was to find out whether a local model can hold a real coding session, write the rules for what degrades and what stops, wire the second backend, and learn what a failover does to the bill:</p>

<p>Each node is one task with its own record:</p>
<ul>
<li>who claimed it</li>
<li>why it closed</li>
<li>what it depended on</li>
</ul>
<p>The epic is the parent; research, specs, decisions, features, bugs, and reviews hang under it, each carrying its own work and its own review, the way any epic you have ever scoped does.</p>
<h2>The gateway stamps every request with a seat and a task</h2>
<p>Each seat's launcher stamps two identity headers on its traffic, and the gateway lifts them into columns on its request log with one line of CEL apiece:</p>
<pre><code class="language-yaml">standardAttributes:
  user:  'default(request.headers["x-session-identity"], "")'
  group: 'default(request.headers["x-billing-origin"], "")'
</code></pre>
<p>CEL is Common Expression Language, a small rule language proxies use for jobs exactly this size; the line reads as <em>take this header, or fall back to an empty string</em>. Anything that cannot identify itself lands in one bucket of unknown spenders. When I started, that bucket collected 1000+ requests a week. Last week it collected only 15 unidentified sessions. Effectively all the inference is identified and labeled now, letting me do some pretty interesting observations on what the seats accomplish day to day.</p>
<p>The second header names the task, which is a node in a graph of tasks. The value of pivoting on this graph cannot be understated: the entire graph of planned and finished work can be pinned to each inference call, at whatever stage is the focus of the task. This allows me to not only understand what tasks cost, but also which specific parts of the task were expensive. Then you start to learn things, like failure modes you'd otherwise miss, or the model doing something that should be hardened in a script.</p>
<p>For each subagent, the Claude process has the ID of the bead it is working in its exports, so the header rides every call that session makes, and the gateway writes it onto the row next to the dollars it derives from token usage. On a subscription those dollars are list-rate equivalents, which is great, it shows how much value I get from my flat subscription each week (I wouldn't be able to afford this on API costs, I'll just say that).</p>
<h2>Each bead comes back wearing its cost</h2>

<p>The same graph, with the dollars written on. Reading it takes no spreadsheet:</p>
<ul>
<li>Each task's number is its own spend, the calls made under that task and nothing else.</li>
<li>The epic's figure is the plain total of the tasks underneath it.</li>
<li>Coordination, the sessions that dispatch and merge everything else, carries its own line instead of being smeared across the work it touched.</li>
</ul>
<p>The most expensive task in the experiment wrote no code at all: the research that benchmarked local models against real bugs from my own backlog, at $61.80, and it decided what everything else was built on.</p>
<h2>Seat totals show where the three weeks went</h2>

<p>Zoom out and the ranking is predictable, at least for an AI explorer who is constantly refining his own harness. The seat that tends the fleet's tooling leads, infrastructure follows, and the whole fallback epic is a thin sliver inside each box. My invoice already knew this much. It is where I used to stop, because it was as far as my numbers went.</p>
<h2>Filter to one epic and the leaderboard flips</h2>

<p>Filter the same seats down to the fallback work alone and the ranking inverts:</p>
<ul>
<li>The infrastructure seat jumps to 1st, which is what owning the gateway route costs.</li>
<li>The fleet's biggest spender drops to 2nd, and more than a third of what it spent here was coordination rather than code.</li>
</ul>
<p>My front desk turns out to be the epic's cheapest line: two decisions and a runbook on the process. The read-only critic bought the merge gate.</p>
<p>This is still an early experiment, and I only run one machine on this practice. But it opens an opportunity for you, as the harness operator, to ask higher-order questions about the efficiency of the system, because the entire thing is a feedback loop. Any session can connect work to tasks to inference to the seat responsible for owning it, and reason and act on it. Some of these questions for me have been:</p>
<ul>
<li>Is my research desk cheap because it is efficient, or because I under-use it?</li>
<li>Why is the seat that makes my decisions the cheapest line on the graph?</li>
<li>What opportunity am I missing?</li>
<li>What is taking up most of my time and tokens?</li>
<li>Of what I completed, what had the biggest impact?</li>
</ul>
<p>I can picture these numbers across a whole engineering org, but I haven't gotten there yet.</p>
<h2>The taps are the layer I actually pay</h2>
<p>Steve Yegge named this layer too. His fleet drinks from what he calls a <a href="https://yegge.ai/essays/the-shape-of-things-to-come/">token tap</a>, a pool of flat-rate subscription accounts his agents rotate through instead of metered API billing, and he runs a dozen of them.</p>
<p>I only have 3. (😇)</p>
<p>Since all of the inference is labelled even by tap in the gateway, I can segment any given week to find how much of each tap was consumed by which seat:</p>

<p>I can split that same tap across beads as well to find which bead epics took up my time and precious tokens that week:</p>

<h2>Put a cost on a unit of your own work</h2>
<p>If you're a software leader right now trying to map inference to outcomes, you really need a context layer to capture this same level of insight but across all of your engineers.</p>
<p>What surprised me is how loose the unit of work can be when you attribute cost to a bead. Our industry tends to ask for <em>cost per PR</em>, but my agents spend most of their time doing work that doesn't end up as a PR. That didn't stop me from trying to valuate it, however.</p>
<p>I found that attributing the cost to a bead, a unit of work that can be defined as truly <em>anything</em>, is the right tempo of scale to match the inference pattern used by models.</p>
<p>And even better: a PR can neatly contain a record of all the beads that produced it, thus earning the <em>cost per PR</em> by adding the cumulative work inside it. Because you're tracking at the inference level, you're getting a much more accurate sense of cost anyways, compared to the PR which is only going to capture costs associated with tasks around code gen.</p>
<p>I had my infra agent cut up some examples for you to study below, if you want to try running this experiment yourself. Be sure to try out <a href="https://agentgateway.dev">agentgateway</a> and <a href="https://github.com/Dicklesworthstone/beads_rust">beads_rust</a> regardless. I also wrote a tutorial on <a href="https://www.andrewzigler.com/feed/if-you-give-a-goose-an-mcp-server">how to stand a gateway like this up</a> that's a good place to start after learning the tools.</p>
<p>gl;hf! 😎</p>
<hr>
<h2>Appendix: the pieces worth copying</h2>
<p><strong>1: lift two headers into columns.</strong> <code>standardAttributes</code> is read at startup only, so this is a restart, not a hot reload. Validate first, and watch the validator reject a deliberately broken control before you trust it.</p>
<pre><code class="language-yaml">config:
  database:
    url: "sqlite:///var/lib/agentgateway/requests.db?mode=rwc"
  standardAttributes:
    user:  'default(request.headers["x-session-identity"], "")'
    group: 'default(request.headers["x-billing-origin"], "")'
</code></pre>
<pre><code class="language-sh">agentgateway --validate-only -f config.yaml   # non-zero exit names the bad field
</code></pre>
<p><strong>2: the sanitizer that makes the unknown-spender bucket structural.</strong> The filter cannot emit <code>?</code>, which frees <code>?</code> to mean "nothing was derived here" and nothing else.</p>
<pre><code class="language-sh">sanitize() { printf '%s' "${1:-}" | tr -s '[:space:]' '-' | tr -cd '[:alnum:]._-'; }

seat=$(sanitize "${MY_SEAT_NAME:-}")
export SEAT_ADDRESS="${seat:-?}"      # a real name can never contain '?'
</code></pre>
<p><strong>3: group by seat, last 7 days.</strong> Read a snapshot, never the file the gateway is writing.</p>
<pre><code class="language-sh">sqlite3 requests.db "VACUUM INTO '/tmp/snap.db'"
</code></pre>
<pre><code class="language-sql">-- datetime() is load-bearing: started_at is ISO8601 with a 'T', and a bare
-- string compare matches every row with today's date without erroring.
SELECT agentgateway_user               AS seat,
       count(*)                        AS requests,
       sum(coalesce(total_tokens, 0))  AS tokens
FROM request_logs
WHERE datetime(started_at) >= datetime('now', '-7 days')
GROUP BY seat
ORDER BY tokens DESC;
</code></pre>
<p><strong>4: the bead join.</strong> The identity schema is closed at two columns, so the per-task header rides the access log's attribute JSON, which is the path that hot-reloads. The sentinel lives in the config, not the client: a launch that never heard of beads still lands as <code>none</code>, honestly.</p>
<pre><code class="language-yaml">frontendPolicies:
  accessLog:
    database:
      add:
        bead_id: 'default(request.headers["x-bead"], "none")'
</code></pre>
<pre><code class="language-sql">-- per-task spend, sentinel excluded; keep the extraction parenthesized
SELECT (attributes_json ->> '$.bead_id') AS bead,
       count(*)                          AS requests,
       round(sum(cost), 2)               AS dollars
FROM request_logs
WHERE (attributes_json ->> '$.bead_id') IS NOT NULL
  AND (attributes_json ->> '$.bead_id') != 'none'
GROUP BY bead
ORDER BY dollars DESC;
</code></pre>
<hr>
<p><em>Andrew Zigler is a 2026 AAIF Ambassador. Find the cohort at <a href="https://aaif.io/ambassadors">aaif.io/ambassadors</a>. I wrote up <a href="https://www.andrewzigler.com/feed/if-you-give-a-goose-an-mcp-server">how to stand a gateway like this up</a> in July; this piece is what its receipts taught me to ask for.</em></p>]]></content:encoded>
            <category>article</category>
            <category>Devlog</category>
        </item>
        <item>
            <title><![CDATA[The engineering productivity gap | How elite AI teams are pulling away from the pack]]></title>
            <link>https://www.andrewzigler.com/feed/the-engineering-productivity-gap-how-elite-ai-teams-are-pulling-away-from-the-pack</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/the-engineering-productivity-gap-how-elite-ai-teams-are-pulling-away-from-the-pack</guid>
            <pubDate>Fri, 31 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[The engineering productivity gap | How elite AI teams are pulling away from the pack]]></description>
            <category>media</category>
        </item>
        <item>
            <title><![CDATA[If you give a Goose an MCP server]]></title>
            <link>https://www.andrewzigler.com/feed/if-you-give-a-goose-an-mcp-server</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/if-you-give-a-goose-an-mcp-server</guid>
            <pubDate>Wed, 08 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A hands-on build: register a local model, federate your MCP servers into one endpoint, scope that endpoint down to the six tools the job needs, and watch every model and tool call land in one log, tagged with the key that made it. Built on agentgateway.]]></description>
            <content:encoded><![CDATA[<p><em>A hands-on build: register a local model, federate your MCP servers into one endpoint, scope that endpoint down to the six tools the job needs, and watch every model and tool call land in one log, tagged with the key that made it. Built on agentgateway.</em></p>
<p>If you give a Goose an MCP server, it gets every tool the server ships.</p>
<p>You handed it a filesystem server because it needed to read one file. It got the read tool, and the write tool, and the edit tool, and 11 more, because <code>tools/list</code> came back full and nobody reads a tool manifest the way they read a diff. The filesystem server exposes 14 tools. The Goose now has all 14, because the server decides what the Goose can do, and that server was written to do everything.</p>
<p>This is a build for taking that decision back, by hand, once, so the tool layer stops being a black box you accept on faith. You put a gateway in the one place every call has to cross. You register your model there, so model calls ride the same layer as tool calls. You federate your MCP servers into a single endpoint and grant one agent exactly six tools on it. Then you watch the whole session, model calls and tool calls alike, land in one log, each row tagged with the key that made it. By the end the Goose holds six tools it can't step outside of, and a dashboard shows you every one it reached for.</p>
<p>The gateway is <a href="https://github.com/agentgateway/agentgateway">agentgateway</a>, a single Rust binary and an <a href="https://aaif.io">AAIF</a> project. It bills itself as a connectivity solution for agents: one proxy in front of model traffic, MCP traffic, and ordinary HTTP, applying the same auth, policy, and logging to all three. That "all three" is what makes this build work. The model and the tools go through one door, so one log holds the whole session. I'm running it in standalone mode — a single binary and a YAML file, with an admin UI at <code>:15000/ui</code> that hot-reloads on save — in front of <a href="https://goose-docs.ai/">Goose</a> talking to a local model. The six-tool set I grant the Goose has a name (that I made up): <strong>tiny-tools</strong>.</p>
<p>One important thing to note: the agent that <em>builds</em> this needs a shell and room to install things, so the builder is you, or an agent that still has the run of the house. The Goose we keep narrowing to six tools is the subject on the workbench. You hold the wrench.</p>
<p>Everything here is one config file. The slices nest like this, top-level keys first:</p>
<pre><code class="language-yaml">config:
  database:
    url: "sqlite:///path/to/requests.db?mode=rwc"   # the request-log DB (Step 5)
  standardAttributes:
    user: 'apiKey.name'          # stamps every logged row with the calling key's name

frontendPolicies:
  accessLog:
    database:
      add: { prompt: 'llm.prompt', completion: 'llm.completion' }   # capture prompts + completions (Step 5)

llm:                             # Step 1 -- the model listener
  port: 15003
  providers: [ ... ]
  models: [ ... ]
  policies:
    apiKey: { ... }              # the goose key, so model calls are keyed and attributed

mcp:                             # Step 2 -- the federated tool listener
  port: 15001
  targets: [ ... ]
  policies:
    apiKey: { ... }              # Step 3 -- the per-agent keys (goose, cc)
    mcpAuthorization: { ... }    # Step 3 -- match the key name to scope its tools
</code></pre>
<p><strong>Before you start.</strong> <a href="https://agentgateway.dev/docs/">Install agentgateway</a> and get it running. Save your config as <code>config.yaml</code> and launch with <code>agentgateway -f config.yaml</code>; the admin UI at <code>:15000/ui</code> hot-reloads on every save, and <code>agentgateway -f config.yaml --validate-only</code> checks a config before you run it. Have <a href="https://ollama.com/">Ollama</a> serving a tool-capable model (<code>ollama pull qwen3:8b</code>), install <a href="https://goose-docs.ai/">Goose</a>, and keep <code>npx</code> (for the filesystem server) and <code>uvx</code> (for the fetch server) within reach.</p>
<h2>Step 1: Put your model behind the gateway</h2>
<p>Start with the model. In my case, I'll use Ollama, just because I'm used to it, but you can use anything that serves a model. Routing it through the gateway is the piece most local setups skip, and it decides whether you can watch the whole session later. Route the model through the gateway and its calls land in the same request log as the tool calls, tagged with the same key. Leave it pointed at Ollama directly and you get a curated tool layer with a blind spot where the model should be.</p>
<p>An agentgateway <code>llm</code> block is a provider plus a list of models. The provider names your backend once; each model references it. The listener also carries the <code>goose</code> key, so a model call arrives identified:</p>
<pre><code class="language-yaml">llm:
  port: 15003
  providers:
    - name: local
      provider: ollama
      params:
        baseUrl: http://localhost:11434/v1
  models:
    - name: qwen3:8b
      provider:
        reference: local
  policies:
    apiKey:
      mode: strict
      keys:
        - key: sk-...            # the same goose key you'll reuse in Step 3
          metadata: { name: goose }
</code></pre>
<p>The gateway now exposes an OpenAI-compatible API on <code>:15003</code>, and every model call through it is one row in the request log, stamped <code>goose</code> by <code>standardAttributes.user</code>. Point Goose at the gateway with four environment variables:</p>
<pre><code class="language-sh">export GOOSE_PROVIDER=openai
export OPENAI_HOST=http://localhost:15003     # bare host:port; Goose appends the OpenAI path
export OPENAI_API_KEY=sk-...                  # the goose key -- our LLM listener is keyed
export GOOSE_MODEL=qwen3:8b
</code></pre>
<p>Agentgateway's own Goose guide uses a placeholder key here, because its example listener takes any key. Ours doesn't. The <code>goose</code> key is what stamps every model row with the same identity the tool calls carry, so it has to be the real one.</p>
<p><img src="https://cdn.zig.computer/aaif/honk-tutorial/04-models-redacted.png" alt="The gateway&#x27;s Models view: the local models registered behind one provider, each routed at the gateway&#x27;s LLM listener. Model calls now cross the same gateway as tool calls, so one log holds the whole session."></p>
<blockquote>
<p><strong>Agent:</strong> in agentgateway's <code>llm</code> config, register an Ollama provider pointed at my local Ollama (<code>baseUrl</code> <code>http://localhost:11434/v1</code>), add my model referencing it, and attach an <code>apiKey</code> policy carrying the <code>goose</code> key so model calls are authenticated and attributed. Set <code>standardAttributes.user: 'apiKey.name'</code> in the top-level <code>config</code> block. Then point Goose at the gateway with <code>GOOSE_PROVIDER=openai</code>, <code>OPENAI_HOST=http://localhost:15003</code>, <code>OPENAI_API_KEY=&#x3C;the goose key></code>, <code>GOOSE_MODEL=qwen3:8b</code>. Read agentgateway's <a href="https://agentgateway.dev/docs/standalone/latest/llm/providers/ollama/">Ollama provider docs</a> and its <a href="https://agentgateway.dev/docs/standalone/latest/integrations/web-uis/goose/">Goose integration guide</a>.</p>
</blockquote>
<h2>Step 2: Federate your MCP servers into one endpoint</h2>
<p>Now the model has somewhere to reach. Give it tools. The interesting part is <em>how</em> you give them. You federate several MCP servers behind the gateway and hand the agent a single endpoint, rather than wiring one connection per server into the client. The gateway multiplexes them. The agent connects to one place, and the servers behind it are the gateway's concern.</p>
<p>You define each server as a <code>target</code> under the <code>mcp</code> block. Two small servers here, a filesystem server scoped to one workspace directory and a fetch server:</p>
<pre><code class="language-yaml">mcp:
  port: 15001
  targets:
    - name: filesystem
      stdio:
        cmd: npx
        args: ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/workspace"]
    - name: fetch
      stdio:
        cmd: uvx
        args: ["mcp-server-fetch"]
</code></pre>
<p>Between them, that's 15 tools behind one endpoint. The gateway namespaces each tool by its target, so the filesystem server's <code>read_text_file</code> shows up as <code>filesystem_read_text_file</code>. Open the Servers view and both targets register and report ready. The Goose connects to this one endpoint, never to either server directly.</p>
<p><img src="https://cdn.zig.computer/aaif/honk-tutorial/01-servers-redacted.png" alt="The MCP Servers view: the filesystem and fetch servers federated behind the gateway, both ready. The agent connects to this single endpoint; the gateway multiplexes the two servers behind it."></p>
<blockquote>
<p><strong>Agent:</strong> federate two <code>stdio</code> MCP targets under one <code>mcp</code> endpoint: <code>@modelcontextprotocol/server-filesystem</code> scoped to a single workspace directory (target name <code>filesystem</code>) and <code>mcp-server-fetch</code> (target name <code>fetch</code>). Confirm both report ready and that the combined <code>tools/list</code> shows all 15 tools, target-prefixed. Read agentgateway's <a href="https://agentgateway.dev/docs/standalone/latest/mcp/connect/virtual/">MCP multiplexing docs</a>; a target name can't contain underscores.</p>
</blockquote>
<h2>Step 3: Scope the endpoint per agent</h2>
<p>Give a Goose 15 tools and it will find a use for one you never meant it to have. So before the Goose ever connects, you decide what's on the endpoint for it, and you decide it per key. One virtual endpoint, and each API key gets its own view of it.</p>
<p>Let's scope the MCP tools into custom virtual MCP servers for two different agents: one on Goose, and one in Claude Code. We're picking the Goose's six by hand for now; Step 7 shows how to derive that set from real traffic instead.</p>
<pre><code class="language-yaml">  policies:                      # under the mcp block from Step 2
    apiKey:
      mode: strict
      keys:
        - key: sk-...            # the Goose's key (the same one from Step 1)
          metadata: { name: goose }
        - key: sk-...            # Claude Code's key
          metadata: { name: cc }
</code></pre>
<p>Then we match on the key's name to scope which tools each one sees:</p>
<pre><code class="language-yaml">    mcpAuthorization:
      rules:
        - allow: apiKey.name == "cc"
        # mcp.tool.name is the underlying tool name (read_text_file), not the
        # filesystem_-prefixed name the client sees in tools/list
        - allow: apiKey.name == "goose" &#x26;&#x26;
                 mcp.tool.name in ["read_text_file", "write_file", "edit_file",
                                   "list_directory", "search_files", "fetch"]
</code></pre>
<p>The <code>cc</code> key (Claude Code) gets everything; the <code>goose</code> key gets six. Same endpoint, and the key decides the view. Write one <code>allow</code> rule and the endpoint flips to default-deny: anything not allowed is denied, and a tool the <code>goose</code> key doesn't allow drops out of <code>tools/list</code> entirely, so the Goose never discovers it exists. That scoped view is tiny-tools.</p>
<p><img src="https://cdn.zig.computer/aaif/honk-tutorial/02-tools.png" alt="The gateway&#x27;s Tool Playground, a session opened with the Goose&#x27;s key: it discovers exactly six tools — read, write, and edit a file, list and search a directory, and fetch a URL — each namespaced by the server it came from. This is tiny-tools from the client&#x27;s side of the wire."></p>
<blockquote>
<p><strong>Agent:</strong> on the federated MCP endpoint, add a <code>strict</code> API-key policy with keys named <code>goose</code> and <code>cc</code>, and an <code>mcpAuthorization</code> rule allowing the <code>goose</code> key exactly <code>read_text_file, write_file, edit_file, list_directory, search_files, fetch</code>. Confirm a session opened with the <code>goose</code> key discovers only those six. Read agentgateway's <a href="https://agentgateway.dev/docs/standalone/latest/mcp/mcp-authz/">MCP authorization docs</a>: one <code>allow</code> rule flips the endpoint to default-deny, and denied tools drop out of <code>tools/list</code>.</p>
</blockquote>
<h2>Step 4: Point Goose at your virtualized MCP server</h2>
<p>You built the scoped endpoint. Now point your Goose at it. Step 1 aimed Goose's model at the gateway; this aims its tools there too, at the tiny-tools view you just carved out.</p>
<p>Run <code>goose configure</code>, choose <strong>Add Extension → Remote Extension (Streamable HTTP)</strong>, and point it at the gateway's MCP endpoint, <code>http://localhost:15001/mcp</code>, with an <code>Authorization: Bearer &#x3C;goose key></code> header so it connects as the <code>goose</code> key. The Goose now reaches its tools through the gateway, and the gateway hands back exactly the six that tiny-tools allows.</p>
<p>By the way, Goose does a lot of what's in tiny-tools out of the box, through a built-in <code>developer</code> extension with its own shell and editor. Toggle it off for this run (<code>goose configure → Toggle Extensions</code>) so tiny-tools is the entire surface, which is the point when you're trying to see the tool layer plainly. In your own setup you'd keep it or drop it depending on whether you want those built-ins alongside your list.</p>
<p>Start a Goose session and its tool list is exactly tiny-tools: the six you granted the <code>goose</code> key, and nothing underneath.</p>
<blockquote>
<p><strong>Agent:</strong> in Goose, add a remote streamable-HTTP MCP extension pointed at <code>http://localhost:15001/mcp</code> with an <code>Authorization: Bearer &#x3C;goose key></code> header, and disable the built-in <code>developer</code> extension. Confirm the Goose session's tools are exactly the six from tiny-tools. Read Goose's <a href="https://goose-docs.ai/docs/getting-started/using-extensions/">extensions guide</a>.</p>
</blockquote>
<h2>Step 5: Watch the whole session in one place</h2>
<p>Every call the Goose makes now crosses the gateway, model and tools alike. So watch them. This is the payoff of routing the model through the gateway back in Step 1: one gateway holds the whole session, in one place you can query.</p>
<p>That place is the request-log database, the same store behind the admin UI's Logs tab. Turn it on in the top-level <code>config</code> block and tag every row with the calling key:</p>
<pre><code class="language-yaml">config:
  database:
    url: "sqlite:///path/to/requests.db?mode=rwc"   # the request-log DB
  standardAttributes:
    user: 'apiKey.name'                              # tag every row with the key's name
</code></pre>
<p>Every call the gateway proxies writes a row there. A tool call at the MCP listener and a model call at the LLM listener both land in the same table, each row carrying its protocol (<code>mcp</code> or <code>llm</code>) and, because of <code>standardAttributes.user</code>, the name of the key that made it. There's also a text access log you can tail to watch calls fly by live; the database is the durable, queryable version, and it's what the skills in Step 7 read.</p>
<p>I gave the Goose a task that read a file, wrote a new one, and fetched a page. Pulling the fields that matter from its rows, the whole session comes back under one identity, tools and model together:</p>
<pre><code>protocol=mcp   read_text_file   user=goose   status=200    16ms
protocol=mcp   write_file       user=goose   status=200     8ms
protocol=mcp   fetch            user=goose   status=200   782ms
protocol=llm   qwen3:8b         user=goose   status=200   1533ms
</code></pre>
<p>One key made every call, so one identity tags every row. That is why the model went behind the gateway in Step 1. (The text access log labels that identity <code>agent</code> rather than <code>user</code>; both read from <code>apiKey.name</code>, so it's one value under two names.)</p>
<p>Turn on payload capture and the log holds not just that the Goose called the model but what it said:</p>
<pre><code class="language-yaml">frontendPolicies:
  accessLog:
    database:
      add: { prompt: 'llm.prompt', completion: 'llm.completion' }
</code></pre>
<p>It's worth having on. Those rows now carry full prompts and completions, so treat the database as sensitive.</p>
<p><img src="https://cdn.zig.computer/aaif/honk-tutorial/03-tool-call.png" alt="Driving the same kind of call by hand: the Tool Playground runs a fetch and shows the HTTP 200 and the returned content in the result pane. It&#x27;s the same request path Goose&#x27;s calls take, which is why every one of them lands in the log."></p>
<blockquote>
<p><strong>Agent:</strong> set <code>config.database.url</code> and <code>standardAttributes.user: 'apiKey.name'</code> so the request-log DB records every call with identity. Run a task through Goose that reads a known file, writes a new one, and fetches a URL. Confirm the DB (and the admin UI's Logs tab) shows the tool rows (<code>protocol=mcp</code>) and the model row (<code>protocol=llm</code>) all under <code>user=goose</code>. Turn on payload capture (<code>frontendPolicies.accessLog.database</code>) to also record the prompt and completion.</p>
</blockquote>
<h2>Step 6: Prove the tools you cut are gone</h2>
<p>An agent never reaches for a tool it can't see — that's the whole point of scoping, and it's also why you can't prove the wall holds just by watching the Goose work. So reach for the cut tool yourself. <code>move_file</code> is one of the filesystem server's original tools, from before you virtualized it, and the <code>goose</code> key never got it. Issue a <code>tools/call</code> for it straight to the gateway's MCP endpoint with the <code>goose</code> key, the same request the Goose's own client would send. (MCP over HTTP opens with an <code>initialize</code> handshake that hands back a session id; reuse it here.)</p>
<pre><code class="language-sh">curl -sS http://localhost:15001/mcp \
  -H "Authorization: Bearer $GOOSE_KEY" -H "Mcp-Session-Id: $SESSION" \
  -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
       "params":{"name":"filesystem_move_file",
                 "arguments":{"source":"a.txt","destination":"b.txt"}}}'
</code></pre>
<p>The gateway answers <code>HTTP 400</code> with a JSON-RPC error:</p>
<pre><code class="language-json">{"jsonrpc":"2.0","id":1,"error":{"code":-32602,"message":"Unknown tool: filesystem_move_file"}}
</code></pre>
<p>That error turns on one word: <em>unknown</em>. A tool the gateway called <em>forbidden</em> would still be in the manifest, a name the Goose could see, reach for, and be told no. This one is <em>unknown</em>. It never entered <code>tools/list</code>, so there's no name to reach for, and as far as the <code>goose</code> key's view of the endpoint goes, <code>move_file</code> was never there. The attempt still lands in the log, so you can watch it happen and watch it go nowhere.</p>
<blockquote>
<p><strong>Agent:</strong> call <code>tools/call</code> for <code>filesystem_move_file</code> against the endpoint with the <code>goose</code> key, and confirm the gateway returns an "Unknown tool" error while still recording the attempt. This verifies the allowlist filters at discovery, before the call is even attempted.</p>
</blockquote>
<h2>Step 7: Run the loop with one skill</h2>
<p>You built tiny-tools by guessing which six the job needs. The skill loop is how you stop guessing. Point the Goose at a full MCP server — a new one you're onboarding, before you've scoped it, on a key that can still see everything — and let it work through a real task. The gateway records all of it. Then you read the record and cut: keep the tools the Goose actually reached for, notice which ones carried real weight, and virtualize the rest away. You whittle a fat server down to a tiny-tools that fits the workflow, from what happened instead of from a guess.</p>
<p>I packaged four read-only components as one <a href="https://github.com/azigler/aaif/tree/main/.claude/skills/agentgateway">agentgateway skill</a>, a Claude Code Agent-Skill you drop in your <code>.claude/skills/</code>:</p>
<ul>
<li><strong>audit</strong> groups every call by identity, tool, and outcome, so you can see which of a server's tools the Goose actually used, and how often.</li>
<li><strong>cost</strong> reads the token counts, so you can see which tools carried the weight. The dollar column stays empty on a local model, but the tokens are the number you tune.</li>
<li><strong>trace</strong> walks a single call, or a session, end to end when one of them looked wrong.</li>
<li><strong>harden</strong> reads what the Goose actually used and drafts the tighter allowlist that would have permitted exactly that, then hands it to you. It proposes; it never applies. A rule that could lock out real traffic waits for a human.</li>
</ul>
<p>They read the request-log database over a consistent snapshot, never the live file, and they share one <a href="https://github.com/azigler/aaif/blob/main/refs/gateway-request-log-cookbook.md">request-log cookbook</a>, the verified query foundation underneath all four. Continuous, standing alerting isn't in here on purpose, because a skill can't hold a live watch. Run audit and cost on a cadence and read the deltas.</p>
<p>The morning after a real run, the loop composes: <strong>audit</strong> shows the Goose reached for a handful of the server's tools and never touched the rest; <strong>cost</strong> shows which of them carried the token weight; <strong>trace</strong> explains the one slow call; and <strong>harden</strong> hands you the tighter allowlist — the tiny-tools for this workflow — built from what actually happened, for you to validate and apply. Read-only the whole way, human-gated at the one step that changes anything.</p>
<blockquote>
<p><strong>Agent:</strong> install the <a href="https://github.com/azigler/aaif/tree/main/.claude/skills/agentgateway">agentgateway skill</a> and its request-log cookbook. After a real Goose session against a full server, run <strong>audit</strong> to see what the Goose actually used, then <strong>harden</strong> to draft the whittled-down allowlist for my review. It proposes policy; it never applies it.</p>
</blockquote>
<p>The black box was a list and a rule the whole time. Now it's yours.</p>
<hr>
<p><em>Andrew Zigler is a 2026 AAIF Ambassador. Find the cohort at <a href="https://aaif.io/ambassadors">aaif.io/ambassadors</a>.</em></p>]]></content:encoded>
            <category>article</category>
            <category>Devlog</category>
        </item>
        <item>
            <title><![CDATA[Life beyond tokenmaxxing]]></title>
            <link>https://www.andrewzigler.com/feed/life-beyond-tokenmaxxing</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/life-beyond-tokenmaxxing</guid>
            <pubDate>Fri, 26 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Life beyond tokenmaxxing]]></description>
            <category>media</category>
        </item>
        <item>
            <title><![CDATA[Build vs. buy: Why DIY engineering metrics break at scale]]></title>
            <link>https://www.andrewzigler.com/feed/build-vs-buy-why-diy-engineering-metrics-break-at-scale</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/build-vs-buy-why-diy-engineering-metrics-break-at-scale</guid>
            <pubDate>Fri, 22 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Build vs. buy: Why DIY engineering metrics break at scale]]></description>
            <category>media</category>
        </item>
        <item>
            <title><![CDATA[We asked Claude to build LinearB (and here's what happened)]]></title>
            <link>https://www.andrewzigler.com/feed/we-asked-claude-to-build-linearb-and-heres-what-happened</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/we-asked-claude-to-build-linearb-and-heres-what-happened</guid>
            <pubDate>Thu, 14 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[We asked Claude to build LinearB (and here's what happened)]]></description>
            <category>media</category>
        </item>
        <item>
            <title><![CDATA[Depths (Ludum Dare 57)]]></title>
            <link>https://www.andrewzigler.com/feed/crossed-wires-ludum-dare-59</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/crossed-wires-ludum-dare-59</guid>
            <pubDate>Sun, 19 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A small-town switchboard operator's shift. Every call is tangled. You sort.]]></description>
            <content:encoded><![CDATA[<p>A small-town switchboard operator's shift. Every call is tangled. You sort.</p>
<p>Two callers arrive on the same phone line. You assign each of their utterances back to the right conversation. Early calls are easy — the goat lady does not sound like the shipping clerk. Later calls have static eating the words on the wire, and you sort by how each caller talks — all-caps outbursts versus two-word dispatches, grief versus directive. Voice recognition as puzzle.</p>
<p>Built solo over the Ludum Dare 59 jam weekend with a handcrafted Claude Code agentic harness and pair-coded with Claude Opus 4.7. The harness pulls in Impeccable design skills and a cross-engine game-dev agent team, alongside project-local skills for spec, impl, review, commit, manifesto and indie-game-taste.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[How to Measure AI Impact: Proving ROI from Copilot, Cursor, and Claude]]></title>
            <link>https://www.andrewzigler.com/feed/how-to-measure-ai-impact-proving-roi-from-copilot-cursor-and-claude</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/how-to-measure-ai-impact-proving-roi-from-copilot-cursor-and-claude</guid>
            <pubDate>Thu, 26 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[How to Measure AI Impact: Proving ROI from Copilot, Cursor, and Claude]]></description>
            <category>media</category>
        </item>
        <item>
            <title><![CDATA[Genuary 2026]]></title>
            <link>https://www.andrewzigler.com/feed/genuary-2026</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/genuary-2026</guid>
            <pubDate>Sat, 31 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Genuary is an annual, month-long creative coding challenge where artists create generative art daily based on specific prompts. It's a celebration of algorithmic art, creative coding, and the vibrant community of generative artists worldwide.]]></description>
            <content:encoded><![CDATA[<p><a href="https://genuary.art/">Genuary</a> is an annual, month-long creative coding challenge where artists create generative art daily based on specific prompts. It's a celebration of algorithmic art, creative coding, and the vibrant community of generative artists worldwide.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[Building future AI news experiences with The Atlantic and Infactory]]></title>
            <link>https://www.andrewzigler.com/feed/infactory-the-atlantic-hackathon</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/infactory-the-atlantic-hackathon</guid>
            <pubDate>Sat, 31 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[I placed 1st in the Infactory x The Atlantic AI hackathon (Building future AI news experiences with The Atlantic and Infactory) by creating a virtual classroom platform that empowers teachers, challenges students, and fosters a deeper relationship with journalism using The Atlantic’s archives as a teaching resource. To create my demo, I orchestrated agents in Claude Code with beads.]]></description>
            <content:encoded><![CDATA[<p>I placed 1st in the <a href="https://www.linkedin.com/company/infactory-ai/">Infactory</a> x <a href="https://www.linkedin.com/company/the-atlantic/">The Atlantic</a> AI hackathon (<a href="https://luma.com/hh-future-ai-news-theatlantic-infactory">Building future AI news experiences with The Atlantic and Infactory</a>) by creating a virtual classroom platform that empowers teachers, challenges students, and fosters a deeper relationship with journalism using The Atlantic’s archives as a teaching resource. To create my demo, I orchestrated agents in Claude Code with beads.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[AdventJS 2025]]></title>
            <link>https://www.andrewzigler.com/feed/adventjs-2025</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/adventjs-2025</guid>
            <pubDate>Thu, 25 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[A complete set of solutions for all 25 challenges in AdventJS 2025. Each challenge solved in JavaScript, TypeScript, and Python with comprehensive test suites and solution logs. Built a custom harness for automated fetching, testing, and submission with quality score tracking.]]></description>
            <content:encoded><![CDATA[<p>A complete set of solutions for all 25 challenges in <a href="https://adventjs.dev/">AdventJS 2025</a>. Each challenge solved in JavaScript, TypeScript, and Python with comprehensive test suites and solution logs. Built a custom harness for automated fetching, testing, and submission with quality score tracking.</p>
<p>Completed all 25 challenges across all three languages (75 total solutions), achieving 5/5 quality scores on all submissions.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[Advent of Code 2025]]></title>
            <link>https://www.andrewzigler.com/feed/advent-of-code-2025</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/advent-of-code-2025</guid>
            <pubDate>Fri, 12 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Solutions for all 12 days of Advent of Code 2025. Each day includes TypeScript solutions with comprehensive test suites, edge case analysis, and detailed solution logs documenting the problem-solving process.]]></description>
            <content:encoded><![CDATA[<p>Solutions for all 12 days of <a href="https://adventofcode.com/2025">Advent of Code 2025</a>. Each day includes TypeScript solutions with comprehensive test suites, edge case analysis, and detailed solution logs documenting the problem-solving process.</p>
<p>Built a custom harness for automated input fetching, answer submission, and star tracking with rate limiting and caching.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[Depths (Ludum Dare 57)]]></title>
            <link>https://www.andrewzigler.com/feed/depths-ludum-dare-57</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/depths-ludum-dare-57</guid>
            <pubDate>Mon, 07 Apr 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[A game about navigating deep folder structures under time pressure. Players take on the role of an office worker who must find a specific file in a chaotic company server within a strict time limit. Created in 72 hours for Ludum Dare 57.]]></description>
            <content:encoded><![CDATA[<p>A game about navigating deep folder structures under time pressure. Players take on the role of an office worker who must find a specific file in a chaotic company server within a strict time limit. Created in 72 hours for <a href="https://ldjam.com/events/ludum-dare/57">Ludum Dare 57</a>.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[Club Work Out No Jogging]]></title>
            <link>https://www.andrewzigler.com/feed/club-work-out-no-jogging-sims-4-mod</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/club-work-out-no-jogging-sims-4-mod</guid>
            <pubDate>Thu, 17 Mar 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[This Sims 4 mod fixed unintended behavior wherein Sims would go jogging while attending fitness-oriented club hangouts. This caused them to scatter all over the map and spend no time socializing with each other, which defeated the point of the feature. This mod adjusted the Sims autonomy to de-prioritize jogging. However, this mod is now deprecated because the original unintended behavior is no longer observed.]]></description>
            <content:encoded><![CDATA[<p>This Sims 4 mod fixed unintended behavior wherein Sims would go jogging while attending fitness-oriented club hangouts. This caused them to scatter all over the map and spend no time socializing with each other, which defeated the point of the feature. This mod adjusted the Sims autonomy to de-prioritize jogging. However, this mod is now deprecated because the original unintended behavior is no longer observed.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[More Club Members]]></title>
            <link>https://www.andrewzigler.com/feed/more-club-members-sims-4-mod</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/more-club-members-sims-4-mod</guid>
            <pubDate>Thu, 17 Mar 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[This Sims 4 mod expanded the limit on club members in the game. By default, club members were arbitrarily capped at 8, but this mod lifted that cap to 20 or 50, depending on user preference. It also introduced more advanced club requirement filters for Sims, because at the time there were limited options. However, this mod is now deprecated. All functionality of this mod is provided by Deaderpool's MC Command Center Clubs module.]]></description>
            <content:encoded><![CDATA[<p>This Sims 4 mod expanded the limit on club members in the game. By default, club members were arbitrarily capped at 8, but this mod lifted that cap to 20 or 50, depending on user preference. It also introduced more advanced club requirement filters for Sims, because at the time there were limited options. However, this mod is now deprecated. All functionality of this mod is provided by <a href="https://deaderpool-mccc.com/#/doc/mc_clubs">Deaderpool's MC Command Center Clubs module</a>.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[Review: Visme puts your visuals ahead of the curve]]></title>
            <link>https://www.andrewzigler.com/feed/review-visme-puts-your-visuals-ahead-of-the-curve</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/review-visme-puts-your-visuals-ahead-of-the-curve</guid>
            <pubDate>Sun, 29 Dec 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[I've been updating some of the images on my website, and conforming to a new image size for all of my social media shares. This turned out to be a pretty big project, since I needed to crop or rebuild over forty different blog post headers into the new format. I didn't want to do it with Photoshop again, so I joined Visme to make it easy! Visme is an easy-to-use online tool that you can use to create and organize all types of visual content. Visme has been around for over 5 years, so it has a lot to offer. Bloggers (like me!) are using it to create static graphics or presentations, but I promise that I'll try not to get too hammy with it!]]></description>
            <content:encoded><![CDATA[<p>I've been updating some of the images on my website, and conforming to a new image size for all of my social media shares. This turned out to be a pretty big project, since I needed to crop or rebuild over forty different blog post headers into the new format. I didn't want to do it with Photoshop again, so I joined <a href="https://www.visme.co/"><strong>Visme</strong></a> to make it easy! Visme is an easy-to-use online tool that you can use to create and organize all types of visual content. Visme has been around for over 5 years, so it has a lot to offer. Bloggers (like me!) are using it to create static graphics or presentations, but I promise that I'll try not to get too hammy with it!</p>
<p><img src="https://images.prismic.io/andrewzigler/85bc317a-f4bb-42b6-8735-f536d40ad038_review-visme-puts-your-visuals-ahead-of-the-curve-02.jpg?auto=compress,format" alt=""></p>
<p>What makes Visme easy is that you can start from professional templates or from a blank canvas. Visme also has millions of assets available right inside the web app (including vector icons), so (for me) this was a process that used to jump between Google Images and Photoshop but now resides solely in one spot: Visme. That simplification made working on my images easy and (for once) something I actually <em>enjoyed</em> doing.</p>
<p><img src="https://images.prismic.io/andrewzigler/8f11d367-4d80-4e77-a109-6d3ab72197a3_review-visme-puts-your-visuals-ahead-of-the-curve-04.jpg?auto=compress,format" alt=""></p>
<p>Visme also has an animation engine on top of the visuals. Virtually any object can be animated with a couple clicks. Visme has over a hundred high-quality and popular fonts that can be applied with many properties and effects. There are a variety of font styles to satisfy any project need and the entire process is simple. You can also apply these effects and properties granularly to each individual text character, allowing for truly fine-tuned control. And for me, that's what makes Visme truly shine!</p>
<p><img src="https://images.prismic.io/andrewzigler/a731e002-4cd1-4e06-8a94-c09f50da076e_review-visme-puts-your-visuals-ahead-of-the-curve-03.jpg?auto=compress,format" alt=""></p>
<p>Branding is simple using the tool, and one of the best features I found for that was the global color library, which allowed me to pick and save colors to reuse within my project. If you're using Visme to create literal presentations, then it's also good to know that you can upload your own audio MP3 files to your visual presentation. Visme also provides a large library of audio tracks that can be attached to one slide (for presentations) or used as background music for an entire project.</p>
<p>As I mentioned initially, I had to methodically go through each social media image on my website and recrop it to fit perfectly in my new dimensions. If it didn't fit, I then used Visme to rebuild or completely remake the image but with correct proportions. I was able to lean on Visme's pixel-precise tools to create images that popped more. The abundance of fonts let me mix up some of what I was doing before. I completed the project quicker than expected so I even made some extra images!</p>
<p>If you're interested in checking out the tool, know that <a href="https://www.visme.co/pricing/">Visme is free for all users</a>! It takes only a few seconds to register and start creating your first project. Most of the features are free so it's easy to experiment with.</p>]]></content:encoded>
            <category>article</category>
            <category>Review</category>
        </item>
        <item>
            <title><![CDATA[JavaScript and Java Most in Demand Software Languages]]></title>
            <link>https://www.andrewzigler.com/feed/javascript-and-java-most-in-demand-software-languages</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/javascript-and-java-most-in-demand-software-languages</guid>
            <pubDate>Fri, 22 Nov 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[Programming languages JavaScript and Java are the most sought-after skills for IT professionals, according to a recent report from online academy Pluralsight. The report, called the Technology Index, breaks down the demand for a software developer’s proficiency in specific languages.]]></description>
            <content:encoded><![CDATA[<p>Programming languages JavaScript and Java are the most sought-after skills for IT professionals, <a href="https://www.techrepublic.com/article/the-most-in-demand-technologies-for-it-professionals/">according to a recent report from online academy Pluralsight</a>. The report, called the Technology Index, breaks down the demand for a software developer’s proficiency in specific languages.</p>
<p>While both languages have been around for over two decades, JavaScript and Java continue to be the most popular in the majority of ranking sites.</p>
<p><a href="https://dzone.com/articles/top-programming-languages-rankings">An analysis from DZone notes that learning Java</a> as your first language is probably the best choice if you’re planning to have a career as a developer. Moreover, a 4-year analysis of job postings from job platform Indeed indicates that both Java and JavaScript remain the most in-demand programming skills. Similarly, there are almost 5 million students <a href="https://www.udemy.com/topic/javascript/">learning JavaScript on Udemy</a>, which is a clear indication of how widely used the program is across industries. With a wide user base and tons of libraries created over the past three decades, both languages remain the most reliable and widely used.</p>
<h2>Battle for the Web: Java vs. JavaScript</h2>
<p>Many people still think the two are related but Java and JavaScript are two different languages altogether. Although they had a brief intersection in the early days of Netscape, the two top languages have diverted paths from thereon.</p>
<p>When JavaScript was first introduced in the 90s, it was packaged as a companion of the then-popular Java for web development. It was not until years later that <a href="">JavaScript was recognized as a programming language on its own</a>. Java, on the other hand, was a pioneer in web development as an object-oriented programming language.</p>
<p>While both languages have been in contention for the best language for web development for a long time now, JavaScript has emerged as the undisputed language for web applications. Around 94% of all webpages on the internet are made from JavaScript but it’s common to have them running Java on the back-end.</p>
<p>Five years ago, people would’ve made clear distinctions in terms of JavaScript being the language used for client-facing coding and Java for server-side coding. But the advent of Node.js made JavaScript a more efficient language to be used for back-end development. As a runtime environment, Node is a light, scalable, and cross-platform way to execute code. This marked the supremacy of JavaScript for the internet including enterprise web programs. With Vue.js and AngularJS frameworks for front-end and React for back-end, it has become the most versatile, fast, and scalable language for web platforms.</p>
<h2>Mobile development</h2>
<p>Part of what kept Java afloat despite the rise of JS is its platform Java Virtual Machine. The "write once, run anywhere" principle of Java made it a versatile and universally compatible language as long as the device has a Java Runtime Environment.</p>
<p>This important feature made it the best language to use for developing mobile applications. In fact, it’s <a href="https://www.androidauthority.com/develop-android-apps-languages-learn-391008/">the official language for Android development</a> and is supported by Android Studio. Around a fourth of the apps currently in the Google Play Store are made with Java and its frameworks.</p>
<p>In conclusion, both JavaScript and Java have stood the test of time. Their adaptability and capacity to integrate with new languages make them the most valuable skills for developers.</p>]]></content:encoded>
            <category>article</category>
            <category>Devlog</category>
        </item>
        <item>
            <title><![CDATA[Untitled Goose Game Review: Avian Agent of Chaos]]></title>
            <link>https://www.andrewzigler.com/feed/untitled-goose-game-review-avian-agent-of-chaos</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/untitled-goose-game-review-avian-agent-of-chaos</guid>
            <pubDate>Wed, 23 Oct 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[People online are losing their minds over a casual simulation game where you take control of a mischievous goose. In its first two weeks, Untitled Goose Game sold over 100,000 copies worldwide. If we hadn't before, we've now definitely crossed the Rubicon. With enough creativity, anything can be a game.]]></description>
            <content:encoded><![CDATA[<p>People online are losing their <em>minds</em> over a casual simulation game where you take control of a mischievous goose. In its first two weeks, <a href="https://goose.game/"><em>Untitled Goose Game</em></a> sold over 100,000 copies worldwide. If we hadn't before, we've now <em>definitely</em> crossed the Rubicon. With enough creativity, <em>anything</em> can be a game.</p>
<p>One of the unique attributes of being a goose is that most people ignore you unless you're causing trouble. Waddling through a world of people absorbed in their own tasks and leisure, you can fly mostly under the radar. When you intrude on their livelihoods or otherwise make a mess, they'll chase and shoo you away. They'll shake their fist and rue the day, but their responses are fully non-violent and realistic. People care about and react to you in more or less the same way they would react to a real live goose in that situation: they'll flap their arms until they scare you off. One meek boy in particular will flee from you and hide! Contrast this with other games that use direct conflict to drive story and provide gameplay, the situations of <em>Untitled Goose Game</em> become low-stakes and more like extended slapstick jokes. And it works. Magically. It would be absurd if it wasn't so realistic in the way the world reacts to you. It never tips into being unrealistic, which makes the experience that much more satisfying from the perspective of a goose. You're finally getting a piece of the pie from which all geese in this world freely eat: terrorizing humans for no discernible reason.</p>
<p>The game features an incredible and dynamic music score that reacts live to the choices you make. When you sneak up on someone or an objective, the pitter-patter of music will build and grow in turn. When you finally seize an opportunity or make some real trouble, the piano score will undergo a reactive crescendo that makes gameplay more like a performance you share with others. As a result, it's wickedly fun to play this game with an audience of friends. The musical choices of the game make revisiting the game enjoyable and always different.</p>
<p>The game is silly and creative, and I was laughing the whole way through. However, it's driven by the premise alone so once you complete all of the main tasks (and all of the bonus tasks) then there's very little reason to revisit it (other than to show your friends that you own <em>Untitled Goose Game</em>, of course). The pranks you can undertake are thrilling, but you'll hit a point where most everything has been done. The brief and endlessly funny interactions between the titular (untitular?) goose and the denizens of the village are charming but once you push all of everyone's buttons, there's very little else to try. The environment is peaceful and fun, which is why it's so fun to try and wreck the idyllic calmness of it all. The world is your canvas and the goose is your paint.</p>
<p>_Untitled Goose Game _drew similar attention as _<a href="http://goat-simulator.com/">Goat Simulator</a> _on social media, both being sandbox-style games in which the player is invited to create chaos as a semi-domesticated animal. Similarly, the contents of both games have become memes in their own right because of the almost-absurd nature of the tasks involved. Indeed, <em>Untitled Goose Game</em> invites memes with its relatively plain palette and simple graphics. The soft colors and flat greens make each moment a snapshot of a pastoral hamlet, almost lost in time with its narrow roads and winding aqueducts (on which you quickly sail away as a fast-swimming goose). The goose itself is white and simple, a most memetic representation of the bird there can be. <a href="https://twitter.com/Foone">@Foone</a> has created a <a href="http://deathgenerator.com/#ugg">meme generator</a> that lets you mock up a game image with your own <em>Untitled Goose Game</em> tasks (and even cross them out), furthering the meme potential. The simplicity of its presentation makes it just as engaging to watch as it is to play. If you want to get your feathers on some mischief, you can buy <em>Untitled Goose Game</em> now for PC, Mac, or the Nintendo Switch for only $20.</p>
<p><em>**2021 update: **Untitled Goose Game now has a local two-player co-op mode, so you can play the same game with a friend and cause trouble as a duo! The game tasks are ultimately the same, but having an extra beak allows for extra mayhem.</em></p>]]></content:encoded>
            <category>article</category>
            <category>Review</category>
        </item>
        <item>
            <title><![CDATA[Relaxing with CouchDB]]></title>
            <link>https://www.andrewzigler.com/feed/relaxing-with-couchdb</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/relaxing-with-couchdb</guid>
            <pubDate>Wed, 25 Sep 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[I'll admit it: databases scare me. I've always avoided databases in my projects because the idea of sorting and scheming all of my necessary data is an intimidating task. Up until now, if I needed to store data then I would save it to a local JSON file or put it in localStorage. However, you can only do that for so long until you start hitting limits in your implementation. So this month I've been dedicating myself to learning databases. Ideally, I just want to think in terms of JSON, so it's no real surprise that my first major database exploration has been with CouchDB, which is a NoSQL database maintained by the Apache Software Foundation. And I have to say, it's been a relaxing experience so far.]]></description>
            <content:encoded><![CDATA[<p>I'll admit it: databases scare me. I've always avoided databases in my projects because the idea of sorting and scheming all of my necessary data is an intimidating task. Up until now, if I needed to store data then I would save it to a local JSON file or put it in <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API">localStorage</a>. However, you can only do that for so long until you start hitting limits in your implementation. So this month I've been dedicating myself to learning databases. Ideally, I just want to think in terms of JSON, so it's no real surprise that my first major database exploration has been with <a href="http://couchdb.apache.org/">CouchDB</a>, which is a <a href="https://en.wikipedia.org/wiki/NoSQL">NoSQL database</a> maintained by the <a href="https://www.apache.org/">Apache Software Foundation</a>. And I have to say, it's been a <em><a href="http://docs.couchdb.org/en/stable/intro/why.html#relax">relaxing</a></em> experience so far.</p>
<p>Before I discuss CouchDB and why it's so great, it's important to define the bigger picture. Proposed in 1970, <a href="https://en.wikipedia.org/wiki/SQL">SQL</a> (Structured Query Language) is a programming language used to manage data in relational databases. Relational databases use relations (typically called tables) to store data, and they match that data by using common characteristics between them. SQL is used to create both data (in objects called tables) and the schema for that data, which describes fields as columns and a single record as a row.</p>
<p>Younger in comparison, <a href="https://en.wikipedia.org/wiki/NoSQL">NoSQL</a> (Not Only SQL, or Not SQL) was coined in 1998 and defines a database that is self-describing, which means it does not require a schema or enforce relations between tables in all cases. All of its entries (typically called documents) are JSON, which are complete data object entities that applications can read and understand. NoSQL refers to high-performance, non-relational databases that utilize not just document-based schemas but a wide variety of data models for its entries. These databases are prized for their ease-of-use, scaling performance, resilience under pressure, and ample availability.</p>
<p>A CouchDB server instance has databases, which in turn store documents. Each document has a unique identifier within the database, and CouchDB provides a <a href="https://en.wikipedia.org/wiki/Representational_state_transfer">RESTful</a> HTTP API for manipulating the server and its contents. Documents are the bread and butter of CouchDB, consisting of any number of fields and attachments that can contain any amount of various value types. Documents also include metadata that’s maintained by the database system (like revision history). Documents form the heart of the database and can change dynamically beyond their original definition. CouchDB applies document updates without locking the entry, to improve performance for all connections. So if another client editing the same document saves their changes first, the remaining client gets an edit conflict error when saving because the document metadata (revision history) has changed. To resolve the update conflict, the latest document version should be opened, the edits reapplied, and the update reattempted. Manipulating entire documents (as opposed to their properties) either succeeds entirely or fails completely. CouchDB never contains partially saved or edited documents because the document commitment system is <a href="https://en.wikipedia.org/wiki/ACID">ACID</a> (<em><strong>A</strong>tomic</em>, <em><strong>C</strong>onsistent</em>, <em><strong>I</strong>solated</em>, and <em><strong>D</strong>urable</em>).</p>
<p>CouchDB isn't the only NoSQL database out there. In fact, there are a lot! One of the most popular options is <a href="https://www.mongodb.com/">MongoDB</a>, which admittedly has more resources (i.e., a larger community with more tooling and marketing) than CouchDB. And MongoDB supports ad hoc querying, which makes the transition from SQL even easier if you're coming from that background. However, there is no <em>one</em> single perfect database for all scenarios. Each database type offers certain features and prioritizes certain tenants of database design. This is best explained by the <a href="https://en.wikipedia.org/wiki/CAP_theorem">CAP theorem</a>, which says it's impossible for a distributed data store to simultaneously provide more than two out of the following three guarantees: <em><strong>C</strong>onsistency</em> (each client always has the same view of the data), <em><strong>A</strong>vailability</em> (all clients can always read and write), and <em><strong>P</strong>artition tolerance</em> (the system works well across physical network partitions).</p>
<p>CouchDB favors availability. For example, CouchDB supports full replication to mobile devices and desktop, so if you need a local database on your mobile application (or if your desktop users need to work offline and sync their work back to a server) then you should use CouchDB. CouchDB uses a replication model called <a href="http://docs.couchdb.org/en/stable/intro/consistency.html">Eventual Consistency</a>. In this system, clients can write data to one node of the database without waiting for other nodes to come into agreement. The system incrementally copies document changes between nodes, meaning everything will <em>eventually</em> be in sync. Another important distinction is that CouchDB relies on <a href="https://en.wikipedia.org/wiki/B-tree">B-tree</a> indexes. This means that whether you have 1 entry or 15 billion, the querying time will always remain below 10 milliseconds. This is huge for performance, making CouchDB low latency and very appealing.</p>
<p>MongoDB, on the other hand, favors consistency. It uses a single authoritative main server that accepts all writes and updates its replicas accordingly. Replicas can be read, but they may possibly not be completely up-to-date at that specific point in time. Consequently, there is no versioning like in CouchDB. In fact, updates can happen in-place, using an algorithm to pad frequently growing documents.</p>
<p>There are drivers and tooling available for CouchDB in several languages, including JavaScript. CouchDB itself is manipulated via a RESTful API, and it's surprisingly easy to get started that way:</p>
<pre><code>const URL = "http://127.0.0.1:5984"

function createDB(dbName) {
    var req = new XMLHttpRequest();
    req.open("PUT", URL + "/" + dbName, true);
    req.setRequestHeader("Content-type", "application/json");

    req.send();
}

function updateDB(dbName, docName, data) {
    var req = new XMLHttpRequest();
    req.open("PUT", URL + '/' + dbName + '/' + docName, true);
    req.setRequestHeader("Content-type", "application/json");

    req.send(JSON.stringify(data));
}

createDB("baseball");
updateDB("baseball", "Rangers", {"pitcher":"Nolan Ryan"});
</code></pre>
<p>But building an interface like this from scratch is a huge hassle and I don't recommend it! Unless absolutely necessary, you should use a community-supported driver so your interface is well-developed and well-tested. When using CouchDB in JavaScript, I recommend <a href="https://pouchdb.com/">PouchDB</a>, which works both in the browser and in Node. By default, PouchDB ships with a <a href="http://www.w3.org/TR/IndexedDB/">IndexedDB</a> adapter for the browser and a <a href="https://github.com/google/leveldb">LevelDB</a> adapter in Node. By using the included adapters in both the browser and in Node, you can use PouchDB as an actual local database that's capable of syncing to a remote store, or you can simply use PouchDB as the interface for manipulating a remote store directly. PouchDB's API works the same in every environment, so you can spend less time worrying about browser/server differences and more time writing clean, consistent code. PouchDB can also be easily used with frameworks like React and Vue. You can either listen for events on a synced database and handle those events, or you can use a package like <em><a href="https://github.com/MDSLKTR/pouch-vue">pouch-vue</a></em> to handle the database directly in your components.</p>
<p>When I initially started digging into databases this month, I wasn't too sure how I wanted to practice my new skills. Coming from several website projects, I was more interested in working on a game or at least something more fun than functional, per se. The last game-related project that I worked on was my <a href="">Pinwheel MUD Engine</a>, and I still have much to do in terms of that project. However, the worldbuilding resources behind Pinwheel have been a personal project of mine for a long time, and many aspects are practically <em>begging</em> to be plucked out and formed into their own independent projects. My Pinwheel MUD is envisioned as a multiplayer hybrid of both an artificial life simulation and a role-playing game. Early in development, one of the creatures I built using the engine was a small human-like frog that exhibited a wide array of characteristics. The concept immediately gripped me and was both intuitive and fun to develop, so I'm expanding on them in a new project called <a href="">Froggins</a>.</p>
<p>In this game concept, you're charged with caring for a nomadic tribe of frog-like creatures who migrate across a marsh and exhibit a wide array of social traits. Part of the game is simulation-based and involves nurturing your little pets as you grow the tribe. Another part of the game is RPG-based and focuses on their adventures in the marshlands. Both gameplay modes meld together to create a story unique to the player. You can trade them with other players to cultivate desired traits within your tribe, and once your creatures mature then you can imbue them with heroic abilities and send them out on quests. The game concept is still in early development and I hope to provide more details in a future blog post.</p>
<p>The concept is inspired by early artificial life simulations like <a href="https://en.wikipedia.org/wiki/Creatures_(video_game_series)">Creatures</a> and browser-based virtual pet games like <a href="https://en.wikipedia.org/wiki/Neopets">Neopets</a>. Since you can train and battle your creatures, it's also a lot like <a href="https://en.wikipedia.org/wiki/Pok%C3%A9mon">Pokémon</a>. Virtual pets have always been fascinating to me because it's interesting how much we can grow to love a digital representation of a living thing. They're ultimately just 1s and 0s on a virtual machine somewhere but they can evoke emotions all the same.</p>
<p>CouchDB underpins the entire project, which persists on a server handling all game logic. Clients connect via the browser and relay user interactions back to the server, which then responds with the outcome. Building this server and it's corresponding client has been an eye-opening undertaking. One of the early obstacles involved creating a communication protocol that's shared between the WebSocket server and the Vue client. I standardized the format for sending and receiving these messages. And I call them <em>ribbits</em>, in celebration of the project's subject matter.</p>
<p>Simply speaking, CouchDB itself is an HTTP server that's capable of serving HTML directly to the browser. You can also attach binary files directly to a database, as well. As a result, you can use actually <a href="http://guide.couchdb.org/draft/standalone.html">serve any web application and its resources directly from a CouchDB instance</a>, removing the intermediary server component from any traditional client–server model. The loaded page can even talk directly to the database using the JavaScript served in the page itself, which completely eliminates the server layer in some implementations! CouchDB’s features are a foundation for building standalone web applications backed by a powerful database. Even CouchDB’s own built-in administrative interface, <a href="https://couchdb.apache.org/fauxton-visual-guide/">Fauxton</a>, is a fully functional database management application built using HTML, CSS, and JavaScript. CouchDB and web applications go hand in hand.</p>
<p>This type of application was once called a <a href="https://github.com/couchapp/couchapp">CouchApp</a> before the functionality was integrated into CouchDB's <a href="http://docs.couchdb.com/en/latest/ddocs/index.html">design documents</a>, which store JavaScript code in the database. When that happened, the use of CouchDB as a combined standalone database and application server became no longer recommended by CouchDB's maintainers. There are significant limitations to a pure CouchDB web server application stack, including security, templating, and tooling. The developers of CouchDB recommend that we use CouchDB only as the database layer, in conjunction with a web application framework like Vue, Angular, or React.</p>
<p>Having dipped my toes into the waters of NoSQL and CouchDB, my fears and anxieties about databases have washed away! I've set up my own CouchDB instance on a server and configured it for use on my local machine. Now that I've learned the basics, I can't wait to integrate it into my pre-existing projects and any new ones!</p>]]></content:encoded>
            <category>article</category>
            <category>Devlog</category>
        </item>
        <item>
            <title><![CDATA[Using Phaser 3 with Nuxt]]></title>
            <link>https://www.andrewzigler.com/feed/using-phaser-3-with-nuxt</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/using-phaser-3-with-nuxt</guid>
            <pubDate>Fri, 30 Aug 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[If you're not familiar with Phaser, it's an open source framework for creating browser games in Canvas and WebGL. It comes with a lot of functionality out of the box, allowing a developer to easily implement sprites, sounds, animations, particles, cameras, physics, user input, and other game logic. It's primarily used to create web games, but it's a rendering engine with built-in state logic so it can be used for a wide array of applications that need to marry visuals with user interaction.]]></description>
            <content:encoded><![CDATA[<p>If you're not familiar with <a href="https://phaser.io/">Phaser</a>, it's an open source framework for creating browser games in <a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API">Canvas</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API">WebGL</a>. It comes with a lot of functionality out of the box, allowing a developer to easily implement sprites, sounds, animations, particles, cameras, physics, user input, and other game logic. It's primarily used to create web games, but it's a rendering engine with built-in state logic so it can be used for a wide array of applications that need to marry visuals with user interaction.</p>
<p>It's a lightweight engine that's used by companies and hobbyists alike to create first-class web games. Nowadays, creating a web game allows for easy portability to other platforms, like apps for iOS and Android. The web is becoming something like a <em>lingua franca</em> for the many diverse technology platforms that we encounter in our lives.</p>
<p>I started playing around with Phaser simply out of fun. Last year, I worked with the <a href="https://tic.computer/">TIC-80 fantasy console</a> and ended up writing an entire <a href="">blog post about using TIC-80 in JavaScript</a>. I used JavaScript at the time simply because I had no interest in Lua. The challenge of using TIC-80 was embracing the restraints of a fantasy console, in a celebration of how things <em>used to be</em> coded. However, I quickly found myself fighting against those boundaries!</p>
<p>Now with Phaser, I'm working to leverage as much performance as possible from the browser. When I work with <a href="https://nuxtjs.org/">Nuxt</a>, I'm similarly doing everything I can to put the browser to work. For both types of frameworks, I'm trying to deliver a user experience that's engaging and memorable. Nuxt is incredibly good at maintaining state and displaying a responsive website, and Phaser is quite excellent at rendering visuals and understanding user input. I quickly saw a lot of potential in using them together, so I started integrating Phaser into this website — which is a Nuxt app!</p>
<p>By default, Nuxt will convert small images into a data URI, which allows the image to be inserted inline with the actual HTML and eliminates the need for an HTTP request. This can result in faster loading times and is a common practice on modern websites. It uses Base64 to encode binary data, and the resulting text has nothing but letters, numbers and the symbols "+", "/" and "=". It's a convenient way to transmit binary data because those 64 characters are common in many character sets and there's a low chance that your data will be corrupted on the other end of the wire. Base64 maps 3 bytes (8 x 3 = 24 bits) in 4 characters that span 6 bits (6 x 4 = 24 bits). The result looks something like "AAAGAAAABgCAYAAADimHc4AAA" and the image size actually becomes 4/3 = 1.3333333 times the original as a result of the encoding. For many web developers, eliminating the HTTP request outweighs the image bloat, especially when used only for small images. That's why Nuxt will automatically convert your small images into data URIs during its default <a href="https://webpack.js.org/">webpack</a> build process.</p>
<p>Base64 images can affect SEO because the encoded image will not be indexed by Google, so they won't show up in a Google image search, or any other search engine. There are many unimportant images on a page that will not affect you or your traffic if they weren't indexed, like social icons. All those little Twitter, Google+, Facebook, etc. images are really small and have no need to be indexed. In fact when you really look at the images on your page you may find many images that fall into this category. Images like those are just not important to your SEO but are still slowing down your pages. A page could have 8 or 12 unimportant images and each of those is an additional HTTP request that can be avoided with Base64.</p>
<p>So if you use Phaser on a Nuxt app with this default configuration, your small image resources will be compiled into data URIs. When your application attempts to load those assets, your console is going to throw this error:</p>
<pre><code>Local data URIs are not supported
</code></pre>
<p>Why does this happen? Like mentioned above, data URIs aren't necessarily more performant <em>just</em> because they eliminate an HTTP request. In the age of HTTP/2, we can send multiple requests in parallel over a single TCP connection in the browser. By encoding your images, you're bloating them and putting more strain on the end user's processor to run the application. This is going to throttle performance because your images are larger (due to the compression bloat) and <em>then</em> they have to be converted from Base64 back into a binary format, and <em>then</em> the image data must be extracted by Phaser. By converting an image into an data URI, you add an extra step to the process — and your users have to pick up the tab!</p>
<p>It's easy to extend the default webpack build in Nuxt, but in this case we can't just push a new build rule for images in <em>nuxt.config.js</em>, because the default loader configuration already handles images. Adding a new build rule wouldn't prevent the first one from converting your small images into data URI. Here's what that default configuration looks like:</p>
<pre><code>[
  {
    test: /\.(png|jpe?g|gif|svg|webp)$/,
    loader: 'url-loader',
    query: {
      limit: 1000, // 1kB
      name: 'img/[name].[hash:7].[ext]'
    }
  },
  {
    test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
    loader: 'url-loader',
    query: {
      limit: 1000, // 1kB
      name: 'fonts/[name].[hash:7].[ext]'
    }
  }
]
</code></pre>
<p>For both loaders, everything below 1 KB will be inlined as a Base64 data URI. The second loader configuration is for fonts and we can leave it alone. But the first loader configuration is for images. We need to exclude our Phaser assets directory from that first loader with a new rule in <em>nuxt.config.js</em>. While we're at it, we'll add a rule to load those Phaser assets with <a href="https://github.com/webpack-contrib/file-loader">file-loader</a>:</p>
<pre><code>build: {
  extend(config, ctx) {
    // exclude Phaser image assets from url-loader
    config.module.rules.forEach(rule => {
      if (rule.test.toString().includes('png')) {
        rule.exclude = /phaser/
      }
    })
    // load Phaser image assets
    config.module.rules.push({
      test: /\.(png|jpe?g|gif|svg|webp)$/i,
      loader: 'file-loader',
      options: {
        include: /phaser/,
        name: '[path][name].[ext]'
      }
    })
  }
}
</code></pre>
<p>We need to also add a build rule for handling our audio assets. Otherwise, Nuxt won't be able to load an mp3 or an <a href="https://en.wikipedia.org/wiki/Ogg">Ogg</a>:</p>
<pre><code>config.module.rules.push({
  test: /\.(ogg|mp3|wav|mpe?g)$/i,
  loader: 'file-loader',
  options: {
    name: '[path][name].[ext]'
  }
})
</code></pre>
<p>Now that we're correctly loading our assets, we need to create a plugin (<em>@/plugins/phaserLoader.js</em>) that shows Nuxt how to load a Phaser game:</p>
<pre><code>import Vue from 'vue'

Vue.prototype.$phaserLoader = {
  loadGame(loaderKey) {
    if (process.client) {
      return require(`@/assets/phaser/${loaderKey}/game`)
    }
  }
}
</code></pre>
<p>Once you do that, don't forget to include it in your <em>nuxt.config.js</em>:</p>
<pre><code>  plugins: [
    { src: '~/plugins/phaserLoader.js' }
  ]
</code></pre>
<p>By injecting this loader into the Vue scope, you can easily load any Phaser game in a component by providing a <em>loaderKey</em> to the <em>loadGame</em> function.  We must confirm we're client-side before the function runs, because otherwise Nuxt will attempt to load Phaser server-side and will throw reference errors (there is no <em>window</em>, for example, which is critical for Phaser).</p>
<p>Now we have everything needed to assemble a very simple Vue component (called <a href=""><em>phaser-container</em></a>) that can load our Phaser game and pass the <a href="https://vuex.vuejs.org/">Vuex</a> <em>store</em> to it:</p>
<pre><code>&#x3C;template>
  &#x3C;div :id="phaserContainer" />
&#x3C;/template>

&#x3C;scrip>
export default {
  name: 'PhaserContainer',

  props: {
    width: {
      type: Number,
      default: 800
    },
    height: {
      type: Number,
      default: 600
    },
    pageContainer: {
      type: String,
      default: 'container'
    },
    phaserContainer: {
      type: String,
      default: 'phaser-container'
    },
    fixedSize: {
      type: Boolean,
      default: false
    },
    game: {
      default() {
        return {}
      }
    }
  },

  destroyed() {
    this.gameObject.destroy()
  },

  mounted() {
    this.$nextTick(() => {
      // get page's main container (for sizing)
      const pageContainer = document.getElementsByClassName(
        this.pageContainer
      )[0]

      // launch game with resizing
      if (this.fixedSize === false) {
        this.gameObject = this.game.launch({
          width:
            pageContainer.clientWidth &#x3C; this.width
              ? pageContainer.clientWidth
              : this.width,
          height:
            pageContainer.clientHeight &#x3C; this.height
              ? pageContainer.clientHeight
              : pageContainer.clientWidth * 0.7 &#x3C; this.height
              ? pageContainer.clientWidth * 0.7
              : this.height,
          parent: this.phaserContainer,
          store: this.$store ? this.$store : null
        })
        // launch game without resizing
      } else {
        this.gameObject = this.game.launch({
          width: this.width,
          height: this.height,
          parent: this.phaserContainer,
          store: this.$store ? this.$store : null
        })
      }
    })
  }
}
&#x3C;/scrip>
</code></pre>
<p>The component can also be easily installed from npm (<a href="https://www.npmjs.com/package/@andrewzigler/phaser-container">@andrewzigler/phaser-container</a>) and used right in your Vue or Nuxt application. Here's an overview of the component:</p>
<pre><code>&#x3C;phaser-container
      width="800"
      height="600"
      :page-container="pageContainer"
      :phaser-container="phaserContainer"
      :fixed-size=false
      :game="componentPayload"
    />

width: (width of game in pixels, default: 800)
height: (height of game in pixels, default: 600)
pageContainer: (class name of parent element, for determining max sizes, default: 'container')
phaserContainer: (ID name of element to contain Phaser element, default: 'phaser-container')
fixedSize: (if true, prevents the component from resizing based on pageContainer, default: false)
game: (imported Phaser game file)
store: (Vuex store, default: undefined)
</code></pre>
<p>In the mounted hook, we're finding the current size of the bounding container (<em>pageContainer</em>) and then resizing the Phaser game based on those dimensions. This could be useful to creating a responsive Phaser game that works across different displays. You can also disable the resizing by changing <em>fixedSize</em> to true. In the example above, <em>payload</em> is passed to the <em>game</em> property. On my example page, <em>payload</em> is determined by a computed function:</p>
<pre><code>computed: {
    componentPayload() {
      switch (this.exampleComponentProps.type) {
        // if the page is loading a Phaser game
        case 'phaser': {
          this.$store.commit(
            'initializePhaser',
            this.exampleComponentProps.loaderKey
          )
          return this.$phaserLoader.loadGame(
            this.exampleComponentProps.loaderKey
          )
        }
        default:
          return {}
      }
    }
}
</code></pre>
<p>You'll recognize our <em>phaserLoader</em> from before. Here it's being used to actually load the game file so our page can use it! While not passed to the actual component, the <em>phaser-container</em> component will attempt to load your store into your Phaser game. It does this by appending the store to the Phaser game's <a href="https://photonstorm.github.io/phaser3-docs/Phaser.Scene.html#registry">registry</a> prop, which is an instance of <em>DataManager</em>, which has an <em>EventEmitter</em>. Since your store will be appended to Phaser's global event manager, you can easily set up an event bus to connect reactive data between Phaser and Nuxt.</p>
<p>Here's an example of how you can put this all together:</p>
<p><em>exampleGame/game.js</em>:</p>
<pre><code>import Phaser from 'phaser'
import BounceScene from './scenes/BounceScene'
import event from './event'

function launch({
  width = 800,
  height = 600,
  parent = 'phaser-container',
  store
}) {
  const game = new Phaser.Game({
    type: Phaser.AUTO,
    width,
    height,
    parent,
    physics: {
      default: 'arcade',
      arcade: {
        gravity: { y: 400 }
      }
    },
    scene: [BounceScene]
  })

  // replace the EventEmitter on the DataManager with our own imported EventEmitter
  game.registry.events = event

  // append the Vuex store to EventEmitter
  game.registry.events.store = store

  // if there is no pre-existing game state, initialize it
  if (!store.getters.phaser.exampleGame.bounces) {
    event.store.commit('savePhaser', {
      gameName: 'exampleGame',
      prop: 'bounces',
      value: 0
    })
  }

  return game
}

export default launch
export { launch }
</code></pre>
<p><em>exampleGame/events.js</em>:</p>
<pre><code>const { Events } = require('phaser')
const events = new Events.EventEmitter()

events.on('bounce', () => {
  let value = events.store.getters.phaser.exampleGame.bounces
  value++

  events.store.commit('savePhaser', {
    gameName: 'exampleGame',
    prop: 'bounces',
    value
  })
})

export default events
</code></pre>
<p><em>exampleGame/scenes/BounceScene.js</em>:</p>
<pre><code>import { Scene } from 'phaser'
import background from '../assets/background.png'
import ball from '../assets/ball.png'
import bounceMp3 from '../assets/bounce.mp3'
import bounceOgg from '../assets/bounce.ogg'

export default class BounceScene extends Scene {
  constructor() {
    super({ key: 'BounceScene' })
  }

  preload() {
    this.load.image('background', background)
    this.load.image('ball', ball)
    this.load.audio('bounce', [bounceMp3, bounceOgg])
  }

  create() {
    this.add.image(400, 300, 'background')

    const ball = this.physics.add.image(400, 200, 'ball')
    ball.setCollideWorldBounds(true)
    ball.body.onWorldBounds = true
    ball.setBounce(1)
    ball.setVelocity(300, 50)

    this.sound.add('bounce')
    this.physics.world.on('worldbounds', () => {
      this.registry.events.emit('bounce')
      this.sound.play('bounce', { volume: 0.6 })
    })
  }
}
</code></pre>
<p>Now that you can integrate your Phaser games into Vue and Nuxt, what will you make? Phaser continues to only get bigger and better. <a href="https://twitter.com/photonstorm">Richard Davey (also known as Photonstorm)</a> just announced this month that development will soon begin for Phaser 4, which will be supported in part by the Facebook Gaming and Instant Games teams. What's in store for the next major version of the framework? More details will be coming in the future, but in the meantime don't miss <a href="https://madmimi.com/p/4f5f0f">their announcement</a>!</p>]]></content:encoded>
            <category>article</category>
            <category>Devlog</category>
        </item>
        <item>
            <title><![CDATA[Adding Auth0 to a Nuxt app]]></title>
            <link>https://www.andrewzigler.com/feed/adding-auth0-to-a-nuxt-app</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/adding-auth0-to-a-nuxt-app</guid>
            <pubDate>Mon, 22 Jul 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[My latest web project required adding security and user authentication to a Nuxt app. The app is served as a static generated (pre-rendered) site on Netlify, which then "unfolds" into a full-fledged app upon loading. From then on, each successive route is explored on the live app in the user's browser and the user is therefore no longer navigating static files. This approach provides many benefits for SEO because the content of each discrete page is already embedded in a static generated file that represents the resource, and this improves both load times and Google's understanding of our website.]]></description>
            <content:encoded><![CDATA[<p>My latest web project required adding security and user authentication to a <a href="https://nuxtjs.org/">Nuxt</a> app. The app is served as a static generated (pre-rendered) site on <a href="https://www.netlify.com/">Netlify</a>, which then "unfolds" into a full-fledged app upon loading. From then on, each successive route is explored on the live app in the user's browser and the user is therefore no longer navigating static files. This approach provides many benefits for SEO because the content of each discrete page is already embedded in a static generated file that represents the resource, and this improves both load times and Google's understanding of our website.</p>
<p>I wanted my guests to create accounts and interact with my new website using an identity. This would also facilitate testing and debugging by allowing me to toggle certain states onto some users (such as admins and beta testers). After some initial research, I settled on <a href="https://auth0.com/">Auth0</a> and decided to use the official Nuxt <em><a href="https://auth.nuxtjs.org/">auth-module</a></em>. I went with Auth0 because it integrates easily with popular social media logins like Facebook, Google and more. User accounts are passwordless as a result, but you can also allow users to create accounts not tied to any pre-existing profiles. Auth0 will even host those user profiles in a database for you. With a free account you can handle up to 7,000 active users across two social media logins, which is more than enough for what I'm working on.</p>
<p>To begin, I installed the <em>auth-module</em> package and configured it for my application:</p>
<pre><code>auth: {
    strategies: {
      auth0: {
        domain: 'YOUR_AUTH0_DOMAIN',
        client_id: 'YOUR_CLIENT_ID'
      }
    },
    redirect: {
      callback: '/login/redirect',
      home: '/profile'
    },
    watchLoggedIn: false
  }
</code></pre>
<p>I'm using slightly different pages to facilitate my redirects, so I defined them here. I have also toggled off a property that automatically redirects on logout. This was causing complications with my static pages, which I'll talk about below. Then on each page that I wanted to require user authentication, I include the middleware:</p>
<pre><code>middleware: ['auth']
</code></pre>
<p>To initiate the login flow for a user, a button can use the Auth0 strategy we configured above with the module's <em>auth</em> instance, which is globally injected. You could even use the instance's properties to conditionally show the button to users that are not logged in, like so:</p>
<pre><code>&#x3C;v-btn v-show="!$auth.loggedIn" @click="$auth.loginWith('auth0')">Log in&#x3C;/v-btn>
</code></pre>
<p>Similarly, you can use the <em>auth</em> instance to facilitate logging the user out:</p>
<pre><code>&#x3C;v-btn v-show="$auth.loggedIn" @click="logout">Log out&#x3C;/v-btn>
</code></pre>
<p>But my approach for logging out ultimately involved wrapping that function and manually redirecting the user myself, to easily accommodate the project whether it's hosted locally or in production. The types of URLs the user might access the app from were also whitelisted in Auth0.</p>
<pre><code>methods: {
    logout() {
      this.$auth.logout()
      window.location.href = `https://YOURAUTHDOMAIN.auth0.com/v2/logout?returnTo=${location.protocol.substr(
        0,
        location.protocol.length - 1
      )}%3A%2F%2F${location.host}`
    }
  }
</code></pre>
<p>As I mentioned above, I encountered some funky behavior when testing <em>auth-module</em> on my static pages. In dev mode, the app flow worked perfectly, even without using a logout wrapper or configuring it as to not automatically redirect the user. This was because the login flow redirected the user to Auth0 for authentication, and upon returning to the app they were actually presented with a static page. This static page would then leap into action, but it would lose the user's authentication in the process. The result was a broken login process when uploaded to Netlify, which was no good! To fix this, I implemented mounted event hooks on the login pages. This is the hook for the actual login page, which automatically redirects the user if they're authenticated and sitting on the login page:</p>
<pre><code> mounted() {
    // schedule a redirect depending on login status
    setTimeout(() => {
      // only redirect if a logged-in user is lingering here
      if (this.$route.path.includes('/login')) {
        if (this.$auth.user) {
          this.$router.push('/profile')
        }
      }
    }, 1.5 * 1000)
  }
</code></pre>
<p>When Auth0 redirects the user, it passes the authentication token in as a URL query parameter back to the website. Since the <em>auth</em> instance isn't instantiated yet upon returning to the site, we need to manually check the URL and retrieve that parameter, which then gets passed into the <em>auth</em> instance. We then manually fetch the user with the <em>auth</em> instance and redirect if needed. This hook is for the login callback page, which is where the user lands upon being redirected back to the website from Auth0:</p>
<pre><code>mounted() {
    if (this.$route.hash) {
      this.$auth.setToken(
        'auth0',
        `Bearer ${this.$route.hash.split('=')[1].split('&#x26;')[0]}`
      )
    }
    // fetch user information from token
    this.$auth.fetchUser()
    // schedule a redirect depending on authentication results
    setTimeout(() => {
      // only redirect if the user is still here
      if (this.$route.path.includes('/login/redirect')) {
        if (!this.$auth.user) {
          this.$router.push('/login')
        } else {
          this.$router.push('/profile')
        }
      }
    }, 1.5 * 1000)
  }
</code></pre>
<p>Now that the login flow is working, we can start using the authenticated user object to enrich our app. First, I created a "Provide user and app metadata" rule on Auth0 that will pass the user's metadata from Auth0 to the app so we can use it however we need. The user can view and edit their own metadata, and each user can also have app metadata that they cannot edit. User metadata can be for preferences or settings, and app metadata can be used for intrinsic properties and permissions. You can add metadata to a user in the Auth0 dashboard or via the API. Here is the Auth0 rule, which you can add in the dashboard from their website:</p>
<pre><code>function (user, context, callback) {
  const namespace = 'YOUR_DOMAIN';
  context.idToken[namespace + 'user_metadata'] = user.user_metadata;
  context.idToken[namespace + 'app_metadata'] = user.app_metadata;
  
  callback(null, user, context);
}
</code></pre>
<p>With our new metadata, we can start building standard Vue functionality to compute values and display them within the app. Since I'm authenticating only with Facebook and Twitter, I have a simple check in place so I can quickly determine which of those sites the user logged in with. Auth0 gets the user's profile image from both sites, but I found the Facebook image to be very low quality and difficult to use. To rectify that, I created a computed property that will build a URL to a high-resolution version of that user's profile picture based on their Facebook ID (which we receive as part of their authentication). Here are some ideas to get you started:</p>
<pre><code>computed: {
    isAdmin(app) {
      if (
        app.$auth.user &#x26;&#x26;
        app.$auth.user['YOUR_DOMAIN/app_metadata'] &#x26;&#x26;
        app.$auth.user['YOUR_DOMAIN/app_metadata'].admin
      ) {
        return true
      } else {
        return false
      }
    },
    isFacebook: function(app) {
      if (app.$auth.user &#x26;&#x26; app.$auth.user.sub.split('|')[0] === 'facebook') {
        return true
      } else {
        return false
      }
    },
    facebookImage: function(app) {
      return app.$auth.user
        ? 'https://graph.facebook.com/' +
            app.$auth.user.sub.split('|')[1] +
            '/picture?width=9999'
        : ''
    },
    name: function(app) {
      return app.$auth.user ? app.$auth.user.name : ''
    },
  }
</code></pre>
<p>Adding Auth0 to Nuxt ended up being a surprisingly complex and interesting task. This was my first time adding authentication to an app, and I found that most of the learning curve came from deciphering how authentication really works in the browser using cookies, and how that gets thrown into question when leveraging Nuxt to statically generate a site. I found more issues upon deploying the static files, which helped me further understand how it works under the hood. The power of Auth0 has truly impressed me, and I already have a wealth of ideas for using the authentication service in the future!</p>]]></content:encoded>
            <category>article</category>
            <category>Devlog</category>
        </item>
        <item>
            <title><![CDATA[Publishing my first-ever npm packages]]></title>
            <link>https://www.andrewzigler.com/feed/publishing-my-first-ever-npm-packages</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/publishing-my-first-ever-npm-packages</guid>
            <pubDate>Sat, 22 Jun 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[I'm continuing to work on a few different Nuxt projects after creating this website, but I quickly hit a point where I was tempted to copy-paste code that I had already written. Knowing that, I paused my projects and returned to my website with the goal of compartmentalizing those useful bits for reuse. After all, this is why the JavaScript ecosystem uses packages!]]></description>
            <content:encoded><![CDATA[<p>I'm continuing to work on a few different Nuxt projects after creating this website, but I quickly hit a point where I was tempted to copy-paste code that I had already written. Knowing that, I paused my projects and returned to my website with the goal of compartmentalizing those useful bits for reuse. After all, this is why the JavaScript ecosystem uses packages!</p>
<p>I extrapolated the structured data component that I previously created into a Vue component called <a href=""><em>structured-data</em></a> (published on npm as a scoped package: <a href="https://www.npmjs.com/package/@andrewzigler/structured-data"><em>@andrewzigler/structured-data</em></a>). To achieve this, I stripped out a lot of my customized code for my own website and created an interface that accepts that data instead. This challenged me to make my own utility more universal, resilient, and composable. Getting this package on npm proved to be a huge endeavor, as it was my first-ever package and I used Babel to transpile the exported Vue component. It ended up being a challenging task but I learned a lot about publishing modern JavaScript and single-file components in the process.</p>
<p>Next, I looked at how I was assembling page metadata and realized that reusing my freshly-researched, fully-compliant SEO strategy would be really helpful for my future projects. If anything, it would allow me to easily update and maintain that code over time. I moved all of those functions into their own framework-agnostic library called <a href=""><em>head-helper</em></a> and published it on npm as my second scoped package: <a href="https://www.npmjs.com/package/@andrewzigler/head-helper"><em>@andrewzigler/head-helper</em></a>. This was a lot easier, as I didn't need to transpile and could simply export an ES6 class with static functions (JavaScript's equivalent of class methods).</p>
<p>After a little testing, I had this website back to full functionality but with all of its custom SEO functionality now imported from newly-externalized libraries. Now I can turn back to my other projects and easily import all of that hard work! It's great to find opportunities to reuse code I've created, and it shows I found a problem that needed addressing. I've grown rather fascinated with metadata and structured data so it was fun to apply those interests and create reusable tools.</p>]]></content:encoded>
            <category>article</category>
            <category>Devlog</category>
        </item>
        <item>
            <title><![CDATA[Enabling Google to crawl my Nuxt app for superior SEO]]></title>
            <link>https://www.andrewzigler.com/feed/enabling-google-to-crawl-my-nuxt-app-for-superior-seo</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/enabling-google-to-crawl-my-nuxt-app-for-superior-seo</guid>
            <pubDate>Thu, 23 May 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[2021 update: in the time since this blog post was first published in 2019, I've rewritten my blog with Gatsby! That said, I'm keeping this post available for historical purposes.]]></description>
            <content:encoded><![CDATA[<p><em><strong>2021 update:</strong> in the time since this blog post was first published in 2019, I've rewritten my blog with Gatsby! That said, I'm keeping this post available for historical purposes.</em></p>
<p>I'm in the final stages of <a href="">turning my website into a Nuxt app</a> and it feels like all my hard work is starting to pay off. When the site was previously statically generated on my computer using Hexo, it was relatively simple (but tedious) to also configure the pages for ideal SEO. Each page was a collection of partial templates that rendered with the page's details from the command line. From there, I created a simple JavaScript plugin for Hexo that allowed me to add the relevant metadata fields that I needed for each page, but it required defining those properties on the actual Markdown files for each blog post. Once again: tedious. Now that the website is an app, those pages are still statically generated but they're created via a web framework that's actually flattening each route of a single-page application into a static page. Nuxt is creating static HTML that transforms into a SPA upon loading. Talk about a turbo charge!</p>
<p>Single-page applications allow developers to connect data with a set of rules for display that data. Instead of tediously managing and curating each page as a standalone experience (and having to replicate any changes or fixes across all of those individual pages), I can define how the app should handle the content of the site depending on what it has available, what the user is seeking, and the size of the viewport. But single-page applications do all of this on the fly, so what happens when Google tries to crawl an SPA? The Googlebot understands and will parse JavaScript on a page, but that's not enough because an SPA usually lives entirely on just one page. Commonly, an SPA itself is emulating URLs in the browser using a router but it's not actually a collection of static page files for the Googlebot to crawl. When Google crawls an SPA, it only crawls the one page the actual app is embedded on, and even when that happens it's unlikely to render any of the dynamic goodies on the "home page" of that SPA. In short: Google sees next to nothing. <em><strong>2021 update:</strong> the Googlebot has become considerably better at rendering pages in JavaScript (like SPAs) to read their contents and follow links. In many cases, it's no longer necessary to precompile an SPA in order for Google to read it. That said, there are still indexing benefits to generating a static site upfront!</em></p>
<p>This is where we can leverage the power of Nuxt. Between a static HTML and a single-page application, Nuxt allows the best of both worlds because it pre-renders all pages to include the necessary HTML. Nuxt pre-renders each page server-side at the time of generation by querying the Prismic API to get my website's most up-to-date information, storing those records in the browser, and ultimately displaying the data in a variety of view states to the user depending on what route they're accessing.  That generation is happening on Netlify anytime I edit my website's content on Prismic or commit changes to the app's GitHub repository, so the generated content is always the latest. Once the page loads, any further navigation by the user results in the Vue app taking control of the browser, as opposed to navigating from static file to static file. The result is a much faster and seamless experience, as the app's state can change much quicker and with less resources than a new page load. And all of this happens seamlessly.</p>
<p>With Nuxt generating every possible route for my app, next I had to ensure the right metadata appeared on each type of page. I did this using <a href="https://vue-meta.nuxtjs.org/"><em>vue-meta</em></a>, which is included in Nuxt. I was able to define top-level metadata properties on the actual app, and pages downstream (children components) have their own overrides. The app uses the most specific metadata it can find for each route and component, so you can define something app-wide and then override it only where necessary by defining the property on a child. Using this concept, I sprinkled properties in the <em><head></em> element for each part of my website. There was even more metadata available to pack into my individual blog posts, so more fields were applied to those.</p>
<p>Finally, I used <a href="https://developers.google.com/search/docs/guides/intro-structured-data">structured data</a> to package all my available data into delicious morsels of rich markup for the Googlebot to parse. For example, when a blog post is displayed, the user sees the title, author, date, header image, and contents of that blog. Structured data allows the Googlebot to understand what information is actually being conveyed on each of my pages. I identified all of the possible types of <a href="https://schema.org/">Schema structured data</a> from my website and then wrote JavaScript to embeds relevant structured data on each page.</p>
<p>As a result, my web app is not only blazing fast (and acing every <a href="https://developers.google.com/web/tools/lighthouse/">Google Lighthouse</a> audit currently), but it's packed with contextual metadata and abiding by best SEO practices. Best of all, Google understands my site as well as any reader!</p>]]></content:encoded>
            <category>article</category>
            <category>Devlog</category>
        </item>
        <item>
            <title><![CDATA[Finding my style with Material Design]]></title>
            <link>https://www.andrewzigler.com/feed/finding-my-style-with-material-design</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/finding-my-style-with-material-design</guid>
            <pubDate>Tue, 30 Apr 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[2021 update: in the time since this blog post was first published in 2019, I've rewritten my blog with Gatsby! That said, I'm keeping this post available for historical purposes.]]></description>
            <content:encoded><![CDATA[<p><em><strong>2021 update:</strong> in the time since this blog post was first published in 2019, I've rewritten my blog with Gatsby! That said, I'm keeping this post available for historical purposes.</em></p>
<p>As I continue <a href="">converting this website into a Nuxt app</a>, I’m encountering design decisions that permeate deep into the style of my actual website. It’s amazing how dramatic the decisions about padding, font, and color can impact the aesthetic and usability of an interface. At this point, I'm more inclined to use an established UI framework that’s been brainstormed and battle-tested into something more polished, as opposed to making something from scratch. As a result, I’ve started learning <a href="https://material.io/">Material Design</a> as I assemble my website and blog with <a href="https://vuetifyjs.com/en/">Vuetify</a> components. <em><strong>2021 update:</strong> I've since learned a lot about styling and this website no longer uses Material Design! I've chosen to make my own styles instead.</em></p>
<p>Starting out, I spent a great deal of time creating mockups for my website in <a href="https://www.sketch.com/">Sketch</a> with the <a href="https://material.io/tools/theme-editor/">Material Theme Editor</a> plugin. I adapted my current website color palette into a new medley of pastels and grays that felt more modern when paired with the rounded corners of my new elements. One by one, I assembled the different components I would need in order to render my website on both desktop and mobile. It took some time, but I built mock-ups for every type of view for my website so I would be forced to make design decisions globally, not on the fly. I didn’t want to pigeonhole myself by making a quick decision that could ripple out and tarnish the entire design, so I worked on a canvas before I started writing HTML and CSS.</p>
<p>This approach worked well for me while learning the basics of Material Design, but as I finish this project and embark on my next endeavor, I know future implementations of Vuetify will be faster and cleaner as I continue to develop my own library of components. The power of prototyping in Sketch became evident when I wanted to make impactful changes, as I could globally change the colors, fonts, and other properties in a way that intelligently affects all of the components downstream. The plugin will even vertically re-center text if the new font has a different midline height. The ability to simulate a hot reload in Sketch enabled me to prototype ideas quickly and efficiently, but moving forward I'll likely just work directly in HTML and CSS.</p>
<p>My final challenge now is adapting text styles within the actual blog posts. I was using Markdown before, but now I’m using a rich text editor that actually serializes the content into HTML, and I can customize how that serialization is performed. I already have as much control as I need by styling the component that holds the actual blog post content, but I’ll also use the serializer to tag the generated HTML correctly and make special tweaks when appropriate. This will make the website appear more polished and professional.</p>
<p>In <a href="">my final blog post of this series</a>, I’ll be exploring the SEO changes I’ve made to the website as part of this conversion.</p>]]></content:encoded>
            <category>article</category>
            <category>Devlog</category>
        </item>
        <item>
            <title><![CDATA[Converting this website and blog into a Nuxt app]]></title>
            <link>https://www.andrewzigler.com/feed/converting-this-website-and-blog-into-a-nuxt-app</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/converting-this-website-and-blog-into-a-nuxt-app</guid>
            <pubDate>Sun, 31 Mar 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[2021 update: in the time since this blog post was first published in 2019, I've rewritten my blog with Gatsby! That said, I'm keeping this post available for historical purposes.]]></description>
            <content:encoded><![CDATA[<p><em><strong>2021 update:</strong> in the time since this blog post was first published in 2019, I've rewritten my blog with Gatsby! That said, I'm keeping this post available for historical purposes.</em></p>
<p>At the end of 2017, <a href="">I built this blog</a> with <a href="https://hexo.io/">Hexo</a> and migrated the rest of my site to use the same Markdown format. Creating the website this way comes with a lot of positives. One of those positives is that I'm able to control the entire build locally on my computer in a very formulaic way. It’s been fairly easy to make small adjustments to the build process and the resulting files, but generating a static site by an admittedly simple method has also impeded my ability to make this website more dynamic and interesting. But that’s all changing now, as I’m converting my website and blog into a pre-rendered <a href="https://nuxtjs.org/">Nuxt</a> app!</p>
<p>Due to the nature of the web, by the time you're reading this the entire site could already be this planned Nuxt app, or it could be something else entirely! _<strong>2021 update:</strong> how true indeed, as the website is now in Gatsby! _But as I write this, I’m still building the app locally and learning as I go. One of my objectives is to move the blog's build process off my hard drive and into a form of cloud hosting. To that end, I'm going to leverage the <a href="https://jamstack.org/">JAMstack</a>.</p>
<p>To house all of my content, I've adopted <a href="https://prismic.io/">Prismic</a> as my headless CMS. Prismic itself is a crafty API that lets you define a data structure and then build documents using those custom templates. You can use their API to query, organize, and display your documents however you want. For my purposes, I created one template for blog posts and another for portfolio projects.</p>
<p>In building those templates, I had to account for all of the fields currently represented on my blog before migrating the contents of each Markdown files into Prismic by hand. I recently completed that migration and now I’m bringing all of the content together in Nuxt.</p>
<p>I've decided to build the blog from scratch with my own Vue components so I can have granular control over the results. I don't want to use a template or a starter because I see it as an opportunity to make something myself and learn how all of the parts work. Once I have an idea of how to approach it, I can look at a more polished project and compare their execution to mine._ _Since I’m converting a pre-existing website and the resulting structure is changing slightly, I'm also faced with challenge of redirecting URLs so no SEO is lost. Thankfully, since I’ll be using <a href="https://www.netlify.com/">Netlify</a> to deploy the site now moving forward, I can easily set up those redirects with a file in the project repository.</p>
<p>When I get the site's framework finalized soon, the next task will be designing the pages. As a result, I’ll be taking a deep dive into styling Vue components in <a href="">my next post</a>!</p>]]></content:encoded>
            <category>article</category>
            <category>Devlog</category>
        </item>
        <item>
            <title><![CDATA[Web Components: spoils of the browser wars]]></title>
            <link>https://www.andrewzigler.com/feed/web-components-spoils-of-the-browser-wars</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/web-components-spoils-of-the-browser-wars</guid>
            <pubDate>Thu, 28 Feb 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[When discussed, “browser wars” typically has the connotation of our browsers fighting to get an edge on its competitors by developing or supporting new features. While the growing pains of this process have made designing for differing browsers sometimes tedious and unreliable, the war seems to finally be coming to an end and it’s time to reap the rewards.]]></description>
            <content:encoded><![CDATA[<p>When discussed, “browser wars” typically has the connotation of our browsers fighting to get an edge on its competitors by developing or supporting new features. While the growing pains of this process have made designing for differing browsers sometimes tedious and unreliable, <a href="https://www.dailymail.co.uk/sciencetech/article-6469151/The-end-browser-wars-Microsoft-switch-toapp-based-technology-Googles-Chrome.html">the war seems to finally be coming to an end</a> and it’s time to reap the rewards.</p>
<p>In 2019, <a href="https://caniuse.com/#search=es6">all modern browsers support ES6</a>, which allows for rapid development and cleaner syntax without needing to transpile for the browser. The ease of advanced JavaScript also brings with it more developed APIs and tools to support anything the website needs within the browser.</p>
<p>The rise of modules in the browser necessitates better encapsulation and versatility with how we solve web problems with JavaScript, and one solution is a new technology called <a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components">Web Components</a>.</p>
<h2><strong>What are Web Components?</strong></h2>
<p>Web Components are made of different web technologies that allow you to create reusable custom elements, and they'll be just as native as a <em><p></em> or <em><button></em> element. These custom elements keep their functionality encapsulated away from the rest of the code, allowing them to just be dropped into any website.</p>
<p>The different technologies that make up Web Components are:</p>
<ul>
<li><strong>Custom Elements:</strong> JavaScript APIs that allow you to define custom elements and their behaviour.</li>
<li><strong>Shadow DOM:</strong> JavaScript APIs for attaching an encapsulated “shadow” DOM tree to an element, which is rendered separately from the main document DOM. This shadow DOM controls the entire scope of the component, so you can keep an element’s features private and they can be scripted and styled without the fear of code collision.</li>
<li><strong>HTML Templates:</strong> The <em><template></em> and <em><slot></em> elements enable you to write markup templates that are not displayed in the rendered page. These can then be reused multiple times as the basis of a custom element’s structure. This essentially lets you “punch holes” in your component to let the native content of the page flow into or around your element.</li>
</ul>
<p>The most handy thing about Web Components is how simple they are to define and use. To instantiate a new component with a shadow DOM, you simply implement the following:</p>
<ol start="0">
<li>Create a class or a function in which you specify your web component functionality.</li>
<li>Register your new custom element using the <em>CustomElementRegistry.define()</em> method, passing it the element name to be defined, the class or function in which its functionality is specified, and optionally, from which element it inherits.</li>
<li>Attach a shadow DOM to the custom element using <em>Element.attachShadow()</em> method. Then you can add child elements and event listeners to the shadow DOM using regular DOM methods.</li>
<li>If needed, define an HTML template using <em><template></em> and <em><slot></em>. Again, you’d use regular DOM methods to clone the template and attach it to your shadow DOM.</li>
<li>Use your custom element wherever you like on your page, just like you would any regular HTML element.</li>
</ol>
<h2><strong>Tooling</strong></h2>
<p>In response to this new development environment with new potential, a group of open source developers have created <a href="https://open-wc.org/">open-wc</a>, which is a collection of generators, frameworks, documentation, and tooling for creating Web Components.</p>
<p>They establish a list of best practices that can help you get your first component off the ground, and I’ve found their resources to be very helpful in the rapidly-growing space. This is a relatively new project, so it’s a great opportunity to jump aboard if you’re seeking a new open source community on the web frontier!</p>
<p>The group was also featured today on <a href="https://blog.npmjs.org/post/182917093835/npm-convos-open-wc">the npm blog</a>, so I highly recommend checking them out!</p>
<h2><strong>Resources</strong></h2>
<p><strong>MDN:</strong> <a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components">Web Components</a></p>
<p><strong>Wikipedia:</strong> <a href="https://en.wikipedia.org/wiki/Browser_wars">Browser wars</a></p>
<p><strong>caniuse:</strong> <a href="https://caniuse.com/#search=web%20components">Web Components</a></p>
<p><a href="https://open-wc.org/"><strong>open-wc</strong></a></p>]]></content:encoded>
            <category>article</category>
            <category>Devlog</category>
        </item>
        <item>
            <title><![CDATA[Initial release of Pinwheel]]></title>
            <link>https://www.andrewzigler.com/feed/initial-release-of-pinwheel</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/initial-release-of-pinwheel</guid>
            <pubDate>Sat, 26 Jan 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[After about six months of work, my Pinwheel MUD engine (a variant of Ranvier) is finally in a usable, distributable state with the groundwork needed to add any variety of features. Up until now, I was rewriting all of the source files and making changes to the entities to support full persistence and a web server. I’ve also made numerous changes to the underlying architecture of the engine and reduced the discrepancies between player and non-player characters. As of now, it’s possible for someone to clone the repository, npm install, and with a single command launch a conjoined MUD-web server that’s ready for content and gameplay.]]></description>
            <content:encoded><![CDATA[<p>After about six months of work, my <a href="">Pinwheel MUD engine</a> (a variant of <a href="https://github.com/RanvierMUD/ranviermud">Ranvier</a>) is finally in a usable, distributable state with the groundwork needed to add any variety of features. Up until now, I was rewriting all of the source files and making changes to the entities to support full persistence and a web server. I’ve also made numerous changes to the underlying architecture of the engine and reduced the discrepancies between player and non-player characters. As of now, it’s possible for someone to clone the repository, npm install, and with a single command launch a conjoined MUD-web server that’s ready for content and gameplay.</p>
<p>I’ve been writing about <a href="">designing MUDs</a> and <a href="">our niche community</a> since the beginning of the project, and my fellow <a href="https://mudcoders.com/">MUD Coders Guildmates</a> have been amazing to chat with and bounce around ideas. <a href="https://mudcoders.slack.com/">Come say hi on Slack</a>!</p>
<p>If you’re unfamiliar, Pinwheel is a <em>new</em> <a href="https://www.andrewzigler.com/blog/2018/06/27/the-case-for-muds-in-modern-times/">MUD</a> engine built entirely in ES6 (JavaScript). It doesn’t require a database and can be online for players within moments of downloading. The engine makes decisions about world persistence and core features so you can focus on building your world and community.</p>
<h2><strong>Features</strong></h2>
<p>The version 0.5.0 release of Pinwheel has:</p>
<ul>
<li>Full game state persistence across sessions (areas, NPCs, players, and items)</li>
<li><a href="http://mud.wikia.com/wiki/DikuMUD">Diku</a>-esque commands</li>
<li>Customization of players and NPCs with species, archetypes, and traits</li>
<li>Usage of human-readable YAML for game data in bundles</li>
<li>Entity-based scripting with few differences between NPCs and players</li>
<li>Turn-based combat with abilities, spells, and disciplines</li>
<li>Usage-based improvement of skills with branching skill trees</li>
<li>Communication channels and player parties</li>
<li>Compatibility with the <a href="https://grapevine.haus/">Grapevine MUD Chat Network</a></li>
<li>Player accounts</li>
<li>Telnet and <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API">WebSocket</a> support</li>
<li>Data storage in JSON files, no database required</li>
<li>Web server bound to state (for a website and/or API)</li>
</ul>
<h2><strong>Roadmap</strong></h2>
<p>Pinwheel still has many features to be added in the future. Some of the upcoming tasks include:</p>
<ul>
<li>Refactor <a href="http://ranviermud.com/">Ranvier</a> into Pinwheel</li>
<li>Reduce the differences between player characters and NPCs</li>
<li>Convert class system into aspects, allowing fine-tuned customization of players and NPCs alike with stackable components (e.g., species, archetypes, and traits)</li>
<li>Rewrite foundation for new combat system</li>
<li>Add configurable time, seasons, and weather</li>
<li>Implement <a href="http://mud.wikia.com/wiki/Copyover_Recover">hot reloading</a> of bundles and core</li>
<li>Enable <a href="http://mud.wikia.com/wiki/Online_Creation">online creation (OLC)</a> of bundle content</li>
<li>Create customizable player organizations</li>
<li>Redesign quests into <a href="https://wiki.guildwars2.com/wiki/Dynamic_event">dynamic events</a></li>
<li>Add crafting, resources, and a basic economy</li>
<li>Expand emoting complexity with syntax for referencing the world</li>
<li>Build an API with <a href="https://expressjs.com/">Express</a> for the engine and game state, allowing extensibility beyond the original platform (e.g., apps, widgets, and third-party clients)</li>
<li>Expand the web server to allow for gameplay, building, and administration in the browser</li>
<li>Convert to <a href="https://docs.npmjs.com/about-packages-and-modules">NPM package</a></li>
</ul>
<h2><strong>Want to get involved?</strong></h2>
<p>I’d love to have more contributors for the project, and there are many ways to get involved regardless of your JavaScript proficiency. Please use the <a href="https://github.com/azigler/pinwheel/issues/new?assignees=&#x26;labels=&#x26;template=bug-report.md&#x26;title="><em>Bug Report</em></a> or <a href="https://github.com/azigler/pinwheel/issues/new?assignees=&#x26;labels=&#x26;template=feature-request.md&#x26;title="><em>Feature Request</em></a> issue templates on GitHub to discuss any bugs or potential contributions to Pinwheel. Feedback and discussions are encouraged!</p>
<p>Additionally, I’ll be creating tutorials and resources on this blog in the near future to help new users learn how to use Pinwheel, so stay tuned!</p>]]></content:encoded>
            <category>article</category>
            <category>Devlog</category>
        </item>
        <item>
            <title><![CDATA[My 2018 in review: Udacity and beyond!]]></title>
            <link>https://www.andrewzigler.com/feed/my-2018-in-review-udacity-and-beyond</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/my-2018-in-review-udacity-and-beyond</guid>
            <pubDate>Mon, 24 Dec 2018 00:00:00 GMT</pubDate>
            <description><![CDATA[We’re almost another revolution around the sun and a lot has changed for me this year. I won a Google scholarship, got a promotion, completed a Udacity Nanodegree, lost some weight, and worked on lots of cool side projects. Now as we slide through December, I’m looking back on this year and what it’s meant to me.]]></description>
            <content:encoded><![CDATA[<p>We’re almost another revolution around the sun and a lot has changed for me this year. I won a Google scholarship, got a promotion, completed a Udacity Nanodegree, lost some weight, and worked on lots of cool side projects. Now as we slide through December, I’m looking back on this year and what it’s meant to me.</p>
<h2><strong>Google and Udacity</strong></h2>
<p>In January of this year, <a href="">I was selected to participate in the Grow with Google Challenge Scholarship</a>, in which I completed a series of web development tracks with 10,000 other developers in the United States. At the end of the program, I was in the top 1,000 performers and <a href="">won a Udacity Mobile Web Specialist scholarship</a>, which <a href="">I completed in July</a>.</p>
<p>The continued education aspect of Udacity has been great, as the program coordinators frequently reconnect with alumni to provide resources and events. I really recommend <a href="https://www.udacity.com/">Udacity</a> to anyone looking to learn a new skill.</p>
<h2><strong>Fitness</strong></h2>
<p>I started a regular yoga practice this April that I’ve been sticking with for the year. Initially, I was doing at least 30 minutes a day every single day, but now I’ve settled into a 5-day weekly routine with longer sessions. For anyone looking to explore yoga in 2019, I highly recommend the videos at <a href="https://www.doyogawithme.com/">DoYogaWithMe</a>. I do a solo practice with these videos, and I’m particularly partial towards anything by <a href="https://www.doyogawithme.com/yoga-classes?field_media_duration_value=All&#x26;term_node_tid_depth=All&#x26;field_instructor_target_id=8&#x26;focus=All&#x26;style=All&#x26;field_subscribers_only_value=All&#x26;sort_by=created">Fiji McAlpine</a>!</p>
<p>Next year, I want to make more changes to my diet to complement what I’m doing on the mat. I’ve been exploring the idea of lifting weights, too. I’m heading into 2019 with a stronger focus on fitness than ever before, and I’m hoping to keep that momentum!</p>
<h2><strong>Work</strong></h2>
<p>This May, while completing my Udacity Nanodegree, <a href="">I also received a promotion at work</a>. While I wouldn’t say it’s because of the certification, my newfound knowledge certainly became central to my new job responsibilities. I’m now managing most of our digital assets and websites, as well as leading web app projects with our proprietary technology.</p>
<p>In the last two months, I’ve also assumed ownership of all AdWords advertising with the goal of expanding into Facebook soon. I’m excited to see where this next year takes me and what I might learn in helping our company. Being provided the opportunities to use and master these skills on the job has been so self-fulfilling, but it’s also drained a significant amount of my free time. I hope to keep working on my side projects next year, so I’ll have to make an effort to consistently carve out time!</p>
<h3><strong>Projects</strong></h3>
<p>Between completing the Google challenge round and starting my actual Udacity course, I started to develop an idle game in Vue called <a href="">Ophion</a>. This was my first time building anything in a JavaScript framework and it was a tough learning process! I was still getting used to ES6 and the project had a lot of initial growing pains. I learned so much while tinkering with the experimental game I was building, but I shelved it upon starting my Nanodegree. I’d like to pick it back up next year, but I have a feeling I’ll take what I learned and apply it to something else first.</p>
<p>Next, I garnered an interest in <a href="https://medium.com/@G05P3L/fantasy-console-wars-a-guide-to-the-biggest-players-in-retrogamings-newest-trend-56bbe948474d">fantasy consoles</a> and set out to build something "old" and different than the modern frameworks I was constantly using. A lot of the consoles are in Lua, but I found <a href="https://tic.computer/">TIC-80</a> in JavaScript and was able to stick with the language I’ve been using all year. I ended up participating in a quarterly FC_JAM, and you can read about my endeavors using <a href="http://localhost:4000/blog/2018/03/03/tic-80-in-javascript/">TIC-80 in JavaScript</a>.</p>
<p>Between those projects, I finished my Nanodegree before tabling both of the above projects to start working on <a href="">Pinwheel</a>, my highly opinionated fork of the <a href="http://ranviermud.com/">Ranvier MUD engine</a>. I eased into the process by exploring the general concepts behind MUDs in the age of modern video games. <a href="">I argue that MUDs provide a sense of <em>telepresence</em> unmatched by similar forms of media</a>, and I’m eager to explore the possibilities with modern technology, as the engine I’m using is built in JavaScript and has been heavily rewritten from the Ranvier original. <em>**2021 update: **as public interest in the metaverse continues to climb and tech companies set out to discover what that means, I still stand by this opinion!</em></p>
<p>In starting this project, I stumbled across <a href="https://mudcoders.com/?gi=f83e7cc7d963">The MUD Coders Guild</a>. We’re all interested in or working on MUDs, and we have a very active <a href="https://slack.mudcoders.com/">Slack</a>. I highly recommend stopping by if you’re reading this! <a href="">The developers in this group have built lots of new MUD technology</a> that connect resources between games in our very niche gaming community. <em>**2021 update: **there's also a <a href="https://discord.gg/a2pSAYqhNY">Discord server</a> from the <a href="https://www.reddit.com/r/MUD">MUD subreddit</a> that has many active text-based enthusiasts and developers.</em></p>
<p>I want to use these new technologies to make it easier to try out a MUD like my own and play it on a wide variety of devices, including mobile. I’m also building a web app to sit on top of my code to allow for another yet interface to the underlying engine. <a href="">I’ve blogged about my maxims and goals for this project</a>, and I know Pinwheel will continue to feature prominently on this blog in 2019. I’m currently nearing a v0.5 release with the initial rewritten skeleton. <em>**2021 update: **this <a href="">version of Pinwheel was released the following month</a>!</em></p>
<h2><strong>Blogging</strong></h2>
<p>I’ve also consistently maintained this blog this year, which is a big accomplishment for me! I’ve kept some blogs before but never with a static site generator. Designing it from scratch, coding the structure, styling the pieces, and maintaining the entire thing has made the blog a labor of love. <em>**2021 update: **the blog may be written in Gatsby now, but it's still full of love!</em></p>
<p>Besides this post, I’ve published <strong>14</strong> blog posts this year totaling more than <strong>25,000</strong> words! That’s a novella—or half a month of NaNoWriMo! I’ve posted at least one post per month for this entire year, which was a goal I set when the blog was created.</p>
<p>I’m exploring the idea of turning this entire blog (and my website) into a PWA (Progressive Web App) soon with <a href="https://vuejs.org/">Vue</a> and <a href="https://vuepress.vuejs.org/">VuePress</a>, but I’ll have to preserve the SEO structure I’ve built so far, because I’ve already accumulated nearly a hundred pages and Google is ranking this site in search results now! See you in 2019!</p>]]></content:encoded>
            <category>article</category>
            <category>Personal</category>
        </item>
        <item>
            <title><![CDATA[Carthaginian Empire in Spain: the Barcid Chessboard]]></title>
            <link>https://www.andrewzigler.com/feed/carthaginian-empire-in-spain-the-barcid-chessboard</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/carthaginian-empire-in-spain-the-barcid-chessboard</guid>
            <pubDate>Mon, 26 Nov 2018 00:00:00 GMT</pubDate>
            <description><![CDATA[This article will analyze the tragic Barca family and the accomplishments, flaws, defeats, and aspirations of this powerful Carthaginian clan. After Carthage was defeated by the Romans in the First Punic War, Carthaginian noble Hamilcar Barca conquered the Numidians and moved north into Spain in 237 BCE. He hoped to conquer new lands, subjugate the Celtiberian natives, and excavate the rich mines of the mountainous peninsula. All of these efforts were fueled by the empire’s recent defeat, with the newly-empowered Phoenician city-state requiring new territories and income to offset the costs of war.]]></description>
            <content:encoded><![CDATA[<p>This article will analyze the tragic Barca family and the accomplishments, flaws, defeats, and aspirations of this powerful Carthaginian clan. After Carthage was defeated by the Romans in the First Punic War, Carthaginian noble Hamilcar Barca conquered the Numidians and moved north into Spain in 237 BCE. He hoped to conquer new lands, subjugate the Celtiberian natives, and excavate the rich mines of the mountainous peninsula. All of these efforts were fueled by the empire’s recent defeat, with the newly-empowered Phoenician city-state requiring new territories and income to offset the costs of war.</p>
<p>In eight years, Hamilcar extensively claimed Hispania, but his death in 228 BCE left the task unfinished. After Hamilcar’s death, his son-in-law Hasdrubal the Fair succeeded him in the command and extended the newly acquired empire. He consolidated it with the foundation of Carthago Nova, establishing it as the capital of the new province. By signing a treaty with Rome, he fixed the Ebro River as the boundary between the two nations, his largest move of power until he was assassinated. Thus set the stage for one of the world’s greatest generals, Hamilcar’s eldest son Hannibal. Supposedly sworn by oath as a child to despise the Romans, Hannibal was raised in an environment of extreme Roman hatred. The Roman historian Livy placed Hannibal on a pedestal, making it more rewarding for the Romans to defeat him, and throughout his plight to conquer Spain and later Italy during the Second Punic War, it seems that Hannibal was comparable to a great Roman, the only flaw being that he was from the Punic Empire. In 201 BCE, his defeat at the Battle of Zama marks the end of the Carthaginian Empire and the loss of their territories in Spain to the Romans. Earlier in their conflicts with the Romans, Hasdrubal and Mago, the two other sons of Hamilcar, were killed, causing full responsibility to rest on Hannibal to complete the legacy of his family and nation.</p>
<p>Starting with Hamilcar, this article will follow the Barca family to Hasdrubal the Fair, then Hannibal, then to the actions of Hasdrubal and Mago, following them in age, not necessarily linearly. To set the stage, however, we must have a brief overview of the effects of the First Punic War. Perhaps the most immediate political result of the First Punic War was the downfall of Carthage’s naval power. Conditions signed in the peace treaty were intended to compromise Carthage’s economic situation and prevent the city’s recovery. The indemnity demanded by the Romans caused strain on the city’s finances and forced Carthage to look to other areas of influence for the money to pay Rome. Carthage was also seeking to make up for their recent territorial losses and to find a plentiful source of silver to pay the large indemnity owed to Rome. She turned her attention to Hispania, and in 237 BCE the Carthaginians, led by Hamilcar Barca, began a series of campaigns to expand their control over the peninsula. Thus sets the stage for the Barcid colonization in modern-day Spain.</p>
<p>The Barca family was a notable clan in the ancient city of Carthage, and, naturally, many of its members were fierce enemies of the Roman Republic. During the 3rd century BCE, the Barcids were one of the leading families in the ruling oligarchy of Carthage. They seem to have realized that the expansion of the Roman Republic into the Mediterranean Sea threatened the mercantile power of Carthage. Accordingly, they fought in the First Punic War and prepared themselves for the Second Punic War. The Barcids were the founders of several Carthaginian cities in the Iberian peninsula, some of which still exist today. The most famous example of this was Carthago Nova, which bears the modern name Cartagena, and also Barcelona.</p>
<p>The known members of this family were as follows: the patriarch, Hamilcar Barca (275-228 BCE), a Carthaginian general in the First Punic War. After the Roman victory, he expanded the colonial possessions in Hispania, where he drowned crossing a river during a battle. His wife’s name is unknown. They had six children, three daughters, and three sons of which became famous military leaders in their own right. Their three daughters married Barcid family allies. His second-eldest daughter was married to Hasdrubal the Fair. Hasdrubal the Fair (270-221 BCE), Hamilcar’s son-in-law, followed the latter in his campaign against the governing aristocracy at Carthage at the close of the First Punic War, and in his subsequent career of conquest in Hispania. After Hamilcar’s death, Hasdrubal succeeded him in the command and extended the newly acquired empire in the west by skillful diplomacy. He was supposedly killed by a Celtic assassin. Hannibal (247-182 BCE) was the oldest son of Hamilcar Barca, one of the best and most famous generals of classical antiquity, and arguably the greatest enemy of the Roman Republic. He won the famous Battle of Cannae but at the end lost the Battle of Zama, ending the Second Punic War. Hannibal was famous for his crossing of the Alps with 60,000 soldiers and 38 elephants, an unimaginable feat to the people of the ancient world, as will be looked at later in this article.</p>
<p>Hasdrubal (245-207 BCE) was the second son of Hamilcar Barca. He defended the Carthaginian cities in Hispania as Hannibal departed to Italy in 218 BCE. Leading reinforcements for his brother Hannibal in 207 BCE, he was defeated and killed in the decisive Battle of the Metaurus.</p>
<p>Mago (243 - 203 BCE) was the third son of Hamilcar Barca, and was present at most of the battles of his famous brother and played a key role in many of them, often commanding the forces that made the “decisive push.”</p>
<p>Hamilcar commanded the Carthaginian expedition to Spain in 237 BCE, and for eight years expanded the territory of Carthage in Spain. Hasdrubal was present when the Iberians ambushed the Carthaginian forces at Akra Leuka, modern Alicante. He, along with his brother Hannibal, escaped, as Hamilcar led the Iberians in the opposite direction and drowned in the River Jucar in 228 BCE. While he began the establishments in Hispania, it would ultimately be Hasdrubal that would truly set Carthaginian presence in Iberia in motion. Hamilcar is important in the grand portrait of Carthaginian action in Hispania because he was the aristocrat who first moved out to the west and established the roots of their new empire after the First Punic War. He also fostered the anti-Roman attitude to his sons, who later all became some of the most fearsome enemies Rome ever faced, and the collectively formed the most serious threat to Rome’s existence prior to its conquest outside of Italy.</p>
<p>Hamilcar’s son-in-law was Hasdrubal the Fair (270 BCE – 221 BCE), who was married to the second eldest daughter of the former. Hasdrubal followed Hamilcar in his campaign against the governing aristocracy at Carthage at the close of the First Punic War, and in his subsequent career of conquest in Hispania. Aside from establishing Carthago Nova, he was best known for establishing the Ebro River as the border between Roman and Punic lands in Hispania. This boundary would later be questioned and incite war between the two nations during the Siege of Saguntum. He was then supposedly killed by a Celtic assassin, as mentioned previously.</p>
<p>Hannibal (247 – 183 BCE), son of Hamilcar Barca, was a Punic Carthaginian military commander, and is generally considered one of the greatest military commanders in history. Hannibal lived during a period of great tension in the Mediterranean, when the Roman Republic established its supremacy over other great powers such as Carthage, the Hellenistic kingdoms of Macedon, Syracuse, and the Seleucid empire. One of his most famous achievements was at the outbreak of the Second Punic War, when he marched an army, which included war elephants, from Iberia over the Pyrenees and the Alps into northern Italy. In his first few years in Italy, he won three dramatic victories—Trebia, Trasimene, and Cannae, in which he distinguished himself for his ability to determine his and his opponent’s strengths and weaknesses, and to play the battle to his strengths and the enemy’s weaknesses—and won over many allies of Rome. Hannibal occupied much of Italy for 15 years, but a Roman counter-invasion of North Africa forced him to return to Carthage, where he was decisively defeated by Scipio Africanus at the Battle of Zama.</p>
<p>Upon the assassination of Hasdrubal in 221 BCE, Hannibal was proclaimed commander-in-chief by the army and confirmed in his appointment by the Carthaginian government. After he assumed command, Hannibal spent two years consolidating his holdings and completing the conquest of Hispania, south of the Ebro River. However, Rome, fearing the growing strength of Hannibal in Iberia, made an alliance with the city of Saguntum, which lay a considerable distance south of the River Ebro and claimed the city as its protectorate. Hannibal perceived this as a breach of the treaty signed with Hasdrubal and so he laid siege to the city, which fell after eight months. Rome reacted to this apparent violation of the treaty and demanded justice from Carthage. In view of Hannibal’s great popularity, the Carthaginian government did not repudiate Hannibal’s actions, and the war he sought was declared at the end of the year. Hannibal was now determined to carry the war into the heart of Italy by a rapid march through Hispania and southern Gaul.</p>
<p>Although the long-term consequences of Hannibal’s war are debatable, this war was undeniably Rome’s “finest hour.” Livy gives us the idea that he was extremely cruel. Any bias attributed to Polybius, however, is more troublesome, since he was clearly sympathetic towards Hannibal. Historical events, which led to the defeat of Carthage during the First Punic War when his father commanded the Carthaginian Army, led Hannibal to plan the invasion of Italy by land across the Alps. The task was daunting, to say the least. The alpine invasion of Italy was a military operation that would shake the Mediterranean World of 218 BCE with repercussions for more than two decades. It was a world war in the sense that it involved about three-quarters of the population of the entire Punic-Greco-Roman world and few people living in the Mediterranean were able to escape it. Virtually every family in Rome lost a member or members in the swath of destruction brought down on them by Hannibal and his Carthaginian armies.</p>
<p>He was defeated in 201 BCE at the Battle of Zama, denoting the end of the Carthaginian Empire and the loss of their territories in Hispania to the Romans. All of his opportunities to be such a powerful figure rose because of the actions of the Barca family in Hispania. The empowerment this elite clan received in the peninsula set the threatening war in motion and nearly crushed one of the world’s most iconic ancient civilizations.</p>
<p>Hasdrubal (245-207 BCE) was the second son of Hamilcar Barca, not to be confused with Hasdrubal the Fair. Much like how the Romans only had a handful of common first names for men, Carthaginians similarly pulled from a small list of names. He was one of the younger brothers of the much more famous Hannibal. Left in command of Hispania when Hannibal departed to Italy in 218 BCE, Hasdrubal was destined to fight for six years against the brothers Gnaeus and Publius Cornelius Scipio.</p>
<p>The expedition led by Gnaeus Scipio in 218 BCE had caught the Carthaginians by surprise, and before Hasdrubal could join Hanno, the Carthaginian commander on the North of Ebro River, the Romans had fought and won the Battle of Cissa and established their army at Tarraco and their fleet at Emporiae. In the spring of 217 BCE, Hasdrubal led a joint expedition north to fight the Romans. He commanded the army, while his deputy commanded the fleet. The army and the fleet moved north side by side and encamped on the mouth of the Ebro River. Carelessness of the Carthaginian fleet enabled Gnaeus Scipio to surprise the Carthaginians and crush their naval contingent at the Battle of Ebro River.</p>
<p>Hasdrubal retreated without fighting the Roman army. Hasdrubal was reinforced by 4,000 infantry and 500 cavalry and was ordered by the Carthaginian senate to march to Italy in 216 BCE. He left Himilco the Navigator, a fellow general, in charge at Carthago Nova and marched for the Ebro river, but was heavily defeated in the Battle of Dertosa in the spring of 215 BCE. This defeat prevented reinforcements reaching Hannibal from both Iberia and Africa at a critical moment of the war, when the Carthaginians held the upper hand in Italy. The Carthaginians from now were forced to contest the Romans in the area between the Ebro and Jucor rivers. In late 212 BCE, Hasdrubal, with timely cooperation from Mago Barca and Hasdrubal Gisco, completely routed his opponents at the Battle of the Upper Baetis, destroying the majority of the Roman army in Iberia and killing both the Scipios. Carthaginians gained control of Iberia up to the Ebro as a result of this victory.</p>
<p>However, the lack of cooperation between the Carthginian generals after the battle led the surviving Roman force of 8,000 retiring north of the river Ebro safely. These troops somehow managed to keep the Carthaginian armies from gaining a foothold north of the Ebro River. The Romans reinforced this detachment with 10,000 troops under Cladius Nero in 211 BCE and with another 10,000 soldiers under Scipio Africanus Major in 210 BCE, who spent the year training his army and improving his diplomatic contacts. The Carthaginian armies were subsequently outgeneraled by Scipio Africanus Major, who, taking advantage of the absence of the three Carthaginian armies being away in 209 BCE captured Carthago Nova and gained other advantages. Hasdrubal was defeated by Scipio at the Battle of Baecula, but managed to retreat with two-thirds of his army intact.</p>
<p>Later in 209 BCE, Hasdrubal was summoned to join his brother in Italy. He eluded Scipio by crossing the Pyrenees at their western extremity and safely made his way into Gaul in the winter of 208. Hasdrubal had waited until the spring of 207 to make his way through the Alps and into Northern Italy. He made much faster progress than his brother had, partly due to the constructions left behind by Hannibal’s army when he had passed via the same route a decade earlier, but also due to the removal of the Gallic threat that had plagued Hannibal during said expeditions.</p>
<p>The Gauls now feared and respected the Carthaginians, and not only was Hasdrubal allowed to pass through the Alps unmolested, his ranks were bolstered by many enthusiastic Gauls. Hasdrubal, in the same fashion as his brother, succeeded in bringing his war elephants, raised and trained in Hispania. However, Hasdrubal’s coordinating messengers were captured and he was ultimately met by two Roman armies and was forced to fight, being decisively defeated at the Battle of the Metaurus. Hasdrubal himself died bravely in the fight; he was beheaded, his head packed in a sack and thrown into his brother Hannibal’s camp as a sign of his utter defeat in 207 BCE, in stark contrast of Hannibal’s treatment of the bodies of fallen Roman Consuls.</p>
<p>Mago (243 - 203 BCE) was the third son of Hamilcar Barca and was present at most of the battles of his famous brother and played a key role in many of them. Little is known about his early years, except that, unlike his brothers, he is not mentioned during the ambush in which his father was killed in 228 BCE. Although Hasdrubal nominally commanded all Carthaginian forces in the Hispania, Mago received an independent command, a division which was to have grave consequences later. The two Barca brothers battled the Romans under the command of the Scipio brothers throughout 215–212 BCE. Mago, in a cavalry ambush of Publius Cornelius Scipio, killed 2,000 Romans near modern-day Alicante in 214 BCE, and also aided in keeping the Hispanic tribes loyal to Carthage. On the whole, the Carthaginians managed to maintain the balance of power in Hispania despite the efforts of the Scipios, but failed to send any aid to Hannibal.</p>
<p>Mago and Hasdrubal Gisco guarded the Carthaginian possessions in Iberia without difficulty, despite the Scipios outnumbering their armies during the absence of Hasdrubal. In 208 BCE, after the Battle of Baecula, Hasdrubal left Hispania to invade Italy and bring reinforcements to his brother Hannibal, who was operating in Lucania. Mago moved with his army to the area between Tagus and Douro rivers in a recruiting mission with Hanno, a newly arrived general. Their mission was successful, but they split the army into two camps and relaxed their vigilance. Mago managed to lead a few thousand survivors to Gades after being ambushed, where he joined forces with Hasdrubal Gisco. This strategy frustrated the strategy of Scipio to force a decisive battle that year, eluding the Roman tactics. Mago then led a campaign to invade Italy (this time by sea) with 15,000 men in early summer of 205 BCE. The Romans devoted seven legions to maintain watch over him and guard northern Italy, but no general action was fought.</p>
<p>In 204 BCE Mago was reinforced with 6,000 infantry and some cavalry from Carthage. The Romans refused to give battle and blocked Mago so he couldn’t reach Hannibal. Wounded in a battle in Cisalpine Gaul, Mago was recalled back to Carthage along with Hannibal to aid in its defense. Therefore, Mago and his army sailed from Italy in 202 BCE under the escort of the Punic fleet, and escaped the Roman navy as he made for Africa. Before arriving in Carthage, however, he died at sea. The ability of Mago as a field commander can be glimpsed from his actions at the battles of Trebbia and Cannae, where his failure might have doomed the Carthaginian army. He was a capable cavalry leader, as his repeated ambushes of the Romans in Iberia and Italy demonstrate.</p>
<p>Through this article the Barcid clan has been illustrated in both its positive and negative lights, following their epic genealogical journey through the conquering of Spain. As varied as the peaks and valleys of the peninsula’s geography, the Barca family had its shares of ups and downs during its conquering and subsequent lost of their western territories. Starting with Hamilcar and ultimately ending with Hannibal, this Carthaginian family was increasingly vigilant in their Iberian conquests, directly conflicting with the imperialistic attitude of Rome. Each member of the family represented a different fear or obstacle that Rome had to face before becoming the most powerful nation in the Mediterranean, and this war-hungry clan nearly conquered what we now consider one of the most imperialistic nations the world has ever seen. All of this was due to the rich environment of the Iberian Peninsula, insulating the locals and inciting the foreigners to war for her beauty, strength, and wealth.</p>]]></content:encoded>
            <category>article</category>
            <category>History</category>
        </item>
    </channel>
</rss>