LoopEngineBETA

Docs

Documentation

How LoopEngine works, from scaffold to durable approvals — agents, adapters, permission rules, and abilities.

Skills

core/skillgarden/ decides which skill files actually get injected into context for a given turn — frontmatter parsing, discovery, activation, and its own budget accounting — not a static "always include this file" mechanism.

An agent's skillsDirs (see Configure an Agent) points at one or more folders of SKILL.md files; SkillGarden decides per turn which of them are relevant enough to load, keeping the ones that aren't out of context entirely rather than paying their token cost on every single call.

How lazy loading actually works

Two phases, deliberately split:

  1. Index now — buildIndex() scans every SKILL.md under skillsDirs, but parses only the frontmatter — name and description, nothing from the body. Entries fill a token budget (skillIndexBudgetTokens, default 2000 — Claude Code itself caps this kind of index around 1% of the window) in order, and whatever doesn't fit is dropped from the index entirely rather than truncated mid-entry. This lightweight index is what actually sits in context on every turn, however many skills exist.
  2. Load later — the full markdown body is only ever read off disk by load()/invoke(), and only for the one skill actually being used. A skill nobody invokes this session costs nothing beyond its one index line.

The model doesn't get one tool per skill — that would make the tool list itself scale with skill count, undoing the whole point. Instead, one shared Skill tool schema is added whenever skillIndex.length is non-zero, taking { skill, args }: the skill's own name is an input, not a separate tool. When the model calls it, run-agent.ts's loop recognizes that one specific tool name and calls skillGarden.invoke(skill, args) right then — reading the file, stripping frontmatter, substituting $ARGUMENTS/$1/$2 placeholders with args — and answers with the full body as the tool_result content. This bypasses ActAuth gating and ToolLane scheduling entirely: a Skill call isn't a real tool call with side effects, it's a context-injection instruction the model just emitted as one.

A skill can also restrict itself to specific work instead of always being eligible: frontmatter's optional paths (glob patterns, */?) only activates it once a touched file in the session matches one of them — a skill with no paths is always eligible once it fits the index budget.

Adding a skill

Three ways, in increasing order of how much loopengine does for you:

  1. Text editor — write agents/<name>/skills/<id>/SKILL.md by hand: a ----fenced frontmatter block (name, description, optional paths) followed by the skill's own markdown body. The only way to reach a nested skill (agents/<name>/skills/deploy/web/SKILL.md, namespaced deploy:web by discovery) or set paths — the Web UI form below doesn't expose either.
  2. Web UI, Skills tab — a flat id, description, and body field; writeSkill() regenerates the frontmatter from those two fields on save. Deliberately scoped to flat (non-nested) skills only, and doesn't preserve any other frontmatter key (like paths) a hand-written skill might have — editing one through this tab drops them.
  3. loopengine add-ability — installs a skill alongside its tool and permission rules as one unit, rather than a skill alone. Right when the skill documents a tool that also needs its own actauth.yml entry to be safe to use — see Abilities and Ability System.