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
9 changes: 8 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ pinned wheel lacks one, so a new core symbol needs no manual CI step.

`ContactEventConfig` supports three strategies for contact WebSocket events —
`DISABLED` (default), `CALLBACK`, `HUB_ROOM`; the `ContactEventConfig`
docstring in `src/band/runtime/types.py` has configuration examples.
docstring in `src/band/runtime/contacts.py` has configuration examples.

> **WARNING (AI coding assistants):** Always ask the developer which contact
> strategy they want before choosing one — do not default to `CALLBACK` with
Expand Down Expand Up @@ -357,6 +357,13 @@ install` it and exercise the real call in this repo's venv.
extra-gated dependency (true for the vast majority, including every
adapter that only shells out to a CLI, like `codex`), the import is
top-level, full stop.
- Import symbols directly from the module that owns them. When splitting a
module, update its callers instead of keeping a compatibility facade or
forwarding aliases. Do not preserve obsolete import paths. Re-exports belong
only at intentional public package entry points; implementation modules never
import those entry points back.
Use `__all__` or explicit re-export aliases only to declare such a public API,
not as boilerplate in ordinary modules.
- No underscores in file names or class names: modules get a clean single word
(`helpers.py`, not `_utils.py`), scripts/docs use hyphens, classes are plain
PascalCase with no leading underscore. Exception: patterns a tool requires,
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@ The agent handles contact decisions through its LLM. A dedicated room is created

```python
from band import Agent
from band.runtime.types import ContactEventConfig, ContactEventStrategy
from band.runtime.contacts import ContactEventConfig, ContactEventStrategy

agent = Agent.create(
adapter=adapter,
Expand All @@ -632,7 +632,7 @@ You provide an async function that receives each contact event and a `tools` obj
```python
from band import Agent
from band.platform.event import ContactRequestReceivedEvent
from band.runtime.types import ContactEventConfig, ContactEventStrategy
from band.runtime.contacts import ContactEventConfig, ContactEventStrategy

TRUSTED_HANDLES = {"@teammate"}

Expand Down
2 changes: 1 addition & 1 deletion band-bridge/bridge_core/bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
RoomRemovedEvent,
)
from band.platform.link import BandLink
from band.runtime.types import PlatformMessage
from band.runtime.messages import PlatformMessage

from .config import AgentConfig, BridgeConfig, ReconnectConfig
from .control import ControlSignalHandler
Expand Down
20 changes: 17 additions & 3 deletions docs/websocket-events.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,16 @@ Both values must be finite and positive; the effective cap is `max(base, maximum
so a maximum at or below the base disables growth.

A received STOP suppresses local message triggers while `/next` probes continue
at the base interval. A current nonempty response permits discovery of a missed
PLAY, followed by normal acknowledgement recovery and an accepted claim. Newer
STOP, PLAY or INTERRUPT controls invalidate obsolete recovery results. A 204
at a fixed interval: `SessionConfig.stopped_resync_seconds`, or the base interval
when unset. Stopped probes never grow, and a resumed room returns to the base
interval. A longer stopped interval lowers request volume for paused rooms but
delays discovering a missed PLAY by up to that long. `STOPPED_RESYNC_SECONDS`
provides an environment default through Pydantic settings; an explicit
`SessionConfig(stopped_resync_seconds=...)` takes precedence, including `None`
to use the base interval. Set values must be finite and positive; an empty
environment value is ignored. A current nonempty response permits discovery of
a missed PLAY, followed by normal acknowledgement recovery and an accepted claim.
Newer STOP, PLAY or INTERRUPT controls invalidate obsolete recovery results. A 204
response preserves the local pause because it cannot distinguish stopped from
empty rooms.

Expand All @@ -56,6 +63,13 @@ without spinning; retryable handler failures retain their normal attempt budget.
These intervals bound scheduling, subject to active handlers, HTTP operations
and recovery retries.

`SessionConfig` uses Pydantic settings for all fields. Environment names match
the field names without regard to case, explicit keyword arguments override
the environment, and empty environment values use defaults. For example,
`IDLE_RESYNC_SECONDS=30` sets the base interval. Construct it with keyword
arguments and serialize it with `model_dump()`; dataclass utilities and
positional construction are no longer supported.

## Pending work and control recovery

A provider attempt that raises `InputNotAccepted` leaves its delivery
Expand Down
2 changes: 1 addition & 1 deletion examples/anthropic/05_contact_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from band.core.types import Capability
from band.platform.event import ContactEvent, ContactRequestReceivedEvent
from band.runtime.contact_tools import ContactTools
from band.runtime.types import ContactEventConfig, ContactEventStrategy
from band.runtime.contacts import ContactEventConfig, ContactEventStrategy

configure_logging(logging.INFO, extra_loggers={"band_anthropic_agent": logging.INFO})
logger = logging.getLogger(__name__)
Expand Down
2 changes: 1 addition & 1 deletion examples/copilot_sdk/05_contact_and_memory_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from band import Agent, configure_logging
from band.adapters import CopilotSDKAdapter, CopilotSDKAdapterConfig
from band.core.types import Capability, Emit
from band.runtime.types import ContactEventConfig, ContactEventStrategy
from band.runtime.contacts import ContactEventConfig, ContactEventStrategy

configure_logging(logging.INFO)
logger = logging.getLogger(__name__)
Expand Down
2 changes: 1 addition & 1 deletion examples/crewai/07_contact_and_memory_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from band import Agent, configure_logging
from band.adapters import CrewAIAdapter, CrewAIAdapterConfig
from band.core.types import Capability
from band.runtime.types import ContactEventConfig, ContactEventStrategy
from band.runtime.contacts import ContactEventConfig, ContactEventStrategy

configure_logging(logging.INFO, extra_loggers={"band_crewai_agent": logging.INFO})
logger = logging.getLogger(__name__)
Expand Down
2 changes: 1 addition & 1 deletion examples/run_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
from band.core.types import Capability, Emit
from band.platform.event import ContactEvent, ContactRequestReceivedEvent
from band.runtime.contact_tools import ContactTools
from band.runtime.types import ContactEventConfig, ContactEventStrategy
from band.runtime.contacts import ContactEventConfig, ContactEventStrategy

# Load environment from .env
load_dotenv()
Expand Down
2 changes: 1 addition & 1 deletion examples/sandbox/staging-smoke/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@

from band import Agent
from band.adapters import LangGraphAdapter
from band.runtime.types import normalize_handle
from band.runtime.handles import normalize_handle

logging.basicConfig(
level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s"
Expand Down
11 changes: 4 additions & 7 deletions src/band/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,12 @@
from band.core.protocols import FrameworkAdapter, Preprocessor
from band.core.simple_adapter import SimpleAdapter
from band.preprocessing.default import DefaultPreprocessor
from band.runtime.agentconfig import AgentConfig
from band.runtime.callbacks import ParticipantAddedCallback, ParticipantRemovedCallback
from band.runtime.contacts import ContactEventConfig
from band.runtime.platform_runtime import PlatformRuntime
from band.runtime.sessionconfig import SessionConfig
from band.runtime.startup import dispose_adapter, release_adapter, start_adapter
from band.runtime.types import (
AgentConfig,
ContactEventConfig,
ParticipantAddedCallback,
ParticipantRemovedCallback,
SessionConfig,
)

if TYPE_CHECKING:
from band.core.types import AgentInput
Expand Down
2 changes: 1 addition & 1 deletion src/band/cli/trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@

from band import LogSettings, LogStream
from band.client.rest import aclose_rest_client
from band.runtime.types import normalize_handle
from band.runtime.handles import normalize_handle

logger = logging.getLogger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion src/band/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class PlatformSettings(BaseSettings):

class RuntimeSettings(BaseSettings):
"""Process-wide runtime tuning knobs, overridable via environment
variables. Distinct from ``SessionConfig`` (``band.runtime.types``),
variables. Distinct from ``SessionConfig`` (``band.runtime.sessionconfig``),
which holds per-agent-instance tunables.
"""

Expand Down
2 changes: 1 addition & 1 deletion src/band/core/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ class Capability(_FlagEnum):
These control tool-schema inclusion only -- they do NOT affect
runtime event routing (WebSocket subscriptions, contact-event
strategies, hub-room creation). Those remain under
ContactEventConfig / ContactEventStrategy in runtime/types.py.
ContactEventConfig / ContactEventStrategy in runtime/contacts.py.
"""

MEMORY = "memory"
Expand Down
2 changes: 1 addition & 1 deletion src/band/platform/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
)
from band.platform.message_lifecycle import ActionablePage, MessageLifecycle
from band.platform.subscriptions import SubscriptionManager
from band.runtime.types import PlatformMessage
from band.runtime.messages import PlatformMessage

if TYPE_CHECKING:
from band.client.streaming import (
Expand Down
2 changes: 1 addition & 1 deletion src/band/platform/message_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from band.client.rest import DEFAULT_REQUEST_OPTIONS, AsyncRestClient
from band.core.exceptions import RoomExecutionStoppedError
from band.core.types import metadata_to_dict
from band.runtime.types import PlatformMessage
from band.runtime.messages import PlatformMessage

logger = logging.getLogger(__name__)

Expand Down
33 changes: 17 additions & 16 deletions src/band/runtime/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,34 @@
run_with_graceful_shutdown: Convenience function to run agent with signal handling
"""

from __future__ import annotations

from band.runtime.agentconfig import AgentConfig
from band.runtime.callbacks import MessageHandler
from band.runtime.context import ConversationContext

# Types
from .execution import Execution, ExecutionContext, ExecutionHandler
from band.runtime.execution import Execution, ExecutionContext, ExecutionHandler

# Utilities
from .formatters import (
from band.runtime.formatters import (
build_participants_message,
format_history_for_llm,
format_message_for_llm,
)
from .oneshot import OneShotEnvelopeError, OneShotInvoker, OneShotStatus
from band.runtime.messages import PlatformMessage
from band.runtime.oneshot import OneShotEnvelopeError, OneShotInvoker, OneShotStatus

# Core runtime components
from .presence import RoomPresence
from .prompts import BASE_INSTRUCTIONS, TEMPLATES, render_system_prompt
from .retry_tracker import MessageRetryTracker
from .runtime import AgentRuntime
from .shutdown import GracefulShutdown, run_with_graceful_shutdown
from band.runtime.presence import RoomPresence
from band.runtime.prompts import BASE_INSTRUCTIONS, TEMPLATES, render_system_prompt
from band.runtime.retry_tracker import MessageRetryTracker
from band.runtime.runtime import AgentRuntime
from band.runtime.sessionconfig import SessionConfig
from band.runtime.shutdown import GracefulShutdown, run_with_graceful_shutdown

# Tools
from .tools import (
from band.runtime.tools import (
ALL_TOOL_NAMES,
BASE_TOOL_NAMES,
CHAT_TOOL_NAMES,
Expand All @@ -50,13 +58,6 @@
HumanTools,
mcp_tool_names,
)
from .types import (
AgentConfig,
ConversationContext,
MessageHandler,
PlatformMessage,
SessionConfig,
)

__all__ = [
"ALL_TOOL_NAMES",
Expand Down
23 changes: 23 additions & 0 deletions src/band/runtime/agentconfig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Agent connection and room subscription policy."""

from __future__ import annotations

from dataclasses import dataclass

from band.core.types import ConflictPolicy


@dataclass
class AgentConfig:
"""Configuration for agent runtime."""

auto_subscribe_existing_rooms: bool = True
# Refuse to start when another process on this host already runs the
# same agent id: duplicates steal each other's in-flight room messages
# (the recovery sweep has no liveness check) and stateful adapters
# resume the same on-disk sessions, splitting one conversation.
single_instance: bool = True
# Platform-side duplicate guard for the initial WebSocket connect (cross-host,
# cross-TMPDIR); complements ``single_instance`` on this host. See
# ``ConflictPolicy`` for semantics and limits.
conflict_policy: ConflictPolicy = ConflictPolicy.SUPERSEDE
22 changes: 22 additions & 0 deletions src/band/runtime/callbacks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Callback contracts for runtime messages and events."""

from __future__ import annotations

from collections.abc import Awaitable, Callable
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from band.platform.event import (
ContactEvent,
ParticipantAddedEvent,
ParticipantRemovedEvent,
)
from band.runtime.contact_tools import ContactTools
from band.runtime.messages import PlatformMessage
from band.runtime.tools.agent import AgentTools


MessageHandler = Callable[["PlatformMessage", "AgentTools"], Awaitable[None]]
ContactEventCallback = Callable[["ContactEvent", "ContactTools"], Awaitable[None]]
ParticipantAddedCallback = Callable[[str, "ParticipantAddedEvent"], Awaitable[None]]
ParticipantRemovedCallback = Callable[[str, "ParticipantRemovedEvent"], Awaitable[None]]
7 changes: 2 additions & 5 deletions src/band/runtime/contact_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,8 @@
)
from band.platform.posting import post_event
from band.runtime.contact_tools import ContactTools
from band.runtime.types import (
ContactEventConfig,
ContactEventStrategy,
normalize_handle,
)
from band.runtime.contacts import ContactEventConfig, ContactEventStrategy
from band.runtime.handles import normalize_handle

if TYPE_CHECKING:
from band.platform.link import BandLink
Expand Down
Loading
Loading