docs/en/concepts/13-privacy.md
This page explains OpenViking privacy configs and how they work with skill write-time extraction and read-time restore.
Privacy configs separate sensitive values (such as api_key, token, base_url) from skill body content so plaintext is not permanently stored in SKILL.md, while keeping full version management and rollback.
Core goals:
Privacy configs are keyed by category + target_key.
category: config category (currently skill uses skill)target_key: target identifier (usually the skill name)Storage layout in user space:
viking://user/{user_space}/privacy/{category}/{target_key}/
├── .meta.json # metadata (active_version/latest_version/labels, etc.)
├── current.json # active version snapshot
└── history/
├── version_1.json
├── version_2.json
└── ...
current.json and history/version_x.json are full values snapshots.
values as the candidate snapshotvalues is identical to current values, no new version is createdcurrent.json)active_version in .meta.jsonWhen adding a skill via add_skill, OpenViking runs extraction + placeholderization.
add_skill
-> SkillProcessor._sanitize_skill_privacy
-> extract_skill_privacy_values (LLM extracts values)
-> placeholderize_skill_content_with_blocks (replace plaintext with placeholders)
-> privacy.upsert(category="skill", target_key=skill_name, values=...)
-> write placeholderized SKILL.md
values is used as privacy key-value pairs.{{ov_privacy:skill:{skill_name}:{field_name}}}original_content_blocksreplacement_content_blocksSKILL.md contains placeholders, not plaintext values.When reading SKILL.md, FSService.read attempts placeholder restoration automatically.
fs.read(uri)
-> get_skill_name_from_uri(uri)
-> privacy.get_current(category="skill", target_key=skill_name)
-> restore_skill_content(content, skill_name, current.values)
Current matching is suffix-based: /skills/{name}/SKILL.md, so it supports:
viking://agent/skills/{name}/SKILL.mdviking://agent/{agent_id}/skills/{name}/SKILL.mdrestore_skill_content behavior:
Placeholder exists in content and value exists and is non-empty
-> replace directly.
Placeholder exists in content but value is missing/empty
-> keep placeholder; add item to unresolved_entries.
Config key is non-empty but not referenced by any placeholder in content
-> add to extra-config notice (Configured but not referenced in content).
If unresolved_entries or extra-config entries exist, append notice block:
[OpenViking Privacy Notice]Related configured privacy values: ...Not replaced (missing config): ... (if any)Configured but not referenced in content: ... (if any)Current implementation only runs restore when a
currentprivacy config exists for that skill. If no current config exists, no notice is appended.
read automatically restores placeholdersCommon commands:
openviking privacy categories
openviking privacy list skill
openviking privacy skill <target_key>
openviking privacy upsert skill <target_key> --values-json '{"api_key":"..."}'
openviking privacy activate skill <target_key> <version>
openviking read viking://agent/default/skills/<target_key>/SKILL.md
read returns restored executable skill text