Design, implement, or diagnose Xberg plugin traits, typed registries, priority collisions, lifecycle, native extractors, and Alef-generated Python plugin bridges. Load for plugin-system work, not ordinary extractor parsing.
73
91%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
| Type | Trait | Location |
|---|---|---|
| Document extractor (binding-facing) | DocumentExtractor: Plugin | plugins/extractor/trait.rs |
| Document extractor (in-crate) | InternalDocumentExtractor: Plugin | plugins/extractor/trait.rs |
| OCR backend | OcrBackend: Plugin | plugins/ocr.rs (a file, not a directory) |
| Post processor | PostProcessor: Plugin | plugins/processor/trait.rs |
| Validator | Validator: Plugin | plugins/validator/trait.rs |
| Embedding backend | EmbeddingBackend: Plugin | plugins/embedding.rs |
| Reranker backend | RerankerBackend: Plugin | plugins/reranker.rs |
| Tokenizer backend | TokenizerBackend: Plugin | plugins/tokenizer.rs |
| Renderer | Renderer: Plugin | plugins/renderer.rs |
Plugin (plugins/traits.rs) is Send + Sync and requires name(); version(),
initialize(), shutdown(), description(), and author() have defaults. There is no
'static trait bound; registry-owned Arc<dyn Trait> supplies the necessary lifetime.
InternalDocumentExtractorDocumentExtractor is the binding-facing surface. In-crate extractors implement
InternalDocumentExtractor and get DocumentExtractor from a blanket impl. Implementing
DocumentExtractor directly in this crate is the wrong layer.
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl InternalDocumentExtractor for MyExtractor {
async fn extract_content(&self, content: &[u8], mime_type: &str, config: &ExtractionConfig)
-> Result<InternalDocument> { /* ... */ }
fn supported_mime_types(&self) -> &[&str] { &["application/x-custom"] }
fn priority(&self) -> i32 { 50 }
}extract_path has a default that reads the file and delegates to extract_content (and
errors without tokio-runtime).
Always use the two-arm cfg_attr form for async_trait. A bare #[async_trait] does not
match the trait declaration on wasm32.
The public trait has exactly four items — extract, supported_mime_types, priority,
can_handle. There is no as_sync_extractor; writing one is a compile error. WASM sync
support is the separate SyncExtractor trait — see wasm-constraints.
| Range | Use |
|---|---|
| 0-25 | Fallback/low-quality |
| 26-49 | Alternative extractors |
| 50 | Default (built-in) |
| 51-75 | Premium/enhanced |
| 76-100 | Specialized/high-priority |
The registry selects the highest priority extractor for each MIME type. The ranges are
conventions over an unclamped i32; negative and values above 100 are representable. Equal
MIME and priority is a collision: the later registration replaces the earlier entry and
warns. Give competing plugins distinct priorities.
// crates/xberg/src/extractors/mod.rs -> register_default_extractors()
let registry = get_document_extractor_registry();
let mut registry = registry.write();
registry.register(Arc::new(MyExtractor::new()))?;Feature-gate optional formats:
#[cfg(feature = "office")]
{
registry.register(Arc::new(DocxExtractor::new()))?;
registry.register(Arc::new(PptxExtractor::new()))?;
}#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl PostProcessor for MyProcessor {
async fn process(&self, result: &mut ExtractedDocument, config: &ExtractionConfig)
-> Result<()> {
result.content = process_content(&result.content);
Ok(())
}
fn processing_stage(&self) -> ProcessingStage { ProcessingStage::Middle }
}The enum is ProcessingStage and the accessor is processing_stage(). Stages:
Early (default) → Middle → Late. process takes &mut ExtractedDocument, not an owned
result.
Send + Sync — Plugin requires it.InternalDocumentExtractor, never DocumentExtractor.cfg_attr async_trait form on every plugin trait impl.#[cfg(feature = "...")] at the registration site.ensure_initialized() (extractors/mod.rs), called before first extraction."pdf-extractor").#[cfg_attr(alef, alef(skip))] or the binding regen aborts — see
alef-generated-bindings.plugins/registry/mod.rs.
There is no universal PluginRegistry.Arc<parking_lot::RwLock<_>>. Their guards are not poisoned and
.read()/.write() return guards directly.HashMap<mime, BTreeMap<priority, entry>>: exact MIME lookup is
constant-time on the outer map; wildcard-family lookup scans registered MIME keys.initialize() and rejects a plugin whose initialization fails.
Registries support register, remove, clear, and shutdown_all; there is no hot reload.The Python bridge is generated into crates/xberg-py/src/lib.rs; there is no hand-written
plugins.rs. Change Alef/configuration and regenerate rather than editing the bridge.
Python::attach. Async host calls enter Python from
tokio::task::spawn_blocking and propagate the caller's contextvars context.allow_threads is in use.Serialize + Deserialize + Default, including unit enums.XbergError::Other with plugin and method context; the original
Python exception type and traceback are not retained. Infallible methods can only warn and
return Default::default(), so a default may indicate bridge failure rather than real data.XbergError::Plugin, which is fallback-eligible;
do not assume Python bridge errors have the same fallback behavior.04336bd
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.