Skip to content

8 min read

An unowned server outranks slow onboarding

On this page

Eighteen months after the first zip went into #eng-tooling, the company's shared agent tooling had a shape somebody finally wrote down: 60 skills in the shared repository, 9 MCP servers, one shared configuration template, and an onboarding process that took a new engineer two days to complete end to end. Three of the nine servers had no identifiable owner. Two teams had forked the configuration template and diverged from it.

Those four facts were presented together in a planning document, and the discussion went where such discussions go. Two days of onboarding is a number with a unit, it applies to every hire, and there were eleven hires coming in the next two quarters, so the estimated cost was 22 engineer-days and the fix was a setup script. The other three facts had no numbers attached and were listed as risks.

Ranking by the incident you cannot respond to

The useful sort order is not cost today but what happens when each fact meets a bad night.

FactWhat it costs while nothing is wrongWhat happens when something goes wrongWho can act
Two-day onboarding~2 engineer-days per hire, ~22 days over two quartersA new hire is slow for a weekAnyone; it is a documentation and scripting problem
60 skills, 9 serversChoice overhead, some duplicationWrong skill fires, bad output in one sessionThe skill's owner
Two forked config templatesNothing visibleTwo teams run configuration that stopped receiving fixes, including permission and reference changesNobody is watching
Three unowned serversNothing visibleA server misbehaves and there is no one to pageNobody

The bottom two rows cost nothing on an ordinary day, which is precisely why they were listed as risks rather than as work. What distinguishes them is not probability but the response: when the onboarding process is slow, someone is annoyed and fixes it; when an unowned server starts misbehaving, the incident has no assignee, and the ordinary escalation path terminates.

The concrete version at this company was pricing-context, one of the three. It exposes a lookup_price_rules tool, it serves about 4,000 calls a day across the revenue and growth teams' agents, and it was written by an engineer who left in April. It runs on a VM in a cloud project that was created for a prototype and is not in the infrastructure inventory, so it appears on no dashboard and in no on-call rota. In September its upstream — the pricing service — added a required region parameter and started returning 400 for requests without one. The server caught the error, logged it to a file on the VM, and returned an empty rule set.

Empty is the failure mode that makes this the top row. A tool returning an error would have surfaced in the agents' sessions as a visible failure that somebody would have reported within an hour. A tool returning {"rules": []} looks like a legitimate answer, so agents asked about pricing for a customer tier answered from whatever was in context, and the answers were plausible. It took nine days for someone in revenue to notice that a discount rule the agent kept omitting did in fact exist. The subsequent question — who owns this, who can restart it, who has access to that cloud project — took another two days and eventually required a request to the identity team to grant someone access to a project whose only administrator had left the company.

The diverged templates are the same category with a slower fuse. Both teams forked in month nine, for reasons that were sensible at the time: one needed a hook the template did not have, the other wanted a narrower permission set for a repository handling payment data. In the nine months since, the upstream template took 23 commits, four of which were the kind covered in the previous chapter — a deny rule for a command that turned out to be dangerous, pinning an MCP server that had been on @latest, vendoring a reference file that had been fetched from an editable wiki, and a hook fix that stopped a session from writing tokens into a log. Neither fork received any of them. Both teams believe they are running the company's shared configuration, because they were, once.

Why the visible number is a symptom

Onboarding time is worth improving and it is not a fact about the system's health, because it is downstream of the catalogue's size and of exactly the discipline the other rows describe. A new engineer's two days is spent on 60 skills' worth of install steps, credential requests, access approvals, and the manual steps that exist wherever a package did not declare what it needed. A setup script that automates the mechanical half takes it to perhaps three hours, which is a real improvement and is announced as a win.

Then the catalogue grows. At the rate this company was adding artefacts — roughly four skills a month, one server a quarter — the 60 becomes 110 within a year, and each addition brings its own credential, its own access request, its own step the script has to learn. Some of those steps cannot be scripted at all, because they are approvals with humans and latency in them, which is the finding from the clean-install work in chapter 2. Onboarding drifts back toward a day and a half, and the script becomes another artefact needing an owner. Meanwhile the unowned servers have been unowned for another year and the forks have taken on another 30 commits of drift.

That is the trap in optimising a visible number: it improves, it is celebrated, and the improvement does not persist because the number was measuring the consequence of something the fix did not touch.

One thing this quarter

Ownership is the fix that changes what happens on the bad night, and it is small enough to complete. Every shared artefact — skill, server, template, hook — declares an owning team and a contact, in a file that CI enforces, with an expiry that forces a renewal decision rather than allowing an entry to be true in 2026 and hollow in 2027.

yaml
# ownership.yaml — one entry per shared artefact, enforced in CI
artefacts:
  - id: mcp/pricing-context
    kind: mcp-server
    owner_team: revenue-platform
    escalation: "#revenue-platform-oncall"
    runbook: go/pricing-context-runbook
    infra: gcp-project/rev-plat-prod
    reviewed: 2026-08-01          # must be within 180 days
  - id: skills/db-migrations
    kind: skill
    owner_team: data-platform
    escalation: "#data-platform"
    runbook: go/db-migrations
    reviewed: 2026-07-14
python
# ci/check_ownership.py — fails the build on unowned or stale artefacts
import sys, datetime, yaml
from pathlib import Path

MAX_AGE = datetime.timedelta(days=180)
today = datetime.date.today()

owned = {a["id"]: a for a in yaml.safe_load(Path("ownership.yaml").read_text())["artefacts"]}
present = {f"skills/{p.parent.name}" for p in Path("skills").glob("*/SKILL.md")}
present |= {f"mcp/{p.stem}" for p in Path("mcp").glob("*.json")}

problems = []
for artefact in sorted(present - owned.keys()):
    problems.append(f"{artefact}: no owner declared")
for artefact_id, entry in owned.items():
    if artefact_id not in present:
        problems.append(f"{artefact_id}: declared but not present — delete the entry")
    elif today - entry["reviewed"] > MAX_AGE:
        problems.append(f"{artefact_id}: ownership last confirmed {entry['reviewed']}")

if problems:
    sys.exit("ownership check failed:\n  " + "\n  ".join(problems))

The renewal date is the part that does the work over time, because ownership does not decay when someone declares it, it decays when the declaring team reorganises and nobody notices. A named team with a six-month confirmation is a claim somebody has to re-make.

State diagram: start → Unregistered (artefact added); Unregistered → Owned (entry added, CI passes); Owned → Stale (180 days without confirmation); Stale → Owned (a team re-confirms); Stale → Orphan (nobody claims it); Orphan → Owned (adopted by a team); Orphan → Removed (no adopter, no traffic); Removed → start.

What you explicitly do not do this quarter is the onboarding script. It is the thing everyone will ask for, it is the easiest to justify, and doing it consumes the quarter's attention on the symptom. The forks are second rather than not-at-all: the ownership file surfaces them, because a forked template is an artefact that appears in two teams' repositories with one entry between them, and reconciling them is next quarter's work with a list already written.

Deletion belongs in this quarter too, since it is the cheapest way to shrink every other problem. Of the 60 skills, the activation telemetry from chapter 4 showed 22 had not fired in 90 days. An unowned server with no traffic is not a governance problem to be solved; it is a VM to be turned off, and turning it off is a decision the ownership pass makes available for the first time.

What it will look like it has failed at

Three months in, this work will have several visible signatures of failure, and expecting them is the difference between finishing it and abandoning it at month four.

Onboarding will still take two days, or slightly longer, because you added a step. The people who wanted the script will say so. There is no honest counter-argument except that you did not attempt to improve it, which is worth saying plainly in advance so that it is a known trade rather than a broken promise.

A number of reviewed dates will contain team names that are technically correct and operationally empty — an artefact assigned to a team of one, or to a team that accepted it to clear a CI failure and has never looked at it. The enforcement produced a filled-in field, and a filled-in field is not a responsive owner. The way to tell the difference is not to read the file but to test it, which means an occasional unannounced page to the escalation channel of a randomly chosen artefact, and a record of whether anyone answered inside an hour.

And nothing will have been prevented that anyone can point to. Incidents that do not happen generate no evidence, so the case for continuing rests on measurements taken before the work started. The two numbers worth capturing in the first week are the share of artefacts with a named owner, which was 6 of 9 servers and an unrecorded fraction of the skills, and the time from a shared-component failure being noticed to a human accepting responsibility for it, which for pricing-context was eleven days. If the second number is not recorded before the fix, the fix has no defence at its first budget review.

None of this replaces the per-artefact discipline that the rest of this topic covered. A registry of owners over sixty unpackaged, unversioned, untested skills is a directory of things that are broken with someone's name attached. The order that works is the one these chapters followed: make one thing installable by someone who was not there when you built it, give it a version and a changelog that a stranger can correlate against, find out who depends on it before you change it, put the permission-bearing parts under review, and only then ask what the catalogue as a whole needs. That is also the order to do it in on Monday — pick the one skill or server your team already depends on most, and take it through those steps until a colleague who has never seen it can install it, break it, report it against a version, and get a fix.

Distribution and reuse — read the current version at https://daviddominguez.dev/learn/en/distribution-and-reuse/an-unowned-server-outranks-slow-onboarding

Back to top