feat(memo): Hermes input gateway — source_channel='hermes' + source_metadata jsonb
PR-Hermes-Docsrv-Bridge-1 v1. Hermes Agent (Mac mini Discord) 를 Document Server
입력 게이트웨이로 reframe — 코딩 executor X, Claude Code 변동 0.
변경:
- migration 267: source_channel enum 에 'hermes' 추가
- migration 268: documents.source_metadata jsonb NOT NULL DEFAULT '{}' 추가
- Document model: source_metadata 컬럼 ORM 매핑 + enum 'hermes' 노출
- MemoCreate: source_channel + source_metadata 필드 수용 (default='memo' 호환)
- create_memo: channel allowlist (memo/voice/hermes) + metadata jsonb 저장
- list_memos: IN tuple 에 'hermes' 추가 (inbox 노출)
- MemoResponse + _to_memo_response: source_metadata 노출 (UI 배지 준비)
LLM 호출 0 — Hermes 의 HTTP POST 만. 분류/요약은 classify_worker 비동기 처리.
promote-to-event guard (562/664) 변경 0 — v1 = hermes 메모 promote 차단 유지.
plan: ~/.claude/plans/idempotent-seeking-hollerith.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+19
-3
@@ -143,6 +143,11 @@ class MemoCreate(BaseModel):
|
||||
content: str
|
||||
title: str | None = None # 선택적 제목 (없으면 첫 줄 자동 생성)
|
||||
ask_includable: bool = True
|
||||
# PR-Hermes-Docsrv-Bridge-1: 외부 채널 진입점 식별. default='memo' (web UI 호환).
|
||||
# 허용 값: memo / voice / hermes / ... (app/models/document.py source_channel enum).
|
||||
source_channel: str | None = None
|
||||
# PR-Hermes-Docsrv-Bridge-1: channel/user/message_id/timestamp 등 채널 메타.
|
||||
source_metadata: dict | None = None
|
||||
|
||||
|
||||
class MemoUpdate(BaseModel):
|
||||
@@ -175,7 +180,8 @@ class MemoResponse(BaseModel):
|
||||
# Memo Intake Upgrade PR-2B — AI 추천 분류 (사용자 1-click promote 의 hint)
|
||||
ai_event_kind: str | None = None
|
||||
ai_event_confidence: float | None = None
|
||||
source_channel: str | None = None # voice/memo 등 진입점 식별 (UI 배지)
|
||||
source_channel: str | None = None # voice/memo/hermes 등 진입점 식별 (UI 배지)
|
||||
source_metadata: dict = {} # PR-Hermes-Docsrv-Bridge-1: channel/user/message_id/timestamp
|
||||
file_type: str | None = None # audio (voice 메모) vs note (text 메모)
|
||||
file_path: str | None = None # voice 메모의 NAS audio 경로 (audio player 용)
|
||||
created_at: datetime
|
||||
@@ -210,6 +216,7 @@ def _to_memo_response(doc: Document) -> MemoResponse:
|
||||
ai_event_kind=doc.ai_event_kind,
|
||||
ai_event_confidence=doc.ai_event_confidence,
|
||||
source_channel=doc.source_channel,
|
||||
source_metadata=dict(doc.source_metadata or {}),
|
||||
file_type=doc.file_type,
|
||||
file_path=doc.file_path,
|
||||
created_at=doc.created_at,
|
||||
@@ -231,6 +238,13 @@ async def create_memo(
|
||||
if not content:
|
||||
raise HTTPException(status_code=400, detail="메모 내용이 비어있습니다")
|
||||
|
||||
# PR-Hermes-Docsrv-Bridge-1: source_channel/metadata override 가능. default='memo' (기존 web UI 호환).
|
||||
channel = body.source_channel or "memo"
|
||||
if channel not in ("memo", "voice", "hermes"):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"source_channel '{channel}' 허용 안 됨 (memo/voice/hermes 만)",
|
||||
)
|
||||
doc = Document(
|
||||
file_path=None,
|
||||
file_hash=_content_hash(content),
|
||||
@@ -240,7 +254,8 @@ async def create_memo(
|
||||
title=body.title.strip() if body.title and body.title.strip() else _auto_title(content),
|
||||
extracted_text=content,
|
||||
review_status="approved",
|
||||
source_channel="memo",
|
||||
source_channel=channel,
|
||||
source_metadata=body.source_metadata or {},
|
||||
user_tags=_parse_hashtags(content),
|
||||
pinned=False,
|
||||
archived=False,
|
||||
@@ -273,9 +288,10 @@ async def list_memos(
|
||||
PR-2C: source_channel='voice' (음성 메모) 도 포함. 사용자 의도 = 메모는 모든 입력의 inbox.
|
||||
voice 메모는 file_type='immutable' + category='audio' + source_channel='voice' 패턴.
|
||||
source_channel 만으로 분리 (file_type 필터는 immutable 다른 binary 까지 끌어옴 — 회피).
|
||||
PR-Hermes-Docsrv-Bridge-1: source_channel='hermes' (Hermes Discord 등 외부 채널 진입) 도 inbox 포함.
|
||||
"""
|
||||
base = select(Document).where(
|
||||
Document.source_channel.in_(("memo", "voice")),
|
||||
Document.source_channel.in_(("memo", "voice", "hermes")),
|
||||
Document.deleted_at == None, # noqa: E711
|
||||
Document.archived == archived,
|
||||
)
|
||||
|
||||
@@ -104,9 +104,12 @@ class Document(Base):
|
||||
source_channel: Mapped[str | None] = mapped_column(
|
||||
Enum("law_monitor", "devonagent", "email", "web_clip",
|
||||
"tksafety", "inbox_route", "manual", "drive_sync", "news", "memo",
|
||||
"voice",
|
||||
"voice", "hermes",
|
||||
name="source_channel")
|
||||
)
|
||||
# 외부 채널 (Hermes Discord 등) 의 channel/user/message_id/timestamp 메타.
|
||||
# extract_meta (OCR 전용) 와 분리.
|
||||
source_metadata: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict)
|
||||
data_origin: Mapped[str | None] = mapped_column(
|
||||
Enum("work", "external", name="data_origin")
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user