Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ src/ai_company/
tools/ # Tool registry, MCP integration, role-based access
```

## Shell Usage

- **NEVER use `cd` in Bash commands** — the working directory is already set to the project root. Use absolute paths or run commands directly. Do NOT prefix commands with `cd C:/Users/Aurelio/ai-company &&`.

## Code Conventions

- **No `from __future__ import annotations`** — Python 3.14 has PEP 649
Expand Down
43 changes: 43 additions & 0 deletions src/ai_company/communication/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Communication domain models for the AI company framework."""

from ai_company.communication.channel import Channel
from ai_company.communication.config import (
CircuitBreakerConfig,
CommunicationConfig,
HierarchyConfig,
LoopPreventionConfig,
MeetingsConfig,
MeetingTypeConfig,
MessageBusConfig,
RateLimitConfig,
)
from ai_company.communication.enums import (
AttachmentType,
ChannelType,
CommunicationPattern,
MessageBusBackend,
MessagePriority,
MessageType,
)
from ai_company.communication.message import Attachment, Message, MessageMetadata

__all__ = [
"Attachment",
"AttachmentType",
"Channel",
"ChannelType",
"CircuitBreakerConfig",
"CommunicationConfig",
"CommunicationPattern",
"HierarchyConfig",
"LoopPreventionConfig",
"MeetingTypeConfig",
"MeetingsConfig",
"Message",
"MessageBusBackend",
"MessageBusConfig",
"MessageMetadata",
"MessagePriority",
"MessageType",
"RateLimitConfig",
]
39 changes: 39 additions & 0 deletions src/ai_company/communication/channel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""Channel domain model."""

from typing import Self

from pydantic import BaseModel, ConfigDict, Field, model_validator

from ai_company.communication.enums import ChannelType
from ai_company.core.types import (
NotBlankStr,
validate_non_blank_unique_strings,
)


class Channel(BaseModel):
"""A named communication channel that agents can subscribe to.

Attributes:
name: Channel name (e.g. ``"#engineering"``).
type: Channel delivery semantics.
subscribers: Agent IDs subscribed to this channel.
"""

model_config = ConfigDict(frozen=True)

name: NotBlankStr = Field(description="Channel name")
type: ChannelType = Field(
default=ChannelType.TOPIC,
description="Channel delivery semantics",
)
subscribers: tuple[str, ...] = Field(
default=(),
description="Agent IDs subscribed to this channel",
)

@model_validator(mode="after")
def _validate_subscribers(self) -> Self:
"""Ensure subscriber entries are non-blank and unique."""
validate_non_blank_unique_strings(self.subscribers, "subscribers")
return self
283 changes: 283 additions & 0 deletions src/ai_company/communication/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
"""Communication configuration models (DESIGN_SPEC Sections 5.4, 5.5)."""

from collections import Counter
from typing import Literal, Self

from pydantic import BaseModel, ConfigDict, Field, model_validator

from ai_company.communication.enums import (
CommunicationPattern,
MessageBusBackend,
)
from ai_company.core.types import (
NotBlankStr,
validate_non_blank_unique_strings,
)

# Default channels from DESIGN_SPEC Section 5.4.
_DEFAULT_CHANNELS: tuple[str, ...] = (
"#all-hands",
"#engineering",
"#product",
"#design",
"#incidents",
"#code-review",
"#watercooler",
)


class MessageBusConfig(BaseModel):
"""Message bus backend configuration.

Maps to DESIGN_SPEC Section 5.4 ``message_bus``.

Attributes:
backend: Transport backend to use.
channels: Pre-defined channel names.
"""

model_config = ConfigDict(frozen=True)

backend: MessageBusBackend = Field(
default=MessageBusBackend.INTERNAL,
description="Transport backend",
)
channels: tuple[str, ...] = Field(
default=_DEFAULT_CHANNELS,
description="Pre-defined channel names",
)

@model_validator(mode="after")
def _validate_channels(self) -> Self:
"""Ensure channel names are non-blank and unique."""
validate_non_blank_unique_strings(self.channels, "channels")
return self
Comment on lines +50 to +54

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The validation logic here to ensure channel names are non-blank and unique is very similar to the logic in _validate_subscribers in src/ai_company/communication/channel.py. To improve maintainability and adhere to the DRY principle, consider extracting this logic into a reusable utility function that can be called from both model_validators.

Comment thread
coderabbitai[bot] marked this conversation as resolved.


class MeetingTypeConfig(BaseModel):
"""Configuration for a single meeting type.

Maps to DESIGN_SPEC Section 5.4 ``meetings.types[]``. Exactly one of
``frequency`` or ``trigger`` must be set.

Attributes:
name: Meeting type name (e.g. ``"daily_standup"``).
frequency: Recurrence schedule (mutually exclusive with trigger).
trigger: Event trigger (mutually exclusive with frequency).
participants: Participant role or agent identifiers.
duration_tokens: Token budget for the meeting.
"""

model_config = ConfigDict(frozen=True)

name: NotBlankStr = Field(description="Meeting type name")
frequency: NotBlankStr | None = Field(
default=None,
description="Recurrence schedule",
)
trigger: NotBlankStr | None = Field(
default=None,
description="Event trigger",
)
participants: tuple[str, ...] = Field(
default=(),
description="Participant role or agent identifiers",
)
duration_tokens: int = Field(
default=2000,
gt=0,
description="Token budget for the meeting",
)

@model_validator(mode="after")
def _validate_frequency_or_trigger(self) -> Self:
"""Exactly one of frequency or trigger must be set."""
if self.frequency is not None and self.trigger is not None:
msg = "Only one of frequency or trigger may be set, not both"
raise ValueError(msg)
if self.frequency is None and self.trigger is None:
msg = "Exactly one of frequency or trigger must be set"
raise ValueError(msg)
return self

@model_validator(mode="after")
def _validate_participants(self) -> Self:
"""Ensure participant entries are non-blank and unique."""
validate_non_blank_unique_strings(self.participants, "participants")
return self


class MeetingsConfig(BaseModel):
"""Meetings subsystem configuration.

Maps to DESIGN_SPEC Section 5.4 ``meetings``.

Attributes:
enabled: Whether the meetings subsystem is active.
types: Configured meeting types (unique by name).
"""

model_config = ConfigDict(frozen=True)

enabled: bool = Field(default=True, description="Meetings subsystem active")
types: tuple[MeetingTypeConfig, ...] = Field(
default=(),
description="Configured meeting types",
)

@model_validator(mode="after")
def _validate_unique_meeting_names(self) -> Self:
"""Ensure meeting type names are unique."""
names = [mt.name for mt in self.types]
if len(names) != len(set(names)):
dupes = sorted(n for n, c in Counter(names).items() if c > 1)
msg = f"Duplicate meeting type names: {dupes}"
raise ValueError(msg)
return self


class HierarchyConfig(BaseModel):
"""Hierarchy enforcement configuration.

Maps to DESIGN_SPEC Section 5.4 ``hierarchy``.

Attributes:
enforce_chain_of_command: Whether chain-of-command is enforced.
allow_skip_level: Whether skip-level messaging is allowed.
"""

model_config = ConfigDict(frozen=True)

enforce_chain_of_command: bool = Field(
default=True,
description="Enforce chain-of-command",
)
allow_skip_level: bool = Field(
default=False,
description="Allow skip-level messaging",
)


class RateLimitConfig(BaseModel):
"""Per-pair message rate limit configuration.

Maps to DESIGN_SPEC Section 5.5 ``rate_limit``.

Attributes:
max_per_pair_per_minute: Maximum messages per agent pair per minute.
burst_allowance: Extra burst capacity above the rate limit.
"""

model_config = ConfigDict(frozen=True)

max_per_pair_per_minute: int = Field(
default=10,
gt=0,
description="Max messages per agent pair per minute",
)
burst_allowance: int = Field(
default=3,
ge=0,
description="Extra burst capacity",
)


class CircuitBreakerConfig(BaseModel):
"""Circuit breaker configuration for agent-pair communication.

Maps to DESIGN_SPEC Section 5.5 ``circuit_breaker``.

Attributes:
bounce_threshold: Bounce count before the circuit opens.
cooldown_seconds: Seconds to wait before retrying after trip.
"""

model_config = ConfigDict(frozen=True)

bounce_threshold: int = Field(
default=3,
gt=0,
description="Bounce count before circuit opens",
)
cooldown_seconds: int = Field(
default=300,
gt=0,
description="Cooldown period in seconds",
)


class LoopPreventionConfig(BaseModel):
"""Loop prevention safeguards.

Maps to DESIGN_SPEC Section 5.5. ``ancestry_tracking`` is always on
and cannot be disabled.

Attributes:
max_delegation_depth: Hard limit on delegation chain length.
rate_limit: Per-pair rate limit settings.
dedup_window_seconds: Deduplication window in seconds.
circuit_breaker: Circuit breaker settings.
ancestry_tracking: Must always be ``True``.
"""

model_config = ConfigDict(frozen=True)

max_delegation_depth: int = Field(
default=5,
gt=0,
description="Hard limit on delegation chain length",
)
rate_limit: RateLimitConfig = Field(
default_factory=RateLimitConfig,
description="Per-pair rate limit settings",
)
dedup_window_seconds: int = Field(
default=60,
gt=0,
description="Deduplication window in seconds",
)
circuit_breaker: CircuitBreakerConfig = Field(
default_factory=CircuitBreakerConfig,
description="Circuit breaker settings",
)
ancestry_tracking: Literal[True] = Field(
default=True,
description="Task ancestry tracking (always on, not configurable)",
)


class CommunicationConfig(BaseModel):
"""Top-level communication configuration.

Aggregates DESIGN_SPEC Sections 5.4 and 5.5 under a single model.

Attributes:
default_pattern: High-level communication pattern.
message_bus: Message bus configuration.
meetings: Meetings subsystem configuration.
hierarchy: Hierarchy enforcement settings.
loop_prevention: Loop prevention safeguards.
"""

model_config = ConfigDict(frozen=True)

default_pattern: CommunicationPattern = Field(
default=CommunicationPattern.HYBRID,
description="High-level communication pattern",
)
message_bus: MessageBusConfig = Field(
default_factory=MessageBusConfig,
description="Message bus configuration",
)
meetings: MeetingsConfig = Field(
default_factory=MeetingsConfig,
description="Meetings subsystem configuration",
)
hierarchy: HierarchyConfig = Field(
default_factory=HierarchyConfig,
description="Hierarchy enforcement settings",
)
loop_prevention: LoopPreventionConfig = Field(
default_factory=LoopPreventionConfig,
description="Loop prevention safeguards",
)
Loading