Welcome to the Eneo community! This guide provides everything needed to contribute to the democratic AI platform for the public sector.
Eneo embodies the principle that "Generative AI must not be a technology for the few, but a technology for everyone." Contributors help build a platform that serves public sector organizations worldwide while maintaining democratic governance and open source principles.
Prerequisites:
- Docker and VS Code with Dev Containers extension
- Git configured with your identity
Setup Steps:
# Fork and clone the repository
git clone https://github.com/YOUR_USERNAME/eneo.git
cd eneo
# Open in VS Code
code .
# When prompted, click "Reopen in Container"
# Wait for devcontainer setup (2-3 minutes first time)
# Configure environment
cp backend/.env.template backend/.env
cp frontend/apps/web/.env.example frontend/apps/web/.env
# Edit .env files with your settings
# Initialize database
cd backend && uv run python init_db.py
# Start development servers (3 terminals)
cd backend && uv run start # Terminal 1
cd frontend && bun run dev # Terminal 2
cd backend && uv run arq src.intric.worker.arq.WorkerSettings # Terminal 3# Create feature branch
git checkout -b feature/your-feature-name
# Make changes and test
# ... your development work ...
# Run tests
cd backend && uv run pytest
cd frontend && bun run test
# Commit changes
git add .
git commit -m "feat: add your feature description"
# Push and create pull request
git push origin feature/your-feature-nameAll contributions must align with Eneo's mission to provide democratic AI for the public sector. Review these requirements before submitting a PR.
PRs must introduce generic functions useful for all platform users.
Focus on:
- Solving common problems across organizations
- Features that benefit the entire user base
- Improvements to core platform capabilities
Avoid:
- Municipality-specific features
- Company-specific integrations
- Custom workflows for individual organizations
All UI contributions must follow Eneo's established design system.
Requirements:
- Use existing components and patterns
- Maintain consistent color scheme, typography, and spacing
- Follow the component library guidelines
- No custom styling that deviates from platform standards
π Note: A comprehensive design system in Figma is coming soon. Until then, follow existing UI patterns in the codebase and use the established component library.
No PR may compromise existing functionality.
Testing Requirements:
- Include comprehensive unit tests (β₯80% coverage)
- Add integration tests for new features
- Test for regressions in related features
- Verify no breaking changes to existing APIs
β Good Contributions:
- "Add support for Gemini AI models" - Benefits all users
- "Improve vector search performance" - Universal improvement
- "Add accessibility features for screen readers" - Platform-wide enhancement
- "Implement batch processing for documents" - General capability
β Unacceptable Contributions:
- "Add custom branding for Municipality X" - Too specific
- "Implement Company Y's approval workflow" - Organization-specific
- "Change color scheme to match our brand" - Violates design system
- "Add hard-coded integration with our internal system" - Not generic
Before submitting your PR, ensure:
- Feature is generic and benefits all users
- UI follows existing design patterns
- All tests pass with adequate coverage
- No breaking changes introduced
- Documentation updated accordingly
- PR description clearly explains the universal benefit
Eneo follows Domain-Driven Design with clear separation of concerns. Understanding this architecture is essential for effective contributions.
π Click to view domain organization
backend/src/intric/
βββ assistants/ # AI Assistant Management
β βββ api/ # FastAPI endpoints
β βββ assistant.py # Domain entity
β βββ assistant_repo.py # Data access
β βββ assistant_service.py # Business logic
β βββ assistant_factory.py # Object creation
βββ spaces/ # Collaborative Workspaces
βββ users/ # User Management
βββ completion_models/ # AI Model Integration
βββ sessions/ # Conversation Management
βββ authentication/ # Security and Access Control
Domain Pattern: Each domain follows a consistent 4-layer architecture:
- API Layer: HTTP request/response handling
- Application Layer: Business use cases
- Domain Layer: Core business logic
- Infrastructure Layer: Database and external services
frontend/
βββ apps/web/ # Main SvelteKit application
β βββ src/routes/ # File-based routing
β βββ src/lib/ # Reusable components and utilities
β βββ src/app.html # Root HTML template
βββ packages/ # Shared packages
β βββ intric-js/ # Type-safe API client
β βββ ui/ # Reusable UI components
βββ package.json # Workspace configuration
Python (Backend):
- Style: PEP 8 compliance with Black formatting
- Type Safety: Full type hints for all functions
- Documentation: Docstrings for public APIs
- Testing: Unit tests with pytest, β₯80% coverage target
TypeScript (Frontend):
- Style: ESLint configuration with strict rules
- Type Safety: Strict TypeScript configuration
- Components: Svelte component best practices
- Testing: Vitest for unit tests, Playwright for E2E
When adding new features, follow the established DDD patterns:
ποΈ Click to view DDD implementation example
1. Create Domain Entity:
# assistants/assistant.py
@dataclass
class Assistant:
id: UUID
space_id: UUID
name: str
description: str
system_prompt: str
completion_model_id: UUID
def update_system_prompt(self, prompt: str) -> None:
"""Update assistant's system prompt with validation."""
if not prompt.strip():
raise ValueError("System prompt cannot be empty")
self.system_prompt = prompt.strip()2. Define Repository Interface:
# assistants/assistant_repo.py
class AssistantRepository(Protocol):
async def find_by_id(self, id: UUID) -> Optional[Assistant]:
"""Find assistant by ID."""
...
async def save(self, assistant: Assistant) -> None:
"""Save assistant to storage."""
...3. Implement Service Layer:
# assistants/assistant_service.py
class AssistantService:
def __init__(self, repo: AssistantRepository):
self._repo = repo
async def create_assistant(self, request: CreateAssistantRequest) -> Assistant:
"""Create new assistant with validation."""
assistant = Assistant(
id=uuid4(),
space_id=request.space_id,
name=request.name,
description=request.description,
system_prompt=request.system_prompt,
completion_model_id=request.completion_model_id
)
await self._repo.save(assistant)
return assistant4. Create API Layer:
# assistants/api/assistant_router.py
@router.post("/assistants", response_model=AssistantResponse)
async def create_assistant(
request: CreateAssistantRequest,
service: AssistantService = Depends()
) -> AssistantResponse:
"""Create a new AI assistant."""
assistant = await service.create_assistant(request)
return AssistantResponse.from_domain(assistant)Test Structure:
tests/
βββ unittests/ # Domain-organized unit tests
β βββ assistants/
β β βββ test_assistant.py
β β βββ test_assistant_service.py
β β βββ test_assistant_factory.py
β βββ conftest.py # Shared test fixtures
βββ fixtures.py # Test data factories
Testing Patterns:
# Test domain entity
def test_assistant_update_system_prompt():
assistant = AssistantFactory.create()
new_prompt = "You are a helpful assistant."
assistant.update_system_prompt(new_prompt)
assert assistant.system_prompt == new_prompt
# Test service layer
async def test_create_assistant():
repo = MockAssistantRepository()
service = AssistantService(repo)
request = CreateAssistantRequest(
space_id=uuid4(),
name="Test Assistant",
description="Test description",
system_prompt="Test prompt",
completion_model_id=uuid4()
)
assistant = await service.create_assistant(request)
assert assistant.name == "Test Assistant"
assert repo.saved_assistant == assistantRun Tests:
cd backend
# Run all tests
uv run pytest
# Run specific domain tests
uv run pytest tests/unittests/assistants/
# Run with coverage
uv run pytest --cov=src --cov-report=html
# Run specific test
uv run pytest tests/unittests/assistants/test_assistant.py::test_assistant_update_system_promptComponent Testing:
// tests/components/AssistantCard.test.ts
import { render, screen } from '@testing-library/svelte';
import AssistantCard from '$lib/components/AssistantCard.svelte';
test('displays assistant information', () => {
const assistant = {
id: '123',
name: 'Test Assistant',
description: 'Test description'
};
render(AssistantCard, { props: { assistant } });
expect(screen.getByText('Test Assistant')).toBeInTheDocument();
expect(screen.getByText('Test description')).toBeInTheDocument();
});Run Frontend Tests:
cd frontend
# Unit tests
bun run test
# E2E tests
bun run test:integration
# Type checking
bun run check
# Linting
bun run lintgitgraph
commit id: "main"
branch feature/new-assistant-type
checkout feature/new-assistant-type
commit id: "feat: add domain entity"
commit id: "feat: add service layer"
commit id: "feat: add API endpoints"
commit id: "test: add comprehensive tests"
checkout main
merge feature/new-assistant-type
commit id: "merge: new assistant type"
Branch Naming:
feature/description- New featuresfix/description- Bug fixesdocs/description- Documentation updatesrefactor/description- Code refactoringtest/description- Test improvements
Follow Conventional Commits:
type(scope): description
[optional body]
[optional footer]
Examples:
feat(assistants): add system prompt validation
fix(spaces): resolve permission check bug
docs(api): update assistant endpoint documentation
test(users): add integration tests for user creation- Create Focused PRs: One feature or fix per pull request
- Write Clear Descriptions: Explain what changes and why
- Include Tests: All new functionality requires tests
- Update Documentation: Keep docs synchronized with code changes
- Request Review: At least one approval required from maintainers
PR Template:
## Description
Brief description of changes and motivation.
## Type of Change
- [ ] Bug fix (non-breaking change fixing an issue)
- [ ] New feature (non-breaking change adding functionality)
- [ ] Breaking change (fix or feature causing existing functionality to change)
- [ ] Documentation update
## Testing
- [ ] Unit tests pass
- [ ] Integration tests pass
- [ ] Manual testing completed
## Checklist
- [ ] Code follows project style guidelines
- [ ] Self-review completed
- [ ] Documentation updated
- [ ] No breaking changes without major version bumpCreating Migrations:
cd backend
# Generate migration for model changes
uv run alembic revision --autogenerate -m "add assistant categories"
# Review generated migration file
# Edit if necessary for data migrations or complex changes
# Apply migration locally
uv run alembic upgrade head
# Test rollback
uv run alembic downgrade -1
uv run alembic upgrade headMigration Best Practices:
- Always review auto-generated migrations
- Include data migrations when schema changes affect existing data
- Test both upgrade and downgrade paths
- Use descriptive migration messages
- Never edit applied migrations; create new ones
Example Migration:
# alembic/versions/add_assistant_categories.py
def upgrade() -> None:
# Add new column
op.add_column('assistants', sa.Column('category', sa.String(50), nullable=True))
# Populate existing records with default value
op.execute("UPDATE assistants SET category = 'general' WHERE category IS NULL")
# Make column non-nullable
op.alter_column('assistants', 'category', nullable=False)
def downgrade() -> None:
op.drop_column('assistants', 'category')Eneo supports Swedish and English. When adding new features:
Adding New Text:
// frontend/apps/web/src/lib/paraglide/messages.js
// Add new message keys
export const messages = {
en: {
assistants: {
create_button: "Create Assistant",
delete_confirm: "Are you sure you want to delete this assistant?"
}
},
sv: {
assistants: {
create_button: "Skapa Assistent",
delete_confirm: "Γr du sΓ€ker pΓ₯ att du vill ta bort denna assistent?"
}
}
};
// Use in components
import * as m from '$lib/paraglide/messages.js';
<button>{m.assistants_create_button()}</button>Translation Guidelines:
- All user-facing text must be translatable
- Use descriptive message keys
- Provide context for translators in comments
- Test both language versions
- Swedish should feel natural, not translated
Data Protection:
- Never log or expose sensitive data (API keys, passwords, PII)
- Validate all input at API boundaries
- Use parameterized queries to prevent SQL injection
- Implement proper access controls for all endpoints
Authentication & Authorization:
# Secure endpoint example
@router.get("/assistants/{assistant_id}")
async def get_assistant(
assistant_id: UUID,
current_user: User = Depends(get_current_user),
service: AssistantService = Depends()
) -> AssistantResponse:
"""Get assistant with proper authorization."""
# Verify user has access to the assistant's space
assistant = await service.get_assistant_with_auth_check(
assistant_id,
current_user
)
return AssistantResponse.from_domain(assistant)Security Issues:
- Report security vulnerabilities privately to: security@sundsvall.se
- Include detailed reproduction steps
- Allow reasonable time for fix before public disclosure
- Security fixes receive priority handling
Backend Dependencies:
cd backend
# Add runtime dependency
uv add package-name
# Add development dependency
uv add --group dev package-name
# Update pyproject.toml with specific version constraints
# Commit both pyproject.toml and uv.lockFrontend Dependencies:
cd frontend
# Add dependency to specific package
bun add package-name --filter web
# Add shared dependency to workspace root
bun add package-name -w
# Add development dependency
bun add -d package-nameDependency Guidelines:
- Justify new dependencies in PR description
- Prefer well-maintained packages with active communities
- Consider bundle size impact for frontend dependencies
- Pin major versions to avoid breaking changes
- Regularly audit and update dependencies
Python Docstrings:
async def create_assistant(
self,
request: CreateAssistantRequest,
user: User
) -> Assistant:
"""Create a new AI assistant.
Args:
request: Assistant creation parameters including name, description,
and configuration settings.
user: The user creating the assistant, used for authorization.
Returns:
The created assistant instance with generated ID.
Raises:
ValidationError: If request parameters are invalid.
AuthorizationError: If user lacks permission to create assistants.
Example:
>>> request = CreateAssistantRequest(name="Helper", ...)
>>> assistant = await service.create_assistant(request, user)
>>> assert assistant.name == "Helper"
"""TypeScript Documentation:
/**
* Creates a new assistant with the provided configuration.
*
* @param data - Assistant creation parameters
* @returns Promise resolving to the created assistant
* @throws {ValidationError} When data is invalid
* @throws {AuthorizationError} When user lacks permissions
*
* @example
* ```typescript
* const assistant = await createAssistant({
* name: "Helper",
* description: "A helpful assistant"
* });
* ```
*/
async function createAssistant(data: CreateAssistantData): Promise<Assistant> {
// Implementation...
}All API endpoints automatically generate OpenAPI documentation. Ensure:
- Clear endpoint descriptions
- Comprehensive request/response models
- Example requests and responses
- Error status codes and descriptions
1. AI Model Integration:
- Add support for new AI providers
- Improve model switching and fallback logic
- Optimize token usage and cost management
2. User Experience:
- Enhance accessibility features
- Improve mobile responsiveness
- Streamline onboarding workflows
3. Performance:
- Optimize database queries
- Improve vector search performance
- Enhance caching strategies
4. Security & Compliance:
- Strengthen authentication mechanisms
- Improve audit logging
- Enhance GDPR compliance features
- Multi-tenant security enhancements
5. Multi-Tenancy & Enterprise Features:
- Per-tenant resource quotas and limits
- Federation provider integrations (new IdPs)
- Credential management improvements
- Observability and debugging tools
6. Knowledge Management:
- Website crawler enhancements (pagination, JavaScript rendering)
- HTTP authentication for protected resources
- Document processing improvements
- Semantic search optimization
New contributors should look for issues labeled good-first-issue:
- Documentation improvements
- UI component enhancements
- Test coverage improvements
- Bug fixes with clear reproduction steps
Eneo follows the Contributor Covenant Code of Conduct. All contributors are expected to:
- Use welcoming and inclusive language
- Respect differing viewpoints and experiences
- Accept constructive criticism gracefully
- Focus on what is best for the community
- Show empathy towards other community members
Development Discussion:
- GitHub Issues for bugs and feature requests
- GitHub Discussions for general questions
- Pull Request comments for code review
Community Support:
- Email: digitalisering@sundsvall.se (public sector organizations)
- GitHub Discussions for community questions
Contributors are recognized through:
- Contribution credits in release notes
- GitHub contributor statistics
- Special recognition for significant contributions
- Invitation to contributor meetings (for active contributors)
Eneo follows Semantic Versioning:
- MAJOR: Breaking changes
- MINOR: New features, backward compatible
- PATCH: Bug fixes, backward compatible
- Minor releases: Monthly (feature additions)
- Patch releases: As needed (critical bug fixes)
- Major releases: Quarterly (breaking changes)
- Feature freeze 1 week before minor releases
- Critical bug fixes accepted during freeze
- All changes require approval from maintainers
- Changelog updated with each release
Getting Started:
- Join the GitHub Discussions
- Look for
good-first-issuelabels - Read through existing code to understand patterns
- Ask questions in discussions or issue comments
Mentorship:
- Experienced contributors provide guidance on complex features
- Code review feedback helps learn best practices
- Pair programming sessions available for significant contributions
Advanced Topics:
- Architecture decision discussions
- Performance optimization strategies
- Security review processes
- Integration with public sector requirements
Code Quality Metrics:
- Test coverage maintained above 80%
- No critical security vulnerabilities
- Performance regression prevention
- Documentation completeness
Community Health:
- Responsive code review (within 48 hours)
- Constructive feedback culture
- Knowledge sharing through documentation
- Inclusive contribution environment
Thank you for contributing to Eneo! Your work helps make AI technology democratic and accessible to public sector organizations worldwide. Together, we're building a platform that truly serves the public interest.
Questions? Join our GitHub Discussions or reach out to digitalisering@sundsvall.se