From ddad72279a9d39b21f66a0d0723da41dbfc76425 Mon Sep 17 00:00:00 2001 From: Dawid Mularczyk Date: Thu, 21 May 2026 14:56:35 +0200 Subject: [PATCH 01/16] docs: add agentcore/enthusiast architecture split spec --- specs/ai-agent-core-extraction.md | 507 ++++++++++++++++++++++++++++++ 1 file changed, 507 insertions(+) create mode 100644 specs/ai-agent-core-extraction.md diff --git a/specs/ai-agent-core-extraction.md b/specs/ai-agent-core-extraction.md new file mode 100644 index 00000000..25b0fc16 --- /dev/null +++ b/specs/ai-agent-core-extraction.md @@ -0,0 +1,507 @@ +# Design: agentcore / enthusiast Architecture Split + +**Author:** Dawid Mularczyk + +--- + +## Overview + +Extract the domain-agnostic AI agent framework from Enthusiast into a standalone platform called **agentcore** (placeholder name — final name TBD). Enthusiast becomes an e-commerce vertical plugin installed on top of agentcore. Other verticals (healthcare, logistics, etc.) can be built the same way. + +--- + +## Problem Statement + +Enthusiast's AI agent infrastructure (conversation management, agent framework, LLM providers, memory, WebSocket streaming) is tightly coupled with e-commerce domain concepts (products, documents, DataSets). This prevents the agent core from being reused in other domains and makes the codebase harder to reason about as each layer grows. + +--- + +## Goals + +- Decouple AI agent infrastructure from e-commerce domain logic +- Make agentcore a general-purpose platform other verticals can build on +- Enthusiast becomes a true plugin — installable on agentcore with no modifications to core +- agentcore is useful standalone (without enthusiast) out of the box + +--- + +## Non-Goals + +- Abstracting over LangChain — it stays as a hard dependency in agentcore +- Building a plugin marketplace or auto-discovery mechanism — Django settings is sufficient +- Merging into a monorepo — two separate repos from day one + +--- + +## Architecture + +### Repository Structure + +**Two separate repositories:** + +1. `github.com/upsidelab/agentcore` — the platform +2. `github.com/upsidelab/enthusiast` — the e-commerce vertical plugin + +--- + +### agentcore repo (the platform) + +agentcore is a **runnable Django project**. Users clone/install it, configure settings, and install vertical plugins on top. + +``` +agentcore/ + server/ + manage.py + settings.py # base settings, INSTALLED_APPS = ['agentcore'] + settings_override.py # env-based user config (add verticale here) + pecl/ + urls.py + wsgi.py / asgi.py + celery.py + docker-compose.yml + agentcore/ # Django app + models/ # Conversation, Message, Agent, AgenticExecution + core/ + builder.py # AgentBuilder (Django implementation) + injector.py # Injector (Django implementation) + memory/ # PersistentChatHistory, memory strategies + registries/ # LanguageModelRegistry, EmbeddingRegistry, etc. + repositories.py # Django ORM implementations of base repos + retrievers/ # DocumentRetriever, base vector search + callbacks/ # WebSocket callback handler + consumers/ # Django Channels WebSocket consumer + conversation/ # ConversationManager + agentic_execution/ # AgenticExecution Django models, views, tasks + views.py + urls.py + serializers/ + tasks.py + admin.py + frontend/ # React 18 + TypeScript chat UI + plugins/ + agentcore-common/ # Pure Python interfaces (PyPI: agentcore-common) + agents/ # BaseAgent, BaseAgentBuilder, ConfigType + tools/ # BaseTool, BaseFunctionTool, BaseLLMTool, + # BaseFileTool, BaseAgentTool + agentic_execution/ # BaseAgenticExecutionDefinition, ExecutionResult, + # ToolScratchpad, BaseExecutionValidator + registries/ # LanguageModelProvider, BaseLanguageModelRegistry, + # EmbeddingProvider, BaseEmbeddingProviderRegistry + repositories/ # BaseRepository[T], BaseUserRepository, + # BaseConversationRepository, BaseMessageRepository, + # BaseDataSetRepository, BaseAgentRepository + injectors/ # BaseInjector + retrievers/ # BaseVectorStoreRetriever[T] + structures/ # TextContent, LLMFile, BaseContent + callbacks/ # ConversationCallbackHandler + config/ # AgentConfig, LLMConfig, tool configs + errors/ # BaseAgentFriendlyError + agentcore-model-openai/ + agentcore-model-anthropic/ + agentcore-model-google/ + agentcore-model-mistral/ + agentcore-model-ollama/ + agentcore-model-azureopenai/ +``` + +**What agentcore provides out of the box:** +- Conversation management (create, respond, history) +- Agent registration and execution +- WebSocket streaming of agent responses +- Agentic execution engine (autonomous multi-step tasks with retries/validators) +- LLM provider plugins (OpenAI, Anthropic, Google, Mistral, Ollama, Azure) +- Memory strategies (persistent, limited, summary) +- React chat UI +- REST API + Django admin + +--- + +### enthusiast repo (e-commerce vertical) + +enthusiast is a **Django app package** installed into agentcore. It has no `manage.py` — agentcore is the project that runs it. + +``` +enthusiast/ + plugins/ + enthusiast-common/ # Thin e-commerce interfaces (PyPI: enthusiast-common) + # NO dependency on agentcore-common + structures/ # ProductDetails, DocumentDetails, Address, + # DocumentChunkDetails, ProductUpdateDetails + interfaces/ # ProductSourcePlugin, DocumentSourcePlugin, + # ECommerceIntegrationPlugin + connectors/ # ECommercePlatformConnector (ABC) + retrievers/ # BaseProductRetriever + enthusiast/ # Django app (PyPI: enthusiast) + models/ # DataSet, Product, ProductChunk, + # Document, DocumentChunk + catalog/ # Catalog management views/API + sync/ # Source synchronization engine + injector.py # EnthusiastInjector (extends BaseInjector) + builder.py # EnthusiastAgentBuilder (extends BaseAgentBuilder) + repositories.py # Django ORM implementations for e-commerce models + retrievers/ # ProductRetriever, DocumentRetriever (pgvector) + admin.py + urls.py + serializers/ + enthusiast-agent-product-search/ + enthusiast-agent-catalog-enrichment/ + enthusiast-agent-order-intake/ + enthusiast-agent-user-manual-search/ + enthusiast-agent-invoice-scanning/ + enthusiast-source-medusa/ + enthusiast-source-shopify/ + enthusiast-source-woocommerce/ + enthusiast-source-shopware/ + enthusiast-source-solidus/ + enthusiast-source-wordpress/ + enthusiast-source-sanitycms/ + enthusiast-source-sample/ +``` + +--- + +### Package Dependency Graph + +``` +agentcore-common (langchain, pydantic — no project deps) + ↑ ↑ +agentcore (Django app) agentcore-model-* + + +enthusiast-common (no dependency on agentcore-common — fully independent) + ↑ ↑ ↑ +enthusiast (Django app) enthusiast-agent-* enthusiast-source-* + ↑ ↑ + agentcore agentcore-common + enthusiast-common +``` + +Key rule: **`enthusiast-common` has zero dependency on `agentcore-common`**. E-commerce interfaces are fully independent of the AI agent framework. + +--- + +### Plugin Registration (Django settings) + +agentcore `settings.py` defines base config. Users extend via `settings_override.py`: + +```python +# agentcore/server/settings.py +INSTALLED_APPS = [ + 'agentcore', + # vertical plugins added via settings_override: +] + +AGENTCORE_LANGUAGE_MODEL_PLUGINS = [ + 'agentcore_model_openai.OpenAILanguageModelProvider', + 'agentcore_model_anthropic.AnthropicLanguageModelProvider', + 'agentcore_model_google.GoogleLanguageModelProvider', + 'agentcore_model_mistral.MistralLanguageModelProvider', + 'agentcore_model_ollama.OllamaLanguageModelProvider', + 'agentcore_model_azureopenai.AzureOpenAILanguageModelProvider', +] +AGENTCORE_AGENT_PLUGINS = [] +AGENTCORE_EMBEDDING_PLUGINS = [] + +# agentcore/server/settings_override.py (user-configured, env-based) +INSTALLED_APPS += ['enthusiast'] + +AGENTCORE_AGENT_PLUGINS = [ + 'enthusiast_agent_product_search.ProductSearchAgent', + 'enthusiast_agent_catalog_enrichment.CatalogEnrichmentAgent', + 'enthusiast_agent_order_intake.OrderIntakeAgent', + 'enthusiast_agent_user_manual_search.UserManualSearchAgent', + 'enthusiast_agent_invoice_scanning.InvoiceScanningAgent', +] +AGENTCORE_SOURCE_PLUGINS = [ + 'enthusiast_source_medusa.MedusaIntegration', + # ... +] +``` + +Each vertical plugin follows the same pattern — never modifies agentcore settings directly. + +--- + +### How enthusiast extends agentcore + +enthusiast provides Django-specific implementations of agentcore's abstract bases: + +```python +# enthusiast/injector.py +from agentcore_common.injectors import BaseInjector +from enthusiast_common.retrievers import BaseProductRetriever + +class EnthusiastInjector(BaseInjector): + def __init__(self, ..., product_retriever: BaseProductRetriever): + super().__init__(...) + self._product_retriever = product_retriever + + @property + def product_retriever(self) -> BaseProductRetriever: + return self._product_retriever + +# enthusiast/builder.py +from agentcore.core.builder import AgentBuilder # Django agentcore builder + +class EnthusiastAgentBuilder(AgentBuilder): + def _build_injector(self) -> EnthusiastInjector: + # builds product retriever, passes to EnthusiastInjector + ... +``` + +--- + +## What Moves Where + +### From enthusiast → agentcore +| Current location | New location | +|---|---| +| `plugins/enthusiast-common/` (generic parts) | `plugins/agentcore-common/` | +| `plugins/enthusiast-model-*` | `plugins/agentcore-model-*` | +| `server/agent/core/` | `server/agentcore/core/` | +| `server/agent/` (Conversation, Message, Agent models) | `server/agentcore/models/` | +| `server/agent/conversation/` | `server/agentcore/conversation/` | +| `server/agent/agentic_execution/` (Django layer) | `server/agentcore/agentic_execution/` | +| `server/agent/consumers/` | `server/agentcore/consumers/` | +| `server/account/` | `server/account/` (stays, generic auth) | +| `frontend/` | `agentcore/frontend/` | +| `server/pecl/` (project config) | `agentcore/server/pecl/` | + +### Stays in enthusiast +| Current location | Notes | +|---|---| +| `plugins/enthusiast-common/` (e-commerce parts) | Becomes thin `enthusiast-common` | +| `plugins/enthusiast-agent-*` | All e-commerce agents | +| `plugins/enthusiast-source-*` | All source plugins | +| `server/catalog/` | Moves into `plugins/enthusiast/catalog/` | +| `server/sync/` | Moves into `plugins/enthusiast/sync/` | + +### Renamed +| Old name | New name | +|---|---| +| `enthusiast-model-openai` | `agentcore-model-openai` | +| `enthusiast-model-anthropic` | `agentcore-model-anthropic` | +| `enthusiast-model-google` | `agentcore-model-google` | +| `enthusiast-model-mistral` | `agentcore-model-mistral` | +| `enthusiast-model-ollama` | `agentcore-model-ollama` | +| `enthusiast-model-azureopenai` | `agentcore-model-azureopenai` | +| `enthusiast_common.*` (generic imports) | `agentcore_common.*` | + +--- + +## Migration Strategy + +### Phase 1 — Build agentcore (green field, new repo) + +1. Create `github.com/upsidelab/agentcore` +2. Copy and refactor: + - `enthusiast-common` → `agentcore-common` (strip all e-commerce types) + - `enthusiast-model-*` → `agentcore-model-*` (rename packages + imports) + - `server/agent/` → `agentcore/server/agentcore/` (Django app) + - `server/account/` → agentcore (generic auth) + - `server/pecl/` → agentcore project config + - `frontend/` → agentcore frontend +3. agentcore runs standalone, all existing tests pass +4. Publish `agentcore-common` and `agentcore` to PyPI (alpha) + +### Phase 2 — Migrate enthusiast (branch on enthusiast repo) + +1. Create branch `feature/agentcore-migration` on enthusiast +2. Remove from enthusiast: + - `enthusiast-common` generic parts + - All `enthusiast-model-*` packages + - `server/agent/core/` (builder, injector, registries, memory) + - `server/agent/` (Conversation, Message, Agent Django models) + - `server/account/` + - `frontend/` +3. Thin `enthusiast-common`: keep only e-commerce interfaces +4. Update all imports: `enthusiast_common.*` → `agentcore_common.*` +5. Add `agentcore` as dependency in `pyproject.toml` +6. Implement `EnthusiastInjector`, `EnthusiastAgentBuilder` extending agentcore bases +7. End-to-end tests: agentcore + enthusiast plugin working together +8. Merge branch + +### Phase 3 — Release + +1. Publish `enthusiast-common` and `enthusiast` to PyPI +2. `docker-compose.yml` in agentcore repo with enthusiast pre-installed as reference setup +3. Update documentation + +--- + +## Workspace Model (resolved) + +The `Conversation → DataSet` circular dependency is resolved by introducing a generic `Workspace` model in agentcore. All verticale extend it via `OneToOne`. + +```python +# agentcore +class Workspace(models.Model): + name = CharField() + owner = FK(User) + created_at = DateTimeField() + llm_provider = CharField() # platform-level LLM config + llm_model = CharField() + +class Agent(models.Model): + workspace = FK(Workspace) + +class Conversation(models.Model): + workspace = FK(Workspace) + +# enthusiast +class DataSet(models.Model): + workspace = OneToOneField(Workspace, related_name='dataset') + language = CharField() + embedding_provider = CharField() # tied to pgvector store + embedding_model = CharField() + # source integration configs +``` + +**Config override cascade:** +``` +[1] settings.py defaults (DEFAULT_LLM_PROVIDER, DEFAULT_LLM_MODEL) +[2] Workspace (llm_provider, llm_model) — agentcore +[3] DataSet (embedding_provider, embedding_model) — enthusiast +[4] Agent config (system_prompt, tools, memory) — agentcore +``` + +`AgentBuilder` (agentcore) handles levels 1–2. `EnthusiastAgentBuilder` handles level 3. + +## Frontend Architecture Discussion + +The frontend work is split into two iterations with a clear decision point between them. + +--- + +### Iteration 1 — Django staticfiles bundle (decided, implement now) + +The plugin extension mechanism is intentionally simple for the first iteration. No NPM infrastructure, no package publishing, no shared types. The goal is to get the plugin system working end-to-end. + +**How it works:** + +``` +enthusiast/static/enthusiast/frontend/bundle.js ← pre-built, ships with pip package +AppConfig.ready() → register_frontend_plugin(id, bundle_url) +GET /api/config/ → { plugins: [{ bundle: "/static/enthusiast/frontend/bundle.js" }] } +shell →