Vancetope — Skills
A Skill is a reusable capability bundle focused on a single LLM turn: description + auto-trigger + prompt extension + tool whitelist + reference docs. Skills activate either implicitly (Arthur recognizes a suitable Skill based on user intent) or explicitly (user types
/skill <name>). Both paths lead to the same Ford loading code. Skills are orthogonal to Recipes: Recipes configure how an Engine runs (Engine choice, defaults, lock), Skills are attached capabilities that define what it does.Persistence: Skills reside as document subtrees under
skills/<name>/SKILL.md(frontmatter + Markdown body) plus sibling reference files. The cascade_user_<login> → project → _tenant → classpath:vance-defaults/skills/runs viaDocumentService. There is no longer a separate Mongo collection for Skills — editing is done via the Document Editor.
See also: recipes ford-engine arthur-engine settings-system
1. Terms and Delimitation
| Term | What it is | Cardinality | Location |
|---|---|---|---|
| Engine | Algorithm with lifecycle. Java code. | few (3-5) | vance-brain/.../<name>/ |
| Recipe | Engine configuration bundle: which Engine, which defaults, prompt prefix, tool adjustments, lock flag. | many (10-100) | YAML + Mongo |
| Skill | Capability bundle: description, auto-trigger, prompt extension, tool whitelist, reference docs. Composable. | many (open-ended, incl. user Skills) | Document subtree skills/<name>/SKILL.md (Cascade) |
| Process | Running instance. Has one Recipe and zero-to-N active Skills. | N per Session | Mongo |
Recipe vs. Skill — the Delimitation:
- Recipe decides the Engine path (Ford? Arthur? with which defaults? locked?). A spawn operation requires exactly one Recipe.
- Skill decides the task coloring (which domain, which tools, which reference docs). A spawn operation can carry 0..N Skills.
Example: Recipe analyze (engine=ford, model=default:analyze, locked=false) is spawned with Skills [code-review, typescript-style]. Ford loads Recipe defaults and merges prompt extensions/tool whitelists/reference docs from both Skills.
What Skills are NOT:
- Not Engines. Skills do not define a Lane lifecycle, Inbox processing, or status transitions.
- Not Recipes. Skills do not select an Engine, set lock semantics, or have a
default-Reciperole. - Not Tools. Tools are atomic capabilities (
web_search,process_create); a Skill uses Tools, but is not one.
2. Skill Schema (YAML Frontmatter + Markdown Body)
A Skill is a Document subtree:
skills/
<name>/
SKILL.md # frontmatter + body (= promptExtension)
references/
<something>.md # associated reference documents
<name> comes from the directory name, not from a name: field. The SKILL.md contains YAML frontmatter (between --- fences) followed by the Markdown body. The body is the promptExtension.
| Frontmatter Field | Type | Required | Meaning |
|---|---|---|---|
title |
String |
yes | Display name (UI, Skill picker) |
description |
String |
yes | One line — what does the Skill do? Used (a) for auto-trigger matching (b) displayed in UI tooltip (c) given to Arthur in Skill selection description |
version |
String (semver) |
yes | 1.0.0, 2.3.1. Manually assigned |
triggers |
List<SkillTrigger> |
no | Auto-activation conditions (pattern match, intent keywords). Empty = only explicitly activatable |
tools |
List<String> |
no | Tool names that the Skill requires. When the Skill is active, these are added to the Engine/Recipe whitelist — never removed |
manualPaths |
List<String> |
no | Folder paths (relative to Document root) that the Skill contributes to manual_read/manual_list as long as it is active. Pattern “short Skill body + detailed Manuals on demand”. Recipe paths from params.manualPaths retain precedence — Skill paths are appended. Sanitization like manual_read: no .., no leading /, backslashes become / |
referenceDocs |
List<SkillReferenceDoc> |
no | Sibling files within the Skill subtree. Appended to the prompt after promptExtension or loaded on demand via Tool (see §5b) |
tags |
List<String> |
no | Discovery hints |
enabled |
boolean |
default true |
Disabled Skills are neither explicitly nor implicitly activatable; a disabled-override hides outer-layer Skills with the same name |
SkillTrigger:
| Field | Type | Meaning |
|---|---|---|
type |
PATTERN \| KEYWORDS |
Matching strategy |
pattern |
String (Regex) |
for type=PATTERN |
keywords |
List<String> |
for type=KEYWORDS |
SkillReferenceDoc:
| Field | Type | Meaning |
|---|---|---|
title |
String |
For INLINE: Doc header for prompt embed. For ON_DEMAND: the manual_read argument used to retrieve the body later |
file |
String |
Path relative to the Skill subtree, e.g., references/checklist.md |
summary |
String |
Optional. One-line teaser that appears after the title in the On-Demand listing (- <title> — <summary>). Ignored for INLINE |
loadMode |
INLINE \| ON_DEMAND |
INLINE = immediately embedded in the prompt upon Skill activation. ON_DEMAND = body is not embedded. Instead, a listing block “On-demand references — load via manual_read:” appears in the system prompt — fits the pattern “short Skill body + detailed Manuals on demand” (see manualPaths) |
Reference-File-Layer-Pinning: A referenceDocs[i].file is read against the cascade level that provided the SKILL.md itself — never re-cascaded. This prevents a user Skill from accidentally pulling a references/checklist.md from the _tenant layer just because its own layer doesn’t have one.
3. Cascade — How a Skill is Resolved
For lookupByName("code-review", scopeContext), the SkillLoader runs:
load(tenantId, userId, projectId, name) → Optional<ResolvedSkill> :=
1. _user_<userId>/skills/<name>/SKILL.md → source = USER
2. DocumentService.lookupCascade(tenantId, projectId,
"skills/" + name + "/SKILL.md")
a. <project>/skills/<name>/SKILL.md → source = PROJECT
b. _vance/skills/<name>/SKILL.md → source = VANCE
c. classpath:vance-defaults/skills/<name>/SKILL.md → source = RESOURCE
3. → empty
First hit wins. User override beats Project, Project beats _tenant, which beats Resource. Step 1 is skipped if userId is null (system spawn without user binding). Step 2 runs with _tenant as Project default if projectId is null.
Listing (Skill picker, auto-trigger search): listAvailable(scopeContext) returns the union of all levels with cascade dedup by name (inner layer beats outer). For auto-trigger matching, Arthur iterates over this list. enabled: false in a layer removes a Skill from the result — even if outer layers provide it.
Bundled Skills are the source of truth for standard capabilities. They are located as directories under vance-brain/src/main/resources/vance-defaults/skills/<name>/SKILL.md (+ optional reference doc files). Format see §9.
Hot-Reload:
- Resource Skills (classpath) require a Brain restart.
- USER/PROJECT/
_tenantSkills are read fresh from Mongo on each lookup.
4. Activation Paths
4a. Implicit (Auto-Trigger by Conversation Engine)
Every Conversation Engine (Ford, Arthur, future ones) calls the SkillTriggerMatcher as a pre-turn hook, as soon as the user message is written to the chat log and before the system prompt is assembled:
- Build
SkillScopeContextfrom the Process (Tenant + Project + User from the Session) SkillResolver.listAvailable(scope)— all Skills visible for this scope (Cascade)- Filter:
enabled == truetriggersnot empty- Skill is not already active on the Process
- If
process.allowedSkillsOverride != null: Skill must be in the whitelist
- Trigger match against the user input (lowercase, PATTERN compile cache):
PATTERN: Java Regexfind()on the lowercased input (CASE_INSENSITIVE)KEYWORDS: Tokenization of the input ([^a-z0-9]+ → split, tokens ≥ 2 characters). Trigger fires if ≥ 50% of keywords occur as whole tokens
- Per match:
SkillSteerProcessor.activate(process, name, oneShot=true)— the Skill is active only for this turn. In case of whitelist violation (race), a warning log is issued, turn continues - Multiple matches are allowed — the LLM receives the combined prompt extensions and tool whitelists
One listAvailable call per turn (Mongo + Classpath) plus O(skills × triggers) matching. Compiled Regex patterns are cached in the matcher. No listing cache in v1 — Mongo costs dominate anyway.
Sub-Process Spawn is orthogonal. If Arthur wants to spawn a worker, he calls process_create(recipe=…) — the worker Recipe carries its own Skill configuration (defaultActiveSkills / allowedSkills from §7). Auto-trigger and spawn decide independently.
4b. Explicit (User Command)
User types in chat:
/skill code-review
Optionally with user message on the same line:
/skill code-review look at PR #42
Behavior:
- Sticky Activation (Default): Skill remains active for subsequent turns of this session until
/skill clearor session end. - One-Shot with
--once:/skill code-review --onceactivates only for the next turn. - Multiple Simultaneously:
/skill reviewfollowed by/skill typescript-styleresults in active list[review, typescript-style].
List / Status / Clear:
| Command | Effect |
|---|---|
/skill list |
Lists available Skills in the current scope, marking which are active |
/skill clear |
Deactivates all active Skills for this session |
/skill clear <name> |
Deactivates only <name> |
4c. Conflict Resolution Implicit ↔ Explicit
Explicit always wins. If the user has said /skill foo, Arthur’s auto-match will not override foo. Arthur can implicitly activate additional Skills, but not explicitly deactivate explicit ones.
4d. UI Visibility
Active Skills are displayed in the chat UI as a badge (Skill: code-review active), regardless of the activation path. The Web UI <EditorShell> reserves a slot for this above the input line (see web-ui.md §7).
5. Loading in Ford
An active Skill affects three places at Lane turn time:
5a. System Prompt
Before the LLM call, Ford assembles the system prompt from:
[1] Engine default prompt (Ford.SYSTEM_PROMPT)
[2] Recipe-promptPrefix (APPEND/OVERWRITE according to promptMode)
[3] Active Skills, in activation order:
--- Skill: <name> ---
<description>
<promptExtension>
--- Reference-Doc: <title> ---
<content> (for each SkillReferenceDoc with loadMode=INLINE)
Merging occurs in a new class SkillPromptComposer in vance-brain/.../skill/. SystemPrompts.compose(...) calls it for the active Skills from the Process.
5b. Tool Whitelist
The tool pool results from:
allowed = (engine.allowedTools()
∪ recipe.allowedToolsAdd)
\ recipe.allowedToolsRemove
∪ ⋃ skill.tools (for each active Skill)
Skills can only add tools, not remove them. If a Recipe removes a tool tool_x from the whitelist, but a Skill explicitly requires tool_x, Skill wins (tool is available). Rationale: a Skill running without its required tool is broken — Recipe restrictions should not silently break Skills.
5c. Reference Docs
INLINE docs are appended to the system prompt (see §5a). ON_DEMAND docs are lazy-loaded via the tool skill_reference_doc(skill, title) — not in v1.
5d. Storage Location of Active Skills
ThinkProcessDocument gets a new field:
private List<ActiveSkillRef> activeSkills; // serialized in Mongo
record ActiveSkillRef(
String name,
SkillScope resolvedFromScope, // where it was found
boolean oneShot, // --once flag
Instant activatedAt
) {}
On the next turn, Ford reads process.getActiveSkills(), resolves each via the scope (cache per turn), and merges them as above. One-shot Skills are removed from the list after the turn.
6. Slash Command /skill
6a. Client-Side (vance-foot, later Web-UI)
Both clients implement /skill as a slash command. In vance-foot via new SkillSlashCommand (Bean, SlashCommand interface) in directory vance-foot/src/main/java/de/mhus/vance/foot/command/.
Subcommands:
| Input | Output Action |
|---|---|
/skill <name> [args...] |
ExternalCommand("skill.activate", { name, args, oneShot=false }) |
/skill <name> --once [args...] |
ExternalCommand("skill.activate", { name, args, oneShot=true }) |
/skill list |
REST call GET /brain/{tenant}/skills/active + GET /brain/{tenant}/skills/effective (no roundtrip through Engine) |
/skill clear |
ExternalCommand("skill.clearAll") |
/skill clear <name> |
ExternalCommand("skill.clear", { name }) |
If the user specifies another message on the same line (/skill foo look here), the client sends two messages: first the ExternalCommand activation, then the normal UserChatInput. Both land idempotently in the same pending queue, drained in the same Lane turn.
6b. Server-Side (vance-brain)
The existing ProcessSteerHandler (WebSocket) forwards process-steer with command="skill.*" to a new SkillSteerProcessor, which either:
- atomically updates the
activeSkillslist of the Process (MongoTemplate.findAndModify), or - responds directly for listing operations, without an Engine turn.
Important: The slash command is a structured SteerMessage, not text. The LLM does not see the activation as user input — it only sees the extended system prompt on the next turn. Thus, /skill foo bar baz is not interpreted as a question to the LLM, but as a control signal.
6c. Auto-Complete
vance-foot uses JLine-3-Completer, which fills /skill <TAB> with names from SkillResolver.listAvailable(scope). Web-UI uses a <VSlashCommandPicker> analogously (see web-ui.md §7).
7. Composition with Recipes
Recipes control Skills on two axes — which Skills are active upon spawn and which Skills are allowed to become active at all. Schema details in recipes.md §6c; here the Skill view.
| Recipe Field | Type | Meaning for Skills |
|---|---|---|
defaultActiveSkills |
List<String> |
Skills written to process.activeSkills as sticky fromRecipe=true upon spawn |
allowedSkills |
List<String>? |
Whitelist. If set, only these Skills may ever become active — trigger match, default active, /skill. Snapshot as process.allowedSkillsOverride |
7a. Spawn Behavior
RecipeResolver.applyDefaulting(...) passes both fields to AppliedRecipe. ThinkProcessService.create(...):
- Converts
defaultActiveSkillsnames intoActiveSkillRefEmbeddedentries (fromRecipe=true, sticky,resolvedFromScope=RESOURCEas default — Engines re-resolve on turn) - Persists
allowedSkillsasSet<String>?onallowedSkillsOverride— snapshot. Subsequent Recipe edits do not affect the running Process
Validation during Recipe Parse: if allowedSkills is set, defaultActiveSkills ⊆ allowedSkills must hold — otherwise IllegalStateException in RecipeLoader.parse.
7b. Whitelist Filter in Activation Path
Three activation paths respect process.allowedSkillsOverride:
| Path | Behavior for Skill outside Whitelist |
|---|---|
/skill <name> (User explicit) |
SkillSteerProcessor.activate throws SkillNotAllowedByRecipeException (“Skill ‘foo’ is not allowed by recipe ‘analyze’”) |
| Trigger Match (future, §4a) | listAvailable is filtered against the whitelist before matching — trigger Skills outside are invisible |
defaultActiveSkills on Spawn |
Already validated during Recipe parse — Recipe would not load at all |
A null whitelist means “no restriction” (current behavior). An empty list [] means “lockdown — no Skill ever active”.
7c. Lock Semantics
| Recipe Lock | User /skill clear |
Trigger Match | User /skill <name> |
|---|---|---|---|
locked=false |
allowed (also fromRecipe Skills) | allowed | allowed |
locked=true |
only Skills NOT originating from the Recipe | allowed (additive, in whitelist) | allowed (additive, in whitelist) |
Recipe-bound Skills (fromRecipe=true) are a default — they are active because the Recipe needs them. The user may activate additional ones (if in allowedSkills), but cannot disable the Recipe set if locked=true.
7d. Conflicts Between Recipe Skills and User Skills
If recipe.defaultActiveSkills = ["typescript-style"] and the user calls /skill typescript-style: idempotent — the existing fromRecipe=true entry is retained, no double-add. If the user wanted the same Skill as one-shot, the oneShot flag flips (sticky remains sticky on conflict — Recipe author intent wins).
Cascade resolution: activation is against the cascade visibility of the Process (SkillScopeContext from Tenant + Project + User). Recipe default Skill typescript-style resolves against the cascade at spawn time — if a Project override exists, it wins. The Recipe only defines the name, not the source.
8. CRUD via the Document Editor
Skills are Documents — editing therefore happens via the Document layer:
- Tenant-wide Skill (
_tenant): Document subtree with pathskills/<name>/SKILL.mdin the_tenantProject. - Project Skill: same path within the respective user Project.
- User Skill: same path within the per-user
_user_<login>system Project. - Reference files: Sibling paths under the same Skill directory (
skills/<name>/references/checklist.mdetc.) in the same Project — not resolved via the cascade. - Rollback to Default: Delete SKILL.md → next lookup falls back to the next outer cascade level.
There is no longer a dedicated Skill REST controller. Who is allowed to do what comes from the Document ACL model, once that is defined. The Document paths themselves carry visibility — a user Skill is in the _user_<login> Project and is therefore by definition only visible to that user (another user has no access to that Project).
9. Bundled Skills
Bundled Skills are located as directories under vance-brain/src/main/resources/vance-defaults/skills/<name>/:
vance-defaults/skills/
code-review/
SKILL.md # Frontmatter (YAML) + Markdown body
references/
checklist.md # ReferenceDoc 1
patterns.md # ReferenceDoc 2
typescript-style/
SKILL.md
SKILL.md format (same at every cascade level — also in Mongo documents in _tenant / Project / _user_<login>):
---
title: Code Review
version: 1.0.0
description: Use when user asks to review PRs, diffs, or code changes
tags: [review, quality]
triggers:
- type: KEYWORDS
keywords: [review, diff, PR, pull request]
- type: PATTERN
pattern: "schau.*(diff|PR|änderung)"
tools:
- file_read
- shell_exec
referenceDocs:
- file: references/checklist.md
title: Review Checklist
loadMode: INLINE
- file: references/patterns.md
title: Common Patterns
loadMode: INLINE
---
# Skill Body (= promptExtension)
You are in code review mode. Your task:
- ...
The Markdown body after the frontmatter is the promptExtension. Reference doc files are separate because they can become large.
Pebble Templating: The body is passed through the same PromptTemplateRenderer at compose time that renders recipe promptPrefix and Engine default prompts. The render context is identical (tier, model, provider, mode, profile, recipe, engine, lang, params) — see recipes §5. This allows a Skill to hold tier-, mode-, or provider-specific variants within its body ({% if tier == "small" %}…{% endif %}) instead of writing two Skills per variant. Reference doc content is not rendered — docs are author-controlled data, not templates, and literal {% %} text in a doc file must pass through unchanged. Skills with Pebble syntax errors are skipped with a WARN log; other active Skills compose normally.
BundledSkillRegistry parses on boot:
- Iterates
classpath:skills/*/SKILL.md - Parses frontmatter (SnakeYAML) + body
- Loads referenced reference doc files from the same layer as the SKILL.md
- Validates (
<name>matches directory,triggers/toolswell-formed) - Parse errors on a single Skill are logged and skipped — the Brain continues to start, defective Skills are invisible in
recipe_list//skill list.
10. Service API
SkillLoader in vance-brain/.../skill/ is the sole source:
public Optional<ResolvedSkill> load(
String tenantId, @Nullable String userId,
@Nullable String projectId, String name);
public List<ResolvedSkill> listAvailable(
String tenantId, @Nullable String userId, @Nullable String projectId);
SkillResolver is a thin facade:
public Optional<ResolvedSkill> resolve(SkillScopeContext ctx, String name);
public List<ResolvedSkill> resolveAll(SkillScopeContext ctx, List<String> names);
public List<ResolvedSkill> listAvailable(SkillScopeContext ctx);
SkillScopeContext is a record (tenantId, userId, projectId) — analogous to ToolInvocationContext, stripped for Skill lookups.
ResolvedSkill additionally carries source: SkillScope (USER/PROJECT/VANCE/RESOURCE) so UI/logging can display the origin.
11. Data Sovereignty
SkillLoader is the only place that materializes Skills — it calls DocumentService.lookupCascade and findByPath, no one else does this for Skill paths. Other services call SkillResolver.resolve(...) / listAvailable(...). Convention is strict, according to CLAUDE.md.
12. DTOs (vance-api)
With @GenerateTypeScript annotation for Web-UI generation:
| Class | Purpose |
|---|---|
SkillScope |
Enum (USER/PROJECT/VANCE/RESOURCE) — Cascade source of a resolved Skill |
SkillTriggerDto |
Trigger representation |
SkillReferenceDocDto |
Reference Doc |
SkillTriggerType |
Enum (PATTERN/KEYWORDS) |
SkillReferenceDocLoadMode |
Enum (INLINE/ON_DEMAND) |
ActiveSkillRefDto |
Active Skill reference in the Process — for UI badge rendering |
13. Skill Scripts (Phase Plan)
A Skill can bring its own JavaScript snippets. Unlike external tools referenced in tools, scripts are part of the Skill subtree — they are versioned, activated, and pinned to the same cascade level as the SKILL.md.
13.1. Schema (Phase 1 — Data Model, not yet implemented in SkillLoader)
In the Skill subtree:
skills/<name>/
SKILL.md
scripts/
fetch-diff.js — one file per script
post-comment.js
Frontmatter lists the scripts:
scripts:
- name: fetch-diff
target: BRAIN
file: scripts/fetch-diff.js
- name: post-comment
target: FOOT
file: scripts/post-comment.js
Multiple scripts per Skill are explicitly allowed — a Skill bundles thematically related operations (“PR review” with fetch_diff, lint, post_comment). Lookup for script files runs via the same sibling reader as ReferenceDocs (same layer, no cross-layer lookup).
13.2. Trigger Model
Primary Mode (Phase 2): Script = Tool. When a Skill is active (per Recipe, auto-trigger, or /skill), the Engine lifecycle registers a virtual tool per script in the turn’s tool loop. Convention: skill_<skillname>__<scriptname>. The LLM decides when to call it, like any other tool. When deactivated, the tool is removed.
Skill triggers (PATTERN/KEYWORDS, §4) explicitly do not trigger a script — triggers decide Skill activation, the LLM tool loop decides script calls. Cleanly separated.
13.3. Execution Location
target |
Engine | Purpose |
|---|---|---|
BRAIN |
JsEngine (Brain, GraalJS/Rhino) |
Server-side operations — workspace files, memory, other Brain tools, REST calls. |
FOOT |
ClientJsEngine (Foot-CLI, GraalJS/Rhino) |
Client-side operations on the user’s machine — local FS, local subprocess, editor integration. The Brain sends script + args over the WS connection; the Foot evaluates and sends the result back. |
BRAIN and FOOT use the same sandboxed GraalJS (allowAllAccess(false)) — no Java interop, no automatic FS access. What the script can do is decided by injected host bindings (see §13.4).
13.4. Host Bindings — the Trust Boundary (Phase 3, deferred)
Today, the JS sandbox is tight: pure compute, no project context. For scripts to do useful things, the Engine lifecycle must inject a vance global into the GraalJS context per call. The final surface is part of the Phase 3 implementation; proposed sketch:
// in the script
const diff = vance.tool('git_diff', { range: 'HEAD~1' }); // Call Brain tool
vance.workspace.write('reports/diff.md', diff); // Brain only
return { lines: diff.split('\n').length };
Proposed Surface (Brain):
| Binding | Purpose |
|---|---|
args |
Input parameters from the LLM tool call (JSON object) |
vance.tool(name, params) |
Synchronously call another registered Brain tool — re-entrancy depth limited |
vance.workspace.read/write/list/delete(path) |
Project workspace FS — project-bound, sandboxed like WorkspaceService today |
vance.context() |
Active Process/Skill/Recipe metadata (read-only) |
vance.log(level, msg) |
Trace logging to Skill output |
Proposed Surface (Foot):
| Binding | Purpose |
|---|---|
args |
same as Brain |
vance.tool(name, params) |
Client tool from the Foot tool set (e.g., local filesystem tools) |
vance.fs.read/write/list/delete(path) |
Local FS, scoped to the user’s Foot working directory |
vance.exec(cmd, args) |
Local subprocess execution — if at all, then with explicit user approval |
vance.log(level, msg) |
Identical to Brain — output lands in Brain trace |
Sandbox boundary: what is not exposed via vance cannot be done by the script. Open points (sub-spec): re-entrancy limit, approval flow for vance.exec, timeout semantics, quota.
13.5. FOOT Routing (Phase 3, deferred)
When calling a script with target=FOOT:
- Brain serializes
{ skillName, scriptName, args }into a WS frameprocess-skill-script-execute. - Foot receives, evaluates in
ClientJsEnginewith local bindings, sends result back. - Brain passes the result as tool result to the LLM loop.
Requires an extension of the client protocol (see client-protokoll-erweiterbarkeit.md). Until Phase 3 is implemented, Brain can be configured to respond to FOOT scripts with “ERROR: foot routing not implemented”.
13.6. Phase Status
| Phase | Content | Status |
|---|---|---|
| 1 | Data model (scripts field, DTOs, Embedded), Editor UI with JS CodeEditor |
implemented |
| 2 | Skill-as-Tool mounting (Engine lifecycle registers scripts as tools in the loop) | open |
| 3 | Host bindings (vance.* surface), sandbox tightening, quota |
open |
| 4 | FOOT routing (WS protocol extension) | open |
In v1 (Phase 1), the scripts field is only stored and edited — the Engine lifecycle ignores it. This is intentional: editor and data model are in place, the runtime contract is not rushed.
14. Visibility & Administration
| Scope | Who can read | Who can write |
|---|---|---|
RESOURCE |
all | only code commit (Resource file under vance-defaults/skills/) |
VANCE |
all Tenant users | Tenant Admin via Document Editor in the _tenant Project |
PROJECT |
all Project members | Project Admin via Document Editor in the respective Project |
USER |
only the owner | only the owner via Document Editor in the _user_<login> Project |
Authorization comes from the Document ACL model — the Document Editor decides who has write access to which Project, and thus indirectly, who can edit Skills at which cascade level. A user Skill is in the _user_<login> Project and is therefore by definition only writable by that user; another user has no access to the Project.
15. Open Points
- Auto-Trigger Language. Currently Regex + keyword matching. If implicit detection works poorly: a subsequent LLM call (“which Skill fits?”) as a second stage — separate spec.
- Skill Marketplace. Sharing between Tenants is not planned for v1. If needed: extra
PUBLICscope with its own sync mechanism. - Versioning. Currently manual
versionstrings, no migration. For breaking changes, the user must clean up themselves. - Reference-Doc
ON_DEMAND. v1 is allINLINE.ON_DEMANDrequires a new toolskill_reference_doc(skill, title)and dynamically changes the token balance — address separately. - Recipe
skillsLock Granularity. Today,recipe.locked=trueatomically locks all Recipe Skills. If finer granularity is needed (individual ones removable):recipe.skillLocks: [name]as a later extension. - Skill Telemetry. Which Skills were activated how often (implicitly/explicitly), with what success? Needed once the list grows — separate spec.