Install
$ agentstack add skill-skywatch-bsky-skywatch-agent-skills-osprey-sml-reference ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
SML (Osprey Rules) Reference
SML is a Python-like language for defining moderation rules in Osprey. Rules extract data from events, evaluate conditions, and emit effects (labels, verdicts, list actions).
Core Type System
Four fundamental types make up SML:
EntityJson & Entity — Typed Identifiers
EntityJson extracts entity IDs from event JSON; Entity constructs them from known values. Labels attach to entities, not raw strings.
# CORRECT — UserId is an Entity; labels attach to it
UserId: Entity[str] = EntityJson(type='UserId', path='$.did', required=False)
# WRONG — JsonData produces str, not Entity[str]. Labels can't attach to str.
UserId: str = JsonData(path='$.did')
EntityJson(type, path, required)→Entity[str]orEntity[int]. Use for IDs:UserId,Handle,AtUri,PdsHost.Entity(type, id)→ construct entity from known values (e.g., computed AT-URI).
JsonData — Primitive Extraction
JsonData extracts primitive values from event JSON.
DisplayName: str = JsonData(path='$.eventMetadata.profile.displayName', required=False, coerce_type=True)
PostsCount: int = JsonData(path='$.eventMetadata.profile.postsCount', required=False)
Returns: str, int, float, bool, Optional[T], or List[T].
Optional[T] — Nullable Types
Use ResolveOptional to unwrap optional values with a default.
AccountAgeSeconds: Optional[int] = JsonData(path='$.eventMetadata.accountAge', required=False)
AccountAgeSecondsUnwrapped: int = ResolveOptional(optional_value=AccountAgeSeconds, default_value=999999999)
Core Constructs
Rule — AND of Conditions
Rule(when_all=[...], description=f'...') # → RuleT
Combines multiple conditions with AND logic. All items in when_all must be the same type (all bool or all RuleT).
NewAccountSpam = Rule(
when_all=[
AccountAgeSeconds 50,
FollowersCount < 5,
],
description=f'New account spam (age={AccountAgeSeconds}s, posts={PostsCount})',
)
WhenRules — OR Trigger + Effects
WhenRules(rules_any=[...], then=[...]) # → None
Triggers effects when ANY rule passes. Always use rules_any=, never rules_all=.
WhenRules(
rules_any=[NewAccountSpam, BotBehavior],
then=[
LabelAdd(entity=UserId, label='spam'),
AtprotoLabel(entity=UserId, label='spam', comment='Auto-detected', expiration_in_hours=168),
],
)
Import & Require
Import(rules=['path/to/file.sml']) # Load models/rules from other files
Require(rule='path/to/file.sml', require_if=condition) # Conditional inclusion
Operators & Type Rules
All items in when_all must be the same type:
RegexMatch(...), comparisons (X < Y),or/andon bools →boolRule(...)→RuleT;RuleT or RuleT→RuleT- Use infix
or(A or B or C), NOT function-callor(A, B, C) notworks on bothboolandRuleT
# CORRECT — all bool
WhenRules(
rules_any=[
Rule(when_all=[PostText != '', RegexMatch(pattern=r'...', target=PostText)]),
],
then=[...],
)
# WRONG — mixing RuleT and bool in when_all
Rule(when_all=[MyRule, SomeCondition == True]) # Can't mix Rule and bool
Effects
Apply effects when rules pass:
LabelAdd(entity, label, apply_if?, expires_after?, delay_action_by?)— add labelLabelRemove(entity, label, ...)— remove labelAtprotoLabel(entity, label, comment, expiration_in_hours)— emit to Bluesky OzoneAtprotoTag(entity, tag, comment, neg?, apply_if?)— add/remove Ozone tag (neg=Trueto remove)DeclareVerdict(verdict)— for synchronous callers
WhenRules(
rules_any=[SomeRule],
then=[
LabelAdd(entity=UserId, label='flagged', expires_after=TimeDelta(days=7)),
LabelAdd(entity=AtUri, label='violation'),
],
)
Key UDFs Quick Reference
| UDF | Parameters | Returns | Purpose | |-----|-----------|---------|---------| | RegexMatch | pattern, target, case_insensitive? | bool | Regex test | | IncrementWindow | key, window_seconds, when_all | int | Sliding window counter | | GetWindowCount | key, window_seconds, when_all | int | Read counter without incrementing | | ListContains | list, phrases, case_sensitive?, word_boundaries? | Optional[str] | Match against YAML word list | | CensorizedListContains | list, phrases, plurals?, must_be_censorized? | Optional[str] | Match lookalike/obfuscated text | | HasLabel | entity, label, manual?, status?, min_label_age? | bool | Check if entity has label | | HasAtprotoLabel | entity, label | bool | Check AT Protocol label | | TimeDelta | weeks?, days?, hours?, minutes?, seconds? | TimeDeltaT | Create a duration | | AnalyzeToxicity | text, when_all | Optional[float] | ML toxicity score | | AnalyzeSentiment | text, when_all | Optional[float] | ML sentiment polarity | | CacheSetStr | key, value, when_all, ttl_seconds? | None | Store string in Redis | | CacheGetStr | key, when_all, default? | str | Read string from Redis |
Progressive Disclosure
For detailed patterns and implementation examples, see:
- 25 Labeling Patterns —
references/labeling-patterns.md. Covers all common use cases: content matching, rate limiting, strike systems, ML scoring, cross-entity labeling, caching, and more. - Naming Conventions & Anti-Patterns —
references/sml-conventions.md. Variable naming, time constants, RegexMatch rules, IncrementWindow keys, type system pitfalls, and what NOT to do. Also includes a Reviewer Checklist section with structured CONV-prefixed check IDs for systematic convention review.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: skywatch-bsky
- Source: skywatch-bsky/skywatch-agent-skills
- License: MIT
- Homepage: https://bsky.app/profile/skywatch.blue
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.