Category: AI-Assisted Development

  • Before We AI

    Before We AI

    I have spent much of my professional life between business, data and enterprise technology.

    One problem keeps returning: producing an answer is often easier than establishing what that answer actually means.

    AI makes producing answers dramatically faster. But before we AI, we need to understand which data is relevant, what it means, which rules apply and who has the authority to decide.

    That thought became the name of my project: before-we-ai.

    It starts with a specific business question and asks:

    What must be true for this answer to deserve trust?

    The system traces the answer back to its sources and assumptions, tests what can be verified and makes visible what still requires a business decision.

    A plausible answer is not necessarily a reliable one

    Imagine a shipbuilder asking:

    What will it cost to build vessel S42?

    The available sources contain a bill of materials, purchase prices, labour hours and overhead rates. AI can combine them and calculate a credible total.

    But which design revision applies? Does “cost” mean expected or actual cost? Is the purchase price the approved costing basis? Are labour hours recorded per vessel or for a production batch? Which overhead rule is current?

    These are not technical details around the answer. They determine what the answer represents.

    A calculation can therefore be correct and still answer the wrong question.

    From data discovery to answer readiness

    The project began as an experiment in understanding unfamiliar business data.

    Modern AI can inspect tables, follow spreadsheet formulas and propose relationships remarkably quickly. I wanted to explore how far this could reduce the effort required to understand a new data landscape.

    But finding a field called Purchase_Price does not establish that it is the price the organisation intends to use for expected costing. Reproducing an existing workbook does not prove that the workbook still follows the approved policy.

    That changed the direction of the project.

    Instead of asking whether AI can understand the data, I began asking:

    What must be true for this particular answer to deserve trust?

    This moves the focus from broad “AI readiness” to the readiness of one intended answer.

    The same data may be adequate for exploration and insufficient for a financial decision. Readiness depends on the intended use and what could happen if the answer is wrong.

    What the draft product looks like

    The first version is deliberately simple.

    A user supplies files or database extracts, asks a business question and clarifies the intended result. The system inspects the sources, establishes what a reliable answer would require and presents a readiness assessment.

    The user should not have to design a semantic model or translate the question into technical language.

    The system asks only the focused questions that the available sources cannot resolve—for example, which BOM revision is approved or whether labour hours apply per vessel or per batch.

    Later, once the answer is sufficiently supported, the product can also execute the final calculation.

    But the calculated number is not the core product.

    The readiness decision is.

    How it works

    It begins with a normal business question:

    What will it cost to build vessel S42?

    Before calculating anything, the system clarifies what the requested answer should mean. Is this expected or actual cost? Should it use the currently approved design? Should the result include overhead?

    It then works backwards from that clarified question and establishes what a reliable answer would require.

    For vessel costing, it needs to know which design applies, which material prices are valid and how labour and overhead should be treated.

    Some of this can come from established domain knowledge. General costing principles provide a starting point and help the system recognise what it should look for.

    More knowledge is discovered in the supplied sources. The system can follow formulas, identify relationships and reconstruct how an existing workbook calculates its result.

    But observation is not the same as approval.

    The fact that a workbook uses Purchase_Price shows how that workbook currently works. It does not establish that Purchase_Price is the company’s approved basis for expected costing.

    Where the sources cannot establish business intent, the system asks a focused question or looks for an authoritative document.

    The result is a growing body of validated knowledge about how this type of answer should be produced in this organisation. Each piece retains its origin, scope and validation status.

    The system then applies that knowledge to the current sources, tests the important dependencies and determines whether the answer is ready.

    In simple terms:

    Ask → Clarify → Establish what a reliable answer requires → Validate the sources → Fill important knowledge gaps → Assess readiness → Answer

    The system does not start from zero each time. Confirmed knowledge can be reused for later questions when its scope still applies.

    This is where the longer-term value begins to emerge: each assessment can improve the organisation’s usable knowledge without pretending to model the entire enterprise in advance.

    The Readiness Map

    The Readiness Map shows how well the requirements for the intended answer are supported.

    It connects what the answer depends on with the available evidence, confirmed business knowledge and unresolved gaps.

    Executable checks can establish that identifiers match across files, formulas reproduce recorded totals or two sources contradict each other.

    They cannot decide which source or rule the organisation intends to use.

    That distinction remains visible in the result.

    The readiness decision is intentionally not expressed as a percentage. “83% trustworthy” would imply a precision the system does not have.

    Instead, the assessment gives one of three controlled outcomes:

    • Ready — all material dependencies are sufficiently supported.
    • Ready with limitations — a clearly defined, narrower answer is justified.
    • Blocked — an unresolved dependency could materially change the answer.

    “Ready with limitations” must mean something precise. In the vessel example, the permitted answer might exclude overhead because no valid overhead rule has been established.

    If the selected material-price basis remains unconfirmed and could materially change even that narrower result, the correct decision is blocked.

    Stopping is therefore not a failed run. It is a useful result when the available evidence does not justify greater confidence.

    What the system learns

    The knowledge used by before-we-ai comes from sources with different levels of authority.

    Established domain knowledge provides general principles. For example, material cost depends on component quantities and an applicable price.

    Source inspection reveals how the available files and systems appear to work. It may show that a workbook calculates material cost by multiplying BOM quantity by purchase price.

    Organisational knowledge establishes what the company intends. An approved policy or authorised person may confirm which BOM revision and price basis must be used.

    These statements are related, but they are not interchangeable:

    Observed: The workbook uses Purchase_Price.

    Confirmed: Purchase_Price is the approved basis for expected vessel costing.

    AI can discover the first statement. It cannot safely authorise the second.

    Confirmed knowledge is therefore stored together with its source, scope and validity. A rule applying to expected costing for one vessel family should not silently become a universal company rule.

    Over time, this creates a reusable knowledge base grounded in actual business questions and validations.

    The architecture, briefly

    The first implementation uses one AI agent. The essential design choice is the separation of responsibilities around it.

    AI interprets the question, proposes possible meanings and determines what should be checked.

    Executable components inspect schemas, extract spreadsheet formulas, run SQL and perform reconciliations.

    Approved documents or people with the relevant authority establish what the organisation intends.

    The readiness layer decides what the available support permits the system to claim.

    The high-level flow is:

    Business question
    → clarify the intended answer
    → establish what a reliable answer requires
    → inspect the available sources
    → propose meanings and checks
    → collect evidence and confirmation
    → assess readiness
    → produce, limit or block the answer

    The principle behind this architecture is simple:

    The model may propose. Evidence may demonstrate. Authority establishes intent. Readiness controls the answer.

    What this could become

    The immediate goal is modest: demonstrate one complete answer path using a realistic business question and a limited set of sources.

    A meaningful result would not merely produce a vessel-cost total. It would be able to say:

    I can reproduce the number in the workbook, but I cannot yet confirm that it represents the current approved expected cost.

    It should then identify exactly what is missing, why it matters and how the gap can be resolved.

    If this works with real data, before-we-ai could sit between enterprise data and AI applications as an answer-readiness control layer.

    Reports, analytics tools and agents could consume an answer together with its readiness status, evidence and limitations.

    It would complement data platforms, catalogues and semantic models rather than replace them. Those systems organise data and context at scale.

    before-we-ai makes a narrower decision:

    Is the available context sufficient for this particular answer?

    Each resolved question can add scoped, reusable organisational knowledge. The knowledge base grows through actual work rather than through an attempt to describe the entire enterprise in advance.

    Why I am building it

    before-we-ai brings together the areas in which I have worked for many years: finance, enterprise data, reporting and transformation.

    It also reflects what interests me most about AI.

    The opportunity is larger than producing answers faster. AI can help expose how business knowledge is distributed and where apparently settled calculations still depend on unspoken assumptions.

    But plausible reasoning and justified confidence remain different things.

    Capability is not authority, and plausible reasoning is not evidence.

    before-we-ai is my attempt to turn that distinction into a working system.

    This was not my first AI-supported project, but it was the most complex so far—and the first time the process genuinely felt like working with a development partner. That experience depended heavily on learning how to use agents effectively: matching the model and reasoning level to the task, choosing the right execution environment, and balancing speed against limited credits. Claude Fable and GPT-5.6 Sol made that shift especially tangible.

    The next step is a small real-world test: one meaningful question, the sources currently used to answer it, and an honest decision about whether the result deserves trust.

    Feedback and co-work wellcome

    The project is open on https://github.com/happychriss/before-we-ai/—feedback, contributions, and sharing are very welcome.

  • Claude Code in a Dev Container: From Coding Help to System Thinking

    Claude Code in a Dev Container: From Coding Help to System Thinking

    Something shifted in how I program. Not just in the tools I use — but in how I think about the work itself.

    It’s Not Really an Agent. But It Feels Like One.

    I recently wrote about why context is the missing layer in AI-driven work — the idea that the sequence is Data → Analytics → Context → Action, and that without context, even smart systems stay limited. You can read that post here.

    Claude Code is a good example of this in practice. It’s not a true autonomous agent. It doesn’t plan multi-step strategies on its own. It doesn’t have memory across sessions by default. But when you give it the right context — the codebase, the requirements, the error logs, the hardware specs — something interesting happens. It starts to act rather than just answer. It writes, runs, reads the output, adjusts, and iterates. That feedback loop — code, execute, observe, fix — is what makes it feel agentic, even though technically it isn’t.

    The intelligence was always there in the model. What unlocks it is context. And building the right container for that context — literally and figuratively — is what this post is about.

    The Old Way Was Frustrating

    I started experimenting with Claude Code early on. The concept is great: give an AI a task, let it write code, run it, see what breaks, fix it, repeat. Fully autonomous.

    But the default experience was painful. Constant confirmation prompts. “Should I proceed?” — yes. “Are you sure?” — yes, just do it. That kills any sense of flow.

    Then I heard about --dangerouslySkipPermissions. That removes all the guardrails. Claude just executes. Which sounds great — until you realize you’re running an autonomous AI agent with full access to your actual PC. Your home directory. Your SSH keys. Everything.

    I also tried running Claude on an isolated machine — an old Raspberry Pi — which worked but was awkward and had no real development environment. Not a long-term solution.

    A Swiss Guy on YouTube Had the Right Idea

    I stumbled across this video. Andreas Spiess — strange accent, great ideas — who simply installed Claude Code inside a virtual machine and ran it with full permissions there. Clean isolation. No risk to the host.

    The direction was right. But a full VM is heavy. I wanted something lighter, faster to spin up, and more developer-friendly.

    After a bit of research I found VS Code Dev Containers — and they turned out to be a perfect fit.

    What Is a Dev Container?

    A Dev Container is just a Docker container — but with a special .devcontainer/ configuration folder in your project. When you open the project in VS Code, it detects this config and asks: “Reopen in Container?”

    You click yes. VS Code reopens. Same editor. Same extensions. Same terminal. But now everything runs inside Docker. The container is isolated from your host. Your actual filesystem, your home directory, your other projects — invisible and unreachable from inside.

    You don’t even notice you’re in a container. That’s the point.

    This is exactly the sandbox Claude Code needs to run with full permissions safely.

    The Template I Built

    I created a reusable template on GitHub — a ready-to-use Dev Container setup for Claude Code with a clear security model and a practical project structure.

    Security model — what’s protected:

    • Host filesystem: only the ./project folder is mounted — nothing else on your PC is visible inside the container
    • No privileged mode: the container cannot escape to the host kernel
    • SSH keys: never copied in — the host SSH agent socket is forwarded, keys stay on your machine
    • GitHub token: passed as env var, never stored in the container
    • USB access: hot-plug support for ESP32 and Arduino via device_cgroup_rules — no full /dev access needed

    There is also an optional outbound firewall that restricts Claude to allowlisted domains only (Anthropic API, GitHub, npm, VS Code). I usually leave it off and just watch what Claude does — but it’s there if you want it.

    The Project Structure — Context Is Everything

    The container gives Claude a clean, isolated workspace. But the real insight is what’s inside the project/ folder. This is where context lives — and context is what makes Claude Code go from impressive demo to actual tool.

    project/
      CLAUDE.md          ← session bootstrap: tells Claude what to read at startup
      requirements.md    ← what you're building — written by you, for Claude
      memory.md          ← live session memory — Claude writes here
      skills/            ← working conventions, loaded every session
      knowledge/         ← confirmed config and integration notes
      external-docs/     ← raw reference material (API docs, specs)
      src/               ← the actual source code

    Every session, Claude reads CLAUDE.md first. That file tells it to load requirements.md, all skill files, and all knowledge files before doing anything else. Claude knows the full project context before writing a single line of code.

    requirements.md is the key file — and writing it is now your main job. You don’t write code anymore. You write what you want. You describe the feature. You think through the architecture. You define constraints. You specify the behavior. Claude reads it and implements it.

    This is the shift I mentioned at the start. You move from coder to architect. The work changes — it’s not about syntax anymore, it’s about clarity of thought. If you can describe what you want precisely, Claude can build it. If your requirements are vague, the output will be vague. Good thinking produces good code.

    Skills are reusable context. A skill file can define how to structure code for a specific hardware board, which GPIO pins are available, what libraries to use, what pitfalls to avoid. You write the skill once — and swap it in for every project that uses that board. Claude immediately works more efficiently because it already knows the environment before you start.

    Knowledge files accumulate truth. When Claude figures out a tricky integration — a working config for a library, a confirmed pin mapping, a tested setup — it writes it into knowledge/. Next session, it starts with that already loaded. No re-discovery.

    Starting a New Project: Three Steps

    The template is designed to be copied per project. Starting fresh is fast:

    1. Copy the template folder, rename it to your project name
    2. Edit .env: set COMPOSE_PROJECT_NAME to something unique
    3. Open the folder in VS Code → “Reopen in Container” → enter API key → start Claude with --dangerouslySkipPermissions

    Each project gets its own isolated container, its own Claude config volume, its own shell history. No cross-contamination between projects.

    The ESP32 Feedback Loop — Hardware Gets Interesting

    The USB passthrough is what makes hardware projects genuinely exciting.

    Claude Code doesn’t just write firmware — it flashes it to the ESP32 via USB, reads the serial debug log, and reacts to errors automatically. The loop looks like this:

    Write code → flash → read serial output → fix error → flash again.

    All without me touching anything. Claude drives the entire cycle.

    I was building a touch display project. Claude couldn’t see the screen, obviously. So it just asked me: “I can’t see the display. Does it look correct? Just say yes or no.” Otherwise it was perfectly happy reading the debug port and iterating on its own.

    That feedback loop is the concrete, hardware version of the same principle from my other post. The model is smart. But what makes it act is context: the code, the error log, the serial output, the confirmed hardware config in knowledge/. Remove any of that and the loop breaks.

    Real Projects Done This Way

    This isn’t theoretical. I’ve used this setup to build several actual projects:

    • Soundbox — hardware audio project with ESP32: Claude wrote the firmware, flashed it via USB, read the serial log, debugged autonomously
    • PyLearn — a Python learning app, posted here on 44-2.de — built in a few evenings
    • A Firefox extension that expands the Perplexity chat sidebar to full screen width — one evening, done

    Projects that would have taken me weeks now take two or three evenings. And the code quality? Honestly, at least as good as mine. Often better structured. Sometimes better documented than I would have bothered to do.

    Watch Out

    A few real risks to know about:

    • API costs: Claude calls the Anthropic API freely from inside the container. Set a spend limit at console.anthropic.com — seriously, do it before you start.
    • Git push risk: Claude has access to your forwarded SSH key and can push or delete remote branches. Protect your important branches on GitHub, or set ENABLE_GIT=false in .env.
    • USB security: The /dev filesystem is bind-mounted for USB support. Only ttyACM and ttyUSB device classes are accessible via cgroup rules — but understand what you’re enabling.
    • Firewall bypass: If you enable NET_ADMIN for the optional firewall, Claude could theoretically flush its own iptables rules. Acceptable if you trust the model. Remove the capability if you don’t.

    Container isolation is very good. It is not magic. Know the tradeoffs.

    What’s Next

    A few things I want to explore:

    • More skill files for different hardware boards — ESP32-S3, RP2040, different Arduino variants — each with pinouts, known-good libraries, and gotchas already loaded
    • Better firewall profiles: project-specific allowlists rather than one global list

    The deeper idea here is that skill files could become a community resource. Share the skill for your board, your framework, your setup — and everyone’s Claude sessions get smarter about that environment immediately.


    The template is on GitHub: happychriss/claude-code-container

    Copy it. Edit requirements.md , change the .env file with your project name, and if needed update your USB device. Describe what you want to build (and if you like – which platform or language). Watch what happens.

  • ESP32 Soundbox in an IKEA Frame: A Weekend Hardware Build with AI Support

    ESP32 Soundbox in an IKEA Frame: A Weekend Hardware Build with AI Support

    A friend of mine has a very particular taste in music. So when our group decided to give gifts that all fit into a common frame — literally — I had an idea. Why not build an actual sound machine inside an IKEA picture frame? Total budget: under €30, one weekend, and one AI coding assistant. Let’s go.

     

    The Hardware

    I didn’t have much time, so I just ordered everything from AliExpress in one shot:

    • Ai-Thinker ESP32-Audio-Kit (ESP32-A1S V2.2) — ESP32 with onboard ES8388 audio codec, two NS4150 class-D amplifiers, SD card slot, Bluetooth, six hardware buttons, all in one board. About €17.
    • WS2812B LED strip, 30 LEDs — individually addressable RGB, wired to GPIO22. Around €4.
    • Two small speakers from my parts bin.
    • A 5×7″ IKEA picture frame — €5.
    • A USB power cable for 5V supply.

    That’s it. No custom PCB, no breadboard rats’ nest. The board has two USB connectors — one for flashing, one for power — which makes things even cleaner.

    The speakers sit inside the frame glued to the plastic backing. If I were to rebuild this, I’d attach them directly to the wooden frame itself so it acts as a resonance body — the 6W amplifier is there, the 4Ω drivers are there, but right now the sound escapes through plastic. It still sounds surprisingly good for five-euro speakers. With the frame as resonator it would be genuinely excellent.

    The Build

    Everything fits together with double-sided tape and a hot glue gun. The only soldering needed is:

    1. Speaker wires to the board’s JST connector pads
    2. LED strip power + signal wire to the board

    The board is recessed slightly into the back of the frame so nothing sticks out beyond the frame depth. You can hang this directly on a wall.

    One small thing I’d add next time: an external power switch. Right now you unplug it. Works fine, but still.

    What It Does

    The feature set ended up being pretty complete:

    • Plays all .mp3 files from the SD card root in alphabetical filename order, looping forever
    • _welcome.mp3 always plays first on boot (underscore sorts before letters) — the box greets you with a purple light frame
    • KEY1 short-press: pause/resume
    • KEY1 long-press (2 s): toggle Bluetooth A2DP sink mode — LEDs pulse blue during pairing, then your phone streams audio through the same codec and the same LED show
    • KEY3/KEY4: previous/next track
    • KEY5/KEY6: volume down/up
    • Bluetooth device name: Dietmars-Soundbox (personalized gift, obviously)

    The LED Show

    This is where it gets technically interesting. The LED task runs a 5-layer composited show on Core 0:

    1. Ambient — slow color breathing based on overall volume
    2. Beat burst — flash on kick detection
    3. Bass blob — low-frequency energy mapped to brightness
    4. Sparkles — random high-frequency glints
    5. Picture frame — the four sides of the frame each get a distinct hue, driven by volume tier

    The audio pipeline works like this: PCM samples are tapped from the I2S write callback (both in SD and Bluetooth mode), converted from stereo int16 to mono float, and pushed into a stream buffer. The LED task drains that buffer, runs a 512-point FFT with a Hann window, splits the result into 8 logarithmic bands from 60 Hz to 20 kHz, applies per-band AGC with a ~10 s time constant, and does beat detection via a dual-EMA sub-bass ratio: kick_fast / kick_slow > 1.3, with a 200 ms minimum gap between beats.

    At quiet passages the frame breathes in calm colors at low saturation. Loud and fast music triggers full-saturation disco snaps where each side of the frame gets a randomly assigned hue with ~80–120° forced separation — so you never get two sides the same color by accident. Volume tiers (quiet / mid / loud) control how many frame sides are lit at once.

    It genuinely sets the mood of a room. That part was satisfying to see work.

    Building It With Claude Code

    I had a Docker container from a previous project — Ubuntu base, Node.js, @anthropic-ai/claude-code installed, USB serial device passthrough configured via docker-compose.yml. I dropped all the AliExpress datasheets and board schematics I could find into an external-docs/ folder, wrote a rough requirements.md, and let Claude loose.

    The setup is worth describing: the repo has a CLAUDE.md that tells Claude to read requirements.md, all skills/ files (working conventions per concern), and all knowledge/ files (validated config notes per component) at the start of every session. This way Claude has context persistence across sessions without relying on conversation history. Each time Claude confirms a working configuration, it writes a note into knowledge/ — so the next session starts with what was already proven to work.

    The first challenge was getting audio out at all. The ES8388 codec is sensitive to init order: SD card must mount before I2S init (GPIO25/26 conflict), MCLK must be stable for 100 ms before codec init, and you must use I2S_CLK_SRC_DEFAULT — APLL causes broadband white noise on ESP32. Claude worked through all of that by trying, failing, reading the error, and adjusting. The right path was to use espressif/esp_codec_dev and never write ES8388 registers directly.

    There was also a nasty bug where every song with embedded cover art crackled at the start. The cause: the ID3v2 tag can contain a several-hundred-KB JPEG, and the Helix MP3 decoder was scanning through it byte-by-byte looking for a sync frame, starving the I2S DMA buffer. The fix was to parse the syncsafe size from the ID3v2 header and fseek() past the entire tag before handing the file to the decoder. Claude found this independently after I described the crackling symptom.

    Bluetooth required a custom 3 MB partition table because the BT stack pushes the binary to ~1.1 MB. The A2DP data callback must be strictly non-blocking — push to a StreamBufferHandle_t, drain on a separate task on Core 1 — otherwise you drop the BT link under any load.

    I’m a decent programmer. I would have needed two or three weekends to get all this working on my own. With Claude Code it was one.

    Bill of Materials

    PartCost
    ESP32-Audio-Kit (A1S V2.2, ES8388)€17
    WS2812B LED strip (30 LEDs)€4
    IKEA picture frame€5
    Misc (USB cable, tape, glue)~€3
    Total~€29

    Speakers from the parts bin, SD card from a drawer. Add another €5 if you’re buying those.

    What’s Next

    The natural upgrade is a small 5V LiPo under the frame for wireless wall-hanging. The LED strip needs 5V anyway, so no boost converter required — just a tiny LiPo with a TP4056 charger board under the frame. And a proper external power switch.

    Full source and the devcontainer setup are on GitHub.


    A few things I took away from this: cheap AliExpress hardware is genuinely capable if you understand what you’re buying. The ES8388 codec is a real audio chip — it sounds good. And Claude Code is a legitimately useful tool for embedded work, not just web stuff, as long as you give it structured context. The knowledge/ folder approach — where confirmed hardware facts accumulate across sessions — made a real difference. It’s worth setting up properly.


    GitHub: happychriss/ESP32-A1S-sound-machine

  • Publishing with AI: A WordPress MCP Workflow

    Publishing with AI: A WordPress MCP Workflow

    Tonight I connected Perplexity directly to my self-hosted WordPress site at 44-2.de — using the WordPress.org MCP connector via Pipedream. The idea: talk to an AI, end up with a published post. No copy-pasting, no switching tabs.

    How to set it up

    1. In WordPress admin, go to Users → Add New. Create a user with the role Editor. Don’t use your admin account.
    2. Open the new user’s profile, scroll down to Application Passwords. Enter a name (e.g. “Perplexity MCP”) and click Add New Application Password.
    3. WordPress shows you the password once. Copy it immediately — you won’t see it again. The name is just a label; the long generated string is the actual password.
    4. Go to the Perplexity add-on page and connect the WordPress integration. Enter your site URL (with https://, not http://), the username, and the application password.
    5. The “connection authenticated” confirmation does not verify your credentials actually work — it only checks that a connection was made. The real test comes when you try to use it.
    6. In the Perplexity chat, press +, add the WordPress MCP, and you’re ready to go.

    What tripped me up

    Wrong user — I started with the admin account. Admin should stay for admin things.

    Pipedream doesn’t auto-refresh — After switching users in WordPress, Pipedream kept using the old credentials. You have to disconnect and reconnect manually for changes to take effect.

    The Application Password UI — I was copying the name instead of the generated password. WordPress shows the actual password once, right next to the name field. Easy to miss if you’re not looking for it.

    What’s next

    GitHub is next. The plan is that progress on projects — commits, notes, decisions — flows into posts through conversations. This post was written and published entirely through the MCP connection. That’s the point.