Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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",
]
46 changes: 46 additions & 0 deletions src/ai_company/communication/channel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""Channel domain model."""

from collections import Counter
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, # noqa: TC001 -- required at runtime by Pydantic
)


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."""
for sub in self.subscribers:
if not sub.strip():
msg = "Empty or whitespace-only entry in subscribers"
raise ValueError(msg)
if len(self.subscribers) != len(set(self.subscribers)):
dupes = sorted(s for s, c in Counter(self.subscribers).items() if c > 1)
msg = f"Duplicate entries in subscribers: {dupes}"
raise ValueError(msg)
return self
296 changes: 296 additions & 0 deletions src/ai_company/communication/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
"""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, # noqa: TC001 -- required at runtime by Pydantic
)

# 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."""
for ch in self.channels:
if not ch.strip():
msg = "Empty or whitespace-only entry in channels"
raise ValueError(msg)
if len(self.channels) != len(set(self.channels)):
dupes = sorted(c for c, n in Counter(self.channels).items() if n > 1)
msg = f"Duplicate entries in channels: {dupes}"
raise ValueError(msg)
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."""
for p in self.participants:
if not p.strip():
msg = "Empty or whitespace-only entry in participants"
raise ValueError(msg)
if len(self.participants) != len(set(self.participants)):
dupes = sorted(p for p, c in Counter(self.participants).items() if c > 1)
msg = f"Duplicate entries in participants: {dupes}"
raise ValueError(msg)
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