<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>Josh Hornby</title>
		<link>https://joshhornby.com</link>
		<description>Recent content on Josh Hornby's blog</description>
		<language>en-gb</language>
		<lastBuildDate>Sun, 15 Mar 2026 08:09:29 +0000</lastBuildDate>
		<generator>Jekyll</generator>
		<atom:link href="https://joshhornby.com/feed.xml" rel="self" type="application/rss+xml" />
		<image>
			<url>https://joshhornby.com/assets/images/headshot.jpg</url>
			<title>Josh Hornby</title>
			<link>https://joshhornby.com</link>
		</image>
		
			<item>
				<title>The Tech Lead Trap</title>
        		<description>&lt;blockquote&gt;
  &lt;p&gt;This post is part of my &lt;a href=&quot;/tags/tech-lead&quot;&gt;Tech Lead Series&lt;/a&gt;, a collection of practical advice for engineers stepping into leadership roles.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A former colleague spent eight years as a tech lead. Same level, different teams. He was good at the job: respected by his teams, trusted by stakeholders, reliable. But when he started looking for his next role, he found that his skills hadn’t grown much in five years. He’d become very good at a job that no longer challenged him.&lt;/p&gt;

&lt;p&gt;He’d fallen into the tech lead trap.&lt;/p&gt;

&lt;p&gt;The trap is subtle. The role is comfortable. You’re competent. People appreciate your work. There’s no obvious crisis forcing change. But beneath the surface, you’ve stopped growing. The job that once stretched you now fits too well.&lt;/p&gt;

&lt;h2 id=&quot;how-the-trap-works&quot;&gt;How the trap works&lt;/h2&gt;

&lt;p&gt;The tech lead role has a natural ceiling. Once you’ve mastered the basics, the work becomes routine. You know how to &lt;a href=&quot;/running-effective-one-to-ones&quot;&gt;run 1:1s&lt;/a&gt;. You know how to &lt;a href=&quot;/scope-creep-and-how-to-fight-it&quot;&gt;push back on scope&lt;/a&gt;. You know how to &lt;a href=&quot;/balancing-coding-and-leading&quot;&gt;balance coding and leading&lt;/a&gt;. The challenges that used to require growth now just require execution.&lt;/p&gt;

&lt;p&gt;This happens for several reasons.&lt;/p&gt;

&lt;p&gt;Your influence is locally bounded. It extends to your team and maybe some adjacent ones. Problems that require broader influence don’t come to you. You solve the same scale of problems year after year.&lt;/p&gt;

&lt;p&gt;A well-running team doesn’t need heroics. When things are going well, there’s less pressure to change anything. The reward for doing the job well is doing the same job again.&lt;/p&gt;

&lt;p&gt;And when work is comfortable, you stop seeking discomfort. But growth happens at the edge of your abilities, not in the middle.&lt;/p&gt;

&lt;p&gt;The stagnation is invisible too. Your manager sees a reliable performer. Your team sees a steady leader. From the outside, everything looks fine. Sometimes you’re the last person to notice.&lt;/p&gt;

&lt;h2 id=&quot;signs-youre-trapped&quot;&gt;Signs you’re trapped&lt;/h2&gt;

&lt;p&gt;Some signals that suggest you’ve stopped growing:&lt;/p&gt;

&lt;p&gt;When did you last struggle with something new? If every problem feels like a variation on something you’ve solved before, you’re not being challenged.&lt;/p&gt;

&lt;p&gt;You can do the job on autopilot. Meetings, decisions, and reviews all run on muscle memory. You’re executing, not thinking.&lt;/p&gt;

&lt;p&gt;You’ve stopped being curious about new technologies, about how other teams work, about problems outside your domain. Curiosity fades when everything feels familiar.&lt;/p&gt;

&lt;p&gt;Your conversations haven’t changed. The same stakeholder issues, the same team dynamics, the same technical debates. Nothing new enters the picture.&lt;/p&gt;

&lt;p&gt;Ask yourself: what have I learned this year that I didn’t know last year? If you can’t answer, you probably haven’t grown.&lt;/p&gt;

&lt;p&gt;And if you’re waiting for something to change, hoping for a promotion or a new project or a different manager, that’s a sign you’ve given up on creating change yourself.&lt;/p&gt;

&lt;h2 id=&quot;getting-out&quot;&gt;Getting out&lt;/h2&gt;

&lt;p&gt;Escaping the trap requires intentional action. Comfort is self-reinforcing, and you won’t drift out of it.&lt;/p&gt;

&lt;p&gt;Volunteer for initiatives outside your usual scope. Cross-functional projects, new domains, problems nobody owns. Unfamiliar territory is where the learning happens.&lt;/p&gt;

&lt;p&gt;Start contributing beyond your team. Help other tech leads, mentor engineers in other groups, take on org-wide technical initiatives. The skills you’ve built have broader applications than you think.&lt;/p&gt;

&lt;p&gt;Write about what you’re learning. Present to your team or company. Teaching forces you to deepen your understanding and exposes gaps in your knowledge.&lt;/p&gt;

&lt;p&gt;Figure out what you’re avoiding because it’s uncomfortable. Public speaking? System design? Strategic thinking? Go toward it.&lt;/p&gt;

&lt;p&gt;Ask people you trust where your blind spots are. What should you be better at? What’s holding you back from the next level? Listen to the answers.&lt;/p&gt;

&lt;p&gt;And sometimes the trap is the role itself. If you’ve genuinely maxed out growth opportunities where you are, moving to a new team, new company, or new role might be the right answer.&lt;/p&gt;

&lt;h2 id=&quot;the-fork-in-the-road&quot;&gt;The fork in the road&lt;/h2&gt;

&lt;p&gt;Most tech leads eventually face a choice: engineering management or the technical track.&lt;/p&gt;

&lt;p&gt;Engineering management means moving toward people leadership. Less code, more 1:1s, more organisational thinking. You become responsible for teams, not systems. The tech lead role is partial preparation, but management requires new skills: hiring, performance management, navigating organisational politics.&lt;/p&gt;

&lt;p&gt;The technical track means moving toward &lt;a href=&quot;/doing-leveraged-work&quot;&gt;Staff or Principal engineer roles&lt;/a&gt;. Deeper technical influence, broader architectural scope, less direct people responsibility. You’re still leading, but through technical contribution rather than team management.&lt;/p&gt;

&lt;p&gt;Both are valid paths. Neither is a promotion from tech lead; they’re different jobs. The trap often comes from not choosing either, staying in the middle ground where you’re doing some of both but not progressing in either.&lt;/p&gt;

&lt;p&gt;Think honestly about which path fits you. Some tech leads love the people side and should go toward management. Some love the technical side and should grow into Staff roles. Some discover they want something else entirely.&lt;/p&gt;

&lt;h2 id=&quot;staying-sharp-in-the-role&quot;&gt;Staying sharp in the role&lt;/h2&gt;

&lt;p&gt;Maybe you’re not ready to leave. Maybe you like being a tech lead and want to stay. You can still avoid the trap.&lt;/p&gt;

&lt;p&gt;Different teams have different challenges. Rotating to a new team resets your learning curve and exposes you to new problems.&lt;/p&gt;

&lt;p&gt;Volunteer for the struggling team, the complex domain, the critical project. Comfort comes from predictability, and the hardest teams teach you the most.&lt;/p&gt;

&lt;p&gt;Identify something you want to get better at and pursue it with intent. Take courses, find mentors, put in deliberate practice.&lt;/p&gt;

&lt;p&gt;Set growth goals alongside your delivery goals. What skill will you have in a year that you don’t have now? What will you understand that you don’t understand today?&lt;/p&gt;

&lt;p&gt;Don’t wait for performance reviews to get feedback. Ask colleagues what you could do better. Seek out honest assessments of your work.&lt;/p&gt;

&lt;h2 id=&quot;the-honest-question&quot;&gt;The honest question&lt;/h2&gt;

&lt;p&gt;The question to ask yourself: am I still growing?&lt;/p&gt;

&lt;p&gt;Performance can continue long after growth stops. Comfort can coexist with stagnation. So the question isn’t whether you’re still good at your job. It’s whether you’re still getting better.&lt;/p&gt;

&lt;p&gt;Are you learning? Are you being challenged? Are you better this year than last year? Will you be better next year than this year?&lt;/p&gt;

&lt;p&gt;If the answer is no, you’re in the trap. And the only way out is to acknowledge it and do something different.&lt;/p&gt;

&lt;p&gt;My colleague who spent eight years at the same level eventually moved to a Staff engineer role at a different company. The transition was hard. He’d atrophied in ways he hadn’t noticed. But within a year, he was learning again, challenged again, growing again.&lt;/p&gt;

&lt;p&gt;The trap isn’t permanent. But escaping it requires you to notice you’re in it.&lt;/p&gt;
</description>
				<pubDate>Tue, 03 Mar 2026 08:00:00 +0000</pubDate>
				<link>https://joshhornby.com/the-tech-lead-trap</link>
				<guid isPermaLink="true">https://joshhornby.com/the-tech-lead-trap</guid>
			</item>
		
			<item>
				<title>Inheriting a Struggling Team</title>
        		<description>&lt;blockquote&gt;
  &lt;p&gt;This post is part of my &lt;a href=&quot;/tags/tech-lead&quot;&gt;Tech Lead Series&lt;/a&gt;, a collection of practical advice for engineers stepping into leadership roles.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Inheriting a struggling team is one of the hardest things a tech lead can face. The problems run deep and the trust is gone. But it’s also an opportunity. Teams in crisis have nowhere to go but up.&lt;/p&gt;

&lt;h2 id=&quot;understanding-what-youve-inherited&quot;&gt;Understanding What You’ve Inherited&lt;/h2&gt;

&lt;p&gt;Before you can fix anything, you need to understand what’s actually broken. The symptoms are visible; the causes are usually hidden.&lt;/p&gt;

&lt;p&gt;Talk to everyone. Schedule 1:1s with every team member in your first week. Ask open questions: What’s working? What’s not? What would they change? What do they wish leadership understood? Listen more than you talk. This is the same advice from &lt;a href=&quot;/your-first-90-days-as-tech-lead&quot;&gt;your first 90 days&lt;/a&gt;, but compressed.&lt;/p&gt;

&lt;p&gt;Read the history. Old retrospectives, incident reports, architecture documents, Slack history. What patterns emerge? What keeps going wrong? What decisions led to this point?&lt;/p&gt;

&lt;p&gt;Understand the departures. If people left, find out why. Exit interviews if they exist, informal conversations if you can manage them. People leave struggling teams for reasons, and those reasons tell you what’s wrong.&lt;/p&gt;

&lt;p&gt;Identify the real problems. The obvious problems are rarely the root problems. Technical debt is often a symptom of unclear priorities. Low morale is often a symptom of feeling unheard. Dig until you find causes, not just effects.&lt;/p&gt;

&lt;p&gt;Find the bright spots. Even struggling teams have things that work. People who still care, processes that function, code that’s actually solid. Build on these rather than only focusing on what’s broken.&lt;/p&gt;

&lt;h2 id=&quot;quick-wins-matter&quot;&gt;Quick Wins Matter&lt;/h2&gt;

&lt;p&gt;Struggling teams have often been promised turnarounds before. They’ve heard the speeches about change. They’re sceptical, and they should be.&lt;/p&gt;

&lt;p&gt;Words alone won’t change their minds. Only action builds credibility.&lt;/p&gt;

&lt;p&gt;Find something you can fix in the first two weeks. Something visible, something the team cares about. It doesn’t have to be big. Cancel a useless meeting. Fix the flaky test that everyone hates. Push back on an unreasonable demand from stakeholders.&lt;/p&gt;

&lt;p&gt;The specific fix matters less than what it signals: things can change, and raising problems with you leads to action.&lt;/p&gt;

&lt;h2 id=&quot;building-trust&quot;&gt;Building Trust&lt;/h2&gt;

&lt;p&gt;Trust is usually the deepest wound. The team has learned that leadership doesn’t deliver and that feedback goes nowhere.&lt;/p&gt;

&lt;p&gt;You rebuild trust through consistent, small actions over time.&lt;/p&gt;

&lt;p&gt;Do what you say. Every commitment you make matters. If you say you’ll follow up on something, follow up. If you say you’ll look into a problem, look into it. Dropped promises confirm that nothing has changed.&lt;/p&gt;

&lt;p&gt;Be honest about what you can’t do. Don’t promise changes you can’t deliver. “I can’t fix the technical debt in a month, but I can protect time for us to address some of it” is honest. “I’ll fix everything” is a lie.&lt;/p&gt;

&lt;p&gt;Admit what you don’t know. You’re new. You don’t understand everything. Pretending otherwise insults your team’s intelligence. “I’m still learning how this system works” builds more trust than pretending expertise.&lt;/p&gt;

&lt;p&gt;Protect them. When unreasonable demands come from outside, push back. Let the team see you doing it. Nothing builds trust faster than watching your lead fight for you.&lt;/p&gt;

&lt;p&gt;Follow through on feedback. When someone raises a concern, act on it or explain why you can’t. The worst thing is asking for feedback and then ignoring it.&lt;/p&gt;

&lt;h2 id=&quot;addressing-performance-issues&quot;&gt;Addressing Performance Issues&lt;/h2&gt;

&lt;p&gt;Struggling teams sometimes have performance issues that were never addressed. The previous lead was too busy firefighting or too conflict-averse to have hard conversations.&lt;/p&gt;

&lt;p&gt;You need to address these, but carefully.&lt;/p&gt;

&lt;p&gt;Don’t rush to judgment. People underperform in struggling teams because the environment enables it. Before concluding someone is a poor performer, give them a functioning team to perform in.&lt;/p&gt;

&lt;p&gt;Separate environment from individual. Is this person struggling because they’re not capable, or because the context makes success impossible? Fix the context before judging the person.&lt;/p&gt;

&lt;p&gt;But don’t avoid the hard calls. Sometimes performance issues are real and persistent. When you’ve given someone a fair chance and &lt;a href=&quot;/giving-hard-feedback&quot;&gt;clear feedback&lt;/a&gt; and they still don’t improve, you have to act. Keeping non-performers hurts everyone else.&lt;/p&gt;

&lt;p&gt;Be fair. The team is watching how you handle this. If you’re arbitrary or cruel, trust collapses. If you’re fair and clear, even difficult decisions can build credibility.&lt;/p&gt;

&lt;h2 id=&quot;fixing-the-technical-mess&quot;&gt;Fixing the Technical Mess&lt;/h2&gt;

&lt;p&gt;Struggling teams usually have struggling codebases. Tech debt, architectural rot, unclear ownership. This can’t be fixed quickly, but it can be managed.&lt;/p&gt;

&lt;p&gt;Stop the bleeding. Before improving anything, stop making it worse. Establish minimum quality standards. No more shortcuts that create debt.&lt;/p&gt;

&lt;p&gt;Make debt visible. Track it. Name it. Talk about it in planning. When the team understands the cost of debt, they’re more motivated to address it. I’ve written about &lt;a href=&quot;/reconsidering-tech-debt&quot;&gt;reconsidering how we talk about tech debt&lt;/a&gt; elsewhere.&lt;/p&gt;

&lt;p&gt;Carve out time. Protect some percentage of each sprint for debt reduction. It doesn’t have to be a lot. Even 10% adds up.&lt;/p&gt;

&lt;p&gt;Celebrate progress. When a gnarly piece of code gets cleaned up, acknowledge it. Progress on debt is real work, even if stakeholders don’t see it.&lt;/p&gt;

&lt;p&gt;Be patient. Technical messes take time to create and time to fix. Rushing leads to new messes. Steady, sustainable progress beats heroic sprints.&lt;/p&gt;

&lt;h2 id=&quot;communicating-with-stakeholders&quot;&gt;Communicating with Stakeholders&lt;/h2&gt;

&lt;p&gt;Stakeholders have learned not to trust this team. They’ve been burned by missed deadlines and broken features. Your job is to reset that relationship.&lt;/p&gt;

&lt;p&gt;Be honest about where things stand. Don’t hide the mess. “We have significant technical debt that’s slowing us down and causing quality issues” is better than pretending everything is fine.&lt;/p&gt;

&lt;p&gt;Set realistic expectations. Don’t promise a quick turnaround to look good. Set timelines you can actually meet. Rebuilt trust comes from kept promises, not optimistic forecasts.&lt;/p&gt;

&lt;p&gt;Show progress. Regular updates on what’s improving. Delivered features, but also fewer incidents and better velocity. Make the recovery visible.&lt;/p&gt;

&lt;p&gt;Ask for what you need. If the team needs time, resources, or protection from demands, advocate for it. Stakeholders can be reasonable when they understand the situation.&lt;/p&gt;

&lt;h2 id=&quot;the-long-game&quot;&gt;The Long Game&lt;/h2&gt;

&lt;p&gt;Turning around a struggling team takes time. Not weeks, but months. Probably six months before things feel genuinely different. A year before the turnaround is clearly sustained.&lt;/p&gt;

&lt;p&gt;The early period is the hardest. You’re fixing problems that aren’t your fault and cleaning up messes you didn’t make. It feels thankless.&lt;/p&gt;

&lt;p&gt;But teams that come through hard times together get strong. The engineers who stay through a turnaround are committed, and they don’t take things for granted.&lt;/p&gt;

&lt;p&gt;The way up is hard, but it’s rewarding in ways that leading a comfortable team never is.&lt;/p&gt;
</description>
				<pubDate>Fri, 27 Feb 2026 08:00:00 +0000</pubDate>
				<link>https://joshhornby.com/inheriting-a-struggling-team</link>
				<guid isPermaLink="true">https://joshhornby.com/inheriting-a-struggling-team</guid>
			</item>
		
			<item>
				<title>Giving Hard Feedback</title>
        		<description>&lt;blockquote&gt;
  &lt;p&gt;This post is part of my &lt;a href=&quot;/tags/tech-lead&quot;&gt;Tech Lead Series&lt;/a&gt;, a collection of practical advice for engineers stepping into leadership roles.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Giving hard feedback is uncomfortable. That’s why most people avoid it. But avoiding it doesn’t make the problem go away. It just delays the conversation until it’s worse for everyone.&lt;/p&gt;

&lt;h2 id=&quot;why-hard-feedback-matters&quot;&gt;Why Hard Feedback Matters&lt;/h2&gt;

&lt;p&gt;People can’t fix what they don’t know is broken. When you see a performance issue, an attitude problem, or a skill gap and don’t address it, you’re denying that person the chance to improve.&lt;/p&gt;

&lt;p&gt;You’re also being unfair to your team. If an engineer consistently ships late, everyone’s velocity drops. Dismissive behaviour in code reviews stops people from contributing. When you don’t address these problems, you’re choosing the comfort of avoiding one conversation over the wellbeing of the whole team.&lt;/p&gt;

&lt;p&gt;Hard feedback is a form of respect. You’re treating someone as capable of hearing the truth and acting on it. The alternative, staying quiet, assumes they can’t handle it. As Camille Fournier notes in &lt;a href=&quot;/notes-on-a-managers-path&quot;&gt;The Manager’s Path&lt;/a&gt;, “when they believe that their manager sees the good things they do, they’ll be more open to hearing about the areas where they might improve.”&lt;/p&gt;

&lt;h2 id=&quot;principles&quot;&gt;Principles&lt;/h2&gt;

&lt;p&gt;Some ideas that guide how to approach these conversations.&lt;/p&gt;

&lt;p&gt;Sooner is better. Small issues addressed early are minor corrections. The same issues left for months become serious problems. If you notice something in week two, address it in week three, not month six.&lt;/p&gt;

&lt;p&gt;Specific is better than vague. “Your work has been slipping” is hard to act on. “The last three PRs had bugs that made it to production, and you’ve missed two sprint commitments” is specific and something they can act on.&lt;/p&gt;

&lt;p&gt;Behaviour, not character. You’re addressing what someone did, not who they are. “You interrupted colleagues in the last three meetings” is about behaviour. “You’re rude” is about character. One can be changed; the other feels like an attack.&lt;/p&gt;

&lt;p&gt;Private, not public. Hard feedback happens in &lt;a href=&quot;/running-effective-one-to-ones&quot;&gt;1:1s&lt;/a&gt;, not in team meetings. Public criticism shames; private conversations allow for honest discussion.&lt;/p&gt;

&lt;p&gt;Direct, not cruel. You can be clear without being harsh. “I need to see improvement in the next month” is direct. “You’re the worst performer on the team” is cruel and useless.&lt;/p&gt;

&lt;h2 id=&quot;the-conversation&quot;&gt;The Conversation&lt;/h2&gt;

&lt;p&gt;A structure that I have found to work is:&lt;/p&gt;

&lt;p&gt;State the purpose. Don’t hide what the conversation is about. “I want to talk about some concerns I’ve had with recent work.” Let them know this is serious.&lt;/p&gt;

&lt;p&gt;Share what you’ve seen. Describe it with specific examples. Facts, not judgments. “In the last sprint, two of your stories weren’t completed, and the one that shipped had a bug that caused an hour of downtime.”&lt;/p&gt;

&lt;p&gt;Explain why it matters. “When stories don’t get completed, the team has to pick up the slack. When bugs ship, we lose customer trust and spend time on firefighting.”&lt;/p&gt;

&lt;p&gt;Ask for their view. You might not have the full picture. “What’s your take on this? Is there context I’m missing?” Sometimes there are good reasons. Sometimes you learn about blockers you can remove.&lt;/p&gt;

&lt;p&gt;Set clear expectations. Be specific about what needs to change. “Going forward, I need you to complete your committed stories and test more before marking things done.”&lt;/p&gt;

&lt;p&gt;Agree on next steps. What happens now? A plan to fix the issues? More check-ins? Specific support you’ll provide? Make it real.&lt;/p&gt;

&lt;p&gt;Write it down. Write up what was discussed and share it. This creates accountability and protects both of you if things escalate.&lt;/p&gt;

&lt;h2 id=&quot;handling-reactions&quot;&gt;Handling Reactions&lt;/h2&gt;

&lt;p&gt;Hard feedback often triggers emotional reactions. Some common ones and how to handle them.&lt;/p&gt;

&lt;p&gt;Defensiveness. They explain why each example isn’t really their fault. Acknowledge their view without backing off. “I hear that there were constraints, and those are worth discussing. But the pattern concerns me regardless of the reasons.”&lt;/p&gt;

&lt;p&gt;Deflection. They point to other people’s problems instead. Redirect. “We’re talking about your situation right now. Other issues are separate conversations.”&lt;/p&gt;

&lt;p&gt;Silence. They shut down and won’t engage. Give them space. “I know this is a lot to process. You don’t have to respond now, but I’d like to check in again tomorrow.”&lt;/p&gt;

&lt;p&gt;Anger. They get upset or hostile. Stay calm. “I can see this is frustrating. I’m not trying to attack you; I’m trying to help us find a way forward.”&lt;/p&gt;

&lt;p&gt;Tears. Emotional reactions happen. Don’t panic or rush to end the conversation. Pause, offer a tissue, give them a moment. The feedback still needs to be heard.&lt;/p&gt;

&lt;p&gt;Your job is to deliver the message clearly and kindly, not to manage their emotional response. Let them react, acknowledge their feelings, but don’t let the reaction derail the conversation.&lt;/p&gt;

&lt;h2 id=&quot;following-up&quot;&gt;Following Up&lt;/h2&gt;

&lt;p&gt;The conversation isn’t the end; it’s the start.&lt;/p&gt;

&lt;p&gt;Check in often. Don’t wait months to see if things improved. Weekly or fortnightly, ask how things are going, note progress, address continued concerns.&lt;/p&gt;

&lt;p&gt;Name improvement. When things get better, say so. “I’ve noticed the last two sprints have gone much better. Nice work.” Positive feedback matters.&lt;/p&gt;

&lt;p&gt;Escalate if needed. If things don’t improve despite clear feedback and support, you need to escalate. This means involving HR, starting formal processes, or having honest conversations about whether this role is the right fit.&lt;/p&gt;

&lt;p&gt;Keep records. Document your conversations, the expectations you set, and whether they were met. This protects both of you and makes sure nothing gets forgotten.&lt;/p&gt;

&lt;h2 id=&quot;common-mistakes&quot;&gt;Common Mistakes&lt;/h2&gt;

&lt;p&gt;Sandwiching. Hiding criticism between compliments waters down the message. The person hears the praise and misses the point. Be direct about what needs to change.&lt;/p&gt;

&lt;p&gt;Waiting too long. The longer you wait, the worse it gets. Address issues early.&lt;/p&gt;

&lt;p&gt;Being vague. Fuzzy feedback is useless. “Do better” doesn’t tell anyone what to do differently. Be specific.&lt;/p&gt;

&lt;p&gt;Making it personal. Attacking someone’s character guarantees defensiveness. Stick to behaviour and impact.&lt;/p&gt;

&lt;p&gt;Not following up. Feedback without follow-up is pointless. The conversation is step one, not the whole process.&lt;/p&gt;

&lt;p&gt;Avoiding it entirely. The most common mistake. Hoping that problems will go away on their own.&lt;/p&gt;

&lt;h2 id=&quot;when-it-doesnt-work&quot;&gt;When It Doesn’t Work&lt;/h2&gt;

&lt;p&gt;Sometimes, despite clear feedback and genuine support, things don’t improve. This is hard but important to accept.&lt;/p&gt;

&lt;p&gt;Not everyone can do every job. Some people are in the wrong role, or have problems that feedback can’t reach, or hear the feedback and choose to ignore it.&lt;/p&gt;

&lt;p&gt;When you’ve been clear and supportive and improvement still doesn’t happen, it’s time for a different conversation. That might mean a performance improvement plan, a role change, or an exit.&lt;/p&gt;

&lt;p&gt;You gave them information and opportunity. What they do with it is their choice.&lt;/p&gt;

&lt;h2 id=&quot;the-hardest-part&quot;&gt;The Hardest Part&lt;/h2&gt;

&lt;p&gt;The hardest part isn’t the conversation itself. It’s the days before, when you’re dreading it. The anxiety, the script-writing in your head, the temptation to postpone.&lt;/p&gt;

&lt;p&gt;The anticipation is always worse than the reality. Once you’re in the conversation, you’re problem-solving, not suffering. And afterward, regardless of how it went, you’ve done your job. You’ve given someone the information they need to get better.&lt;/p&gt;
</description>
				<pubDate>Tue, 24 Feb 2026 08:00:00 +0000</pubDate>
				<link>https://joshhornby.com/giving-hard-feedback</link>
				<guid isPermaLink="true">https://joshhornby.com/giving-hard-feedback</guid>
			</item>
		
			<item>
				<title>Making Technical Decisions Stick</title>
        		<description>&lt;blockquote&gt;
  &lt;p&gt;This post is part of my &lt;a href=&quot;/tags/tech-lead&quot;&gt;Tech Lead Series&lt;/a&gt;, a collection of practical advice for engineers stepping into leadership roles.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Decisions that aren’t documented don’t exist. They live in the memories of people who were in the room, until those people leave or forget. A decision made in a Slack thread that scrolled past last month might as well never have happened.&lt;/p&gt;

&lt;p&gt;Making technical decisions is one part of the job. Making them stick is another.&lt;/p&gt;

&lt;h2 id=&quot;why-decisions-dont-stick&quot;&gt;Why Decisions Don’t Stick&lt;/h2&gt;

&lt;p&gt;Even good decisions fade without reinforcement.&lt;/p&gt;

&lt;p&gt;They’re not written down. Verbal agreements in meetings evaporate. Six months later, nobody remembers what was decided or why.&lt;/p&gt;

&lt;p&gt;They’re written but not findable. The decision exists in a Confluence page nobody visits, a Slack message nobody searches for, a Google Doc with no clear owner.&lt;/p&gt;

&lt;p&gt;The context isn’t captured. Even when the decision is documented, the reasoning isn’t. A year later, someone asks “why do we do it this way?” and nobody can answer.&lt;/p&gt;

&lt;p&gt;People weren’t involved. Decisions made by a small group don’t feel binding to people who weren’t asked. They’ll revisit the decision because, from their view, it was never really made.&lt;/p&gt;

&lt;p&gt;There’s no follow-up. A decision without follow-through is just a suggestion. If nobody checks whether the decision is being followed, it won’t be.&lt;/p&gt;

&lt;h2 id=&quot;match-documentation-to-the-decisions-weight&quot;&gt;Match documentation to the decision’s weight&lt;/h2&gt;

&lt;p&gt;Not every decision needs the same treatment.&lt;/p&gt;

&lt;p&gt;Verbal agreement works for small, reversible decisions that affect only your team. “Let’s use this library for parsing.” These can live in team memory, but expect to explain them again.&lt;/p&gt;

&lt;p&gt;Written summary works for decisions that affect multiple people or span time. A Slack message that captures what was decided and why. A brief note in your team’s docs. Easy to write, easy to find later.&lt;/p&gt;

&lt;p&gt;Architecture Decision Records (ADRs) work for decisions that limit future work. An ADR is a short document that captures the context, the decision, the options you rejected, and the results. It becomes part of your project’s permanent record.&lt;/p&gt;

&lt;p&gt;Request for Comments (RFCs) work for decisions that need input from many people or have high stakes. An RFC is a proposal that invites feedback before the decision is final. It documents both the decision and the discussion that led to it.&lt;/p&gt;

&lt;p&gt;Most decisions need only verbal agreement or written summary. Save ADRs and RFCs for decisions that matter: technology choices, architectural patterns, interfaces between systems.&lt;/p&gt;

&lt;h2 id=&quot;adrs-work-because-theyre-simple&quot;&gt;ADRs work because they’re simple&lt;/h2&gt;

&lt;p&gt;A good ADR has six sections and fits on one page:&lt;/p&gt;

&lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gh&quot;&gt;# ADR-001: Use PostgreSQL for user service data&lt;/span&gt;

&lt;span class=&quot;gu&quot;&gt;## Status&lt;/span&gt;
Accepted

&lt;span class=&quot;gu&quot;&gt;## Context&lt;/span&gt;
We need a database for the new user service. The team has
experience with PostgreSQL and MongoDB. We expect relational
queries across user profiles, permissions, and audit logs.

&lt;span class=&quot;gu&quot;&gt;## Decision&lt;/span&gt;
Use PostgreSQL for the user service.

&lt;span class=&quot;gu&quot;&gt;## Results&lt;/span&gt;
Relational queries are straightforward. We lose MongoDB&apos;s
flexible schema, which means migrations for schema changes.

&lt;span class=&quot;gu&quot;&gt;## Options rejected&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;gs&quot;&gt;**MongoDB**&lt;/span&gt;: Better schema flexibility, but our query
  patterns are relational and the team knows PostgreSQL better.
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;gs&quot;&gt;**MySQL**&lt;/span&gt;: Similar fit, but less team experience and weaker
  JSON support.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Keep it short. If writing an ADR feels like a chore, it’s too long.&lt;/p&gt;

&lt;h2 id=&quot;rfcs-fail-when-theyre-theatre&quot;&gt;RFCs fail when they’re theatre&lt;/h2&gt;

&lt;p&gt;RFCs work when they’re real invitations for input, not rubber stamps.&lt;/p&gt;

&lt;p&gt;Share early. An RFC shared before you’ve made up your mind gets real feedback. An RFC shared when you’ve already decided just annoys people.&lt;/p&gt;

&lt;p&gt;Set clear timelines. “Comments by Friday” creates urgency. Open-ended feedback periods drag on forever.&lt;/p&gt;

&lt;p&gt;Ask for feedback directly. Don’t just post and wait. Ask specific people for input. Reach out to experts, to people affected, to the usual critics.&lt;/p&gt;

&lt;p&gt;Respond to feedback. Every comment deserves a response, even if it’s “thanks, but we’re going in a different direction for these reasons.” Ignored feedback teaches people not to bother.&lt;/p&gt;

&lt;p&gt;Make the decision. An RFC that never ends is worse than no RFC. Set a deadline, weigh the feedback, make a call, and write it down.&lt;/p&gt;

&lt;h2 id=&quot;involvement-determines-whether-decisions-stick&quot;&gt;Involvement determines whether decisions stick&lt;/h2&gt;

&lt;p&gt;The decision-making process matters as much as the decision itself.&lt;/p&gt;

&lt;p&gt;Involve the right people. Not everyone, but not no one. People affected by a decision should have a chance to weigh in. Decisions made without asking get reopened.&lt;/p&gt;

&lt;p&gt;Be explicit about the process. Are we deciding today? Taking input for a week then deciding? Who makes the final call? Vagueness about the process creates vagueness about the outcome.&lt;/p&gt;

&lt;p&gt;Disagree and commit. Once a decision is made, everyone should support it, even those who disagreed. If you can’t get to agreement, be clear that you’re making a call despite disagreement and expect people to follow through.&lt;/p&gt;

&lt;p&gt;Don’t revisit without new information. Every decision can be debated forever. Establish that decisions stay made unless new facts arise. “We already decided this” should end conversations, not start them.&lt;/p&gt;

&lt;h2 id=&quot;decisions-need-visible-attention-not-rules&quot;&gt;Decisions need visible attention, not rules&lt;/h2&gt;

&lt;p&gt;Follow-through doesn’t mean a heavy process. It means someone is paying attention.&lt;/p&gt;

&lt;p&gt;Check in code reviews. When you see code that goes against a decision, ask about it. Sometimes there’s a good reason; sometimes someone didn’t know. Either way, it’s a conversation.&lt;/p&gt;

&lt;p&gt;Point to decisions in discussions. When someone proposes something that clashes with a prior decision, point to the docs. “We decided X for these reasons. Has something changed?”&lt;/p&gt;

&lt;p&gt;Update decisions when needed. Things change. If a decision no longer makes sense, update it explicitly. Don’t let it fade away; mark it replaced and write down why.&lt;/p&gt;

&lt;p&gt;Model it yourself. Your own code should follow the decisions. If you ignore the standards, so will everyone else.&lt;/p&gt;

&lt;h2 id=&quot;keep-decisions-close-to-the-code&quot;&gt;Keep decisions close to the code&lt;/h2&gt;

&lt;p&gt;ADRs belong in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/docs/adr&lt;/code&gt; folder in the repo. Decisions about this system live with this system. They’re versioned, searchable, and visible in pull requests.&lt;/p&gt;

&lt;p&gt;Use a consistent numbering system. ADR-001, ADR-002. Numbers make decisions easy to reference. “This implements ADR-015” in a commit message links decisions to outcomes.&lt;/p&gt;

&lt;p&gt;Review active ADRs once a quarter. Are they still relevant? Are they being followed? Are any ready to be replaced?&lt;/p&gt;

&lt;p&gt;If your team has never used ADRs, don’t mandate them for everything. Write one ADR for one decision and see if it helps. Build the habit bit by bit.&lt;/p&gt;

&lt;h2 id=&quot;shared-understanding-not-bureaucracy&quot;&gt;Shared understanding, not bureaucracy&lt;/h2&gt;

&lt;p&gt;Technical decisions shape how your systems evolve. When they’re undocumented, they get forgotten. When they’re made in isolation, they get ignored. When nobody follows up, they become suggestions.&lt;/p&gt;

&lt;p&gt;The goal isn’t a process for its own sake. It’s building a shared record of how things work and why. New team members can understand the system’s history. Future decisions build on past ones. And the PostgreSQL you decided on three months ago is still what gets used.&lt;/p&gt;
</description>
				<pubDate>Fri, 20 Feb 2026 08:00:00 +0000</pubDate>
				<link>https://joshhornby.com/making-technical-decisions-stick</link>
				<guid isPermaLink="true">https://joshhornby.com/making-technical-decisions-stick</guid>
			</item>
		
			<item>
				<title>Scope Creep and How to Fight It</title>
        		<description>&lt;blockquote&gt;
  &lt;p&gt;This post is part of my &lt;a href=&quot;/tags/tech-lead&quot;&gt;Tech Lead Series&lt;/a&gt;, a collection of practical advice for engineers stepping into leadership roles.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Scope creep doesn’t announce itself. It arrives as reasonable requests. Each addition seems small. Each conversation is “while we’re building this anyway.” Nobody makes a bad decision. You just make a lot of small decisions that add up to a bad outcome.&lt;/p&gt;

&lt;h2 id=&quot;recognising-the-pattern&quot;&gt;Recognising the Pattern&lt;/h2&gt;

&lt;p&gt;“While you’re in there…” The logic seems sound. You’re already working on the payment system, so why not also add refund handling? Because refund handling is a week of work you didn’t plan for.&lt;/p&gt;

&lt;p&gt;“This would only take a few hours.” People who don’t build software underestimate how long things take. A few hours becomes a few days. And even if it really is a few hours, a dozen “few hours” changes add up to weeks.&lt;/p&gt;

&lt;p&gt;“The customer really needs this.” Urgent requests from customers feel impossible to refuse. But customers will always want more. The question is whether this specific addition is worth delaying everything else.&lt;/p&gt;

&lt;p&gt;“We forgot to include…” Discovered requirements feel like corrections to an incomplete plan, not additions. But they’re still additions. The plan was complete for what it covered; this is new scope.&lt;/p&gt;

&lt;p&gt;“Just a small tweak.” Small is relative. Small compared to the original feature can still be real work. And small things ship with testing, documentation, edge cases.&lt;/p&gt;

&lt;p&gt;The common thread: each individual addition seems fair. The problem is the pile-up.&lt;/p&gt;

&lt;h2 id=&quot;why-it-happens&quot;&gt;Why It Happens&lt;/h2&gt;

&lt;p&gt;Scope creep has a few common causes.&lt;/p&gt;

&lt;p&gt;When the initial scope isn’t well-defined, people fill in the gaps with guesses. Those guesses get turned into requirements as work moves forward.&lt;/p&gt;

&lt;p&gt;Different stakeholders have different expectations. Without clear agreement on what’s in and what’s out, each stakeholder adds their own version. This is why &lt;a href=&quot;/working-with-product-managers&quot;&gt;working closely with your PM&lt;/a&gt; on scope definition matters so much.&lt;/p&gt;

&lt;p&gt;PMs want to please stakeholders. Tech leads want to please PMs. Engineers want to solve problems. Everyone’s drive is to say yes. Nobody’s drive is to protect the timeline. Learning &lt;a href=&quot;/when-to-say-no&quot;&gt;when to say no&lt;/a&gt; is one of the most important skills for a tech lead.&lt;/p&gt;

&lt;p&gt;“We’ve already built most of it, we might as well add this too.” The logic feels right but ignores that adding more makes it even later.&lt;/p&gt;

&lt;p&gt;When the goal is vague (“make a good dashboard”), scope expands to fill available time. When the goal is specific (“show four metrics, ship in two weeks”), additions are clearly out of scope.&lt;/p&gt;

&lt;h2 id=&quot;fighting-it-early&quot;&gt;Fighting It Early&lt;/h2&gt;

&lt;p&gt;The best time to fight scope creep is before work starts.&lt;/p&gt;

&lt;p&gt;Define scope explicitly. Write down what’s included and what’s not. “The dashboard will show revenue, users, conversion, and churn. It will not include filtering, export, or alerts.” The explicit exclusions matter as much as the inclusions.&lt;/p&gt;

&lt;p&gt;Get sign-off on boundaries. Make sure everyone with influence agrees on the scope before work begins. When additions arise, you can point back to the agreement.&lt;/p&gt;

&lt;p&gt;Size the work with buffers. Things take longer than estimated. If you estimate two weeks and commit to two weeks, any addition blows the timeline. Build in slack so small adjustments don’t become crises. &lt;a href=&quot;/estimation-pragmatism&quot;&gt;Good estimation&lt;/a&gt; isn’t about perfect numbers; it’s about reducing surprise.&lt;/p&gt;

&lt;p&gt;Separate must-have from nice-to-have. Prioritise hard upfront. When pressure mounts, you know what can be cut.&lt;/p&gt;

&lt;h2 id=&quot;having-the-conversation&quot;&gt;Having the Conversation&lt;/h2&gt;

&lt;p&gt;Once creep starts, you need to address it directly. Hoping it stops on its own doesn’t work.&lt;/p&gt;

&lt;p&gt;Name what’s happening. “I’ve noticed we’ve added six features since we started. Our two-week timeline is now impossible.” Make the pile-up visible.&lt;/p&gt;

&lt;p&gt;Quantify the impact. “Each of these additions adds roughly two days. We’re now looking at five weeks instead of two.” Numbers make the problem concrete.&lt;/p&gt;

&lt;p&gt;Present the trade-offs. “We can add this feature, but it means either pushing the deadline or cutting something else. Which would you prefer?” Force a decision rather than absorbing the cost silently.&lt;/p&gt;

&lt;p&gt;If the team committed to a deadline, additions should require clear trade-offs. Don’t let creep quietly extend timelines. Make it a choice. And &lt;a href=&quot;/managing-up-as-tech-lead&quot;&gt;keep your manager informed&lt;/a&gt; as scope shifts. They’ll need to manage expectations upward too.&lt;/p&gt;

&lt;p&gt;When scope changes, record what changed and why. This creates accountability and helps prevent further creep.&lt;/p&gt;

&lt;h2 id=&quot;structural-approaches&quot;&gt;Structural Approaches&lt;/h2&gt;

&lt;p&gt;Beyond conversations, some structural approaches help.&lt;/p&gt;

&lt;p&gt;Timebox hard. “We have two weeks. What can we build in two weeks?” Start from the constraint and work backward. This frames additions as taking away from something else.&lt;/p&gt;

&lt;p&gt;Keep a backlog of good ideas that aren’t in scope for this release. People feel heard without adding to current work. Review the list for the next iteration.&lt;/p&gt;

&lt;p&gt;Smaller releases mean less chance for creep. A two-week feature ships before scope can expand much. A six-month project piles up changes for six months.&lt;/p&gt;

&lt;p&gt;Some teams require formal change requests for scope additions. The process itself discourages casual additions.&lt;/p&gt;

&lt;p&gt;Burn-down charts, task boards, regular demos. When everyone can see how much work remains, it’s harder to pretend additions are free. &lt;a href=&quot;/hill-charts&quot;&gt;Hill charts&lt;/a&gt; are useful here because they show uncertainty, not just completion.&lt;/p&gt;

&lt;h2 id=&quot;when-to-accept-changes&quot;&gt;When to Accept Changes&lt;/h2&gt;

&lt;p&gt;Not all scope additions are creep. Sometimes requirements genuinely change. Sometimes you learn something that makes the original plan wrong.&lt;/p&gt;

&lt;p&gt;Changes driven by new information are legitimate: user research reveals the original approach won’t work, a dependency changes, business conditions shift, or you discover a technical constraint that forces redesign.&lt;/p&gt;

&lt;p&gt;The difference is whether the change is driven by new information or by wishful thinking. “We learned customers don’t use this feature that way” is new information. “The VP would also like it to do this” is wishful thinking.&lt;/p&gt;

&lt;p&gt;Accept legitimate changes. Push back on scope creep. The skill is knowing which is which.&lt;/p&gt;

&lt;h2 id=&quot;the-uncomfortable-truth&quot;&gt;The Uncomfortable Truth&lt;/h2&gt;

&lt;p&gt;You cannot eliminate scope creep entirely. Some additions will get through. Some projects will expand beyond their original bounds. This is normal.&lt;/p&gt;

&lt;p&gt;What you can do is make creep visible, make trade-offs explicit, and protect the team’s ability to deliver. Every time you let an addition slip in without discussion, you teach stakeholders that additions are free. Every time you name the creep and force a decision, you build a culture that respects constraints.&lt;/p&gt;
</description>
				<pubDate>Tue, 17 Feb 2026 08:00:00 +0000</pubDate>
				<link>https://joshhornby.com/scope-creep-and-how-to-fight-it</link>
				<guid isPermaLink="true">https://joshhornby.com/scope-creep-and-how-to-fight-it</guid>
			</item>
		
			<item>
				<title>Sentry AI Tracing with Laravel&apos;s AI SDK</title>
        		<description>&lt;p&gt;In my &lt;a href=&quot;/sentry-ai-tracing-laravel&quot;&gt;previous post&lt;/a&gt;, I showed how to manually wrap OpenAI calls with Sentry spans to get LLM monitoring in PHP. It worked, but every AI call needed to pass through a tracing method. You had to remember to use it.&lt;/p&gt;

&lt;p&gt;Laravel now ships an &lt;a href=&quot;https://laravel.com/docs/12.x/ai-sdk&quot;&gt;official AI SDK&lt;/a&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;laravel/ai&lt;/code&gt;) that changes this. The SDK fires events for every agent interaction, which means tracing becomes a listener. Write it once, and every AI call in your app gets traced automatically.&lt;/p&gt;

&lt;h2 id=&quot;what-changed&quot;&gt;What changed&lt;/h2&gt;

&lt;p&gt;The old approach required wrapping each call:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;traceOpenAIRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;operation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;chat_completions&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;apiCall&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The new SDK has agents as first-class objects. You define an agent class with instructions, tools, and a schema, then call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prompt()&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SalesCoach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;prompt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;Analyse this sales transcript...&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The SDK fires events at each stage. Six are useful for tracing:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PromptingAgent&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AgentPrompted&lt;/code&gt; for non-streamed chat calls&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StreamingAgent&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AgentStreamed&lt;/code&gt; for streamed chat calls&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GeneratingEmbeddings&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EmbeddingsGenerated&lt;/code&gt; for embedding calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each event carries an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invocationId&lt;/code&gt; to correlate the start and finish.&lt;/p&gt;

&lt;h2 id=&quot;the-listener&quot;&gt;The listener&lt;/h2&gt;

&lt;p&gt;Instead of wrapping individual calls, register a single event listener:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;App\Listeners&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Laravel\Ai\Events\AgentPrompted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Laravel\Ai\Events\AgentStreamed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Laravel\Ai\Events\EmbeddingsGenerated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Laravel\Ai\Events\GeneratingEmbeddings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Laravel\Ai\Events\PromptingAgent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Laravel\Ai\Events\StreamingAgent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Laravel\Ai\Providers\Provider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Sentry\SentrySdk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Sentry\Tracing\Span&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Sentry\Tracing\SpanContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Sentry\Tracing\SpanStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SentryAiTracing&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cd&quot;&gt;/**
     * @var array&amp;lt;string, array{span: Span, parentSpan: Span}&amp;gt;
     */&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$activeSpans&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handlePromptingAgent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;PromptingAgent&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;startSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handleStreamingAgent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;StreamingAgent&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;startSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handleAgentPrompted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;AgentPrompted&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;finishSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handleAgentStreamed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;AgentStreamed&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;finishSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handleGeneratingEmbeddings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;GeneratingEmbeddings&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$parentSpan&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SentrySdk&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getCurrentHub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$parentSpan&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;nv&quot;&gt;$context&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SpanContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setOp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;gen_ai.embeddings&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setDescription&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;embeddings &apos;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setOrigin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;auto.ai.laravel&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;gen_ai.system&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;strtolower&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;provider&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()),&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;gen_ai.request.model&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;gen_ai.operation.name&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;embeddings&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;

        &lt;span class=&quot;nv&quot;&gt;$childSpan&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$parentSpan&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;startChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;SentrySdk&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getCurrentHub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$childSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$activeSpans&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;invocationId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;s1&quot;&gt;&apos;span&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$childSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s1&quot;&gt;&apos;parentSpan&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$parentSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handleEmbeddingsGenerated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;EmbeddingsGenerated&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$entry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$activeSpans&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;invocationId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$entry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;nb&quot;&gt;unset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$activeSpans&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;invocationId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;

        &lt;span class=&quot;nv&quot;&gt;$span&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;span&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

        &lt;span class=&quot;nv&quot;&gt;$span&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;array_merge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$span&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;s1&quot;&gt;&apos;gen_ai.response.model&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s1&quot;&gt;&apos;gen_ai.usage.input_tokens&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;]));&lt;/span&gt;

        &lt;span class=&quot;nv&quot;&gt;$span&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;SpanStatus&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$span&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;finish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;SentrySdk&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getCurrentHub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;parentSpan&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;startSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;PromptingAgent&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$parentSpan&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SentrySdk&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getCurrentHub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$parentSpan&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;nv&quot;&gt;$providerName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prompt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;provider&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Provider&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;strtolower&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prompt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;provider&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;unknown&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;nv&quot;&gt;$agentName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;class_basename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prompt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;agent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;nv&quot;&gt;$context&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SpanContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setOp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;gen_ai.chat&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setDescription&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;chat &apos;&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prompt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setOrigin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;auto.ai.laravel&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;gen_ai.system&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$providerName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;gen_ai.request.model&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prompt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;gen_ai.operation.name&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;chat&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;gen_ai.agent.name&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$agentName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;gen_ai.pipeline.name&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$agentName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;

        &lt;span class=&quot;nv&quot;&gt;$childSpan&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$parentSpan&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;startChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;SentrySdk&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getCurrentHub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$childSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$activeSpans&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;invocationId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;s1&quot;&gt;&apos;span&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$childSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s1&quot;&gt;&apos;parentSpan&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$parentSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;finishSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;AgentPrompted&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$entry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$activeSpans&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;invocationId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$entry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;nb&quot;&gt;unset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$activeSpans&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;invocationId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;

        &lt;span class=&quot;nv&quot;&gt;$span&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;span&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$usage&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;usage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$meta&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;nv&quot;&gt;$span&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;array_merge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$span&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;s1&quot;&gt;&apos;gen_ai.response.model&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$meta&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s1&quot;&gt;&apos;gen_ai.usage.input_tokens&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$usage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;promptTokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s1&quot;&gt;&apos;gen_ai.usage.output_tokens&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$usage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;completionTokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;]));&lt;/span&gt;

        &lt;span class=&quot;nv&quot;&gt;$span&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;SpanStatus&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$span&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;finish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;SentrySdk&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getCurrentHub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;parentSpan&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;cd&quot;&gt;/**
     * Reset active spans (used in testing).
     */&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$activeSpans&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Laravel auto-discovers listeners by convention. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;handlePromptingAgent&lt;/code&gt; method maps to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PromptingAgent&lt;/code&gt; event, and so on for the rest. Drop this class into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/Listeners&lt;/code&gt; and every agent and embedding call gets a Sentry span with the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gen_ai.*&lt;/code&gt; attributes from before.&lt;/p&gt;

&lt;h2 id=&quot;whats-different-from-the-manual-approach&quot;&gt;What’s different from the manual approach&lt;/h2&gt;

&lt;p&gt;The old wrapper only traced calls that explicitly used it. Forget to wrap a call and you get no span. The listener catches everything that goes through the SDK, regardless of which agent or provider made the call.&lt;/p&gt;

&lt;p&gt;The SDK abstracts providers behind a common interface. The listener pulls the provider name from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$event-&amp;gt;prompt-&amp;gt;provider-&amp;gt;name()&lt;/code&gt;, so switching from OpenAI to Anthropic doesn’t need any tracing changes.&lt;/p&gt;

&lt;p&gt;Streaming was awkward before because token counts arrive at the end of the stream. The SDK handles that internally and fires &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AgentStreamed&lt;/code&gt; with the full usage data already collected. The listener code is identical for both paths.&lt;/p&gt;

&lt;p&gt;Embeddings follow the same pattern but with different span data. Chat completions track both input and output tokens. Embeddings only have input tokens since there’s no generated text, just vectors.&lt;/p&gt;

&lt;p&gt;Each event gets a unique &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invocationId&lt;/code&gt;. The listener uses this to match start and finish events, stored in a static &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$activeSpans&lt;/code&gt; array. This handles concurrent requests without race conditions.&lt;/p&gt;

&lt;h2 id=&quot;the-same-patterns-still-apply&quot;&gt;The same patterns still apply&lt;/h2&gt;

&lt;p&gt;The graceful degradation check from the original post still applies. If there’s no active Sentry transaction (queue workers, artisan commands), &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getSpan()&lt;/code&gt; returns null and the listener exits early without errors.&lt;/p&gt;

&lt;p&gt;Parent span restoration is the same. After finishing the AI span, we reset the hub to the parent span so later operations attach to the right place in the trace hierarchy.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gen_ai.*&lt;/code&gt; attribute conventions are unchanged. Sentry’s LLM monitoring UI still picks up token counts, model versions, and provider information the same way.&lt;/p&gt;

&lt;h2 id=&quot;beyond-sentry&quot;&gt;Beyond Sentry&lt;/h2&gt;

&lt;p&gt;The SDK fires events that any listener can hook into. You could add listeners for logging, cost tracking, or rate limiting alongside the tracing listener.&lt;/p&gt;

&lt;p&gt;The previous post required you to build the plumbing and remember to use it. Now the framework gives you the hooks.&lt;/p&gt;
</description>
				<pubDate>Sat, 14 Feb 2026 08:00:00 +0000</pubDate>
				<link>https://joshhornby.com/sentry-ai-tracing-laravel-ai-sdk</link>
				<guid isPermaLink="true">https://joshhornby.com/sentry-ai-tracing-laravel-ai-sdk</guid>
			</item>
		
			<item>
				<title>When to Say No</title>
        		<description>&lt;blockquote&gt;
  &lt;p&gt;This post is part of my &lt;a href=&quot;/tags/tech-lead&quot;&gt;Tech Lead Series&lt;/a&gt;, a collection of practical advice for engineers stepping into leadership roles.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Learning to say no is one of the hardest parts of becoming a tech lead. It feels like failing people. It takes time to understand that thoughtful no’s are how you protect your team’s ability to deliver.&lt;/p&gt;

&lt;p&gt;The instinct is to say yes. Yes to the PM’s feature request, yes to the stakeholder’s timeline, yes to the “quick addition” that’s never quick. It feels like being helpful. But every yes is a commitment of your team’s finite time and energy. Say yes to everything and you deliver nothing well.&lt;/p&gt;

&lt;h2 id=&quot;why-saying-no-is-hard&quot;&gt;Why Saying No Is Hard&lt;/h2&gt;

&lt;p&gt;Saying yes feels good. You’re being helpful. You’re being a team player. You’re not the person blocking progress. The immediate social reward is powerful.&lt;/p&gt;

&lt;p&gt;Saying no feels bad. You’re disappointing someone. You might be wrong. You might miss an opportunity. The immediate social cost is real.&lt;/p&gt;

&lt;p&gt;But the calculus changes when you think about consequences. Every yes is a commitment of your team’s time and energy. Time spent on one thing is time not spent on another. When you say yes to everything, you’re implicitly saying no to quality, to sustainability, to the things that matter most.&lt;/p&gt;

&lt;p&gt;The job of a tech lead is to protect the team’s capacity to do good work. That requires saying no often.&lt;/p&gt;

&lt;h2 id=&quot;when-to-say-no&quot;&gt;When to Say No&lt;/h2&gt;

&lt;p&gt;Not every request deserves no. The skill is knowing when to push back.&lt;/p&gt;

&lt;p&gt;When the value doesn’t justify the cost. Some features take three weeks to build and help 1% of users. Some “quick changes” require touching six systems. Before saying yes, understand what something actually costs. If the math doesn’t work, say so.&lt;/p&gt;

&lt;p&gt;When quality will suffer. Rushing to meet an arbitrary deadline by cutting corners creates debt that slows future work. Sometimes the right answer is adjusting scope or timeline, not accepting lower quality.&lt;/p&gt;

&lt;p&gt;When the team is overloaded. You see burnout signals before anyone else: late nights, dropped balls, declining morale. Protect your team from well-meaning people who don’t understand the cost of their requests.&lt;/p&gt;

&lt;p&gt;When there’s a better solution. Often the first solution proposed isn’t the best one. If you can solve the same problem more simply, push back on the original approach even if it’s already been promised to someone.&lt;/p&gt;

&lt;p&gt;When it conflicts with priorities. Every company has more ideas than capacity. When a new request conflicts with agreed priorities, the answer is usually no unless those priorities change.&lt;/p&gt;

&lt;p&gt;When your gut says no. Sometimes you can’t explain exactly why something feels wrong. Trust that instinct, at least enough to slow down and understand it. Your experience is spotting patterns in situations that haven’t fully shown themselves.&lt;/p&gt;

&lt;h2 id=&quot;how-to-say-no&quot;&gt;How to Say No&lt;/h2&gt;

&lt;p&gt;The word “no” rarely needs to be spoken. Good pushback sounds like problem-solving, not rejection.&lt;/p&gt;

&lt;p&gt;“Help me understand.” Start by understanding the request fully. Why does this matter? What problem does it solve? Who needs it and when? Sometimes requests dissolve under examination. Sometimes you learn something that changes your view.&lt;/p&gt;

&lt;p&gt;“Here’s what I’m seeing.” Share your view. The cost of the request, the impact on other work, the risks you’re worried about. Give people the information to make an informed decision.&lt;/p&gt;

&lt;p&gt;“Here are our options.” Rarely is it yes or no. Usually there’s a middle path: a smaller version, a later timeline, a different approach. Present alternatives that address the underlying need.&lt;/p&gt;

&lt;p&gt;“Not now, but maybe later.” Some requests aren’t wrong, just poorly timed. Deferring isn’t the same as rejecting. Add it to the backlog, set a time to revisit, make clear you’re not dismissing it.&lt;/p&gt;

&lt;p&gt;“Yes, if.” Sometimes yes depends on something else. “Yes, if we can move the deadline. Yes, if we cut the scope on this other feature. Yes, if we get another engineer.” Conditional yes clarifies trade-offs.&lt;/p&gt;

&lt;h2 id=&quot;saying-no-to-different-people&quot;&gt;Saying No to Different People&lt;/h2&gt;

&lt;p&gt;The approach varies depending on who’s asking.&lt;/p&gt;

&lt;p&gt;Product managers need to understand trade-offs, not just hear no. Explain the cost. Offer alternatives. Be a partner in finding the best solution. If you just block things without explanation, the relationship gets worse.&lt;/p&gt;

&lt;p&gt;Stakeholders outside your immediate team have less context. They need education more than negotiation. Explain how prioritisation works, what capacity looks like, why their request might not fit right now.&lt;/p&gt;

&lt;p&gt;Your own manager is delicate. You can’t just say no to your boss. But you can share your view, flag concerns, and ask clarifying questions. “I’m happy to do this, but it means X won’t happen. Is that the right trade-off?”&lt;/p&gt;

&lt;p&gt;Your own team sometimes proposes approaches you disagree with. The dynamic is different here. You have more authority but also more responsibility to explain your reasoning. Don’t just veto; teach.&lt;/p&gt;

&lt;h2 id=&quot;the-no-that-isnt-no&quot;&gt;The No That Isn’t No&lt;/h2&gt;

&lt;p&gt;Sometimes the answer is technically yes but really no. Be careful with these.&lt;/p&gt;

&lt;p&gt;“Sure, but it’ll take six months.” If you know something won’t get prioritised with that timeline, you’re not really saying yes. Be honest about whether something is actually going to happen.&lt;/p&gt;

&lt;p&gt;“Let me check and get back to you.” A useful delay when you need time to think. A harmful one when you’re just avoiding the conversation. Don’t use a process as a shield.&lt;/p&gt;

&lt;p&gt;“We’d need to talk to someone else.” Sometimes this is fair. Sometimes it’s passing the buck. Own the decision if it’s yours to own.&lt;/p&gt;

&lt;h2 id=&quot;building-the-reputation&quot;&gt;Building the Reputation&lt;/h2&gt;

&lt;p&gt;Saying no well requires credibility. People need to believe you’re saying no for good reasons, not because you’re blocking or lazy.&lt;/p&gt;

&lt;p&gt;Say yes when you should. If you say no to everything, people stop listening. Show that you’re willing to take on work, stretch when necessary, be flexible. Your no’s carry more weight when they’re not your default.&lt;/p&gt;

&lt;p&gt;Explain your reasoning. People can disagree with a no, but they should understand it. Hidden reasoning breeds resentment.&lt;/p&gt;

&lt;p&gt;Follow through on your yes’s. When you commit to something, deliver. Reliable execution earns you the right to push back on new requests.&lt;/p&gt;

&lt;p&gt;Be consistent. If you say no to one person’s feature and yes to another’s without clear reasoning, you’ll be seen as political rather than principled.&lt;/p&gt;

&lt;h2 id=&quot;the-hard-truth&quot;&gt;The Hard Truth&lt;/h2&gt;

&lt;p&gt;You will sometimes say no and be wrong. You’ll block something that would have worked, delay something that mattered, frustrate someone who had a good idea. This is unavoidable.&lt;/p&gt;

&lt;p&gt;The alternative is worse. Saying yes to everything guarantees failure. Not obvious, dramatic failure, but the slow failure of burned-out teams and missed commitments and declining quality.&lt;/p&gt;

&lt;p&gt;Your job isn’t to be right every time. It’s to make thoughtful decisions about where your team’s finite time and energy should go. That means saying no often, even when it’s uncomfortable.&lt;/p&gt;
</description>
				<pubDate>Fri, 13 Feb 2026 08:00:00 +0000</pubDate>
				<link>https://joshhornby.com/when-to-say-no</link>
				<guid isPermaLink="true">https://joshhornby.com/when-to-say-no</guid>
			</item>
		
			<item>
				<title>Managing Up</title>
        		<description>&lt;blockquote&gt;
  &lt;p&gt;This post is part of my &lt;a href=&quot;/tags/tech-lead&quot;&gt;Tech Lead Series&lt;/a&gt;, a collection of practical advice for engineers stepping into leadership roles.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My first serious mistake as a tech lead came from not managing up. The team had been struggling with a legacy system for months. I mentioned it in passing to my engineering manager, assuming she knew how bad it was. She didn’t. When the system finally caused a major incident, she was blindsided. Her question afterward stung: “Why didn’t you tell me this was a problem?”&lt;/p&gt;

&lt;p&gt;I had told her. Once, briefly, buried in a longer conversation. I’d assumed that was enough. It wasn’t.&lt;/p&gt;

&lt;p&gt;Managing up isn’t about politics or self-promotion. It’s about giving your manager the information they need to make good decisions and support your team effectively.&lt;/p&gt;

&lt;h2 id=&quot;what-your-manager-needs&quot;&gt;What Your Manager Needs&lt;/h2&gt;

&lt;p&gt;Your engineering manager has visibility problems you don’t think about. They’re responsible for multiple teams, attend meetings you never see, and make trade-offs across contexts you don’t have. They depend on you for ground truth.&lt;/p&gt;

&lt;p&gt;Risks and blockers. What could go wrong? What’s slowing the team down? What’s the thing that keeps you up at night? Your manager needs to know these things before they become crises.&lt;/p&gt;

&lt;p&gt;Team health. How are people doing? Is anyone struggling, burning out, or thinking about leaving? Your manager can’t help with problems they don’t know exist. This is information you gather in &lt;a href=&quot;/running-effective-one-to-ones&quot;&gt;your 1:1s&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Progress and context. Not just what’s done, but what it means. A shipped feature is more useful information when paired with customer reaction or remaining work.&lt;/p&gt;

&lt;p&gt;Your honest assessment. When asked how things are going, “fine” is not helpful. What’s actually happening? Where are the gaps between reality and how things look?&lt;/p&gt;

&lt;h2 id=&quot;how-to-communicate&quot;&gt;How to Communicate&lt;/h2&gt;

&lt;p&gt;The failure mode isn’t usually silence. It’s the wrong type of communication at the wrong time.&lt;/p&gt;

&lt;p&gt;Regular updates beat irregular ones. A weekly written update, even just bullet points, builds trust better than occasional long conversations. Your manager knows what’s happening without having to ask. I use &lt;a href=&quot;/weekly-updates&quot;&gt;four questions I answer every week&lt;/a&gt; to structure this.&lt;/p&gt;

&lt;p&gt;Lead with the important stuff. Don’t bury risks in good news. If there’s a problem, say it first. “We’re on track for launch, but I’m worried about the payment integration” is better than three paragraphs about progress followed by a buried concern.&lt;/p&gt;

&lt;p&gt;Quantify when you can. “The team is stressed” is vague. “Three people have worked weekends for the last month and one mentioned looking at other jobs” is specific and something they can act on.&lt;/p&gt;

&lt;p&gt;Provide options, not just problems. When raising an issue, come with thoughts on how to address it. “We have a problem” puts the burden on your manager. “We have a problem, and here are three ways we could handle it” starts a useful conversation.&lt;/p&gt;

&lt;p&gt;Know what they’ll get asked. Your manager gets questioned by their manager, by stakeholders, by other teams. Think about what questions they might face and give them the answers. This isn’t manipulation; it’s helping them help you.&lt;/p&gt;

&lt;h2 id=&quot;advocating-for-your-team&quot;&gt;Advocating for Your Team&lt;/h2&gt;

&lt;p&gt;Part of managing up is making sure your team gets what they need. Resources, recognition, protection from unreasonable demands. Your manager can provide these things, but only if you make the case.&lt;/p&gt;

&lt;p&gt;Be specific about needs. “We need more people” is a weak argument. “We need a senior backend engineer because the team has no one who can work on the payment system, and that’s blocking three features on the roadmap” is a case your manager can take to their peers.&lt;/p&gt;

&lt;p&gt;Connect to business outcomes. Leadership cares about business results. Frame your asks in terms of what the business gets. Not “we need to pay down technical debt” but “this technical debt is causing two hours of incident response per week and will cause an outage within six months.”&lt;/p&gt;

&lt;p&gt;Pick your battles. You can’t fight for everything. Decide what matters most and focus your energy there. Constant requests wear down your influence.&lt;/p&gt;

&lt;p&gt;Acknowledge constraints. Your manager operates within limits you don’t always see. Budget, headcount, political realities. Acknowledge these when making requests. It shows you understand their position and makes you easier to work with.&lt;/p&gt;

&lt;h2 id=&quot;influencing-without-authority&quot;&gt;Influencing Without Authority&lt;/h2&gt;

&lt;p&gt;Tech leads often need to influence decisions they don’t control. Architectural standards, team processes, resource allocation. You can’t mandate these things, but you can shape them.&lt;/p&gt;

&lt;p&gt;Build credibility first. Your influence comes from your track record. Deliver consistently, show good judgment, follow through on commitments. People listen to people they trust. This is especially true in remote companies where you need to &lt;a href=&quot;/getting-in-the-room-at-remote-company&quot;&gt;get in the room&lt;/a&gt; through consistent, visible contribution.&lt;/p&gt;

&lt;p&gt;Understand the decision-maker. What does your manager care about? What pressures are they under? What would make them look good to their manager? Frame your proposals in terms of their priorities.&lt;/p&gt;

&lt;p&gt;Make it easy to say yes. Do the work upfront. Don’t bring a vague idea; bring a proposal with options and trade-offs already thought through. The easier you make the decision, the more likely you get what you want.&lt;/p&gt;

&lt;p&gt;Accept no gracefully. Sometimes the answer is no. Accept it without resentment, understand why, and move on. How you handle no affects whether you get yes next time.&lt;/p&gt;

&lt;h2 id=&quot;common-mistakes&quot;&gt;Common Mistakes&lt;/h2&gt;

&lt;p&gt;Assuming they know. If you haven’t explicitly told your manager something, assume they don’t know. They have too much going on to guess problems from hints.&lt;/p&gt;

&lt;p&gt;Waiting too long. Bad news doesn’t improve with age. Tell your manager about problems early, when they’re still manageable. Surprising them with a crisis destroys trust.&lt;/p&gt;

&lt;p&gt;Only communicating when you need something. If your manager only hears from you when there’s a problem or a request, the relationship becomes transactional. Share wins, interesting things you noticed, things you’ve learned.&lt;/p&gt;

&lt;p&gt;Complaining without solutions. Anyone can spot problems. What separates good tech leads is coming with options. Even if you don’t know the answer, show that you’ve thought about it.&lt;/p&gt;

&lt;p&gt;Going around your manager. If you disagree with a decision, escalating around your manager is almost never the right move. It damages trust for good. If you must escalate, tell them first.&lt;/p&gt;

&lt;h2 id=&quot;the-underlying-principle&quot;&gt;The Underlying Principle&lt;/h2&gt;

&lt;p&gt;Managing up isn’t a separate activity from your job. It’s how you make your job possible. Your manager controls resources, shields you from nonsense, and advocates for your team in rooms you’re not in. They can only do these things if you give them what they need.&lt;/p&gt;

&lt;p&gt;Think of it as a partnership. You bring ground-level reality. They bring organisational context. Together, you can navigate problems neither could handle alone.&lt;/p&gt;
</description>
				<pubDate>Tue, 10 Feb 2026 08:00:00 +0000</pubDate>
				<link>https://joshhornby.com/managing-up-as-tech-lead</link>
				<guid isPermaLink="true">https://joshhornby.com/managing-up-as-tech-lead</guid>
			</item>
		
			<item>
				<title>Working with Designers</title>
        		<description>&lt;blockquote&gt;
  &lt;p&gt;This post is part of my &lt;a href=&quot;/tags/tech-lead&quot;&gt;Tech Lead Series&lt;/a&gt;, a collection of practical advice for engineers stepping into leadership roles.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A designer once handed me a Figma file with a custom animation for a loading spinner. It was beautiful. It was also going to take a week to implement, on a feature that needed to ship in three days. When I asked if we could use a simpler animation, she was visibly frustrated. “Why do I bother making designs if engineering just ignores them?”&lt;/p&gt;

&lt;p&gt;She wasn’t wrong to be frustrated. I wasn’t wrong about the constraints. We were both failing at the same thing: collaborating early enough that this conversation never needed to happen.&lt;/p&gt;

&lt;h2 id=&quot;the-core-tension&quot;&gt;The Core Tension&lt;/h2&gt;

&lt;p&gt;Engineering and design optimise for different things. Design optimises for user experience: how it looks, how it feels, whether it delights. Engineering optimises for what we can build: can we do this reliably, keep it working, within our constraints?&lt;/p&gt;

&lt;p&gt;Both matter. Neither is more important. But they pull in different directions.&lt;/p&gt;

&lt;p&gt;This tension isn’t a problem to solve. It’s a dynamic to manage. The best products come from holding both perspectives in balance. That requires a relationship where you can have honest conversations about trade-offs without it feeling like a battle.&lt;/p&gt;

&lt;h2 id=&quot;the-product-trio&quot;&gt;The Product Trio&lt;/h2&gt;

&lt;p&gt;Marty Cagan popularised the idea of the Product Trio: product manager, designer, and tech lead working together as equal partners. Each owns a different dimension of the problem.&lt;/p&gt;

&lt;p&gt;The PM owns what problem to solve and why it matters. The designer owns how the solution works for the user. The tech lead owns how it gets built. All three perspectives are needed to build something valuable, usable, and possible.&lt;/p&gt;

&lt;p&gt;The key insight is shared ownership rather than hand-offs. Strong teams make decisions together while exploring the problem, not one after another during the build. When one role dominates, teams build the wrong thing, build the right thing poorly, or fail to ship at all.&lt;/p&gt;

&lt;p&gt;I covered &lt;a href=&quot;/working-with-product-managers&quot;&gt;working with product managers&lt;/a&gt; earlier in this series. The same principles apply here: early involvement, shaping each other’s work, productive disagreement. The trio works when all three roles respect what the others bring.&lt;/p&gt;

&lt;h2 id=&quot;collaborate-early&quot;&gt;Collaborate Early&lt;/h2&gt;

&lt;p&gt;The spinner story happened because I wasn’t involved until the design was finished. By then, the designer had invested time and creative energy. Asking for changes felt like rejection.&lt;/p&gt;

&lt;p&gt;Early involvement prevents this. When you’re in the conversation from the start, constraints are shared information, not late surprises. The design evolves with what’s possible in mind.&lt;/p&gt;

&lt;p&gt;What early involvement looks like:&lt;/p&gt;

&lt;h3 id=&quot;join-the-problem-definition&quot;&gt;Join the problem definition&lt;/h3&gt;

&lt;p&gt;Before wireframes exist, there’s a problem being explored. Be in that conversation. Your technical knowledge shapes what solutions are even possible.&lt;/p&gt;

&lt;h3 id=&quot;share-constraints-upfront&quot;&gt;Share constraints upfront&lt;/h3&gt;

&lt;p&gt;If the team is using a design system with limited components, say so. If there’s a performance budget that rules out certain approaches, mention it. Let the designer work with real constraints rather than discovering them later.&lt;/p&gt;

&lt;h3 id=&quot;review-rough-ideas-not-polished-designs&quot;&gt;Review rough ideas, not polished designs&lt;/h3&gt;

&lt;p&gt;Looking at early sketches is more useful than seeing final mockups. Changes are easier and feel less personal when the design is still rough.&lt;/p&gt;

&lt;h3 id=&quot;pair-on-complex-interactions&quot;&gt;Pair on complex interactions&lt;/h3&gt;

&lt;p&gt;For anything involving animation, state transitions, or complex data, sit together and work through it. The designer understands what they want to achieve; you understand what’s achievable. Meet in the middle.&lt;/p&gt;

&lt;h2 id=&quot;having-the-pushback-conversation&quot;&gt;Having the Pushback Conversation&lt;/h2&gt;

&lt;p&gt;Sometimes you need to say no, or at least “not like this.” These conversations go badly when they feel like engineering vetoing design. They go well when they feel like problem-solving together.&lt;/p&gt;

&lt;h3 id=&quot;start-with-understanding&quot;&gt;Start with understanding&lt;/h3&gt;

&lt;p&gt;Before explaining why something is hard, understand why it was designed that way. “Help me understand what this animation is trying to achieve” opens a conversation. “We can’t do this animation” closes it.&lt;/p&gt;

&lt;h3 id=&quot;explain-the-cost-not-just-the-answer&quot;&gt;Explain the cost, not just the answer&lt;/h3&gt;

&lt;p&gt;“This will take two weeks” is more useful than “we can’t do this.” It lets the designer weigh the trade-off themselves. Sometimes they’ll decide it’s worth it. Sometimes they’ll find another way.&lt;/p&gt;

&lt;h3 id=&quot;offer-alternatives&quot;&gt;Offer alternatives&lt;/h3&gt;

&lt;p&gt;Don’t just spot problems; bring solutions. “We can’t do this custom animation, but here’s what we can do with our existing library” gives the designer something to work with.&lt;/p&gt;

&lt;h3 id=&quot;pick-your-battles&quot;&gt;Pick your battles&lt;/h3&gt;

&lt;p&gt;Not every gap from the spec matters equally. A button that’s slightly smaller than the mockup probably isn’t worth fighting about. An interaction that breaks accessibility is. Focus your pushback on things that actually matter.&lt;/p&gt;

&lt;h3 id=&quot;never-say-just-when-it-isnt&quot;&gt;Never say “just” when it isn’t&lt;/h3&gt;

&lt;p&gt;Dismissing design decisions as unimportant (“it’s just a colour”) damages trust. If you’re pushing back, do it respectfully. If you don’t understand why something matters, ask.&lt;/p&gt;

&lt;h2 id=&quot;the-pixel-perfect-debate&quot;&gt;The Pixel Perfect Debate&lt;/h2&gt;

&lt;p&gt;Every tech lead eventually has the “pixel perfect” conversation. The designer wants the implementation to match their mockup exactly. Engineering is shipping something close enough. Both sides get frustrated.&lt;/p&gt;

&lt;p&gt;Here’s the reality: perfect implementation of every design detail is expensive. Engineering time spent matching mockups exactly is time not spent on other features. But designs exist for a reason. Visual consistency matters. User experience details matter.&lt;/p&gt;

&lt;p&gt;The answer isn’t all-or-nothing. It’s deciding together what matters.&lt;/p&gt;

&lt;p&gt;Some things should be pixel perfect:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Spacing and alignment in core UI components&lt;/li&gt;
  &lt;li&gt;Brand colours and typography&lt;/li&gt;
  &lt;li&gt;Key user-facing screens like onboarding or checkout&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some things don’t need to be:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Internal tools or admin screens&lt;/li&gt;
  &lt;li&gt;Early iterations that will change based on feedback&lt;/li&gt;
  &lt;li&gt;Features with low usage or visibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Have the conversation explicitly. “For this feature, what’s the priority: speed or polish?” Get agreement before work starts, not after it ships.&lt;/p&gt;

&lt;h2 id=&quot;building-the-relationship&quot;&gt;Building the Relationship&lt;/h2&gt;

&lt;p&gt;Like all relationships, this one requires upkeep.&lt;/p&gt;

&lt;h3 id=&quot;learn-their-tools&quot;&gt;Learn their tools&lt;/h3&gt;

&lt;p&gt;You don’t need to master Figma, but understanding how design files work helps. You can export assets correctly, read specs, and ask good questions.&lt;/p&gt;

&lt;h3 id=&quot;share-your-tools&quot;&gt;Share your tools&lt;/h3&gt;

&lt;p&gt;Help designers understand how the codebase works, what reusable components exist, what’s easy versus hard. This knowledge makes their designs easier to build.&lt;/p&gt;

&lt;h3 id=&quot;give-them-access&quot;&gt;Give them access&lt;/h3&gt;

&lt;p&gt;Let designers see the staging environment, use the product, experience what users experience. Bugs and gaps are easier to discuss when everyone can see them.&lt;/p&gt;

&lt;h3 id=&quot;celebrate-their-work&quot;&gt;Celebrate their work&lt;/h3&gt;

&lt;p&gt;When a design makes the product better, say so. Designers often only hear from engineering when there’s a problem. Positive feedback builds the relationship.&lt;/p&gt;

&lt;h3 id=&quot;include-them-in-retrospectives&quot;&gt;Include them in retrospectives&lt;/h3&gt;

&lt;p&gt;If a feature shipped with design compromises, talk about why together. What could you do differently next time to avoid late-stage pushback?&lt;/p&gt;

&lt;h2 id=&quot;the-goal&quot;&gt;The Goal&lt;/h2&gt;

&lt;p&gt;The goal isn’t engineering and design agreeing on everything. It’s both sides understanding each other well enough to have productive disagreements. To argue about the right approach because you’re both trying to build something great, not because you’re protecting your territory.&lt;/p&gt;

&lt;p&gt;This takes time. It requires you to see design as a partner in building good products, not a department that hands you requirements. When the relationship is right, you’ll find that the frustrating conversations become interesting problems to solve together.&lt;/p&gt;
</description>
				<pubDate>Fri, 06 Feb 2026 08:00:00 +0000</pubDate>
				<link>https://joshhornby.com/working-with-designers</link>
				<guid isPermaLink="true">https://joshhornby.com/working-with-designers</guid>
			</item>
		
			<item>
				<title>Working with Product Managers</title>
        		<description>&lt;blockquote&gt;
  &lt;p&gt;This post is part of my &lt;a href=&quot;/tags/tech-lead&quot;&gt;Tech Lead Series&lt;/a&gt;, a collection of practical advice for engineers stepping into leadership roles.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Early in my career, I worked with a PM who would appear on Monday mornings with a fully formed spec, hand it to the team, and disappear until the demo. The specs were detailed, complete with wireframes and acceptance criteria. They were also frequently wrong. By the time we’d built what was specified, the requirements had changed, or we’d discovered technical constraints that made the approach unworkable.&lt;/p&gt;

&lt;p&gt;Years later, I worked with a PM who did the opposite. She’d share half-formed ideas before they were ready, ask for technical input on problems we didn’t understand yet, and change direction based on our feedback. It felt chaotic. It was also the most productive team I’ve ever been on.&lt;/p&gt;

&lt;p&gt;The difference wasn’t the PMs. It was the relationship.&lt;/p&gt;

&lt;h2 id=&quot;the-partnership-model&quot;&gt;The Partnership Model&lt;/h2&gt;

&lt;p&gt;In healthy teams, the tech lead and PM operate as partners, not as spec-writer and spec-implementer. Both own the outcome. Both contribute to the solution. The boundaries are fuzzy by design.&lt;/p&gt;

&lt;p&gt;The PM brings customer understanding, market context, and business priorities. The tech lead brings technical constraints, implementation options, and system knowledge. Neither has the full picture alone.&lt;/p&gt;

&lt;p&gt;This sounds obvious, but it’s not how many teams actually work. The default mode is handoff: PM decides what, engineering decides how. This creates problems that compound over time.&lt;/p&gt;

&lt;p&gt;When you’re not involved in the “what”, you build solutions to the wrong problems. When the PM isn’t involved in the “how”, they specify things that are expensive or impossible. The handoff model optimises for clean boundaries at the cost of good outcomes.&lt;/p&gt;

&lt;h2 id=&quot;what-good-looks-like&quot;&gt;What Good Looks Like&lt;/h2&gt;

&lt;h3 id=&quot;early-involvement&quot;&gt;Early involvement&lt;/h3&gt;

&lt;p&gt;You’re in the conversation when problems are being defined, not just when solutions are being specified. You hear about customer feedback before it becomes a Jira ticket.&lt;/p&gt;

&lt;h3 id=&quot;mutual-influence&quot;&gt;Mutual influence&lt;/h3&gt;

&lt;p&gt;The PM changes their approach based on your technical input. You change your approach based on their customer insight. Neither person’s opinion automatically wins.&lt;/p&gt;

&lt;h3 id=&quot;shared-ownership&quot;&gt;Shared ownership&lt;/h3&gt;

&lt;p&gt;When something ships and doesn’t work, you both own the failure. When something succeeds, you both take credit. The “that’s not my job” reflex is gone.&lt;/p&gt;

&lt;h3 id=&quot;productive-disagreement&quot;&gt;Productive disagreement&lt;/h3&gt;

&lt;p&gt;You argue about the right approach, sometimes heatedly. But you argue in service of the outcome, not to win. And you reach decisions both can commit to.&lt;/p&gt;

&lt;h3 id=&quot;trust-in-absence&quot;&gt;Trust in absence&lt;/h3&gt;

&lt;p&gt;When the PM makes a call without you, you trust it was reasonable. When you make a technical decision that affects the product, they trust you thought about the user impact.&lt;/p&gt;

&lt;h2 id=&quot;building-the-relationship&quot;&gt;Building the Relationship&lt;/h2&gt;

&lt;p&gt;This partnership doesn’t happen by itself. You have to build it.&lt;/p&gt;

&lt;h3 id=&quot;schedule-regular-time-together&quot;&gt;Schedule regular time together&lt;/h3&gt;

&lt;p&gt;A weekly sync, just the two of you. Not to review tickets, but to discuss what’s coming, what’s unclear, what’s worrying each of you. This prevents surprises and builds understanding.&lt;/p&gt;

&lt;h3 id=&quot;learn-their-world&quot;&gt;Learn their world&lt;/h3&gt;

&lt;p&gt;Sit in customer calls. Read the support tickets. Understand what success looks like from their view. The more you understand their context, the better your technical decisions will be. This is what separates &lt;a href=&quot;/product-engineers&quot;&gt;product engineers&lt;/a&gt; from those who just build to spec.&lt;/p&gt;

&lt;h3 id=&quot;share-your-constraints&quot;&gt;Share your constraints&lt;/h3&gt;

&lt;p&gt;Don’t let technical debt, system limits, or team capacity be invisible. If something is expensive or risky, say so early. Give them the information they need to make good trade-offs.&lt;/p&gt;

&lt;h3 id=&quot;ask-why-not-just-what&quot;&gt;Ask why, not just what&lt;/h3&gt;

&lt;p&gt;When a feature request arrives, understand the problem it’s solving. Often there’s a simpler solution once you understand the actual need. Sometimes the problem isn’t worth solving at all.&lt;/p&gt;

&lt;h3 id=&quot;offer-alternatives&quot;&gt;Offer alternatives&lt;/h3&gt;

&lt;p&gt;If you can’t do what they’re asking, don’t just say no. Come back with options. “We can’t do X, but we could do Y which addresses most of the same need in half the time.”&lt;/p&gt;

&lt;h2 id=&quot;when-to-push-back&quot;&gt;When to Push Back&lt;/h2&gt;

&lt;p&gt;Part of the tech lead’s job is saying no. Not to block things, but to be helpful. Some situations that call for pushback:&lt;/p&gt;

&lt;h3 id=&quot;when-the-complexity-isnt-worth-the-value&quot;&gt;When the complexity isn’t worth the value&lt;/h3&gt;

&lt;p&gt;A feature that takes three months to build but helps 2% of users might not be the right investment. You have visibility into the cost that the PM might not have.&lt;/p&gt;

&lt;h3 id=&quot;when-theres-a-better-solution&quot;&gt;When there’s a better solution&lt;/h3&gt;

&lt;p&gt;If you can solve the same problem more simply, make the case. PMs aren’t married to their specs; they’re married to solving customer problems.&lt;/p&gt;

&lt;h3 id=&quot;when-quality-will-suffer&quot;&gt;When quality will suffer&lt;/h3&gt;

&lt;p&gt;Rushing to hit a deadline by cutting corners creates debt that slows future work. Sometimes the right answer is to adjust scope or timeline rather than sacrifice quality.&lt;/p&gt;

&lt;h3 id=&quot;when-the-team-is-overloaded&quot;&gt;When the team is overloaded&lt;/h3&gt;

&lt;p&gt;You see the strain on your team more clearly than anyone. If people are burning out or cutting corners because there’s too much to do, push for sustainable pace.&lt;/p&gt;

&lt;p&gt;The key is how you push back. “No” is rarely useful. “Here’s what I’m seeing and here are some options” starts a conversation.&lt;/p&gt;

&lt;h2 id=&quot;common-failure-modes&quot;&gt;Common Failure Modes&lt;/h2&gt;

&lt;h3 id=&quot;the-adversarial-relationship&quot;&gt;The adversarial relationship&lt;/h3&gt;

&lt;p&gt;Tech lead and PM view each other as obstacles. Engineering thinks product doesn’t understand technical constraints. Product thinks engineering always overcomplicates things. Both are probably right, and both are making it worse.&lt;/p&gt;

&lt;h3 id=&quot;the-absent-pm&quot;&gt;The absent PM&lt;/h3&gt;

&lt;p&gt;The PM is spread across too many teams, so they drop off specs and disappear. Engineering makes product decisions they shouldn’t be making alone, and the PM is surprised by what gets built.&lt;/p&gt;

&lt;h3 id=&quot;the-spec-following-tech-lead&quot;&gt;The spec-following tech lead&lt;/h3&gt;

&lt;p&gt;The tech lead treats specs as requirements to build rather than problems to solve. They don’t push back, don’t offer alternatives, don’t engage with the product thinking.&lt;/p&gt;

&lt;h3 id=&quot;the-scope-creeping-conversation&quot;&gt;The scope-creeping conversation&lt;/h3&gt;

&lt;p&gt;Every discussion about how to build becomes a negotiation about adding features. What started as a simple improvement becomes a three-month project because neither person knows how to say “not now.”&lt;/p&gt;

&lt;h2 id=&quot;the-underlying-principle&quot;&gt;The Underlying Principle&lt;/h2&gt;

&lt;p&gt;The best tech lead-PM relationships share one thing: both people are trying to solve the same problem rather than protect their territory.&lt;/p&gt;

&lt;p&gt;When the PM sees engineering as a partner in solving customer problems, they share context generously and welcome technical input. When the tech lead sees product as a partner in building good software, they engage with customer needs rather than just technical specifications.&lt;/p&gt;

&lt;p&gt;This requires trust, which requires time. You won’t have this relationship on day one. But you can start building it by showing up as a partner rather than an implementer, by engaging with the product thinking rather than just the tickets, by treating the PM’s success as your own.&lt;/p&gt;
</description>
				<pubDate>Tue, 03 Feb 2026 08:00:00 +0000</pubDate>
				<link>https://joshhornby.com/working-with-product-managers</link>
				<guid isPermaLink="true">https://joshhornby.com/working-with-product-managers</guid>
			</item>
		
	</channel>
</rss>
