openloops.skills#
The agent-facing surface: two skills, one subagent, and the command that installs them.
The ol command is plumbing. What most people actually want is an agent that knows
how to use the plumbing and tells them what matters – “what is being done, and what
needs my attention?” – without their ever typing ol owed. That agent is a file:
a SKILL.md an agent host loads when the question comes up. So openloops ships three
of them, inside the package, and this module is how they get from the wheel to the host.
|
the read skill. Runs the three commands, and synthesises rather than pastes. The one to load. |
|
the capture skill. Without something filing
|
|
a subagent: the same sweep in a fresh context, returning a page instead of three screens of output. |
Nothing here is openloops-specific machinery. A skill is a markdown file in a
directory an agent host reads; installing one is a symlink. The whole module is a
hundred lines because that is genuinely all it is, and the alternative – a copy of
each skill pasted into every user’s config – is a copy that goes stale the first time
pip install -U openloops lands.
Two decisions worth stating, because both are the kind that get quietly reversed:
A symlink, not a copy. The point is that upgrading the package upgrades the skill. Copying is the fallback, taken only where symlinks are unavailable (Windows without Developer Mode, where creating one needs a privilege an ordinary process lacks), and the plan says which happened rather than pretending they are the same thing.
Nothing already there is ever overwritten. A destination holding something that is
not ours reads conflict: it is reported, not replaced, and force=True is the
only way past. Someone’s hand-written skill of the same name is theirs, and silently
eating it would be a worse failure than not installing at all.
This is deliberately not in openloops.tools. That module is the list every
surface dispatches from – an MCP server, an HTTP endpoint – and “symlink files into
this machine’s agent config” is not an operation a remote surface could honestly offer.
It belongs beside openloops.job, the other installer, which the CLI also wraps
directly.
>>> skills_dir().name, agents_dir().name
('skills', 'agents')
>>> sorted(asset.name for asset in bundled())
['openloops', 'openloops-needs-human', 'openloops-sweep']
Module Attributes
Where Claude Code keeps skills and subagents, and the variable that relocates it. |
Functions
The bundled subagent definitions, inside the installed package. |
|
|
Every skill and subagent this package ships, sorted by name. |
|
The agent host's config directory: |
|
Make the bundled skills and subagent visible to an agent host. |
The bundled skills directory, inside the installed package. |
Classes
|
One installable thing: a skill directory or a subagent file. |
- class openloops.skills.Asset(kind, name, source)[source]#
Bases:
objectOne installable thing: a skill directory or a subagent file.
nameis what the host will call it, and it is the file or folder name too – the agent-skill spec requires the folder name to equal the skill’sname:, so deriving one from the other cannot drift.
- openloops.skills.HOST_ENV_VAR = 'CLAUDE_CONFIG_DIR'#
Where Claude Code keeps skills and subagents, and the variable that relocates it. Honoured rather than reimplemented: a user who has moved their config has done so for a reason, and an installer that ignores it writes to a directory nobody reads.
- openloops.skills.agents_dir()[source]#
The bundled subagent definitions, inside the installed package.
- Return type:
- openloops.skills.bundled()[source]#
Every skill and subagent this package ships, sorted by name.
Discovered from the directories rather than listed here: a list would be a second place to update, and the one that gets forgotten.
- openloops.skills.host_dir(target=None)[source]#
The agent host’s config directory:
target,CLAUDE_CONFIG_DIR, or~/.claude.- Return type:
>>> import os >>> host_dir('/somewhere/else').name 'else'
- openloops.skills.install_skills(*, target=None, only=None, copy=False, force=False, dry_run=False)[source]#
Make the bundled skills and subagent visible to an agent host. Idempotent.
Links each one into
target(default:CLAUDE_CONFIG_DIRor~/.claude) so that upgrading the package upgrades the skill.copy=Truetakes a copy instead, which is also what happens by itself where symlinks are unavailable.Returns the plan it carried out – one row per asset, each carrying its verdict and the reason for it.
dry_run=Truereturns the same plan having touched nothing, which is the only way to see what a run would do before it does it.Nothing that is already there is overwritten: an occupied destination reads
conflictand is left exactly as it was untilforce=True.only=installs a subset by name. The case it exists for: you already have your own capture skill, adapted to your own fleet, and a second one competing for the same triggers is worse than either alone – so take the reader and the subagent and leave the capture skill out.- Return type:
dict[str, Any]
>>> plan = install_skills(target='/nonexistent/host', only=['openloops'], dry_run=True) >>> [row['name'] for row in plan['actions']] ['openloops']
>>> plan = install_skills(target='/nonexistent/host', dry_run=True) >>> plan['counts']['install'], plan['dry_run'] (3, True) >>> sorted({row['action'] for row in plan['actions']}) ['install']