# Creating and Using Skills for Agent Task Automation in Flue: A Complete Guide

> Master agent task automation in Flue by creating and using skills. Learn how to implement reusable markdown workflows and invoke them with session skill for efficient LLM tool integration.

- Repository: [Astro/flue](https://github.com/withastro/flue)
- Tags: how-to-guide
- Published: 2026-05-11

---

**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`](https://github.com/withastro/flue/blob/main/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`](https://github.com/withastro/flue/blob/main/packages/sdk/src/context.ts) at lines 58-60) computes this absolute path at runtime.

### Front Matter and Content

A valid [`SKILL.md`](https://github.com/withastro/flue/blob/main/SKILL.md) requires front matter with `name` and `description` keys:

```markdown
---
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`](https://github.com/withastro/flue/blob/main/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`](https://github.com/withastro/flue/blob/main/packages/sdk/src/context.ts) at lines 98-130). This function:

1. Locates the skills directory using `skillsDirIn`
2. Parses each [`SKILL.md`](https://github.com/withastro/flue/blob/main/SKILL.md) file to extract the front matter
3. Returns an array of `{name, description}` objects
4. 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`](https://github.com/withastro/flue/blob/main/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:

```typescript
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`](https://github.com/withastro/flue/blob/main/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:

```typescript
// 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`](https://github.com/withastro/flue/blob/main/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`](https://github.com/withastro/flue/blob/main/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.

```typescript
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`](https://github.com/withastro/flue/blob/main/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:

```typescript
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`](https://github.com/withastro/flue/blob/main/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 `discoverLocalSkills` in [`packages/sdk/src/context.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/context.ts), which populates the system prompt at initialization
* **Invocation** occurs via `session.skill()`, which delegates to prompt builders in [`packages/sdk/src/result.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/result.ts) and handles both name-based and path-based resolution
* **Validation** uses Valibot schemas with `createResultTools` enforcing output compliance through `finish` and `give_up` tool 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`](https://github.com/withastro/flue/blob/main/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`](https://github.com/withastro/flue/blob/main/packages/sdk/src/context.ts) parses each [`SKILL.md`](https://github.com/withastro/flue/blob/main/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`](https://github.com/withastro/flue/blob/main/packages/sdk/src/result.ts), which validate the LLM's response against your schema and return a typed `data` object instead of raw text.