Files
ss-tools/docs/adr/ADR-0004-plugin-architecture.md
busya 24d3b7d1f9 refactor(task-manager): implement task resilience and execution lifecycle improvements
Enhance the reliability and observability of the task execution engine
by introducing retry mechanisms, idempotency, and structured progress
tracking.

- Implement centralized retry logic with exponential backoff support
  in `JobLifecycle`.
- Add `retry_task` API endpoint and `TaskManager` method for manual
  task restarts.
- Introduce task idempotency using `_idempotency_key` to prevent
  duplicate executions.
- Add `retry_count`, `max_retries`, `last_error`, and `progress` fields
  to the `Task` model and ensure persistence via `TaskPersistenceService`.
- Upgrade `SchedulerService` to use differential synchronization with
  the persistent `SQLAlchemyJobStore` for better job durability.
- Implement structured heartbeat logging to support real-time progress
  updates.
- Update project documentation and ADRs to reflect the new plugin
  runtime and task resilience patterns.
- Add comprehensive unit and integration tests for the new task
  lifecycle features.
2026-07-12 15:31:56 +03:00

4.6 KiB
Raw Blame History

[DEF:ADR-0004:ADR]

@STATUS SUPERSEDED

@PURPOSE Historical proposal for subprocess-isolated, TOML-manifest plugins. Superseded by ADR-0016, which records the implemented in-process PluginBase runtime.

@SUPERSEDED_BY [ADR-0016:ADR]

@RELATION DEPENDS_ON -> [ADR-0001:ADR]

@RELATION DEPENDS_ON -> [ADR-0003:ADR]

@RELATION CALLS -> [ADR-0005:ADR]

@RATIONALE Extensibility is a core architectural value: the system must support LLMdriven analysis, custom data transformations, and environmentspecific logic without modifying core code. A plugin system prevents the monolith from accumulating every domainspecific feature and enables thirdparty (or futureself) contributions without forking.

@RATIONALE Process isolation was chosen over inprocess imports because: (a) plugins may use incompatible library versions, (b) a crashing plugin must not take down the orchestrator, (c) security boundary — plugins should not access the orchestrator's database connection directly.

@REJECTED Inprocess Python importlib plugin loading — rejected because a misbehaving plugin can corrupt global state, exhaust memory, or crash the server. Process isolation provides a hard boundary.

@REJECTED Dockercontainer per plugin — rejected because it adds excessive orchestration complexity and startup latency for plugins that are mostly lightweight LLM prompt chains. Subprocess isolation is sufficient.

@REJECTED WebAssembly (WASI) sandbox — rejected because the Python AI/LLM ecosystem (langchain, transformers) does not yet reliably compile to WASM. Premature optimization.

Decision

Plugin Architecture

superset-tools Core
├── core/plugin_loader.py       # Plugin discovery, loading, lifecycle
├── core/plugin_executor.py     # Subprocess execution, timeout, error boundary
├── core/plugin_registry.py     # Registered plugins, metadata, health
└── plugins/                    # Plugin packages (each = one directory)
    ├── llm_analysis/           # LLMdriven Superset data analysis; v2 adds dual-path execution (Path A: Playwright screenshot + multimodal LLM; Path B: text-only API + dataset health checking)
    ├── dataset_orchestration/  # LLM dataset operations
    └── git_integration/        # Gitbased version control for dashboards

Plugin Contract

v2 update: Task-based validation was introduced via ValidationTaskService, creating persistent validation policies with ValidationRun aggregates for grouping per-dashboard results. Each run is tracked as a ValidationRun record with aggregate pass/fail/warn counts, replacing the previous single-shot task result pattern. See ValidationTaskService in services/validation_service.py.

Every plugin MUST provide:

  1. plugin.toml — metadata manifest at the plugin root

    [plugin]
    id = "llm_analysis"
    name = "LLM Data Analysis"
    version = "1.0.0"
    entrypoint = "plugin.py"
    timeout_sec = 300
    max_memory_mb = 512
    requires = ["superset-api>=1.0", "openai>=1.0"]
    
  2. plugin.py — entrypoint with two required functions:

    • def register(registry: PluginRegistry) -> PluginInfo — declare capabilities
    • def execute(task: TaskContext) -> TaskResult — run the plugin
  3. Task context contract (Pydantic TaskContext):

    • task_id: str, plugin_id: str, action: str, params: dict, superset_env: SupersetConnection, auth_token: str (scoped, shortlived)
  4. Result envelope (Pydantic TaskResult):

    • status: Literal["success", "warning", "error"], data: dict | None, error_message: str | None, execution_time_ms: int, artifacts: list[str] (file paths to saved artifacts)

Plugin Lifecycle

Discover → Validate → Register → [Execute] → Report
   │          │          │          │
   │    Check TOML    Store in    Spawn subprocess
   │    schema,       registry     with timeout +
   │    dependencies  in DB        memory limit

Isolation Guarantees

  • Subprocess: subprocess.run(..., timeout=timeout_sec), killed on timeout.
  • Memory: resource.setrlimit(RLIMIT_AS, max_memory_mb * 1024 * 1024) before exec.
  • No DB access: Plugins receive only a scoped REST API token, never a database connection.
  • No filesystem writes outside allowed dirs: Configurable artifact directory per plugin.

RBAC Integration

Plugin access is governed by ADR-0005 (RBAC):

  • Each plugin declares required_roles: ["admin", "analyst"] in plugin.toml.
  • Plugin executor checks the user's role set before allowing execution.
  • Forbidden access returns 403 with audit log entry.

[/DEF:ADR-0004:ADR]