Creating and Using Skills for Agent Task Automation in Flue: A Complete Guide
Flue treats skills as reusable, markdown-driven workflows that an LLM invokes like a tool, stored in .agents/skills/<name>/SKILL.md and executed via session.skill().
In the withastro/flue repository, the SDK provides a lightweight framework for agent task automation where skills function as portable instruction sets. Unlike hardcoded logic, these skills live as markdown files read at runtime, allowing you to edit agent capabilities without rebuilding your application. This guide covers the complete lifecycle of skill creation, discovery, and execution based on the actual source implementation.
What Are Skills in Flue?
Skills are reusable automation workflows defined in markdown files with YAML front matter. Each skill resides at .agents/skills/<name>/SKILL.md and contains a description that the LLM uses to understand when and how to invoke the workflow. The SDK treats these as first-class tools: when you call session.skill("triage"), the system builds a prompt that instructs the model to run that specific skill file.
According to the source code in packages/sdk/src/context.ts, the discoverLocalSkills function scans the skills directory at initialization and returns metadata objects containing name and description for each valid skill found. This discovery mechanism ensures the system prompt automatically includes an Available Skills list, making skills discoverable to the LLM without manual registration.
Defining Your First Skill
File Structure
Create a directory structure under your project root:
.agents/skills/triage/SKILL.md
The path is strict: Flue expects skills to live under .agents/skills/ relative to the current working directory. The helper function skillsDirIn (defined in packages/sdk/src/context.ts at lines 58-60) computes this absolute path at runtime.
Front Matter and Content
A valid SKILL.md requires front matter with name and description keys:
---
name: triage
description: Collect logs, reproduce the failure, and suggest a fix.
---
# Triage a crash
1. Run `git log -1` to get the latest commit.
2. Fetch the crash log from `/var/log/app.log`.
3. Summarise the error and propose a remediation plan.
Only the front matter is parsed by Flue for registration; the markdown body is read directly by the LLM when the skill executes. As noted in packages/sdk/src/build.ts (lines 198-203), these files are not bundled into the application—they are read dynamically from the sandboxed working directory.
How Flue Discovers Available Skills
When you initialize a session via init(), the SDK invokes discoverSessionContext, which calls discoverLocalSkills (located in packages/sdk/src/context.ts at lines 98-130). This function:
- Locates the skills directory using
skillsDirIn - Parses each
SKILL.mdfile to extract the front matter - Returns an array of
{name, description}objects - Populates the system prompt with an Available Skills section via
composeSystemPrompt
This automatic discovery means any skill file you create becomes immediately available to the agent without code changes or redeployment.
Calling Skills from a Session
Flue provides two invocation patterns through the Session.skill() method in packages/sdk/src/session.ts (lines 63-98): name-based and path-based resolution.
Name-Based Skills
Use this approach for skills discovered during initialization:
import { init } from '@flue/sdk';
const session = await init({
cwd: '/path/to/your/project',
});
// Invokes the triage skill registered in the system prompt
const result = await session.skill('triage', {
args: { issueId: 12345 },
});
console.log(result.text); // LLM-generated output after executing the skill
Under the hood, buildSkillByNamePrompt (in packages/sdk/src/result.ts at lines 39-45) generates the prompt that instructs the model to execute the skill.
Path-Based Skills
When the identifier contains a slash or ends with .md, Flue treats it as a direct file path:
// Resolves to .agents/skills/triage/reproduce.md
const result = await session.skill('triage/reproduce.md', {
args: { commitHash: 'abc123' },
});
The SDK validates the path via resolveSkillFilePath and uses buildSkillByPathPrompt (in packages/sdk/src/result.ts at lines 58-66) to construct the execution prompt. If the file does not exist, the error handling logic in Session.skill (lines 86-96) surfaces a clear runtime error.
Enforcing Structured Results
For type-safe outputs, supply a Valibot schema. The SDK uses createResultTools (in packages/sdk/src/result.ts at lines 29-36) to inject special finish and give_up tools that validate the LLM's output against your schema before returning.
import * as v from 'valibot';
const IssueSchema = v.object({
title: v.string(),
severity: v.enum(['low', 'medium', 'high']),
fixable: v.boolean(),
});
const { data, usage } = await session.skill('triage', {
schema: IssueSchema,
});
// data is typed as { title: string; severity: 'low'|'medium'|'high'; fixable: boolean }
The withScopedRuntime function (in packages/sdk/src/session.ts at lines 66-71) manages the runtime injection of these schema-enforced result tools, ensuring the LLM cannot complete the skill call without producing valid JSON matching your specification.
Advanced Pattern: Skills Within Tasks
You can compose skills inside broader task workflows. The task method allows the LLM to invoke skills dynamically while maintaining structured output requirements:
const ReportSchema = v.object({
summary: v.string(),
steps: v.array(v.string()),
});
const report = await session.task(
'Run the "triage" skill on issue #42 and produce a short report.',
{
schema: ReportSchema,
},
);
console.log(report.data.steps);
All skill calls are recorded in the immutable SessionHistory, with token usage normalized via the utilities in packages/sdk/src/usage.ts. This history supports compaction for long-running sessions while preserving the complete audit trail of skill executions.
Summary
- Skills are markdown files with YAML front matter stored in
.agents/skills/<name>/SKILL.md - Discovery happens automatically through
discoverLocalSkillsinpackages/sdk/src/context.ts, which populates the system prompt at initialization - Invocation occurs via
session.skill(), which delegates to prompt builders inpackages/sdk/src/result.tsand handles both name-based and path-based resolution - Validation uses Valibot schemas with
createResultToolsenforcing output compliance throughfinishandgive_uptool calls - Runtime loading means skills are read from disk at execution time, not bundled, enabling rapid iteration without rebuilds
Frequently Asked Questions
What file format does Flue use for skills?
Flue uses standard markdown files named SKILL.md with YAML front matter containing name and description keys. Only the front matter is parsed by the SDK for registration; the markdown body is provided directly to the LLM as instructions.
How does Flue discover available skills?
The SDK automatically scans the .agents/skills/ directory when you call init(). The discoverLocalSkills function in packages/sdk/src/context.ts parses each SKILL.md file and injects a list of available skills into the system prompt, making them discoverable to the LLM without manual configuration.
Can I use a custom file path instead of a registered skill name?
Yes. If you pass a string containing a slash or ending with .md to session.skill(), Flue treats it as a relative path and resolves it using resolveSkillFilePath. This bypasses the registry and loads the skill directly from the specified location within .agents/skills/.
How do I enforce structured output from a skill?
Supply a Valibot schema in the schema option when calling session.skill(). The SDK automatically injects finish and give_up tools via createResultTools in packages/sdk/src/result.ts, which validate the LLM's response against your schema and return a typed data object instead of raw text.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →