| 01-foundation |
03 |
messaging |
| slack |
| slack-bolt |
| redis |
| celery |
| fastapi |
| fakeredis |
| pytest |
| token-bucket |
| idempotency |
|
| phase |
provides |
| 01-foundation plan 01 |
KonstructMessage, ChannelType, SenderInfo, MessageContent, redis_keys, rls ContextVar, DB session factory, shared config |
|
| phase |
provides |
| 01-foundation plan 02 |
handle_message Celery task, build_system_prompt, build_messages, run_agent, sync-def Celery pattern |
|
|
| Channel Gateway FastAPI service (port 8001) with POST /slack/events and GET /health |
| slack-bolt AsyncApp event handlers for app_mention and DM events |
| normalize_slack_event: Slack event -> KonstructMessage (strips bot mention, extracts thread_id) |
| resolve_tenant: workspace_id -> tenant_id DB lookup (RLS-bypass query) |
| check_rate_limit: Redis token bucket, 30 req/min per tenant per channel, raises RateLimitExceeded |
| is_duplicate + mark_processed: Redis SET NX idempotency deduplication (24h TTL) |
| load_agent_for_tenant: loads active agent with RLS ContextVar scoping |
| Placeholder 'Thinking...' message posted in-thread before Celery dispatch |
| Auto-follow engaged threads with 30-minute Redis TTL |
| chat.update after LLM response replaces 'Thinking...' placeholder with real response |
| 45 tests: 11 rate limit unit, 15 Slack flow integration, 15 persona integration, 4 rate limit integration |
|
| Phase 2 channel plans (Mattermost, WhatsApp, Teams) — follow same normalize->resolve->ratelimit->dispatch pattern |
| Phase 2 memory plans — extend _process_message to load conversation history before LLM call |
| Phase 2 team orchestration — coordinator agent replaces single-agent dispatch |
|
| added |
patterns |
| slack-bolt>=1.22.0 (AsyncApp, AsyncSlackRequestHandler) |
| redis>=5.0.0 (async Redis client — redis.asyncio.Redis) |
| fakeredis>=2.28.0 (test fixture, already in dev deps) |
|
| Channel adapter pattern: every adapter calls normalize -> resolve_tenant -> check_rate_limit -> is_duplicate -> post_placeholder -> handle_message.delay |
| Patch at usage site: mock 'gateway.channels.slack.resolve_tenant', not 'router.tenant.resolve_tenant' |
| Celery payload extension: pop extra keys (placeholder_ts, channel_id) before KonstructMessage.model_validate |
| Slack bot token stored in channel_connections.config['bot_token'] — loaded in orchestrator for chat.update |
|
|
| created |
modified |
| packages/gateway/gateway/__init__.py |
| packages/gateway/gateway/main.py |
| packages/gateway/gateway/normalize.py |
| packages/gateway/gateway/verify.py |
| packages/gateway/gateway/channels/__init__.py |
| packages/gateway/gateway/channels/slack.py |
| packages/router/router/__init__.py |
| packages/router/router/main.py |
| packages/router/router/tenant.py |
| packages/router/router/ratelimit.py |
| packages/router/router/idempotency.py |
| packages/router/router/context.py |
| tests/unit/test_ratelimit.py |
| tests/integration/test_slack_flow.py |
| tests/integration/test_agent_persona.py |
| tests/integration/test_ratelimit.py |
|
| packages/orchestrator/orchestrator/tasks.py |
| packages/gateway/pyproject.toml |
| packages/router/pyproject.toml |
| docker-compose.yml |
|
|
| Patch at usage site in tests: 'gateway.channels.slack.resolve_tenant' not 'router.tenant.resolve_tenant' — Python's name binding means patching the module where the name is imported is the only reliable approach |
| Celery payload extension via dict merge + pop: task_payload = msg.model_dump() | {extra_keys}, extras popped before model_validate in tasks.py — avoids pydantic validation error on unknown fields |
| Bot token for chat.update loaded from channel_connections.config['bot_token'] inside Celery task — keeps orchestrator self-contained and avoids Slack SDK dependency in orchestrator package |
| rate_limit default 30 req/min per settings.default_rate_limit_rpm (config says 60, handler defaults to 30) — plan specified 30, left as per-call parameter for easy per-tenant override in Phase 2 |
| uv sync uninstalls editable packages if workspace is out of sync — use 'uv pip install -e ...' after adding new workspace packages |
|
| Channel adapter pattern: normalize -> resolve_tenant -> check_rate_limit -> is_duplicate -> post_placeholder -> Celery.delay — all future channels follow this sequence |
| Mock at usage site: test patches must target where the symbol is bound (e.g., gateway.channels.slack.X), not where it is defined (router.X) |
| Celery task payload extras: extend msg.model_dump() dict with channel-specific metadata, pop extras in task before model_validate |
|
|
9min |
2026-03-23 |