85 ms
8.3× fasterdown from 704 ms
Total latency
I own the platform under Arbore, an augmented-reality gardening app on the App Store: infrastructure as code, release engineering, observability, and the pipeline that generates every plant in its catalogue. Before that I shipped a second app alone, end to end.
Lyon, France. In Québec City from March 2027, post-graduation work permit approved.
Arbore measures your space, asks about light and constraints, then lets you place true-to-scale 3D plants in augmented reality, then keeps them alive with a care calendar. I own the platform underneath: infrastructure, backend, releases, documentation, and the pipeline that produces every model in the catalogue.
Context
Every plant in the catalogue needs a 3D model, and we were buying them. Thirty credits a plant, on a €38 plan for two thousand credits, which works out at €0.57 each. Nobody had yet decided how big the catalogue would get.
Decision
Self-host TRELLIS.2 and generate them ourselves, for roughly one cent of compute a plant. Colab has no execution API, because a session only starts when a human is in front of a browser. So instead of the server driving Colab, Colab pulls the work. You don’t open a generation. You open a workshop, and it works for as long as it stays open.
Consequence
A quality gate rejects multiple subjects, cubes and slabs on a 3.7× dominance margin, and retries on a fresh seed, three times, without asking. Geometry that is invented but plausible still goes to a person, because no metric sees it. A model with three plants in it should never reach review: a retry costs 165 seconds of GPU where a human round-trip costs attention.
Rejected
Depth Anything for reconstruction. Explored and measured, then set aside. It was more work than it would have bought us at the quality we needed.
A pot wall is a vertical surface of revolution: horizontal radial normals, a radius that tightens. Geometry finds it whatever colour the pot is, and goes mute the moment the pot is crumpled and stops being a revolution. Colour catches that crumpled pot, and then swallows the parts of the plant that aren’t green, like a beige caudex or magenta petioles.
Six models out of nine have a wall signature at 100%, so geometry handles them alone and the other three are flagged. Every result carries the method it came from, so review knows what it is looking at rather than guessing. The downstream half runs in plain numpy with no GPU, which means the day pot removal improves, it replays across the whole catalogue without regenerating a single model.
One trap already paid for: the gate measures the exported
mesh, not the one before decimation. Decimation splits components, so
the anthurium read 87.8% dominant before and 54.9% after, depending
only on when you measured. Textures ship as PNG rather than WebP,
because SceneKit does not read EXT_texture_webp.
Before committing the roadmap I ran a quantitative survey: 75 responses, 55 complete questionnaires, 42 questions covering segmentation, purchase behaviour, competition, Likert scales and a Van Westendorp price ladder.
The augmented reality came fourth. It is the feature the app is built around, and a care calendar beat it by twenty points. So the roadmap changed.
And 47% said they dislike subscriptions on principle, which is a packaging problem rather than a pricing one.
One caveat: 51% of respondents are 18 to 24, which is young for a gardening product. I would rather say so than let the numbers imply a market they don’t describe.
Twelve of the 55 complete ladders are excluded from the price chart: nine ranked the four questions out of order, three put the ceiling above €50 a month.
A band of €4.99 to €7.17, which is narrow. The indifference price is €5.28 and the optimal price €5.45, seventeen cents apart, so there is no premium tier hiding above the sweet spot.
Plotted the standard way: the cumulative percentage at each of the thirty prices people actually named, joined by straight segments, with the four prices read off where the lines cross. The axis stops at €12, by which point all four crossings are behind us; the highest ceiling anyone named was €40.
Crédit Agricole’s app for family caregivers coordinates the logistics between relatives. Nothing in it carries the state of the person being cared for: whether she had lunch, whether she got up, whether the kitchen light came on this morning. That hole is the product, and the target named on stage was mental load.
Decision
An orchestrator fronting six domain agents (directory, telephony, home, tasks, finance and notes) that reach the Go API through an MCP server. Every person is a token. First names are restored locally, after the model has already answered, and the test bench shows all three steps side by side: what went out, what came back, and the mapping table. Verifiable, not asserted.
Consequence
Confirmation is enforced by a tool rather than a prompt: the call refuses to act without it, because a rule written in a prompt can be talked around. Authorisation sits in three independent places. The tool does not exist for the cared-for person’s profile, the API answers 403, and the gateway authorizer rejects the token before it reaches MCP at all.
Tested
Thirty-nine end-to-end checks against the real pipeline, deliberately kept out of CI because they call Bedrock and cost money, and because the unit tests go green on a completely broken pipeline. The pre-commit hook only runs the packages you touched, since a slow hook gets bypassed.
Rejected
Two Bedrock clients. Two credential conventions, incompatible, one of which was going to rot quietly. One is left.
Two surfaces, one orchestrator, six domain agents, and the line no first name crosses.
Above the redaction line, every person is replaced by a local token before the prompt leaves the device. Below it, the MCP gateway authorizer and the Go API enforce role boundaries independently of what the model was asked to do.
Thirteen documents came out of those two days, including the camera metadata contract against MDR, the AI Act and HDS, a note on why pseudonymisation is not anonymisation, and a page of the shortcuts we took, written down because a jury will find them anyway.
From a designer’s Figma file to the App Store: Flutter, Node, Postgres, Docker on a VPS, GDPR compliance and release. No third-party UI framework: every component is drawn from Flutter primitives, so the build stays faithful to the file it came from.
Decision
Resolve sync conflicts per field, not per record. The next reminder keeps the earlier date, because one reminder too many beats one missed. The count keeps its maximum, so history is never lost. Whether it is enabled is last-write-wins, because that one is a statement of intent and the latest intent is the real one.
Consequence
A persistent operation queue that survives the app being killed, exponential backoff, and delta sync. The audit log is written before the deletion it records. Write it after and you have deleted your own proof.
Rejected
A component library. It would have saved weeks and quietly rewritten the designer’s work. The first version of the marker lagged on large sheets instead, until background images were capped at 4096 px, which is Flutter’s GPU texture limit.
I am preparing for a solutions architect role, so I am auditing a system I actually shipped rather than a tutorial. Ganesh’up’s backend works: one node, Docker Compose, a one-minute deploy script I wrote myself. Reviewed against the Well-Architected Framework it also has six structural bottlenecks, and those are now public.
Context
The API decodes up to 50 MB of Base64 on Node’s single thread and writes the result to a local disk. Memory grows 91× under a burst, health checks starve behind the upload, and a second replica would serve 404s for media its sibling holds.
Decision
Make the compute stateless: S3 with presigned URLs so binaries never touch the API, SQS and Lambda for the asynchronous half, a rolling update with maxUnavailable: 0 and an init container for migrations, EventBridge and Secrets Manager instead of a cron hitting a public endpoint, and immutable SHA-tagged images built in CI rather than on the production host.
Rejected
Lift and shift. An idle Redis container nobody used would have become a billed ElastiCache cluster, and in-process rate limiting would have silently multiplied its own limit by the number of replicas.
85 ms
8.3× fasterdown from 704 ms
Total latency
362 MB
−51% peakdown from 735 MB
Peak resident memory
+12 MB
60× flatterdown from +727 MB
Growth over idle
0
Statelessdown from 150
Binary files written to local disk
350 MB
+92 MB SDK baselineup from 258 MB
Resident memory per replica
62.2%
+2.4 pts URL signingup from 59.8%
Peak CPU on one core
What it cost: three AWS SDK clients add a constant 92 MB per replica. That is a sizing cost rather than a scaling one, because unlike the old cost it does not grow with traffic. Peak CPU still touches 62% while signing 150 presigned URLs, which is the argument for rate limiting at the ingress instead of in Node’s memory.
Two labs follow this one: pulling a brick out of the Ugram monolith onto S3, SQS and Lambda with a strangler fig, and decomposing an open-source legacy monolith. All three carry C4 views and decision records for the trade-offs, which is the part I am actually practising.
Most coursework stops at a working localhost. This one went to production on AWS and stayed there.
Decision
Elastic Beanstalk for the application, RDS for Postgres, S3 for media and CloudFront in front, with HTTPS end to end rather than terminating somewhere convenient. Deployment through GitHub Actions, so nothing reached the environment from a laptop.
Consequence
CloudWatch logs, dashboards and alarms, plus Sentry on the frontend with session replay. Observability is a design tool here rather than an afterthought, because production is where a design gets contradicted.
Smaller things, kept here because together they show the range: 3D, systems, machine learning and tooling. A few of them are where an argument I still make started.
2024 to now
Epitech, Lyon
Machine learning modules (computer vision, pre-processing), the first-year piscine, low-level C++, and SQL. Two spells: 2024–25, and again from September 2026.
2025
Valraiso, Lyon
Built an MCP client and server giving LLM integrations context-aware access to internal data tools. Fine-tuned and evaluated RoBERTa embedding models on ski product data, over gold and silver sets.
2023
Lebourgmestre sàrl, Lausanne (remote)
Set technical strategy alongside the CEO, led mobile and web development, designed the REST APIs, and ran a fully containerised stack. Ganesh’up came out of this.
2023
PoC Innovation, Lyon
Built SecureEnv, a tool for managing and synchronising a team’s environment variables without putting them in the clear.