Back to list
lbds137

tzurot-architecture

by lbds137

A Discord bot that uses webhooks to represent multiple AI characters.

7🍴 1📅 Jan 24, 2026

SKILL.md


name: tzurot-architecture description: Contains MANDATORY microservices architecture rules for Tzurot v3. MUST be consulted before creating new services or deciding where code belongs. Covers service boundaries, dependencies, and anti-patterns from v2. lastUpdated: '2026-01-21'

Tzurot v3 Architecture

Use this skill when: Adding new features, deciding where code belongs, designing system interactions, or refactoring service boundaries.

Quick Reference

Discord User
    ↓
bot-client (Discord.js)
    ↓ HTTP
api-gateway (Express + BullMQ)
    ↓ Redis Queue
ai-worker (AI + pgvector)
    ↓
OpenRouter/Gemini API

Core Principles

  1. Simple, clean classes - No DDD over-engineering (learned from v2)
  2. Clear service boundaries - Each service has single responsibility
  3. No circular dependencies - Services can't import from each other
  4. Shared code in common-types - Cross-service types, utils, services
  5. Constructor injection - Simple dependency passing, no DI containers

Three Microservices

ServiceResponsibilityDoesDoes NOT
bot-clientDiscord interfaceEvents, webhooks, commands, formattingBusiness logic, AI calls, direct DB
api-gatewayHTTP API + queueEndpoints, validation, job creationAI processing, Discord interaction
ai-workerAI + memoryJobs, memory, AI calls, embeddingsHTTP endpoints, Discord interaction

Where to Put New Code

TypeLocation
Webhook/message formattingbot-client/
Slash commandsbot-client/commands/
HTTP endpointsapi-gateway/routes/
Job creationapi-gateway/queue.ts
AI provider clientsai-worker/providers/
Memory/embeddingsai-worker/services/
Shared types/constantscommon-types/
Discord type guardscommon-types/types/

Autocomplete Utilities (CRITICAL)

ALWAYS check for existing utilities before writing autocomplete handlers.

Available in bot-client/src/utils/autocomplete/:

UtilityPurposeOption Names
handlePersonalityAutocompletePersonality selectionpersonality, character
handlePersonaAutocompleteProfile/persona selectionprofile, persona
// ✅ GOOD - Delegate to shared utility
import { handlePersonalityAutocomplete } from '../../utils/autocomplete/personalityAutocomplete.js';

await handlePersonalityAutocomplete(interaction, {
  optionName: 'personality',
  showVisibility: true,
  ownedOnly: false,
});

// ❌ BAD - Duplicating 50+ lines of autocomplete logic

Error Message Patterns

LayerPatternExample
api-gatewayClean JSON, NO emojis{ "error": "NOT_FOUND", "message": "Persona not found" }
bot-clientADD emojis for users'❌ Profile not found.'
// ✅ Gateway - clean for programmatic use
sendError(res, ErrorResponses.notFound('Persona'));

// ✅ Bot - emoji for users
await interaction.editReply({ content: '❌ Profile not found.' });

Anti-Patterns from v2 (DON'T DO)

PatternWhy Notv3 Alternative
Generic IRepository<T>Too abstractConcrete service methods
DI containersOver-engineeredDirect instantiation
Controller→UseCase→Service→Repository→ORMToo many layersRoute→Service→Prisma
Complex event busUnnecessaryRedis pub/sub for cache only
Value objects everywhereOverheadSimple validation functions
// ❌ v2 - Container hell
container.bind('PersonalityService').to(PersonalityService);
const service = container.get('PersonalityService');

// ✅ v3 - Simple
const service = new PersonalityService(prisma);

Dependency Injection

// ✅ GOOD - Simple constructor injection
class MyService {
  constructor(
    private prisma: PrismaClient,
    private redis: Redis
  ) {}
}

const service = new MyService(prisma, redis);

When to Extract a Service

Extract when:

  • Shared across multiple microservices → common-types
  • Complex business logic
  • Stateful operations
  • Easier testability needed

Keep inline when:

  • Used in one place only
  • Stateless utility function
  • Very simple logic

Complexity Signals (ESLint Warnings)

ESLint warnings indicate when to refactor:

WarningThresholdAction
max-statements>30Extract helper functions
complexity>15Use data-driven patterns
max-lines-per-function>100Split responsibilities
max-params>5Use options object pattern

📚 See: tzurot-code-quality skill for refactoring patterns

Database Access

Direct Prisma in services - No repository pattern

// ✅ Direct Prisma
async getPersonality(id: string) {
  return this.prisma.personality.findUnique({ where: { id } });
}

// ❌ Generic repository
interface PersonalityRepository {
  findById(id: string): Promise<Personality>;
}

Configuration

  • Environment variables: Secrets (tokens, DB URLs)
  • common-types constants: Application config (timeouts, limits)
import { TIMEOUTS, RETRY_CONFIG } from '@tzurot/common-types';
const timeout = TIMEOUTS.LLM_INVOCATION;
  • tzurot-code-quality - Refactoring patterns for complexity
  • tzurot-async-flow - BullMQ job patterns
  • tzurot-db-vector - Database patterns
  • tzurot-types - Type definitions
  • tzurot-council-mcp - Major design decisions

References

  • Full architecture: CLAUDE.md#architecture
  • Project structure: CLAUDE.md#project-structure
  • Architecture decisions: docs/architecture/ARCHITECTURE_DECISIONS.md

Score

Total Score

60/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon