# NousResearch/hermes-agent · New /compact alias and flags for /compress command

## Open now

- Nothing open on GitHub.

These members propose adding a /compact alias for the /compress command, along with preview, dry-run, and aggressive flags to enhance context compaction behavior. The work appears to include a salvaged prior pull request and a related feature request.

## TL;DR

- 3 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 1 PR(s) closed without merging.
- A fix was merged to main in PR #57029. Verify it addresses your case.
- **Recommended:** read PR #57029 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `resolved` · deciding PR(s): `pr-57029`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.67`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #3240: \[Feature\]: Enhanced Context Compaction Command (/compact)

## Prior & competing PRs

- **#57029: feat(commands): /compact alias + preview flags for /compress (#3243 salvage)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `cli.py`, `gateway/slash_commands.py`, `hermes_cli/commands.py`, `hermes_cli/partial_compress.py`, `locales/af.yaml`, `locales/de.yaml`, `locales/en.yaml`, `locales/es.yaml`, `locales/fr.yaml`, `locales/ga.yaml`, `locales/hu.yaml`, `locales/it.yaml`, `locales/ja.yaml`, `locales/ko.yaml`, `locales/pt.yaml`, `locales/ru.yaml`, `locales/tr.yaml`, `locales/uk.yaml`, `locales/zh-hant.yaml`, `locales/zh.yaml` (+3 more)
  - fix hunks:
  - `cli.py`

    ```diff
    @@ -9314,9 +9314,11 @@ def _manual_compress(self, cmd_original: str = ""):
                 return
     
             from hermes_cli.partial_compress import (
    +            extract_compress_flags,
                 parse_partial_compress_args,
                 rejoin_compressed_head_and_tail,
                 split_history_for_partial_compress,
    +            summarize_compress_preview,
             )
     
             # Args after the command word (e.g. "/compress here 3" -> "here 3").
    ```

    ```diff
    @@ -9326,9 +9328,42 @@ def _manual_compress(self, cmd_original: str = ""):
                 if len(_parts) > 1:
                     raw_args = _parts[1].strip()
     
    +        # Strip --preview/--dry-run/--aggressive before positional parsing
    +        # so the flags coexist with 'here [N]' / focus-topic forms.
    +        raw_args, preview, aggressive = extract_compress_flags(raw_args)
             partial, keep_last, focus_topic = parse_partial_compress_args(raw_args)
             focus_topic = focus_topic or ""
     
    +        if aggressive:
    +            # LLM-free hard truncation is not supported: it would need its
    +            # own transcript-persistence path outside the guarded
    +            # _compress_context rotation machinery. Surface that instead of
    +            # silently mis-parsing the flag as a focus topic.
    +            print("(._.) --aggressive is not supported; use '/compress here [N]' "
    +                  "to keep only recent exchanges, or /undo to drop turns.")
    +            if not preview:
    +                return
    +
    +        if preview:
    +            from agent.model_metadata import estimate_request_tokens_rough
    +            _sys_prompt = getattr(self.agent, "_cached_system_prompt", "") or ""
    +            _tools = getattr(self.agent, "tools", None) or None
    +            approx_tokens = estimate_request_tokens_rough(
    +                self.conversation_history,
    +                system_prompt=_sys_prompt,
    +                tools=_tools,
    +            )
    +            report = summarize_compress_preview(
    +                self.conversation_history,
    +                partial,
    +                keep_last,
    +                focus_topic or None,
    +                approx_tokens,
    +            )
    +            for line in report["lines"]:
    +                print(f"🗜️  {line}")
    +            return
    +
             original_count = len(self.conversation_history)
             with self._busy_command("Compressing context..."):
                 try:
    ```
  - `gateway/slash_commands.py`

    ```diff
    @@ -3043,13 +3043,44 @@ async def _handle_compress_command(self, event: MessageEvent) -> str:
             # Parse args: either a focus topic (full compress) or the
             # boundary-aware "here [N]" form (partial compress).
             from hermes_cli.partial_compress import (
    +            extract_compress_flags,
                 parse_partial_compress_args,
                 rejoin_compressed_head_and_tail,
                 split_history_for_partial_compress,
    +            summarize_compress_preview,
             )
             _raw_args = (event.get_command_args() or "").strip()
    +        # Strip --preview/--dry-run/--aggressive before positional parsing
    +        # so the flags coexist with 'here [N]' / focus-topic forms.
    +        _raw_args, _preview, _aggressive = extract_compress_flags(_raw_args)
             partial, keep_last, focus_topic = parse_partial_compress_args(_raw_args)
     
    +        _agg_note = ""
    +        if _aggressive:
    +            # LLM-free hard truncation is not supported on this surface —
    +            # it would need its own transcript-persistence branch outside
    +            # the guarded _compress_context rotation machinery (#44794).
    +            _agg_note = t("gateway.compress.aggressive_unsupported")
    +            if not _preview:
    +                return _agg_note
    +
    +        if _preview:
    +            # Report what WOULD be compressed — no agent, no writes.
    +            from agent.model_metadata import estimate_request_tokens_rough
    +            _pv_msgs = [
    +                {"role": m.get("role"), "content": m.get("content")}
    +                for m in history
    +                if m.get("role") in {"user", "assistant"} and m.get("content")
    +            ]
    +            approx_tokens = [redacted:secret-assignment]
    +            report = summarize_compress_preview(
    +                _pv_msgs, partial, keep_last, focus_topic, approx_tokens
    +            )
    +            lines = [f"🗜️ {line}" for line in report["lines"]]
    +            if _aggressive:
    +                lines.append(_agg_note)
    +            return "\n".join(lines)
    +
             try:
                 from run_agent import AIAgent
                 from agent.manual_compression_feedback import summarize_manual_compression
    ```
  - `hermes_cli/commands.py`

    ```diff
    @@ -88,8 +88,8 @@ class CommandDef:
                    args_hint="<platform>", cli_only=True),
         CommandDef("branch", "Branch the current session (explore a different path)", "Session",
                    aliases=("fork",), args_hint="[name]"),
    -    CommandDef("compress", "Compress conversation context (add 'here [N]' to keep recent N turns)", "Session",
    -               args_hint="[here [N] | focus topic]"),
    +    CommandDef("compress", "Compress conversation context (add 'here [N]' to keep recent N turns; --preview shows what would happen)", "Session",
    +               aliases=("compact",), args_hint="[here [N] | focus topic | --preview|--dry-run]"),
         CommandDef("rollback", "List or restore filesystem checkpoints", "Session",
                    args_hint="[number]"),
         CommandDef("snapshot", "Create or restore state snapshots of Hermes config/state", "Session",
    ```
  - `hermes_cli/partial_compress.py`

    ```diff
    @@ -108,6 +108,95 @@ def parse_partial_compress_args(
         return False, DEFAULT_KEEP_LAST, text or None
     
     
    +def extract_compress_flags(raw_args: str) -> Tuple[str, bool, bool]:
    +    """Strip ``--preview``/``--dry-run``/``--aggressive`` flags from the
    +    argument string after ``/compress`` (or its ``/compact`` alias).
    +
    +    Flags may appear anywhere and coexist with the positional forms
    +    (``here [N]``, ``--keep N``, or a focus topic); the returned
    +    remainder is what :func:`parse_partial_compress_args` should see.
    +
    +    Returns ``(remaining_args, preview, aggressive_requested)``:
    +
    +    * ``preview`` — True when ``--preview`` or ``--dry-run`` was given.
    +      The caller must report what WOULD be compressed (message counts,
    +      token estimate, boundary) and make **no changes**.
    +    * ``aggressive_requested`` — True when ``--aggressive`` was given.
    +      The current surfaces do not implement an LLM-free hard-truncate
    +      path (it would need its own transcript-persistence branch outside
    +      the guarded ``_compress_context`` rotation machinery), so callers
    +      surface a "not supported" note instead of silently treating the
    +      flag as a focus topic.
    +    """
    +    preview = False
    +    aggressive = False
    +    kept: List[str] = []
    +    for tok in (raw_args or "").split():
    +        low = tok.lower()
    +        if low in ("--preview", "--dry-run", "--dryrun"):
    +            preview = True
    +        elif low == "--aggressive":
    +            aggressive = True
    +        else:
    +            kept.append(tok)
    +    return " ".join(kept), preview, aggressive
    +
    +
    +def summarize_compress_preview(
    +    history: List[Dict[str, Any]],
    +    partial: bool,
    +    keep_last: int,
    +    focus_topic: Optional[str],
    +    approx_tokens: int,
    +) -> Dict[str, Any]:
    +    """Build the ``/compress --preview`` report — pure, no side effects.
    +
    +    Shared by the CLI (``cli.py::_manual_compress``) and the gateway
    +    (``gateway/slash_commands.py::_handle_compress_command``) so both
    +    surfaces report the same numbers the real run would use.
    +
    +    Returns a dict with ``head_count``/``tail_count``/``lines`` where
    +    ``lines`` is a ready-to-print list of report strings.
    +    """
    +    total = len(history)
    +    head = list(history)
    +    tail: List[Dict[str, Any]] = []
    +    effective_partial = partial
    +    if partial:
    +        head, tail = split_history_for_partial_compress(history, keep_last)
    +        if not tail:
    +            # Same degenerate-split fallback the real run applies.
    +            effective_partial = False
    +            head, tail = list(history), []
    +
    +    lines = [
    +        "Preview — no changes made.",
    +        f"Would compress {len(head)} of {total} message(s) "
    +        f"(~{approx_tokens:,} tokens currently in context).",
    +    ]
    +    if effective_partial:
    +        lines.append(
    +            f"Boundary: keeping the last {keep_last} exchange(s) "
    +            f"({len(tail)} message(s)) verbatim."
    +        )
    +    elif partial:
    +        lines.append(
    +            "Boundary: 'here' split would keep everything — "
    +            "falling back to full compression."
    +        )
    +    if focus_topic:
    +        lines.append(f'Focus topic: "{focus_topic}"')
    +    lines.append("Run the command again without --preview to apply.")
    +
    +    return {
    +        "head_count": len(head),
    +        "tail_count": len(tail),
    +        "total": total,
    +        "partial": effective_partial,
    +        "lines": lines,
    +    }
    +
    +
     def _coerce_keep(value: str) -> int:
         """Parse a keep-count token, clamping to [1, MAX_KEEP_LAST]."""
         try:
    ```
  - `locales/af.yaml`

    ```diff
    @@ -88,6 +88,7 @@ gateway:
         not_enough:            "Nie genoeg gesprek om saam te pers nie (ten minste 4 boodskappe nodig)."
         no_provider:           "Geen verskaffer opgestel nie -- kan nie saampers nie."
         nothing_to_do:         "Niks om saam te pers nie (die transkripsie is steeds heeltemal beskermde konteks)."
    +    aggressive_unsupported: "--aggressive word nie ondersteun nie; gebruik '/compress here [N]' om net onlangse uitruilings te behou, of /undo om beurte te verwyder."
         focus_line:            "Fokus: \"{topic}\""
         summary_failed:        "⚠️ Opsomming kon nie gegenereer word nie ({error}). {count} historiese boodskap(pe) is verwyder en met 'n plekhouer vervang; vroeëre konteks kan nie meer herstel word nie. Oorweeg om jou auxiliary.compression-modelopstelling na te gaan."
         aborted:               "⚠️ Kompressie gestaak ({error}). Geen boodskappe is laat val nie — die gesprek is onveranderd. Voer /compress uit om weer te probeer, /reset vir 'n skoon sessie, of kyk na jou auxiliary.compression-modelkonfigurasie."
    ```
  - `locales/de.yaml`

    ```diff
    @@ -88,6 +88,7 @@ gateway:
         not_enough:            "Nicht genug Konversation zum Komprimieren (mindestens 4 Nachrichten erforderlich)."
         no_provider:           "Kein Anbieter konfiguriert — Komprimierung nicht möglich."
         nothing_to_do:         "Noch nichts zu komprimieren (das Transkript ist weiterhin vollständig geschützter Kontext)."
    +    aggressive_unsupported: "--aggressive wird nicht unterstützt; verwende '/compress here [N]', um nur die letzten Austausche zu behalten, oder /undo, um Beiträge zu entfernen."
         focus_line:            "Fokus: \"{topic}\""
         summary_failed:        "⚠️ Zusammenfassungsgenerierung fehlgeschlagen ({error}). {count} historische Nachricht(en) wurden entfernt und durch einen Platzhalter ersetzt; früherer Kontext ist nicht mehr wiederherstellbar. Überprüfen Sie die Konfiguration des auxiliary.compression-Modells."
         aborted:               "⚠️ Komprimierung abgebrochen ({error}). Keine Nachrichten wurden entfernt — die Konversation ist unverändert. Führe /compress aus, um es erneut zu versuchen, /reset für eine neue Sitzung, oder prüfe deine auxiliary.compression-Modellkonfiguration."
    ```
  - `locales/en.yaml`

    ```diff
    @@ -103,6 +103,7 @@ gateway:
         not_enough:            "Not enough conversation to compress (need at least 4 messages)."
         no_provider:           "No provider configured -- cannot compress."
         nothing_to_do:         "Nothing to compress yet (the transcript is still all protected context)."
    +    aggressive_unsupported: "--aggressive is not supported; use '/compress here [N]' to keep only recent exchanges, or /undo to drop turns."
         focus_line:            "Focus: \"{topic}\""
         summary_failed:        "⚠️ Summary generation failed ({error}). {count} historical message(s) were removed and replaced with a placeholder; earlier context is no longer recoverable. Consider checking your auxiliary.compression model configuration."
         aborted:               "⚠️ Compression aborted ({error}). No messages were dropped — conversation is unchanged. Run /compress to retry, /reset for a clean session, or check your auxiliary.compression model configuration."
    ```
  - `locales/es.yaml`

    ```diff
    @@ -88,6 +88,7 @@ gateway:
         not_enough:            "No hay suficiente conversación para comprimir (se necesitan al menos 4 mensajes)."
         no_provider:           "No hay proveedor configurado — no se puede comprimir."
         nothing_to_do:         "Aún no hay nada que comprimir (la transcripción sigue siendo todo contexto protegido)."
    +    aggressive_unsupported: "--aggressive no es compatible; usa '/compress here [N]' para conservar solo los intercambios recientes, o /undo para eliminar turnos."
         focus_line:            "Enfoque: \"{topic}\""
         summary_failed:        "⚠️ Falló la generación del resumen ({error}). Se eliminaron {count} mensaje(s) históricos y se reemplazaron por un marcador; el contexto anterior ya no se puede recuperar. Considera revisar la configuración del modelo auxiliary.compression."
         aborted:               "⚠️ Compresión abortada ({error}). No se eliminó ningún mensaje — la conversación está intacta. Ejecuta /compress para reintentar, /reset para una sesión limpia, o revisa la configuración de tu modelo auxiliary.compression."
    ```
  - `locales/fr.yaml`

    ```diff
    @@ -88,6 +88,7 @@ gateway:
         not_enough:            "Conversation insuffisante pour la compression (au moins 4 messages nécessaires)."
         no_provider:           "Aucun fournisseur configuré — compression impossible."
         nothing_to_do:         "Rien à compresser pour l'instant (la transcription est encore entièrement du contexte protégé)."
    +    aggressive_unsupported: "--aggressive n'est pas pris en charge ; utilisez '/compress here [N]' pour ne conserver que les échanges récents, ou /undo pour supprimer des tours."
         focus_line:            "Focus : \"{topic}\""
         summary_failed:        "⚠️ Échec de la génération du résumé ({error}). {count} message(s) historique(s) ont été supprimés et remplacés par un espace réservé ; le contexte antérieur n'est plus récupérable. Vérifiez la configuration du modèle auxiliary.compression."
         aborted:               "⚠️ Compression interrompue ({error}). Aucun message n'a été supprimé — la conversation est inchangée. Lancez /compress pour réessayer, /reset pour une nouvelle session, ou vérifiez la configuration de votre modèle auxiliary.compression."
    ```
  - `locales/ga.yaml`

    ```diff
    @@ -92,6 +92,7 @@ gateway:
         not_enough:            "Níl go leor comhrá le dlúthú (teastaíonn 4 theachtaireacht ar a laghad)."
         no_provider:           "Níl aon soláthraí cumraithe — ní féidir dlúthú."
         nothing_to_do:         "Níl aon rud le dlúthú fós (tá an traschríbhinn fós uile mar chomhthéacs cosanta)."
    +    aggressive_unsupported: "Ní thacaítear le --aggressive; úsáid '/compress here [N]' chun na malartuithe is déanaí amháin a choinneáil, nó /undo chun sealanna a scriosadh."
         focus_line:            "Fócas: \"{topic}\""
         summary_failed:        "⚠️ Theip ar ghiniúint achoimre ({error}). Baineadh {count} teachtaireacht stairiúil agus cuireadh ionadaí ina n-áit; níl an comhthéacs roimhe seo in-aisghabhála a thuilleadh. Smaoinigh ar an gcumraíocht auxiliary.compression a sheiceáil."
         aborted:               "⚠️ Cuireadh deireadh leis an dlúthú ({error}). Níor baineadh aon teachtaireacht — tá an comhrá gan athrú. Rith /compress chun é a thriail arís, /reset le haghaidh seisiún glan, nó seiceáil do chumraíocht samhla auxiliary.compression."
    ```
  - `locales/hu.yaml`

    ```diff
    @@ -88,6 +88,7 @@ gateway:
         not_enough:            "Nincs elég beszélgetés a tömörítéshez (legalább 4 üzenet kell)."
         no_provider:           "Nincs konfigurált szolgáltató — nem lehet tömöríteni."
         nothing_to_do:         "Még nincs mit tömöríteni (a teljes átirat még védett kontextus)."
    +    aggressive_unsupported: "A --aggressive nem támogatott; használd a '/compress here [N]' parancsot, hogy csak a legutóbbi váltásokat tartsd meg, vagy a /undo parancsot körök törléséhez."
         focus_line:            "Fókusz: \"{topic}\""
         summary_failed:        "⚠️ Az összefoglaló generálása sikertelen ({error}). {count} korábbi üzenet eltávolítva és helykitöltővel helyettesítve; a korábbi kontextus már nem helyreállítható. Érdemes ellenőrizni az auxiliary.compression modell konfigurációját."
         aborted:               "⚠️ Tömörítés megszakítva ({error}). Egyetlen üzenet sem lett eldobva — a beszélgetés változatlan. Futtass /compress parancsot az újrapróbálkozáshoz, /reset egy új munkamenethez, vagy ellenőrizd az auxiliary.compression modell konfigurációt."
    ```
  - `locales/it.yaml`

    ```diff
    @@ -88,6 +88,7 @@ gateway:
         not_enough:            "Conversazione insufficiente da comprimere (servono almeno 4 messaggi)."
         no_provider:           "Nessun provider configurato — impossibile comprimere."
         nothing_to_do:         "Niente da comprimere per ora (la trascrizione è ancora tutta contesto protetto)."
    +    aggressive_unsupported: "--aggressive non è supportato; usa '/compress here [N]' per conservare solo gli scambi recenti, oppure /undo per rimuovere i turni."
         focus_line:            "Focus: \"{topic}\""
         summary_failed:        "⚠️ Generazione del riepilogo non riuscita ({error}). {count} messaggio/i storico/i sono stati rimossi e sostituiti con un segnaposto; il contesto precedente non è più recuperabile. Considera di controllare la configurazione del modello auxiliary.compression."
         aborted:               "⚠️ Compressione interrotta ({error}). Nessun messaggio è stato eliminato — la conversazione è invariata. Esegui /compress per riprovare, /reset per una nuova sessione, o controlla la configurazione del modello auxiliary.compression."
    ```
  - `locales/ja.yaml`

    ```diff
    @@ -88,6 +88,7 @@ gateway:
         not_enough:            "圧縮するための会話が不十分です (少なくとも 4 件のメッセージが必要)。"
         no_provider:           "プロバイダーが構成されていません — 圧縮できません。"
         nothing_to_do:         "まだ圧縮するものがありません (トランスクリプトはすべて保護されたコンテキストのままです)。"
    +    aggressive_unsupported: "--aggressive はサポートされていません。'/compress here [N]' で直近のやり取りだけを残すか、/undo でターンを削除してください。"
         focus_line:            "フォーカス: \"{topic}\""
         summary_failed:        "⚠️ 要約の生成に失敗しました ({error})。{count} 件の履歴メッセージが削除され、プレースホルダーに置き換えられました。以前のコンテキストは復元できません。auxiliary.compression モデルの設定を確認してください。"
         aborted:               "⚠️ 圧縮が中止されました ({error})。メッセージは削除されていません — 会話はそのままです。再試行するには /compress、新しいセッションを開始するには /reset を実行するか、auxiliary.compression モデル設定を確認してください。"
    ```
  - `locales/ko.yaml`

    ```diff
    @@ -88,6 +88,7 @@ gateway:
         not_enough:            "압축할 대화가 충분하지 않습니다 (최소 4개의 메시지가 필요합니다)."
         no_provider:           "구성된 제공자가 없습니다 -- 압축할 수 없습니다."
         nothing_to_do:         "아직 압축할 내용이 없습니다 (대화 내용이 모두 보호된 컨텍스트입니다)."
    +    aggressive_unsupported: "--aggressive는 지원되지 않습니다. '/compress here [N]'으로 최근 대화만 유지하거나 /undo로 턴을 삭제하세요."
         focus_line:            "초점: \"{topic}\""
         summary_failed:        "⚠️ 요약 생성에 실패했습니다 ({error}). 과거 메시지 {count}개가 제거되어 자리표시자로 대체되었으며, 이전 컨텍스트는 더 이상 복구할 수 없습니다. auxiliary.compression 모델 설정을 확인해 보세요."
         aborted:               "⚠️ 압축이 중단되었습니다 ({error}). 메시지가 삭제되지 않았으며 대화는 그대로 유지됩니다. 다시 시도하려면 /compress를 실행하거나, 새 세션을 시작하려면 /reset을 사용하거나, auxiliary.compression 모델 설정을 확인하세요."
    ```
  - `locales/pt.yaml`

    ```diff
    @@ -88,6 +88,7 @@ gateway:
         not_enough:            "Não há conversa suficiente para comprimir (são necessárias pelo menos 4 mensagens)."
         no_provider:           "Nenhum fornecedor configurado — não é possível comprimir."
         nothing_to_do:         "Ainda não há nada para comprimir (a transcrição continua a ser todo o contexto protegido)."
    +    aggressive_unsupported: "--aggressive não é suportado; usa '/compress here [N]' para manter apenas as trocas recentes, ou /undo para remover turnos."
         focus_line:            "Foco: \"{topic}\""
         summary_failed:        "⚠️ Falha ao gerar o resumo ({error}). {count} mensagem(ns) histórica(s) foram removidas e substituídas por um marcador; o contexto anterior já não pode ser recuperado. Considera verificar a configuração do modelo auxiliary.compression."
         aborted:               "⚠️ Compressão abortada ({error}). Nenhuma mensagem foi removida — a conversa está inalterada. Executa /compress para tentar de novo, /reset para uma sessão nova, ou verifica a configuração do modelo auxiliary.compression."
    ```
  - `locales/ru.yaml`

    ```diff
    @@ -88,6 +88,7 @@ gateway:
         not_enough:            "Недостаточно беседы для сжатия (нужно минимум 4 сообщения)."
         no_provider:           "Провайдер не настроен — сжатие невозможно."
         nothing_to_do:         "Пока нечего сжимать (стенограмма всё ещё полностью является защищённым контекстом)."
    +    aggressive_unsupported: "--aggressive не поддерживается; используйте '/compress here [N]', чтобы сохранить только недавние обмены, или /undo, чтобы удалить ходы."
         focus_line:            "Фокус: \"{topic}\""
         summary_failed:        "⚠️ Не удалось сгенерировать сводку ({error}). {count} историч. сообщений было удалено и заменено заполнителем; предыдущий контекст больше нельзя восстановить. Проверьте конфигурацию модели auxiliary.compression."
         aborted:               "⚠️ Сжатие прервано ({error}). Сообщения не были удалены — разговор не изменился. Запустите /compress для повторной попытки, /reset для новой сессии или проверьте конфигурацию модели auxiliary.compression."
    ```
  - `locales/tr.yaml`

    ```diff
    @@ -88,6 +88,7 @@ gateway:
         not_enough:            "Sıkıştırmak için yeterli konuşma yok (en az 4 mesaj gerekli)."
         no_provider:           "Yapılandırılmış sağlayıcı yok — sıkıştırılamıyor."
         nothing_to_do:         "Henüz sıkıştırılacak bir şey yok (transkript hâlâ tamamen korunan bağlam)."
    +    aggressive_unsupported: "--aggressive desteklenmiyor; yalnızca son alışverişleri tutmak için '/compress here [N]' kullanın veya turları silmek için /undo kullanın."
         focus_line:            "Odak: \"{topic}\""
         summary_failed:        "⚠️ Özet oluşturma başarısız ({error}). {count} geçmiş mesaj kaldırılıp yer tutucuyla değiştirildi; önceki bağlam artık kurtarılamaz. auxiliary.compression model yapılandırmanızı kontrol edin."
         aborted:               "⚠️ Sıkıştırma iptal edildi ({error}). Hiçbir mesaj silinmedi — konuşma değişmedi. Tekrar denemek için /compress, temiz bir oturum için /reset komutunu çalıştırın veya auxiliary.compression model yapılandırmanızı kontrol edin."
    ```
  - `locales/uk.yaml`

    ```diff
    @@ -88,6 +88,7 @@ gateway:
         not_enough:            "Недостатньо розмови для стиснення (потрібно щонайменше 4 повідомлення)."
         no_provider:           "Постачальника не налаштовано — неможливо стиснути."
         nothing_to_do:         "Поки що немає що стискати (стенограма все ще є повністю захищеним контекстом)."
    +    aggressive_unsupported: "--aggressive не підтримується; використовуйте '/compress here [N]', щоб зберегти лише останні обміни, або /undo, щоб видалити ходи."
         focus_line:            "Фокус: \"{topic}\""
         summary_failed:        "⚠️ Не вдалося згенерувати зведення ({error}). {count} історичних повідомлень було видалено та замінено заповнювачем; попередній контекст більше не можна відновити. Перевірте конфігурацію моделі auxiliary.compression."
         aborted:               "⚠️ Стиснення скасовано ({error}). Жодне повідомлення не було видалено — розмова не змінилася. Виконайте /compress, щоб повторити спробу, /reset для нової сесії, або перевірте конфігурацію моделі auxiliary.compression."
    ```
  - `locales/zh-hant.yaml`

    ```diff
    @@ -88,6 +88,7 @@ gateway:
         not_enough:            "對話內容不足，無法壓縮（至少需要 4 則訊息）。"
         no_provider:           "未設定提供方 — 無法壓縮。"
         nothing_to_do:         "目前沒有可壓縮的內容（對話記錄仍全部為受保護的上下文）。"
    +    aggressive_unsupported: "不支援 --aggressive；請使用 '/compress here [N]' 只保留最近的對話，或使用 /undo 刪除回合。"
         focus_line:            "聚焦：\"{topic}\""
         summary_failed:        "⚠️ 摘要產生失敗（{error}）。{count} 則歷史訊息已被移除並以佔位符取代；先前的上下文已無法復原。建議檢查 auxiliary.compression 模型設定。"
         aborted:               "⚠️ 壓縮已中止 ({error})。未刪除任何訊息 — 對話保持不變。執行 /compress 重試，執行 /reset 開始新工作階段，或檢查你的 auxiliary.compression 模型設定。"
    ```
  - `locales/zh.yaml`

    ```diff
    @@ -88,6 +88,7 @@ gateway:
         not_enough:            "对话内容不足，无法压缩（至少需要 4 条消息）。"
         no_provider:           "未配置提供方 — 无法压缩。"
         nothing_to_do:         "暂无可压缩内容（对话记录仍全部为受保护上下文）。"
    +    aggressive_unsupported: "不支持 --aggressive；请使用 '/compress here [N]' 只保留最近的对话，或使用 /undo 删除回合。"
         focus_line:            "聚焦：\"{topic}\""
         summary_failed:        "⚠️ 摘要生成失败（{error}）。{count} 条历史消息已被移除并替换为占位符；之前的上下文已无法恢复。建议检查 auxiliary.compression 模型配置。"
         aborted:               "⚠️ 压缩已中止 ({error})。未删除任何消息 — 对话保持不变。运行 /compress 重试，运行 /reset 开始新会话，或检查你的 auxiliary.compression 模型配置。"
    ```
  - `scripts/release.py`

    ```diff
    @@ -45,6 +45,7 @@
     
     # Auto-extracted from noreply emails + manual overrides
     AUTHOR_MAP = {
    +    "[redacted:email]": "Mibayy",  # PR #3243 salvage (/compact alias + preview/aggressive flags for /compress)
         "[redacted:email]": "CrazyBoyM",  # PR #55828 salvage (image_gen openai-codex: wire image-to-image / reference-image editing via Codex Responses input_image parts; magic-byte + read-guard + 25MB-cap + clamp-to-16 hardening)
         "r0gersm1th@users.noreply.github.com": "r0gersm1th",  # PR #3219 salvage (whatsapp bridge: resolve LID sender IDs to phone numbers in the message payload so phone-based allowlists match; commit authored by collaborator r0gersm1th, PR by @ajmeese7)
         "[redacted:email]": "tarunravi",  # PR #2696 salvage (api-server: inline MEDIA:<path> image tags as base64 data URLs in final responses so remote OpenAI-compatible frontends can render server-local screenshots; the PR's tool-progress-streaming and SSE-sentinel pieces were independently superseded on main)
    ```
  - `tests/cli/test_compress_flags.py`

    ```diff
    @@ -0,0 +1,155 @@
    +"""Tests for /compress --preview/--dry-run/--aggressive flags and the
    +/compact alias (PR #3243 salvage).
    +
    +Covers the pure helpers in ``hermes_cli.partial_compress`` plus alias
    +resolution in the command registry. The CLI and gateway surfaces both
    +route through these helpers, so the flag semantics are pinned here once.
    +"""
    +
    +from hermes_cli.commands import COMMANDS, resolve_command
    +from hermes_cli.partial_compress import (
    +    DEFAULT_KEEP_LAST,
    +    extract_compress_flags,
    +    parse_partial_compress_args,
    +    summarize_compress_preview,
    +)
    +
    +
    +def _history(n_pairs: int) -> list[dict[str, str]]:
    +    h: list[dict[str, str]] = []
    +    for i in range(n_pairs):
    +        h.append({"role": "user", "content": f"u{i}"})
    +        h.append({"role": "assistant", "content": f"a{i}"})
    +    return h
    +
    +
    +# ── /compact alias resolution ─────────────────────────────────────────
    +
    +
    +def test_compact_resolves_to_compress():
    +    cmd = resolve_command("compact")
    +    assert cmd is not None
    +    assert cmd.name == "compress"
    +    assert "compact" in cmd.aliases
    +
    +
    +def test_compact_alias_with_slash():
    +    cmd = resolve_command("/compact")
    +    assert cmd is not None and cmd.name == "compress"
    +
    +
    +def test_compact_listed_in_flat_commands():
    +    assert "/compact" in COMMANDS
    +    assert "alias for /compress" in COMMANDS["/compact"]
    +
    +
    +def test_compress_args_hint_documents_preview():
    +    cmd = resolve_command("compress")
    +    assert cmd is not None
    +    assert "--preview" in (cmd.args_hint or "")
    +
    +
    +# ── extract_compress_flags ────────────────────────────────────────────
    +
    +
    +def test_no_flags_passthrough():
    +    rest, preview, aggressive = extract_compress_flags("here 3")
    +    assert rest == "here 3"
    +    assert preview is False
    +    assert aggressive is False
    +
    +
    +def test_preview_flag_stripped():
    +    rest, preview, aggressive = extract_compress_flags("--preview")
    +    assert rest == ""
    +    assert preview is True
    +    assert aggressive is False
    +
    +
    +def test_dry_run_is_preview():
    +    for form in ("--dry-run", "--dryrun", "--DRY-RUN"):
    +        _, preview, _ = extract_compress_flags(form)
    +        assert preview is True, form
    +
    +
    +def test_aggressive_flag_detected():
    +    rest, preview, aggressive = extract_compress_flags("--aggressive")
    +    assert rest == ""
    +    assert preview is False
    +    assert aggressive is True
    +
    +
    +def test_flags_coexist_with_here_form():
    +    rest, preview, aggressive = extract_compress_flags("--preview here 4")
    +    assert rest == "here 4"
    +    assert preview is True
    +    partial, keep, focus = parse_partial_compress_args(rest)
    +    assert partial is True and keep == 4 and focus is None
    +
    +
    +def test_flags_coexist_with_focus_topic():
    +    rest, preview, _ = extract_compress_flags("database schema --dry-run")
    +    assert rest == "database schema"
    +    assert preview is True
    +    partial, _, focus = parse_partial_compress_args(rest)
    +    assert partial is False and focus == "database schema"
    +
    +
    +def test_aggressive_dry_run_combo():
    +    rest, preview, aggressive = extract_compress_flags("--aggressive --dry-run")
    +    assert rest == ""
    +    assert preview is True and aggressive is True
    +
    +
    +def test_empty_args():
    +    rest, preview, aggressive = extract_compress_flags("")
    +    assert rest == "" and preview is False and aggressive is False
    +
    +
    +# ── summarize_compress_preview ────────────────────────────────────────
    +
    +
    +def test_preview_full_compress_counts():
    +    hist = _history(5)
    +    report = summarize_compress_preview(hist, False, DEFAULT_KEEP_LAST, None, 1234)
    +    assert report["head_count"] == 10
    +    assert report["tail_count"] == 0
    +    assert report["total"] == 10
    +    assert report["partial"] is False
    +    joined = "\n".join(report["lines"])
    +    assert "no changes made" in joined.lower()
    +    assert "10 of 10" in joined
    +    assert "1,234" in joined
    +
    +
    +def test_preview_partial_boundary_counts():
    +    hist = _history(5)
    +    report = summarize_compress_preview(hist, True, 2, None, 999)
    +    # Keeping last 2 exchanges = 4 tail messages, 6 head messages.
    +    assert report["head_count"] == 6
    +    assert report["tail_count"] == 4
    +    assert report["partial"] is True
    +    joined = "\n".join(report["lines"])
    +    assert "last 2 exchange" in joined
    +
    +
    +def test_preview_partial_degenerate_falls_back_to_full():
    +    hist = _history(2)  # keep_last=5 would swallow everything
    +    report = summarize_compress_preview(hist, True, 5, None, 100)
    +    assert report["partial"] is False
    +    assert report["head_count"] == 4
    +    joined = "\n".join(report["lines"])
    +    assert "falling back to full compression" in joined
    +
    +
    +def test_preview_includes_focus_topic():
    +    hist = _history(4)
    +    report = summarize_compress_preview(hist, False, DEFAULT_KEEP_LAST, "db schema", 50)
    +    assert 'Focus topic: "db schema"' in "\n".join(report["lines"])
    +
    +
    +def test_preview_is_side_effect_free():
    +    hist = _history(4)
    +    before = [dict(m) for m in hist]
    +    summarize_compress_preview(hist, True, 1, None, 10)
    +    assert hist == before
    ```
  - `tests/gateway/test_compress_preview.py`

    ```diff
    @@ -0,0 +1,120 @@
    +"""Tests for gateway /compress --preview/--dry-run/--aggressive flags
    +(PR #3243 salvage).
    +
    +The preview path must return a report WITHOUT building an agent or
    +touching the transcript; --aggressive must return an explanatory
    +message rather than being mis-parsed as a focus topic.
    +"""
    +
    +from datetime import datetime
    +from unittest.mock import MagicMock
    +
    +import pytest
    +
    +from gateway.config import GatewayConfig, Platform, PlatformConfig
    +from gateway.platforms.base import MessageEvent
    +from gateway.session import SessionEntry, SessionSource, build_session_key
    +
    +
    +def _make_source() -> SessionSource:
    +    return SessionSource(
    +        platform=Platform.TELEGRAM,
    +        user_id="u1",
    +        chat_id="c1",
    +        user_name="tester",
    +        chat_type="dm",
    +    )
    +
    +
    +def _make_event(text: str) -> MessageEvent:
    +    return MessageEvent(text=text, source=_make_source(), message_id="m1")
    +
    +
    +def _make_history(n_pairs: int = 3) -> list[dict[str, str]]:
    +    h: list[dict[str, str]] = []
    +    for i in range(n_pairs):
    +        h.append({"role": "user", "content": f"u{i}"})
    +        h.append({"role": "assistant", "content": f"a{i}"})
    +    return h
    +
    +
    +def _make_runner(history: list[dict[str, str]]):
    +    from gateway.run import GatewayRunner
    +
    +    runner = object.__new__(GatewayRunner)
    +    runner.config = GatewayConfig(
    +        platforms={Platform.TELEGRAM: PlatformConfig(enabled=True, token="***")}
    +    )
    +    session_entry = SessionEntry(
    +        session_key=build_session_key(_make_source()),
    +        session_id="sess-1",
    +        created_at=datetime.now(),
    +        updated_at=datetime.now(),
    +        platform=Platform.TELEGRAM,
    +        chat_type="dm",
    +    )
    +    runner.session_store = MagicMock()
    +    runner.session_store.get_or_create_session.return_value = session_entry
    +    runner.session_store.load_transcript.return_value = history
    +    runner.session_store.rewrite_transcript = MagicMock()
    +    runner.session_store.update_session = MagicMock()
    +    runner.session_store._save = MagicMock()
    +    runner._session_db = None
    +    return runner
    +
    +
    [redacted:email]
    +async def test_preview_reports_without_mutating():
    +    runner = _make_runner(_make_history(3))
    +    result = await runner._handle_compress_command(_make_event("/compress --preview"))
    +    assert "no changes made" in result.lower()
    +    assert "6 of 6" in result
    +    runner.session_store.rewrite_transcript.assert_not_called()
    +    runner.session_store.update_session.assert_not_called()
    +
    +
    [redacted:email]
    +async def test_dry_run_alias_matches_preview():
    +    runner = _make_runner(_make_history(3))
    +    result = await runner._handle_compress_command(_make_event("/compress --dry-run"))
    +    assert "no changes made" in result.lower()
    +    runner.session_store.rewrite_transcript.assert_not_called()
    +
    +
    [redacted:email]
    +async def test_preview_with_here_boundary():
    +    runner = _make_runner(_make_history(4))
    +    result = await runner._handle_compress_command(
    +        _make_event("/compress --preview here 2")
    +    )
    +    assert "last 2 exchange" in result
    +    assert "4 of 8" in result
    +    runner.session_store.rewrite_transcript.assert_not_called()
    +
    +
    [redacted:email]
    +async def test_aggressive_returns_unsupported_note_without_mutating():
    +    runner = _make_runner(_make_history(3))
    +    result = await runner._handle_compress_command(
    +        _make_event("/compress --aggressive")
    +    )
    +    assert "--aggressive is not supported" in result
    +    runner.session_store.rewrite_transcript.assert_not_called()
    +
    +
    [redacted:email]
    +async def test_aggressive_dry_run_shows_preview_plus_note():
    +    runner = _make_runner(_make_history(3))
    +    result = await runner._handle_compress_command(
    +        _make_event("/compress --aggressive --dry-run")
    +    )
    +    assert "no changes made" in result.lower()
    +    assert "--aggressive is not supported" in result
    +    runner.session_store.rewrite_transcript.assert_not_called()
    +
    +
    [redacted:email]
    +async def test_preview_still_requires_enough_history():
    +    runner = _make_runner(_make_history(1))  # only 2 messages
    +    result = await runner._handle_compress_command(_make_event("/compress --preview"))
    +    assert "not enough" in result.lower()
    ```
- **#3243: feat(commands): \`/compact\` alias + \`--preview\`/\`--dry-run\`/\`--aggressive\` flags for \`/compress\`**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#57029`, `#44794`

## Fix status

- Merged to main in PR #57029 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-07-02`.
- Subsystem: `agent`
