Intent stubs in Garak are populated from five sources—typology defaults, text/JSON/YAML files in garak/data/cas/intent_stubs/, or code-generated stubs from Intent subclasses—and assembled via get_intent_stubs(). The Stub base dataclass and its subclasses (TextStub, ConversationStub) enforce type safety on content while using intent code and content hash for identity, though mutability of these fields after construction risks breaking set invariants. An intent code is a dot-separated string (e.g., harm.violence.direct) that identifies a specific harmful-intent category within Garak's typology system.
garak/services/intentservice.py supports five stub source types for an intent: a typology default stub, plain text files (.txt, one stub per line), JSON files (.json, list of strings), YAML files (.yml/.yaml, list of strings), and code-generated stubs from an Intent subclass module; the entry point for stub assembly is get_intent_stubs().[1] Text stub files for an intent code are located in garak/data/cas/intent_stubs/ and must match the glob patterns <intent_code>.txt or <intent_code>_*.txt.[1] _get_stubs_json() and _get_stubs_yaml() in garak/services/intentservice.py require the top-level item in the stub file to be a list; non-list top-level items log a warning and produce no stubs.[1] Code-generated stubs are only supported for fully-specified intent codes (length > 4); shorter prefix codes cause intentservice.py to return an empty set immediately.[1]
garak/intents/base.py defines the Stub base dataclass with an intent field (str or None) and a _content property; hashing is based on the string concatenation of intent and _content, making stubs usable in sets.[2] Stub.__eq__() compares equality by both intent and content, so two stubs with the same text but different intent codes are not equal.[2] The Stub hash is noted as contentious in garak/intents/base.py because intent and content remain mutable after instantiation, which can break set and dict invariants if a stub is mutated after insertion.[2]
TextStub in garak/intents/base.py is a Stub subclass that enforces str-only content; setting content to a non-string raises TypeError.[2]
ConversationStub in garak/intents/base.py accepts either a str or a garak.attempt.Conversation when setting content; a str is automatically wrapped in a Conversation containing a single Message, and any other type raises TypeError.[2] ConversationStub.__post_init__() also wraps a constructor-provided str into a Conversation, so passing a plain string directly in the constructor is supported.[2] ConversationStub.from_textstub() converts a TextStub into a ConversationStub by wrapping the text content in a Conversation with a single Message.[2] ConversationStub hashes differently from the base Stub: it uses repr() of the content rather than str(), in order to distinguish conversation structures.[2]
Sources