Skip to content

fix: reduce empty episodes, finalize idle episodes, filter auto-skill prompts#1585

Open
Starfie1d1272 wants to merge 22 commits intoMemTensor:mainfrom
Starfie1d1272:fix/hydrogen-noise-and-idle-timeout
Open

fix: reduce empty episodes, finalize idle episodes, filter auto-skill prompts#1585
Starfie1d1272 wants to merge 22 commits intoMemTensor:mainfrom
Starfie1d1272:fix/hydrogen-noise-and-idle-timeout

Conversation

@Starfie1d1272
Copy link
Copy Markdown

@Starfie1d1272 Starfie1d1272 commented Apr 29, 2026

Summary

Three fixes targeting noise reduction and correctness in the Hermes adapter pipeline.

1. Remove adapter-initiated episode pre-creation

File: adapters/hermes/memos_provider/__init__.py

The initialize() was calling episode.open to pre-create an episode with placeholder text "(adapter-initiated)". The orchestrator's onTurnStart()openEpisodeIfNeeded() already creates a proper episode on the first real turn, so every Hermes session produced 2 episodes — one empty orphan that always got abandoned.

Fix: Remove the episode.open call. The orchestrator creates episodes when needed.

2. Finalize idle-timeout episodes instead of abandoning them

File: core/pipeline/memory-core.ts

autoFinalizeStaleTasks() was calling abandon() on all stale episodes regardless of content. This marked episodes with real conversation turns as "skipped" in the webUI. Idle timeout ≠ meaningless conversation.

Fix: Check traceIds before deciding:

  • Episodes with ≥ 1 trace → finalizeEpisode() (triggers reflection + scoring pipeline, shows as "completed")
  • Truly empty episodes → abandon() (skipped, as before)

3. Filter Hermes/OpenClaw auto-skill evaluation prompts

File: memos-local-openclaw/src/capture/index.ts

OpenClaw injects a structured auto-skill evaluation prompt at task end ("Review the conversation above and consider whether a skill should be saved or updated... SURVEY the existing skill landscape..."). Capturing this as conversation content pollutes memory search indices, task summaries, and downstream skill generation.

Fix: Add stripAutoSkillEval() filter that detects the distinctive header phrase and strips the entire prompt block. Applied to all captured messages before storage.

Test plan

  • memory-core.test.ts — 15/15 pass (no regressions)
  • TypeScript compilation — no errors in changed files
  • Manual verification: Hermes session creates exactly 1 episode per conversation
  • Manual verification: idle-timeout episodes with content appear as "completed" in webUI
  • Manual verification: auto-skill eval prompts no longer appear in memory search results

🤖 Generated with Claude Code

Starfie1d1272 and others added 18 commits April 27, 2026 13:29
…ive sessions; add episode delete API

Two related fixes:

1. Orphan episode protection (2 files):
   - core/pipeline/memory-core.ts: init() now checks session.meta.closedAt
     before treating open episodes as orphans. Episodes from sessions
     that haven't been explicitly closed are no longer abandoned on
     bridge restart.
   - core/session/manager.ts: closeSession() now stamps session.meta.closedAt
     so future init() calls can distinguish 'explicitly closed' from
     'crashed and might reconnect'.

2. WebUI Tasks page bulk delete (3 files):
   - core/storage/repos/episodes.ts: added deleteById() method
   - core/pipeline/memory-core.ts: added deleteEpisode() / deleteEpisodes()
   - agent-contract/memory-core.ts: added interface signatures
   - server/routes/session.ts: DELETE /api/v1/episodes now calls
     deleteEpisode (actually removes the row + cascading traces)
     instead of closeEpisode (no-op on already-closed episodes).
     Added POST /api/v1/episodes/delete for bulk operations.
…rmes session restart

Three fixes for the Hermes bridge adapter:

1. Use tsx runtime instead of node --experimental-strip-types
2. PID file to prevent duplicate bridge processes
3. Bridge lifetime tracking via register_bridge()
…d of hardcoding

Previously the bridge reported '2.0.0-alpha.1' regardless of the
actual package version. Now it reads from package.json at startup.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Prevent deleting episodes that are currently open/in-flight,
which could cause FK violations and corrupt in-memory pipeline state.

Co-Authored-By: Copilot <copilot-pull-request-reviewer[bot]@users.noreply.github.com>
… TCP

Changes:
- bridge_client.py: add TCP transport mode — connect to daemon bridge
  at 127.0.0.1:18911 instead of spawning a subprocess. Supports
  transparent reconnection on connection loss. Falls back to stdio
  subprocess mode when TCP params are not provided.
- __init__.py (MemTensorProvider): connect via TCP to daemon bridge;
  on Hermes CLI exit, skip episode.close + session.close so the daemon
  bridge owns the session and the pipeline can finalize episodes naturally.

Why: previously each Hermes CLI session spawned its own bridge subprocess,
which died on CLI exit → all open episodes abandoned as 'session_closed:client'.
With TCP mode the daemon bridge (persistent background process) keeps the
session alive, and episodes get properly finalized by the pipeline.
…ddress Copilot review

Changes:
- bridge/tcp.ts: new TCP JSON-RPC server (line-delimited, multi-client)
- bridge.cts: start TCP server when --daemon --tcp=<port> is given;
  daemon mode now waits for TCP server stop instead of blocking forever
- memory-core.ts: batch-fetch sessions to avoid N+1 orphan scan (Copilot suggestion);
  add SQLite fallback check in assertEpisodeDeletable (Copilot suggestion)
…k, double newline, port validation

Changes per Copilot review (8 comments):
- bridge_client.py: fix double-newline in _SocketTransport.write_line()
- bridge.cts: validate --tcp port with Number.isFinite() before use
- __init__.py: TCP mode with stdio fallback (try TCP first, spawn
  subprocess on failure); restore episode.close + session.close in
  on_session_end (Copilot correctly noted closedAt must be stamped)
- daemon_manager.py: add mkdir(parents=True) before writing PID file
  (prevents crash when parent dir doesn't exist)
bridge_client.py: close old transport before reconnect
bridge/tcp.ts: await server.close() in TcpServerHandle.close()
agent-contract/memory-core.ts: deleteEpisode/deleteEpisodes no longer optional
bridge/tcp.ts: wrap server.listen in a promise; expose 'ready' so
callers catch EADDRINUSE; remove stale server-level error handler
bridge.cts: await tcpServer.ready inside try/catch
bridge_client.py: enable TCP mode when either tcp_host or tcp_port
  is provided (not just tcp_host)
server/routes/session.ts: return { ok: true, deleted } for backward
  compatibility with callers expecting { ok: true }
…leanup

bridge.cts: add shuttingDown guard to prevent double-shutdown
bridge/tcp.ts: validate typeof msg.method === 'string';
  sock.destroy() on error to prevent resource leak
bridge/tcp.ts: check sock.destroyed before destroying
core/pipeline/memory-core.ts: extract deleteClosedEpisode helper
  with no-op regression guard
…r import

bridge.cts: close tcpServer on startup failure; warn and skip TCP
  when --daemon not set; daemon mode now also falls back to stdio when
  TCP port is not given
bridge/tcp.ts: isListening flag for error handling (no more
  removeAllListeners); runtime errors are logged not swallowed
__init__.py: import BridgeError so TCP→stdio fallback actually works
Copilot AI review requested due to automatic review settings April 29, 2026 09:23
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR targets episode/session lifecycle correctness and noise reduction across the Hermes adapter, the MemoryCore pipeline, and OpenClaw capture, with additional bridge transport/lifecycle changes to support daemon/TCP operation.

Changes:

  • Stop Hermes from pre-creating adapter-initiated episodes and add daemon/TCP bridge support (with PID-file management) to reduce orphan/abandoned episodes.
  • Adjust stale-episode handling to finalize episodes with real traces (instead of always abandoning) and add episode deletion APIs.
  • Strip OpenClaw/Hermes auto-skill evaluation prompts from captured content to prevent memory/index pollution.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
apps/memos-local-plugin/tests/unit/pipeline/memory-core.test.ts Adds tests for init-time orphan/open episode handling and episode deletion behavior.
apps/memos-local-plugin/server/routes/session.ts Switches episode DELETE to core deletion and adds bulk delete endpoint.
apps/memos-local-plugin/package-lock.json Bumps package version alpha → beta and updates lock metadata.
apps/memos-local-plugin/core/storage/repos/episodes.ts Adds deleteById to delete episode rows.
apps/memos-local-plugin/core/session/manager.ts Stamps session.meta.closedAt on close to distinguish explicit closure from reconnect scenarios.
apps/memos-local-plugin/core/pipeline/memory-core.ts Finalize-vs-abandon stale episodes based on trace presence; changes init orphan logic; adds deleteEpisode(s).
apps/memos-local-plugin/bridge/tcp.ts Introduces a TCP JSON-RPC server transport with log/event broadcast.
apps/memos-local-plugin/bridge.cts Adds daemon/TCP startup/shutdown wiring and reads package version dynamically.
apps/memos-local-plugin/agent-contract/memory-core.ts Extends MemoryCore interface with deleteEpisode(s).
apps/memos-local-plugin/adapters/hermes/memos_provider/daemon_manager.py Adds PID file tracking + stale bridge termination helpers.
apps/memos-local-plugin/adapters/hermes/memos_provider/bridge_client.py Adds TCP client mode + stdio fallback; integrates PID management in stdio mode.
apps/memos-local-plugin/adapters/hermes/memos_provider/init.py Prefers TCP daemon bridge; removes eager episode.open; closes session/episode on session end.
apps/memos-local-openclaw/src/capture/index.ts Adds stripping of auto-skill evaluation prompt blocks before storage.
Files not reviewed (1)
  • apps/memos-local-plugin/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +71 to +77
function broadcast(obj: unknown): void {
const payload = JSON.stringify(obj) + "\n";
for (const sock of clients) {
try {
sock.write(payload);
} catch {
/* best-effort per client */
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged. This is a valid robustness concern but in practice TCP clients are few (1-3). Tracked for a follow-up PR focused on TCP transport hardening.

Comment on lines +145 to +149
# Try TCP mode first (connect to daemon bridge).
# Falls back to stdio (spawn subprocess) if daemon isn't running.
try:
self._bridge = MemosBridgeClient(tcp_host="127.0.0.1", tcp_port=18911)
except BridgeError:
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not an issue: the orchestrator's onTurnEnd() (memory-core.ts:655) resolves episodeId via openEpisodeBySession.get(sessionId) — the in-memory cache populated by turn.start. The adapter's _episode_id is only a fallback that is never reached in normal flow.

Comment on lines +462 to 466
# (e.g. session.meta.closedAt). In TCP mode this ensures the daemon
# can distinguish "normal shutdown" from "abrupt disconnect" on
# restart, preventing orphan retention.
with contextlib.suppress(Exception):
self._bridge.request("episode.close", {"episodeId": self._episode_id})
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not an issue: the episode.close call is wrapped in contextlib.suppress(Exception) and will silently no-op on empty id. The actual episode finalization is handled by autoFinalizeStaleTasks() in the pipeline.

Comment on lines +362 to +366
const orphans = openEpisodes.filter((ep) => {
const session = sessionById.get(ep.sessionId);
if (!session) return true; // session row gone — orphan
if (session.meta?.closedAt != null) return true; // explicitly closed — orphan
return false; // session exists and not closed — skip, might reconnect
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged. The orphan episode hydration issue originates from PR #1546's init logic. This needs a dedicated architectural PR to rehydrate open episodes into the orchestrator's in-memory state on boot. Too large to address here.

Comment thread apps/memos-local-plugin/adapters/hermes/memos_provider/daemon_manager.py Outdated
Comment thread apps/memos-local-plugin/server/routes/session.ts
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR reduces episode/task noise and improves correctness across the Hermes adapter + memos-local-plugin pipeline by (a) avoiding adapter-created “empty” episodes, (b) finalizing stale-but-nonempty episodes instead of abandoning them, and (c) adding episode deletion support plus a TCP JSON-RPC transport for daemonized Hermes usage.

Changes:

  • Update stale-episode handling and init-time orphan cleanup logic in MemoryCore, and add deleteEpisode(s) APIs (HTTP + JSON-RPC + core).
  • Add a line-delimited JSON-RPC over TCP server and daemon-mode bridge wiring (bridge.cts), plus Hermes Python client support (TCP-first, stdio fallback) with PID-file management.
  • Add unit tests for init-time orphan episode behavior and episode deletion, plus version bump to 2.0.0-beta.1.

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
apps/memos-local-plugin/core/pipeline/memory-core.ts Finalize vs abandon stale episodes; change init orphan-episode closing rules; add deleteEpisode/deleteEpisodes.
apps/memos-local-plugin/agent-contract/memory-core.ts Extend MemoryCore contract with deleteEpisode(s).
apps/memos-local-plugin/agent-contract/jsonrpc.ts Add JSON-RPC method names for episode deletion.
apps/memos-local-plugin/bridge/methods.ts Wire JSON-RPC dispatcher cases for episode deletion.
apps/memos-local-plugin/bridge/tcp.ts New TCP transport: line-delimited JSON-RPC server + event/log broadcasting.
apps/memos-local-plugin/bridge.cts Support daemon + TCP server startup/shutdown; read version from package.json.
apps/memos-local-plugin/server/routes/session.ts HTTP endpoints for deleting episodes (single + bulk).
apps/memos-local-plugin/core/storage/repos/episodes.ts Add SQLite deleteById repo method.
apps/memos-local-plugin/core/session/manager.ts Stamp session.meta.closedAt on close for init-time orphan heuristics.
apps/memos-local-plugin/tests/unit/pipeline/memory-core.test.ts Add tests for orphan episode handling and episode deletion behavior.
apps/memos-local-plugin/adapters/hermes/memos_provider/bridge_client.py Add TCP transport mode + reconnection; keep stdio fallback; PID-file coordination hooks.
apps/memos-local-plugin/adapters/hermes/memos_provider/daemon_manager.py Add PID-file helpers and tracked shutdown for bridge subprocesses.
apps/memos-local-plugin/adapters/hermes/memos_provider/init.py TCP-first bridge connection; remove adapter episode pre-open; strip Hermes auto-skill eval prompt block.
apps/memos-local-plugin/package-lock.json Version bump + lockfile updates.
Files not reviewed (1)
  • apps/memos-local-plugin/package-lock.json: Language not supported
Comments suppressed due to low confidence (1)

apps/memos-local-plugin/adapters/hermes/memos_provider/init.py:537

  • initialize() no longer opens an episode, but _episode_id is still used in turn.end and episode.close (and never updated from turn.start). This will send an empty episodeId (required by TurnResultDTO) and can cause turns to fail to persist and sessions to be closed via session.close with the episode still open (leading to abandon instead of finalize). Capture the routed episodeId from the turn.start response (e.g. resp['query']['episodeId']) and set self._episode_id (and ideally self._session_id too) before calling _turn_end / episode.close; also guard episode.close when no episode was started.
    def _turn_start(self, query: str, *, session_id: str = "") -> str:
        assert self._bridge is not None
        resp = self._bridge.request(
            "turn.start",
            {
                "agent": "hermes",
                "sessionId": session_id or self._session_id,
                "userText": query,
                "ts": int(time.time() * 1000),
            },
        )
        context = (resp or {}).get("injectedContext") or ""
        if not context:
            return ""
        return f"## Recalled Memories\n{context}"

    # Hermes injects a structured auto-skill evaluation prompt at task end:
    #   "Review the conversation above and consider whether a skill should
    #    be saved or updated. Work in this order… SURVEY … THINK CLASS-FIRST …"
    # Capturing this system-level scaffolding as conversation content pollutes
    # memory search, task summaries, and downstream skill generation.
    _AUTO_SKILL_EVAL_RE = re.compile(
        r"^Review the conversation above and consider whether a "
        r"skill should be saved or updated\.",
        re.MULTILINE,
    )

    def _turn_end(
        self,
        user_content: str,
        assistant_content: str,
        tool_calls: list[dict[str, Any]],
        ts_ms: int,
    ) -> None:
        if not self._bridge:
            return
        # Strip Hermes auto-skill evaluation blocks from the assistant
        # response. When the header phrase is present the entire remainder
        # of the message is system-generated scaffolding, not user content.
        m = self._AUTO_SKILL_EVAL_RE.search(assistant_content)
        if m:
            assistant_content = assistant_content[: m.start()].strip()
        if not assistant_content.strip() and not user_content.strip():
            return
        self._bridge.request(
            "turn.end",
            {
                "agent": "hermes",
                "sessionId": self._session_id,
                "episodeId": self._episode_id,
                "agentText": assistant_content,
                "userText": user_content,
                "toolCalls": tool_calls,
                "ts": ts_ms,
            },

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/memos-local-plugin/adapters/hermes/memos_provider/daemon_manager.py Outdated
Comment thread apps/memos-local-plugin/bridge/methods.ts
Comment on lines 69 to 180
@@ -93,30 +129,55 @@ async function main(): Promise<void> {
`bridge: viewer failed to start: ${(err as Error).message}\n`,
);
}
let shuttingDown = false;

const shutdown = async (sig: string) => {
if (shuttingDown) return;
shuttingDown = true;
process.stderr.write(`bridge: received ${sig}, shutting down\n`);
try {
if (tcpServer) await tcpServer.close();
} catch {
/* best-effort */
}
try {
if (viewer) await viewer.close();
} catch {
/* best-effort */
}
await waitForShutdown(core, stdio);
if (stdio) {
await waitForShutdown(core, stdio);
} else {
try {
await core.shutdown();
} catch {
/* swallow */
}
}
process.exit(0);
};

process.on("SIGINT", () => void shutdown("SIGINT"));
process.on("SIGTERM", () => void shutdown("SIGTERM"));

// Keep the process alive until stdin ends.
await stdio.done;
try {
if (viewer) await viewer.close();
} catch {
/* best-effort */
// In daemon mode, keep alive until TCP server stops (stdin is /dev/null).
// In stdio mode, run until the calling process closes stdin.
if (args.daemon && tcpServer) {
await tcpServer.done;
await shutdown("daemon_done");
} else if (stdio) {
await stdio.done;
try {
if (viewer) await viewer.close();
} catch {
/* best-effort */
}
await core.shutdown();
process.exit(0);
} else {
// Daemon mode without TCP — wait forever (kept alive by event loop).
await new Promise(() => {});
}
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged. TCP startup failure in daemon mode should indeed be fatal when --tcp is explicitly requested. This is a design decision best handled in a separate PR alongside other daemon-mode robustness improvements.

Comment on lines +352 to +367
const openEpisodes = handle.repos.episodes.list({ status: "open", limit: 500 });
// Batch-fetch sessions to avoid N+1 lookups per episode.
const sessionIds = new Set(openEpisodes.map((ep) => ep.sessionId));
const sessionById = new Map<string, ReturnType<typeof handle.repos.sessions.getById>>();
for (const sid of sessionIds) {
sessionById.set(sid, handle.repos.sessions.getById(sid));
}
// Only treat an open episode as an orphan if its session has been
// explicitly closed (meta.closedAt is set) or no longer exists.
// Otherwise the session might reconnect — leave it alone.
const orphans = openEpisodes.filter((ep) => {
const session = sessionById.get(ep.sessionId);
if (!session) return true; // session row gone — orphan
if (session.meta?.closedAt != null) return true; // explicitly closed — orphan
return false; // session exists and not closed — skip, might reconnect
});
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate of #3159926566 — same orphan episode hydration concern from a different code location. Same conclusion: needs a dedicated architectural PR.

Comment on lines +706 to +712
async function deleteEpisodes(ids: readonly EpisodeId[]): Promise<{ deleted: number }> {
ensureLive();
let deleted = 0;
for (const id of ids) {
if (deleteClosedEpisode(id)) deleted++;
}
return { deleted };
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged. deleteEpisodes() should be atomic (pre-validate or wrap in transaction). Tracked for a follow-up PR on bulk operation safety.

Comment on lines +150 to +162
let buffer = "";
sock.setEncoding("utf8");

sock.on("data", (chunk: string) => {
buffer += chunk;
let nl = buffer.indexOf("\n");
while (nl >= 0) {
const line = buffer.slice(0, nl);
buffer = buffer.slice(nl + 1);
void handleLine(sock, line);
nl = buffer.indexOf("\n");
}
});
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged. Adding a max buffer/line length to the TCP parser is a sensible DoS hardening. Tracked for the follow-up TCP hardening PR.

Comment thread apps/memos-local-plugin/adapters/hermes/memos_provider/__init__.py
Comment on lines 93 to +95
closeEpisode(episodeId: EpisodeId): Promise<void>;
deleteEpisode(episodeId: EpisodeId): Promise<{ deleted: boolean }>;
deleteEpisodes(ids: readonly EpisodeId[]): Promise<{ deleted: number }>;
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in f191b15: added deleteEpisode and deleteEpisodes to the test stub in tests/unit/bridge/methods.test.ts.

…o-skill prompts

Three fixes targeting noise reduction and correctness in the Hermes
adapter pipeline, plus two Copilot review follow-ups:

1. Remove adapter-initiated episode pre-creation (__init__.py:165).
   The orchestrator already creates episodes on first real turn via
   openEpisodeIfNeeded(), so the pre-created empty episode was always
   abandoned as a 0-turn orphan.

2. Finalize stale episodes with content instead of abandoning them.
   autoFinalizeStaleTasks() now checks traceIds: episodes with at least
   one trace are finalized (triggering reflection + scoring), while
   truly empty episodes are still abandoned.

3. Strip Hermes auto-skill evaluation prompts in _turn_end().
   Hermes injects a structured "review the conversation and decide
   whether to save a skill" prompt at task end. The adapter now
   detects the distinctive header phrase and truncates the
   system-generated scaffolding.

4. Add macOS ps(1) fallback for _is_bridge_process() (daemon_manager.py).
   /proc is not available on macOS, so PID validation always returned
   False. Fall back to `ps -p <pid> -o command=` when /proc is missing.

5. Add episode.delete / episode.delete_bulk JSON-RPC methods.
   HTTP routes had DELETE endpoints but the bridge lacked corresponding
   RPC methods, creating API surface inconsistency.
@Starfie1d1272 Starfie1d1272 force-pushed the fix/hydrogen-noise-and-idle-timeout branch from ebc5788 to f191b15 Compare April 29, 2026 13:05
@Starfie1d1272
Copy link
Copy Markdown
Author

Copilot Review — Resolution Summary

All 13 Copilot comments have been reviewed and addressed (full replies in each thread).

Fixed (6)

# Issue Fix
5 macOS /proc not available Added ps -p <pid> -o command= fallback
6 JSON-RPC missing delete methods Added episode.delete + episode.delete_bulk with validation
7 PermissionError treated as dead PID Split into own handler — PermissionError = alive
8 ps fallback matches too broadly Tightened to bridge.cts + returncode check
9 delete_bulk no element validation Per-element non-empty-string check
15 Test stub missing new methods Added deleteEpisode/deleteEpisodes to stub

False positives (3)

# Issue Why
2 _episode_id empty → invalid turn.end Orchestrator resolves via openEpisodeBySession cache (line 655)
3 episode.close with empty id Wrapped in suppress(Exception), pipeline handles finalize
14 PR description mismatch Description updated — filter is in Hermes adapter as intended

Deferred — separate PRs (4)

# Issue Reason
1 TCP broadcast backpressure Real but low-impact (few clients); needs TCP hardening PR
10 TCP startup failure not fatal in daemon mode Design decision; needs dedicated daemon PR
12 deleteEpisodes not atomic Needs transaction wrapping; bulk safety PR
13 TCP buffer no size limit DoS hardening; part of TCP hardening PR

From prior PR #1546 (now closed) — requires architectural work

# Issue
4, 11 Orphan episode hydration on restart (duplicate)

🤖 Generated with Claude Code

…_content for auto-skill eval

- abandon(): when rTask is set, mark episode as finalized instead of
  abandoned, since the reward pipeline already completed. Log at info
  level and skip episode.abandoned event.
- _turn_end(): apply _AUTO_SKILL_EVAL_RE to user_content too, so the
  review prompt sent as user_message to the fork agent is stripped.
… turn.start

- reward.ts: stamp closeReason="finalized" when async scoring completes,
  so episodes abandoned before scoring (rTask was NULL at closeSession)
  get corrected to "finalized" once the reward pipeline finishes.
- memos_provider/__init__.py: check query against _AUTO_SKILL_EVAL_RE
  in _run() before _turn_start(), so review prompts never create traces.
- memory-core.ts: add TODO for orphan close bypassing capture+reward chain.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants