Skip to content

Coach. — after launch

Published: 4 tags 6 min read
Updated:
Roughly two minutes and 15 seconds before Omar won his IKF belt and his fourteenth fight in a row.

Updates since the launch: each goal is now its own workspace, the agent opens goals from chat, and ~20 small commits of UX polish.

I shipped Coach. a few days ago and immediately started using it every day. I also immediately started feeling what was missing. This post is what happened since.

Goals became workspaces

The first version had one big plan and one long conversation. After two weeks the conversation was a knot — health questions tangled with finance questions, the agent couldn't decide which persona to wear, and the plan listed actions from contexts that had nothing to do with each other.

The fix was to model goals as workspaces. Each goal is now a sidebar entry that owns its own conversation, its own plan actions, and the prompt that tells the agent how to behave.

A label field (finance, fitness, emotional, learning, health, legal, general) picks the specialization: finance becomes hard math without tax advice, emotional validates first then proposes, fitness leans on consistency over intensity. general means "I haven't decided yet, ask me where to focus".

Switching workspaces is one click. Each carries its own "latest conversation" + history, so nothing gets lost when you change topic.

The agent opens goals via chat

You used to click "+ new" and fill a modal. Now you talk. I added a CreateGoal(name, label) tool to the agent's toolbox. If you say "I want to start tracking my mental health", the agent confirms, calls CreateGoal, and the new workspace appears in the sidebar — without you leaving the chat.

This connects to memory: the agent sees your other goals, knows when something similar already exists, and suggests using the existing one instead of duplicating.

The hardest bug

The worst bug I shipped to production was the agent narrating an action it never executed. You'd say "add X to the plan", it would reply "done, added X, here's your updated plan:", the response would cut off after the colon, and the action would never appear because the tool never fired. The model hallucinated the verb.

The fix was three layers:

  1. Detect. A decorateAssistantResponse() pass scans the final reply for past-tense action verbs and checks whether the matching tool actually fired in this turn. If not, it appends an italic warning and logs the conversation id at WARN level for postmortem.
  2. Auto-retry. When the first pass looks broken, Coach.php automatically runs a second stream pass with a corrective system nudge ("execute now, don't narrate"). Same conversation, so the model sees its own broken reply and the order to fix it.
  3. Reinforce the prompt. Explicit ❌/✅ patterns showing the exact failure mode, plus a closing rule: never end a response with a colon unless the list either appears in the text or a tool was already called.

I also moved from Flash to Pro for the interactive chat. Pro is slower (4-5s to first token versus 1-2s) but ~10x more reliable on multi-tool turns. The cron jobs stay on Flash — single-shot and short, Flash handles them.

Polish

The core flow worked, but the app felt cheap. A few weeks of small commits later it stopped feeling cheap:

  • Pulsing dots in the assistant bubble the moment you click Send. The bubble appears in 88ms instead of staying empty for 1300ms while the model warms up.
  • Optimistic user-message insert — your message hits the thread in 18ms via Alpine, before the Livewire round-trip completes. A morph hook removes the placeholder when the server-rendered version arrives; no flicker, no duplicate.
  • Active-goal indicator in the sidebar — a 3px orange left-border + bg tint + bold title. You always know which workspace you're in.
  • Compacted composer — the upload drop-zone collapses to a small clip-icon button when no files are attached, freeing 130 vertical pixels for the chat.
  • Mobile/PWA — viewport locked against iOS pinch-zoom + URL-bar wobble, dynamic viewport units, overscroll containment, scroll-lock when overlays open.
  • Brand consistency — every primary CTA uses the orange accent; green is reserved for "complete an action" where it carries semantic meaning.

Small things that turned out nice

Email replies thread the conversation. Every scheduled ping carries a Reply-To with subaddressing — reply+<conversationId>@coach.... When you reply from your phone, the inbound webhook extracts the UUID, logs you in via auth()->login() so global scopes activate, and continues the same conversation. The agent sees the email body as if you typed it in the chat.

Memory has structure. CoachMemory stores facts with a kind and label. The agent saves proactively after analyzing a document or making a decision, so two months later you can still ask about that topic without re-attaching anything.

i18n is real. Every user-facing string lives in lang/{pt_BR,en}/. Tests pass in both locales — CI runs with APP_LOCALE=en while local dev runs pt_BR, so any string asserted in Portuguese would fail in CI.

178 tests. TDD on every new feature: tool date parsing, goal-scoped action creation, multi-tenant isolation, invitation token validation, Reply-To routing, webhook auth, response-decorator detection. Pest 4, runs in <5 seconds.

Stack

No big change underneath: Laravel 13 + PHP 8.4, Filament v5 for the chat page, Livewire 4 with token streaming, laravel/ai SDK with Gemini 2.5 (Pro for interactive, Flash for crons), Resend for transactional email, Pest 4 for tests, SQLite locally + MySQL in production, Tailwind v4 + a hand-tuned coach.css for the orange-accent minimalist look.

What's next

  • Per-goal custom specialization prompts (editable from the UI, not just the seven built-in labels)
  • An auto-switch when the agent creates a goal mid-conversation (ask if you want to move there or stay)
  • Per-goal weekly briefings instead of one global recap
  • A small mobile-only tweak to the plan flyout for very long action titles

Repo: github.com/allurco/coach. MIT, self-host, bring your own Gemini key. It's been stable in daily use as my own coach for a while now; if you want to use it for any goal other than money, just create the workspace and the agent adapts.

Share
X LinkedIn Facebook