        if guest_mention:
            return True
        if chat_id_str in self._telegram_free_response_chats():
            return True
        if not self._telegram_require_mention():
            return True
        if self._is_reply_to_bot(message):
            return True
        # When guest_mode is True, _is_guest_mention already called
        # _message_mentions_bot above — skip the redundant second call.
        if not self._telegram_guest_mode() and self._message_mentions_bot(message):
            return True
        return self._message_matches_mention_patterns(message)

    async def _ensure_forum_commands(self, message) -> None:
        """Lazy-register bot commands for forum supergroups.

        Forum topics don't inherit AllGroupChats scope — Telegram resolves
        via BotCommandScopeChat(chat_id).  Register on first message so the
        command menu works in topic views.
        """
        async with self._forum_lock:
            try:
                chat = getattr(message, "chat", None)
                if not chat or not getattr(chat, "is_forum", False):
                    return
                chat_id = int(chat.id)
                if chat_id in self._forum_command_registered:
                    return
                from telegram import BotCommand, BotCommandScopeChat
                from hermes_cli.commands import telegram_menu_commands
                menu_commands, _ = telegram_menu_commands(max_commands=MAX_COMMANDS_PER_SCOPE)
                bot_commands = [BotCommand(name, desc) for name, desc in menu_commands]
                await self._bot.set_my_commands(bot_commands, scope=BotCommandScopeChat(chat_id=chat_id))
                self._forum_command_registered.add(chat_id)
                logger.info("[%s] Lazy-registered %d commands for forum chat %s", self.name, len(bot_commands), chat_id)
            except Exception as e:
                logger.warning("[%s] Forum command lazy-registration failed: %s", self.name, e)

    def _effective_update_message(self, update: Update) -> Optional[Message]:
        """Return the message-like payload for every Telegram message ingress."""
        for field in (
            "effective_message",
            "message",
            "edited_message",
            "channel_post",
            "edited_channel_post",
        ):
            value = getattr(update, field, None)
            if value is not None:
                return value
        return None

    def _room_bootstrap_reserves_ingress(self, message: object) -> bool:
        """Reserve every nonterminal bootstrap chat before generic dispatch."""
        room_bootstrap = (
            getattr(self, "_room_bootstrap_transport", None)
            or self._get_room_bootstrap_transport()
        )
        room_store = getattr(room_bootstrap, "store", None)
        room_chat = getattr(getattr(message, "chat", None), "id", None)
        if room_store is None or room_chat is None:
            return False
        try:
            return room_store.get_by_chat(str(room_chat)) is not None
        except Exception as exc:
            logger.warning(
                "[%s] room bootstrap ingress reservation failed closed: %s",
                self.name,
                type(exc).__name__,
            )
            return True

    async def _handle_contact_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
        """Reserve contact updates before any generic ingress can see them."""
        message = self._effective_update_message(update)
        if message is not None:
            if self._room_bootstrap_reserves_ingress(message):
                return
            await self._reserve_adaptive_review_update(update, message)

    async def _handle_edited_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
        """Reserve edited messages; edited content never replays a workflow."""
        message = self._effective_update_message(update)
        if message is not None:
            if self._room_bootstrap_reserves_ingress(message):
                return
            await self._reserve_adaptive_review_update(update, message)

    async def _handle_channel_post(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
        """Reserve channel posts before the ordinary message routes."""
        message = self._effective_update_message(update)
        if message is not None:
            if self._room_bootstrap_reserves_ingress(message):
                return
            await self._reserve_adaptive_review_update(update, message)

    async def _handle_edited_channel_post(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
        """Reserve edited channel posts before the ordinary message routes."""
        message = self._effective_update_message(update)
        if message is not None:
            if self._room_bootstrap_reserves_ingress(message):
                return
            await self._reserve_adaptive_review_update(update, message)
    async def _handle_text_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
        """Handle incoming text messages.

        Telegram clients split long messages into multiple updates.  Buffer
        rapid successive text messages from the same user/chat and aggregate
        them into a single MessageEvent before dispatching.
        """
        msg = self._effective_update_message(update)
        if not msg or not msg.text:
            return
        preview = self._get_nutrition_onboarding_preview()
        if preview is not None and preview.reserves(msg):
            await preview.handle_text(msg)
            return
        if " ".join(str(msg.text).split()) == "서비스 중단 및 동의 철회 요청":
            nutrition = self._get_nutrition_coaching()
            if nutrition is None and self._nutrition_coaching_declared_enabled():
                return
            if nutrition is not None:
                transition = nutrition.withdraw_customer(self._nutrition_address(msg))
                await msg.reply_text(transition.reply.text)
                return
        if await self._handle_nutrition_onboarding_text(update, msg):
            return
        if self._room_bootstrap_reserves_ingress(msg):
            return
        if await self._reserve_adaptive_review_update(update, msg):
            return
        nutrition = self._get_nutrition_coaching()
        if nutrition is None and self._nutrition_coaching_declared_enabled():
            return
        if nutrition is not None:
            address = self._nutrition_address(msg)
        if nutrition is not None:
            if self._nutrition_operator_actor(address, nutrition) is not None and await self._handle_nutrition_draft_edit_text(
                msg, address, nutrition
            ):
                return
            if self._is_nutrition_operator_space(address):
                await msg.reply_text(
                    "운영자 검토실입니다. 초안 알림의 버튼을 사용해 생성·수정·승인·전송해 주세요."
                )
                return
            resolved = nutrition.resolve(address)
            if resolved is not None:
                if self._is_nutrition_customer_start_command(msg.text):
                    await self._send_nutrition_customer_card(
                        msg,
                        nutrition,
                        address=address,
                    )
                    return
                transition = nutrition.handle_text(address, msg.text)
                await self._render_nutrition_text(msg, transition, resolved.bridge)
                return
            open_onboarding = getattr(nutrition, "open_customer_onboarding", None)
            if (
                self._is_nutrition_customer_start_command(msg.text)
                and callable(open_onboarding)
            ):
                onboarding = open_onboarding(address)
                if onboarding is not None:
                    await msg.reply_text(
                        onboarding.text,
                        reply_markup=self._nutrition_onboarding_markup(onboarding),
                    )
                    return
            stopped_customer = getattr(nutrition, "resolve_stopped", None)
            if callable(stopped_customer) and stopped_customer(address) is not None:
                await msg.reply_text(
                    "서비스가 중단된 상태입니다. 새 체크인과 코칭 전달은 제공하지 않습니다."
                )
                return
            if self._is_nutrition_customer_start_command(msg.text):
                return
            if nutrition.owns_space(address.chat_id, address.topic_id):
                return
        # Typed wizard answers must never enter generic message batching or
        # LLM/session aggregation.  A falsey result means no active private
        # wizard exists, preserving the normal Telegram behavior unchanged.
        bridge = self._get_physique_checkin()
        if bridge is not None:
            owner_id = self._physique_text_owner_id(msg)
            chat_id = self._physique_chat_id(msg)
            topic_id = self._physique_thread_id(msg)
            if self._is_physique_recovery_command(msg.text):
                # A scheduled send can fail after the KST-day claim is safely
                # persisted. Only the exact owner/topic may manually recover;
                # foreign/wrong-topic commands are consumed, never sent to an
                # LLM or replied to outside the private target.
                if bridge.accepts_context(owner_id, chat_id, topic_id):
                    await self.send_inline_card("physique-checkin-morning")
                return
            if bridge.accepts_context(owner_id, chat_id, topic_id) and self._is_physique_source_approval_request(msg.text):
                await self._handle_physique_source_approval_text(msg)
                return
            if bridge.accepts_context(owner_id, chat_id, topic_id) and self._is_physique_feedback_replay_request(msg.text):
                finalized = bridge.latest_finalized_coaching_snapshot()
                if finalized is not None:
                    await self._render_physique_feedback_replay(msg, finalized)
                    return
            reply = bridge.handle_text(
                msg.text,
                owner_id,
                chat_id,
                topic_id,
            )
            raw_snapshot = bridge.active_checkin_snapshot()
            snapshot = raw_snapshot if isinstance(raw_snapshot, dict) else None
            if reply is not None:
                if reply.accepted:
                    if reply.prompt is not None:
                        await self._render_physique_text_prompt(msg, reply)
                    return
                interpreted = await self._interpret_physique_active_turn(msg.text, snapshot) if snapshot is not None else None
                if interpreted is not None:
                    action, value, coach_text = interpreted
                    applied = bridge.apply_model_action(action, value)
                    if applied.accepted:
                        await self._render_physique_model_turn(
                            msg, applied, coach_text, edit_current=action in {"clarify_current_step", "rewrite_prompt"},
                        )
                        return
                await self._render_physique_conversation_reply(msg, msg.text)
                return
            if snapshot is not None:
                interpreted = await self._interpret_physique_active_turn(msg.text, snapshot)
                if interpreted is not None:
                    action, value, coach_text = interpreted
                    applied = bridge.apply_model_action(action, value)
                    if applied.accepted:
                        await self._render_physique_model_turn(
                            msg, applied, coach_text, edit_current=action in {"clarify_current_step", "rewrite_prompt"},
                        )
                        return
                await self._render_physique_conversation_reply(msg, msg.text)
                return
            if bridge.accepts_context(owner_id, chat_id, topic_id) and self._is_physique_numeric_answer(msg.text):
                await self.send_inline_card("physique-checkin-morning")
                return
            if bridge.accepts_context(owner_id, chat_id, topic_id):
                await self._render_physique_conversation_reply(msg, msg.text)
                return
        if not self._should_process_message(msg):
            if self._should_observe_unmentioned_group_message(msg):
                self._observe_unmentioned_group_message(msg, MessageType.TEXT, update_id=update.update_id)
            return
        await self._ensure_forum_commands(update.message)

        event = self._build_message_event(msg, MessageType.TEXT, update_id=update.update_id)
        event.text = self._clean_bot_trigger_text(event.text)
        await self._cache_replied_media(msg, event)
        event = self._apply_telegram_group_observe_attribution(event)
        self._enqueue_text_event(event)

    async def _handle_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
        """Handle incoming command messages."""
        msg = self._effective_update_message(update)
        if not msg or not msg.text:
            return
        rehearsal_match = _REHEARSAL_CUSTOMER_START_RE.fullmatch(str(msg.text))
        if rehearsal_match is not None:
            await self._handle_rehearsal_customer_start(
                msg,
                rehearsal_match.group(1),
            )
            return
        command = msg.text.split(maxsplit=1)[0].split("@", 1)[0].lower()
        if command == "/nutritionops":
            from gateway.platforms.telegram_nutrition_operator_console import (
                root_menu,
            )

            address = self._nutrition_address(msg, msg)
            result = root_menu(
                address,
                owner_address=self._nutrition_operator_menu_authority(address),
            )
            await self._send_adaptive_operator_result(msg, result)
            return
        if self._room_bootstrap_reserves_ingress(msg):
            return
        if await self._reserve_adaptive_review_update(update, msg):
            return
        if command == "/nutritionpreview":
            preview = self._get_nutrition_onboarding_preview()
            if preview is not None:
                await preview.handle_command(msg)
            return
        if command == "/coachingid":
            user_id = getattr(getattr(msg, "from_user", None), "id", None)
            if user_id is None:
                await msg.reply_text("사용자 ID를 확인할 수 없습니다. 익명 관리자 전송을 끄고 다시 시도해 주세요.")
                return
            chat_id = getattr(getattr(msg, "chat", None), "id", "")
            topic_id = getattr(msg, "message_thread_id", None) or self._GENERAL_TOPIC_THREAD_ID
            await msg.reply_text(
                "고객 등록 주소입니다.\n"
                f"user_id={user_id}\nchat_id={chat_id}\ntopic_id={topic_id}"
            )
            return
        if not self._should_process_message(msg, is_command=True):
            return
        await self._ensure_forum_commands(msg)

        event = self._build_message_event(msg, MessageType.COMMAND, update_id=update.update_id)
        event.text = self._clean_bot_trigger_text(event.text)
        await self._cache_replied_media(msg, event)
        event = self._apply_telegram_group_observe_attribution(event)
        await self.handle_message(event)

    async def _handle_location_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
        """Handle incoming location/venue pin messages."""
        msg = self._effective_update_message(update)
        if not msg:
            return
        if self._room_bootstrap_reserves_ingress(msg):
            return
        if await self._reserve_adaptive_review_update(update, msg):
            return
        if not self._should_process_message(msg):
            if self._should_observe_unmentioned_group_message(msg):
                self._observe_unmentioned_group_message(msg, MessageType.LOCATION, update_id=update.update_id)
            return

[886 more lines in file. Use offset=13810 to continue.]


"""Profile-gated configuration for multi-customer nutrition coaching."""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import cast

JsonValue = object

@dataclass(frozen=True, slots=True)
class AdaptiveReviewOperator:
    """The exact Telegram triple used to authenticate review ingress."""

    user_id: str
    chat_id: str
    topic_id: int
    version: int

    def __post_init__(self) -> None:
        user_id = str(self.user_id or "").strip()
        chat_id = str(self.chat_id or "").strip()
        if not user_id or not chat_id or type(self.topic_id) is not int or self.topic_id != 59:
            raise ValueError("adaptive review operator address is invalid")
        if isinstance(self.version, bool) or not isinstance(self.version, int) or self.version < 1:
            raise ValueError("adaptive review operator version is invalid")
        object.__setattr__(self, "user_id", user_id)
        object.__setattr__(self, "chat_id", chat_id)

    @property
    def key(self) -> tuple[str, str, str]:
        return self.user_id, self.chat_id, str(self.topic_id)

    def as_dict(self) -> dict[str, object]:
        return {
            "user_id": self.user_id,
            "chat_id": self.chat_id,
            "topic_id": self.topic_id,
            "version": self.version,
        }

@dataclass(frozen=True, slots=True)
class OperatorCardRoute:
    """An explicitly configured destination for generation review cards."""

    user_id: str
    chat_id: str
    topic_id: int
    version: int

    def __post_init__(self) -> None:
        user_id = str(self.user_id or "").strip()
        chat_id = str(self.chat_id or "").strip()
        if (
            not user_id
            or not chat_id
            or isinstance(self.topic_id, bool)
            or type(self.topic_id) is not int
            or self.topic_id != 0
            or user_id != chat_id
        ):
            raise ValueError("operator card route is invalid")
        if isinstance(self.version, bool) or type(self.version) is not int or self.version < 1:
            raise ValueError("operator card route version is invalid")
        object.__setattr__(self, "user_id", user_id)
        object.__setattr__(self, "chat_id", chat_id)

@dataclass(frozen=True, slots=True)
class NutritionCoachingConfig:
    registry_path: Path
    operator_card_route: OperatorCardRoute | None = None

    @classmethod
    def from_extra(cls, extra: JsonValue) -> NutritionCoachingConfig | None:
        if not isinstance(extra, dict):
            return None
        raw_value = cast(dict[str, object], extra).get("nutrition_coaching")
        if not isinstance(raw_value, dict):
            return None
        raw = cast(dict[str, object], raw_value)
        if raw.get("enabled") is not True:
            return None
        path = Path(str(raw.get("registry_path", "")))
        if not path.parts or path.is_absolute() or ".." in path.parts:
            return None
        route_value = raw.get("operator_card_route")
        route: OperatorCardRoute | None = None
        if route_value is not None:
            if not isinstance(route_value, dict):
                return None
            route_raw = cast(dict[str, object], route_value)
            if set(route_raw) != {"user_id", "chat_id", "topic_id", "version"}:
                return None
            raw_topic_id = route_raw["topic_id"]
            version = route_raw["version"]
            if (
                isinstance(raw_topic_id, bool)
                or not isinstance(raw_topic_id, (int, str))
                or isinstance(version, bool)
                or not isinstance(version, int)
            ):
                return None
            try:
                route = OperatorCardRoute(
                    str(route_raw["user_id"]),
                    str(route_raw["chat_id"]),
                    int(raw_topic_id),
                    version,
                )
            except (TypeError, ValueError):
                return None
        return cls(path, route)

@dataclass(frozen=True, slots=True)
class AdaptiveNutritionConfig:
    enabled: bool
    operator_chat_id: str
    operator_topic_id: int
    delivery_enabled: bool
    operator_user_id: str = ""
    operator_version: int = 0
    review_operator: AdaptiveReviewOperator | None = None
    schedule_confirm_enabled: bool = False

    @classmethod
    def from_extra(cls, extra: JsonValue) -> "AdaptiveNutritionConfig | None":
        if not isinstance(extra, dict):
            return None
        raw = extra.get("adaptive_nutrition")
        if not isinstance(raw, dict) or raw.get("enabled") is not True:
            return None
        review = raw.get("review_operator")
        review_operator: AdaptiveReviewOperator | None = None
        if isinstance(review, dict):
            try:
                if "version" not in review:
                    return None
                review_operator = AdaptiveReviewOperator(
                    str(review.get("user_id", "")).strip(),
                    str(review.get("chat_id", "")).strip(),
                    int(review.get("topic_id")) if str(review.get("topic_id", "")).isdigit() else review.get("topic_id"),
                    review.get("version"),
                )
            except (TypeError, ValueError):
                return None
        # An enabled adaptive surface is never valid with only the historical
        # chat/topic pair. That shape cannot authenticate the review actor and
        # must fail closed rather than silently creating an unbound ingress.
        if review_operator is None:
            return None
        chat_id = str(raw.get("operator_chat_id", "")).strip()
        topic_id = raw.get("operator_topic_id")
        if chat_id and chat_id != review_operator.chat_id:
            return None
        if topic_id is not None:
            try:
                legacy_topic = int(topic_id)
            except (TypeError, ValueError):
                return None
            if legacy_topic != review_operator.topic_id:
                return None
        chat_id = review_operator.chat_id
        topic_id = review_operator.topic_id
        delivery = raw.get("delivery_enabled", False)
        schedule_confirm = raw.get("schedule_confirm_enabled", False)
        if (
            not chat_id
            or isinstance(topic_id, bool)
            or topic_id != 59
            or not isinstance(delivery, bool)
            or not isinstance(schedule_confirm, bool)
        ):
            return None
        return cls(
            True,
            chat_id,
            59,
            delivery,
            review_operator.user_id,
            review_operator.version,
            review_operator,
            schedule_confirm,
        )


    """Raised when the durable draft ledger cannot be trusted for a write."""

    def __init__(self, detail: str = "draft ledger is unavailable") -> None:
        detail = str(detail)
        super().__init__(detail)
        self.detail = detail
def _resolve_committed_registry_path(profile_root: Path) -> Path:
    """Resolve the profile package's canonical registry path without revalidating it."""
    from checkin_cli.customer_admin import _resolve_profile_root, _resolve_registry_path

    root = _resolve_profile_root(Path(profile_root))
    return _resolve_registry_path(root)

def load_committed_customer_registry(profile_root: Path) -> tuple[CustomerRegistry, Path]:
    """Load the profile registry through its committed-activation runtime gate."""
    from checkin_cli.customer_admin import load_runtime_customer_registry

    root = Path(profile_root)
    registry = load_runtime_customer_registry(root)
    return registry, _resolve_committed_registry_path(root)

class NutritionCoachingCoordinator:
    """Route customer submissions and owner draft requests without generic ingress."""

    def __init__(
        self,
        profile_root: Path,
        registry: CustomerRegistry,
        registry_path: Path | None = None,
        *,
        kst_date_provider: Callable[[], date | datetime] | None = None,
        generation_now_provider: Callable[[], datetime] | None = None,
        customer_transport: object | None = None,
        delivery_enabled: bool = False,
        review_operator: object | None = None,
        owner_scheduled_routes: object = (),
        generic_reserved_routes: object = (),
    ) -> None:
        self._kst_date_provider = kst_date_provider or _current_kst_date
        self._generation_now_provider = generation_now_provider or (lambda: datetime.now(_KST))
        self._customer_transport = customer_transport
        self._delivery_enabled = delivery_enabled is True
        self._review_operator = review_operator
        self._owner_scheduled_routes = owner_scheduled_routes
        self._generic_reserved_routes = generic_reserved_routes
        self._profile_root = Path(profile_root)
        self._registry_path = Path(registry_path) if registry_path is not None else None
        self._registry_generation = (
            self._registry_fingerprint(self._registry_path)
            if self._registry_path is not None
            else None
        )
        self._live_registry_valid = registry_path is None or self._registry_generation is not None
        self._live_registry_error: str | None = None
        owner_actions = self._profile_root / "data" / "owner-actions"
        self._service_state_store = CustomerServiceStateStore(
            owner_actions / "customer-service-state.json"
        )
        self._service_state_error: str | None = None
        self._requests_path = owner_actions / "draft-requests.json"
        self._finalization_journal_path = (
            owner_actions / "checkin-finalization-journal.json"
        )
        self._drafts_path = owner_actions / "drafts.json"
        self._generations_path: Path = owner_actions / "draft-generations.json"
        self._generation_authority_path: Path = (
            owner_actions / "draft-generations.authority.json"
        )
        self._deliveries_path = owner_actions / "draft-deliveries.json"
        self._generation_cards_path = owner_actions / "draft-generation-cards.json"
        self._outbox_path = self._deliveries_path
        self._configure_registry(registry)
        self._finalization_recovery_error: str | None = None
        try:
            self._recover_checkin_finalizations()
        except DraftLedgerError as exc:
            self._finalization_recovery_error = exc.detail
        self.schedule_confirm_handler = _ScheduleConfirmationFacade(self)

    @staticmethod
    def _registry_fingerprint(path: Path) -> tuple[int, int, int, int] | None:
        try:
            stat = path.stat()
        except OSError:
            return None
        return (
            int(getattr(stat, "st_ino", 0)),
            int(getattr(stat, "st_size", 0)),
            int(getattr(stat, "st_mtime_ns", 0)),
            int(getattr(stat, "st_ctime_ns", 0)),
        )

    def _configure_registry(self, registry: CustomerRegistry) -> None:
        from checkin_cli.wizard import WizardService

        self._registry = registry
        self._registered_routes = {
            tuple(str(value) for value in customer.spec.telegram.key): customer
            for customer in registry.customers
        }
        self._stopped_routes = {
            tuple(str(value) for value in customer.spec.telegram.key): str(
                customer.spec.customer_key
            )
            for customer in registry.customers
            if customer.spec.enabled is False
        }
        if self._review_operator is not None:
            validator = validate_review_space_disjoint
            if not callable(validator):
                raise RuntimeError("adaptive review-space validator is unavailable")
            customer_routes = []
            for candidate in registry.customers:
                spec = getattr(candidate, "spec", None)
                if getattr(spec, "enabled", False) is not True:
                    continue
                customer_routes.append(getattr(spec, "telegram", None))
            try:
                validator(
                    self._review_operator,
                    customer_routes=tuple(customer_routes),
                    owner_scheduled_routes=(
                        registry.owner,
                        self._owner_scheduled_routes,
                    ),
                    generic_reserved_routes=self._generic_reserved_routes,
                )
            except Exception as exc:
                raise AdaptiveWorkflowError(
                    "adaptive review-space collision validation failed"
                ) from exc
        routes: dict[tuple[str, str, str], ResolvedCustomer] = {}
        by_key: dict[str, ResolvedCustomer] = {}
        event_sources: dict[str, object] = {}
        for customer in registry.customers:
            if not customer.spec.enabled:
                continue
            telegram = customer.spec.telegram
            wizard_root = customer.data_root / "wizard"
            service = WizardService.for_registered(customer)
            event_source = getattr(service, "_events", None)
            if event_source is None or not callable(getattr(event_source, "_read_events", None)):
                raise RuntimeError("customer canonical EventStore is unavailable")
            config = PhysiqueCheckinConfig(
                telegram.user_id,
                telegram.chat_id,
                telegram.topic_id,
                43_200,
                False,
                False,
            )
            bridge = PhysiqueCheckinBridge(
                config,
                service,
                wizard_root / "telegram-bindings.json",
                customer_key=customer.spec.customer_key,
            )
            resolved = ResolvedCustomer(customer, bridge)
            routes[telegram.key] = resolved
            by_key[customer.spec.customer_key] = resolved
            event_sources[customer.spec.customer_key] = event_source
        self._routes = routes
        self._by_key = by_key
        self._event_sources = event_sources
        self._spaces = {
            (chat_id, topic_id)
            for _, chat_id, topic_id in self._registered_routes
        }

    def _ensure_live_registry(self) -> bool:
        path = getattr(self, "_registry_path", None)
        if not isinstance(path, Path):
            return True
        if not callable(profile_authority_lock):
            self._live_registry_valid = False
            self._live_registry_error = "customer registry reload failed: authority_lock_unavailable"
            return False
        with profile_authority_lock(self._profile_root):
            try:
                registry, canonical_path = load_committed_customer_registry(self._profile_root)
                path = canonical_path
                self._registry_path = canonical_path
                generation = self._registry_fingerprint(path)
                if generation is None:
                    raise RuntimeError("customer registry is unavailable")
            except Exception as exc:
                self._live_registry_valid = False
                self._live_registry_error = (
                    f"customer registry reload failed: {type(exc).__name__}"
                )
                return False
            if (
                generation == getattr(self, "_registry_generation", None)
                and getattr(self, "_live_registry_valid", True)
            ):
                self._live_registry_error = None
                return True
            try:
                self._configure_registry(registry)
            except Exception as exc:
                self._live_registry_valid = False
                self._live_registry_error = (
                    f"customer registry reload failed: {type(exc).__name__}"
                )
                return False
            self._registry_generation = generation
            self._live_registry_valid = True
            self._live_registry_error = None
            return True

    def refresh_live_registry(self) -> bool:
        """Refresh on-disk customer authority and revalidate its activation receipt."""
        return self._ensure_live_registry()
    @property
    def owner_space(self) -> tuple[str, str]:
        return self._registry.owner.space_key

    @property
    def owner(self) -> IncomingAddress:
        return IncomingAddress(*self._registry.owner.key)

    @property
    def profile_root(self) -> Path:
        return self._profile_root

    @property
    def registry(self) -> CustomerRegistry:
        return self._registry

    def set_customer_transport(self, transport: object | None) -> None:
        """Install the authoritative registered-customer transport."""
        if transport is not None and not any(
            callable(getattr(transport, name, None))
            for name in ("send_customer", "send_adaptive_customer")
        ):
            raise AdaptiveWorkflowError("customer transport is unavailable")
        self._customer_transport = transport

    def set_delivery_enabled(self, enabled: bool) -> None:
        """Set the explicit production delivery gate."""
        if type(enabled) is not bool:
            raise AdaptiveWorkflowError("adaptive delivery gate is invalid")
        self._delivery_enabled = enabled

    def adaptive_nutrition_coordinator(
        self,
        customer_key: str,
        *,
        event_path: Path | str | None = None,
        customer_transport: object | None = None,
        delivery_enabled: bool | None = None,
    ) -> AdaptiveNutritionCoordinator:
        """Return the fixed-topic adaptive surface for one live customer."""
        if not self._ensure_live_registry():
            raise AdaptiveWorkflowError("customer registry is unavailable")
        key = str(customer_key)
        resolved = getattr(self, "_by_key", {}).get(key)
        if resolved is None or not bool(getattr(resolved.customer.spec, "enabled", False)):
            raise AdaptiveWorkflowError("adaptive customer route is unavailable")
        data_root = getattr(resolved.customer, "data_root", None)
        if not isinstance(data_root, Path):
            raise AdaptiveWorkflowError("adaptive customer data root is unavailable")
        canonical_event_path = data_root / "nutrition-plans" / "events.jsonl"
        if event_path is not None and Path(event_path).resolve() != canonical_event_path.resolve():
            raise AdaptiveWorkflowError("production adaptive events must use the customer runtime")
        event_source = self.event_source(key)
        if event_source is None or not callable(getattr(event_source, "_read_events", None)):
            raise AdaptiveWorkflowError("canonical customer EventStore is unavailable")
        return AdaptiveNutritionCoordinator(
            customer_key=key,
            starts_on=_plan_window_start(resolved.customer),
            event_path=canonical_event_path,
            profile_root=self._profile_root,
            registry_path=self._registry_path,
            canonical_event_source=event_source,
            customer_runtime=resolved.customer,
            authority=self,
            customer_transport=(
                customer_transport
                if customer_transport is not None
                else getattr(self, "_customer_transport", None)
            ),
            delivery_enabled=(
                self._delivery_enabled
                if delivery_enabled is None
                else delivery_enabled is True
            ),
        )

    adaptive_operator_coordinator = adaptive_nutrition_coordinator
    def set_adaptive_delivery(
        self,
        customer_key: str,
        enabled: bool,
        *,
        operator_id: object,
        topic_id: object = 59,
    ) -> Mapping[str, object]:
        adaptive = self.adaptive_nutrition_coordinator(customer_key)
        return adaptive.set_persisted_delivery(
            enabled,
            operator_id=operator_id,
            topic_id=topic_id,
        )
    def _registry_customer(self, customer_key: str) -> CustomerRuntime | None:
        for candidate in getattr(self._registry, "customers", ()):
            spec = getattr(candidate, "spec", None)
            if getattr(spec, "customer_key", None) == customer_key:
                return candidate
        return None

    def _customer_has_safety_hold(self, resolved: ResolvedCustomer) -> bool:
        events_path = getattr(resolved.customer, "data_root", None)
        events_path = (
            events_path / "wizard" / "events.jsonl"
            if isinstance(events_path, Path)
            else None
        )
        if events_path is None or not events_path.exists():
            return False
        try:
            for line in events_path.read_text(encoding="utf-8").splitlines():
                if not line:
                    continue
                event = json.loads(line)
                if self._safety_value_has_hold(self._field(event, "safety")):
                    return True
        except (OSError, TypeError, ValueError):
            return True
        return False
    def _transport_kst_date(self, override: date | datetime | None = None) -> date | None:
        value: object = override
        if value is None:
            provider = getattr(self, "_kst_date_provider", None)
            try:
                value = provider() if callable(provider) else _current_kst_date()
            except Exception:
                return None
        return _coerce_kst_date(value)

    def _customer_plan_window_allows(
        self,
        customer: object,
        *,
        kst_date: date | datetime | None = None,
    ) -> bool:
        starts_on = _plan_window_start(customer)
        current = self._transport_kst_date(kst_date)
        return (
            starts_on is not None
            and current is not None
            and starts_on <= current <= starts_on + timedelta(days=27)
        )
    def _adaptive_plan_window_allows(
        self,
        customer: object,
        *,
        kst_date: date | datetime | None = None,

[18639 more lines in file. Use offset=2460 to continue.]


8월 17 08:47:23.434056 cube-B850M-AORUS-ELITE-WIFI6E-ICE python[343199]: ERROR gateway.platforms.telegram: telegram_ingress stage=receipt update_id=629525116 reason_code=handler_exception


**Investigating config parsing and adapter creation errors**

bash

rg -n 'def _get_nutrition_coaching|NutritionCoachingConfig|load_committed_customer_registry|NutritionCoachingCoordinator\(' /home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py /home/cube/projects/richard/hermes-agent/gateway/run.py /home/cube/projects/richard/hermes-agent/gateway/platforms/*.py | head -200; rg -n 'def _is_nutrition_customer_start_command|def _nutrition_address|def customer_transport_allowed|def current_kst_date|def resolve\(' /home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py /home/cube/projects/richard/hermes-agent/gateway/platforms/nutrition_coaching.py

cd /home/cube/.hermes/profiles/dualcoachtest && printf '%s\n' '--- config relevant ---'; python - <<'PY'
import yaml, json
p=yaml.safe_load(open('config.yaml'))
# print only structurally relevant config, redact token-like scalar leaves
for key in ('home','timezone','gateway','platforms'):
    if key in p: print(key, json.dumps(p[key], ensure_ascii=False, indent=2, default=str))
PY
printf '%s\n' '--- registry selected ---'; python - <<'PY'
import json
p=json.load(open('customers/registry.json'))
print(json.dumps(p, ensure_ascii=False, indent=2))
PY
printf '%s\n' '--- service state ---'; python - <<'PY'
from pathlib import Path
for f in [Path('data/owner-actions/customer-service-state.json'),Path('data/telegram-ingress-receipts-v1-d0aacf0f4bdbb7c0.json')]:
 print('##',f); print(f.read_text() if f.exists() else 'MISSING')
PY

cd /home/cube/.hermes/profiles/dualcoachtest && printf '%s\n' '--- gateway log incident ---'; rg -n -C 12 '629525116|handler_exception|8527916639|checkin' logs/gateway.log logs/agent.log logs/gateway-shutdown-diag.log 2>/dev/null | tail -300; printf '%s\n' '--- metadata around incident ---'; stat customers/registry.json data/owner-actions/customer-service-state.json data/telegram-ingress-receipts-v1-d0aacf0f4bdbb7c0.json


/home/cube/projects/richard/hermes-agent/gateway/platforms/nutrition_coaching_config.py:73:class NutritionCoachingConfig:
/home/cube/projects/richard/hermes-agent/gateway/platforms/nutrition_coaching_config.py:78:    def from_extra(cls, extra: JsonValue) -> NutritionCoachingConfig | None:
/home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py:135:    NutritionCoachingConfig,
/home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py:624:        self._nutrition_coaching_config = NutritionCoachingConfig.from_extra(config.extra)
/home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py:4618:    def _get_nutrition_coaching(self):
/home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py:4704:                load_committed_customer_registry,
/home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py:4707:            registry, registry_path = load_committed_customer_registry(profile_root)
/home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py:4720:            self._nutrition_coaching = NutritionCoachingCoordinator(
/home/cube/projects/richard/hermes-agent/gateway/platforms/nutrition_coaching.py:2114:def load_committed_customer_registry(profile_root: Path) -> tuple[CustomerRegistry, Path]:
/home/cube/projects/richard/hermes-agent/gateway/platforms/nutrition_coaching.py:2281:                registry, canonical_path = load_committed_customer_registry(self._profile_root)
/home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py:135:    NutritionCoachingConfig,
/home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py:624:        self._nutrition_coaching_config = NutritionCoachingConfig.from_extra(config.extra)
/home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py:4618:    def _get_nutrition_coaching(self):
/home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py:4704:                load_committed_customer_registry,
/home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py:4707:            registry, registry_path = load_committed_customer_registry(profile_root)
/home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py:4720:            self._nutrition_coaching = NutritionCoachingCoordinator(
/home/cube/projects/richard/hermes-agent/gateway/platforms/nutrition_coaching.py:2551:    def customer_transport_allowed(
/home/cube/projects/richard/hermes-agent/gateway/platforms/nutrition_coaching.py:2704:    def resolve(self, address: IncomingAddress) -> ResolvedCustomer | None:
/home/cube/projects/richard/hermes-agent/gateway/platforms/nutrition_coaching.py:3118:    def current_kst_date(self) -> date:
/home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py:5244:    def _nutrition_address(self, message_or_query: object, message: object | None = None):
/home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py:7490:    def _is_nutrition_customer_start_command(text: str) -> bool:


--- config relevant ---
timezone "Asia/Seoul"
gateway {
  "message_timestamps": {
    "enabled": false
  },
  "strict": false,
  "media_delivery_allow_dirs": [],
  "trust_recent_files": true,
  "trust_recent_files_seconds": 600,
  "multiplex_profiles": false
}
platforms {
  "telegram": {
    "enabled": true,
    "allow_from": [
      8693203710
    ],
    "group_allow_from": [
      8693203710
    ],
    "group_allowed_chats": [
      -1004484458766
    ],
    "extra": {
      "nutrition_onboarding_preview": {
        "enabled": true,
        "chat_id": "-1004484458766",
        "topic_name": "[PREVIEW] 영양 온보딩",
        "topic_id": "219"
      },
      "room_bootstrap": {
        "enabled": true,
        "nutrition_onboarding": true
      },
      "nutrition_coaching": {
        "enabled": true,
        "registry_path": "customers/registry.json",
        "operator_review": {
          "user_id": "8693203710",
          "chat_id": "-1004484458766",
          "topic_id": "59"
        },
        "operator_card_route": {
          "user_id": "8693203710",
          "chat_id": "8693203710",
          "topic_id": 0,
          "version": 1
        }
      },
      "physique_checkin": {
        "enabled": false,
        "owner_id": "8693203710",
        "chat_id": "-1004484458766",
        "topic_id": "3",
        "allowed_chats": [
          "-1004484458766"
        ],
        "allowed_topics": [
          "3"
        ],
        "allow_anonymous_sender_chat": true,
        "coaching_feedback_enabled": true,
        "require_mention": false,
        "expires_seconds": 43200
      },
      "adaptive_nutrition": {
        "enabled": true,
        "delivery_enabled": false,
        "analytics_shadow": false,
        "operator_candidates": false,
        "activation": false,
        "delivery": false,
        "review_operator": {
          "user_id": "8693203710",
          "chat_id": "-1004484458766",
          "topic_id": "59",
          "version": 1
        },
        "separate_bot": {
          "dedicated": true,
          "bot_username": "dual_coach_pilot_test_bot"
        }
      }
    }
  },
  "discord": {
    "enabled": false
  },
  "whatsapp": {
    "enabled": false
  },
  "whatsapp_cloud": {
    "enabled": false
  },
  "slack": {
    "enabled": false
  },
  "signal": {
    "enabled": false
  },
  "mattermost": {
    "enabled": false
  },
  "matrix": {
    "enabled": false
  },
  "homeassistant": {
    "enabled": false
  },
  "email": {
    "enabled": false
  },
  "sms": {
    "enabled": false
  },
  "dingtalk": {
    "enabled": false
  },
  "api_server": {
    "enabled": false
  },
  "webhook": {
    "enabled": false
  },
  "msgraph_webhook": {
    "enabled": false
  },
  "feishu": {
    "enabled": false
  },
  "wecom": {
    "enabled": false
  },
  "wecom_callback": {
    "enabled": false
  },
  "weixin": {
    "enabled": false
  },
  "bluebubbles": {
    "enabled": false
  },
  "qqbot": {
    "enabled": false
  },
  "yuanbao": {
    "enabled": false
  },
  "relay": {
    "enabled": false
  }
}
--- registry selected ---
{
  "version": 1,
  "registry_mode": "ordinary_v1",
  "diagnostic_session_digest": null,
  "owner": {
    "user_id": "8693203710",
    "chat_id": "8693203710",
    "topic_id": "0"
  },
  "customers": [
    {
      "customer_key": "task26_live_2e_r2_20260815_8527916639",
      "display_name": "Task26 Live Synthetic Customer",
      "enabled": true,
      "telegram": {
        "user_id": "8527916639",
        "chat_id": "8527916639",
        "topic_id": "0"
      },
      "schedule": {
        "daily_time": "08:00:00",
        "weekly_weekday": 0,
        "monthly_day": 1
      },
      "profile": {
        "primary_goal": "maintain",
        "starting_context": "nutrition-onboarding-v1",
        "dietary_restrictions": [],
        "allergies": [],
        "food_preferences": [],
        "disliked_foods": [],
        "supplements": [],
        "cooking_access": "기본 조리 가능",
        "budget_band": "보통",
        "meal_count": 3,
        "schedule_constraints": "없음",
        "digestion_context": null,
        "sleep_goal_hours": null,
        "recovery_goal": null,
        "training_context": "현재도 주 3회, 회당 60분 정도의 중간 강도 근력운동을 꾸준히 하고 있으며, 앞으로도 같은 수준으로 유지할 계획입니다. 평소 하루 6,000~8,000보 정도 걷습 니다.",
        "coach_notes": null
      },
      "ai_processing_consent": {
        "granted": true,
        "recorded_on": "2026-08-16",
        "notice_version": "privacy-v1"
      },
      "plan": {
        "starts_on": "2026-08-16",
        "focus": "nutrition_90_training_10",
        "weeks": [
          {
            "week": 1,
            "calories_kcal": 2760,
            "protein_g": 160,
            "meal_structure": [
              "식사 1",
              "식사 2",
              "식사 3"
            ],
            "carbohydrate_g": 350,
            "fat_g": 80,
            "water_liters": null,
            "nutrition_focus": "승인된 열량·매크로 범위 준수",
            "recovery_focus": null
          },
          {
            "week": 2,
            "calories_kcal": 2760,
            "protein_g": 160,
            "meal_structure": [
              "식사 1",
              "식사 2",
              "식사 3"
            ],
            "carbohydrate_g": 350,
            "fat_g": 80,
            "water_liters": null,
            "nutrition_focus": "승인된 열량·매크로 범위 준수",
            "recovery_focus": null
          },
          {
            "week": 3,
            "calories_kcal": 2760,
            "protein_g": 160,
            "meal_structure": [
              "식사 1",
              "식사 2",
              "식사 3"
            ],
            "carbohydrate_g": 350,
            "fat_g": 80,
            "water_liters": null,
            "nutrition_focus": "승인된 열량·매크로 범위 준수",
            "recovery_focus": null
          },
          {
            "week": 4,
            "calories_kcal": 2760,
            "protein_g": 160,
            "meal_structure": [
              "식사 1",
              "식사 2",
              "식사 3"
            ],
            "carbohydrate_g": 350,
            "fat_g": 80,
            "water_liters": null,
            "nutrition_focus": "승인된 열량·매크로 범위 준수",
            "recovery_focus": null
          },
          {
            "week": 5,
            "calories_kcal": 2760,
            "protein_g": 160,
            "meal_structure": [
              "식사 1",
              "식사 2",
              "식사 3"
            ],
            "carbohydrate_g": 350,
            "fat_g": 80,
            "water_liters": null,
            "nutrition_focus": "승인된 열량·매크로 범위 준수",
            "recovery_focus": null
          },
          {
            "week": 6,
            "calories_kcal": 2760,
            "protein_g": 160,
            "meal_structure": [
              "식사 1",
              "식사 2",
              "식사 3"
            ],
            "carbohydrate_g": 350,
            "fat_g": 80,
            "water_liters": null,
            "nutrition_focus": "승인된 열량·매크로 범위 준수",
            "recovery_focus": null
          },
          {
            "week": 7,
            "calories_kcal": 2760,
            "protein_g": 160,
            "meal_structure": [
              "식사 1",
              "식사 2",
              "식사 3"
            ],
            "carbohydrate_g": 350,
            "fat_g": 80,
            "water_liters": null,
            "nutrition_focus": "승인된 열량·매크로 범위 준수",
            "recovery_focus": null
          },
          {
            "week": 8,
            "calories_kcal": 2760,
            "protein_g": 160,
            "meal_structure": [
              "식사 1",
              "식사 2",
              "식사 3"
            ],
            "carbohydrate_g": 350,
            "fat_g": 80,
            "water_liters": null,
            "nutrition_focus": "승인된 열량·매크로 범위 준수",
            "recovery_focus": null
          },
          {
            "week": 9,
            "calories_kcal": 2760,
            "protein_g": 160,
            "meal_structure": [
              "식사 1",
              "식사 2",
              "식사 3"
            ],
            "carbohydrate_g": 350,
            "fat_g": 80,
            "water_liters": null,
            "nutrition_focus": "승인된 열량·매크로 범위 준수",
            "recovery_focus": null
          },
          {
            "week": 10,
            "calories_kcal": 2760,
            "protein_g": 160,
            "meal_structure": [
              "식사 1",
              "식사 2",
              "식사 3"
            ],
            "carbohydrate_g": 350,
            "fat_g": 80,
            "water_liters": null,
            "nutrition_focus": "승인된 열량·매크로 범위 준수",
            "recovery_focus": null
          },
          {
            "week": 11,
            "calories_kcal": 2760,
            "protein_g": 160,
            "meal_structure": [
              "식사 1",
              "식사 2",
              "식사 3"
            ],
            "carbohydrate_g": 350,
            "fat_g": 80,
            "water_liters": null,
            "nutrition_focus": "승인된 열량·매크로 범위 준수",
            "recovery_focus": null
          },
          {
            "week": 12,
            "calories_kcal": 2760,
            "protein_g": 160,
            "meal_structure": [
              "식사 1",
              "식사 2",
              "식사 3"
            ],
            "carbohydrate_g": 350,
            "fat_g": 80,
            "water_liters": null,
            "nutrition_focus": "승인된 열량·매크로 범위 준수",
            "recovery_focus": null
          }
        ]
      }
    }
  ]
}
--- service state ---
## data/owner-actions/customer-service-state.json
MISSING
## data/telegram-ingress-receipts-v1-d0aacf0f4bdbb7c0.json
{
  "receipts": {},
  "terminal_failures": {
    "629525116": {
      "reason_code": "handler_exception",
      "stage": "blocked_receipt"
    }
  },
  "terminal_receipts": {
    "629525115": {
      "provenance_digest": "27e486b3ecfa1ac379f56472c0087a6188a5b2c43568dc81f61ad37b7da7a8a2",
      "reason_code": "business_commit_reconciled",
      "stage": "recovery_receipt"
    }
  },
  "version": 3
}


--- gateway log incident ---
logs/agent.log:10036:2026-08-06 12:58:37,530 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_collection: nutrition_onboarding_ingress update_id=629524956 actor_id=8527916639 chat_id=-1004325423947 topic_id=9 reply_to_message_id=52 publication_message_id=52
logs/agent.log:10037:2026-08-06 12:58:41,279 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_collection: nutrition_onboarding_ingress update_id=629524957 actor_id=8527916639 chat_id=-1004325423947 topic_id=9 reply_to_message_id=54 publication_message_id=54
logs/agent.log:10038:2026-08-06 12:58:45,034 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_collection: nutrition_onboarding_ingress update_id=629524958 actor_id=8527916639 chat_id=-1004325423947 topic_id=9 reply_to_message_id=56 publication_message_id=56
logs/agent.log:10039:2026-08-06 12:58:48,159 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_collection: nutrition_onboarding_ingress update_id=629524959 actor_id=8527916639 chat_id=-1004325423947 topic_id=9 reply_to_message_id=58 publication_message_id=58
logs/agent.log:10040:2026-08-06 12:58:51,131 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_collection: nutrition_onboarding_ingress update_id=629524960 actor_id=8527916639 chat_id=-1004325423947 topic_id=9 reply_to_message_id=60 publication_message_id=60
logs/agent.log:10041:2026-08-06 12:58:54,068 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_collection: nutrition_onboarding_ingress update_id=629524961 actor_id=8527916639 chat_id=-1004325423947 topic_id=9 reply_to_message_id=62 publication_message_id=62
logs/agent.log:10042:2026-08-06 12:58:57,079 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_collection: nutrition_onboarding_ingress update_id=629524962 actor_id=8527916639 chat_id=-1004325423947 topic_id=9 reply_to_message_id=64 publication_message_id=64
logs/agent.log:10043:2026-08-06 12:59:01,001 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_collection: nutrition_onboarding_ingress update_id=629524963 actor_id=8527916639 chat_id=-1004325423947 topic_id=9 reply_to_message_id=66 publication_message_id=66
logs/agent.log:10044:2026-08-06 12:59:05,343 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_collection: nutrition_onboarding_ingress update_id=629524964 actor_id=8527916639 chat_id=-1004325423947 topic_id=9 reply_to_message_id=68 publication_message_id=68
logs/agent.log:10045:2026-08-06 12:59:08,485 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_collection: nutrition_onboarding_ingress update_id=629524965 actor_id=8527916639 chat_id=-1004325423947 topic_id=9 reply_to_message_id=70 publication_message_id=70
logs/agent.log:10046:2026-08-06 12:59:13,488 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_collection: nutrition_onboarding_ingress update_id=629524966 actor_id=8527916639 chat_id=-1004325423947 topic_id=9 reply_to_message_id=72 publication_message_id=72
logs/agent.log:10047:2026-08-06 12:59:19,749 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_collection: nutrition_onboarding_ingress update_id=629524967 actor_id=8527916639 chat_id=-1004325423947 topic_id=9 reply_to_message_id=74 publication_message_id=74
logs/agent.log:10048:2026-08-06 12:59:24,729 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_collection: nutrition_onboarding_ingress update_id=629524968 actor_id=8527916639 chat_id=-1004325423947 topic_id=9 reply_to_message_id=76 publication_message_id=76
logs/agent.log:10049:2026-08-06 12:59:29,141 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_collection: nutrition_onboarding_ingress update_id=629524969 actor_id=8527916639 chat_id=-1004325423947 topic_id=9 reply_to_message_id=78 publication_message_id=78
logs/agent.log-10050-2026-08-06 12:59:30,118 WARNING gateway.platforms.telegram_nutrition_onboarding_runtime_publication: Nutrition onboarding advisory summary unavailable: ReconciliationUnavailable
logs/agent.log-10051-2026-08-06 13:04:25,159 INFO gateway.run: Received SIGTERM — initiating shutdown
logs/agent.log-10052-2026-08-06 13:04:25,159 WARNING gateway.run: Shutdown context: signal=SIGTERM under_systemd=no parent_pid=4171522 parent_name=senpi loadavg_1m=0.21 parent_cmdline='senpi'
logs/agent.log-10053-2026-08-06 13:04:25,160 INFO gateway.run: Stopping gateway...
logs/agent.log-10054-2026-08-06 13:04:25,160 INFO gateway.run: Shutdown phase: notify_active_sessions done at +0.00s
logs/agent.log-10055-2026-08-06 13:04:25,162 INFO gateway.run: Shutdown phase: drain done at +0.00s (drain took 0.00s, timed_out=False, active_at_start=0, active_now=0)
logs/agent.log-10056-2026-08-06 13:04:25,986 INFO telegram.ext.Application: Application is stopping. This might take a moment.
logs/agent.log-10057-2026-08-06 13:04:25,987 INFO telegram.ext.Application: Application.stop() complete
logs/agent.log-10058-2026-08-06 13:04:25,989 INFO gateway.platforms.telegram: [Telegram] Disconnected from Telegram
logs/agent.log-10059-2026-08-06 13:04:25,989 INFO gateway.run: ✓ telegram disconnected (0.83s)
logs/agent.log-10060-2026-08-06 13:04:25,989 INFO gateway.run: Shutdown phase: all adapters disconnected at +0.83s
logs/agent.log-10061-2026-08-06 13:04:26,008 INFO gateway.run: Shutdown phase: final-cleanup tool kill done at +0.85s
--
logs/agent.log-10096-2026-08-06 13:04:37,581 INFO gateway.platforms.telegram: [Telegram] set_my_commands OK for scope BotCommandScopeDefault (30 cmds)
logs/agent.log-10097-2026-08-06 13:04:38,009 INFO gateway.platforms.telegram: [Telegram] set_my_commands OK for scope BotCommandScopeAllPrivateChats (30 cmds)
logs/agent.log-10098-2026-08-06 13:04:38,438 INFO gateway.platforms.telegram: [Telegram] set_my_commands OK for scope BotCommandScopeAllGroupChats (30 cmds)
logs/agent.log-10099-2026-08-06 13:04:38,438 INFO gateway.platforms.telegram: [Telegram] Telegram menu: 30 commands registered, 22 hidden (over 30 limit). Use /commands for full list.
logs/agent.log-10100-2026-08-06 13:04:38,440 INFO gateway.platforms.telegram: [Telegram] Connected to Telegram (polling mode)
logs/agent.log-10101-2026-08-06 13:04:38,442 INFO gateway.run: ✓ telegram connected
logs/agent.log-10102-2026-08-06 13:04:38,442 INFO gateway.run: Gateway running with 1 platform(s)
logs/agent.log-10103-2026-08-06 13:04:38,444 INFO gateway.run: Channel directory built: 2 target(s)
logs/agent.log-10104-2026-08-06 13:04:39,446 INFO gateway.run: Press Ctrl+C to stop
logs/agent.log-10105-2026-08-06 13:04:39,457 INFO cron.scheduler_provider: In-process cron scheduler started (interval=60s)
logs/agent.log-10106-2026-08-06 13:04:39,459 INFO gateway.run: Gateway housekeeping started (interval=60s)
logs/agent.log-10107-2026-08-06 13:04:39,460 INFO gateway.run: kanban dispatcher: another gateway already holds the dispatcher lock (/home/cube/.hermes/kanban/.dispatcher.lock); this gateway will NOT dispatch.
logs/agent.log:10108:2026-08-06 13:10:58,427 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_ingress action=attest actor_id=8527916639 token_generation=29 publication_generation=29 message_id=80 publication_message_id=80
logs/agent.log-10109-2026-08-06 13:14:32,915 INFO gateway.run: Received SIGTERM — initiating shutdown
logs/agent.log-10110-2026-08-06 13:14:32,916 WARNING gateway.run: Shutdown context: signal=SIGTERM under_systemd=no parent_pid=4171522 parent_name=senpi loadavg_1m=0.37 parent_cmdline='senpi'
logs/agent.log-10111-2026-08-06 13:14:32,916 INFO gateway.run: Stopping gateway...
logs/agent.log-10112-2026-08-06 13:14:32,916 INFO gateway.run: Shutdown phase: notify_active_sessions done at +0.00s
logs/agent.log-10113-2026-08-06 13:14:32,918 INFO gateway.run: Shutdown phase: drain done at +0.00s (drain took 0.00s, timed_out=False, active_at_start=0, active_now=0)
logs/agent.log-10114-2026-08-06 13:14:33,747 INFO telegram.ext.Application: Application is stopping. This might take a moment.
logs/agent.log-10115-2026-08-06 13:14:33,747 INFO telegram.ext.Application: Application.stop() complete
logs/agent.log-10116-2026-08-06 13:14:33,749 INFO gateway.platforms.telegram: [Telegram] Disconnected from Telegram
logs/agent.log-10117-2026-08-06 13:14:33,749 INFO gateway.run: ✓ telegram disconnected (0.83s)
logs/agent.log-10118-2026-08-06 13:14:33,749 INFO gateway.run: Shutdown phase: all adapters disconnected at +0.83s
logs/agent.log-10119-2026-08-06 13:14:33,770 INFO gateway.run: Shutdown phase: final-cleanup tool kill done at +0.85s
logs/agent.log-10120-2026-08-06 13:14:33,770 INFO gateway.run: Shutdown phase: SessionDB close done at +0.85s
--
logs/agent.log-10154-2026-08-06 13:14:42,930 INFO gateway.platforms.telegram: [Telegram] set_my_commands OK for scope BotCommandScopeDefault (30 cmds)
logs/agent.log-10155-2026-08-06 13:14:43,368 INFO gateway.platforms.telegram: [Telegram] set_my_commands OK for scope BotCommandScopeAllPrivateChats (30 cmds)
logs/agent.log-10156-2026-08-06 13:14:43,805 INFO gateway.platforms.telegram: [Telegram] set_my_commands OK for scope BotCommandScopeAllGroupChats (30 cmds)
logs/agent.log-10157-2026-08-06 13:14:43,805 INFO gateway.platforms.telegram: [Telegram] Telegram menu: 30 commands registered, 22 hidden (over 30 limit). Use /commands for full list.
logs/agent.log-10158-2026-08-06 13:14:43,806 INFO gateway.platforms.telegram: [Telegram] Connected to Telegram (polling mode)
logs/agent.log-10159-2026-08-06 13:14:43,808 INFO gateway.run: ✓ telegram connected
logs/agent.log-10160-2026-08-06 13:14:43,809 INFO gateway.run: Gateway running with 1 platform(s)
logs/agent.log-10161-2026-08-06 13:14:43,810 INFO gateway.run: Channel directory built: 2 target(s)
logs/agent.log-10162-2026-08-06 13:14:44,812 INFO gateway.run: Press Ctrl+C to stop
logs/agent.log-10163-2026-08-06 13:14:44,823 INFO cron.scheduler_provider: In-process cron scheduler started (interval=60s)
logs/agent.log-10164-2026-08-06 13:14:44,825 INFO gateway.run: Gateway housekeeping started (interval=60s)
logs/agent.log-10165-2026-08-06 13:14:44,826 INFO gateway.run: kanban dispatcher: another gateway already holds the dispatcher lock (/home/cube/.hermes/kanban/.dispatcher.lock); this gateway will NOT dispatch.
logs/agent.log:10166:2026-08-06 13:19:34,067 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_ingress action=attest actor_id=8527916639 token_generation=29 publication_generation=29 message_id=80 publication_message_id=80
logs/agent.log-10167-2026-08-06 13:19:34,067 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_stage stage=member_check_start action=attest
logs/agent.log-10168-2026-08-06 13:19:34,867 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_stage stage=member_check_pass action=attest
logs/agent.log-10169-2026-08-06 13:19:34,870 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_stage stage=authority_pass action=attest
logs/agent.log-10170-2026-08-06 13:19:34,870 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_stage stage=route_pass action=attest
logs/agent.log-10171-2026-08-06 13:30:15,720 INFO gateway.run: Received SIGTERM — initiating shutdown
logs/agent.log-10172-2026-08-06 13:30:15,720 WARNING gateway.run: Shutdown context: signal=SIGTERM under_systemd=no parent_pid=4171522 parent_name=senpi loadavg_1m=0.37 parent_cmdline='senpi'
logs/agent.log-10173-2026-08-06 13:30:15,721 INFO gateway.run: Stopping gateway...
logs/agent.log-10174-2026-08-06 13:30:15,721 INFO gateway.run: Shutdown phase: notify_active_sessions done at +0.00s
logs/agent.log-10175-2026-08-06 13:30:15,722 INFO gateway.run: Shutdown phase: drain done at +0.00s (drain took 0.00s, timed_out=False, active_at_start=0, active_now=0)
logs/agent.log-10176-2026-08-06 13:30:16,508 INFO telegram.ext.Application: Application is stopping. This might take a moment.
logs/agent.log-10177-2026-08-06 13:30:16,509 INFO telegram.ext.Application: Application.stop() complete
logs/agent.log-10178-2026-08-06 13:30:16,511 INFO gateway.platforms.telegram: [Telegram] Disconnected from Telegram
--
logs/agent.log-10216-2026-08-06 13:30:26,989 INFO gateway.platforms.telegram: [Telegram] set_my_commands OK for scope BotCommandScopeDefault (30 cmds)
logs/agent.log-10217-2026-08-06 13:30:27,423 INFO gateway.platforms.telegram: [Telegram] set_my_commands OK for scope BotCommandScopeAllPrivateChats (30 cmds)
logs/agent.log-10218-2026-08-06 13:30:27,856 INFO gateway.platforms.telegram: [Telegram] set_my_commands OK for scope BotCommandScopeAllGroupChats (30 cmds)
logs/agent.log-10219-2026-08-06 13:30:27,856 INFO gateway.platforms.telegram: [Telegram] Telegram menu: 30 commands registered, 22 hidden (over 30 limit). Use /commands for full list.
logs/agent.log-10220-2026-08-06 13:30:27,857 INFO gateway.platforms.telegram: [Telegram] Connected to Telegram (polling mode)
logs/agent.log-10221-2026-08-06 13:30:27,859 INFO gateway.run: ✓ telegram connected
logs/agent.log-10222-2026-08-06 13:30:27,860 INFO gateway.run: Gateway running with 1 platform(s)
logs/agent.log-10223-2026-08-06 13:30:27,861 INFO gateway.run: Channel directory built: 2 target(s)
logs/agent.log-10224-2026-08-06 13:30:28,863 INFO gateway.run: Press Ctrl+C to stop
logs/agent.log-10225-2026-08-06 13:30:28,874 INFO cron.scheduler_provider: In-process cron scheduler started (interval=60s)
logs/agent.log-10226-2026-08-06 13:30:28,876 INFO gateway.run: Gateway housekeeping started (interval=60s)
logs/agent.log-10227-2026-08-06 13:30:28,878 INFO gateway.run: kanban dispatcher: another gateway already holds the dispatcher lock (/home/cube/.hermes/kanban/.dispatcher.lock); this gateway will NOT dispatch.
logs/agent.log:10228:2026-08-06 13:31:29,029 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_ingress action=attest actor_id=8527916639 token_generation=29 publication_generation=29 message_id=80 publication_message_id=80
logs/agent.log-10229-2026-08-06 13:31:29,029 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_stage stage=member_check_start action=attest
logs/agent.log-10230-2026-08-06 13:31:29,867 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_stage stage=member_check_pass action=attest
logs/agent.log-10231-2026-08-06 13:31:29,870 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_stage stage=authority_pass action=attest
logs/agent.log-10232-2026-08-06 13:31:29,870 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_stage stage=route_pass action=attest
logs/agent.log-10233-2026-08-06 13:31:29,875 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_stage stage=transition_pass action=attest state=trainer_review
logs/agent.log-10234-2026-08-06 13:31:29,875 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_applied action=attest state=trainer_review
logs/agent.log-10235-2026-08-06 13:35:37,403 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_ingress action=train_ok actor_id=8693203710 token_generation=30 publication_generation=30 message_id=82 publication_message_id=82
logs/agent.log-10236-2026-08-06 13:35:37,403 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_stage stage=member_check_start action=train_ok
logs/agent.log-10237-2026-08-06 13:35:38,394 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_stage stage=member_check_pass action=train_ok
logs/agent.log-10238-2026-08-06 13:35:38,397 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_stage stage=authority_pass action=train_ok
logs/agent.log-10239-2026-08-06 13:37:28,801 INFO gateway.run: Received SIGTERM — initiating shutdown
logs/agent.log-10240-2026-08-06 13:37:28,801 WARNING gateway.run: Shutdown context: signal=SIGTERM under_systemd=no parent_pid=4171522 parent_name=senpi loadavg_1m=0.39 parent_cmdline='senpi'
--
logs/agent.log-12170-2026-08-10 23:29:10,916 INFO cron.scheduler_provider: In-process cron scheduler started (interval=60s)
logs/agent.log-12171-2026-08-10 23:29:10,918 INFO gateway.run: Gateway housekeeping started (interval=60s)
logs/agent.log-12172-2026-08-10 23:29:10,919 INFO gateway.run: kanban dispatcher: holding singleton dispatcher lock (/home/cube/.hermes/kanban/.dispatcher.lock)
logs/agent.log-12173-2026-08-10 23:29:15,921 INFO gateway.run: kanban dispatcher: embedded in gateway (interval=60.0s)
logs/agent.log-12174-2026-08-10 23:30:37,257 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_ingress stage=ingress update_id=629525050
logs/agent.log-12175-2026-08-10 23:30:38,212 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_stage stage=member_check_start
logs/agent.log-12176-2026-08-10 23:30:38,475 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_stage stage=member_check_pass
logs/agent.log-12177-2026-08-10 23:30:38,477 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_ingress stage=authority update_id=629525050
logs/agent.log-12178-2026-08-10 23:30:38,477 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_stage stage=authority_pass
logs/agent.log-12179-2026-08-10 23:30:38,477 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_stage stage=route_pass
logs/agent.log-12180-2026-08-10 23:30:38,478 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_ingress stage=route update_id=629525050
logs/agent.log-12181-2026-08-10 23:30:38,478 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_ingress stage=validation update_id=629525050
logs/agent.log:12182:2026-08-10 23:30:38,480 INFO gateway.platforms.telegram_polling_receipts: telegram_ingress stage=receipt update_id=629525050 reason_code=handler_exception
logs/agent.log:12183:2026-08-10 23:30:38,480 INFO gateway.platforms.telegram: nutrition_ingress stage=receipt update_id=629525050 reason_code=handler_exception
logs/agent.log:12184:2026-08-10 23:30:38,480 ERROR gateway.platforms.telegram: telegram_ingress stage=receipt update_id=629525050 reason_code=handler_exception
logs/agent.log-12185-2026-08-10 23:36:31,365 INFO gateway.run: Received SIGTERM — initiating shutdown
logs/agent.log-12186-2026-08-10 23:36:31,365 WARNING gateway.run: Shutdown context: signal=SIGTERM under_systemd=yes parent_pid=2585 parent_name=systemd loadavg_1m=1.29 parent_cmdline='/usr/lib/systemd/systemd --user'
logs/agent.log-12187-2026-08-10 23:36:31,366 INFO gateway.run: Stopping gateway...
logs/agent.log-12188-2026-08-10 23:36:31,366 INFO gateway.run: Shutdown phase: notify_active_sessions done at +0.00s
logs/agent.log-12189-2026-08-10 23:36:31,368 INFO gateway.run: Shutdown phase: drain done at +0.00s (drain took 0.00s, timed_out=False, active_at_start=0, active_now=0)
logs/agent.log-12190-2026-08-10 23:36:31,368 INFO telegram.ext.Application: Application is stopping. This might take a moment.
logs/agent.log-12191-2026-08-10 23:36:31,368 INFO telegram.ext.Application: Application.stop() complete
logs/agent.log-12192-2026-08-10 23:36:31,369 INFO gateway.platforms.telegram: [Telegram] Disconnected from Telegram
logs/agent.log-12193-2026-08-10 23:36:31,369 INFO gateway.run: ✓ telegram disconnected (0.00s)
logs/agent.log-12194-2026-08-10 23:36:31,369 INFO gateway.run: Shutdown phase: all adapters disconnected at +0.00s
logs/agent.log-12195-2026-08-10 23:36:31,388 INFO gateway.run: Shutdown phase: final-cleanup tool kill done at +0.02s
logs/agent.log-12196-2026-08-10 23:36:31,388 INFO gateway.run: Shutdown phase: SessionDB close done at +0.02s
--
logs/agent.log-13058-2026-08-12 17:22:11,256 INFO gateway.run: Press Ctrl+C to stop
logs/agent.log-13059-2026-08-12 17:22:11,268 INFO cron.scheduler_provider: In-process cron scheduler started (interval=60s)
logs/agent.log-13060-2026-08-12 17:22:11,269 INFO gateway.run: Gateway housekeeping started (interval=60s)
logs/agent.log-13061-2026-08-12 17:22:11,271 INFO gateway.run: kanban dispatcher: holding singleton dispatcher lock (/home/cube/.hermes/kanban/.dispatcher.lock)
logs/agent.log-13062-2026-08-12 17:22:16,273 INFO gateway.run: kanban dispatcher: embedded in gateway (interval=60.0s)
logs/agent.log-13063-2026-08-12 17:46:02,685 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_ingress stage=ingress update_id=629525051
logs/agent.log-13064-2026-08-12 17:46:03,682 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_stage stage=member_check_start
logs/agent.log-13065-2026-08-12 17:46:03,922 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_stage stage=member_check_pass
logs/agent.log-13066-2026-08-12 17:46:03,923 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_ingress stage=authority update_id=629525051
logs/agent.log-13067-2026-08-12 17:46:03,923 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_stage stage=authority_pass
logs/agent.log-13068-2026-08-12 17:46:03,923 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_onboarding_callback_stage stage=route_pass
logs/agent.log-13069-2026-08-12 17:46:03,923 INFO gateway.platforms.telegram_nutrition_onboarding_runtime_callback: nutrition_ingress stage=route update_id=629525051
logs/agent.log:13070:2026-08-12 17:46:03,944 INFO gateway.platforms.telegram_polling_receipts: telegram_ingress stage=receipt update_id=629525051 reason_code=handler_exception
logs/agent.log:13071:2026-08-12 17:46:03,944 INFO gateway.platforms.telegram: nutrition_ingress stage=receipt update_id=629525051 reason_code=handler_exception
logs/agent.log:13072:2026-08-12 17:46:03,944 ERROR gateway.platforms.telegram: telegram_ingress stage=receipt update_id=629525051 reason_code=handler_exception
logs/agent.log-13073-2026-08-12 18:34:07,829 INFO gateway.run: Received SIGTERM — initiating shutdown
logs/agent.log-13074-2026-08-12 18:34:07,830 WARNING gateway.run: Shutdown context: signal=SIGTERM under_systemd=yes parent_pid=2585 parent_name=systemd loadavg_1m=0.43 parent_cmdline='/usr/lib/systemd/systemd --user'
logs/agent.log-13075-2026-08-12 18:34:07,830 INFO gateway.run: Stopping gateway...
logs/agent.log-13076-2026-08-12 18:34:07,830 INFO gateway.run: Shutdown phase: notify_active_sessions done at +0.00s
logs/agent.log-13077-2026-08-12 18:34:07,832 INFO gateway.run: Shutdown phase: drain done at +0.00s (drain took 0.00s, timed_out=False, active_at_start=0, active_now=0)
logs/agent.log-13078-2026-08-12 18:34:07,832 INFO telegram.ext.Application: Application is stopping. This might take a moment.
logs/agent.log-13079-2026-08-12 18:34:07,832 INFO telegram.ext.Application: Application.stop() complete
logs/agent.log-13080-2026-08-12 18:34:07,833 INFO gateway.platforms.telegram: [Telegram] Disconnected from Telegram
logs/agent.log-13081-2026-08-12 18:34:07,833 INFO gateway.run: ✓ telegram disconnected (0.00s)
logs/agent.log-13082-2026-08-12 18:34:07,833 INFO gateway.run: Shutdown phase: all adapters disconnected at +0.00s
logs/agent.log-13083-2026-08-12 18:34:07,851 INFO gateway.run: Shutdown phase: final-cleanup tool kill done at +0.02s
logs/agent.log-13084-2026-08-12 18:34:07,851 INFO gateway.run: Shutdown phase: SessionDB close done at +0.02s
--
logs/agent.log-13526-2026-08-13 08:27:49,316 INFO gateway.platforms.telegram: [Telegram] set_my_commands OK for scope BotCommandScopeAllGroupChats (30 cmds)
logs/agent.log-13527-2026-08-13 08:27:49,316 INFO gateway.platforms.telegram: [Telegram] Telegram menu: 30 commands registered, 22 hidden (over 30 limit). Use /commands for full list.
logs/agent.log-13528-2026-08-13 08:27:49,317 INFO gateway.platforms.telegram: [Telegram] Connected to Telegram (polling mode)
logs/agent.log-13529-2026-08-13 08:27:49,319 INFO gateway.run: ✓ telegram connected
logs/agent.log-13530-2026-08-13 08:27:49,320 INFO gateway.run: Gateway running with 1 platform(s)
logs/agent.log-13531-2026-08-13 08:27:49,322 INFO gateway.run: Channel directory built: 2 target(s)
logs/agent.log-13532-2026-08-13 08:27:50,323 INFO gateway.run: Press Ctrl+C to stop
logs/agent.log-13533-2026-08-13 08:27:50,332 INFO cron.scheduler_provider: In-process cron scheduler started (interval=60s)
logs/agent.log-13534-2026-08-13 08:27:50,333 INFO gateway.run: Gateway housekeeping started (interval=60s)
logs/agent.log-13535-2026-08-13 08:27:50,334 INFO gateway.run: kanban dispatcher: holding singleton dispatcher lock (/home/cube/.hermes/kanban/.dispatcher.lock)
logs/agent.log-13536-2026-08-13 08:27:55,337 INFO gateway.run: kanban dispatcher: embedded in gateway (interval=60.0s)
logs/agent.log-13537-2026-08-13 08:40:19,905 INFO gateway.platforms.telegram_polling_receipts: telegram_ingress stage=receipt update_id=629525054
logs/agent.log:13538:2026-08-13 08:40:48,566 INFO gateway.platforms.telegram: nutrition_operator_route actual=('8527916639', '8527916639', '0') configured=('8693203710', '-1004484458766', '59') matched=False
logs/agent.log-13539-2026-08-13 08:40:49,599 INFO gateway.platforms.telegram_polling_receipts: telegram_ingress stage=receipt update_id=629525055
logs/agent.log:13540:2026-08-13 08:40:54,151 INFO gateway.platforms.telegram: nutrition_operator_route actual=('8527916639', '8527916639', '0') configured=('8693203710', '-1004484458766', '59') matched=False
logs/agent.log-13541-2026-08-13 08:40:54,612 INFO gateway.platforms.telegram_polling_receipts: telegram_ingress stage=receipt update_id=629525056
logs/agent.log:13542:2026-08-13 08:41:10,841 INFO gateway.platforms.telegram: nutrition_operator_route actual=('8527916639', '8527916639', '0') configured=('8693203710', '-1004484458766', '59') matched=False
logs/agent.log-13543-2026-08-13 08:41:11,826 INFO gateway.platforms.telegram_polling_receipts: telegram_ingress stage=receipt update_id=629525057
logs/agent.log:13544:2026-08-13 08:41:15,012 INFO gateway.platforms.telegram: nutrition_operator_route actual=('8527916639', '8527916639', '0') configured=('8693203710', '-1004484458766', '59') matched=False
logs/agent.log-13545-2026-08-13 08:41:15,468 INFO gateway.platforms.telegram_polling_receipts: telegram_ingress stage=receipt update_id=629525058
logs/agent.log:13546:2026-08-13 08:41:17,664 INFO gateway.platforms.telegram: nutrition_operator_route actual=('8527916639', '8527916639', '0') configured=('8693203710', '-1004484458766', '59') matched=False
logs/agent.log-13547-2026-08-13 08:41:18,119 INFO gateway.platforms.telegram_polling_receipts: telegram_ingress stage=receipt update_id=629525059
logs/agent.log:13548:2026-08-13 08:41:20,607 INFO gateway.platforms.telegram: nutrition_operator_route actual=('8527916639', '8527916639', '0') configured=('8693203710', '-1004484458766', '59') matched=False
logs/agent.log-13549-2026-08-13 08:41:21,073 INFO gateway.platforms.telegram_polling_receipts: telegram_ingress stage=receipt update_id=629525060
logs/agent.log:13550:2026-08-13 08:41:31,267 INFO gateway.platforms.telegram: nutrition_operator_route actual=('8527916639', '8527916639', '0') configured=('8693203710', '-1004484458766', '59') matched=False
logs/agent.log:13551:2026-08-13 08:41:31,271 INFO gateway.platforms.telegram_polling_receipts: telegram_ingress stage=receipt update_id=629525061 reason_code=handler_exception
logs/agent.log:13552:2026-08-13 08:41:31,271 INFO gateway.platforms.telegram: nutrition_ingress stage=receipt update_id=629525061 reason_code=handler_exception
logs/agent.log:13553:2026-08-13 08:41:31,271 ERROR gateway.platforms.telegram: telegram_ingress stage=receipt update_id=629525061 reason_code=handler_exception
logs/agent.log-13554-2026-08-13 11:43:26,382 INFO gateway.run: Received SIGTERM — initiating shutdown
logs/agent.log-13555-2026-08-13 11:43:26,382 WARNING gateway.run: Shutdown context: signal=SIGTERM under_systemd=yes parent_pid=2585 parent_name=systemd loadavg_1m=0.08 parent_cmdline='/usr/lib/systemd/systemd --user'
logs/agent.log-13556-2026-08-13 11:43:26,383 INFO gateway.run: Stopping gateway...
logs/agent.log-13557-2026-08-13 11:43:26,383 INFO gateway.run: Shutdown phase: notify_active_sessions done at +0.00s
logs/agent.log-13558-2026-08-13 11:43:26,385 INFO gateway.run: Shutdown phase: drain done at +0.00s (drain took 0.00s, timed_out=False, active_at_start=0, active_now=0)
logs/agent.log-13559-2026-08-13 11:43:26,385 INFO telegram.ext.Application: Application is stopping. This might take a moment.
logs/agent.log-13560-2026-08-13 11:43:26,385 INFO telegram.ext.Application: Application.stop() complete
logs/agent.log-13561-2026-08-13 11:43:26,385 INFO gateway.platforms.telegram: [Telegram] Disconnected from Telegram
logs/agent.log-13562-2026-08-13 11:43:26,385 INFO gateway.run: ✓ telegram disconnected (0.00s)
logs/agent.log-13563-2026-08-13 11:43:26,386 INFO gateway.run: Shutdown phase: all adapters disconnected at +0.00s
logs/agent.log-13564-2026-08-13 11:43:26,404 INFO gateway.run: Shutdown phase: final-cleanup tool kill done at +0.02s
logs/agent.log-13565-2026-08-13 11:43:26,404 INFO gateway.run: Shutdown phase: SessionDB close done at +0.02s
--
logs/agent.log-13587-2026-08-13 11:43:27,015 INFO hermes_cli.plugins: Plugin discovery complete: 40 found, 33 enabled
logs/agent.log-13588-2026-08-13 11:43:27,313 INFO gateway.run: Starting Hermes Gateway...
logs/agent.log-13589-2026-08-13 11:43:27,314 INFO gateway.run: Session storage: /home/cube/.hermes/profiles/dualcoachtest/sessions
logs/agent.log-13590-2026-08-13 11:43:27,319 INFO gateway.run: Agent budget: max_iterations=90 (agent.max_turns from config.yaml, or HERMES_MAX_ITERATIONS from .env, or default 90)
logs/agent.log-13591-2026-08-13 11:43:27,320 INFO gateway.run: Secret redaction: ENABLED (tool output, logs, and chat responses are scrubbed before delivery)
logs/agent.log-13592-2026-08-13 11:43:27,320 INFO gateway.run: Active profile: dualcoachtest
logs/agent.log-13593-2026-08-13 11:43:27,324 INFO gateway.run: Previous gateway exited cleanly — skipping session suspension
logs/agent.log-13594-2026-08-13 11:43:27,485 INFO gateway.run: Connecting to telegram...
logs/agent.log-13595-2026-08-13 11:43:27,863 INFO gateway.platforms.telegram: [Telegram] Auto-discovered Telegram fallback IPs: 149.154.166.110
logs/agent.log-13596-2026-08-13 11:43:27,863 INFO gateway.platforms.telegram: [Telegram] Telegram fallback IPs active: 149.154.166.110
logs/agent.log-13597-2026-08-13 11:43:29,024 INFO telegram.ext.Application: Application started
logs/agent.log-13598-2026-08-13 11:43:30,060 INFO gateway.platforms.telegram: [Telegram] set_my_commands OK for scope BotCommandScopeDefault (30 cmds)
logs/agent.log:13599:2026-08-13 11:43:30,422 INFO gateway.platforms.telegram_polling_receipts: telegram_ingress stage=receipt update_id=629525061 reason_code=handler_exception
logs/agent.log:13600:2026-08-13 11:43:30,457 INFO gateway.platforms.telegram: nutrition_operator_route actual=('8527916639', '8527916639', '0') configured=('8693203710', '-1004484458766', '59') matched=False
logs/agent.log:13601:2026-08-13 11:43:30,464 ERROR gateway.platforms.telegram: telegram_ingress stage=receipt update_id=629525061 reason_code=handler_exception
logs/agent.log-13602-2026-08-13 11:43:30,513 INFO gateway.platforms.telegram: [Telegram] set_my_commands OK for scope BotCommandScopeAllPrivateChats (30 cmds)
logs/agent.log-13603-2026-08-13 11:43:30,946 INFO gateway.platforms.telegram: [Telegram] set_my_commands OK for scope BotCommandScopeAllGroupChats (30 cmds)
logs/agent.log-13604-2026-08-13 11:43:30,947 INFO gateway.platforms.telegram: [Telegram] Telegram menu: 30 commands registered, 22 hidden (over 30 limit). Use /commands for full list.
logs/agent.log-13605-2026-08-13 11:43:30,949 INFO gateway.platforms.telegram: [Telegram] Connected to Telegram (polling mode)
logs/agent.log-13606-2026-08-13 11:43:30,952 INFO gateway.run: ✓ telegram connected
logs/agent.log-13607-2026-08-13 11:43:30,953 INFO gateway.run: Gateway running with 1 platform(s)
logs/agent.log-13608-2026-08-13 11:43:30,955 INFO gateway.run: Channel directory built: 2 target(s)
logs/agent.log-13609-2026-08-13 11:43:31,795 INFO gateway.platforms.telegram_polling_receipts: telegram_ingress stage=receipt update_id=629525064
logs/agent.log-13610-2026-08-13 11:43:31,956 INFO gateway.run: Press Ctrl+C to stop
logs/agent.log-13611-2026-08-13 11:43:31,968 INFO cron.scheduler_provider: In-process cron scheduler started (interval=60s)
logs/agent.log-13612-2026-08-13 11:43:31,970 INFO gateway.run: Gateway housekeeping started (interval=60s)
logs/agent.log-13613-2026-08-13 11:43:31,970 INFO gateway.run: kanban dispatcher: holding singleton dispatcher lock (/home/cube/.hermes/kanban/.dispatcher.lock)
--
logs/agent.log-13722-2026-08-13 12:06:54,997 INFO gateway.platforms.telegram: [Telegram] Telegram menu: 30 commands registered, 22 hidden (over 30 limit). Use /commands for full list.
logs/agent.log-13723-2026-08-13 12:06:54,999 INFO gateway.platforms.telegram: [Telegram] Connected to Telegram (polling mode)
logs/agent.log-13724-2026-08-13 12:06:55,000 INFO gateway.run: ✓ telegram connected
logs/agent.log-13725-2026-08-13 12:06:55,002 INFO gateway.run: Gateway running with 1 platform(s)
logs/agent.log-13726-2026-08-13 12:06:55,003 INFO gateway.run: Channel directory built: 2 target(s)
logs/agent.log-13727-2026-08-13 12:06:56,005 INFO gateway.run: Press Ctrl+C to stop
logs/agent.log-13728-2026-08-13 12:06:56,016 INFO cron.scheduler_provider: In-process cron scheduler started (interval=60s)
logs/agent.log-13729-2026-08-13 12:06:56,017 INFO gateway.run: Gateway housekeeping started (interval=60s)
logs/agent.log-13730-2026-08-13 12:06:56,019 INFO gateway.run: kanban dispatcher: holding singleton dispatcher lock (/home/cube/.hermes/kanban/.dispatcher.lock)
logs/agent.log-13731-2026-08-13 12:07:01,021 INFO gateway.run: kanban dispatcher: embedded in gateway (interval=60.0s)
logs/agent.log-13732-2026-08-13 12:12:51,950 INFO gateway.platforms.telegram_polling_receipts: telegram_ingress stage=receipt update_id=629525067
logs/agent.log-13733-2026-08-13 12:13:04,539 INFO gateway.platforms.telegram_polling_receipts: telegram_ingress stage=receipt update_id=629525068
logs/agent.log:13734:2026-08-13 12:13:15,193 INFO gateway.platforms.telegram: nutrition_operator_route actual=('8527916639', '8527916639', '0') configured=('8693203710', '-1004484458766', '59') matched=False
logs/agent.log-13735-2026-08-13 12:13:16,181 INFO gateway.platforms.telegram_polling_receipts: telegram_ingress stage=receipt update_id=629525069
logs/agent.log:13736:2026-08-13 12:13:27,181 INFO gateway.platforms.telegram: nutrition_operator_route actual=('8527916639', '8527916639', '0') configured=('8693203710', '-1004484458766', '59') matched=False
logs/agent.log-13737-2026-08-13 12:13:28,160 INFO gateway.platforms.telegram_polling_receipts: telegram_ingress stage=receipt update_id=629525070
logs/agent.log-13738-2026-08-13 12:13:37,859 INFO gateway.platforms.telegram_polling_receipts: telegram_ingress stage=receipt update_id=629525071
logs/agent.log-13739-2026-08-13 12:13:42,544 ERROR gateway.platforms.telegram: nutrition operator card publication failed draft_id=90f6df222e870e58
logs/agent.log-13740-Traceback (most recent call last):
logs/agent.log-13741-  File "/home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py", line 6911, in _publish_nutrition_generation_card
logs/agent.log-13742-    message = await self._send_nutrition_topic(
logs/agent.log-13743-              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
logs/agent.log-13744-  File "/home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py", line 5913, in _send_nutrition_topic
logs/agent.log-13745-    return await self._send_message_strict_topic(
logs/agent.log-13746-           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
logs/agent.log-13747-  File "/home/cube/projects/richard/hermes-agent/gateway/platforms/telegram.py", line 3748, in _send_message_strict_topic
logs/agent.log-13748-    return await self._bot.send_message(**kwargs)
--
logs/agent.log-18232-2026-08-16 00:22:22,123 INFO hermes_cli.plugins: Plugin 'web-tavily' registered web provider: tavily
logs/agent.log-18233-2026-08-16 00:22:22,123 INFO hermes_cli.plugins: Plugin 'web-xai' registered web provider: xai
logs/agent.log-18234-2026-08-16 00:22:22,204 INFO hermes_cli.plugins: Plugin discovery complete: 40 found, 33 enabled
logs/agent.log-18235-2026-08-16 00:22:22,480 INFO gateway.run: Starting Hermes Gateway...
logs/agent.log-18236-2026-08-16 00:22:22,480 INFO gateway.run: Session storage: /home/cube/.hermes/profiles/dualcoachtest/sessions
logs/agent.log-18237-2026-08-16 00:22:22,486 INFO gateway.run: Agent budget: max_iterations=90 (agent.max_turns from config.yaml, or HERMES_MAX_ITERATIONS from .env, or default 90)
logs/agent.log-18238-2026-08-16 00:22:22,486 INFO gateway.run: Secret redaction: ENABLED (tool output, logs, and chat responses are scrubbed before delivery)
logs/agent.log-18239-2026-08-16 00:22:22,486 INFO gateway.run: Active profile: dualcoachtest
logs/agent.log-18240-2026-08-16 00:22:22,760 INFO gateway.run: Connecting to telegram...
logs/agent.log-18241-2026-08-16 00:22:23,121 INFO gateway.platforms.telegram: [Telegram] Auto-discovered Telegram fallback IPs: 149.154.166.110
logs/agent.log-18242-2026-08-16 00:22:23,121 INFO gateway.platforms.telegram: [Telegram] Telegram fallback IPs active: 149.154.166.110
logs/agent.log-18243-2026-08-16 00:22:23,963 INFO telegram.ext.Application: Application started
logs/agent.log:18244:2026-08-16 00:22:23,964 ERROR gateway.platforms.telegram: [Telegram] nutrition_coaching initialization failed: ValueError: configured registry path is outside the profile authority. Check registry_path=customers/registry.json, the profile workspace/checkin_cli package, and enabled customer consent/address records.
logs/agent.log-18245-2026-08-16 00:22:25,000 INFO gateway.platforms.telegram: [Telegram] set_my_commands OK for scope BotCommandScopeDefault (30 cmds)
logs/agent.log-18246-2026-08-16 00:22:25,445 INFO gateway.platforms.telegram: [Telegram] set_my_commands OK for scope BotCommandScopeAllPrivateChats (30 cmds)
logs/agent.log-18247-2026-08-16 00:22:25,890 INFO gateway.platforms.telegram: [Telegram] set_my_commands OK for scope BotCommandScopeAllGroupChats (30 cmds)
logs/agent.log-18248-2026-08-16 00:22:25,890 INFO gateway.platforms.telegram: [Telegram] Telegram menu: 30 commands registered, 22 hidden (over 30 limit). Use /commands for full list.
logs/agent.log-18249-2026-08-16 00:22:25,893 INFO gateway.platforms.telegram: [Telegram] Connected to Telegram (polling mode)
logs/agent.log-18250-2026-08-16 00:22:25,896 INFO gateway.run: ✓ telegram connected
logs/agent.log-18251-2026-08-16 00:22:25,897 INFO gateway.run: Gateway running with 1 platform(s)
logs/agent.log-18252-2026-08-16 00:22:25,898 INFO gateway.run: Channel directory built: 0 target(s)
logs/agent.log-18253-2026-08-16 00:22:26,900 INFO gateway.run: Press Ctrl+C to stop
logs/agent.log-18254-2026-08-16 00:22:26,919 INFO cron.scheduler_provider: In-process cron scheduler started (interval=60s)
logs/agent.log-18255-2026-08-16 00:22:26,921 INFO gateway.run: Gateway housekeeping started (interval=60s)
logs/agent.log-18256-2026-08-16 00:22:26,922 INFO gateway.run: kanban dispatcher: holding singleton dispatcher lock (/home/cube/.hermes/kanban/.dispatcher.lock)
--
logs/agent.log-19364-2026-08-17 01:41:23,996 INFO gateway.platforms.telegram: [Telegram] set_my_commands OK for scope BotCommandScopeAllPrivateChats (30 cmds)
logs/agent.log-19365-2026-08-17 01:41:24,426 INFO gateway.platforms.telegram: [Telegram] set_my_commands OK for scope BotCommandScopeAllGroupChats (30 cmds)
logs/agent.log-19366-2026-08-17 01:41:24,426 INFO gateway.platforms.telegram: [Telegram] Telegram menu: 30 commands registered, 22 hidden (over 30 limit). Use /commands for full list.
logs/agent.log-19367-2026-08-17 01:41:24,428 INFO gateway.platforms.telegram: [Telegram] Connected to Telegram (polling mode)
logs/agent.log-19368-2026-08-17 01:41:24,430 INFO gateway.run: ✓ telegram connected
logs/agent.log-19369-2026-08-17 01:41:24,432 INFO gateway.run: Gateway running with 1 platform(s)
logs/agent.log-19370-2026-08-17 01:41:24,432 INFO gateway.run: Channel directory built: 0 target(s)
logs/agent.log-19371-2026-08-17 01:41:25,434 INFO gateway.run: Press Ctrl+C to stop
logs/agent.log-19372-2026-08-17 01:41:25,445 INFO cron.scheduler_provider: In-process cron scheduler started (interval=60s)
logs/agent.log-19373-2026-08-17 01:41:25,447 INFO gateway.run: Gateway housekeeping started (interval=60s)
logs/agent.log-19374-2026-08-17 01:41:25,449 INFO gateway.run: kanban dispatcher: holding singleton dispatcher lock (/home/cube/.hermes/kanban/.dispatcher.lock)
logs/agent.log-19375-2026-08-17 01:41:30,450 INFO gateway.run: kanban dispatcher: embedded in gateway (interval=60.0s)
logs/agent.log:19376:2026-08-17 08:47:23,427 INFO gateway.platforms.telegram: nutrition_operator_route actual=('8527916639', '8527916639', '0') configured=('8693203710', '8693203710', '0') matched=False
logs/agent.log:19377:2026-08-17 08:47:23,433 INFO gateway.platforms.telegram_polling_receipts: telegram_ingress stage=receipt update_id=629525116 reason_code=handler_exception
logs/agent.log:19378:2026-08-17 08:47:23,433 INFO gateway.platforms.telegram: nutrition_ingress stage=receipt update_id=629525116 reason_code=handler_exception
logs/agent.log:19379:2026-08-17 08:47:23,433 ERROR gateway.platforms.telegram: telegram_ingress stage=receipt update_id=629525116 reason_code=handler_exception
--- metadata around incident ---
  File: customers/registry.json
  size: 6970      	Blocks: 16         IO Block: 4096   regular file
Device: 10302h/66306d	Inode: 31101937    Links: 1
Access: (0600/-rw-------)  Uid: ( 1000/    cube)   Gid: ( 1000/    cube)
Access: 2026-08-17 08:29:36.884814558 +0900
Modify: 2026-08-17 08:29:36.884150975 +0900
Change: 2026-08-17 08:29:36.884150975 +0900
 Birth: 2026-08-17 08:29:36.883661559 +0900
  File: data/telegram-ingress-receipts-v1-d0aacf0f4bdbb7c0.json
  size: 401       	Blocks: 8          IO Block: 4096   regular file
Device: 10302h/66306d	Inode: 30577509    Links: 1
Access: (0600/-rw-------)  Uid: ( 1000/    cube)   Gid: ( 1000/    cube)
Access: 2026-08-17 08:49:21.562432142 +0900
Modify: 2026-08-17 08:47:23.431535149 +0900
Change: 2026-08-17 08:47:23.431584048 +0900
 Birth: 2026-08-17 08:47:23.430584040 +0900
stat: cannot stat 'data/owner-actions/customer-service-state.json': No such file or directory (os error 2)

Command exited with code 1