Job Hunting in 2026: Why I Built a Symfony App with AI Agents (and What I Learned)
After being laid off, I built a Symfony 7.4 application start to finish using AI coding agents and a structured skill pipeline. Here's exactly how - bugs, bad habits, and breakthroughs included.
A few weeks ago, mid-recruitment-cycle, I hit a form field I hadn’t seen before. This company didn’t want a cover letter. They wanted a logs from a coding session with an AI agent i’d be proud to share. Upload a logs of AI agent of how i build something with Claude Code, Codex, or equivalent. Show your workflow. Prove you’re not just copy-pasting from ChatGPT.
That’s when I knew the bar had moved. It’s not enough to say “I work with AI.” You have to show it. And if you’re a Laravel developer in Poland in mid-2026, you’ve got a second problem: the Polish PHP market runs on Symfony, not Laravel. The numbers on the job boards don’t lie, with roughly about three Symfony openings for every Laravel one in current moment.
So I had two things to prove and no one to prove them to except myself and the next recruiter. I decided to build a Symfony application, soup to nuts, using AI agents and document the entire process so thoroughly that anyone who asked “how do you work with AI?” could look at a single repository and see the answer.
The result is TriageFlow - an MVP of AI-powered medical triage system. Here’s the real story of how it got built: what worked, what broke, and what I’d do differently next time.
Not starting from zero
Before writing a single line of code, I did something that turned out to be more valuable than I expected: I used the job market itself as my specification. A few months earlier I’d built a Python script to scrape Polish job boards for PHP openings. Small but time saving tool born out of the frustration of manually scanning different aggregators every morning. Now I pointed that script at the Symfony market specifically, scraped senior-level listings, and had an AI agent extract every concrete skill, tool, and architectural expectation those listings demanded.
The output wasn’t a wish list. It was a market-validated skills matrix: what recruiters and tech leads in Poland actually expect from a senior Symfony developer in 2026. That matrix became my north star (as much as we can put trust in openings requirements). Every architectural decision in TriageFlow had to answer to it. Not to my opinions, not to Laravel habits, but to what the market said mattered.
The workflow problem I had to fix first
I’d been using AI coding tools since ChatGPT opened to the public in early 2023. Copilot, Cline, Aider. I’d gone through the phases exploring new tools. By early 2026 I was using OpenCode, an open-source coding agent, and I had a decent collection of skills and subagents installed from Superpowers and Matt Pocock’s toolset.
But here’s what I wasn’t doing: I wasn’t invoking the skills. I was prompting around them. Serious problem for stable and reliable workflow.
I’d open a session, paste a task description, and write a long prompt like “use TDD, plan first, review at the end” - essentially asking the agent to manually replicate what skills like tdd-workflow, writing-plans, and requesting-code-review were already designed to do. The skills were sitting there, installed, ready to enforce gates and guardrails. I was just too deep in prompt-habit to reach for them and certain that from prompt context agent will be able to call proper skills, sometimes it worked, sometimes it failed. “/Skill issue”, hehe.
This is the single biggest mistake I see developers make with AI coding tools: treating them like a smarter chatbot instead of an orchestrator you delegate to. The skills aren’t suggestions. They’re structure. If you don’t invoke them, you’re flying without instruments. No wonder that Claude Developers workflow is fundamentaly based around skills calls.
So I made a rule for TriageFlow: every session starts with a skill, every session ends with a handoff. No more “good enough” prompt engineering. Either the skill pipeline runs or the session doesn’t start.
The stack and why it matters
A quick note on tool choices, because they weren’t arbitrary:
- OpenCode over Claude Code / Codex: I wanted open-source. OpenCode has subagent delegation built in (via OpenAgentsControl), support for MCP servers, and a
handoffskill that writes a committable markdown file at session end — critical for continuity across 20+ development sessions. - DeepSeek V4 API over Claude / GPT: Cost was a factor, but the real reason was throughput. DeepSeek’s free-tier and flash models handled 90% of the scaffolding and boilerplate. For architectural reasoning and code review I used the pro model, but the flash models handled mechanical work just fine.
- Dynamic Context Pruning (DCP): A plugin that strips tool-calling metadata from the prompt before it hits the model. Hundreds of thousands tokens, that don’t belong in your context window. Without DCP, my sessions would have been throttled to half the issues per day just fighting token limits.
- MCP: Playwright + Context7: Playwright for browser testing and visual verification. Context7 for fetching current framework documentation. Training data tells you about Symfony 6; I needed Symfony 7.4 now. Context7 fetched the real docs when needed to think how utilize Symfony features.
- Three repos, one project: The documentation repo (where OpenCode lives and where all ADRs, handoffs, and the operating guide sit) is separate from the backend and frontend repos, each with its own commit history and CI pipeline. This meant the agent always knew which codebase it was operating in, was operating in bigger picture, and handoff files could reference commits unambiguously.
The pipeline: how 21 sessions became a working app
The process followed an 8-gate pipeline I’ll trace once, because it ran identically for every issue:
Phase 0: Architecture (one session)
Started with the project brief. A markdown file describing TriageFlow at a high level and fed it into an architect agent with the grill-with-docs skill. This is the alignment gate. The skill asks hard questions: What do you call this thing? A submission or a case? An interview or a consultation? Are turns a domain concept or an implementation detail? Thirteen architectural decisions came out of that session, captured as Architecture Decision Records (ADRs) in docs/adr/. The output wasn’t code. It was ‘our project language’. A shared vocabulary file (CONTEXT.md) that every subsequent session loaded before doing anything else.
Phase 1: Plan & Issues (one session)
Next: write-plan + brainstorm to decompose the PRD into a roadmap, then to-issue to convert the roadmap into seven GitHub issues on the triageflow-docs repo. Each issue was a vertical slice with defined exit criteria, dependency chains, and Context7 fetch directives for the docs the agent would need. Issues 1-7 formed a dependency chain: scaffolding → auth → triage pipeline → (admin dashboard, synthetic generator) → testing & polish.
Phase 2: Execution (17+ sessions, and lots of subagents sessions)
Every issue started the same way: load the handoff from the previous session (for continuity), run grill-with-docs again (because context decays between sessions), then execute the core loop:
🧠 align → grill-with-docs (domain language + ADRs)
📋 plan → write-plan → task breakdown
🔨 build → tdd-workflow → CoderAgent (execute subtasks)
🧪 test → TestEngineer (fill coverage gaps)
👁️ review → CodeReviewer (quality + security)
✅ verify → verification-before-completion
📦 handoff → commit-ready markdown summary
After every 2-3 completed issues, I’d run a zoom-out session. Load the full app, check the state of everything, fix integration breaks. Those sessions caught things that individual-issue reviews missed: stale auth state creeping across React components, a JSON API wrapper shape that didn’t match the frontend’s axios expectations, a Messenger transport that was configured but never actually installed.
Phase 3: Polish (remaining sessions)
Once all seven core issues were closed, next 8 improvements issues was planned and created. I layered on production concerns: OpenRouter rate limiter with fallback-model switching, an accessibility audit across all 15 pages (Lighthouse score from 83 to 91), code coverage enforcement at 80% via CI-level clover.xml parsing, a bilingual UI with full i18n support (react-i18next, 16 locale files across English and Polish), and a business-website landing page with its own layout wrapper, with more app feeling using nextlevelbuilder/ui-ux-pro-max-skill. An architectural decision I made here (keeping both the public marketing site and the authenticated SPA in a single React app with two layout wrappers rather than splitting into separate frontends) turned out to be one of the best choices of the project (ADR-0008). When a visitor hits / they see a glassmorphism hero and feature cards. Log in, and the same React app becomes a triage dashboard. One build. Two identities.
What broke (and what that tells you)
The bugs that showed up tell a more honest story than the features that worked. Here are the ones that mattered:
The Messenger That Never Messenged. symfony/doctrine-messenger wasn’t installed. The config was there, the transports were declared, the message handlers were wired, but the package wasn’t in composer.json. Every triage submission sat in “pending” forever. A CI pipeline would have caught this. Mine was still being built. With zoom-out sessions i was able to catch such problems quick. Problem was simple, Phase 1 plan already included code snippet and made agent confused that package was already installed.
The AI That Wrapped Its Answers in Markdown Code Blocks. The OpenRouter client was calling json_decode() on the AI’s response. Worked fine for two turns. On turn three, the model started wrapping its JSON in ```json fences. json_decode returned null, the application threw a 500, and the user’s interview silently died mid-conversation. Fix: preg_replace to strip markdown wrappers before decoding. Lesson: the AI you’re integrating isn’t an API, it’s an AI. Plan for it to act like one and use such options like structured_output.
Stale Auth State Across Components. Login and logout worked. But the React components each held their own useState for auth, so logging in updated one component and left five others believing the user was still logged out. Refactored to a shared AuthProvider context. This wasn’t an AI bug; it was an architecture bug that individual-issue sessions couldn’t see because each session only touched one component at a time. The zoom-out session caught it.
36-Step E2E Walkthrough: 3 Failures. After issues 1-5 were “done,” I ran a manual E2E verification: 36 acceptance-test steps end to end. 33 passed. 3 failed. That 3/36 failure rate (8%) is the gap between “each issue passed its tests” and “the system actually works.” It’s also the gap between a GitHub repo and a portfolio project you can stand behind.
What actually worked
The handoff skill was the quiet hero of the entire project. Every session ended with a structured markdown file committing: what was done, why, what’s next, any blockers, any decisions made. The next session loaded that file and continued without losing a beat. Twenty-one handoff files sit in docs/handoffs/ alongside 9 ADRs, each handoff a snapshot of the exact state of the project at the end of a coding session. I didn’t need to “remember where I was.” The file remembered.
grill-with-docs was the runner-up. Starting every session by re-aligning on domain language and ADRs sounds like overhead. It’s not. It’s insurance. And pretty much something that we are expecting AI to do - domain focused thinking. When you spread a project across two weeks and 17 sessions, the agent at session 17 has no memory of what it agreed to at session 3. grill-with-docs rebuilds that alignment in 60 seconds from the CONTEXT.md and ADRs. Skipping it means building on assumptions that expired last Tuesday.
The caveman skill - a token-compression mode that drops filler language while keeping technical precision that kept longer sessions running when context windows were at their limit. When you’re 40 messages into a debugging session and the agent starts hallucinating because the prompt is 95% scrollback, caveman cuts that by 75%. It’s not elegant but it’s sometimes necessary (and kinda funny).
What I deliberately left for the next project
- Custom hooks. I made a conscious call early on: this project’s focus was skill discipline. Custom hooks - the mechanism i’m eager to try that lets you react to events like session start, tool calls, or errors, are powerful, but they’re a separate learning curve. Trying to master both skills and hooks in the same project would have split my attention, by adding new layer of thinking on which action should additionaly happen what. I decided to lock in skill-based workflow first, then tackle hooks in the next portfolio app. No regrets about that sequencing.
- Frontend-first design. The frontend got an accessibility audit and Lighthouse fixes as a late-stage session (Issue #12), but that should have been a Phase 1 concern. Retrofitting accessibility into a completed UI is the worst way to do it.
Did I actually fix the skill consistency problem?
Honestly: partially. This was the entire point of the project, so I owe it an honest grade.
What improved massively: The handoff skill became automatic. I didn’t finish a single session without it. grill-with-docs became the non-negotiable opener for every issue. The core pipeline (align → plan → build → test → review → verify → handoff) ran structurally, not optionally. The operating guide and tools×scenarios matrix I wrote for the project meant I never had to think “which skill do I use for X?”. The matrix answers that before the session even starts with quick check.
What didn’t: Old habits die hard. On quick bug fixes (the ones that feel like a 10-minute job) I’d still catch myself firing a long-form prompt to the default agent instead of reaching for systematic-debugging. The irony: those “quick” fixes almost always took longer because I skipped the diagnostic gate and went straight to code. A bug I thought was a React issue turned out to be a missing composer package and systematic-debugging would have found that in five minutes, but my prompt-to-default-agent reflex chased a red herring for an hour.
The takeaway isn’t that skills are hard to use. They’re not, you just need to stick to few task scenarios that you are already know very well from pre-ai era coding. It’s that replacing a new bad prompt-habit with a skill-habit takes more than one project. The pipeline worked. The friction wasn’t in the tools, it was in my new AI muscle memory.
Why this matters
In 2026, being “a developer who uses AI” is like being “a developer who uses Google” in 2010, it’s table stakes. The differentiation isn’t whether you use AI. It’s how. And surveys shows that, even if developers evolved from using Copilot as a chat and started using cli coding agents, they are still stuck in chat mindset using basic agents.
TriageFlow exists because I wanted to be able to sit across from a tech lead and say: “Here’s exactly how I work. Here are 21 handoff files from 21 real sessions, 9 ADRs documenting every architectural fork in the road, 252 backend tests and 146 frontend tests with 31 E2E Playwright specs, all passing, all enforced in CI. Here’s the operating guide I wrote for my own agents. Here are the bugs that shipped and how I found them.”
That’s not a bullet point on a CV. It’s provenance. It’s the difference between claiming a skill and having the receipts.
Could I have built this app faster without AI? Absolutely not, not in three weeks, not in Symfony (which I was learning as I built, after few years of not using Symfony), not with the level of documentation and testing discipline the pipeline enforced. Could I have built it better with a different AI tool? Maybe, and I will as i’m using pi.dev agent right now. But I’d rather have a documented process with a tool I’ve learned to use well than an undocumented one with a tool I’m still fumbling with.
So if you read one thing from this post, make it this: the job market doesn’t want to hear that you “embrace AI.” It wants to see your workflow logs, your agent prompt files, your skill pipeline, and your handoff artifacts. The bar has moved. Build accordingly.
TriageFlow is open-source. The documentation repository - including the full raw log, all 21 handoff files, 9 ADRs, the operating guide, and the tools×scenarios matrix - is at github.com/psswid/triageflow-docs. The backend and frontend repositories are linked from there.