Author: Sanmi

  • Why we stopped tracking hours

    Why we stopped tracking hours

    We killed the timesheet in March. Nobody misses it. What replaced it made our finance team nervous for about six weeks, and then it stopped making them nervous. Here is what happened.

    What we track now

    We stopped asking engineers, designers, and PMs to log hours against project codes in Harvest. It rewarded the wrong thing: presence. A person could sit on a Linear ticket for three days, log 24 hours to it, and produce nothing shippable. The timesheet said the work was funded. The product said otherwise.

    We now track two things per squad, weekly, in a Notion database that pulls from Linear and GitHub:

    • Shipped units of work: tickets that closed the loop, meaning code merged, feature flagged on for at least one real customer, and no rollback within seven days.
    • Cycle time: median days from “In Progress” to “Shipped” on Linear, per squad, broken out by ticket size (S, M, L).

    That is it. No story points. No hours. If a squad shipped four medium tickets in a week with a cycle time of 3.2 days, that is the artifact. We review it every Thursday in a 25 minute meeting called Ship Review.

    What finance pushed back on

    Our CFO’s first question was fair: how do we capitalize engineering costs for the R&D credit if nobody is logging hours to projects? HMRC wants a defensible allocation. Auditors want a paper trail. “The vibes were good on Thursday” is not a paper trail.

    Her second question was harder. If a contractor bills us for 40 hours and we have no internal hours to compare against, how do we know we are not being overcharged?

    What we told them

    We proposed a trade. Finance gets a defensible model. Squads keep their calendars.

    1. Every squad has a fixed roster, and every roster maps to one or two product areas in a Notion table. Payroll cost per squad is known. That is our allocation basis, reviewed quarterly.
    2. Contractors still log hours in Harvest, because they bill by the hour. Employees do not, because they do not.
    3. The Ship Review output feeds a monthly note to finance: shipped units, cycle time trend, and any squad where cycle time doubled without a headcount change. That flags stuck work faster than a timesheet ever did.

    Six months in, our R&D claim went through without a query. Cycle time on our payments squad dropped from 8.1 days to 4.4. Nobody has asked to bring hours back.

    A timesheet measures whether you showed up. A shipped ticket measures whether the customer got something.

    We know which one we would rather report on.

  • Salary transparency, eighteen months in

    Salary transparency, eighteen months in

    In January of last year, we pushed a Notion page live to the whole company. It listed every role at Velo, the band it sat in, the floor, the ceiling, and the multiplier we used to convert level to base. We wrote about it on a Wednesday afternoon in Slack. By Friday, three people had asked for a chat.

    Eighteen months later, that page is still up. Some of what we expected happened. Most of what mattered did not. We wanted to write down what we published, what we held back, and one thing that broke.

    What we put on the page

    The Notion doc has six tabs. It contains everything a new hire or a curious teammate would ask us in a one to one:

    • Level definitions for engineering, design, product, and go to market, with three example projects per level.
    • Base salary bands for every level, in GBP and EUR, refreshed each January using Radford and our own market pulls.
    • The formula that turns level into offer: floor plus a location factor, capped at the ceiling.
    • Equity grants as a range of shares, plus a plain English note on our 409A and the strike price at the last round.
    • The rubric we use in promotion committee, including the four criteria and how we score them.
    • A change log at the bottom, so people can see when a band moved and why.

    We linked it from the onboarding checklist in Linear and pinned it in the #comp channel. New hires get a Loom walkthrough on day two.

    What we did not publish

    We were honest with ourselves about the gaps. A few things stayed off the page on purpose:

    • Individual salaries. We publish bands, not names. Two teammates asked for a fully open sheet in month four. We said no, and wrote a short doc explaining why.
    • Bonus targets for the two people on commission. Their plans are bespoke, and putting a range on them would have misled the rest of the team.
    • The one off retention grants we handed out during a rough patch last summer. Three people got them. We told each of them the same thing: this is not part of the standard rubric.
    • Founder salaries for the first year. We added those in month nine, after a Datadog outage weekend where the on call engineer asked, in a Slack DM, whether the founders were still on a discount. It was a fair question and we should have answered it sooner.

    We are still uneasy about the retention grants. If you publish a rubric and then quietly step outside it, you owe people an explanation. The next time we do one, it will show up in the change log with a category, even if the individual amounts do not.

    The hire that fell through

    In April, we were close to signing a senior backend engineer out of Berlin. She had spent an hour on a Figma review with our design lead and shipped a small GitHub PR against a starter repo. The team was ready.

    Our offer was the top of the L5 band. She had a competing offer that was twenty two percent higher, from a US company hiring in Europe. She sent us a screenshot of the other letter. We said no to the counter, because raising for one hire would have blown a hole through the band we had just published.

    She took the other job. It was the right call and it stung. If our page had been private, we would have found a way to say yes and moved on. Publishing the numbers meant we could not privately break our own rules without someone finding out on their first day. That constraint cost us a good hire.

    The point of writing something down is that it becomes harder to walk away from. That cuts both ways.

    The quiet compression

    The thing we did not predict was what happened to the bands themselves.

    Before publishing, our L4 engineering range spanned roughly forty percent floor to ceiling. Managers used the full width. Two people at the same level, hired six months apart, could sit twelve thousand pounds apart based on how the negotiation went.

    Once the numbers were visible, the top of the band became a promise. Anyone below the midpoint asked, reasonably, why. Over three review cycles, the width of that band shrank to about eighteen percent. Not because we changed the policy on paper, but because managers stopped defending the low end and stopped offering the high end without a clear promotion signal.

    The average went up. The variance went down. Our comp spend for the L4 cohort grew by nine percent year over year, and we lost the flexibility we used to have to reward a rare skill without moving someone a level.

    We are not sorry about it. It made every one to one about pay shorter, and it turned promotion committee, which meets the second Thursday of each month, into an argument about evidence rather than an argument about numbers. Two people got promoted last cycle who would have been talked out of it under the old system, because the manager could no longer say, quietly, that they were already paid like the next level.

    What we would do again

    If we were starting over, we would publish sooner and narrower. Wider bands look generous on paper. They also give managers room to make decisions they cannot defend in daylight. We would rather have the argument up front.

    The Notion page is still open in a tab on our screens most Fridays. It has not fixed hiring. It has made the conversation we have about pay a smaller, calmer thing. Given where we started, that is enough.

  • How we run the same customer interview every quarter

    How we run the same customer interview every quarter

    Every quarter, on the second Wednesday of the last month, we sit down with three Velo customers and ask them the same eight questions we asked last quarter. Same script. Same order. Roughly the same time of day. The value is not in any one conversation. The value shows up when we diff the transcripts.

    The script we do not change

    The temptation to tweak questions is strong, and we resist it. Once you edit a question, you cannot compare answers across quarters. Our fixed eight, in order:

    • Walk us through the last thing you did in Velo this morning.
    • What did you almost use Velo for, and then use something else?
    • Which tab do you open first when you log in?
    • Who else at your company touches Velo, and how?
    • What have you built around Velo that we did not ship?
    • When was the last time Velo surprised you, good or bad?
    • If we shut down tomorrow, what would you replace us with?
    • What are you doing on the days you do not open Velo?

    Three users, chosen from a rotating pool of twelve power accounts. One customer we keep constant across the year for a longitudinal read. Two rotate.

    The diff is the deliverable

    We record with Grain, get transcripts back within the hour, and drop them into a Notion database with one row per user per quarter. On Friday we run a working session with product, design, and one engineer. The rule for that session: no talking about individual quotes until we have compared the same question across quarters.

    What we look for in the diff:

    1. Words that appear this quarter and did not appear last quarter. Q1 nobody said “audit.” In Q2, all three did.
    2. The “almost used Velo for” answer shifting toward one workflow. Two quarters in a row of the same near-miss becomes a Linear ticket in the next planning cycle.
    3. Tab-first answers drifting away from the tab we consider the home screen. That happened to Reports in Q3 last year and pushed the nav redesign.
    4. Homegrown scripts built around us. Every quarter someone shows us a Google Sheet stitched to our API. That sheet is the roadmap.

    The interview does not tell us what to build. The diff between interviews does.

    What we do with it

    By the following Monday, the working session produces a one page memo in Notion with three sections: patterns that hardened, patterns that softened, and one thing we were wrong about. That memo goes to the whole company in Slack, and the three tickets it generates land in the top of the next sprint. That is the whole loop. Eight questions, three users, one diff, one memo, three tickets.

  • Kill the quarterly roadmap, keep the direction

    Kill the quarterly roadmap, keep the direction

    Two years ago we kept a proper roadmap. Quarterly planning weeks, OKR drafts in Notion, a review meeting every other Friday, and a Linear project called Q3 Commitments that we updated on Tuesdays and lied about on Fridays. We shipped things. We also spent about six engineering weeks per quarter arguing over rows in a spreadsheet.

    We stopped doing that in March. What replaced it: a single Notion page called Next six months, in rough order. It fits on one screen. It has no dates past the current month, no confidence percentages, and no owner column.

    What the page looks like

    Three sections, always:

    1. Now. Two or three things we are building this week and next. Each links to a Linear ticket.
    2. Soon. Four to eight things we intend to start inside the next quarter. Ordered top to bottom by priority. No dates.
    3. Later, probably. Bets we think matter for the six month horizon but have not committed to. Read the room, not the calendar.

    The page has a Last edited line at the top. If it has been more than three weeks, someone owes the team an update on Slack.

    What we killed with it

    • The quarterly planning offsite. We used to lose a Thursday and half a Friday to it.
    • The OKR grading meeting. Nobody misses it.
    • The roadmap in Figma that lived in parallel to the Linear board and disagreed with it by week three.
    • The reforecast conversation, where we pretended a plan drafted in January still described reality in April.

    We kept the direction. The company still has a one paragraph statement of what we are building over the next 18 months. It sits at the top of the same page. Every commit against Now and Soon should be defensible against that paragraph.

    Why the world changing is fine

    When a competitor ships something surprising, or a big customer hits a wall, or Datadog tells us the ingestion pipeline is cracking, we open the page and reorder it. That takes an hour, not a planning cycle. The team sees the change on Slack the same day. Nobody asks whether we are missing a Q3 objective, because there is no Q3 objective to miss.

    The trade we made: we gave up the theatre of certainty. We got back the six weeks a year we used to spend rehearsing it.

  • The PRD template we abandoned

    The PRD template we abandoned

    For eighteen months, every feature at Velo started the same way. Someone opened a Notion page called “PRD Template v4,” scrolled past six pages of headings, and started typing. Problem statement. Goals. Non-goals. Success metrics. User stories. Edge cases. Rollout plan. Open questions. Appendix.

    By the time the doc reached our Thursday product review, it was 2,400 words of dutiful prose. When we asked what the user would do differently after we shipped, the room went quiet.

    What the template was hiding

    We reread twelve PRDs from Q1 and Q2. A pattern showed up in the verbs.

    • “Improve onboarding” appeared in four docs.
    • “Optimise the flow” showed up in five.
    • “Reduce friction” was in nine of the twelve.
    • “Increase engagement” closed out most success sections.

    None of these verbs pointed at anything a human could do or fail to do. They were placeholders for thinking, and the template rewarded us for placing them. Six pages of scaffolding meant six pages to fill, and the easiest way to fill six pages was to reach for words that meant almost anything.

    The reviews got worse in a specific way. Engineers would ask a sharp question on page one, and by page five we had lost the thread. Design would flag an interaction we had not considered, and someone would promise to “cover it in the appendix.” The appendix became a graveyard. In one memorable case, a Linear ticket sat in Backlog for eleven weeks because the PRD had three conflicting definitions of “activated user” and nobody wanted to be the person to pick one.

    We had built a document that made disagreement expensive to surface.

    The one-page brief

    In August we tried something smaller. Three prompts, one page, hard cap at 400 words.

    1. Problem. What is going wrong for a specific person on a specific day? Name the person by role, not persona.
    2. Target user. Who feels this problem most acutely, and which of them do we have access to for interviews within the next week?
    3. First observable behaviour. What will this person do after we ship that they cannot do or would not do today? Describe the action, not the outcome.

    That third prompt did most of the work. “Increase engagement” fails it. “The ops lead exports the reconciliation report as a CSV before their Monday standup” passes it. You can watch a person do that or not do it. You can ask them why they did not. You can count it in Datadog.

    We wrote the first brief for a billing export feature. It took forty minutes. The Thursday review took nineteen. Two engineers pushed back on the target user selection, we agreed to run three interviews before writing a line of code, and the ticket moved to In Progress the following Tuesday.

    What we gained

    The obvious thing was time. Our median PRD used to take a product manager somewhere between four and seven hours to draft, plus another two in edits after review. The brief takes forty to ninety minutes. Reviews compressed from an hour to under thirty because there was nowhere to hide vague thinking.

    The less obvious thing was that engineers started reading the doc. When something is six pages of prose, the second engineer on a project skims. When it is one page and the third bullet is a specific behaviour, they read it, and they argue. We stopped confusing “we have a PRD” with “we know what we are building.” Writing a brief did not feel like progress in the way writing a six-page PRD had felt like progress. That turned out to be honest.

    What we lost

    We lost some things we miss. The old PRD forced us to enumerate edge cases up front, and the brief does not. Two of our first four briefs shipped with a rollback plan improvised in a Slack thread the morning of launch. We have since added a lightweight “what breaks” checklist that lives next to the brief but is not part of it, and reviews now include a five-minute walkthrough of that checklist.

    We also lost some institutional memory. A six-page PRD, for all its flaws, was a decent artefact for the person joining the team six months later. Our briefs are terse enough that a new engineer opening one in GitHub cannot always reconstruct why we made the calls we did. We are experimenting with a short decisions log per feature, kept in the same Notion database, updated as we go. It is not a Figma spec and it is not a design review transcript, but it stops the “why did we do it this way” question from getting answered with a shrug.

    What we would tell you

    If your PRD template has more than three prompts, ask the last five people who filled one in whether the extra prompts changed a single decision. If the answer is no, cut them. The template is not neutral. It teaches your team what counts as thinking.

    The verbs are the tell. If your docs are full of them and nobody has flinched in a review this quarter, the template is doing the flinching for you.

  • The debugging log we always regret losing

    The debugging log we always regret losing

    Every engineer on our team keeps a file called bug.md or scratch.txt or something equally forgettable. It lives in a folder that never gets checked into git. Nobody told us to make it. We all did anyway, because the third time a hard bug ate a full Thursday, we figured out that memory is the worst debugging tool we own.

    The file is append-only by convention, never by tooling. New entries go at the bottom with a timestamp and whatever we were poking at when the thought landed. When the bug closes, the log stays. When a laptop dies or we forget to sync it to iCloud, we mourn. One of us lost eight months of notes last October and still brings it up in retros.

    Here are three excerpts from the past quarter, lightly sanitized, that show why we would rather lose a Figma file than lose one of these.

    Excerpt one: the phantom timeout

    Tuesday, 2:14pm. Datadog spiked p95 on the checkout service to 8.2s. No deploys in 48 hours. Two engineers pulled in from a payments meeting.

    14:16 - checked prod dashboard, only /orders POST spiking, /orders GET fine
    14:19 - pulled last 500 traces, 91% share a redis GET on session token
    14:23 - ran KEYS scan against staging, 1.2M sessions cached from Friday load test
    14:27 - Marcus reminded me eviction policy on this cluster is noeviction
    14:31 - confirmed via redis-cli config get maxmemory-policy
    14:33 - shipping maxmemory-policy allkeys-lru now, PR #4821
    14:58 - p95 back to 340ms
    

    Six lines, one closed Linear ticket, one lesson written into our runbook. If we had captured a screenshot of the Datadog spike and nothing else, we would have a picture of the pain and none of the path out. Three months from now, when p95 climbs again on a Friday afternoon, this log will be the first thing we grep.

    Excerpt two: the flaky test that was not flaky

    This one ran across five days and two engineers. The Linear ticket collected fourteen comments. The log had forty entries. Only one of them mattered.

    Wed 10:04 - test_billing_webhook_signature failing on CI, passes local
    Wed 10:22 - retried 3x on CI, one green one red one red
    Wed 11:40 - added extra logging, deployed to CI branch
    Wed 14:15 - noticed failures happen only on runners pulled after 13:00 UTC
    Thu 09:30 - runner image was rebuilt yesterday, openssl bumped to 3.2
    Thu 09:45 - openssl 3.2 changed HMAC digest default padding behavior
    Thu 10:12 - our webhook signer never set padding explicitly, tests inherited default
    Fri 16:20 - PR #4903 pins padding, backports to test util
    

    The Wednesday 14:15 note is the whole thing. It is a stray observation, not a fix. If we had been operating inside a Slack thread and a screenshot pipeline, that observation would have been buried under seventeen emoji reactions and lost forever. In the log, it sits alone on a line, waiting to be reread on Thursday morning when it finally clicks.

    Excerpt three: the null we could not explain

    Mon 08:50 - user report: dashboard renders "customer name: null" for 4 accounts
    Mon 09:10 - all 4 accounts created between Aug 3 and Aug 5
    Mon 09:12 - Aug 3 was our migration cutover date
    Mon 09:14 - checked backfill script, filters WHERE created_at < '2025-08-03'
    Mon 09:15 - accounts created ON Aug 3 fell into the gap
    Mon 09:20 - one-off patch queued, wrote regression test, added to postmortem
    

    Twenty minutes, start to finish. A screenshot would have told us the UI was broken. The log told us we shipped a boundary bug in a migration script, and reminded us to check for the same class of mistake in the next migration on the roadmap.

    Why context beats screenshots

    Screenshots capture a state. Logs capture a decision tree. When we return to a screenshot, we see what we saw. When we return to a log, we see what we tried, what we ruled out, and what surprised us. The last of those is the reason we keep writing these files.

    A short list of what belongs in the log:

    • The exact command we ran, copy-pasted with its output
    • The question we were asking when we ran it
    • What we expected and what we got instead
    • Any stray observation that felt off but was not the bug
    • The commit hash, PR number, or Linear ID at the end

    A shorter list of what does not belong:

    • Long paragraphs
    • Screenshots
    • Anything we have to format before saving

    The rule that keeps the log useful is that it must be faster to add a line than to think about not adding it. The moment it feels like writing, it stops getting written. That is why nobody on our team keeps this file in Notion. Notion asks us to name the page, pick a template, and choose an emoji. By then the thought is gone.

    We have started nudging new engineers to keep one within their first week. Not as a policy. As a warning. On the day their laptop dies, they will remember which files they miss most. This is always one of them.

  • The two week feature flag rule

    The two week feature flag rule

    Most engineering blogs will tell you feature flags are free. Ship dark, roll out slow, keep the fallback path warm. We used to believe this too. Then we counted our flags.

    Last month we had 47 flags in LaunchDarkly. Nine were older than a quarter. Four were older than us remembering why they existed. Two of them fought each other in production and caused a payout retry loop that Datadog flagged at 2:14 on a Tuesday afternoon.

    Why the “keep it around, it’s cheap” argument is wrong

    The pitch for long lived flags is that they cost nothing. A boolean check, a config entry, a line in a dashboard. The real cost shows up somewhere else:

    • Every conditional doubles the state space a new engineer has to hold in their head when touching that file.
    • Test matrices grow multiplicatively. Two stale flags plus one new one gives you eight paths, and nobody writes eight tests.
    • Old flags rot silently. The useNewCheckout branch you shipped in March has drifted from the legacy branch it lives next to, and neither of you noticed.
    • On call gets worse. When something breaks, the first question is always “what flags are on for this customer?” and the answer takes twenty minutes to assemble.

    The flag was supposed to be a valve. Left open, it becomes a fork in the codebase you have to maintain twice.

    The rule we adopted

    Every flag has an owner and a fourteen day timer. At day fourteen, the losing branch gets deleted. Not deprecated, not marked for cleanup in Linear, deleted. If we shipped the new checkout behind a flag and it stuck, the old checkout code goes. If the rollout failed, the new code goes.

    The uncomfortable part: sometimes we delete a branch that turns out to be needed later, and we rewrite it. We accept that cost. A week of rework is cheaper than a year of dual maintenance.

    The mechanics are boring. Every Monday, a GitHub Action posts the list of aged flags to our #eng-hygiene Slack channel with the owner tagged. If the flag is still needed, the owner extends it once, by another week, and writes why in the thread. Second extensions get escalated to the Friday engineering sync.

    A flag that has been on for a month is not a flag. It is a feature you forgot to finish shipping.

    Since we started, our flag count has dropped from 47 to 12. Half the incidents we traced back to “unexpected flag interaction” have stopped happening. The rework tax has been real, and worth it.

  • How twelve of us run trunk-based development

    How twelve of us run trunk-based development

    We moved to trunk-based development eighteen months ago, when the team was seven engineers and the release train was groaning under its own weight. We are twelve now, split across three squads, and every one of us commits to main multiple times a day. What follows is the routine we settled into, and the two things that caught us off guard.

    None of this is theoretical. It is what we do between our Monday planning meeting and our Friday demo, using GitHub, Linear, Datadog, and a lot of small pull requests.

    The daily rhythm

    Our workflow rests on three habits that we practice without thinking about them now. When a new engineer joins, these are the three things we teach in the first week.

    • Feature flags before feature code. Every user-visible change ships behind a flag in LaunchDarkly. The flag lands in a separate PR, sometimes an hour before the feature work starts. That way the wiring is reviewed on its own, and the rollout is a config change rather than a deploy.
    • Branches that live less than a day. Our house rule is that a branch should not sleep. If the sun sets on your branch, you either open a draft PR to get eyes on it, or you merge whatever green subset you have behind a flag. We measure this: last quarter, the median branch age was 6 hours and 42 minutes.
    • Five-minute code review triage. At 10:15 and 15:15, whoever is on review rotation opens the GitHub PR queue and gives every open PR one of three responses: approved, one specific question, or a note that the review will land by end of day. No PR is allowed to sit without a signal for more than four hours during working time.

    The triage rule is the one that took the most discipline to adopt. Two twelve-minute windows a day sounds trivial, but it forced us to break up large PRs. If you cannot skim a diff in ninety seconds, the reviewer flags it and asks for a split. We settled on a soft cap of 400 lines of diff, and a hard cap of 800.

    What we do when things break

    Trunk-based development only works if main is trustworthy. Ours is protected by a required check that runs unit tests, a smoke suite against a staging environment, and a Datadog synthetic against three critical paths. When the check goes red, whoever pushed the offending commit has two options: revert within fifteen minutes, or roll forward within thirty. We track this in a Notion page called the Green Log. Since January, we have had eleven red-main incidents, and nine of them were resolved by revert.

    We optimise for the next commit, not the current one. A revert is not a failure. A stuck main is.

    What surprised us

    We expected the obvious wins: faster feedback, smaller blast radius, less time spent on release branches. Those all showed up. What we did not predict were two second-order effects that changed how the team behaves.

    Merge conflicts became rare. We assumed twelve people pushing to one branch would create a merge conflict every few hours. In practice, the opposite happened. Because everyone rebases against a fresh main multiple times a day, conflicts surface early, when the diffs are small and the context is still in someone’s head. Our GitHub metrics show a conflict rate of roughly one PR in forty, down from one in eight when we were running two-week release branches. The conflicts we do get are almost always resolved in under ten minutes by the author.

    Design conversations moved earlier. This is the change we care about most. When branches lived for a week, most technical debate happened at review time, when the code was already written and the author was invested. Now, because a branch cannot survive a day, engineers ask for a design opinion before they open the editor. Our #eng-design Slack channel used to see two or three threads a week. It now sees eight or nine, and most of them are fifteen-minute exchanges about approach, not aesthetics. A few times a month, one of those threads turns into a Figma sketch or a Linear ticket for a spike.

    We think the mechanism is simple. Short branches make the cost of throwing away code visible. If you have to justify a day of work, you tolerate risk. If you only have to justify two hours, you ask the question first.

    What we would tell a team starting today

    If we were setting this up again from scratch, we would do these things in this order:

    1. Put LaunchDarkly, or a homegrown equivalent, in place before you change any branching rules. Without flags, small merges are frightening.
    2. Write down the review triage times and defend them. Ours are on the team calendar as recurring events.
    3. Pick a diff-size cap and enforce it socially. Numbers matter less than the shared expectation that big PRs get split.
    4. Track red-main minutes as a team metric, not an individual one. We share the Green Log in our Friday retro.

    Trunk-based development is not a productivity trick. It is a set of constraints that make the team’s default behaviour better. Eighteen months in, we are shipping about twice as often as we used to, and the conversations we have about how to build things are earlier and more useful. Those two outcomes are worth more to us than any of the workflow mechanics that produce them.

  • How we handle mid-cycle scope creep without cancelling the cycle

    How we handle mid-cycle scope creep without cancelling the cycle

    Every two-week cycle at Velo starts with a plan we believe. By Wednesday of week one, something has usually shifted. A support pattern turns into an incident. A customer contract lands with a hard integration date. A design review surfaces a flaw that means the header refactor is bigger than we scoped. The temptation, when this happens, is to either pretend nothing changed and burn the team out, or blow up the cycle and replan from scratch. We do neither.

    Instead we run a small, boring ritual every Wednesday at 11:00 called the mid-cycle trim. It takes 30 minutes, involves the tech lead and the PM for each squad, and follows the same three moves every time. Here is what we do, and why the ritual survived four quarters when almost nothing else in our process did.

    The three moves

    We treat the cycle as a fixed container. If new work goes in, existing work comes out. That constraint is the whole game. The Wednesday trim is where we honour it.

    1. Cut the two lowest-value items still open. We pull up the Linear cycle view, sort by our internal priority score, and mark the bottom two tickets as cycle: deferred. They keep their estimates and their context; they lose their promised delivery date. If a ticket is already in review, we leave it; the cost of context-switching outweighs the saving.
    2. Split the highest-value item into a smaller slice. The biggest ticket in the cycle is usually the one hiding the most risk. We ask the engineer working on it: what is the smallest piece of this that a real customer can use on Friday of week two? That becomes the new scope. The rest becomes a follow-up ticket, linked, with the notes carried across.
    3. Log the trade in the rolling scope-change page. We keep one long Notion page per quarter titled Scope changes, Q3. Every trim adds a row: date, cycle number, what came in, what came out, who decided, one sentence on why. No approvals, no drama. A record.

    That is the whole ritual. Cut two, split one, log the trade. We finish by 11:30 and post the diff in the squad Slack channel by lunch.

    Why each move earns its place

    The cuts exist because engineers will not admit a ticket is low value while it is theirs. Naming the bottom two of the list, out loud, in the same meeting every week, removes the social cost. Nobody has to argue for their pet feature to survive. They only have to argue against a specific cut, and if they cannot, the cut stands.

    The split exists because our worst cycles were always the ones where the biggest ticket slipped by three days and dragged three smaller ones with it. Forcing a Friday-of-week-two slice on the largest ticket surfaces the risk while there is still time to react. The engineer often finds the slice is 40 percent of the work and delivers 80 percent of the value, which is a good trade even without a scope crisis.

    The log exists because we used to have the same argument every quarter about whether the team was under-delivering. Now we open the Notion page in the retro and count. Last quarter we absorbed 23 mid-cycle changes across six squads without extending a single cycle. That number ended a lot of arguments.

    What we do not do

    A few things we tried and dropped:

    • We do not require the CTO or a product lead to approve trims. The squad decides. If the squad is wrong, we catch it in retro, not in a gate.
    • We do not track “scope creep” as a metric per squad. It punishes the teams closest to customers, which is the opposite of what we want.
    • We do not roll deferred tickets automatically into the next cycle. They go back to the backlog and compete on merit. About a third never come back, which tells us the trim was correct.
    • We do not run the trim on Monday. Too early, the picture is still forming. Or Friday. Too late, the week is already lost. Wednesday is the fulcrum.

    What it looks like in practice

    Two weeks ago, the billing squad walked into Wednesday with a fresh Datadog alert showing invoice PDFs failing for 4 percent of enterprise customers. They cut a small copy update on the settings page and a nice-to-have export format. They split the ongoing tax-rate refactor: ship the US states this cycle, hold EU VAT for the next one. They logged three lines in the Notion page. The incident work landed on Thursday of week two. Nobody worked a weekend.

    That cycle looked, from the outside, like a normal cycle. That is the point. The ritual is not glamorous, and it does not solve the underlying question of why new work keeps arriving. It gives us a repeatable way to say yes to the important new thing without lying to ourselves about the cost. We recommend stealing it.

  • Why we killed the daily standup for a 15-minute doc

    Why we killed the daily standup for a 15-minute doc

    Our daily standup used to run 22 minutes on a good day. Ten engineers, one PM, one designer, all half awake, waiting their turn to recite tickets that lived in Linear anyway. We killed it in March. What replaced it is a single Notion page called Morning Doc, and it takes each of us about 90 seconds to fill in.

    How the doc works

    Every engineer, PM, and designer on the delivery pod owns a row. By 10am local time, each owner drops three lines under their name:

    • Yesterday: what shipped or moved, linked to the Linear ticket.
    • Today: the one thing they intend to close before EOD.
    • Blocker: a person, a decision, or a dependency. If none, they write none.

    That is the entire contract. No status colors, no percentages, no vibes. If your row is empty at 10:01, our engineering lead pings you once in Slack. Miss it twice in a week and you owe the pod a coffee run.

    Blockers move to threads, not meetings

    The rule we care about most: blockers do not sit in the doc. The moment someone writes one, they cross-post it into #pod-delivery as a thread with the format Blocker: [who I need] [what I need] [by when]. Whoever owns the answer replies in that thread. Our internal SLA is one hour during working time. Last month we hit it 94 percent of the time, measured with a small GitHub Action that scrapes reply timestamps.

    If a blocker sits unanswered past noon, it gets escalated to the pod lead. That has happened four times this quarter.

    What we gained, and what we gave up

    The wins are boring and real:

    1. We got roughly 110 engineer-minutes back per day across the pod.
    2. Blockers surface in writing, so Thursday retros have receipts instead of memory.
    3. Nobody performs progress for an audience. The doc rewards specificity over storytelling.

    What we gave up is real too. New hires miss the social glue of seeing faces every morning, so we kept a 20-minute Tuesday pod sync for demos and messy conversations. Designers occasionally want a live thinking-out-loud session, and we book those ad hoc in Figma instead of pretending standup was the right container.

    The morning doc is not clever. It is a shared page with a deadline and a Slack rule attached. But it turns out most of what standup gave us was the deadline, and most of what it cost us was the meeting.