Creating a Tone Skill for AI-Assisted Blog Posts
How I turned implicit writing patterns into a reusable agent skill for consistent Clanker-authored content.
I had blog posts written by Clanker but no formal guidelines. The voice was consistent because I manually prompted for it every time. I wanted a skill that future sessions could load to maintain that voice without guessing.
Why the skill needed maintenance
Each post followed a similar pattern, problem-first structure, honest tone, inline attribution, but nothing documented why. A new session without context could drift into marketing language or over-explain things. The voice existed in the posts, but never as formal instructions.
What the skill codifies
I reviewed existing Clanker posts, extracted the recurring patterns, and wrote them into a skill file at .agents/skills/clanker-post/SKILL.md following the opencode Agent Skills spec.
The skill covers:
- Voice. First person, active voice, no AI disclaimers
- Tone. Stabilized, concise, humble, honest, informative
- Structure. A menu of incident, architecture, retrospective, how-to, source-investigation, Personal, and TIL shapes, plus the original problem-first sequence as an optional fallback
- Factual maintenance. Version markers, evidence labels, qualified claims, and a correction workflow for older posts
- Attribution. Inline source credits, References section when sources are used
- Checklist. Pre-publish checks for voice, evidence, version scope, links, and structure
The hardest part was separating a consistent voice from a repeated outline. The original skill made The Problem, What Changed, Results, and What I'd Do Differently the default for almost every Development post. That sequence still works for straightforward implementation notes, but it made incident reports, architecture decisions, and source investigations sound mechanically identical.
The updated skill keeps that sequence as a fallback. It also documents structures that match different kinds of evidence:
- Incident report: Symptom → Investigation → Root cause → Fix → Verification → Follow-up
- Architecture decision: Constraints → Options considered → Decision → Implementation → Tradeoffs
- Retrospective: Initial assumption → Evidence that disproved it → Updated model → Consequences
- How-to: Goal → Preconditions → Steps → Verification → Caveats
- Source investigation: Observed behavior → Version boundary → Source trace → Confirming evidence → Practical consequence
Personal posts have no required outline. TIL posts retain their compact discovery format because it already fits their purpose.
Factual maintenance
The first version emphasized citations but said little about maintaining claims after publication. A References section can still support an overbroad conclusion or conflate adjacent controls. The update adds four evidence labels: observed, documented, inferred, and reported.
Version-sensitive posts now get a compact scope marker near the introduction:
> [!NOTE]
> Tested with Dagger 0.21.7 and pnpm 11.8.0 on 2026-07-16.
Tested with means I exercised that combination. Fact-checked against means I verified the statement against current primary documentation without reproducing it locally. The distinction prevents a documentation lookup from being presented as an experiment.
The skill also rejects unsupported statistical generalizations. Several fast incident responses do not establish that most malicious packages are found within 24 hours. In that case the accurate statement is that a cooldown creates an observation window, with a project-specific duration and no guarantee of detection.
Corrections now have a workflow: update the original post, link the newer investigation, identify the narrowed claim, refresh the version marker, and check internal links after a rename or split. This matters for posts such as the Dagger cache write-up, where a later source trace narrowed an earlier claim about reuse across ephemeral CI engines.
// .agents/skills/clanker-post/SKILL.md
---
name: clanker-post
description: Write AI-assisted blog posts for frangonf.com in a stabilized,
concise, humble, honest, and informative tone.
---
The skill loads on demand via skill({ name: "clanker-post" }). No config changes, no global skills modified.
Linting Enforcement
The hard rules in the skill (no em dashes, no negation-first constructions) needed automated enforcement. I added markdownlint-rule-search-replace to catch em dashes in prose via markdownlint.
The rule is scoped to src/content/ via a subdirectory config. The root loads the module, the subdirectory enables the rule. They merge at lint time. This keeps em dashes allowed in skill files and docs while catching them in blog prose. See Scoping markdownlint rules to subdirectories for the full explanation.
searchScope: "text" skips code blocks and inline code. The rule runs in the pre-commit hook via hk, so em dashes never reach a commit.
Maintenance retrospective
I wrote the first skill by extracting the most visible pattern from a small set of posts. That stabilized the voice, but it also promoted one outline into a default. Reviewing the larger corpus made the distinction clearer: tone should remain stable while structure follows the material.
I would also have added factual-maintenance rules alongside citation rules from the start. Primary links improve traceability, but the post still needs to state what was tested, what came from documentation, and what changed when later evidence narrowed the conclusion.
References
- opencode Agent Skills. Skill file format and discovery paths
- markdownlint-rule-search-replace. Custom rule for em dash detection
This post was written with AI assistance.