Your First Skill¶
In a nutshell
A skill is an add-on that teaches OVOS a new ability. This page is a hands-on walkthrough. You'll create a tiny skill from scratch, install it, and talk to it, in about ten minutes. You only need to be comfortable creating a few text files. New to the words here? Keep the Glossary open.
By the end you'll have a skill that answers when you say "hello". Once you've done it once, every other skill is just more of the same idea.
Before you start: OVOS needs to already be installed
This walkthrough assumes OVOS is already installed and its Python environment is available to work in. See ovos-installer or RaspOVOS if you haven't done that yet. The ten minutes below covers writing and installing the skill itself, once that environment is in place.
The flow runs from creating the folder layout to a working skill, checks the "hello" reply, and loops back to the pip install step on failure:
flowchart TD
A["Step 1: create folder layout"] --> B["Step 2: write skill code"]
B --> C["Step 3: write .intent file"]
C --> D["Step 4: write .dialog file"]
D --> E["Step 5: pyproject.toml + entry point"]
E --> F["Step 6: pip install -e ."]
F --> G["Restart ovos-core"]
G --> H{"Say 'hello'"}
H -->|OVOS replies| I["Done"]
H -->|no reply| J["Check ovos-logs show -l skills"]
J --> F
Diagram: The flow runs from creating the folder layout through the pip install step to restarting ovos-core, then checks whether saying "hello" gets a reply, looping back to the pip install step on failure.
What a skill is made of¶
A skill is a small folder with three kinds of files:
| File | What it holds | Example |
|---|---|---|
the skill code (__init__.py) |
the Python that runs when an intent matches | "when the user says hello, speak a greeting" |
intent files (*.intent) |
example sentences the user might say | hello, hi there |
dialog files (*.dialog) |
lines OVOS can speak back (it picks one at random) | Hello! Nice to meet you. |
The intent and dialog files live in a locale/<language>/ folder, so the same skill can be
translated. That's the whole model. Anatomy of a Skill covers it in depth.
Step 1: Create the folder layout¶
Make this structure, and actually replace youruser with your own name/handle (and pick
your own skill name) before installing, not later: the folder and pyproject.toml names
become the package name and skill_id, and two skills with the same skill_id installed on
one machine shadow each other. On your own single machine the literal names work, which is
exactly why the collision goes unnoticed until a second copy shows up (a shared computer, a
classroom, a copied repo):
ovos-skill-my-first/
├── pyproject.toml
└── ovos_skill_my_first/
├── __init__.py
└── locale/
└── en-us/
├── intents/
│ └── Hello.intent
└── dialog/
└── hello.dialog
Both layouts work
OVOS walks the entire locale/<lang>/ folder looking for a file by name. So grouping
files into intents//dialog/ subfolders (as above) or dropping them flat directly in
locale/en-us/ both work equally well. Pick whichever keeps your skill readable. The
language folder name itself is also case-insensitive (en-us and en-US are the same
folder to OVOS). See Anatomy of a Skill and
Intent Design for more on this layout.
Step 2: Write the skill code¶
Every skill is a Python class that subclasses OVOSSkill. A decorator (a
line starting with @ placed just above a function) tells OVOS what that function is for. Here
@intent_handler("Hello.intent") means "run this function when the user says something matching
Hello.intent" (see Decorators for the full list). self.speak_dialog("hello")
then speaks a random line from hello.dialog.
ovos_skill_my_first/__init__.py:
from ovos_workshop.skills import OVOSSkill
from ovos_workshop.decorators import intent_handler
class MyFirstSkill(OVOSSkill):
def initialize(self):
# runs once, after the skill is fully loaded and connected to the bus —
# this is the place for setup that needs self.settings, self.bus, etc.
# (a plain __init__ still runs too early for that; see Skill Settings)
self.log.info("MyFirstSkill is ready")
@intent_handler("Hello.intent")
def handle_hello(self, message):
self.speak_dialog("hello")
That's the entire skill. initialize() is optional. You only need it once you have setup work
that depends on the skill being fully wired up (reading settings, registering
extra event handlers, and so on).
Step 3: Tell OVOS what the user might say¶
ovos_skill_my_first/locale/en-us/intents/Hello.intent: one example phrase per line. OVOS
learns the pattern from these, so you don't have to list every wording:
Three file formats, don't mix them up
A skill uses three different formats side by side: pyproject.toml is TOML,
.intent and .dialog files are plain text (one phrase per line, no quotes, no
commas, no brackets), and only skill.json is JSON. The classic mistake is decorating
an .intent file with JSON punctuation. Every stray quote or comma becomes part of the
phrase to match.
Step 4: Write what OVOS says back¶
ovos_skill_my_first/locale/en-us/dialog/hello.dialog: one option per line. OVOS picks one at
random so the assistant doesn't sound robotic:
Step 5: Make it installable¶
A skill is just a Python package that advertises itself to OVOS through an entry point.
pyproject.toml:
You don't need to understand this file yet
Replace ovos-skill-my-first (the project name), the ovos_skill_my_first package
folder wherever it appears, and youruser (your username), then copy the rest as-is.
[build-system]
requires = ["setuptools>=61", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "ovos-skill-my-first"
version = "0.0.1"
description = "My first OVOS skill"
requires-python = ">=3.9"
license = {text = "Apache-2.0"}
dependencies = ["ovos-workshop>=0.0.1"]
# "<skill-name>.<author>" becomes the skill_id; the right-hand side is package:ClassName
[project.entry-points."opm.skill"]
"my-first.youruser" = "ovos_skill_my_first:MyFirstSkill"
[tool.setuptools]
packages = ["ovos_skill_my_first"]
[tool.setuptools.package-data]
# match whichever locale layout you picked: the first glob covers files placed flat
# directly in locale/<lang>/, the second covers files grouped in locale/<lang>/intents/
# and locale/<lang>/dialog/ subfolders — list both if you're not sure which you'll use
ovos_skill_my_first = ["locale/*/*", "locale/*/*/*"]
The entry-point key (my-first.youruser) becomes your skill's skill_id
(<skill-name>.<author>). The value points at your skill class. The opm.skill group is
how the Plugin Manager discovers installed skills.
For the full packaging reference (including GUI assets in package-data), see Anatomy of a Skill.
Step 6: Install it and talk to it¶
First, activate the same Python environment OVOS runs in, so the skill installs into the
interpreter ovos-core actually uses. For example, source ~/.venvs/ovos/bin/activate for
a venv install. In plain English: a virtual environment is an isolated
Python install. "Activating" it just means that pip will now install
into the one OVOS actually uses, instead of somewhere else. See the
Glossary if these terms are new.
That exact path is only an example, not something you can assume. raspOVOS, ovos-installer,
and container installs each put it somewhere different.
- Check where your particular install created its environment, for example the installer's summary screen (see ovos-installer). On a fleet of identically-imaged machines (a classroom set, a batch of satellites) the path is the same everywhere. Find it once and share it, rather than having every person hunt for it separately.
- Advanced: if you installed via systemd, its unit file's
Environment=/ExecStart=lines show the path. - Still stuck? See Troubleshooting.
If you're running OVOS in
a container instead, there's no host environment to activate. Install into the container
directly: docker compose exec <service> pip install -e . (run from the skill folder, with
the skill's path mounted into the container, or copy it in first).
From inside the ovos-skill-my-first/ folder:
Confirm OVOS can see it before you go any further. This reads the same entry points the skill loader reads, so it separates a packaging mistake from a matching problem:
python -c "from ovos_plugin_manager.skills import find_skill_plugins; print(list(find_skill_plugins()))"
Your skill id should appear in that list. If it does not, the entry point in pyproject.toml
is wrong and no amount of restarting or talking will help.
Your skill matches with a .intent file, which is handled by Padatious. Padatious is an
optional install, so a plain OVOS install may not have it. Check, and add it if it is missing:
The pipeline ID is ovos-padatious-pipeline-plugin, but the package you install is
ovos-padatious. pip install ovos-padatious-pipeline-plugin fails, since there is no such
package. See Pipeline IDs vs.
plugins.
Restart ovos-core (or it will pick the skill up on its next scan). On an
ovos-installer setup that is systemctl --user restart ovos-core.service. See
Stage 1 of Troubleshooting
for other setups and how to check the services. Then say your configured wake word first
(default "Hey Mycroft"), wait for the listening chime, and then say:
"hello"
OVOS replies with one of your dialog lines. You just wrote a skill.
If OVOS doesn't reply
Check the skills log for your skill_id: ovos-logs show -l skills. See
Troubleshooting for how to read what it's telling you.
The most common cause is the one in Step 6: Hello.intent is a Padatious intent, and
Padatious is an optional install. Re-run that check.
You do not need to edit any pipeline config. The stage is in the shipped default
intents.pipeline already. The plugin just has to be installed for the default to have
anything to load. Test Your Skill hits the same requirement in
the automated test.
Prefer ovos-say-to for repeatable tests (and know the spoken-test failure mode)
You can send the utterance straight onto the bus as text, skipping the wake word and mic
entirely: ovos-say-to "hello". This is the recommended way to test while iterating.
It is deterministic and works with no microphone. It is also the way to go when many
machines test at once (a workshop, a classroom): the default spoken path sends your audio
to a shared public community STT server (see
Privacy & Security), so a room full of devices testing
simultaneously can see recognition turn slow or flaky from server load. That failure
looks like "OVOS didn't reply" but has nothing to do with your skill. See
Troubleshooting
for more text-based ways to test a skill.
Adding a file later? Know which install you have¶
If you add a new .intent or .dialog file after this walkthrough, what you do next depends on
how the skill is installed. With an editable install (pip install -e ., as used above), the
files live on disk where you edited them, so a restart of ovos-core (or the skills service) is
enough. No reinstall needed. With a normal wheel install, the files were copied into the package
at install time, so you must reinstall the skill (pip install . again, or the equivalent for a
built wheel) before OVOS can see the new file.
Where to go next¶
- Pull a value out of what the user said (a name, a city, a number). See Intent Design.
- Have a back-and-forth ("what's your name?" then a reply). See Continuous Conversation.
- Save settings or files. See Skill Settings and Filesystem.
- Make it sound good and behave well. See Skill Best Practices.
- Test it automatically. See Test Your Skill, which continues this
exact example. For the broader
ovoscopereference, see Testing Skills with ovoscope. - Publish it so others can install it. See Sharing your skill.
- See how an utterance actually travels through OVOS. See Life of an Utterance.
- Browse real skills for ideas in Skill Examples.
- Questions along the way? Ask in the skills channel on OVOS Chat.
Read next: Skill Structure Related: Skill Cookbook · Intent Design · Test Your Skill · Skill Development Overview