Plugin Lifecycle

The AnimaPlugin base class, lifecycle hooks, configuration hooks, and the full startup sequence.

AnimaPlugin Base Class

All plugins inherit from AnimaPlugin. Every hook is optional — implement only what your plugin needs.

from core.plugin_loader import AnimaPlugin

class MyPlugin(AnimaPlugin):
    # Set by the plugin loader (do not override)
    plugin_dir: str     # absolute path to plugin directory
    data_dir: str       # absolute path to plugin data directory
    manifest: dict      # parsed plugin.toml
    config: dict        # parsed settings.toml

Lifecycle Hooks

on_start(engine)

Called when ANIMA starts this plugin. Use for initialization, loading state, starting background threads.

def on_start(self, engine) -> None:
    """
    Args:
        engine: The core Engine instance. Use this for all API calls.
    """
    count = engine.get_belief_count()
    print(f"Plugin ready with {count} beliefs")

on_stop(engine)

Called when ANIMA stops this plugin. Use for cleanup, saving state, stopping background threads.

def on_stop(self, engine) -> None:
    """Use for: cleanup, saving state, stopping background threads."""
    pass

on_cycle_complete(engine, cycle_number, stats)

Called after each ANIMA consolidation cycle completes. Use for post-cycle processing, building, or analysis.

def on_cycle_complete(self, engine, cycle_number: int, stats: dict) -> None:
    """
    Args:
        engine: The core Engine instance
        cycle_number: Which cycle just finished (1-indexed)
        stats: Dict with cycle metrics
    """
    new_beliefs = stats.get("new_beliefs", 0)
    print(f"Cycle {cycle_number}: {new_beliefs} new beliefs")

Configuration Hooks

register_tables(db_conn)

Create plugin-specific database tables. Called once during startup. Core tables (beliefs, belief_links, etc.) already exist.

def register_tables(self, db_conn) -> None:
    """
    Args:
        db_conn: SQLite connection to the plugin's persistence.db
    """
    db_conn.execute("""
        CREATE TABLE IF NOT EXISTS my_data (
            id TEXT PRIMARY KEY,
            value TEXT,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    """)

register_endpoints(app, get_engine)

Register plugin-specific HTTP endpoints. Use APIRouter for namespaced routes.

from fastapi import APIRouter

def register_endpoints(self, app, get_engine) -> None:
    router = APIRouter()

    @router.get("/my-plugin/status")
    async def status():
        engine = get_engine()
        return {"beliefs": engine.get_belief_count()}

    app.include_router(router)

get_prompts()

Return custom prompts for this plugin's domain. If not implemented, ANIMA uses default generic prompts.

def get_prompts(self) -> dict:
    """
    Returns:
        dict with keys:
            "extraction_prompt": str - template for belief extraction
                Placeholders: {content}, {filename}, {file_type}, {max_beliefs}
            "system_prompt": str - system prompt for extraction LLM
    """
    return {
        "extraction_prompt": "Read this document and extract...",
        "system_prompt": "You are a domain expert in...",
    }

get_filters()

Return custom belief filters. The domain filter rejects beliefs that don't belong to this plugin's domain.

def get_filters(self) -> dict:
    """
    Returns:
        dict with keys:
            "domain_filter": callable(statement: str) -> bool
                Return True to REJECT the belief, False to keep it.
    """
    def _reject_off_topic(statement):
        # Use LLM inference, not keyword matching
        return False
    return {"domain_filter": _reject_off_topic}

get_orchestrator_class()

Return a custom orchestrator class that drives the plugin's lifecycle loop (scan, extract, dream, repeat).

def get_orchestrator_class(self) -> type | None:
    """
    Returns:
        A class implementing the orchestrator interface,
        or None to use the default DMSOrchestrator.
    """
    return None

Startup Sequence

This is the exact order ANIMA follows when loading your plugin:

StepWhat Happens
1ANIMA discovers plugin (scans plugins/ directory)
2Reads plugin.toml — validates manifest
3Loads plugin.py — instantiates AnimaPlugin subclass
4Calls register_tables(db_conn) — plugin creates its tables
5Calls register_endpoints(app, get_engine) — plugin adds HTTP routes
6Calls on_start(engine) — plugin initializes
7ANIMA runs cycles...
8Calls on_cycle_complete(engine, N, stats) after each cycle
9Calls on_stop(engine) on shutdown

Events Table

EventWhenArguments
on_startANIMA starts this pluginengine
on_stopANIMA stops this pluginengine
on_cycle_completeAfter each consolidation cycleengine, cycle_number, stats
register_tablesOnce during startupdb_conn
register_endpointsOnce during startupapp, get_engine
get_promptsWhen extraction needs prompts(none)
get_filtersWhen triage needs domain filter(none)
get_orchestrator_classOnce during startup(none)

Cycle Stats Dict

The stats parameter passed to on_cycle_complete:

{
    "beliefs": 512,
    "edges": 650,
    "new_beliefs": 15,
    "new_edges": 8,
    "integration_pct": 74.4,
    "dream_stats": {
        "accepted": 7,
        "rejected": 2,
    },
    "triage_stats": {
        "accepted": 12,
        "rejected": 5,
    },
}