diff --git a/backend/packages/harness/deerflow/runtime/journal.py b/backend/packages/harness/deerflow/runtime/journal.py index 65dec3754..dccc9f481 100644 --- a/backend/packages/harness/deerflow/runtime/journal.py +++ b/backend/packages/harness/deerflow/runtime/journal.py @@ -366,6 +366,19 @@ class RunJournal(BaseCallbackHandler): """Record the first human message for convenience fields.""" self._first_human_msg = content[:2000] if content else None + def record_middleware(self, name: str, hook: str, action: str, changes: dict) -> None: + """Record a middleware trace event. + + Called by middleware implementations when they perform a meaningful + state change (e.g., title generation, summarization, HITL approval). + Pure-observation middleware should not call this. + """ + self._put( + event_type="middleware", + category="trace", + content={"name": name, "hook": hook, "action": action, "changes": changes}, + ) + async def flush(self) -> None: """Force flush remaining buffer. Called in worker's finally block.""" if self._buffer: diff --git a/backend/tests/test_run_journal.py b/backend/tests/test_run_journal.py index 8518bf50b..3904293f2 100644 --- a/backend/tests/test_run_journal.py +++ b/backend/tests/test_run_journal.py @@ -743,3 +743,24 @@ class TestLlmRequestResponse: await j.flush() events = await store.list_events("t1", "r1") assert not any(e["event_type"] == "llm_start" for e in events) + + +class TestMiddlewareTrace: + @pytest.mark.anyio + async def test_record_middleware(self, journal_setup): + j, store = journal_setup + j.record_middleware( + name="TitleMiddleware", + hook="after_model", + action="generate_title", + changes={"title": "Test Title", "thread_id": "t1"}, + ) + await j.flush() + events = await store.list_events("t1", "r1") + mw_events = [e for e in events if e["event_type"] == "middleware"] + assert len(mw_events) == 1 + assert mw_events[0]["category"] == "trace" + assert mw_events[0]["content"]["name"] == "TitleMiddleware" + assert mw_events[0]["content"]["hook"] == "after_model" + assert mw_events[0]["content"]["action"] == "generate_title" + assert mw_events[0]["content"]["changes"]["title"] == "Test Title"