<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>HanLHo. - Fractional Architect &amp; Software Product Engineer - skills-primer</title>
    <link rel="self" type="application/atom+xml" href="https://hanlho.com/tags/skills-primer/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://hanlho.com"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-07-13T00:00:00+00:00</updated>
    <id>https://hanlho.com/tags/skills-primer/atom.xml</id>
    <entry xml:lang="en">
        <title>On Fluent Acceptance-Testing DSLs</title>
        <published>2026-07-13T00:00:00+00:00</published>
        <updated>2026-07-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hanlho.com/p/on-fluent-acceptance-testing-dsls/"/>
        <id>https://hanlho.com/p/on-fluent-acceptance-testing-dsls/</id>
        
        <content type="html" xml:base="https://hanlho.com/p/on-fluent-acceptance-testing-dsls/">&lt;p&gt;I just completed &lt;a href=&quot;https:&#x2F;&#x2F;codeberg.org&#x2F;hanlho&#x2F;skills-primer&quot;&gt;a small CLI tool&lt;&#x2F;a&gt; in which I used a testing style, &#x27;fluent acceptance-testing Domain Specific Languages (DSLs)&#x27;, heavily, with good results. They are custom test specifications that read close to natural language, test behaviour rather than implementation, and survive refactoring because they are intended to be run against stable interfaces.&lt;&#x2F;p&gt;
&lt;aside class=&quot;sidebar-note sidebar-note-right&quot;&gt;
  &lt;p&gt;On terminology used in this post: I use coding &#x27;agent&#x27;, instead of &#x27;harness&#x27;, to refer to CLI tools like Codex, Claude Code or Pi.&lt;&#x2F;p&gt;

&lt;&#x2F;aside&gt;
&lt;p&gt;Last year I started a seven-part blog post series on this approach. I bit off more than I could chew, the summer holidays started, and it never happened. Instead of finishing that series, I&#x27;ll condense what I wrote then and point you to &lt;a href=&quot;https:&#x2F;&#x2F;codeberg.org&#x2F;hanlho&#x2F;agentfiles&#x2F;src&#x2F;branch&#x2F;main&#x2F;agents&#x2F;dotagents&#x2F;.agents&#x2F;skills&#x2F;designing-fluent-acceptance-dsl&#x2F;SKILL.md&quot;&gt;a reference&lt;&#x2F;a&gt; and &lt;a href=&quot;https:&#x2F;&#x2F;codeberg.org&#x2F;hanlho&#x2F;skills-primer&#x2F;tests&#x2F;&quot;&gt;a full test suite in this style&lt;&#x2F;a&gt; rather than motivating everything in detail. I think this will work too.&lt;&#x2F;p&gt;
&lt;p&gt;At their heart, &#x27;fluent acceptance-testing DSLs&#x27; are always custom, meaning all naming can be tailored to project requirements and the domain language, but they must follow a specific codified pattern: setup, action, assertion.&lt;&#x2F;p&gt;
&lt;p&gt;Below is an example for a command line utility. At a high level, each specification or test consists of three parts:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Setup:&lt;&#x2F;strong&gt; configure state, input, and environment. Example: &lt;code&gt;Cmd::given()...&lt;&#x2F;code&gt;. &lt;code&gt;Cmd&lt;&#x2F;code&gt; because we are testing a command line utility, in this project I chose &lt;code&gt;given()&lt;&#x2F;code&gt; to initiate the setup phase. What follows are the setup steps without distracting technical infrastructure.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Action:&lt;&#x2F;strong&gt; execute the operation under test. Example: &lt;code&gt;when_run()&lt;&#x2F;code&gt;. CLI commands are often &lt;em&gt;run&lt;&#x2F;em&gt;. This is when the actual underlying command gets executed.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Assertion:&lt;&#x2F;strong&gt; verify behaviour and diagnostics. Example: &lt;code&gt;.expect_...().expect_...()....&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;After each transition to a new part, typically a new struct&#x2F;object is returned that only has the relevant methods for the next part. For example, the method that runs the action (in this case a CLI command) returns a new structure with methods that verify expectations. Note: there can be any number of &#x27;parts&#x27; aside these three main ones but the 3 mentioned are standard.&lt;&#x2F;p&gt;
&lt;p&gt;An example probably makes this clearer:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#eff1f5;color:#4f5b66;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;test&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;prime_should_discover_skills_from_default_path&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    Cmd::given()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;command_prime&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;with_skill&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;            &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;example-skill&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;            &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Use when testing example scenarios.&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;            &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;# Example Skill&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Do the thing.&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;        )
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;when_run&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;should_succeed&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;expect_prime_instructions&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;expect_available_skills&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;expect_skill&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;example-skill&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Use when testing example scenarios&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;test&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;prime_should_not_discover_skills_when_default_path_empty&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    Cmd::given()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;command_prime&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;when_run&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;should_succeed&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;expect_prime_instructions&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;expect_no_skills_detected&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;expect_out_does_not_contain&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;skill&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The full test suite and its infrastructure are available &lt;a href=&quot;https:&#x2F;&#x2F;codeberg.org&#x2F;hanlho&#x2F;skills-primer&#x2F;src&#x2F;commit&#x2F;f5b6ca8d2d6572e97cf51f2dc58524bf9b9963a6&#x2F;tests&#x2F;prime.rs&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This pattern is applicable to any kind of test and any type of executable. Although not strictly necessary, you should test against a stable contract, preferably from a usability point of view. In these cases you can apply this style to unit tests as well. If starting a CLI (or webserver or any other kind of &#x27;container&#x27; or service) becomes too much overhead, there is usually a contract at a lower level in the code you can test against.&lt;&#x2F;p&gt;
&lt;p&gt;This style is very flexible, which allows for expressive, readable, and consistent executable test descriptions, more so than other custom styles I have seen. For example, in the above we might have chosen differently:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Cmd::given()&lt;&#x2F;code&gt;: could have been &lt;code&gt;SkillsPrimer::prime().with_skill_on_disk(...)&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;should_succeed().expect_*&lt;&#x2F;code&gt;: &lt;code&gt;should_succeed()&lt;&#x2F;code&gt; is also an expectation but this reads more fluently and it allows for the DSL to be more constrained on success and failure checks.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;expect_prime_instructions().expect_available_skills()&lt;&#x2F;code&gt;: could have been &lt;code&gt;expect().prime_instructions().available_skills()&lt;&#x2F;code&gt;. The &lt;code&gt;expect()&lt;&#x2F;code&gt; method could then return a struct with a more limited set of methods, allowing for better scoping and deferred execution (which enables reporting of multiple failures in a test instead of only the first one).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The full mechanics I have encoded in a skill which is the reference for more details: &lt;a href=&quot;https:&#x2F;&#x2F;codeberg.org&#x2F;hanlho&#x2F;agentfiles&#x2F;src&#x2F;branch&#x2F;main&#x2F;agents&#x2F;dotagents&#x2F;.agents&#x2F;skills&#x2F;designing-fluent-acceptance-dsl&#x2F;SKILL.md&quot;&gt;Designing Fluent Acceptance-Test DSLs&lt;&#x2F;a&gt;. This skill is my reference I keep updating with new findings and, incidentally, is also how I instruct coding agents on how to write these kinds of tests.&lt;&#x2F;p&gt;
&lt;aside class=&quot;sidebar-note sidebar-note-right&quot;&gt;
  &lt;p&gt;With coding agents this style is becoming even more important because &lt;strong&gt;Fluent Acceptance-Test DSLs serve as &lt;strong&gt;executable&lt;&#x2F;strong&gt; specifications a coding agent or LLM can work against.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Coding agents work better if you give them something they can verify their work against. With these kinds of DSLs we can describe functionality in a way they can verify against. Also, since these tests are well-structured, coding agents can work with them and add more tests easily.&lt;&#x2F;p&gt;

&lt;&#x2F;aside&gt;
&lt;p&gt;Here is what I consider the main characteristics and benefits of these tests:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Behaviour-focused:&lt;&#x2F;strong&gt; tests describe expected behaviour, not implementation mechanics. They serve as executable specifications!&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Decoupled from code structure:&lt;&#x2F;strong&gt; tests run against stable, public interfaces (CLI, HTTP, key internal interfaces) so large and small refactorings don&#x27;t break them!&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Fluent, chainable DSL:&lt;&#x2F;strong&gt; custom methods form a readable chain (.given() &#x2F; .when() &#x2F; .then() or similar), written in the application&#x27;s own language.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Multi-level:&lt;&#x2F;strong&gt; the same approach works at application level (HTTP, CLI) and unit level. Strategic unit tests target specific, stable interfaces when that is more effective.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Custom, no external framework:&lt;&#x2F;strong&gt; the DSL is your own code with basic assertions. No Cucumber step definitions or heavyweight BDD frameworks.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Evolvable:&lt;&#x2F;strong&gt; start simple, refine as needs grow. The DSL can be refactored to match the domain more closely over time.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Consistent:&lt;&#x2F;strong&gt; reusable assertions, expectations, fixtures, and builders across the test suite.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Discoverable:&lt;&#x2F;strong&gt; IDE autocomplete (cmd-space) shows available steps and assertions, guiding the test writer. Click through a DSL method to see exactly which API calls or assertions are made.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The trade-offs:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Error localisation:&lt;&#x2F;strong&gt; when a test fails, the abstraction means you may need to drill deeper to find the cause (you trade error localisation for refactorability).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Code coverage concerns:&lt;&#x2F;strong&gt; more code is exercised per test, making coverage less targeted at specific lines. (Which may be a good thing too.)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Customisable:&lt;&#x2F;strong&gt; a double-edged sword; flexible but depends on team skill and discipline. No imposed structure like Cucumber provides.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I consider it a benefit that these DSLs are pure code, but I am not sure this is for every team or role. In some of my projects I end up with twice as much test code as production code.&lt;&#x2F;p&gt;
&lt;p&gt;To conclude, I will show another example from the same project which required a different DSL. As a short background on this project, which is not the focus of this post but merely serves as an illustration example: the project is aimed at coding agents that do not support skills. To that end, it provides a &lt;code&gt;prime&lt;&#x2F;code&gt; command that primes a coding agent to support skills. Below is an example of an end-to-end test suite that can run actual prompts with a real agent that does not have skill detection built-in.&lt;&#x2F;p&gt;
&lt;p&gt;Note:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AgentWithoutSkills&lt;&#x2F;code&gt;. We start with an agent that does not support skills out of the box. (The actual agent is hidden from the test. Under the hood, this is Zerostack but can be any agent that does not support skills.)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;unprimed()&lt;&#x2F;code&gt; and &lt;code&gt;primed()&lt;&#x2F;code&gt;. The main difference between the tests, the first test runs the agent without skills support and verifies skills are indeed not loaded. The second test &lt;em&gt;primes&lt;&#x2F;em&gt; this agent and verifies skills &lt;em&gt;are&lt;&#x2F;em&gt; loaded.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#eff1f5;color:#4f5b66;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;test&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;unprimed_agent_without_skills_should_not_find_skills&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    AgentWithoutSkills::unprimed()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;when_run_with&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;            &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Reply exactly with SKILL_NOT_FOUND if you cannot find or load a skill named test-skill. Reply exactly with TEST_SKILL_LOADED only if you actually load that skill.&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;        )
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;should_succeed&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;expect_output&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;SKILL_NOT_FOUND&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;expect_output_not_contain&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;TEST_SKILL_LOADED&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;rstest&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;case&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;load_with_name&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Load test-skill&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)]
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;case&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;invoke_with_name&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Invoke test-skill&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)]
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;case&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;use_with_name&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Use test-skill&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;primed_agent_without_skills_should_find_skills&lt;&#x2F;span&gt;&lt;span&gt;(#[case] &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;prompt&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;str&lt;&#x2F;span&gt;&lt;span&gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;    AgentWithoutSkills::primed()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;with_skills_from&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;tests&#x2F;fixtures&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;when_run_with&lt;&#x2F;span&gt;&lt;span&gt;(prompt)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;should_succeed&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;expect_output&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;TEST_SKILL_LOADED&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The full source code for this end-to-end test and its infrastructure for the DSL is &lt;a href=&quot;https:&#x2F;&#x2F;codeberg.org&#x2F;hanlho&#x2F;skills-primer&#x2F;src&#x2F;commit&#x2F;f5b6ca8d2d6572e97cf51f2dc58524bf9b9963a6&#x2F;tests&#x2F;end_to_end.rs&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I hope most of this is clear. Please do not hesitate to reach out if you want to know more, would like me to help you set this up, or work together on fleshing this out more (really!). I am curious to know if this resonates with others in the field.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Implementing and understanding agent skills</title>
        <published>2026-07-01T00:00:00+00:00</published>
        <updated>2026-07-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hanlho.com/p/implementing-and-understanding-agent-skills/"/>
        <id>https://hanlho.com/p/implementing-and-understanding-agent-skills/</id>
        
        <content type="html" xml:base="https://hanlho.com/p/implementing-and-understanding-agent-skills/">&lt;p&gt;This article explains how to add agent skills support to coding harnesses lacking native support, based on my experience implementing &lt;a href=&quot;https:&#x2F;&#x2F;codeberg.org&#x2F;hanlho&#x2F;skills-primer&quot;&gt;Skills Primer&lt;&#x2F;a&gt;, a CLI tool I wrote.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;skills-integration-instructions-and-skills-catalogue&quot;&gt;Skills integration instructions and skills catalogue&lt;&#x2F;h2&gt;
&lt;p&gt;The &lt;a href=&quot;https:&#x2F;&#x2F;agentskills.io&#x2F;client-implementation&#x2F;adding-skills-support&quot;&gt;agent skills documentation&lt;&#x2F;a&gt; explains very well how to integrate or add skills support to your coding agent. But to summarise, there are &lt;a href=&quot;https:&#x2F;&#x2F;agentskills.io&#x2F;client-implementation&#x2F;adding-skills-support#where-to-place-the-catalog&quot;&gt;two ways to implement agent skills&lt;&#x2F;a&gt;: through agent instructions (a prompt), or by using the tooling support from the coding agent. Since I needed to integrate with a tool that also did not provide extension points or plugins, I was left with the first option: building a prompt to give to the agent to integrate skills. It is also the most general and widely supported way to add skills to any coding agent.&lt;&#x2F;p&gt;
&lt;p&gt;The documentation also suggests prompts to activate skills. For example, here is the &lt;a href=&quot;https:&#x2F;&#x2F;agentskills.io&#x2F;client-implementation&#x2F;adding-skills-support#behavioural-instructions&quot;&gt;behavioural instruction&lt;&#x2F;a&gt; for the prompt-based implementation:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;markdown&quot; style=&quot;background-color:#eff1f5;color:#4f5b66;&quot; class=&quot;language-markdown &quot;&gt;&lt;code class=&quot;language-markdown&quot; data-lang=&quot;markdown&quot;&gt;&lt;span&gt;The following skills provide specialized instructions for specific tasks.
&lt;&#x2F;span&gt;&lt;span&gt;When a task matches a skill&amp;#39;s description, use your file-read tool to load
&lt;&#x2F;span&gt;&lt;span&gt;the SKILL.md at the listed location before proceeding.
&lt;&#x2F;span&gt;&lt;span&gt;When a skill references relative paths, resolve them against the skill&amp;#39;s
&lt;&#x2F;span&gt;&lt;span&gt;directory (the parent of SKILL.md) and use absolute paths in tool calls.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is also &lt;a href=&quot;https:&#x2F;&#x2F;codeberg.org&#x2F;hanlho&#x2F;skills-primer&#x2F;src&#x2F;branch&#x2F;main&#x2F;src&#x2F;lib.rs#L197-L199&quot;&gt;the prompt that Skills Primer uses&lt;&#x2F;a&gt;, almost verbatim. The &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;earendil-works&#x2F;pi&#x2F;blob&#x2F;main&#x2F;packages&#x2F;coding-agent&#x2F;src&#x2F;core&#x2F;skills.ts#L342-L346&quot;&gt;pi coding agent&lt;&#x2F;a&gt; does the same.&lt;&#x2F;p&gt;
&lt;p&gt;In Skills Primer, I call these instructions the &lt;em&gt;integration instructions&lt;&#x2F;em&gt;, one of two parts to instruct a large language model to use agent skills. The second part is the &lt;em&gt;skills catalogue&lt;&#x2F;em&gt;: an XML listing all available skills. For example:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;xml&quot; style=&quot;background-color:#eff1f5;color:#4f5b66;&quot; class=&quot;language-xml &quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;available_skills&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;skill&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;pdf-processing&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;Extract PDF text, fill forms, merge files. Use when handling PDFs.&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;location&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&#x2F;home&#x2F;user&#x2F;.agents&#x2F;skills&#x2F;pdf-processing&#x2F;SKILL.md&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;location&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;skill&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;skill&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;data-analysis&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;Analyze datasets, generate charts, and create summary reports.&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;location&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&#x2F;home&#x2F;user&#x2F;project&#x2F;.agents&#x2F;skills&#x2F;data-analysis&#x2F;SKILL.md&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;location&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;skill&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;available_skills&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Together, the integration instructions and the skills catalogue are all the model needs to load a skill&#x27;s instructions. Skills Primer&#x27;s main task is to build the XML (the variable part of the prompt) and prefix it with the skill-loading instructions shown above. The project calls this construction of the catalogue plus integration instructions &lt;em&gt;priming&lt;&#x2F;em&gt;, implemented as a CLI command: &lt;code&gt;skills-primer prime&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This design preserves progressive disclosure. Only the catalogue, names and descriptions, go into the agent&#x27;s context at startup. The full &lt;code&gt;SKILL.md&lt;&#x2F;code&gt; body loads only when the model decides a skill matches the task, matching the three-tier loading model (catalogue → instructions → resources) that native implementations use.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;bootstrapping&quot;&gt;Bootstrapping&lt;&#x2F;h2&gt;
&lt;p&gt;One component is missing: how do we get the agent harness to run Skills Primer and send its output to the model? This also needs to be done using a prompt and in this project I refer to this as the &lt;em&gt;bootstrap instructions&lt;&#x2F;em&gt;. These instructions go into the agent&#x27;s system prompt; they run the &lt;code&gt;prime&lt;&#x2F;code&gt; command, capture its output, and have the model treat that output as additional context when starting a session. These are the current instructions Skills Primer uses:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;markdown&quot; style=&quot;background-color:#eff1f5;color:#4f5b66;&quot; class=&quot;language-markdown &quot;&gt;&lt;code class=&quot;language-markdown&quot; data-lang=&quot;markdown&quot;&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;## Mandatory Bootstrap
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Before answering the first user request, run this exact command in the terminal:
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;`skills-primer prime`
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Then treat the command&amp;#39;s stdout as additional project instructions for this session.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;After the command completes, restart your reasoning about the user&amp;#39;s request from scratch using the command&amp;#39;s stdout as if it had been present in AGENTS.md before the session started.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Before answering the user, you must:
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;1. Inspect the available skill names and descriptions from the command output.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;2. Decide whether the user&amp;#39;s request matches a skill by name or description.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;3. If a skill matches, read that skill&amp;#39;s SKILL.md and follow it before producing the final answer.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;4. If any skill description matches the user&amp;#39;s request, do not answer directly until that skill has been loaded and followed.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Do not answer from general knowledge until you have completed that skill-matching step.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Do not summarize, reinterpret, or defer this step.
&lt;&#x2F;span&gt;&lt;span&gt;Do not answer the user&amp;#39;s request until the command has completed.
&lt;&#x2F;span&gt;&lt;span&gt;If the command fails, report the failure and stop.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is a relatively short instruction set, but there is also quite a bit going on. There is even some overlap with the integration instructions in the priming instructions.&lt;&#x2F;p&gt;
&lt;p&gt;What matters for understanding skills is that this prompt gave the most stability when tested with a real agent. My first attempt, simply instructing the model to run &lt;code&gt;skills-primer prime&lt;&#x2F;code&gt; and treat its output as additional context, proved unreliable: skills loaded less than half of the time. To address this, I have built an end-to-end test suite that I can repeat a number of times to measure how well different bootstrap prompts perform. For details, see &lt;a href=&quot;https:&#x2F;&#x2F;codeberg.org&#x2F;hanlho&#x2F;skills-primer&#x2F;src&#x2F;branch&#x2F;main&#x2F;docs&#x2F;prompts&#x2F;bootstrap-instructions&quot;&gt;the prompts I tried and their reliability scores&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;As a side note, what makes this bootstrapping prompt more effective than a simpler prompt is:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The prompt defines a clear control flow: it tells the model exactly what to do before answering, rather than just giving general instructions.&lt;&#x2F;li&gt;
&lt;li&gt;The request to restart reasoning seems to give skill loading slightly higher priority over other agent instructions, at a small performance cost.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;To increase the effectiveness even more, you could replace the first line with:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#eff1f5;color:#4f5b66;&quot;&gt;&lt;code&gt;&lt;span&gt;Before answering any user request, run this exact command in the terminal:
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;but this increases token cost and, so far, it does not seem to be needed for skills to work.&lt;&#x2F;p&gt;
&lt;p&gt;My goal was to make skills work in Zerostack, an agent that does not support them. I have used Skills Primer for the past few weeks, and the skills I expect to load do load reliably. I consider this implementation good enough. And in case I know of a skill that should be loaded, but is not, explicitly invoking it by instructing &lt;code&gt;use skill &#x27;grill-me&#x27;&lt;&#x2F;code&gt; almost always works (as much as with other coding agents). Manually invoking a skill will, of course, only work if the &lt;code&gt;skills-primer prime&lt;&#x2F;code&gt; command was successfully bootstrapped at the start of the session.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;learnings-based-on-implementing-skills&quot;&gt;Learnings based on implementing skills&lt;&#x2F;h2&gt;
&lt;p&gt;Non-determinism still feels uneasy, and my learnings did not improve on this. There are no explicit programmatic calls that ensure skills are loaded. As I showed above, skills are ultimately implemented completely by prompting an LLM. I already knew this, but implementing my own skills integration only made me more aware. Compared to direct prompts, there is an extra level of uncertainty because there is an extra step: leaving it to the model to decide when a skill and its instructions are loaded and executed.&lt;&#x2F;p&gt;
&lt;p&gt;Because of this reliance on the model, skills will work more reliably with better models. I tested mainly with DeepSeek-v4-Flash, a high-performing execution model. I have had almost no issues with the loading of skills themselves, but making the bootstrapping instructions reliable was less straightforward. I imagine smaller models miss skill instructions, or skills themselves, more often.&lt;&#x2F;p&gt;
&lt;p&gt;Skills have clearly achieved widespread adoption, almost any agent supports them, and I do like them for their &#x27;composability&#x27;. But I am still conflicted about the fact that they rely on &lt;em&gt;instructions&lt;&#x2F;em&gt; to work. On the other hand, given that coding harnesses are built around sending and receiving instructions, I am not sure it matters. A model that is good at following instructions, with higher levels of determinism, will also be good at interpreting skill-loading instructions and making them work.&lt;&#x2F;p&gt;
&lt;p&gt;In other words, I will probably need to accept that large language models follow instructions probabilistically, not deterministically. The non-determinism that makes me uneasy is not introduced by skills; it is inherent to the tool. Skills may increase non-determinism slightly, but they make instruction-driven behaviour composable, discoverable, and reusable. For me, the trade-off is worth it.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Why I built skills-primer: making skills work in any agent</title>
        <published>2026-06-25T00:00:00+00:00</published>
        <updated>2026-06-25T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hanlho.com/p/why-i-built-skills-primer/"/>
        <id>https://hanlho.com/p/why-i-built-skills-primer/</id>
        
        <content type="html" xml:base="https://hanlho.com/p/why-i-built-skills-primer/">&lt;p&gt;This page explains why I implemented &lt;a href=&quot;http:&#x2F;&#x2F;codeberg.org&#x2F;hanlho&#x2F;skills-primer&quot;&gt;Skills Primer&lt;&#x2F;a&gt;, a CLI tool that enables skills integration in coding agents that lack native skills support.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-i-implemented-skills-primer&quot;&gt;Why I implemented skills-primer&lt;&#x2F;h2&gt;
&lt;p&gt;Most major agents (Claude Code, Codex, others) support Agent Skills natively. But some agents do not, by choice or because they are early-stage, and I use one of them: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gi-dellav&#x2F;zerostack&quot;&gt;Zerostack&lt;&#x2F;a&gt; does not support skills by design. While I accept this premise and find it interesting to explore, over the past half year I have built a collection of skills as a primary means of sharing and reusing functionality across different coding agents. I did not want to give that up immediately, so I investigated what would be needed to achieve this.&lt;&#x2F;p&gt;
&lt;p&gt;So I built a project to reuse my skills library in this situation. It seemed to be a fun, small, focused project where I could build a CLI tool to integrate functionality into a coding agent. It was also an exercise in AI-assisted engineering in which I feed back experience into improving my agent skills and tooling.&lt;&#x2F;p&gt;
&lt;p&gt;Implementation proved possible through prompting alone. There was no need to write agent-specific code. The project could be a narrowly scoped CLI tool, one of my favourite areas for refining AI-assisted development.&lt;&#x2F;p&gt;
&lt;p&gt;Writing the CLI required me to read the spec and implement discovery, parsing, deduplication, and skills catalogue generation. This was also an opportunity to understand how Agent Skills work under the hood.&lt;&#x2F;p&gt;
&lt;p&gt;This was also an exercise in &quot;AI-targeted&quot; CLI design. There are two main ways to expose functionality to a coding agent: a CLI tool or an MCP server. I prefer CLI tools, and this was an opportunity to build one. I also drew inspiration from the Beads project&#x27;s design: a command that generates its own usage instructions for the LLM. Instead of relying on an LLM to invoke &lt;code&gt;help&lt;&#x2F;code&gt;, it primes the LLM directly with Beads usage instructions. This design seemed a perfect fit for skills integration I was aiming to build.&lt;&#x2F;p&gt;
&lt;p&gt;While I do use Zerostack locally, it is very new and still in development. If you&#x27;re looking for a minimal coding agent, I would currently recommend Pi instead. &lt;code&gt;skills-primer&lt;&#x2F;code&gt; was developed mainly with Pi, with occasional use of Codex (for GPT-5.4 and GPT-5.5 planning and review support) and Zerostack (for focused narrow tasks).&lt;&#x2F;p&gt;
&lt;p&gt;For the full implementation, including discovery, parsing, deduplication, and catalogue generation, see the &lt;a href=&quot;http:&#x2F;&#x2F;codeberg.org&#x2F;hanlho&#x2F;skills-primer&quot;&gt;repo&lt;&#x2F;a&gt;. For other posts on this blog related to skills-primer &lt;a href=&quot;&#x2F;tags&#x2F;skills-primer&#x2F;&quot;&gt;go here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
