# meaningfool - Full Content > Complete markdown content of all public articles and daily logs. Generated: 2026-03-03T15:38:42.098Z ## Site Information Personal website of Josselin Perrus, product manager in Paris. --- ## Articles ### Giving Agents the Ability to Point at Things **URL**: https://meaningfool.net/articles/detecting-bounding-boxes **Date**: 2026-03-03 **Type**: Article ### Giving Agents the Ability to Point at Things πŸ‘‰ **Working on illustrations revealed an unexpected limitation**: I worked with Nano Banana to create illustrations for my [Agent Frameworks for the Rest of Us](https://agent-frameworks-report.meaningfool.net/) report, and I tried to have Claude Code make programmatic edits on generated images such as resizing or repositinoning some elements. But that turned out to be very tedious, Claude Code being very imprecise. **Agents/LLM can't locate what they see on images.** Claude Code can "see" the image, it can describe it and its constitutive elements and their relative positioning, i.e. the composition of the image. But when you ask it to "select" an element, it fails quite miserably: the bounding box it produces is in the vicinity of the element but far enough off to be useless for pixel-level edits. **So how can we make agents better at locating things on an image?** Can we provide them with a "selection" tool that fixes the problem, like a calculator removes the need for LLMs to guess the results of arithmetic operations. I tested the following approaches: - **DINO model** (2023): a zero-shot object detection model - **DINO + Skill**: DINO augmented by a specific skill to compensate for its failure modes - **SAM3** (2025): the recent release of Segment Anything Model by Meta that does mask detection from prompts --- #### Eval-based approach **I needed a way to benchmark different ideas.** So I built a small eval harness that runs "selection" tasks against simple images populated with geometric shapes (circles, rectangles, triangles). The dataset is generated programmatically, and each case associates: - An image - A query ("blue circle," "small red triangle") - A ground-truth bounding box. ![Sample eval images across difficulty levels](../images/eval-sample-grid.png) **Two datasets, two difficulty levels:** - **Single-factor** (59 cases): varies one parameter at a time β€” canvas size, object count, target size, contrast level. The goal is to isolate what factors matter. - **Combined-factors** (31 cases after filtering ambiguous ones): stacks multiple hard factors together. Small targets on large canvases with low contrast and dense clutter. **The metric is IoU** (intersection over union) β€” how much the predicted bounding box overlaps with the expected one. --- #### Baselines: models without tools | Mode | Single-factor (IoU) | Combined-factors (IoU) | Failures | |------|:-------------------:|:------------:|:--------:| | Claude Sonnet (direct API) | 0.65 | 0.25 | 0% | | Gemini Flash (direct API) | 0.62 | 0.50 | 2% | | Claude Code harness (baseline) | 0.58 | 0.19 | 1% | --- #### Grounding DINO: good candidates, bad ranking **[Grounding DINO](https://github.com/IDEA-Research/GroundingDINO) is a specialized open-vocabulary object detector.** Given an image and a text query, it returns bounding box candidates ranked by confidence. It's available on [Replicate](https://replicate.com/adirik/grounding-dino) **Results: on its own, DINO underperformed the LLM baselines:** | Mode | Single-factor (IoU) | Combined-factors (IoU) | Failures | |------|:-------------------:|:------------:|:--------:| | Claude Code harness (baseline) | 0.58 | 0.19 | 1% | | Grounding DINO (top-1) | 0.53 | 0.18 | 0% | **But the issue is ranking, not detection.** Looking at DINO's top-5 candidates, the correct bounding box is in there ~84% of the time. The problem: DINO's confidence score doesn't reliably surface the best match. ![DINO selecting the wrong shape](../images/dino-wrong-shape-example.png) --- #### The skill: closing the agent loop **DINO and Claude Code had non-overlapping failure modes**: - Claude Code is good at spatial reasoning but cannot draw a bounding box - DINO identifies precise bounding boxes but can't reason about which one is the expected one. **The skill closes the loop**: 1. It calls Grounding DINO which output its top 5 predictions 2. The 5 bounding boxes are drawn on top of the image 3. Claude Code picks one candidate from overlay image **Results** | Mode | Single-factor (IoU) | Combined-factors (IoU) | Failures | |------|:-------------------:|:------------:|:--------:| | Claude Code harness (baseline) | 0.58 | 0.19 | 1% | | Grounding DINO (top-1) | 0.53 | 0.18 | 0% | | **Harness + skill** | **0.82** | **0.35** | **0%** | --- #### SAM-3: precise when it answers [SAM-3](https://ai.meta.com/sam2/) segments the image into regions and matches them to the prompt. Results: - **When SAM returns a result, it is remarkably precise.** - **But in 21% of cases SAM returned no masks at all.** The model didn't produce any segmentation for the query. | Mode | Single-factor (IoU) | Combined-factors (IoU) | Failure rate | |------|:-------------------:|:----------------------:|:------------:| | SAM-3 (when it answers) | 0.97 | 0.95 | β€” | | SAM-3 (overall) | 0.79 | 0.70 | 21% | | Harness + skill | 0.82 | 0.35 | 0% | ![SAM-3 failure cases with expected bounding boxes](../images/sam3-failure-grid-with-expected-bboxes.png) **SAM-3 failure modes:** SAM-3 fails mostly 1. When there is low contrast which distorts the "colors" and 2. When it is supposed to detect a shape of "lime" color. **Hypothesis:** failures might be resolved through better prompting, as SAM-3 is probably able to detect the expected shapes but may fail to match some colors in the prompt with the corresponding color in the image. --- #### What to keep in mind **The object-selection problem is similar to the letter-counting one.** LLMs can be great at the semantic level but fail at operating on the details (of words or images). And tools are here to the rescue. **Will multimodal models get better at localization natively?** 1- Gemini Flash 3.0 already showed solid localization capabilities in this eval. I did not test Gemini 3.1, but if it keeps getting better, the common case gets absorbed into native capabilities. Specialized detectors like DINO or SAM get pushed toward edge cases. 2- If object selection remains a weak spot, interfacing LLMs with SAM-3 or other small image models will keep making sense. --- ### Agent Frameworks for the Rest of Us **URL**: https://meaningfool.net/articles/agent-frameworks-for-the-rest-of-us **Date**: 2026-02-13 **Type**: Article ### Agent Frameworks for the Rest of Us #### Agent Frameworks Landscape **How do you build an AI agent?** There are all these frameworks (LangChain/LangGraph, Vercel AI SDK, PydanticAI, Claude Agent SDK, Mastra, OpenCode) to help you build agents. What actually makes them different from one another? The present report shares my findings diving into Agent Frameworks. **Who is the "Rest of Us"?** People that are not technical enough to just "get it". I am a PM, somewhat technical but not overly so. And as such this report certainly contains inaccuracies or errors. Feedback will improve it :) **Mapping the frameworks**: I need maps to orient myself. I've come to think of frameworks as belonging to one of the following 3 categories. - **Orchestration frameworks**: LangGraph, PydanticAI, Mastra, Vercel AI SDK - **Agent SDKs**: Claude Agent SDK, Pi SDK - **Agent servers**: Opencode More specifically, frameworks are positioned based on 2 questions: - **Where does orchestration live?** Orchestration *outside* the agent loop (app-driven) ↔ orchestration *inside* the agent loop (agent-driven). - **Where is the agent boundary?** Agent *IN* the app (agent-as-a-feature) ↔ agent *IS* the app (agent-as-a-service). ![Framework positioning map](../images/framework-map.webp) **What you can expect** in the following parts: - Part 1: What "agent" means β€” the three ingredients every agent is made of. - Part 2: How frameworks differ in who decides what happens next β€” your code or the model. - Part 3: Why the most powerful agent tool is a Unix shell, and what that implies. - Part 4: What it takes to turn an agent library into an agent server. - Part 5: Real projects that made different architectural choices, and what you can learn from them. --- ### Part 1 - What's an agent, anyway? **An agent is an LLM running tools in a loop**: - [Simon Willison](https://simonwillison.net/2025/Sep/18/agents/)'s one-liner β€” "An LLM agent runs tools in a loop to achieve a goal" β€” has become the closest thing to a consensus definition. - [Harrison Chase](https://blog.langchain.com/deep-agents/) (LangChain) said the same thing differently: "The core algorithm is actually the same β€” it's an LLM running in a loop calling tools." **So an agent has 3 ingredients**: - An LLM - Tools - A loop. Let's unpack each one. #### What an LLM does **An LLM is a text-completion machine.** You send it a chain of characters. It predicts the next most probable character, then the next, until it stops. When you ask a question, the sequence of most probable next characters is likely to be a sentence that resembles an answer to your question. **An LLM can only produce text.** It cannot browse the web. It cannot run a calculation using a program. It cannot read a file or call an API. #### What a tool is A tool gives an LLM capabilities it does not have natively. Tools enable: - **Things LLM cannot do:** access the internet, query a database, execute code. - **Things LLM do badly:** arithmetic, find exact-matches in a document... **The LLM cannot execute tools on its own though:** - The LLM returns a structured object specifying which tool to call. - The host program runs the tool. - The host program passes the result back to the LLM. #### How LLMs learned to call tools We said that an LLM can only produce text. So how does it ask for calling a tool? Does it return a text saying "I need to run the calculator" or something like that? **To call a tool, the LLM returns a JSON object** that says which tool it wants to run, and with which parameters. For example, if the LLM wants to check the weather in Paris, instead of responding with text, it returns something like: ```json { "type": "tool_use", "name": "get_weather", "input": { "city": "Paris" } } ``` But how did the LLM learn to generate such JSON objects as the *most likely chain of characters* in the middle of a conversation in plain English? **The original training data contains no tool-calling examples.** Nobody writes "output a JSON object to invoke a calculator function" on the internet. **LLMs are specifically trained to learn when to use tools** through fine-tuning on tool-use transcripts: - The models are trained on many examples of conversations where the assistant produces structured function invocations, receives results, and continues. OpenAI shipped this first commercially (June 2023, GPT-3.5/GPT-4), and other providers followed. - The model does not learn each specific tool. It learns the *general pattern*: when to invoke, how to format the call, how to integrate the result. - The specific tools available are described in the prompt β€” the model reads their names, descriptions, and parameter schemas as text. **Tool hallucination is a consequence of tool training.** The model can generate calls to tools that were never provided, or fabricate parameters. UC Berkeley's [Gorilla project](https://gorilla.cs.berkeley.edu/) (Berkeley Function-Calling Leaderboard) has documented this systematically β€” it is one reason agent frameworks invest in validation and error handling. #### The two-step pattern **When you call an LLM with tools enabled, two things can happen:** 1. The model responds with **text** β€” it has enough information to answer directly. 2. The model responds with a **tool-call request** β€” a structured object specifying which tool to call and what arguments to pass. If the model requests a tool call, *your code* executes it. You send the result back as a follow-up message. The model uses that result to formulate its answer β€” or to request yet another tool call. **Tool use always involves at least two model calls.**: - The first model call returns a tool call request - The second model call is provided the conversation + the result of the tool call. ``` messages = [system_prompt, user_message] ### LLM Call 1 β€” send the conversation + list of available tools response = llm(messages, tools=available_tools) ### Did the model respond with text, or with a tool-call request? if response.has_tool_call: tool_call = response.tool_call result = execute(tool_call.name, tool_call.arguments) messages.append(tool_call) messages.append(tool_result(tool_call.id, result)) # LLM Call 2 β€” send the conversation again, now including the tool result response = llm(messages, tools=available_tools) ``` #### The agentic loop **Many tasks require more than one tool call.** A coding assistant might read a file, edit it, run the tests, check output, fix a failing test β€” all in sequence. The model cannot know in advance how many steps it will need. **The solution: wrap the two-step pattern in a loop.** ``` messages = [system_prompt, user_message] loop: response = llm(messages, tools=available_tools) if response.has_tool_calls: for call in response.tool_calls: result = execute(call.name, call.arguments) messages.append(call) messages.append(tool_result(call.id, result)) continue break # no tool calls β€” done print(response.text) ``` **The model loops:** calling tools, receiving results, deciding what to do next, until it produces text instead of another tool call. **In practice, you add guardrails:** a maximum number of iterations, a cost budget, validation checks. But the core mechanism is the same. **What this looks like in practice.** Here is a simplified trace of an agent booking a restaurant. Each block is one iteration of the loop: ``` User: "Find me a good Italian restaurant near the office for Friday dinner, 4 people." Agent: [tool: search_web("Italian restaurants near 123 Main St")] β†’ 3 results: Trattoria Roma, Pasta House, Il Giardino Agent: [tool: get_reviews("Trattoria Roma", "Pasta House", "Il Giardino")] β†’ Trattoria Roma: 4.7β˜…, Pasta House: 3.9β˜…, Il Giardino: 4.5β˜… Agent: [tool: check_availability("Trattoria Roma", friday, party=4)] β†’ available at 7:30 PM and 8:00 PM Agent: "Trattoria Roma is the best rated (4.7β˜…) and has two slots Friday for 4: 7:30 PM or 8:00 PM. Want me to book one?" ``` **Four loop iterations.** Three tool calls, then a text response that ends the loop. The agent decided which restaurants to look up, which one to check availability for first (the highest rated), and when it had enough information to stop. The program just ran the tools and passed results back. #### What to keep in mind - **An agent is an LLM + tools + a loop.** Every agent framework β€” PydanticAI, LangGraph, Claude Agent SDK, OpenAI Agents SDK β€” implements some version of this loop. They differ in what they build *around* it. - **Tool calling is a two-step pattern.** The model requests, your code executes, the result feeds back. - **The model decides when to stop.** In the simplest case, it stops when it produces text instead of a tool call. But in real systems, stop conditions can be implemented based on budgets, validation, user acceptance, timeouts. --- ### Part 2 - Where orchestration lives **Frameworks differ on who owns the orchestration**: the app (orchestration frameworks) or the agent (agent sdks)? #### From prompting to agents ![Progression from prompting to agents](../images/progression-timeline.webp) ##### 1️⃣ The Massive Prompt At first people would cram all the instructions, context, examples, and output format into a single call and hope the LLM would get it right in one pass. **This was brittle:** - LLMs were unreliable on tasks that require multiple steps or intermediate reasoning. - Long prompts produced less predictable output: some parts of the prompt would get overlooked or confuse the model. The longer the prompt, the less consistent the results over multiple runs. - Long prompts were easy to break: even small changes could alter dramatically the behaviour. ##### 2️⃣ The Prompt Chain Getting better results meant breaking things down. Instead of one monolithic prompt, you split the task into smaller steps β€” each with its own prompt, its own expected output, and its own validation logic. The output of step 1 feeds into step 2, and so on. With prompt chaining each step has a narrow, well-defined responsibility. ##### 3️⃣ The Workflow Once you add tool calling, each step in the chain can now do real work β€” query a database, search the web, validate data against an API. The chain becomes a workflow: a sequence of steps implementing the agentic loop, connected by routing logic. ##### 4️⃣ The "General Agent" With better models, another option emerged: instead of defining the workflow step by step, give the agent tools and a goal, and let it figure out the steps on its own. We are somewhat back to 1️⃣ β€” one prompt, one call β€” but with the addition of tool calling and much better (thinking) models. This is agent-driven control flow, and it coexists with workflows rather than replacing them. ##### An "orchestration" definition Whether you define the workflow yourself (steps 2️⃣ and 3️⃣) or let the agent figure it out (step 4️⃣), someone has to decide the structure β€” the sequence of actions that leads to the outcome. That's what orchestration means. > **Orchestration** is the logic that structures the flow: the sequence of steps, the transitions between them, and how the next step is determined. This section focuses on the question: who owns that logic? who owns the control flow? - **App-driven control flow**: the logic is decided by the developer and "physically constrained" through code. - **Agent-driven control flow**: the logic is suggested by the developer and it is left to the LLM / agent to follow the instructions. #### App-driven control flow **Within the app-driven control flow, the app owns the state machine**: - The developer defines the graph: the nodes (steps), the edges (transitions), the routing logic. - The LLM is a component called within each step but the app enforces the flow defined by the developer. ![App-driven workflow graph](../images/workflow-graph.webp) Anthropic's ["Building Effective Agents"](https://www.anthropic.com/research/building-effective-agents) blog post catalogs several variants of app-driven control flow: - **Prompt chaining** β€” each LLM call processes the output of the previous one. - **Routing** β€” an LLM classifies an input and directs it to a specialized follow-up. - **Parallelization** β€” LLMs work simultaneously on subtasks, outputs are aggregated. - **Orchestrator-workers** β€” a central LLM breaks down tasks and delegates to workers. - **Evaluator-optimizer** β€” one LLM generates, another evaluates, in a loop. **Orchestration frameworks provide the infrastructure for building these workflows.** They abstract the plumbing so that developers can focus on the workflow logic. More specifically they handle: - Parsing tool calls, feeding results back into the next model call. - Stop conditions, error handling, retries, timeouts. Here is schematically how the developer would implement the restaurant reservation workflow: ``` workflow = new Workflow() workflow.add_step("search", search_restaurants) workflow.add_step("get_reviews", fetch_reviews) workflow.add_step("check_avail", check_availability) workflow.add_step("respond", format_response) workflow.add_route("search" β†’ "get_reviews") workflow.add_route("get_reviews" β†’ "check_avail") workflow.add_route("check_avail" β†’ "respond") result = workflow.run("Italian restaurant near the office, Friday, 4 people") ``` On top of that, the developer defines the functions for each step. For example, `search_restaurants` might use the LLM internally to parse search results: ``` function search_restaurants(query, location): raw_results = web_search(query + " near " + location) parsed = llm("Extract restaurant names and addresses from: " + raw_results) return parsed ``` How the main orchestration frameworks compare: - **LangGraph** (Python + TypeScript) was the first such framework. You wire every node and edge by hand. - **PydanticAI** (Python) takes a different approach: graph transitions are defined as return type annotations on nodes, so the type checker enforces valid transitions at write-time. - **Vercel AI SDK** (Typescript) started as a low-level tool loop + unified provider layer, then added agent abstractions in v5-v6 (2025). - **Mastra** (Typescript) builds on top of Vercel AI SDK β€” it delegates model routing and tool calling to the AI SDK and adds the application layer on top (workflows, memory, evaluation). There are other such orchestration frameworks. Cues to recognize app-driven control flows: - Explicit stage transitions in code or config. - Multiple different prompts or schemas. - The app decides when to request user input. - The model may call tools *within* a step, but the macro progression is app-owned. #### Agent-driven control flow **With Agent-driven control flow, the agent decides what happens next.** It looks like this: ``` agent = Agent( model = "claude-sonnet", system_prompt = "You are a coding assistant. Read files, edit code, run tests.", tools = [read_file, edit_file, run_tests, search_codebase], max_turns = 50 ) result = agent.run("Fix the failing test in src/auth.ts") ``` The agent decides: - What to read first. - What to edit. - When to run tests. - Whether to try a different approach after a failure. - When to stop. **The orchestration moves *inside* the agent loop**: it's not enforced by the app but left to the model's own judgment. Agent SDKs provide a "harness" that can be customized by the developer. This harness provide orchestration cues to the model to steer it towards the expected goals: - **System prompts, policies and instructions (in agent.md or similar)**: the rules of the road: what to do, what not to do, how to behave. - **Tools**: what pre-packaged tools are available to search, fetch, edit, run commands, apply patches. - **Permissions**: which tools are allowed, under what conditions, with what scoping. - **Skills**: pre-packaged behaviours and assets the agent can invoke. - **Hooks / callbacks**: places the host can intercept or augment behavior (logging, approvals, guardrails). This report examines three agent SDKs that implement agent-driven control flow: - **Claude Agent SDK** exposes the Claude Code engine as a library, with all the harness elements above built in. - **Pi SDK** is an opinionated, minimalistic framework. Notably it can work in environments without bash or filesystem access, relying on structured tool calls instead. - **OpenCode** ships as a server with an HTTP API β€” the harness plus a ready-made service boundary. There are other agent-driven frameworks. Typical signs of agent-driven control flow: - **The hosting app is thin**: it relays messages, enforces permissions, renders results. - **The logic lives in the harness** in the form of system prompts, context files, skills and other "capabilities" that steer the agent towards the expected outcome. #### What to keep in mind Three points from this section: - **Orchestration is about who decides what happens next.** In app-driven control flow, the developer defines the graph. In agent-driven control flow, the model decides based on goals, tools, and prompts. Both are valid β€” the choice depends on how predictable the task is. - **Orchestration frameworks handle the plumbing.** Whether you choose app-driven or agent-driven, frameworks give you the loop, tool wiring, and error handling so you can focus on the logic β€” not on parsing JSON and managing retries. - **In agent-driven systems, the harness replaces the graph.** The agent has more freedom, but it is not unsupervised. System prompts, permissions, skills, and hooks are what steer it. The harness is the developer's control surface when there is no explicit workflow. - **Orchestration libraries are adding agent-driven control flow**: [LangChain Deep Agents](https://blog.langchain.com/deep-agents/) and [PydanticAI](https://ai.pydantic.dev/multi-agent-applications/) both list deep agents as a first-class pattern. ### Part 3 - Two tools to rule them all **The tools provided to an agent encode assumptions about how things should be done.** A search_web β†’ get_reviews β†’ check_availability pipeline implies a specific strategy. It limits the ability of the model to figure out how to reach the goal. **Bash and the file system in contrast are universal tools** that Agent SDKs have made a choice to consider a given. In this part, I'll look into why and how those tools change the game. #### The limits of predefined tools **Tools define what the agent can do.** If you give it `search_web`, `read_file`, and `send_email`, those are its capabilities. Nothing more. **Every capability must be anticipated and implemented in advance**: - Want the agent to compress a file? You need a `compress_file` tool. - Want it to resize an image? You need a `resize_image` tool. - Want it to check disk space, parse a CSV, or ping a server? Each one requires a tool. **Even slight changes in the task require updating the tool set.** Say you built a `send_email(to, subject, body)` tool. Now the user wants to attach a file β€” you need an `attachments` parameter. Then they want to CC someone β€” another parameter. Each small requirement change means updating the tool's schema and implementation. **Designing an effective tool list is a hard balance to strike**. Anthropic's [guidance on tool design](https://www.anthropic.com/engineering/writing-tools-for-agents) puts it directly: "Too many tools or overlapping tools can distract agents from pursuing efficient strategies." But too few tools, or tools that are too narrow, can prevent the agent from solving the problem at all. #### Bash as the universal tool ##### Bash is the Unix shell: a command-line interface that has been around since 1989 It is the standard way to interact with Unix-like systems (Linux, macOS). You type commands, the shell executes them, you see the output. Consider a task like: "find all log files from this week, check which ones contain errors, and count the number of errors in each." - With predefined tools, you would need `list_files` with date filtering, `search_file` to find matches, `count_matches` per file β€” three separate tools, plus the logic to combine the results. - With bash: 3 commands. No tool definitions, no schema changes if the task evolves. ```bash ### Find log files from the last 7 days find . -name "*.log" -mtime -7 ### Which ones contain errors grep -l "ERROR" $(find . -name "*.log" -mtime -7) ### Count errors in each for f in $(find . -name "*.log" -mtime -7); do echo "$f: $(grep -c 'ERROR' "$f") errors" done ``` ##### Why does bash matter for agents? **Bash scripts can replace specialized tools**: - Giving an agent bash access is giving it access to the entire Unix environment: file operations, network requests, text processing, program execution - And the ability to combine them in ways you did not anticipate. **Vercel achieved 100% success rate. 3.5x faster. 37% fewer tokens** : - Their text-to-SQL agent d0 had 17 specialized tools (query builders, schema inspectors, result formatters) and achieved an 80% success rate. - Then they ["deleted most of it and stripped the agent down to a single tool: execute arbitrary bash commands."](https://vercel.com/blog/we-removed-80-percent-of-our-agents-tools) - The result: one general-purpose tool outperformed seventeen specialized ones. ##### Bash is not just more flexible β€” it is also faster. **Each tool call means an additional inference. Calling a lot of tools is expensive**: - Remember the two-step pattern: the model requests a tool call, the system executes it, the result feeds back. - For a task requiring 10 tool calls, that is 10 inference passes. **With bash, the agent can write a script that chains multiple operations together and save on intermediate inferences**: - The [CodeAct research paper](https://arxiv.org/abs/2402.01030) (ICML 2024) found code-based actions achieved up to 20% higher success rates than JSON-based tool calls. - Manus adopted a similar approach from their launch using [fewer than 20 atomic functions](https://manus.im/blog/Context-Engineering-for-AI-Agents-Lessons-from-Building-Manus), and offload the real work to generated scripts running inside a sandbox. - [Anthropic](https://www.anthropic.com/engineering/code-execution-with-mcp) and [Cloudflare's Code Mode](https://blog.cloudflare.com/code-mode/) experiment confirmed that writing code beats tool calling #### The filesystem as the universal persistence layer To persist an information, a user-facing artifact, a plan or intermediate results, an agent needs a tool and a storage mechanism. **Predefined persistence tools have the same problem as predefined action tools:** - A `save_note(title, content)` tool works for text notes. But what about images? JSON structures? Binary files? A directory of related files? - The tool's schema defines and limits what can be stored. Each storage mechanism has its own interface, its own constraints. **The filesystem has no predefined schema or constraints:** - A file can contain anything: Markdown, JSON, images, binaries, code. A directory can organize files however makes sense. - The agent decides where to put it, what to write, what to name it, how to structure it. **The filesystem allows the agent to communicate with itself**: - The agent can store information that it may need further down the road. Manus describes this as ["File System as Extended Memory"](https://manus.im/blog/Context-Engineering-for-AI-Agents-Lessons-from-Building-Manus): "unlimited in size, persistent by nature, and directly operable by the agent itself." - The filesystem also allows the agent to share memories between sessions, removing the need for elaborate memorization / retrieval tools. #### What to keep in mind - **Bash is a universal tool.** Instead of anticipating every capability and implementing a specific tool, you give the agent access to the Unix environment. It can compose arbitrary operations from basic primitives β€” and LLMs are already trained on how to do this. - **The filesystem is universal persistence.** Instead of defining schemas for what the agent can store, you give it a directory. It can write any file type, organize however makes sense, and the files persist across sessions for free. - **All major agent SDKs assume both.** The Claude Agent SDK, OpenCode, and Codex all ship bash and filesystem tools as built-in. Pi SDK is a notable exception β€” it can work without filesystem access. - **This has architectural consequences.** Bash and filesystem access require a runtime that provides them. - **An alternative is emerging: reimplement the interpreter.** Vercel's [`just-bash`](https://github.com/vercel-labs/just-bash) is a bash interpreter written in TypeScript: 75+ Unix commands reimplemented with a virtual in-memory filesystem. No real shell, no real filesystem, no container needed. Pydantic's [`monty`](https://github.com/pydantic/monty) does the same for Python: a subset interpreter written in Rust, where `open()`, `subprocess`, and `exec()` simply do not exist. ### Part 4 - Agent SDK to Agent Server: crossing the service boundary **An agent can be many thing**: ephemeral or long-lived, stateful or stateless, automating processes behind-the-scene or user-facing. How do these behavioural features map with technical capabilities provided by the agent frameworks? And the other way around, OpenCode has a server-client architecture while other frameworks are libraries: how does that matter when you want to build an agent In this part, I'm looking at how agent behaviours and agent implementation details are related. In particular what technical layers need to be implemented to go from an Agent SDK to an Agent Server (OpenCode). #### What's an Agent "SDK" anyway? ##### Libraries and services ##### Think of the difference between Excel and Google Sheets. An Excel spreadsheet lives on your machine. Nobody else can see it while you're working. It exists on your machine and only your machine. Google Sheets lives on Google's servers. You open it in a browser, but the spreadsheet is not on your machine. You can close your browser and it's still there. You can open it from your phone, from another laptop. It keeps running whether or not you're connected. Excel behaves in this example like a library, it's embedded. Google Sheets is "hosted": it lives behind the service boundary. It's a service. **The lifecycle of a service is not bound to the lifecycle of the client that is calling it.** The service boundary is not just about separate physical machines β€” it is about whether a capability runs inside an application or as a separate, independent process. An application calls a library directly; it connects to a service over a protocol. ##### A more technical example: databases. SQLite is embedded. Your application links the library, calls functions directly. No service boundary. When your app exits, SQLite exits. PostgreSQL is hosted. It runs as a separate server process. Your application connects over a socket, sends SQL as messages, receives results. Service boundary. PostgreSQL keeps running after your app disconnects. ##### What is the difference between an Agent SDK and a regular coding agent? What's the difference between Claude Agent SDK and Claude Code, between Codex SDK and Codex, between Pi coding agent and Pi SDK? An Agent SDK provides the same kind of capabilities you would expect from a coding agent β€” but as a "programmable interface" (API) instead of a user interface - **Send a prompt, get a response** β€” the equivalent of typing a message in Claude Code. In the SDK: `query(prompt)`. - **Resume a previous conversation** β€” pick up where you left off, with full context. In the SDK: pass a `sessionId`. - **Control which tools the agent can use** β€” restrict it to read-only, or give it full access. In the SDK: `allowedTools`. - **Intercept the agent's behavior** β€” get notified before or after a tool call, log actions, add approval gates. In the SDK: hooks. ```python ### Send a prompt to the Claude Agent SDK with a list of allowed tools from claude_agent_sdk import query async for message in query( prompt="Run the test suite and fix any failures", options={"allowed_tools": ["Bash", "Read", "Edit"]} ): print(message) ``` With an Agent SDK, you may: - **Automate** tasks - **Extend** an existing app with agentic features **Example: automated code review in CI.** - You run the Claude Agent SDK in a GitHub Actions job. - When a PR is opened, the agent reviews the code, runs tests, and posts comments. - There is no service boundary: the agent is instantiated within the GitHub Actions runner process, and is constrained by that runner's limits β€” 6-hour max job duration, fixed RAM and disk, no persistent state between runs. **Example: agentic search in a support app.** - A customer support app adds an agentic search capability to help users refine their query and find the information they need. - The support app user chats with the agent that searches, filters and combine information from the knowledge base, ticket history,... The user can turn its search into a support ticket answer or any other relevant action. - The agent is a function call within the app process. When the search completes (or the user navigates away), the session is gone. No agent service boundary. **In both cases, the agent runs within the host process.** It starts, does its work, and stops. No independent lifecycle. No reconnection. No background continuation. #### How is an Agent Server different from an Agent SDK? ##### The Agent Server use case If you want to build a ChatGPT clone, an Agent SDK is a start. But it's not enough. **You need the agent's lifecycle to be decoupled from the client's so that you can**: - Access from anywhere, not just a CI job or a bot on your server. - Close your browser, come back later, and find the agent still running β€” or finished. - Connect multiple people to the same agent session. - Get real-time progress as the agent works. **You cannot just put the SDK on a server and call it done.** The SDK gives you the agent loop. It does not handle what comes with running a process that other people connect to over a network: - **Authentication** β€” who is allowed to talk to this agent, and how do you verify that? - **Network resilience** β€” clients disconnect, requests timeout, connections drop mid-stream. The library assumes a stable in-process caller. ##### Agent-specific server capabilities Authentication and network resilience need to be thought through for any client-server application. Agents require additional layers: **Transport**: how the user's browser (or app) talks to the agent server. You build an HTTP server that accepts requests and returns agent output. The question is how much real-time interaction you need. There are multiple options of growing complexity from standard HTTP request/response (the user submits a task and waits for the complete result: no progress updates while the agent works) to Websocket. See focus on the Transport layer in Part 5 for more details. **Routing**: how each message reaches the right conversation. You build this by assigning a session ID to each conversation and maintaining a registry β€” a lookup table that maps session IDs to agent processes. When a message comes in, the server looks up the session ID and forwards the message to the right place. **Persistence**: how conversations can be accessed and resumed later. You build this by "persisting" the conversation state (messages, context, artifacts). Unless the runtime is run without interruption that means saving the state and reloading it when the user reconnects. Part 5 shows how different projects solve this differently. **Lifecycle**: what happens when the user closes the tab while the agent is working. When the agent runs inside the request handler, when the user disconnects, the connection closes and the agent stops. For longer tasks, you need the agent to survive disconnection. To do so, first you need to separate the agent process from the request handler. The agent runs in its own container or background process, not inside the HTTP handler. ![SDK to Agent Server layers](../images/onion-layers.webp) #### OpenCode: the only Agent Server OpenCode ships as a server with most layers built in. | Layer | OpenCode provides | What it does not provide | | ------------------ | -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Transport** | HTTP API + SSE streaming. Client sends prompts via POST, receives output via SSE. | No WebSocket. SSE is one-way β€” the client cannot send messages while the agent is streaming without making a separate HTTP request. | | **Routing** | Full session management β€” create, list, fork, delete conversations. Each session has an ID. | Sessions are scoped to one machine. No global registry for routing across multiple servers or sandboxes. | | **Persistence** | Sessions, messages, and artifacts saved to disk as JSON files. Restart the server and conversations are still there. | Persistence is tied to the local filesystem. If the machine or sandbox is destroyed, the files are gone. No external database, no durable state across environments. | | **Lifecycle** | Server continues running when client disconnects. Agent keeps processing. Reconnect with `opencode attach`. | No recovery from server crashes β€” in-flight work is lost. No job queue, no supervisor, no automatic restart. | | **Multi-client** | Multiple SSE clients can watch the same session simultaneously. | Only one client can prompt at a time (busy lock). No presence awareness, no real-time sync between clients. Multiple viewers, single driver. | | **Authentication** | Optional HTTP Basic Auth. | No tokens, no user identity, no multi-tenant isolation, no fine-grained permissions. | #### What to keep in mind - **An Agent SDK is a library. An Agent Server is a service.** The SDK runs inside your process β€” when it stops, the agent stops. A server runs independently β€” the agent survives disconnection. - **Crossing the service boundary means building four layers:** transport (how the client talks to the server), routing (how messages reach the right session), persistence (how state survives restarts), lifecycle (how the agent runs without a client connected). - **OpenCode is the only agent SDK that ships as a server.** It provides all four layers out of the box, scoped to a single machine. For global routing, multi-tenant access, or cloud deployment, you build the remaining pieces yourself. ### Part 5 β€” Agent architectures by example It's possible to cross the service boundary without rebuilding everything OpenCode provides. Depending on the use case, you may need to implement only some of the layers. **The single biggest design decision is whether you are building a stateful or stateless agent.** Statefulness can be achieved with an agent being "always on", being hosted on a VPS for example. But that's not scalable: you end up paying even when the agent is idle. **Alternatively relying on ephemeral environments comes with a persistence challenge**: how do you persist the state when the environment is torn down? Part 5 walks through real projects to illustrate how agents are assembled from different technical bricks, reviewing a variety of architectural choices. #### Claude in the Box: the job agent **Agent Framework**: Claude Agent SDK **Cloud services**: Cloudflare Worker + Cloudflare Sandbox **Layers**: transport + artifacts persistence **Link**: [github.com/craigsdennis/claude-in-the-box](https://github.com/craigsdennis/claude-in-the-box) **Description**: - **This is a job agent, not a chatbot.** No conversation, no back-and-forth during execution, no session to resume. - **Use case**: a job that is best performed by an agent, i.e. extract structured data from a document. - A ~100-line project that wraps the Claude Agent SDK. **User journey:** the client sends a POST request with a prompt and stays connected. The agent's raw output streams back in real time: progress messages, tool calls, intermediate results. When the agent finishes, the Worker collects the final output files (the artifacts) and stores them in KV and returns it to the client. **Technical flow:** - The Worker receives the POST and spins up a Cloudflare Sandbox. - The agent runs inside the sandbox using the Claude Agent SDK's `query()` function. It reads, writes files, runs bash commands β€” all within the container. - The agent's stdout is streamed back through the Worker to the client as chunked HTTP. This is the live feed β€” a mix of everything the agent does. - When the agent finishes, the Worker reads the output files (e.g. `fetched.md`, `review.md`) from the sandbox filesystem. The Worker stores them in Cloudflare KV (keyed by a cookie) so the client can retrieve them after the sandbox is destroyed. ``` Browser β†’ HTTP POST β†’ Cloudflare Worker (~100 lines) β†’ Cloudflare Sandbox β†’ Claude Agent SDK query() ← streams stdout back β†’ reads artifacts β†’ stores in KV β†’ destroys sandbox ``` ##### Highlight: Why Cloudflare requires two layers: Worker + Sandbox? **Cloudflare Workers are like application "valets"**: - They are the frontdoor for internet traffic (they handle HTTP requests) and decide what to do / which services to call. In technical terms, they route, orchestrate and connects to Cloudflare services like KV and Durable Objects. - Additional benefit: Worlers sleep between requests and bills only for the time it runs β€” cheap and instant. - Limitation: it runs in a V8 isolate β€” a lightweight JavaScript sandbox with no filesystem, no shell, and a 30-second CPU time limit. It cannot run the Claude Agent SDK. **The Sandbox is the opposite**: - It is a full Ubuntu container with bash, Node.js, a filesystem, and no time limit β€” everything the agent needs. - But it has no public URL. It cannot receive requests from the internet or talk to Cloudflare services directly. Neither can do the whole job alone. The Worker provides the service boundary (HTTP endpoint, streaming, artifact storage). The Sandbox provides the execution environment (bash, filesystem, long-running agent). The ~100 lines of glue between them wire up the HTTP endpoint, bridge the stream, and collect artifacts. ##### Server layers implementation | Layer | Status | Implementation | | ------------------ | --------------------- | ----------------------------------------------------------------------------------------------- | | Authentication | Skipped | Anyone can call the endpoint. | | Network resilience | Skipped | If the connection drops, the work is lost. | | Transport | Implemented (minimal) | Chunked HTTP streaming β€” the user watches progress in real time, but cannot send anything back. | | Routing | Skipped | No session IDs, no conversations to switch between. Each request is independent. | | Persistence | Partial | Final artifacts only (stored in KV). No conversation history, no ability to resume. | | Lifecycle | Skipped | The agent dies with the request. Close the tab and the work stops. | #### sandbox-agent: the adapter **Agent Framework**: Agent-agnostic (supports Claude Code, Codex, OpenCode, Amp) **Cloud services**: None β€” runs inside any sandbox (designed to be embedded) **Layers**: transport + partial routing **Link**: [github.com/rivet-dev/sandbox-agent](https://github.com/rivet-dev/sandbox-agent) **Description**: - **This is a transport adapter.** It solves one problem (giving every coding agent a unified HTTP+SSE transport) and leaves everything else to the consumer. - **Use case**: when a developer wants to deploy a variety of coding agents in sandboxes, this provides a built-in transport solution. The developer doesn't need to understand each agent's native protocol, and doesn't need to change anything when switching sandbox providers. **Technical flow:** - The daemon starts inside a sandbox and listens on an HTTP port. - The client creates a session via REST, specifying which agent to run (Claude Code, Codex, OpenCode, Amp). - The daemon spawns the agent process and translates its native protocol into a universal event schema with sequence numbers. - Events stream to the client over SSE. - When the agent needs approval (e.g. to run a bash command), the daemon converts the blocking terminal prompt into an SSE event. The client replies via a REST endpoint. - If the client disconnects, it reconnects and resumes from the last-seen sequence number. ``` Your App (anywhere) | HTTP + SSE v +--[sandbox boundary]-------------------+ | sandbox-agent (Rust daemon) | | claude | codex | opencode | | [filesystem, bash, git, tools...] | +----------------------------------------+ ``` ##### Highlight: the Transport layer Transport is how a client and a server exchange data over a network. There is a spectrum of transport modes, from simplest to most capable: | Mode | What the user experiences | Interaction | Reconnection | |------|--------------------------|-------------|--------------| | **HTTP request/response** | Submit a task, wait, get the full result when done. No progress updates while the agent works. | One-shot. | N/A. | | **Chunked HTTP streaming** | Submit a task, watch the agent's output stream in real time β€” like a terminal in the browser. | Watch only β€” the user cannot send input mid-stream. | None. Connection drops = work lost. | | **Server-Sent Events (SSE)** | Same real-time streaming, but the connection survives drops. The browser reconnects automatically and resumes from the last event. | Watch + interact via separate requests (e.g. approve a command via a button click). | Built-in (automatic). | | **WebSocket** | Full interaction while the agent works β€” approve commands, provide context, cancel tasks. Multiple users can watch the same session. | Bidirectional, real-time. | Application must implement. | Claude-in-the-Box uses chunked HTTP streaming. sandbox-agent outputs SSE. Ramp Inspect uses WebSocket. Each step up adds capability and complexity. Now, the agents that sandbox-agent supports speak different native protocols β€” none of which are network transports: - **JSONL on stdout** β€” Claude Code and Amp run as child processes, spawned per message. They write one JSON object per line to stdout. - **JSON-RPC over stdio** β€” Codex runs a persistent server process (`codex app-server`) that communicates via structured JSON-RPC requests and responses over stdin/stdout. Still a local process β€” not network-accessible. - **HTTP server** β€” OpenCode already runs its own HTTP+SSE server (see Part 4). It is network-accessible without translation. For OpenCode, sandbox-agent is not necessary. ##### Server layers implementation | Layer | Status | Implementation | | ------------------ | --------------------- | ----------------------------------------------------------------------------------------------------------------------- | | Authentication | Skipped | Runs inside a sandbox β€” assumes the sandbox boundary provides isolation. | | Network resilience | Partial | SSE sequence numbers allow clients to reconnect and resume from last-seen event. | | Transport | Implemented | HTTP + SSE β€” structured event stream with sequence numbers for reconnection. REST endpoints for approvals/cancellation. | | Routing | Partial | In-memory session management β€” multiple sessions per daemon, but no persistent session registry. | | Persistence | None | If the daemon crashes or the sandbox is destroyed, there is no way to recover or reconnect to a conversation. | | Lifecycle | Minimal | Agent process managed by the daemon, but no background continuation beyond the sandbox's lifetime. | #### Ramp Inspect β€” the full production stack **Agent Framework**: OpenCode **Cloud services**: Modal Sandbox VMs + Cloudflare Durable Objects + Cloudflare Workers **Layers**: transport + routing + persistence + lifecycle + authentication + network resilience (all layers) **Link**: [builders.ramp.com/post/why-we-built-our-background-agent](https://builders.ramp.com/post/why-we-built-our-background-agent) **Description**: Ramp's internal background coding agent that creates pull requests from task descriptions. Reached ~30% of all merged PRs within months. **User journey:** an engineer describes a task in Slack, the web UI, or a Chrome extension. The agent works in the background β€” the engineer can close the tab, switch clients, come back later from a different device. When done, the agent posts a PR or a Slack notification. Multiple engineers can watch the same session simultaneously. **Technical flow:** - Each task gets a session β€” one session = one Durable Object + one Modal VM + one conversation. The session ID is the permanent address for the task. - The client connects via WebSocket to a Cloudflare Worker, which routes the connection to the session's Durable Object. - The DO is the hub: it holds WebSocket connections from all clients watching this session, stores conversation history in embedded SQLite, and forwards messages to the Modal VM. When the agent produces output, the DO broadcasts it to every connected client. - The VM runs OpenCode with a full dev environment: git, npm, pytest, Postgres, Chromium, Sentry integration. - The agent works independently of any client connection. If all clients disconnect, the VM keeps running. - On completion, the agent posts results via Slack notification or GitHub PR. - Modal VMs have a 24-hour maximum TTL. Before the VM is terminated, its state is captured through Modal's snapshot API β€” a full point-in-time capture of the filesystem (code, dependencies, build artifacts, environment). The snapshot can be restored into a fresh VM days later. ``` Clients (Slack, Web UI, Chrome Extension, VS Code) β†’ Cloudflare Workers β†’ Durable Object (per-session: SQLite, WebSocket Hub, Event Stream) β†’ Modal Sandbox VM (OpenCode agent, full dev environment) ``` ##### Highlight: Durable Objects as the coordination layer In Part 4, we saw that OpenCode is a single-server agent β€” it has session management, persistence, and transport, but all scoped to one machine. To make it globally accessible, you need global routing, persistent state that survives restarts, and WebSocket management across clients. This is the gap Ramp filled with Durable Objects. A Durable Object is a stateful micro-server with a globally unique ID (while Workers are stateless). Any request from anywhere in the world can reach a specific DO by its ID β€” Cloudflare routes it automatically. Each DO has its own embedded SQLite database (up to 10 GB), and it can hold WebSocket connections. It runs single-threaded, which matches the agent pattern: one session = one sequential execution context. **What makes DOs useful for agents specifically:** - **Global routing without a registry.** The DO ID *is* the session address. No load balancer, no session-affinity configuration, no lookup table. A client in Tokyo and a client in New York both reach the same DO by passing the same ID. - **State that survives hibernation.** When no clients are active, the DO hibernates β€” it is evicted from memory but the WebSocket connections are kept alive at Cloudflare's edge, and the SQLite data persists. Billing stops. When a client sends a message, the DO wakes up, the message is delivered, and processing continues. The client does not know the DO was hibernating. - **Re-attach for free.** If a client actually disconnects (browser closed, network drop), a new connection to the same DO ID restores the session. The conversation history is in SQLite. Cloudflare's Agents SDK (which builds on DOs) goes further: it automatically syncs state on reconnection and can resume streaming from where it left off. **Why a Modal VM is required on top of the DO:** A DO is a lightweight JavaScript runtime β€” it cannot run bash, access a filesystem, or execute agent tools. It is the coordination layer (routing, state, WebSocket), not the execution layer. Code execution happens in a separate VM or container. This is why Ramp pairs DOs with Modal VMs: the DO routes and remembers, the VM computes. ##### Server layers implementation | Layer | Status | Implementation | |-------|--------|----------------| | Authentication | Internal only | Restricted to Ramp employees β€” no public access. | | Network resilience | Implemented | WebSocket with DO hibernation β€” connections survive idle periods, clients reconnect seamlessly. | | Transport | Implemented | WebSocket β€” bidirectional, real-time, multiple clients connect to the same session simultaneously. | | Routing | Implemented | Cloudflare Durable Objects β€” per-session, globally routed, guaranteed affinity by session ID. | | Persistence | Implemented (two layers) | DO SQLite for conversation state + Modal snapshots for full VM state (code, deps, environment). | | Lifecycle | Implemented (full) | Agent survives client disconnection β€” background continuation is the core design principle. | #### Cloudflare Moltworker β€” the platform provides the layers **Agent engine**: Pi SDK (LLM abstraction + core agent loop) **Agent product**: OpenClaw (personal AI assistant built on Pi SDK β€” multi-channel gateway, session management, skills platform) **Cloud services**: Cloudflare Worker + Durable Objects + Sandbox + R2 + AI Gateway **Layers**: ALL (transport, routing, persistence, lifecycle, authentication, network resilience) **Link**: [github.com/cloudflare/moltworker](https://github.com/cloudflare/moltworker) β€” blog: [blog.cloudflare.com/moltworker-self-hosted-ai-agent](https://blog.cloudflare.com/moltworker-self-hosted-ai-agent/) **Description**: - **[OpenClaw](https://github.com/openclaw/openclaw) (previously Moltbot, ex-Clawbot, ex-Clawdis) is all the rage since January**: a personal assistant that you can work with from your messaging app. There are different options for hosting, the first being you own computer or a VPS. Cloudflare Moltworker project provides an option to deploy it on Cloudflare ecosystem. - The stack has three layers: **Pi SDK** provides the agent engine (LLM calls, tool execution, agent loop). **OpenClaw** builds a complete personal assistant on top of Pi β€” multi-channel inbox (WhatsApp, Telegram, Slack, Discord), its own session management, a skills platform, and companion apps. **Moltworker** is the deployment layer β€” it packages OpenClaw into a Cloudflare container, handles authentication (Cloudflare Access), persists state to R2, and proxies requests from the internet to the agent. **User journey:** the user accesses their agent via a browser, protected by Cloudflare Access (Zero Trust). They chat with the agent, which can browse the web, execute code, and remember context across sessions. They can close the browser and come back β€” conversations persist. The agent can also run autonomously on a cron schedule with no client connected at all. **Technical flow:** - The browser connects through Cloudflare Access, which enforces identity-based authentication before any request reaches the application. - The Worker receives the request and routes it to the appropriate Durable Object instance. - The Durable Object establishes a WebSocket connection with the client and manages the container lifecycle β€” same pattern as Ramp (DO β†’ compute), but here the compute is a Cloudflare Container instead of a Modal VM. - The container (a full Linux VM) runs the OpenClaw agent. It has an R2 bucket mounted at `/data/moltbot` via s3fs for persistent storage. - When the user goes idle, the container sleeps (configurable via `sleepAfter`). The Durable Object hibernates without dropping the WebSocket. - On the next message, the DO wakes, the container restarts, and the R2 mount provides continuity β€” session memory and artifacts survive the restart. ``` Internet β†’ Cloudflare Access (Zero Trust) β†’ Worker (V8 isolate, API router) β†’ Durable Object (routing, state, WebSocket) β†’ Container (Linux VM, managed via Sandbox) β†’ /data/moltbot β†’ R2 Bucket (via s3fs) β†’ OpenClaw (Pi SDK agent) ``` ##### Highlight: how persistence works with ephemeral compute Both Ramp and Moltworker face the same problem: the agent runs in an ephemeral machine (Modal VM or Cloudflare Container) that will eventually be destroyed. How do you keep state across restarts? The 2 projects made different design decisions: - With Modal, and its snapshot feature, the full state of the VM is saved and restored. There is no need to think ahead what information needs to be saved and restored. - Cloudflare Containers don't have the same feature. So the approach with Moltworker is to provide an additional persistance layer: the agent has a sort of virtual drive that rely on a Coudflare R2 bucket (a storage product similar to AWS S3). Meaning that part of the filesystem (located `/data/moltbot`) it is automatically saved. But not all of it. | | Ramp (Modal) | Moltworker (Cloudflare) | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | **What dies** | VM is terminated after 24-hour TTL | Container filesystem is wiped on sleep | | **Conversation state** | Stored in Durable Object (SQLite) β€” survives VM restarts | Stored in Durable Object (SQLite) β€” survives container restarts | | **Code, deps, environment** | Modal snapshot API β€” full point-in-time capture of the VM filesystem. Taken before termination, restored into a fresh VM later. | R2 bucket mounted at `/data/moltbot` via s3fs β€” everything written there survives. No snapshot, just continuous persistence. | | **What survives** | Everything (full VM state frozen and restored) | Only what's explicitly written to `/data/moltbot` | | **What's lost** | Nothing (if snapshotted before termination) | Anything on the container filesystem outside the R2 mount | | **Trade-off** | Full fidelity but requires snapshot orchestration | Simpler but selective β€” you must design for it | ##### Server layers implementation | Layer | Status | Implementation | | ------------------ | ----------- | ---------------------------------------------------------------------------------------------------------- | | Authentication | Implemented | Cloudflare Access (Zero Trust) β€” identity-based access control before any request reaches the application. | | Network resilience | Implemented | DO hibernation keeps WebSocket alive during idle periods. Container wakes on next message. | | Transport | Implemented | WebSocket (via Durable Objects) + HTTP API for the entrypoint Worker. | | Routing | Implemented | Durable Object instance IDs β€” globally routable, all requests for same ID reach the same location. | | Persistence | Implemented | Multi-layer: DO SQLite for conversation, R2 bucket mounted via s3fs for artifacts and session memory. | | Lifecycle | Implemented | Agent survives client disconnection. DO hibernates. Containers sleep/wake. Cron enables autonomous runs. | #### What to keep in mind - **Not every use case needs all the layers.** Claude in the Box ships a useful product with just HTTP streaming and KV storage. - **Transport is a spectrum β€” pick the simplest that fits.** Chunked HTTP for job agents (Claude in the Box), SSE for streaming with reconnection (sandbox-agent), WebSocket for bidirectional interaction and multiplayer (Ramp, Moltworker). Each step up adds capability and complexity. - **Background continuation requires decoupling the agent from the HTTP handler.** The agent runs in its own process or container, not inside the request. - **Statefulness is the main design choice and the principal source of complexity:** resumable conversations require persistent routing (so the client finds the right session), storage and coordination layers that outlive the agent execution environment. --- #### Going further - **LangGraph β€” Thinking in LangGraph** The mental model behind app-driven orchestration: explicit graphs, state machines, and developer-defined control flow. Includes the email-triage workflow example. https://docs.langchain.com/oss/python/langgraph/thinking-in-langgraph - **Unix Was a Love Letter to Agents** β€” Vivek Haldar Argues that the Unix philosophy β€” small tools, text interfaces, composition β€” aligns perfectly with how LLMs work. "An LLM is exactly the user Unix was designed for." https://vivekhaldar.com/articles/unix-love-letter-to-agents/ - **Vercel β€” How to build agents with filesystems and bash** Practical guide to the filesystem-and-bash pattern. "Maybe the best architecture is almost no architecture at all. Just filesystems and bash." https://vercel.com/blog/how-to-build-agents-with-filesystems-and-bash - **From "Everything is a File" to "Files Are All You Need"** (arXiv 2025) Academic paper arguing that Unix's 1970s design principles apply directly to autonomous AI systems. Cites Jerry Liu: "Agents need only ~5-10 tools: CLI over filesystem, code interpreter, web fetch." https://arxiv.org/html/2601.11672 - **Turso β€” AgentFS: The Missing Abstraction** Argues for treating agent state like a filesystem but implementing it as a database. "Traditional approaches fragment state across multiple toolsβ€”databases, logging systems, file storage, and version control." https://turso.tech/blog/agentfs - **How Claude Code is built** β€” Pragmatic Engineer Deep dive into Claude Code's architecture. "Claude Code embraces radical simplicity. The team deliberately minimizes business logic, allowing the underlying model to perform most work." https://newsletter.pragmaticengineer.com/p/how-claude-code-is-built - **What I learned building an opinionated and minimal coding agent** β€” Mario Zechner The author of Pi SDK on building a coding agent with under 1,000 tokens of instructions and no elaborate tool set. "If I don't need it, it won't be built." https://mariozechner.at/posts/2025-11-30-pi-coding-agent/ - **Agent Design Is Still Hard** β€” Armin Ronacher Building production agents requires custom abstractions over SDK primitives. "The differences between models are significant enough that you will need to build your own agent abstraction." Covers cache management, failure isolation, and shared filesystem state. https://lucumr.pocoo.org/2025/11/21/agents-are-hard/ - **Minions: Stripe's one-shot, end-to-end coding agents** β€” Alistair Gray Stripe's homegrown coding agents that operate fully unattended β€” from task to merged PR β€” producing over 1,000 merged PRs per week. Orchestrates across internal MCP servers, CI systems, and developer infrastructure. https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents - **The two patterns by which agents connect sandboxes** β€” Harrison Chase Agent IN sandbox (runs inside, you connect over the network) vs sandbox as tool (agent runs locally, calls sandbox via API). Each has different trade-offs for security, iteration speed, and coupling. https://x.com/hwchase17/status/2021261552222158955 --- ### How long for that change? **URL**: https://meaningfool.net/articles/how-long-for-that-change **Date**: 2026-02-02 **Type**: Article ### How long for that change? **Can we break down the time it takes to make a change?** - It depends on the size of the change: obviously. - The talent, seniority and dedication of the team matters: sure. - The size of the change: no question. But there is something else. **Some changes, even small in size can have an outsize impact.** This may be due to ill-chosen abstractions, spaghetti code, lack of proper devops, or other factors. This extra factor is called "complexity" or "technical debt". **But either concept offer little leverage to make things better:** - They are vague and source of personal interpretation. - They keep non-engineer at bay. - They don't surface what matters: what makes small changes disproportionately costly. Kent Beck introduced a much more actionable concept: the **Cost of change**. Kent Beck framed [the Cost of change](https://tidyfirst.substack.com/p/change) as the time it takes to: - Understand what needs to be changed - Make the change - Validate the change - Deploy the change > cost(change) = cost(understand) + cost(modify) + cost(validate) + cost(deploy) ![kent-beck-cost-of-change](../images/kent-beck-cost-of-change.png) #### The bundling effect Looking more closely at terms, some are fixed - they do not depend on the size of the change: - Spinning up a local development environment - Running the full test suite - The duration of the CI/CD pipeline - The time it takes to understand the code (which is high whatever the change under high coupling) Linear functions with a constant term are subadditive: > cost(A+B) < cost(A) + cost(B) That means basically that bundling 2 changes together is cheaper than doing them separately. **So the higher the cost of change, the stronger the incentive to bundle changes together.** ![sub-additive](../images/sub-additive.jpg) #### The rework effect Let's now consider that we operate at 0 fixed cost. So there should be no penalty in making multiple separate changes... ...if we ignore the cost of rework. Consider a feature: - Costing W if implemented in one go. - Costing W' = Sum(W1,..., Wn) if implemented in n iterations. If each iteration is to be released independently, meaning that the software is fully functional at each step, we can expect some level of rework with each iteration. **Each iteration adds a little extra cost: the cost of rework.** > Sum(W1,..., Wn) > W There are 3 possible strategies / philosophies to deal with the cost of rework: - **Waterfall:** Reduce the number of iterations. - **Big Design Up Front:** Reduce the rework for each iteration by designing upfront to make each work unit forward-compatible with future iterations. - **eXtreme Programming:** Embrace the rework. ![cost-of-rework](../images/cost-of-rework.jpg) #### Big Design Up Front vs eXtreme Programming There are 2 ways to approach rework: 1. Avoid it as much as possible (Big Design Up Front) 2. Embrace it because there is no way around it (eXtreme Programming - XP) **Big Design Up Front:** 1. Bets that additional up-front design work more than offsets the prevented rework down the road. 2. Bets that current assumptions (that design decisions are based upon) will hold true. **XP** 1. Bets on minimizing assumptions, even if it means embracing rework. 2. Bets that frequency (of learning) beats speed (of delivery). Rework, however, is only as impactful as the cost of change: - High cost of change means rework is expensive, pushing toward Big Design Up Front - Lower cost of change means rework is cheap, pushing toward XP ![rework-vs-assumption-tradeoff](../images/rework-vs-assumption-tradeoff.jpg) #### Conclusion As a non-programmer, who came to software through a "project" design-first approach, I've found in Agile, in the 2nd half of 2010s, a mix of powerful AND moot concepts and practices. But nothing to string them together and cut through the noise. Only floating pieces. Understanding the Cost of change changed that: - I could now make sense of observations I had made over my career about **software development dynamics:** the bundling bias, the slicing fallacy. - I could now visualize how **Big Design Up Front and XP are just 2 opposite views on the assumption / rework tradeoff.** - I could now identify how **some product roles (PMs and designers) and practices (specs, handoffs) make more sense in high cost of change environments.** And it provides an angle to look at the **impact of GenAI** in product building: - GenAI collapses the time to *Make the change* and *Validate the change*. With proper devops, the bottleneck becomes the ability to *Understand what needs to be changed*. - *Software* Cost of change used to be the overwhelming contributor to the Cost of change. When it drops, what are the remaining contributors that define the competitive landscape? - But actually, is Cost of change dropping uniformly? Or is GenAI actually increasing the discrepancies? - Where is the Cost of change when you are building AI models? --- ### A Month (learning about agents) in review **URL**: https://meaningfool.net/articles/a-month-learning-about-agents-in-review **Date**: 2026-02-01 **Type**: Article ### A Month (learning about agents) in review #### Tooling and models - Added Zed to the stack: it does not eat up all my memory when I have multiple projects open as VS Code forks do. - Goolge may have been nerfing their Gemini 3 Pro model, it is not as good as it used to be. At the same time Antigravity quotas for Opus seem to have decreased. My cheap inference days might be over. - Tempted to try out OpenAI models/codex, as my experience with Claude matches [this one](https://x.com/MarcJSchmidt/status/2006809732582093095) ![switch-to-codex-tweet](../images/switch-to-codex-tweet.png) #### Two skills finalized I finalized two Claude Code skills that encode my own workflows: - **Spec-driven development skill.** Encodes my flavor of spec-driven development: feature creation, folder numbering, spec writing (slices, test-first), and implementation principles. Spec and plan files serve as a record of research findings and decisions. - **[Publishing skill](https://github.com/meaningfool/meaningfool-writing/tree/main/.claude/skills/publishing).** Handles the publishing pipeline for my website (meaningfool.github.io): from publishing an article (checking for frontmatter, fixing links and images if needed) to rebuilding the website (an Astro website with a Git submodule for the content). **Next steps:** Skill development is likely to become important, and it's still early stage. Nonetheless there are several opportunities to learn from the community about emerging patterns and best practices: - [Vercel agent-skills](https://x.com/fernandorojo/status/2016684080738554054) - [Upskill Agents - HuggingFace](https://huggingface.co/blog/upskill) - [Skills are all you need](https://x.com/irl_danB/status/2016584260618944767) - [@Sawyerhood browser-agent skill](https://github.com/SawyerHood/dev-browser) #### Bookmarks enrichment pipeline **Goal:** automate the process of enriching bookmarks with metadata and tags. The workflow now: 1. **Capture**: Use [Karakeep](https://karakeep.app/)'s browser extension to save links 2. **Enrich**: A personal Typescript CLI connects to Karakeep's API and processes each bookmark: - Classifies the URL (tweet vs regular link) - For tweets/X posts: fetches content via [Bird CLI](https://github.com/steipete/bird), generates visual tweet banners, grabs screenshots - For regular links: fetches HTML content, converts to clean Markdown using [Turndown](https://github.com/mixmark-io/turndown), extracts summaries using the [summarize CLI](https://github.com/steipete/summarize) - Updates bookmark metadata back into Karakeep 3. **Tag**: AI-driven tagging using Gemini, but grounded in my own tagging taxonomy -- not just whatever the AI comes up with. 4. **Automate**: The whole thing runs on my Mac every 2 hours via a launchd daemon. Rate-limited for Twitter (5 tweets per run, 10s delays with jitter). **Next steps:** - Synchronize these enriched bookmarks with GitHub as a Git-based knowledge base - Enable semantic search through the collection - Allow Claude Code to access this knowledge, so I can query my bookmarks conversationally #### Agent frameworks deep dive I spent time understanding the landscape of agent frameworks and agentic architectures. **Frameworks explored:** - **Claude Agent SDK** -- SDK-first approach, agent embeds in your code - **Pydantic AI SDK** -- orchestration library, app-in-control, protocol-first - **OpenCode** -- server-first/runtime approach, the loop is the default behavior - **LangGraph** -- graph-based agent orchestration - **Pydantic AI** (the broader ecosystem) **Some early findings:** - **Pydantic-like** (orchestration library) vs **Pi/OpenCode-like** (agent runtime): the difference is where the loop lives and what the interface contract looks like. Orchestration libraries have the app call the LLM; runtimes act like an operator where the loop is the default behavior. - **Server-first** vs **SDK-first** agent architecture: server-first means the agent runs as an independent service; SDK-first means the agent embeds in your code. Different trade-offs for deployment, control, and integration. **Next step:** Produce a synthesized report on what I learned across all of this. #### ClawdBot / MoltBot experimentation - I dove into ClawdBot (now renamed MoltBot). I haven't fully adopted it yet, but I ran some experiments. - Deployed 2 instances: one on my Mac, another on Hetzner. - It was the opportunity for my [first ever PR](https://github.com/openclaw/openclaw/pull/48) to a public open-source project. - This project poses obvious security risks: - [Suplly Chain Attack](https://x.com/theonejvo/status/2015892980851474595) - [Clawdbot Security Hardening](https://x.com/DanielMiessler/status/2015865548714975475) - [Clawdbot Risks](https://x.com/rahulsood/status/2015397582105969106) **Next steps:** - Check for security precautions and giving it access to more personal data/services. --- ### Prolifics and frugals **URL**: https://meaningfool.net/articles/prolifics-and-frugals **Date**: 2026-01-26 **Type**: Article ### Prolifics and frugals **The world splits into prolifics and frugals.** **Prolifics create a lot.** They attempt many things. They don't refine much, they multiply instead. **Frugals prefer to distill.** They care about signal over noise. They care about simplicity, About differences that make a difference. **AI is the prolific genie.** It's verbose. It produces fast. It assumes a lot. **Prolifics are best positioned to leverage AI** It fits their nature. It amplifies their natural inclination. But can they not drown in their own output (slop?) **Frugals may actually benefit the most** If they can add volume to their production. If they can automate distillation. If they can get started and deal with the initial mess. At least for a moment. Long enough to figure out how to work with their new prolific partner. --- ### The Slicing Fallacy **URL**: https://meaningfool.net/articles/the-slicing-fallacy **Date**: 2026-01-19 **Type**: Article ### The Slicing Fallacy **Slice, baby, slice** That's how you're going to iterate faster. Or is it? You sliced that feature into 3 iterations But wait! Iteration 2 implies to rework a lot that happened in iteration 1 Could we not just ship them together? And actually, 3 releases, that's 3 times the overhead: 3 times the manual testing, the deployment and so on. Can't we just do a single release? **When 2 changes cost more than a larger one, you bundle.** That's what Cost of Change does: The higher it is, the stronger the incentives to delay and to bundle. **You cannot slice your way out of slow iterations** That's the slicing fallacy. You are slow because you have high cost of change And slicing won't help as long as this remains true. ![](../images/slice-fail-guru.jpg) --- ### We Can't Stop Planning (Even When We Should) **URL**: https://meaningfool.net/articles/we-cant-stop-planning **Date**: 2026-01-12 **Type**: Article ### We Can't Stop Planning (Even When We Should) **We are pathological planners.** We should have a word for that. Something like *planopathia anticipationis* maybe. We plan because of incentives and psychological urges. **We plan because it feels professional and responsible**. Anticipating future problems signals expertise. Unpreparedness signals incompetence. **We plan because we can't resist adding to the list.** We hoard ideas, good and bad, scared we might forget. We order, reorder, discuss, schedule and align. We love tending to those little lists of ours. **We plan because we fear uncertainty.** What if...? What if...? What if...? We need the sense of control it provides. **Planning makes sense when your survival depends on it** Planning is an evolutionary moat that we climbed. When bad decisions can lead to an irreversible death, accurate anticipation is highly valuable. And over-planning is preferable to under-planning. **But where change is cheap,** When you can revert past decisions at no cost, When you can switch directions instantly, Anticipation is not only a waste of time, Anticipation is also a waste of optionality. **Cost of change is dropping and we did not notice** Or rather our hardwired brain has a hard time adjusting. Meaning we miss-allocate our energy where cost of change used to be high, Instead of where it remains relatively higher. **AI scrambles the Cost of change landscape.** Deliberately avoiding planning, Resisting the incentives to anticipate, Are relevant strategies until the dust settles. ![](../images/plan-conspiracy.gif) --- ### Cost of Change is Agile's ceiling **URL**: https://meaningfool.net/articles/cost_of_change_is_agile_ceiling **Date**: 2026-01-05 **Type**: Article ### Cost of Change is Agile's ceiling **Agile is all about slicing the work.** Smaller chunks mean delivering value faster. Smaller chunks mean the ability to reduce WIP and increase flow. That's what I kept hearing. **Until I discovered it's only half of the story.** Cutting things into ever tinier pieces Won't move the needle if cost of change is high. **Cost of change is what it sounds like:** It's the time it costs for making a change, but also planning, debugging, deploying... **High Cost of change incentivizes Anticipation** Costly changes mean: you better know precisely what you are doing before you do it. Costly changes mean: it makes sense to invest time in upfront planning and design. **High Cost of change incentivizes larger scopes and fewer iterations.** Because 2 small changes are more expensive than a larger one. **Cost of Change is Agile's ceiling.** When change is expensive, waterfall is the rational choice. **But product people don't talk about it (or do they?)** Even though cost of change implies some "management overhead" Or maybe precisely because some people's role exist only thanks to that overhead. **So what happens if/when AI collapses the software cost of change?** Do we finally get to be more Agile? How does product management shift? --- ### Compounding Development Process **URL**: https://meaningfool.net/articles/compounding_dev_process **Date**: 2025-12-24 **Type**: Article ### Compounding Development Process I recently picked up and finished a [data lineage project](../articles/2025-12-22-antigravity_is_onto_something.md). I wanted to - **Make the most of this learning opportunity** and be able to report on my learnings (learn in public). - **Build a robust and consistent process** that would prevent me from "vibing" into a wall as happened when I first worked on this project last July. - **Build a self-improving process** This diagram captures the flow as it stands today: ![Development Process Diagram](../images/dev-process.png) The process is structured into three main phases, bookended by a "Getting Started" and "Finishing" step. #### Starting a new feature Each feature starts with: - Its dedicated **branch** - A corresponding **folder in the `specs` directory**: it will host all the documentation for the feature: specifications, research, learnings and ad hoc audit reports. #### Phase 1: Planning & Slicing **I wanted from the start to use TDD**: - I believe it's a virtuous process - Even more so when the goal is learning - Even more so when **using AI**: making baby steps and using tests as the source of truth prevents from drowning in the amount of code (and slop) that AI inevitably creates. **Step 1: Break it down**: - **Identify the baby steps**, the most atomic changes in behavior of the app from the user perspective. - If the feature is too large for a single spec, create **multiple specs** (named `01-my-first-spec.md`, `02-second-spec.md`) - Within a spec, break things down into **slices**. Each slice should add one new behavior or a very limited number of cohesive behaviors. At the end of each slice, we ought to have a working piece of software. **Step 2: Define expectation through tests**: - **In TDD, tests are the source of truth**, not the code. That's where the expected behaviors are encoded. - Each change in behavior is testable. - We (the AI and I) define the set of **E2E tests** that will verify the behavior. **Step 3: Plan the implementation**: - Only once the tests are defined do we consider the implementation plan and details. - Practically, the AI has a very hard time reasoning separately about the implementation and the tests. - But clear tests remove ambiguity and allow the AI to course correct during the implementation phase. ![Slice content excerpt](../images/spec-slice-content.png) #### Phase 2: Implementation The implementation phase is strictly TDD-inspired: **RED**: - We implement the tests first and confirm they fail. - This validates that our tests are actually testing something. **GREEN**: We implement the code to make the tests pass. **REFACTOR**: - This is where we pause. When one or multiple specs have been implemented, I ask the model to take a step back. We look at the app from different perspectivesβ€”code quality, architecture, consistency, and identify refactoring needs. - Audit reports are stored in the spec folder, e.g. `post-03-code-quality-review.md`. - Small improvements are tackled right away. - Larger refactoring result in a new spec. #### Phase 3: Analysis & Continuous Learning **The research**: - Throughout the processβ€”whether planning, implementing, or debuggingβ€”we log our findings in `research.md`. - We are basically extracting from the chats the insights that we want to preserve both for agents that would need to makes changes to the feature later, and for the humans to understand the context behind design and implementation decisions. **The learnings**: - As part of my learning in public process, I want to be able to report on my learnings (captured in my daily logs) - Learnings are extracted from the research and distilled into `learnings.md`, to which I add my own observations and insights. **The retro**: - Some of the insights relate to the development process itself. - This leads to improving `agent.md` and other documentation files, or creating new workflow commands (slash commands in CC) #### Wrapping Up At the end of a spec: - Either we identify new work within the current feature that results in new slices or new specs - Or the feature is done and we merge the branch The cycle completes when the feature is done. We merge the branch. Or, if the process revealed more work, we might identify new slices for the current spec, create new specs for the ongoing feature, or add items to the backlog. It’s a rhythm of expanding and contracting scope, but always moving forward with a working piece of software. #### Conclusion The result is a process that compounds learnings and process improvements over time. That's what Every coined as [Compounding Engineering](https://every.to/c/compounding-engineering). And Lance Martin created his own [process using "diaries"](https://rlancemartin.github.io/2025/12/01/claude_diary/). --- ### 3 weeks with Antigravity and Gemini **URL**: https://meaningfool.net/articles/antigravity_is_onto_something **Date**: 2025-12-22 **Type**: Article ### 3 weeks with Antigravity and Gemini #### The pitch 3 weeks ago I picked up a project started last July with Cloud Code + Sonnet 4.0. - Sonnet 4.5 was available but not Opus 4.5 - Antigravity and Gemini 3 had just dropped **I pitted Antigravity + Gemini against CC + Sonnet 4.5** on a task: identify a commit buried deep in a dirty commit history (I've gotten better at git hygiene since). For such a task I would never have trusted Sonnet 4.0 (it could not properly handle git history as far as 2 commits away). **Gemini won**: - Gemini nailed it from the first answer and had a much better understanding of the full history of the project. - Sonnet 4.5 gave me a wrong answer. So I gave it a shot. The experience following was really a **step-change** compared to what I experienced with CC + Sonnet 4.0 back in July. Some more details below. *Bear in mind that I'm a PM, so my assessment here is from the POV of a non-developer. And you can test the app here, and see the code on Github.* #### The ratings **Capability (Gemini): A+**: - Certainly what made the largest difference in my experience. It's really a step change compared to Sonnet 4.0 - Sonnet 4.0 would regularly get stuck, endlessly looping or delivering broken code. It would need handholding and breaking things down in smaller steps to come through. - Gemini 3.0 just outputs functional code. It might not be what you intended, nor the best code. But that's something you can iterate from. **Context management (Antigravity): A+**: - 2nd major win: the capability of the model+harness does not degrade significantly with longer conversations. I'm not sure how they manage the context, but they removed a major cognitive load for me: no more stressing over the remaining number of tokens. - I still start new conversations regularly, but on my own terms / schedule **Enjoyment (Antigravity + Gemini): A+**: - Best measure: ratio of time spent getting it back on track from 80% to 30% - The result is the model can be interacted with at a much higher level: we have had discussions about code architecture, best practices. It provided a lot of learning opportunities that were not forced on me because of the inability of the model to work on its own. - Gemini 3 immediately felt faster than Sonnet 4.0 or 4.5...and now Gemini 3 Flash :) ![Gemini + Antigravity](../images/gemini+antigravity.png) **Instruction-following (Antigravity + Gemini): C+** - Because Gemini is a smarter model, it does not require as much low-level instructions about how to do things. It feels less random. It can be steered at a higher level than Sonnet 4.0. - Writing some instructions have a real and consistent impact for some things... but not others: - Antigravity would follow my template and process for spec-driven development. - But it would keep getting started on implementation before I gave the go, despite my clear agent.md rules. - And it would keep struggling on end-to-end testing practices despite my providing explicit guidance. - It's not a specific Antigravity issue, but the instruction-providing experience in all those coding-agent is really lagging. **Closed-loop development (Antigravity): C-** - Antigravity should be praised for trying to design a tighter development loop for the agent itself and for the agent-human pair. More specifically: - Antigravity provided a browser capability to the agent (as did Cursor, but I did not try it yet). The issue: it does not know when it's a good time to use it. And it's excruciatingly slow, regularly asking for user permission.So it's mostly useless for now. - Antigravity created "artifacts" (implementation_plan.md, walkthrough.md, task.md, replays) to initiate and close the loop with the developer. This goes in the right direction, especially for people that are new to development. The issue is that you can't fit it to your process. So for more experienced developer (even at my level), this is a distraction. At those artefacts did not get in the way (although it's quite unclear what is the "source of truth" when you have competing docs) - Right now the only way to define your own "loop" with specific checkpoints and artifacts is in agent.md hoping. Which is usually picked up, but not a guarantee either. Being able to customize the loop and the artifacts would be a real step forward. - Although the efforts at closing the development loop really are not yet very valuable, that sets Antigravity apart from the competition. **Testing (Antigravity + Gemini): D** - Models are good and biased towards writing code, not driving development through sound testing strategies. - The D reflects that gap, although it got better since Sonnet 4.0. - Antigravity + Gemini are at least compatible with TDD: steering the development cycle around RED - GREEN phases mostly worked, while it was mostly ignored by Sonnet 4.0 - The 1st problem is that Gemini is a decent partner for discussing testing strategy, but I would never hand it the keys. At least for End-to-End tests that I used systematically to test the change in behviour shipped to users. - The 2nd problem is that Gemini + Antigravity is pretty bad at writing robust E2E tests (I believe it would be the same for other models on the market): it regularly produces flaky tests that it struggles to debug. - The 3rd problem is that it regularly misreports on failing tests. - Testing (E2E testing in particular) is definitely the most deficient and time-consuming area at the moment. **Reasoning (Gemini): C-** - Working with graph that have oriented edges revealed challenging for Gemini. - When working on graph validation rules, graph layout or graph traversal, I had to be very prescriptive about the algorithm details. #### Conclusion **Models** The last generation of models (Gemini 3 and I guess Opus 4.5) is getting us into a new territory. But that's not news. As far as I am concerned, it's making development enjoyable (except when dealing with E2E testing). **IDE vs CLI** I started my journey using Cursor: the IDE felt like the right place, and the CLI a bit intimidating for a non-developer. But when I tried Claude Code, some months ago, I immediately adopted it over Cursor. It felt like it delivered as much or more without the UI bloat of the IDE. With Antigravity, the additional UI, compared to a simple CLI, adds value. **The harness** Antigravity has strong propositions, that go in the right direction. I'm really curious to see where they take it. But harness market is accelerating: it's not just Cursor vs Claude Code vs Amp anymore. There are promising proposals coming from OpenCode (and from [Oh My OpenCode](https://github.com/code-yeongyu/oh-my-opencode)) or [CodeBuff](https://www.codebuff.com/). They made somewhat of an entry, hopefully they are going to keep up with the ecosystem. --- ### The semi-async valley of death **URL**: https://meaningfool.net/articles/semi-async_valley_of_death **Date**: 2025-10-22 **Type**: Article ### The semi-async valley of death ![The Semi-Async Valley of Death](../images/semi-async_vallley_of_death.png) Cognition published this great diagram on their blog, although buried at the end of a technical post. It deserves more attention. There are already 2 classes of models: - The fast ones (Haiku and all *-mini or *-nano models) - The accurate ones (Sonnet, o3 then GPT5-Thinking,...) Fast models have taken a backseat so far: The race between frontier models has been about accuracy: we have been happily trading speed for higher accuracy. And we'll keep making that tradeoff until models reach a threshold from which the marginal value of accuracy starts declining. That's when we'll see the field split into 2 categories of models, at both end of the spectrum on cognition's sketch: - The Oracles: slow but accurate on larger and larger scopes. They are used asynchronously. - The Pair-programmers: fast and accurate on smaller scopes. They allow for interactive collaboration. It's unclear where's that threshold. Some people seem to be ready to adopt GLM 4.6 or Haiku 4.5 claiming they are accurate enough and their speed offers a different much more focused approach. --- ### Spec-driven development has it backwards **URL**: https://meaningfool.net/articles/spec-driven-development-has-it-backwards **Date**: 2025-10-15 **Type**: Article ### Spec-driven development has it backwards #### My initial skepticism My first reaction to spec-driven development was dismissive. People argued that upfront and detailed planning get better results when "vibe engineering". Although I've embraced it since, I felt and still feel it's a step in the wrong direction: one that favours **big design upfront**. My hope is that it's just temporary. #### How I came to embrace spec-driven development At first I was iterating directly with the agent in the chat: starting from a simple ask and then making corrections and adding new requirements. Since, I'm not a developer by trade, this was a very organic process (i.e. sometimes very messy). My git history shows for it. I ran into two issues that forced me to reconsider: - **Context loss**: when hitting a dead end, or reconsidering my approach, I was struggling to provide a cohesive context about what / how to change things, because previous decisions were not recorder anywhere. **Everything lived in conversations** (intentions, expectations, decisions), and all was "lost" at each new conversation. - **Extracting learnings**: I'm learning in public and documenting my journey teaching myself ai-assisted building. When I tried to automate the creation of a daily activity update, I realized that Claude did not manage to provide a good synthesis of my daily learnings just looking at my code changes. Because it lacked some of the context that was trapped in the discussions (and because it was looking at too many file diffs that it could not connect around an implicit intention) **"Garbage in, garbage out"** : too much noise, not enough signal. The problem was not so much Claude or my prompting but my development method that was too messy. I decided for a more disciplined approach: - A **more intentional use of git features** (commits and branches), without smaller, more focused batches of work. - A **record of the research findings and decisions made** (which landed me on having `spec.md` and `plan.md` files) And so here I am, spending time iterating over specs and plans before getting started. #### The problem with spec-driven approaches And that's exactly the issue I have with the process: big design upfront. Especially as I'm learning a bunch of new things: I need to be able to go down the wrong path and course-correct. And **it breaks Gall's law** that is very dear to me: "A complex system that works is invariably found to have evolved from a simple system that worked" (which I picked up from Kent Beck or Allen Hollub, or both) That's one of my key Agile learnings: complex systems emerge from simple ones. And this workflow-style is taking us in the opposite direction. #### How we got there: blame the LLMs LLMs have a much worst time making changes to an existing thing than starting from scratch. My experience is that it has a very hard time maintaining a clear distinction between the "as is" and the "to be". They limit between both blurs as the context grows, especially when there is repetition in the structure (code + tests). Said otherwise: **LLMs have high cost of change**. So high that it's actually much cheaper to start from scratch than steer it away from a bad spot. #### Where this will hopefully end up In my ideal world, LLM would have (very) low cost of change. Course-correction, and design, could happen as you discover by doing what exactly you want to do and how. In that world, spec would still exist. But **spec would emerge** from the conversations with the agent. They would be an analytical record of decisions made. And a basis for higher-level discussion when needed. The journey would be bottom - up, and up only as much as needed. **Design would happen as you go, as needed.** But for now, I have to be content going back to the waterfall habits of try and think ahead about everything. And get it wrong. And that's what I've seen people doing recently: accept that you are going to get it wrong. And adopt another practice from the high-cost-of-change times: 1. Prototype to discover. 2. Keep the specs, and trash the prototype --- Related links: - [A look at Spec Kit, GitHub’s spec-driven software development toolkit](https://ainativedev.io/news/a-look-at-spec-kit-githubs-spec-driven-software-development-toolkit) from Tessl blog (Tessl is a spec-driven agent company that gets some of it right IMO). Highlights : - Spec-driven agent does exactly and not more than what you asked. - Spec-driven dev can feel clunky (lots of overhead) --- ### AI Engineer Paris Conference Notes **URL**: https://meaningfool.net/articles/ai-engineer-paris-conference-notes **Date**: 2025-09-26 **Type**: Article #### Highlights - **Reat-time Voice AI (Kyutai)**: the best talk. Great breakdown of the voice market. Amazing work and demos, although there was no announcement, and most of the work is at least a few months old. Looks like people are sleeping on this :-/ - **Gemini has a lot of things in store**. More than can market for. Their world model is something. They also have great open source demo apps on using nano-banana, and more... #### Other worthy moments - **"We are integrators"** (Local-CI tooling by - Dagger): - The quote resonates as a new comer it really feels like I'm integrating libraries, CLI tools, frameworks. And expertise in which bricks to use is a differentiator. - The talk made interesting points about problems, but how Dagger is the solution was less striking (although, to be fair, there is probably a large part I did not properly understand) - **Giving memories to agents** (Context Engineering - Shopify): interesting take on adding different kind of memories - not sure if these terms were their own invention - Implicit memories: created by abstracting conversations between user and agent. I tried to naively implement that with a slash command in CC to improve claude.md without explicit instructions. Some kind of autonomous learning loop. - Episodic memories: e.g. serve the same request that was asked for a few days ago. Definitely a thing when you have repeated tasks that you wish could be standardized as you repeat them. It's like "implicit tools". Today, you have to either create instructions or specific commands to make such calls / processes more deterministic. - **Writing SQL** (Building an Analytics Agent - Metabase): - Provide tools/functions that abstract the main queries rather than asking the agent to write the full query - Take on not optimizing for benchmarks: I mostly agree, but one could argue that optimizing benchmarks will deteriorate the vibes beyond a certain point because the benchmark / evals doe not capture an important dimension allowing some aspects to degrade. - **Tip for prompting** (Blackforest Labs): use positive form, avoid negation. I've found myself trying to apply this already. --- ### Voice AI Maven Course Notes **URL**: https://meaningfool.net/articles/voice-ai-maven-course-notes **Date**: 2025-09-01 **Type**: Article ### Voice AI Maven Course Notes ##### Introduction This is my personal write-up and reflection from taking ["Voice AI and Voice Agents" course by Kwindla Kramer on Maven](https://maven.com/pipecat/voice-ai-and-voice-agents-a-technical-deep-dive). I’m sharing my main learnings following this course (part 1), in the spirit of [β€œlearn in public”](https://www.swyx.io/learn-in-public) Also I put together the one thing that I missed while following this course: some map of the voice ai landscape (part 2) ##### Part 1 - Main learnings During the course, several concepts initially confused me but became clearer through hands-on experience and research. Here are the main areas where I had breakthrough moments: ###### 1\. Transport Layer When working with voice, lag is worse than degraded audio. WebSocket is TCP-based. TCP enforces that all packets are delivered in order, triggering re-transmission (and blocking the rest of the sequence) when a packet is missing. For reliable server-to-server connections (like Twilio to your bot server), it’s ok to use WebSocket. But for client-to-server audio, always use WebRTC. WebRTC is UDP-based, meaning missing packets will be dropped, and won’t generate lag on unreliable connections. Telephony: if you want to connect to a phone number, you need to connect to that number though PSTN. For more sophisticated scenarios (call hand-offs, multiple callers,…) you need to use SIP. ###### 2\. Achieving conversational latency True conversational latency requires sub-500ms end-to-end processing: Every component adds latency - transport (network routing), STT processing, LLM inference (especially time-to-first-token), TTS generation, and the return trip. Geographic proximity matters: having local edge connections can save tens of milliseconds compared to long-haul internet routing. ###### 3\. Speech-to-Speech is not production-ready S2S models can capture information that is lost through text such as intonation, accent,… But they are "not production ready" for most use cases: Multi-turn conversations and long contexts cause issues with generation reliability and latency. Additionally, you lose granular control over context management (what part of the conversation or instructions are in the focus at any given time) - the API handles context internally. S2S models are not as mature as text-2-text models in terms of context management, tool use, instruction following. And how to eval them is an open question. But it’s just getting started. ###### 4\. Scaffolding for long and complex conversations There are two main approaches to architecting the conversation: The monolithic approach uses one detailed prompt (potentially 5,000+ tokens) to handle the entire conversation flow, but risks task completion failures, tool calling issues, and degraded instruction following as context grows. The sequential step approach breaks conversations into discrete phases using state machine patterns, allowing for context resets and targeted prompting, but risks losing context between steps and making backtracking difficult. For simple conversations (1-2 minutes), monolithic works fine. For complex, structured workflows like patient intake, the sequential approach with proper scaffolding often proves more reliable. ##### Understanding the Voice AI Value Chain ![](https://bloom-cannon-55d.notion.site/image/attachment%3Ada512e5d-ad62-42ee-8cbd-acc7f2db417b%3Avoice-ai-landscape.png?table=block&id=2337abcf-0cbb-802d-b739-e23e90d1d56b&spaceId=17ff6ac5-56c3-4d5a-8ba3-014a9113aaee&width=2000&userId=&cache=v2) ###### End-to-End Solutions These platforms handle the entire voice agent pipeline, integrating multiple models, managing the transport layer and providing abstractions for managing the logic and orchestration of the agent. Companies: Vapi, LiveKit, Layercode, Pipecat Cloud ###### Inference Providers These companies allow you to run your models: Serverless infrastructure providers (Modal, Cerebrium, Baseten): they can run any kind of computation, that includes running a model, on their GPUs AI inference providers (Groq, Fireworks, fal.ai): they offer open-source and sometimes closed-source models that they optimizen, through an API. Google, OpenAI: they provide their own model through an API. ###### Voice Model Specialists There are many more options than those mentioned during the course. The following website provides comparisons of models performances on the main metrics of interest: [https://artificialanalysis.ai/](https://artificialanalysis.ai/) Speech-to-text: Deepgram, Gladia Text-to-speech: Cartesia, PlayAI Speech-to-speech: OpenAI, Google Gemini --- ### Faster answers, better questions **URL**: https://meaningfool.net/articles/faster-answers-better-questions **Date**: 2025-01-22 **Type**: Article ### Faster answers, better questions Thinking about how programming and product building are being changed by the chatGPT, Cursor, Bolt and the likes: - When you build dishwashers, πŸ“ˆ increased speed = increased throughput πŸ“ˆ. - But software and product development don’t have a fixed output: we're simultaneously discovering both the solution and refining our understanding of the problem itself. **We search through the problem-solution space πŸ”**. - It’s an ITERATIVE process: each cycle of building helps clarify the intent, leading to the next cycle. - ⚑Increasing speed⚑, at first, will accelerate the process, but the path through the problem-solution space is unchanged. - But when the response time falls below a certain threshold though, **the process switches to INTERACTIVE**: we stop planning ahead, we get started and let our hands do the thinking. It’s a fundamentally different search strategy (different path, different results) which, I would argue, produces **superior results as it requires less assumptions about the problem**. - It feels like it's what GenAI / CodeGen tools are taking us to interactive land for at least part of the process. Already, building a simple prototype has become a conversation. ![Problem-solution space exploration strategies](../images/substack-test-image.webp) --- ## Daily Logs ### Activity Log - 2025-12-18 **URL**: https://meaningfool.net/articles/2025-12-18-daily-log **Date**: 2025-12-18 **Type**: Daily Log #### Learnings - **Type safety**: - Zod 4.x uses 'invalid_value' code and '.values' property for enum/literal failures, which aren't always reflected in standard types. - Used a 'Narrow Interface' ({ values?: string[] }) and 'as unknown as Narrow' to safely probe for this metadata without using 'any'. - Decoupled Zod schemas from user-facing messages by centralizing error formatting in the Importer layer for dynamic decoration and path mapping. --- ### Activity Log - 2025-12-12 **URL**: https://meaningfool.net/articles/2025-12-12-daily-log **Date**: 2025-12-12 **Type**: Daily Log #### Readings - β€œIf I can cut a 20 person team that I manage down to 4 or 5 highly accountable and AI empowered senior engineers that report directly to me, then I don’t even need a project manager. I might not even need business analysts” https://obie.medium.com/what-happens-when-the-coding-becomes-the-least-interesting-part-of-the-work-ab10c213c660 - S. Willison on HTML tools https://simonwillison.net/2025/Dec/10/html-tools/?ck_subscriber_id=887778336 - Claude mem to initialize context / reference recent conversations https://github.com/thedotmack/claude-mem #### Learnings - **Refactoring strategy**: Avoid 'Big Bang' replacements; favor Iterative Refactoring (Structure -> Visuals) to prevent drift and confusion. - **E2E Testing Architecture**: Use Strict Page Object Model and abstract Intent ('selectProperty') over Implementation ('click(".pill")') to protect against UI changes. - **Component Architecture**: Use Data-Driven UI (Blueprints/Engines) over manual JSX scripting to ensure consistency and reduce code volume. --- ### Activity Log - 2025-12-09 **URL**: https://meaningfool.net/articles/2025-12-09-daily-log **Date**: 2025-12-09 **Type**: Daily Log #### Readings - Stream: interesting Voice ai models / plaform https://getstream.io/video/ - To test in more details: https://paper.design/ #### Learnings - **Consolidated project documentation**: - Merged dispersed docs (`project.md`, `specs/README.md`, `specs/TESTING.md`) into single authoritative `AGENT.md` - Enriched `restore-context` workflow with project overview, golden rules, and "how to start" guidance - Intent: Reduce context switching and ensure critical information is provided exactly when needed - **Structured AGENT.md as a user manual**: - How to Create/Start a Feature - How to Write Specs (Vertical Slices, Impact Analysis) - How to Write Tests (E2E-First, POM, Fixtures) - How to Run Tests and Debug - How to Write Learnings (Retrospective vs Log) - **Refactoring strategy**: Un-suppress and Fix (Slice-based refactoring). - **Type validation**: Implemented Zod for runtime validation (replacing manual casts). --- ### Activity Log - 2025-12-08 **URL**: https://meaningfool.net/articles/2025-12-08-daily-log **Date**: 2025-12-08 **Type**: Daily Log #### Learnings - **Explicit State for Attention vs. Derived State**: Visual "attention grabbing" mechanisms (flashing, bouncing) should be driven by events (clicking a side panel item), not just static properties. --- ### Activity Log - 2025-12-05 **URL**: https://meaningfool.net/articles/2025-12-05-daily-log **Date**: 2025-12-05 **Type**: Daily Log #### Learnings - **Created Test Stabilization Plan**: - Identified need for Page Object Model (POM) to reduce duplication - Fixture reorganization by feature - Shift complex logic validation from E2E to unit tests - E2E test pruning to reduce flakiness and execution time --- ### Activity Log - 2025-12-03 **URL**: https://meaningfool.net/articles/2025-12-03-daily-log **Date**: 2025-12-03 **Type**: Daily Log #### Readings - Steipete Whatsapp relay + « ClawdΒ Β» agent: https://github.com/steipete/warelay/blob/main/docs/clawd.md - Extensive conversation about how to host warily + Clawd and more generally how to host agents using Claude Code SDK using Cloudflare #### Learnings - **Established Feature Completion Protocol**: - Use "Squash and Merge" for feature branches - Keep feature branches alive after merge (for reference/hotfixes) - Commit message MUST include insights from `learnings.md` - **E2E testing strategy**: - **Dynamic Aria-Labels**: Components (like ShadCN MultiSelect) often update `aria-label` to reflect state (e.g., "prop1, selected"). This breaks `getByRole` selectors that rely on exact name matches. - **Policy Reinforcement**: Always prefer state-independent attributes like `data-value` or `data-testid` over text content or accessibility labels for selection logic. - **Failure Protocol**: When a selector fails, do not guess. Inspect the DOM immediately to find stable attributes. - **Browser Subagent vs. MCP**: The Browser Subagent is a user-centric tool perfect for UI verification and "inspect element" tasks (via DOM dumps). It is distinct from the code-centric Chrome DevTools MCP, which is better for deep protocol debugging. --- ### Activity Log - 2025-12-01 **URL**: https://meaningfool.net/articles/2025-12-01-daily-log **Date**: 2025-12-01 **Type**: Daily Log #### Analysis - **Reorganized spec folders with number-based naming**: - Changed from date-based (`2025-11-19-csv-import`) to implementation-order naming (`02-csv-import`) - Rationale: Numbers reflect chronological implementation order, making project history clearer - New policy: Features get numbered when work begins (branch created), planned features remain unnumbered - Updated README.md to document the new naming convention --- ### Activity Log - 2025-11-28 **URL**: https://meaningfool.net/articles/2025-11-28-daily-log **Date**: 2025-11-28 **Type**: Daily Log #### Analysis - **Implemented Robust Graph Layout Algorithm for Data Lineage Visualization**: - Developed a custom "Single Pass Sink-Anchored" algorithm to replace Dagre for handling complex lineage graphs with invalid flows and cycles - Key design decision: Traverse **upstream** (Target β†’ Source) from "Sinks" (Outputs and Dead-end Steps) to naturally handle Right-to-Left visual flow - Introduced "Selective Reversal" to virtually reverse invalid edges (`Output->Step`, `Step->Input`) so they participate in flow calculation - Handled degenerate cases: Input-Only graphs (early exit to Layer 0), strict Input separation (force Inputs to MaxLayer+1 when needed) - Solved the Floating Cycles problem (isolated `A->B->C->A` loops with no sinks) using a "Greedy Deterministic Leader" approach: pick lowest-ID unvisited node as Pseudo-Sink, anchor to Layer 1, unroll upstream --- ### Activity Log - 2025-11-27 **URL**: https://meaningfool.net/articles/2025-11-27-daily-log **Date**: 2025-11-27 **Type**: Daily Log #### Learning - **Evolved the Graph Layout Algorithm Through Iterative Specification**: - Started with a Multi-Pass Anchoring strategy (3 passes: anchor to Output, Input, then Islands) - Simplified to Single Pass Sink-Anchored strategy after recognizing the complexity was unnecessary - **Defined Comprehensive Test Cases for Invalid Graphs**: - Expanded from 2 test cases to 10 distinct scenarios covering edge cases: - Invalid Source Direction (backward Output edge) - Missing Output (Input Chain) / Missing Input (Output Chain) - Isolated Chain (no Input, no Output - "Islands") - Mixed scenarios: Valid Chain + Missing Output/Input/Isolated Chain - Input-Input and Output-Output connections - Input-Only Graph (degenerate case) - Each test case now has explicit, testable assertions for both layer indices and visual coordinates - **Established Two-Layer Testing Strategy**: - Layer 1 (Algorithm Logic): Use `data-layer` attribute on nodes for E2E tests to verify computed layer indices - Layer 2 (Visual Rendering): Assert relative X-coordinates to verify correct rendering - Benefits: Decouples logic verification from pixel-perfect rendering; failures indicate which layer failed --- ### Activity Log - 2025-11-26 **URL**: https://meaningfool.net/articles/2025-11-26-daily-log **Date**: 2025-11-26 **Type**: Daily Log #### Learning - **Performed Architectural Review and Refactoring of Graph Validation Feature**: - Identified technical debt in the monolithic `validateGraph` function (162 lines, "God Function" mixing disparate rules) - Created detailed refactoring plan with 3 slices, ordered by risk: Components β†’ Mapper β†’ Validation Strategies - Key architectural patterns applied: - **Strategy Pattern for Validation**: Orchestrator (`graph-validation.ts`) coordinates validators but knows nothing about specific rules - **Smart Components, Dumb Containers**: Pushed edge path complexity down into `CustomEdge` so `LineageGraph` becomes declarative - **Hexagonal Architecture**: Domain layer (validators) is pure TypeScript with zero UI dependencies; `reactflow-mapper.ts` acts as Anti-Corruption Layer - **Documented Architectural Insights**: - Created comprehensive architecture review with Mermaid dependency diagram - Analyzed the codebase through the lens of Hexagonal Architecture (Ports and Adapters) - Key insight: The separation allows business logic to be tested without a browser and makes the UI library swappable - **E2E testing**: - **Robust Testing with `data-testid`**: Relying on text content is fragile; use `data-testid` and configure production builds to strip them via `reactRemoveProperties` - **Visual Verification Strategy**: "If it's visual, the test should take a picture" - use screenshot assertions for reproducible, version-controlled artifacts --- ### Activity Log - 2025-11-25 **URL**: https://meaningfool.net/articles/2025-11-25-daily-log **Date**: 2025-11-25 **Type**: Daily Log #### Reading - A semantic grep as a CLI tool with mgrep. This unlocks use cases involving "RAG", at least for a prototyping phase involving Claude Code SDK https://elite-ai-assisted-coding.dev/p/boosting-claude-faster-clearer-code - 2nd time I hear about this "Effect" framework Seems to be a strange but powerful thing https://www.youtube.com/watch?v=MHpf_XMz_aM. #### Learning - **TypeScript Best Practices**: - Avoid Non-Null Assertions (`!`) - use explicit guards or destructuring - "Parse, Don't Validate": Transform loose input into strict types at the boundary - Use Type Predicates (`isNode(n)`) instead of `as Node[]` for type safety - **React Patterns**: - Controlled vs Uncontrolled: Start with Controlled for critical UI components - Animation Cleanup: Use `onAnimationEnd` instead of `setTimeout` to avoid magic numbers - **Refactoring Insights**: - Key-based Reset pattern for robust state reset in controlled components - Layered Architecture: Domain (parsing), State (hook), and UI layers - Explicit State Clearing on validation failure (YAGNI on unused complexity) --- ### Activity Log - 2025-11-24 **URL**: https://meaningfool.net/articles/2025-11-24-daily-log **Date**: 2025-11-24 **Type**: Daily Log - In-depth discussion about Vite, what it is, how it works: https://chatgpt.com/share/692563ca-3e7c-8010-81b6-a31f87f8aaf2 - Late-interaction talk by Antoine Chaffin: https://maven.com/p/1973fe/going-further-late-interaction-beats-single-vector-limits - Swyx AIE keynote Β« No more slop Β». How do we make that a reality? Particularly true for design: - Avoid Β« distributional convergence Β»: did not have the term for the concept, but very important to convey https://www.youtube.com/watch?v=xmbSQz-PNMM&t=878s - Jason Zhou with Superdesign explicitely refers to distributional convergence (https://x.com/jasonzhou1993/status/1992954826762998124) - Pietro Schirano with MagiPath seems to have the same kind of concern (https://maven.com/p/462213/from-designer-to-design-architect-with-magic-path) --- ### Activity Log - 2025-11-21 **URL**: https://meaningfool.net/articles/2025-11-21-daily-log **Date**: 2025-11-21 **Type**: Daily Log #### Learnings - **Avoid Non-Null Assertions (`!`)**: Using `!` to bypass TypeScript's null checks creates hidden dependencies between validation and usage logic. Prefer explicit runtime guards or destructuring with checks to narrow types safely. --- ### Activity Log - 2025-11-20 **URL**: https://meaningfool.net/articles/2025-11-20-daily-log **Date**: 2025-11-20 **Type**: Daily Log #### Learnings - **E2E Test Data Strategy decision**: - Use external fixture files for file-based features (CSV, JSON uploads) - enables manual verification - Tradeoff: Fixture files create "two-file problem" but worth it for having E2E coverage with files that can be used for manual verification --- ### Activity Log - 2025-11-19 **URL**: https://meaningfool.net/articles/2025-11-19-daily-log **Date**: 2025-11-19 **Type**: Daily Log #### Analysis - **Established spec-driven development workflow**: - Created a structured folder system for features: `specs/YYYY-MM-DD-feature-name/` containing task.md (master checklist), research.md (findings), learnings.md (insights), and numbered implementation plans - Designed for multi-session AI development with clear handoff points between chats - Intent: Enable context preservation across conversations and maintain clear "why" alongside "what" - **Reset project to clean baseline**: - Reverted to commit 691216b (Clean ReactFlow + Vitest state) - Removed extensive CLAUDE.md configuration (656 lines) - Intent: Start fresh with cleaner foundation for new feature development - Cleaned up the codebase by removing dead code and brittle tests --- ### Activity Log - 2025-11-18 **URL**: https://meaningfool.net/articles/2025-11-18-daily-log **Date**: 2025-11-18 **Type**: Daily Log - Agents: how to build a plan from intention and pre-requisites: https://www.marktechpost.com/2025/11/08/how-to-build-an-agentic-voice-ai-assistant-that-understands-reasons-plans-and-responds-through-autonomous-multi-step-intelligence/ - Audio transcription metrics: https://dev.to/amara2025/building-an-audio-transcription-tool-a-deep-dive-into-wer-metrics-3j9o --- ### Activity Log - 2025-11-17 **URL**: https://meaningfool.net/articles/2025-11-17-daily-log **Date**: 2025-11-17 **Type**: Daily Log - Voice AI case study: https://modal.com/blog/decagon-case-study - A Skill that helps break down discourse into a graph: https://jarango.com/2025/11/10/the-llmapper-agent-skill/ - Cloudflare Workers bindings: https://blog.cloudflare.com/workers-environment-live-object-bindings/ - Claude Code system reminders, how Claude Code reminds itself about what's important: https://medium.com/@outsightai/peeking-under-the-hood-of-claude-code-70f5a94a9a62 - Anthropic own version of Code mode: https://www.anthropic.com/engineering/code-execution-with-mcp - Kieran Klaassen's Claude Code sub-agents https://github.com/EveryInc/every-marketplace/blob/main/plugins/compounding-engineering/agents/best-practices-researcher.md --- ### Activity Log - 2025-11-14 **URL**: https://meaningfool.net/articles/2025-11-14-daily-log **Date**: 2025-11-14 **Type**: Daily Log - Cloudflare Durable Objects in details technical explanation: https://boristane.com/blog/what-are-cloudflare-durable-objects/?ck_subscriber_id=887778336 --- ### Activity Log - 2025-11-04 **URL**: https://meaningfool.net/articles/2025-11-04-daily-log **Date**: 2025-11-04 **Type**: Daily Log #### Reading - Better aesthetics with Claude: https://github.com/anthropics/claude-cookbooks/blob/main/coding/prompting_for_frontend_aesthetics.ipynb #### Analysis - **Designed AI-Powered Career Coaching System**: - Created a structured coaching repository with clear separation of concerns: `coaching-design/` (static design files), `coaching-sessions/` (individual session records), `analysis/` (rolling analysis files) - Established a **Divergence β†’ Convergence** structure for the coaching process: - Phase 1 (Divergence): Deep dives into 4 career directions + 6 projects to explore what's attractive and reveal preferences - Phase 2 (Convergence): Pattern synthesis, reality checks, decision framework, and 90-day action plan - Key architectural decision: Analysis files are "living documents" updated after each session, while session files remain untouched post-session as a record - **Established AI Coaching Session Protocol**: - Start protocol: Check sessions-plan, read full context from coaching-design and analysis, read last session - During session: Capture emergent insights, track new anchors (directions/projects), identify connections, adapt session plan - End protocol: Create session file, update analysis files (directions, projects, patterns, preferences), recap with user, confirm next session topic - Each direction/project exploration explicitly asks "Connections to explore" to build cross-anchor understanding --- ### Activity Log - 2025-11-01 **URL**: https://meaningfool.net/articles/2025-11-01-daily-log **Date**: 2025-11-01 **Type**: Daily Log - Testing principles: don't mock dependencies: 1. Create Thin Adapters Around Libraries 2. Use Real Implementations in Integration Tests 3. Use Official Testing Utilities https://laconicwit.com/dont-mock-your-framework-writing-tests-you-wont-regret/ --- ### Activity Log - 2025-10-31 **URL**: https://meaningfool.net/articles/2025-10-31-daily-log **Date**: 2025-10-31 **Type**: Daily Log - How skills work in details: https://leehanchung.github.io/blogs/2025/10/26/claude-skills-deep-dive/ --- ### Activity Log - 2025-10-21 **URL**: https://meaningfool.net/articles/2025-10-21-daily-log **Date**: 2025-10-21 **Type**: Daily Log - Experiment with Flux Lora on Fal - Steipete https://steipete.me/posts/just-talk-to-it - Talks about GPT-5 and how it "gets me" - A lot of context has to be provided for the agent to "get" what one is trying to do - Once an agent is able to infer / recreate that context from previous output we get into "get me" zone and removes the need for so much upfront context --- ### Activity Log - 2025-10-20 **URL**: https://meaningfool.net/articles/2025-10-20-daily-log **Date**: 2025-10-20 **Type**: Daily Log - Git is holding us back (https://www.youtube.com/watch?v=eBXyn8SXFtU) - Deep dive Anthropic Skills - Makes me think of https://github.com/emcie-co/parlant: progressive disclosure based on conditionals - Devin: Rebuilding Devin for Claude Sonnet 4.5 (https://cognition.ai/blog/devin-sonnet-4-5-lessons-and-challenges) - Devin: The semi-async valley of death https://cognition.ai/blog/swe-grep --- ### Activity Log - 2025-10-14 **URL**: https://meaningfool.net/articles/2025-10-14-daily-log **Date**: 2025-10-14 **Type**: Daily Log - Geoff Huntley Amazon Kiro analysis https://ghuntley.com/amazon-kiro-source-code/ - Looked into Beads: worth considering for spec management https://github.com/steveyegge/beads - MCP is the wrong abstraction (https://www.youtube.com/watch?v=bAYZjVAodoo), I had read the Cloudflare article about MCP being RPC, but did not fully grasp until this video. - Future of Bun (https://www.youtube.com/watch?v=dSIgEJSi0rY): great analysis of the landscape and why becoming the place where agent code runs is key for Bun. Also need to further investigate the multiple responsibilities taken by bun. - Vite won (https://www.youtube.com/watch?v=w61mLV5nZK0) learnt about bundler - Code Complete with Steve McConnell https://newsletter.pragmaticengineer.com/p/code-complete-with-steve-mcconnell - Top-down vs bottom-up design approach: rare people are capable of both. People that use one type usually can't understand that others use the other approach as they are themselves unable to practice it - Looks typically like the John Ousterhout vs Kent Beck, the first being unable to conceive that design could happen as you go. --- ### Activity Log - 2025-10-13 **URL**: https://meaningfool.net/articles/2025-10-13-daily-log **Date**: 2025-10-13 **Type**: Daily Log - Ray Fernando: cursor plan mode, Sonnet able to pick up a change in scope / direction and recalibrate https://www.youtube.com/watch?v=CnMTzH9IR58 - Geoff Huntley How to build a coding agent https://ghuntley.com/agent/ --- ### Activity Log - 2025-10-09 **URL**: https://meaningfool.net/articles/2025-10-09-daily-log **Date**: 2025-10-09 **Type**: Daily Log - Figuring out a potential setup for Obsidian / Github quick capture and synchronization on both mobile and desktop for speech, text and image: https://chatgpt.com/canvas/shared/68ef587d86dc8191bfc6fcf37b0d0188 --- ### Activity Log - 2025-10-08 **URL**: https://meaningfool.net/articles/2025-10-08-daily-log **Date**: 2025-10-08 **Type**: Daily Log - Understanding Git Flow and the alternatives: https://chatgpt.com/share/68ef5910-6f54-8010-b928-8ff2f47cfd34 --- ### Activity Log - 2025-10-02 **URL**: https://meaningfool.net/articles/2025-10-02-daily-log **Date**: 2025-10-02 **Type**: Daily Log - **Agent experience tweaks**: - Fixed frontmatter validation when publishing an article - Enhanced /publish-article command so that it does not ask for permissions - Added llms.txt and llms-full.txt for AI agents experience --- ### Activity Log - 2025-10-01 **URL**: https://meaningfool.net/articles/2025-10-01-daily-log **Date**: 2025-10-01 **Type**: Daily Log - **Website SEO and indexing infrastructure**: - Goal: not being a total slop on the topic even though ranking is not a priority for now. - Covered, without going into the weeds: automatic sitemap generation, robots.txt, canonical URL generation in Astro, RSS-feed, font-loading optimization,... --- ### Activity Log - 2025-09-30 **URL**: https://meaningfool.net/articles/2025-09-30-daily-log **Date**: 2025-09-30 **Type**: Daily Log Readings: - MCP vs CLI tool: no real difference in terms of context consumption. What is needed is better designed tools. https://mariozechner.at/posts/2025-08-15-mcp-vs-cli/ - Analysis of Claude Code system prompt https://mariozechner.at/posts/2025-08-03-cchistory/ --- ### Activity Log - 2025-09-26 **URL**: https://meaningfool.net/articles/2025-09-26-daily-log **Date**: 2025-09-26 **Type**: Daily Log - **Daily log command refinement**: - Problem: daily logs lack specificity. The attempt is to improve the capturing of relevant context / intent on one end (=improve signal) and focus the context passed to the daily command on the right data (=reduce noise) - Improve the signal: started on yesterday, generate spec and plan files that capture the context and the research, as well as the change in direction. Still have to refine how / when to use them. - Reduce the noise: instead of passing diffs for all the files altered to the daily command, pass only the spec-*.md and plan-*.md markdown file diffs (i.e. focus on high intent-signaling files) - **First try at Codex**: - I'm sticking with CC for now as I want to find my workflow with it. But I already found out that ChatGPT 5 - Think is much better for research. - Codex is also on my single attempt able to come up with a sound plan in a single-shot where CC seems to be circling between bad options. - Makes me consider even more the switch or how to make them work together using RepoPrompt - Hope Claude 4.5 will close the gap - **Process documentation improvement**: Updated the daily command to focus analysis on architectural research findings and process improvements, shifting away from technical implementation minutiae toward strategic decision documentation. - **meaningfool.net site is live**: Migrated from GitHub Pages default domain to custom domain (meaningfool.github.io β†’ meaningfool.net) --- ### Activity Log - 2025-09-25 **URL**: https://meaningfool.net/articles/2025-09-25-daily-log **Date**: 2025-09-25 **Type**: Daily Log ### Activity Log - 2025-09-25 - **Daily command architecture overhaul**: - /daily called multiple bash scripts and intermediate files for no good reason - The initial effort did not rely on the newly-gained understanding that bash commands are executed first, the output incorporated in the command prompt, and the whole thing is passed to Claude - Also mutliple bash scripts in the command cannot share information (hence the original need of external files) - New approach: - Generate the list of commits using a bash script, pass it all to Claude so that it can create the summary - No more caching of the results or complex decision rules about when to generate a file or not - **GitHub API limitation to access the history of commits**: - GitHub Search API only indexes default branches. - Alternative strategies were identified as brittle or complex: https://chatgpt.com/share/68d593f0-a81c-8010-833a-d1a7eaccebd7 - Decision to retrieve only default branches commits. This will be a forcing-function for short-lived branches. Maybe a good thing. - **Tested new planning methodology**: - Recent setbacks trying to organize issues led to testing a new approach even for seemingly simple features : - Start with spec document. Iterate. - Generate detailed plan. Iterate. - When approach changes, update the plan and the specs, explain why in the commit - PROs: - Decisions are not lost in past conversations. Context can be ported from one convo to the next. - Living documentation of the decisions - CONs: - The spec is out of date at the end: I failed to update it consistently - NEXT: - To be perfected for larger changes / codebases - Contrast with self-claimed "spec-driven" approaches such as kilo and Tessl --- ### Activity Log - 2025-09-19 **URL**: https://meaningfool.net/articles/2025-09-19-daily-log **Date**: 2025-09-19 **Type**: Daily Log ### Activity Log - 2025-09-19 - **Added draft folder**: - Intent: provide full access to my writing / publishing process, without featuring it on my website (i.e. accessible through Github only) - Needed a way to "publish" and article: - Move from _draft to articles - Add the required frontmatter - **Learnings about Claude Code slash commands**: - All bash commands execute first, outputs become part of the prompt passed to Claude. It's like templating languages (e.g. jinja) where the script in the HTML is executed yielding an html-only file to be passed to the client. - No way to alternate between bash execution and Claude processing within a command (consequence of the previous item). - No way to pass variables or values from one bash command to another within the slash command. - Bash permission validation occurs before any bash commands execute - @ file references are intercepted by Claude's file system, not passed as bash arguments, e.g. calling /publish @article-file-name is equivalent to having no argument. - **Learnings about tool permissions**: - Confirmed and tried the autorization of tools at the slash command level. - Note: CC really does not know how it functions itself. This had to be researched. And I would actually need to write some guidelines for it to be better at creating new slash commands. --- ### Activity Log - 2025-09-18 **URL**: https://meaningfool.net/articles/2025-09-18-daily-log **Date**: 2025-09-18 **Type**: Daily Log ### Activity Log - 2025-09-18 - **Publishing workflow changes**: - Problem: so far it was a theoretic workflow with dummy articles at the root. The additional scaffolding (folders for articles, dailies, images,...) had broken the publishing workflow - Use of generateId callback to abstract from the path / filename in the submodule (e.g. articles/2025-01-06-my-article has a slug articles/my-article instead of articles/articles/2025-01-06-my-article) - Implemented proper date prefixing to display articles chronologically in my IDE - **Publish command overhaul** - Intent: break down monolithic publish command into focused single-responsibility scripts - Created separate scripts for content update and deployment to enable independent testing and better error isolation - Frontmatter validation: - Enhanced frontmatter validation script with better UX including success messages and directory exclusions - Integrated validation into publish pipeline to catch errors before deployment rather than during build - **Learnings about images in Astro**: - Evaluated 5 different image handling options (Astro assets, markdown-it plugins, custom components, external services, basic CSS) - Chose basic CSS approach for immediate compatibility with anticipated Content Layer API migration path --- ### Activity Log - 2025-09-17 **URL**: https://meaningfool.net/articles/2025-09-17-daily-log **Date**: 2025-09-17 **Type**: Daily Log ### Activity Log - 2025-09-17 - **Clean up development planning artifacts reflecting new approach** - Problem: following the attempt to use issues and devlog to plan and log what was done, the conclusion was that: - It was cumbersome in many respects, and did not solve the issue of collecting valuable insights to create worthy daily activity logs - I was attempting to automate the creation of something I had never tried to create manually. So I would focus on writing good logs first before automating the process. - Remove AI orchestration issue tracking system (devlog.md, issues.md) - Remove slash commands for issue management (/issue, /close) --- ### Activity Log - 2025-09-16 **URL**: https://meaningfool.net/articles/2025-09-16-daily-log **Date**: 2025-09-16 **Type**: Daily Log ### Activity Log - 2025-09-16 **Context engineering** - Latent.space podcast: Context Engineering for Agents - Lance Martin - Link: https://www.youtube.com/watch?v=_IlTcWciEC4 - Additionnal resources (in the yt video and in https://x.com/RLanceMartin/status/1966204974150680803) - Personal highlights: - "Summarization is hard": it resonates with my efforts to create a daily activity summary - Agentic search is more efficient in code than semantic search: mimics human processes, low-level tooling (file system) --- ### Activity Log - 2025-09-15 **URL**: https://meaningfool.net/articles/2025-09-15-daily-log **Date**: 2025-09-15 **Type**: Daily Log ### Activity Log - 2025-09-15 - **Harnessing Claude Code**: - Collected a few repo consolidating CC harnesses (slash commands, hooks, subagents): - Read Claude doc in details about harnessing features - Investigated with ChatGPT what emergent usage patterns can be found for slash commands, hooks, subagents, Output styles - Convo: https://chatgpt.com/share/68cac39c-93ac-8010-9b12-e787d6539d70 - Findings: - Subagents seem an appropriate way to manage context in some cases - Hooks seem both powerful, but usage patterns are still unclear to me - Output styles: usage pattern are quite obvious but to overwrite CC system prompt does not look like a trivial endeavor to do well. Would need a way to generate good output styles - **Bypassing "thinking" to create "fast commands"**: - Investigated with ChatGPT how I could create "fast commands", i.e. predefined commands that would not necessitate any thinking: - Convo: https://chatgpt.com/share/68cabdd5-9a08-8010-abd9-536e3df174b7 - Findings: - An elegant way to do that is to use hooks to intercept the command, run a predefined script and stop after the hook. - A second option would be to use the Haiku model instead of Sonnet / Opus used by default in CC. To be investigated if that can be set for a specific scope within a session or if it's a session-wide setting. - **Backlog tooling**: - Claude task master: https://github.com/eyaltoledano/claude-task-master - MCP tool: preference for a CLI tool if possible - Backlog.md: https://deepwiki.com/MrLesk/Backlog.md - CLI tool: looks appropriate - Need to test the fit and investigate further its features --- ### Activity Log - 2025-09-12 **URL**: https://meaningfool.net/articles/2025-09-12-daily-log **Date**: 2025-09-12 **Type**: Daily Log ### Activity Log - 2025-09-12 - **/daily command improvements** - Tags: personal operating system - Problems: - The command prompt contained a number of lines of bash code making it hard to understand and maintain - Claude Code would repeatedly ask for permissions slowing down / interrupting the process - Learnings: - Coding principle apply to the "harness" as well. Splitting the responsibilities, testing... Although they are "simpler" tools, Claude Code still tends to create complex / brittle code - Permissions can be set at multiple levels and for files. Claude Code muddied the settings.json with permissions for whole bash scripts instead of files. Cleanup task + policy to come up with (backlog) - Discovered slash commands cannot bypass AI processing even with ! prefix. Claude will "think" whatever is in there. Creating an incompressible delay - Results: - The daily activity logs generated don't match my expectations. Claude Code does not manage to group things logically and reverse engineer the intent. - **AI orchestration infrastructure**: - Tags: personal operating system - Analysis: - "Garbage in, garbage out": daily activity logs are bad because I don't have the structure and discipline to properly identify and log the problems I'm tackling, nor the research and decisions happening along the way. My commits reflect that I'm doing all at once. - If I want to be able to generate better daily logs, I need to encapsulate the work and record my intent explictly. - This is somewhat frustrating as all the information I need is already present (implictly) in my conversations with Claude Code. I started from the expectation that a powerful assistant would be able to draw from that. But I did not give access to the conversations yet (unclear how to provide that). And I would have the problem that conversations are not aligned on commits and on issues. - So I had the (for now) misguided expectation that AI would be able to process my messy communications and coding manners, but realized that the GIGO principle still applies. - Approach: - Create proper "issues" (I never clicked with the rigidity of US / epics 2-levels hierarchy) in "issues.md" - When issues get closed, they are moved to the devlog + enriched in technical details - Conclusion - Finding myself recreating all the backlog management features (create / close issues) in a system that is not very reactive (slash commands take at least 5 sec) - Not the right approach: I know some CLI tools provide that kind of capabilities if that's where I want to go **Thoughts** - Only automate what you know how to do: - I started by automating my daily activity generation without doing it manually before - I can tell what a bad output is (when presented with a result, I know what I don't like about it) - But I can't describe what a good output is - So before "fixing my dev process" hoping it will be enough to fix my daily activity generation, I'm going to start by writing those activity logs manually, and use /daily as helper / starter to do so that I can improve to the point of complete automation if possible - I'm going nonetheless to try and adopt better coding practice with better compartementalization and better use of commits, branches and other git features that can help make things more organized --- ### Activity Log - 2025-09-11 **URL**: https://meaningfool.net/articles/2025-09-11-daily-log **Date**: 2025-09-11 **Type**: Daily Log ### Activity Log - 2025-09-11 - **Github submodules implementation for personal site** - Tags: content management, implementation - Intent: group all my writing in a single repo, including the articles published on my personal site - Issues and learnings: - Submodule pointers mechanics: - When the content in the submodule repo is updated (commit + push), the pointer from the repo pointing remains on the same commit. - To point towards the last commit, the pointer needs to be updated, commited and pushed - At first I wanted a fully automated publication meachnism: "new content pushed => automatic rebuild" - This implied running 2 Github Actions sequentially: 1- the submodule auto update, 2- the Astro rebuild - GITHUB_TOKEN don't allow for that. It requires a PAT (Personal Access Token) approach. - After trying I changed course to have a manual deployment process for now. - Exclusion patterns to exclude files that are not .md or are not articles to be processed by Astro - **Track actions and decisions using a devlog.md** - Tags: personal operating system - Intent: keep track of the development trajectory that can be hard to extract from Github commits. - Problem to solve: - I started with a plan.md capturing the "grand scheme", but did not want to pollute it with fine-grained consideration. - I ended up starting another file tracking progress, what was tested, what failed and why, and the successive decisions. - This file ended up capturing both the plan and the logs. - Creating a separate log file so that I can see if plans and logs can be kept separate or if I need to move towards another organization - **Create a /daily command to track daily activity** - Tags: personal operating system - Intent: automatically create a publishable summary of my activity / learnings on a daily basis - Approach: - Started with the analysis of the git history for the given day (other types of activities could be captured and added later) - Used a mix of prompting and bash commands to make the execution of certain parts more deterministic (such as the retrieval of commits) and hopefully faster (less thinking) --- ### Activity Log - 2025-09-09 **URL**: https://meaningfool.net/articles/2025-09-09-daily-log **Date**: 2025-09-09 **Type**: Daily Log ### Activity Log - 2025-09-09 #### ANALYSIS - **Setup personal website content management**: - Opt for submodule, struggle with CC to get it to work #### RAW COMMITS Repository: meaningfool/meaningfool-writing Commit: [e5cf624428ba824114c0b6ac5d1b3f7dddb475f5] Update submodule to include new test article Description: - Add 'Testing the Git Submodule Content Workflow' article - Demonstrates complete content workflow from writing repo to production Repository: meaningfool/meaningfool-writing Commit: [ba446344d6a0a18241a51c58064982d54e1f3d54] Fix date format in frontmatter for Astro content collections Repository: meaningfool/meaningfool-writing Commit: [744b674dfbb4ea01965531179c2364288db711b8] Move articles to root directory for cleaner URL structure Repository: meaningfool/meaningfool-writing Commit: [06b23227d28f3831f95dd8ec42ac29c3f48d10e6] Add test article: Testing the Git Submodule Content Workflow Repository: meaningfool/meaningfool-writing Commit: [0d297b8ac2c2555d809e6d2faa214dbfd84264ff] Add test article for automated workflow Description: Test the webhook integration between writing repo and main site Repository: meaningfool/meaningfool-writing Commit: [f004637ce99289c148c83c3c51e11ca429f374a1] Initial repository structure with sample articles Repository: meaningfool/meaningfool-writing Commit: [970c4c966e7442135e54151b8ccec1d6b0fed9ba] Fix circular reference: remove self-referencing submodule Description: - Remove .gitmodules file that shouldn't exist in writing repo - Remove src/content/writing submodule reference - Writing repo should only contain markdown files, not submodules Repository: meaningfool/meaningfool.github.io Commit: [34443029446fc73ef734283e09acf69809d59253] Fix circular reference in writing submodule Description: - Update to fixed version of writing repo (970c4c9) - Writing repo no longer contains self-referencing submodule - Should fix GitHub Actions build failure with 'Missing parameter: slug' - Update restoration log with root cause analysis Repository: meaningfool/meaningfool.github.io Commit: [ef185aefa31d0ba09469e61b2264054ed82a414e] Complete Git submodules restoration documentation Description: πŸŽ‰ MISSION ACCOMPLISHED: Git submodules are now fully operational! - Documented complete resolution of content synchronization - All 3 articles now building and displaying correctly - Local build verification shows perfect functionality - Git submodules approach successfully restored with Vite fix The original vision of separated content management is now reality! Repository: meaningfool/meaningfool.github.io Commit: [ed66ad443f01c4bdedcbad13c28f119f28fa8bfc] Fix content workflow permissions: add contents write permission Description: - Add contents: write permission to allow workflow to push changes - Required for repository_dispatch triggered workflows Repository: meaningfool/meaningfool.github.io Commit: [63e959b9f810387dcd43fef171f1696a121ee4a8] Fix content update workflow: add git user config Description: - Add git user configuration to prevent identity errors - Workflow should now successfully commit submodule updates Repository: meaningfool/meaningfool.github.io Commit: [c15ce9014eeeda78f52e6eefa5171e8334354cef] Update documentation files Tracked file changes: plan.md β€” +134 / -329 (463 total) ```diff @@ -1,329 +1,134 @@ -# Content Management with Git Submodules Plan - -## Overview - -This plan details the step-by-step process to set up a Git submodule system for managing content from the `meaningfool-writing` repository, which doesn't exist yet. We'll create both repositories and establish the submodule connection. - -## Current State - -- Main repository: `meaningfool.github.io` (exists, Astro site) -- Content repository: `meaningfool-writing` (does not exist yet) -- Articles currently: Stored as markdown files in `src/content/articles/` -- About page: Content stays in `src/pages/about.astro` -- Articles location: Will move to `src/content/writing/` via submodule - -## Phase 0: Branch Setup - -### 0.1 Create Feature Branch -**Actions:** -1. Create and switch to a new branch for submodule work -2. This allows safe experimentation without affecting main branch -3. Enables easy rollback if issues arise - -**Commands:** -```bash -# In main site directory -cd /Users/josselinperrus/Projects/meaningfool.github.io -git checkout -b feature/content-submodule -git push -u origin feature/content-submodule -``` - -**Benefits:** -- Safe to experiment without affecting main branch -- Can create PR for review before merging -- Easy rollback if submodule setup has issues -- Allows testing deployment on branch before merging - -## Phase 1: Create and Set Up Content Repository - -### 1.1 Create meaningfool-writing Repository -**Actions:** -1. Create new GitHub repository: `meaningfool-writing` -2. Initialize with README.md -3. Clone locally to work with it -4. Set up directory structure - -**Directory structure for meaningfool-writing:** -``` -meaningfool-writing/ -β”œβ”€β”€ articles/ # Blog posts/articles -β”‚ β”œβ”€β”€ article-1.md # Individual articles -β”‚ β”œβ”€β”€ article-2.md -β”‚ └── images/ # Article images -β”‚ β”œβ”€β”€ article-1/ -β”‚ └── article-2/ -└── README.md # Repository documentation -``` - -**Commands to execute:** -```bash -# After creating repo on GitHub -git clone https://github.com/meaningfool/meaningfool-writing.git -cd meaningfool-writing -mkdir -p articles/images -touch articles/.gitkeep -git add . -git commit -m "Initial repository structure" -git push origin main -``` - -### 1.2 Create Sample Content -**Actions:** -1. Create 2-3 sample markdown articles -2. Add sample images for testing -3. Commit and push to establish content - -**Sample article format:** -```markdown ---- -title: "Sample Article Title" -date: "2024-01-15" -description: "Brief description of the article" -tags: ["tag1", "tag2"] ---- - -# Article Content - -Sample article content here... -``` - -**Commands:** -```bash -# Create sample articles -echo "Sample article 1" > articles/sample-1.md -echo "Sample article 2" > articles/sample-2.md -git add . -git commit -m "Add sample content for testing" -git push origin main -``` - -## Phase 2: Add Submodule to Main Site - -### 2.1 Add Submodule -**Actions:** -1. Navigate to main site repository -2. Add meaningfool-writing as submodule in src/content/ -3. Configure submodule settings -4. Test submodule functionality - -**Commands:** -```bash -# In meaningfool.github.io directory -cd /Users/josselinperrus/Projects/meaningfool.github.io - -# Add submodule - this creates src/content/writing/ -git submodule add https://github.com/meaningfool/meaningfool-writing.git src/content/writing - -# Initialize and update submodule -git submodule init -git submodule update - -# Commit the submodule addition -git add .gitmodules src/content/writing -git commit -m "Add meaningfool-writing content submodule" -git push origin main -``` - -### 2.2 Configure Astro Content Collections -**Actions:** -1. Update src/content/config.ts to point to submodule content -2. Create content collection schema for articles -3. Update existing components to use collections - -**File: src/content/config.ts** -```typescript -import { defineCollection, z } from 'astro:content'; - -const articles = defineCollection({ - type: 'content', - schema: z.object({ - title: z.string(), - date: z.date(), - description: z.string().optional(), - tags: z.array(z.string()).optional(), - }), -}); - -export const collections = { - articles: articles, -}; -``` - -**Commands:** -```bash -# Check if config exists and update it -ls src/content/config.ts -# Edit the file to point to the right directory structure -``` - -### 2.3 Update Site to Use Submodule Content -**Actions:** -1. Modify pages to read from collections instead of hardcoded data -2. Update image paths to point to submodule -3. Create dynamic routing for articles -4. Test content rendering - -**Files to modify:** -- `src/pages/index.astro` - Update to use articles from submodule -- `src/pages/articles/[slug].astro` - Update to use articles from submodule -- `src/content/config.ts` - Update collection path to point to submodule - -## Phase 3: Workflow and Automation - -### 3.1 Development Workflow -**Process:** -1. Edit content in meaningfool-writing repository -2. Push changes to meaningfool-writing -3. Update submodule in main site -4. Test and deploy - -**Commands for updating content:** -```bash -# In main site directory -cd src/content/writing -git pull origin main # Get latest content -cd ../../.. # Back to main site root -git add src/content/writing # Stage submodule update -git commit -m "Update content submodule" -git push origin main -``` - -### 3.2 Automated Submodule Updates (Optional) -**Actions:** -1. Create GitHub Action to auto-update submodule -2. Trigger site rebuild when content changes -3. Set up proper permissions - -**File: .github/workflows/update-content.yml** -```yaml -name: Update Content Submodule -on: - repository_dispatch: - types: [content-updated] - workflow_dispatch: - -jobs: - update-content: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Update submodule - run: | - git submodule update --remote src/content/writing - git add src/content/writing - if git diff --cached --quiet; then - echo "No content changes" - else - git commit -m "Auto-update content submodule" - git push - fi -``` - -## Phase 4: Testing and Validation - -### 4.1 Local Testing Steps -**Tests to perform:** -1. Clone site fresh and verify submodule works -2. Test content updates propagate correctly -3. Verify images load from submodule -4. Check article routing works -5. Validate build process includes submodule content - -**Commands:** -```bash -# Test fresh clone -rm -rf test-clone -git clone --recursive https://github.com/meaningfool/meaningfool.github.io.git test-clone -cd test-clone -bun install -bun dev -# Visit localhost:4321 and verify content loads -``` - -### 4.2 Deployment Testing -**Tests to perform:** -1. Verify GitHub Actions handles submodules -2. Check deployed site includes submodule content -3. Test content update workflow end-to-end - -**GitHub Actions considerations:** -- Ensure workflow uses `submodules: recursive` -- Check permissions allow submodule access -- Verify build process includes content from submodule - -## Phase 5: Documentation and Maintenance - -### 5.1 Update Documentation -**Actions:** -1. Update CLAUDE.md with submodule workflow -2. Document content editing process -3. Add troubleshooting guide - -### 5.2 Common Operations Documentation -**Document these workflows:** - -**Adding new content:** -```bash -cd meaningfool-writing -# Create new article -git add . && git commit -m "Add new article" -git push origin main - -cd ../meaningfool.github.io -git submodule update --remote src/content/writing -git add src/content/writing -git commit -m "Update content" -git push origin main -``` - -**Troubleshooting submodules:** -```bash -# Reset submodule if stuck -git submodule deinit src/content/writing -git submodule init -git submodule update - -# Force update submodule -git submodule update --remote --force src/content/writing -``` - -## Risk Assessment and Mitigation - -### Potential Issues: -1. **Submodule not initialized on fresh clones** - - Mitigation: Update GitHub Actions to use `--recursive` - - Document clone command: `git clone --recursive` - -2. **Content out of sync** - - Mitigation: Clear workflow documentation - - Consider automated updates - -3. **Build failures if submodule missing** - - Mitigation: Add fallback content or error handling - - Test deployment thoroughly - -4. **Permission issues accessing submodule** - - Mitigation: Use public repository - - Configure proper GitHub Actions permissions - -### Testing Checklist: -- [ ] Fresh clone with `--recursive` works -- [ ] Content displays correctly on site -- [ ] Images load from submodule -- [ ] Build process completes successfully -- [ ] Deployment includes submodule content -- [ ] Content updates propagate to live site - -## Success Criteria: -1. meaningfool-writing repository created and populated -2. Submodule successfully added to main site -3. Content renders correctly from submodule -4. Workflow for updating content is documented and tested -5. Site builds and deploys with submodule content -6. Team can edit content independently of site code - -This plan provides a comprehensive approach to setting up content management via Git submodules, with clear steps for testing and validation at each phase. \ No newline at end of file +# Content Management Implementation Status + +## What Was Accomplished βœ… + +### βœ… Phase 1: Repository Setup +- **meaningfool-writing repository**: Created and populated with sample content +- **Sample articles**: Added 2 initial articles with proper frontmatter +- **Test article**: Added "Testing the Git Submodule Content Workflow" article + +### βœ… Phase 2: Site Integration +- **Content collections**: Configured Astro `writing` collection in `src/content/config.ts` +- **Page updates**: Modified `index.astro` and `articles/[slug].astro` to use writing collection +- **Component updates**: Updated `ArticlesList.astro` for new collection type +- **Clean URLs**: Articles generate clean URLs like `/articles/sample-article-1` + +### βœ… Phase 3: Testing & Deployment +- **Local testing**: Playwright browser testing confirmed content displays correctly +- **Production deployment**: All articles successfully deploy to https://meaningfool.github.io/ +- **Content workflow**: Demonstrated end-to-end content creation and deployment + +### βœ… Phase 4: Content Structure +- **Legacy cleanup**: Removed old `src/content/articles/` directory +- **Simplified collections**: Only using `writing` collection now +- **Documentation**: Updated CLAUDE.md with content management workflows + +## What Happened: Submodule to Regular Files ⚠️ + +**Original Plan**: Use Git submodules to manage content in separate `meaningfool-writing` repository +**What Actually Happened**: Converted to regular files due to deployment complexity + +### The Conversion Process: +1. **Submodule Issues**: Git submodules created complex nested directory structures during deployment +2. **Build Failures**: GitHub Actions deployment failed with routing errors +3. **Emergency Fix**: Converted submodule content to regular files in main repository +4. **Result**: Content now lives directly in `src/content/writing/` as normal files + +### Current State: +- **Repository structure**: Single repository with all content and code +- **Content location**: `src/content/writing/*.md` (regular files, not submodule) +- **Workflow**: Standard git workflow - edit, commit, push to main repository +- **Deployment**: Working perfectly with regular files + +## Remaining Tasks (If Desired) πŸ“‹ + +### Option A: Restore Git Submodule Approach +If you want the original vision of separated content management: + +#### A1. Fix Submodule Setup +- Remove current `src/content/writing/` directory +- Properly re-add `meaningfool-writing` as Git submodule +- Ensure clean directory structure (no nesting) +- Test local development server recognizes submodule content + +#### A2. Fix Deployment Pipeline +- Update GitHub Actions to properly handle submodules +- Ensure `actions/checkout@v4` uses `submodules: recursive` +- Test deployment with submodule content +- Verify production site includes submodule articles + +#### A3. Implement Automation +- Create webhook in `meaningfool-writing` to trigger main site updates +- Set up automated submodule update workflow +- Test end-to-end content workflow: write β†’ commit β†’ auto-deploy + +#### A4. Document Workflows +- Update CLAUDE.md with correct submodule workflows +- Document content editing process for writers +- Create troubleshooting guide for submodule issues + +### Option B: Improve Current Approach +If you prefer the simplified single-repository approach: + +#### B1. Content Organization +- Create content categories/tags system +- Add content templates for consistent formatting +- Implement content validation workflows + +#### B2. Writing Experience +- Set up content editing in separate IDE window +- Create content contribution guidelines +- Add pre-commit hooks for content validation + +#### B3. Advanced Features +- Add RSS feed generation +- Implement content search functionality +- Add related articles recommendations + +## Decision Point πŸ€” + +**Choose your path:** + +### Path A: **Separated Content Management** (Original Vision) +- βœ… Clean separation of content and code +- βœ… Independent content workflows +- βœ… Team collaboration friendly +- ❌ Complex deployment setup +- ❌ Git submodule learning curve +- ❌ Potential deployment fragility + +### Path B: **Unified Repository** (Current State) +- βœ… Simple, proven approach +- βœ… Reliable deployment +- βœ… Easy development setup +- ❌ Content and code mixed together +- ❌ All contributors need main repo access +- ❌ No independent content workflows + +## Technical Debt & Lessons Learned πŸ“ + +### What Went Wrong: +1. **Submodule Complexity**: Git submodules are powerful but require careful setup +2. **CI/CD Integration**: Deployment pipelines need explicit submodule configuration +3. **Directory Structure**: Nested paths caused routing issues in static site generation +4. **Testing Gap**: Should have tested deployment pipeline earlier in process + +### What Worked Well: +1. **Astro Content Collections**: Excellent for managing markdown content +2. **Feature Branch Development**: Safe experimentation without affecting production +3. **Automated Deployment**: GitHub Actions works perfectly with regular files +4. **Content Structure**: Clean frontmatter and file organization + +### Recommendations: +- **For simple blogs**: Use regular files approach (current state) +- **For team collaboration**: Fix and use Git submodules approach +- **For large content teams**: Consider headless CMS integration instead + +## Next Steps πŸš€ + +1. **Decide on approach**: Submodules (Path A) vs Regular files (Path B) +2. **Implement chosen path**: Follow remaining tasks above +3. **Add real content**: Replace sample articles with actual blog posts +4. **Enhance site features**: SEO, analytics, styling improvements + +The foundation is solid - content management is working end-to-end. The choice now is about workflow complexity vs. team collaboration needs. \ No newline at end of file ``` Repository: meaningfool/meaningfool.github.io Commit: [f6c827e4ca6dab34250aafd60b7e1b230029e3e2] Update submodule restoration progress log Description: - Document successful deployment pipeline testing phase - Record push to remote branch for GitHub Actions testing Repository: meaningfool/meaningfool.github.io Commit: [652df8a9bff50232e2288eabcf4f31ff275ff022] Successfully restore Git submodules with Vite fix Description: - Remove regular content files from src/content/writing/ - Add meaningfool-writing repository as Git submodule - Vite preserveSymlinks configuration resolves content collection issues - βœ… VERIFIED: Articles display correctly on homepage - βœ… VERIFIED: Individual articles render full content - βœ… VERIFIED: No more 404 errors or empty collections 🎯 Git submodules are now working perfectly with Astro! Repository: meaningfool/meaningfool.github.io Commit: [dbf46fcf1cc945e056e9bb5051944666d42da557] Add Vite preserveSymlinks fix for Git submodules Description: - Configure astro.config.mjs with preserveSymlinks: true - This resolves Astro content collections not recognizing submodule content - Verified fix works with current content (articles display properly) - Ready to proceed with Git submodule setup 🎯 This was the missing piece that caused the original submodule deployment issues! Repository: meaningfool/meaningfool.github.io Commit: [ba95656c2ef2a430acf0f610cb9f866b429ea7c8] Add comprehensive submodule restoration plan and progress tracking Description: - Document complete analysis of previous submodule attempt - Create detailed 6-phase implementation plan - Add progress log for real-time tracking - Safety first: working on feature branch Tracked file changes: COMPREHENSIVE_PLAN.md β€” +244 / -0 (244 total) ```diff @@ -0,0 +1,244 @@ +# Comprehensive Git Submodule Implementation Plan + +## Executive Summary + +This document outlines a comprehensive plan to successfully implement Git submodules for content management in the `meaningfool.github.io` project. Based on analysis of the codebase, git history, and GitHub documentation, this plan addresses the previous deployment failures and provides a robust path forward. + +## Background Analysis + +### Current Situation +- **Previous attempt**: Git submodules implemented in `feature/content-submodule` branch but failed during deployment +- **Current state**: Reverted to regular files approach in main repository (`src/content/writing/`) +- **Infrastructure**: GitHub Actions workflows already configured for submodules with `submodules: recursive` +- **Goal**: Achieve separated content management while maintaining reliable deployment + +### What Went Wrong Previously +From `plan.md` analysis and commit history: +1. **Deployment failures**: GitHub Actions deployment failed with "routing errors" +2. **Complex directory structures**: Nested paths caused static site generation issues +3. **Emergency conversion**: Submodule content converted to regular files to restore functionality + +### Key Advantages of Submodule Approach +- βœ… Clean separation of content and site code +- βœ… Independent content workflows +- βœ… Team collaboration friendly (content writers don't need main repo access) +- βœ… Version control for content separate from code changes + +### Current Infrastructure Analysis +**Strengths:** +- GitHub Actions already configured with `submodules: recursive` +- Automated content update workflow (`update-content.yml`) ready +- Astro content collections properly configured for `writing` collection + +**Issues to Address:** +- Missing `.gitmodules` file (was removed during reversion) +- `src/content/writing/` exists as regular directory, not submodule +- Need to identify and fix root cause of deployment failures + +## Implementation Plan + +### Phase 1: Investigation and Preparation (Day 1) + +#### 1.1 Root Cause Analysis +- [ ] **Examine deployment logs** from failed submodule deployment attempts +- [ ] **Test submodule locally** to identify exact failure points +- [ ] **Verify `meaningfool-writing` repository** status and access +- [ ] **Document specific error messages** and failure modes + +#### 1.2 Environment Validation +- [ ] **Verify GitHub Actions permissions** for submodule access +- [ ] **Test local development** with submodule configuration +- [ ] **Validate Astro content collection** configuration for submodule paths +- [ ] **Check deployment target** (GitHub Pages) for submodule compatibility + +### Phase 2: Clean Submodule Implementation (Day 2-3) + +#### 2.1 Repository Cleanup +```bash +# Remove current content files (backup first) +git checkout main +cp -r src/content/writing/ backup-content/ +git rm -r src/content/writing/ +git commit -m "Remove regular content files to prepare for submodule" +``` + +#### 2.2 Proper Submodule Addition +```bash +# Add submodule correctly +git submodule add https://github.com/meaningfool/meaningfool-writing.git src/content/writing +git add .gitmodules src/content/writing +git commit -m "Add content submodule with proper configuration" +``` + +#### 2.3 Content Migration +- [ ] **Migrate current content** to `meaningfool-writing` repository +- [ ] **Ensure proper frontmatter** format consistency +- [ ] **Test content accessibility** in local development +- [ ] **Verify clean URL generation** (`/articles/[slug]`) + +### Phase 3: Deployment Pipeline Hardening (Day 3-4) + +#### 3.1 GitHub Actions Optimization +Current `deploy.yml` is configured correctly, but needs validation: + +```yaml +# Verify this configuration works +- name: Checkout your repository using git + uses: actions/checkout@v4 + with: + submodules: recursive +``` + +**Testing Steps:** +- [ ] **Create test branch** with submodule setup +- [ ] **Deploy to test environment** first +- [ ] **Validate content rendering** in deployed site +- [ ] **Check all article URLs** are accessible +- [ ] **Monitor build logs** for submodule-related warnings + +#### 3.2 Common Submodule Issues Prevention +Based on GitHub documentation and common pitfalls: + +**SSH vs HTTPS URLs:** +- Ensure submodule uses HTTPS URLs for GitHub Actions compatibility +- Verify `.gitmodules` uses `https://github.com/` not `git@github.com:` + +**Submodule State Management:** +- Ensure submodule points to specific commit, not floating branch reference +- Document submodule update procedures + +#### 3.3 Automated Content Updates +The existing `update-content.yml` workflow needs testing: +- [ ] **Test repository_dispatch** trigger from content repo +- [ ] **Verify automated submodule updates** work correctly +- [ ] **Test manual workflow_dispatch** trigger +- [ ] **Validate commit message formatting** and push permissions + +### Phase 4: Content Workflow Implementation (Day 4-5) + +#### 4.1 Content Repository Webhooks +Set up automated triggers from `meaningfool-writing` to main site: + +```bash +# In meaningfool-writing repository +# Add webhook to trigger main site update on push +curl -X POST \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + https://api.github.com/repos/meaningfool/meaningfool-writing/hooks \ + -d '{ + "name": "repository_dispatch", + "config": { + "url": "https://api.github.com/repos/meaningfool/meaningfool.github.io/dispatches", + "content_type": "json", + "secret": "webhook_secret" + }, + "events": ["push"] + }' +``` + +#### 4.2 Content Editing Workflow +Document the complete content creation process: +1. **Write content** in `meaningfool-writing` repository +2. **Commit and push** to main branch +3. **Webhook triggers** main site update automatically +4. **Site rebuilds** and deploys with new content + +### Phase 5: Testing and Validation (Day 5-6) + +#### 5.1 End-to-End Testing +- [ ] **Local development testing** + - Clone fresh repository with submodules + - Verify `bun dev` works with submodule content + - Test article navigation and rendering +- [ ] **Staging deployment testing** + - Deploy to test branch first + - Validate all content appears correctly + - Check performance and build times +- [ ] **Production deployment testing** + - Deploy to main with monitoring + - Verify no content is lost + - Test automated content updates + +#### 5.2 Rollback Preparation +- [ ] **Document rollback procedure** to regular files +- [ ] **Keep backup** of current working content +- [ ] **Test rollback process** on test branch first + +### Phase 6: Documentation and Maintenance (Day 6-7) + +#### 6.1 Update Documentation +- [ ] **Update CLAUDE.md** with corrected submodule workflows +- [ ] **Document troubleshooting procedures** for common issues +- [ ] **Create content contributor guide** for writers +- [ ] **Update README.md** with submodule setup instructions + +#### 6.2 Monitoring and Alerts +- [ ] **Set up deployment monitoring** for submodule-related failures +- [ ] **Create GitHub Issues templates** for content workflow problems +- [ ] **Document performance baselines** for build times with submodules + +## Risk Assessment and Mitigation + +### High Risk Items +| Risk | Impact | Mitigation | +|------|---------|------------| +| **Deployment failure recurs** | High | Thorough testing on staging branch first | +| **Content becomes inaccessible** | High | Maintain backup of all content files | +| **Performance degradation** | Medium | Monitor build times and optimize if needed | +| **Team workflow confusion** | Medium | Clear documentation and training | + +### Low Risk Items +- Webhook configuration complexity (well-documented APIs) +- Local development setup (submodules are standard Git feature) +- GitHub Actions configuration (already mostly correct) + +## Success Metrics + +### Technical Metrics +- [ ] **Zero deployment failures** for 1 week after implementation +- [ ] **Build time increase** less than 30% compared to regular files +- [ ] **All content accessible** at expected URLs +- [ ] **Automated updates** working within 5 minutes of content push + +### Workflow Metrics +- [ ] **Content editing workflow** documented and tested +- [ ] **Team members** can contribute content without main repo access +- [ ] **Emergency procedures** documented and tested +- [ ] **Development setup** works for new contributors + +## Alternative Approaches + +### Option B: Enhanced Single Repository +If submodule approach fails again, consider these improvements: +- **Content organization**: Implement content categories and tagging +- **Workflow separation**: Use GitHub branch protection for content-only changes +- **Automation**: Pre-commit hooks for content validation +- **Collaboration**: GitHub's web interface for content editing + +### Option C: Hybrid Approach +- **Git subtrees** instead of submodules (more complex but more integrated) +- **Headless CMS integration** (Contentful, Strapi, etc.) +- **Content API approach** with separate content service + +## Timeline Estimate + +| Phase | Duration | Dependencies | +|--------|----------|--------------| +| Phase 1: Investigation | 1 day | Access to logs and repos | +| Phase 2: Implementation | 2 days | Clear requirements | +| Phase 3: Deployment | 2 days | Test environment access | +| Phase 4: Workflow Setup | 1 day | Repository permissions | +| Phase 5: Testing | 2 days | Working implementation | +| Phase 6: Documentation | 1 day | Completed testing | +| **Total** | **9 days** | All dependencies met | + +## Conclusion + +This plan addresses the previous submodule implementation failures by: +1. **Thorough investigation** of root causes +2. **Systematic implementation** with testing at each phase +3. **Risk mitigation** through backup and rollback procedures +4. **Clear documentation** for ongoing maintenance + +The submodule approach remains viable and valuable for content management, provided we address the deployment pipeline issues properly. This plan minimizes risk while maximizing the benefits of separated content management. \ No newline at end of file ``` --- ## Footer Generated: 2026-03-03T15:38:42.098Z Total Articles: 15 Total Daily Logs: 40