Forensic watermarking of AI-agent skill packages. Give every buyer of a paid skill a uniquely individualized — but behavior-identical — copy, so a leaked copy can be traced back to who leaked it.
Deterrence and traceability, not copy prevention. Like a watermarked ebook or a numbered screener: a determined buyer can still copy the file, but they can't do it anonymously. This is social-DRM for skill text, not encryption.
This is the open reference embedder — the public surface of a larger system.
It does one thing: canonical skill + customer_id → K changed words + a fingerprint map. Detection/attribution, multi-channel carriers, behavioral
canaries, collusion-resistant coding, and the production codebooks/registry live
outside this repo (see Scope).
Pure standard library, Python ≥ 3.9 — no third-party dependencies.
pip install -e . # or just put the skillmark/ package on your pathfrom skillmark import watermark
result = watermark(
text, # the canonical skill (SKILL.md contents)
skill_id="market-sizing",
customer_id="customer-A",
secret_key=b"...", # server-side secret; NEVER shipped
k=8, # number of sites to mark
)
result.text # the watermarked copy to ship to this customer
result.fingerprint_map # {sentence_id, offset, original, replacement} per site — keep PRIVATECLI:
export SKILLMARK_SECRET='your-server-side-secret'
skillmark embed SKILL.md --skill-id market-sizing --customer-id customer-A -k 8 \
--out SKILL.customer-A.md --map customer-A.map.jsonRun the demo: python examples/demo.py.
For each skill S and customer C:
- Normalize the skill text into stable sentence units.
- Keyed randomness:
seed = HMAC(secret_key, skill_id || customer_id). - Select
Keligible sentences. - For each selected sentence: choose one meaning-preserving phrasing variant, apply it, and record
{sentence_id, original, replacement}. - Store the fingerprint map privately.
- Ship the watermarked skill to the customer.
Attribution only works if the watermark is reproducible: given the secret key
you can re-derive exactly what customer C's copy should be and match it against a
recovered file. seed = HMAC(secret_key, skill_id ‖ customer_id) gives that —
same inputs reproduce the same copy byte-for-byte; a different customer_id
yields a different seed and different marks. Three things make it hold in
practice, and this library is built around them:
- Seed on a stable
skill_id, not a whole-document content hash. If you seed selection from a hash of the whole doc, every edit reshuffles all selections. A stable id keeps a customer's fingerprint stable across skill versions where the sentences didn't change. (You store the map per version regardless.) - Use a reproducible keyed stream over a canonical order — HMAC-SHA256, never
Python's
hash()(salted per process viaPYTHONHASHSEED) or unseededrandom. Eligible sentences are ranked and taken in a stable order. - Stable sentence IDs. Each id is a hash of the normalized sentence content, so the same sentence gets the same id across runs and regardless of position (reorder-robust).
Rather than seed one global shuffle, selection and variant choice are derived
per sentence — rank_i = HMAC(seed, sentence_id) (take the K smallest);
variant_i = HMAC(seed, sentence_id‖"v") mod n. This keeps selection stable under
edits (adding or removing one sentence only affects that sentence near the
K-threshold) and makes each mark independent — which is what a future detector
needs for partial-leak and collusion attribution.
A watermark on a skill must never change what the skill does. The default
ConservativeTableProvider ships only a small set of behavior-neutral,
discourse-level phrase variants (e.g. for example ↔ for instance, prior to
↔ before). It deliberately never touches imperative modals (must/should/may),
negations, numbers, code, or identifiers.
For real use, supply your own VariantProvider (richer, LLM-vetted variants) and
pass a validate(original, replacement, sentence) -> bool hook to watermark()
that runs the skill's own evaluation and rejects any variant that changes
behavior.
In this repo (public): canonical skill parser, carrier selection, variant generation (default table), the deterministic embedder, and a local example map store.
Not here (private / future): the detector/attribution service, the multi-channel carriers (schema field names, tool-call variants, default filenames, tests, behavioral canaries), collusion-resistant (Tardos-style) coding, production variant codebooks, the customer fingerprint registry, and secret keys.
MIT — see LICENSE. Note the MIT license grants copyright permissions only; it does not grant any patent rights. The underlying SkillMark watermarking system is the subject of a separate patent application. Nothing here is legal advice.