A Hacker News post on September 2, 2026 linked Matt Pocock's public repository, Skills For Real Engineers. The repository is not a new agent runtime. It is a collection of small Markdown skills taken from the author's .agents directory, with an installer for putting selected files into a project. The discussion around the post made the useful question sharper: which instructions belong in a general repository guide, and which deserve their own reusable workflow?
That distinction matters once an agent edits a real codebase. A short prompt can suggest an approach. A repository guide can describe local rules. A skill can package a repeatable operation with its trigger, supporting files, and expected checkpoints. The last item is valuable only when the operation has a boundary that a human can inspect. Otherwise, a skill is just a longer prompt with a more impressive filename.
The repository is part of the agent
An agent does not arrive at a repository with the same assumptions as its author. It must learn the project’s names, tests, issue tracker, architecture, and definition of done. The README for Pocock’s collection describes this problem directly: agents are often asked to discover a project’s vocabulary as they work, then spend many words restating concepts that the project could have named once.
The collection’s CONTEXT.md example shows the practical fix. A project-specific phrase can replace a long explanation of a domain event. That is not decoration. It gives the agent a stable search term for functions, files, tests, and design notes. The repository becomes part of the skill’s input rather than a blank directory that the agent may interpret differently on every run.
This does not mean that every local rule belongs in a skill. A global rule such as “run the formatter before committing” fits an AGENTS.md or CLAUDE.md file. A workflow such as “turn an ambiguous request into a spec, then create tickets” has a trigger, a sequence, and a visible result. That workflow is a better skill candidate. The test is simple: could a teammate invoke it by name and know what artifact should exist when it finishes?
What a skill actually contains
Pocock’s repository uses ordinary Markdown files, grouped into directories such as skills/engineering and skills/productivity. Its README separates user-invoked skills from model-invoked skills. User-invoked entries include flows such as /grill-me, /to-spec, and /implement. Model-invoked entries include reusable practices such as test-driven development, bug diagnosis, code review, and codebase design. The distinction is about who may start the flow, not about whether one kind of file is magically more intelligent.
The repository also shows why a skill is different from a README. A README explains a collection and links to its members. A skill file describes the work to perform, the questions to ask, the files to consult, and the checks to complete. The surrounding repository supplies scripts, templates, reference documents, and installation metadata where needed. Together, these parts form an operational package.
It is useful to keep the contract small. A skill needs a trigger, inputs, ordered actions, and an observable verification rule. Those fields do not claim to be the schema of Pocock’s repository. They are a local convention for teams that want to make a file-based workflow easier to review and test.
name: add-health-route
trigger:
- add a health route
inputs:
handler_file:
type: path
required: true
route_path:
type: string
required: true
allowed_prefix: "/"
steps:
- action: append_if_missing
path: "{{ handler_file }}"
content: |
@app.get("{{ route_path }}")
def health():
return {"status": "ok"}
verify:
- action: contains
path: "{{ handler_file }}"
text: "@app.get(\"{{ route_path }}\")"
The example is deliberately boring. It names one file, accepts two values, performs one mutation, and checks one result. It does not promise to understand a whole web framework. That modesty makes review possible. A maintainer can ask whether the route belongs in that file, whether the decorator is correct, and whether the verification is strong enough before the agent runs it.
Start with a narrow contract
A broad instruction such as “improve this service” gives an agent too many legal interpretations. A narrow contract removes choices that do not belong to the task. It can state the permitted directories, the files that must already exist, the command that may run, and the result that proves completion. When an assumption is false, the skill should stop before mutation and report the missing condition.
Narrow does not mean trivial. Pocock’s collection includes workflows for grilling through a plan, writing a test-driven change, diagnosing a difficult bug, and reviewing a diff. Each workflow is substantial, but each has a recognizable seam. tdd is about a red-green-refactor loop. diagnosing-bugs is about building a feedback loop around one hard bug. code-review examines a bounded diff against standards and the originating specification.
The contract should also say what the skill will not do. A route skill should not silently redesign authentication. A ticket skill should not publish a specification to an issue tracker without making the destination explicit. A test skill should not claim success because a file changed. Negative boundaries protect the repository from enthusiastic automation.
The input contract is where safety begins. Reject absolute paths if the operation is meant to stay inside the repository. Reject a missing required value. Restrict enumerated options. Resolve defaults before the first write. These checks are cheap, and they turn an ambiguous request into a visible failure instead of a guess.
Turn the contract into executable checks
Written instructions explain intent, but an exit status or an exact file check gives the agent something firmer to observe. The verification need not be elaborate. It can assert that a generated file exists, a required string appears once, a test command exits successfully, or a diff stays inside an allowed directory.
A useful check tests the outcome rather than the action. “The append step ran” is weak evidence. “The route decorator appears in the intended file, the test passes, and no file outside the allowlist changed” is stronger. The more consequential the operation, the more the verification should inspect the external state that matters.
The same rule applies to skills copied from a public collection. Installation is not verification. The README for mattpocock/skills offers two paths: a Claude Code plugin or the skills.sh installer, which can copy selected editable files into a repository. After installation, a team still needs to confirm that the chosen files are in the expected directory, that their triggers are reachable in the chosen agent, and that project-specific paths and terminology have been adapted. A copied workflow is a starting point, not proof that it fits.
A small runner can make the local convention testable without introducing a framework. The important details are path confinement, input validation, bounded commands, and structured results. The runner below treats the YAML contract as data. It never passes a rendered command through a shell.
from pathlib import Path
import subprocess
import yaml
class SkillRunner:
def __init__(self, contract_path: Path, repo_path: Path):
self.contract_path = contract_path
self.repo_path = repo_path.resolve()
with contract_path.open(encoding="utf-8") as stream:
self.contract = yaml.safe_load(stream)
def _inputs(self, provided):
values = {}
for name, spec in self.contract.get("inputs", {}).items():
if name in provided:
value = provided[name]
elif "default" in spec:
value = spec["default"]
elif spec.get("required"):
raise ValueError(f"missing required input: {name}")
else:
continue
allowed_prefix = spec.get("allowed_prefix")
if allowed_prefix and not str(value).startswith(allowed_prefix):
raise ValueError(f"invalid value for input: {name}")
values[name] = str(value)
return values
def _render(self, text, values):
for name, value in values.items():
text = text.replace("{{ " + name + " }}", value)
return text
def _path(self, value):
candidate = (self.repo_path / value).resolve()
if candidate != self.repo_path and self.repo_path not in candidate.parents:
raise ValueError("path leaves repository")
return candidate
def run(self, provided):
values = self._inputs(provided)
for step in self.contract.get("steps", []):
if step["action"] == "append_if_missing":
path = self._path(self._render(step["path"], values))
path.parent.mkdir(parents=True, exist_ok=True)
content = self._render(step["content"], values)
current = path.read_text(encoding="utf-8") if path.exists() else ""
if content not in current:
path.write_text(current + content, encoding="utf-8")
elif step["action"] == "command":
argv = [self._render(str(item), values) for item in step["argv"]]
completed = subprocess.run(
argv, cwd=self.repo_path, capture_output=True,
text=True, timeout=int(step.get("timeout", 30)),
check=False,
)
if completed.returncode:
return {"ok": False, "phase": "step", "returncode": completed.returncode,
"stderr": completed.stderr}
else:
raise ValueError(f"unsupported action: {step['action']}")
for check in self.contract.get("verify", []):
path = self._path(self._render(check["path"], values))
expected = self._render(check["text"], values)
if check["action"] != "contains" or expected not in path.read_text(encoding="utf-8"):
return {"ok": False, "phase": "verify", "check": check.get("name", "unnamed")}
return {"ok": True, "phase": "verify"}
This is not a general agent framework. It supports two file-level actions and one command action, and it returns early on a failed command or check. That is enough to demonstrate the property a skill needs: the procedure is explicit, and the result is not inferred from a confident paragraph.
Keep context local and explicit
Context should be close to the workflow that consumes it. A repository-wide guide can define language, package manager, test command, and protected directories. A skill can point to the small set of files needed for its operation. A reference document can explain a domain term without forcing every task to reread the whole project.
Pocock’s README makes this separation concrete through CONTEXT.md, ADRs, and skills that update or use them. The point is not to put every fact into one enormous instruction file. Large instruction files become difficult to audit, and agents may spend attention on rules unrelated to the current change. A pointer to a focused document is often better than a duplicate explanation.
Local context also improves portability. The public collection can be installed as editable files, but the README warns that a team should choose one installation philosophy rather than installing the same skills twice through different mechanisms. After installation, replace generic assumptions with the project’s actual issue tracker, documentation location, labels, and vocabulary. A skill copied unchanged is not automatically a team process.
The context boundary should be visible in the contract. List the files the skill may read. List the paths it may write. Name commands and give them time limits. If a workflow needs a human decision, produce a proposal and stop at that seam. Do not hide a product decision inside a filesystem helper.
Test failure paths, not demos
The happy path proves that the example was arranged correctly. Failure tests prove that the boundary exists. For a file-writing skill, test a missing required input, a path outside the repository, an unsupported option, a command timeout, and a verification mismatch. The assertion should cover both the returned status and the absence of an unsafe mutation.
A failure result should be useful to the next action. “Failed” is not enough. Include the phase, the named check, the return code, or the rejected input. Avoid copying an entire environment into the result; a short diagnostic is easier for a human and an agent to inspect.
from pathlib import Path
import yaml
from runner import SkillRunner
def make_contract(path: Path, expected: str = "@app.get(\"/health\")"):
contract = {
"inputs": {"handler_file": {"type": "path", "required": True}},
"steps": [{
"action": "append_if_missing",
"path": "{{ handler_file }}",
"content": '@app.get("/health")
',
}],
"verify": [{
"name": "route",
"action": "contains",
"path": "{{ handler_file }}",
"text": expected,
}],
}
path.write_text(yaml.safe_dump(contract), encoding="utf-8")
def test_successful_run(tmp_path):
contract = tmp_path / "skill.yaml"
make_contract(contract)
result = SkillRunner(contract, tmp_path).run({"handler_file": "app/routes.py"})
assert result == {"ok": True, "phase": "verify"}
assert '@app.get("/health")' in (tmp_path / "app/routes.py").read_text()
def test_failed_verification_is_reported(tmp_path):
contract = tmp_path / "skill.yaml"
make_contract(contract, expected="marker-that-is-not-written")
result = SkillRunner(contract, tmp_path).run({"handler_file": "app/routes.py"})
assert result["ok"] is False
assert result["phase"] == "verify"
assert result["check"] == "route"
The second test is intentionally unglamorous. The write happens, but the contract still reports failure because the stated outcome is absent. That distinction prevents a runner from confusing “the step executed” with “the task succeeded.” In a real repository, the verification would usually call the project’s existing tests rather than inventing a second test system.
A small Python skill runner
The runner’s most important design choice is what it refuses to do. It does not interpret free-form prose as a command. It does not accept a path that escapes the repository. It does not turn a missing input into a guessed default unless the contract declares that default. It does not report success until every verification entry passes.
The command action is still a sharp tool. A fixed argument list is safer than a string assembled for a shell, but it can still delete data or contact an external service. Give the skill an allowlist of commands, use a short timeout, capture output, and run it in a temporary checkout when the operation is experimental. For deployment, billing, credentials, or irreversible migrations, make the human approval an explicit boundary instead of pretending that a local exit code proves the remote state.
This is also where existing engineering skills help. Pocock’s collection treats test-driven development, diagnosis, architecture, and code review as separate disciplines that can be combined around a change. A local runner should not reproduce all of those practices. It should invoke the project’s established tools and verify the seam that belongs to the skill.
Where this approach stops helping
A skill contract is a good fit when the task has a stable trigger, a small input surface, and a deterministic check. It is a poor fit for work whose correctness depends on an unresolved product decision, a broad architectural tradeoff, or a remote system that cannot be inspected from the repository. In those cases, the skill should gather evidence, produce a proposal, and stop before the decision point.
The Hacker News comments on Pocock’s repository raise a fair boundary question: some practices could fit in a general AGENTS.md or CLAUDE.md, while a skill is more useful for a named workflow, a project-specific script, unusual domain knowledge, or a sequence that benefits from explicit gates. That is a better rule than treating every useful paragraph as a new command.
The practical recommendation is to start with one narrow skill that touches a known seam. Define its inputs and refusal conditions. Reuse the repository’s own tests. Add failure cases before sharing the file. If the skill grows a long list of exceptions, stop adding branches and move the complex reasoning into a normal tool or a human review step. The product is the bounded, inspectable workflow. The agent is the caller that applies it to the repository in front of it.
Originally published on Dispatch.
Top comments (0)