Nicholas Clooney

blog: AgentOS: The Agent Environment That Gets Smarter As You Build

Published AgentOS: The Agent Environment That Gets Smarter As You Build, a post about the project environment I am building around AI agents so a fresh session does not have to rediscover the same context every time.

It uses ProjectSpire as the working example: instructions as project memory, plans for intent, Captain Logs for collaboration taste, devlogs for technical history, and skills or workflows for repeated mechanical steps.

The useful idea is that the repo should accumulate context as it is used, so the human still supplies the judgment, but the surrounding system gets better at carrying that judgment forward.

Earlier in thread

Nicholas Clooney

feature: Neow's Cafe Gets a Real Card Catalog

Neow's Cafe card catalog screen showing a two-column grid of Slay the Spire 2 cards Neow's Cafe card catalog screen with card filters open above the card grid Neow's Cafe card catalog screen showing filtered Slay the Spire 2 cards
Neow's Cafe browsing catalog-backed card data instead of bundled mock cards.

I spent today turning ProjectSpire's iOS app "Neow's Cafe" from a mock-card browser into something much closer to a real Slay the Spire 2 card catalog.

The main decision was to keep the first version boring in the best way: a static, versioned catalog generated from the game data, served locally, and loaded directly by the app instead of inventing a REST API too early.

This is what the folder structure looks like now:

		
  1. ## Catalog layout
  2. Generate a static catalog under a versioned root:
  3. ```text
  4. v0.103.2/
  5. manifest.json
  6. cards.index.json
  7. cards/<card-slug>.json
  8. images/card_portraits/...
  9. ```

That structure gave the app one small index for browsing and filtering, while keeping full per-card files and portrait assets nearby for detail/debug views later. The important bit is that the card grid does not need to fetch hundreds of separate files just to show the collection.

		
  1. `cards.index.json` is the grid, search, and filter payload. It contains all card summaries needed by the app:
  2. - id
  3. - slug
  4. - title
  5. - description
  6. - energy cost
  7. - type
  8. - rarity
  9. - pool
  10. - portrait path
  11. - optional detail path
  12. Keep individual card JSON files for detail and debug views, not for the main grid.

On the Swift side, CardCatalogService.swift now loads manifest.json, follows it to cards.index.json, and decodes the catalog into app cards. I also removed the old bundled sample portraits, so the app is now much more dependent on the generated catalog behaving like the source of truth.

The Cards screen got some polish too: the catalog can be refreshed from the view, the grid is now a two-column layout that preserves the card aspect ratio in CardsView.swift, and I cleaned up the filter model so "no filter" is represented by optional UI state instead of fake .all enum cases (filter cleanup commit).

The other nice bit from today is process-oriented: ProjectSpire now has Captain Logs for collaboration notes and a reusable workflow for turning a day's commits and documentation changes into these timeline summaries. That should make it easier to keep writing about the work without having to rediscover the shape of the day from raw git history every time.

Nicholas Clooney

thoughts: Codex vs Claude Code for ProjectSpire

After a few weeks working on ProjectSpire with Codex, I’m leaning toward it as my default for software engineering projects. The main frustration has been hitting the Pro account limit; otherwise the quality has been good, the interaction feels responsive, and the output gives me instant feedback while it works. Claude Code still feels more like a black box to me: it can disappear into minutes of research and thinking on its own, and the effective limit feels lower. That tradeoff matters, because for this kind of project I want a tight engineering loop more than a long silent reasoning pass.

Nicholas Clooney

feature: Card Parser v0.2.3 - Calculated Vars and New Formatters

Shipped Card Parser v0.2.3 to ProjectSpire, which adds calculated variable resolution, numeric symbol extraction, and conditional text formatters. Cards like Ashen Strike now show computed damage values instead of raw placeholders, and I've added choose, cond, inverseDiff, and boolean formatters for rendering conditional card text. The parser now threads card type, target type, and runtime display vars (HasRider, Sapping, Energized, etc.) through text resolution, making the pipeline much more precise about card state and context.

Human-AI collaboration: architect and developer

The whole card parser has been built in this mode: I act as architect, GPT-5.5 acts as developer. Every meaningful parser improvement came from me inspecting concrete generated JSON against real card examples and asking source-fidelity questions. GPT-5.5 didn't discover that cost upgrades can be negative, or that Bash's upgraded Vulnerable value wasn't being applied, or that X-cost cards needed their own shape. I did, by reading the output and comparing it to what the game actually does.

The pattern that emerged: I'd spot a class of issue on a specific card, explain what the game source was doing and why the output was wrong, and GPT-5.5 would produce a working fix. Then I'd push to turn each discovery into a repeatable check rather than a one-off patch. The coverage audit script, the unresolved placeholder CSV, the hard failures on missing source files: all of those came from me steering toward systemic fixes after catching individual bugs.

What GPT-5.5 is good at in this loop is the mechanical throughput. Regex extraction, threading new state through a resolution pipeline, mirroring changes to the audit script, regenerating 55 JSON files, splitting work into clean commits. The domain knowledge, the quality bar, and the architectural decisions all come from the human side. GPT-5.5 doesn't know what CalculatedVar means in the game engine or why display vars like HasRider matter for conditional text. It doesn't need to, once I describe the shape of the problem clearly enough.

The productivity gain isn't just speed. It's that I can stay at the architectural level, thinking about which cards are still wrong and why, without losing momentum to implementation mechanics. The feedback loop stays tight: inspect, identify, describe, implement, verify, repeat.

Example: resolved card output

Here's what a fully resolved card looks like now. Ball Lightning's resolved block shows the base and upgraded display states, with structured text runs that carry source variable references and style annotations:

		
  1. "resolved": {
  2. "base": {
  3. "title": "Ball Lightning",
  4. "cost": 1,
  5. "energy_cost": {
  6. "kind": "int",
  7. "value": 1
  8. },
  9. "description": {
  10. "plain": "Deal 7 damage.\nChannel 1 Lightning.",
  11. "runs": [
  12. {
  13. "text": "Deal "
  14. },
  15. {
  16. "text": "7",
  17. "source_var": "Damage"
  18. },
  19. {
  20. "text": " damage.\n"
  21. },
  22. {
  23. "text": "Channel",
  24. "style": "gold"
  25. },
  26. {
  27. "text": " 1 "
  28. },
  29. {
  30. "text": "Lightning",
  31. "style": "gold"
  32. },
  33. {
  34. "text": "."
  35. }
  36. ]
  37. }
  38. },
  39. "upgraded": {
  40. "title": "Ball Lightning+",
  41. "cost": 1,
  42. "energy_cost": {
  43. "kind": "int",
  44. "value": 1
  45. },
  46. "description": {
  47. "plain": "Deal 10 damage.\nChannel 1 Lightning.",
  48. "runs": [
  49. {
  50. "text": "Deal "
  51. },
  52. {
  53. "text": "10",
  54. "source_var": "Damage",
  55. "style": "green"
  56. },
  57. {
  58. "text": " damage.\n"
  59. },
  60. {
  61. "text": "Channel",
  62. "style": "gold"
  63. },
  64. {
  65. "text": " 1 "
  66. },
  67. {
  68. "text": "Lightning",
  69. "style": "gold"
  70. },
  71. {
  72. "text": "."
  73. }
  74. ]
  75. },
  76. "changed": [
  77. "description"
  78. ]
  79. }
  80. }
  81. }

Nicholas Clooney

feature: ProjectSpire STS2 resource recovery workflow

I worked with GPT-5.5 on a reproducible Slay the Spire 2 resource extraction plan and then landed it in ProjectSpire across the recovery scripts, allowlist, generated resource subset, image-format experiment, and workflow docs.

The Principles matter more than the files: keep the full recovered dump local and ignored, track only curated resources with a current use, make extraction scriptable instead of manual, prefer readable Python tooling, keep binary assets repo-friendly with WebP and Git LFS, and write down the decisions close to the evidence.

The implementation follows that shape by keeping Lab/unpacked/ as the local source dump and generating Lab/resources/ from Lab/resources.allowlist.yaml, starting with localization plus WebP q85 card portraits.

That gives my STS2 projects inside the ProjectSpire monorepo access to assets like this at roughly a fraction of the original size, around 10%, without needing to commit the full recovered dump.

Recovered Slay the Spire 2 card portrait for Believe In You
One of the recovered card portraits from the first curated resource subset.

You can find the commits/changes here: https://github.com/NicholasClooney/ProjectSpire/compare/a1fd19e...a1b6e9d

Nicholas Clooney

wip: ProjectSpire card data resolution note

I added a ProjectSpire design note in 399f74d that pushes the card pipeline toward a two-pass model: keep the parser output source-faithful, then resolve localization and rendered text separately for the app.

I created that work with GPT-5.5 in plan mode, and it asked a few genuinely useful clarification questions before I let it draft anything substantial, which made the whole process feel a lot more controlled than a blind codegen pass. I also pushed back on several of its first suggestions and made a lot of the consequential decisions myself, especially around keeping canonical variable names intact and separating raw data from resolved display data. That feels like a strong pattern for future ProjectSpire work: use the AI models to widen the search space, but keep the architecture decisions and edits grounded in my own judgment.

Nicholas Clooney

wip: ProjectSpire iOS card library foundations

I’ve been working on ProjectSpire’s iOS app (codename: Neow’s Cafe) in NicholasClooney/ProjectSpire as a 1:1 Slay the Spire 2 card library, and the useful part is not just the filtering UI and refactor cleanup, but the way I’m trying to work with AI.

I get better results when I lay down the foundations myself first, especially around quality, guard rails, and how the data is modeled, and then let AI work inside that framework instead of asking it to define the framework for me. It also helps a lot when I have AI propose higher-level API or contract changes before it starts making edits.

Here's a snapshot of the visual changes. There is also quite a bit of non-visual work too, like reorganizing the source files into clearer areas such as App, Components, Models, Views, Logic, and Dependencies, splitting the banner text into its own component, moving the app toward injected dependencies instead of hardcoded wiring, and a few other things.

...and the changes can be found here on GitHub

Nicholas Clooney

blog: The Confident Lie: What AI Got Wrong About @ViewBuilder

I published The Confident Lie: What AI Got Wrong About @ViewBuilder, a SwiftUI debugging note that came out of the ProjectSpire card view work. It captures a small but useful lesson: body gets @ViewBuilder from the View protocol, but a custom computed some View property needs the annotation explicitly if I want an if without an else. The compiler was right, the AI was overconfident, and now the mistake is written down somewhere I can find again.

Nicholas Clooney

wip: ProjectSpire extracted card model

I’ve moved the SwiftUI card view forward by adding a real Card model, so even though the screen does not look dramatically different yet, the app is much closer to rendering cards from extracted data instead of hardcoded values. There is even a small visual regression in the golden text compared with the previous screenshot, but the important change is underneath: I can now refine the card parser and JSON output models, bring those records and required images directly into the app, and aim to emulate any card regardless of rarity, type, or data shape. The relevant work is in the ProjectSpire compare for the card view changes and the new card model.

Updated SwiftUI Slay the Spire 2 card view powered by an extracted card model
New Card model version.
Previous comparison of a Slay the Spire 2 card in the game and the earlier SwiftUI card view
Previous visual pass for comparison.
Nicholas Clooney

wip: ProjectSpire SwiftUI card view

I’m recreating the Slay the Spire 2 card view in SwiftUI with assets extracted from the game, and I’m very happy with how close the first pass feels. The current work is captured in ProjectSpire snapshot/2026-04-28, especially CardView.swift; most of it is still hardcoded, but the visual foundation is there. Next I want to generalize it so the view can take a card data object and dynamically reload the text, colors, and assets, which might eventually turn into a Slay the Spire wiki app for the phone.

Comparison of a Slay the Spire 2 card in the game on the left and a matching SwiftUI card view on the right
Nicholas Clooney

note: Localization Formatters - Slay The Spire 2 Research Note

I published Localization Formatters - Slay The Spire 2 Research Note, a ProjectSpire note on how card localization formatter functions such as diff() are resolved and applied. GPT-5.5 researched and wrote the note, and I am honestly amazed by how well and how quickly it produced a detailed explanation from decompiled sources in minutes. This is exactly the kind of agent-assisted research loop that makes ProjectSpire feel much more possible.

Nicholas Clooney

idea: ProjectSpire mod tooling directions

I’m recording the ProjectSpire ideas now even though they’ve been rattling around for a while: an unofficial SpireAPI for mods, a REST layer on top of it, and a voice-command/accessibility layer that could eventually add Whisper-backed recognition and text-to-speech. Writing them down gives me one place to grow the monorepo instead of leaving the ideas scattered in my head.