Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

harvest: single-turn headless sessions from other local tooling are harvested as user tasks and fill the session cap

Đang mở
#319 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 2 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
3/5
Thời gian dự kiến
1-2 ngày
Mức phù hợp với người mới
45/100
Loại issue
Lỗi
Độ rõ ràng
Khá rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
python
Lĩnh vực
devtools

Hướng nghiên cứu

Start with harvest() in skillopt_sleep/harvest.py, where transcripts are sorted newest first and cut off at the session cap (lines 403-433), and with _is_agent_session() and SKILLOPT_SLEEP_AGENT_MARKERS (around lines 186-195). Run the synthetic repro in the issue with and without the env var and check the test suite for harvest. Done means the documentation covers the marker variables, and the cap reports how many candidates were skipped as machine-generated; the default-exclusion question needs a maintainer decision first.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

Version examined
  • microsoft/SkillOpt main @ 343db229dbd5ddaf9df6b1d5540d8bcdb2604d5c (2026-10-06), package skillopt_sleep.
  • Claude Code plugin skillopt-sleep 0.1.0 from the same commit.
  • Windows 11 Pro (build 26300), Python 3.12.13, plugin runner started from Git Bash 5.3.
  • Transcripts written by the Claude Code desktop app (Code tab), version 2.1.271.
Observed behavior

Local tooling outside SkillOpt can run Claude Code headlessly in the same project folder, for example to summarize the previous session every hour. Every such run leaves a single-turn transcript: one machine-written user prompt and one answer.

In one real project folder of the desktop app, 274 of 324 transcripts were of this kind at the first measurement, and 298 of 348 a few hours later: one more arrives every hour. Their only user prompt starts with "Below is a conversation log from a Claude Code coding session. Create a summary …". We counted them with the engine's own digest_transcript(), _is_headless_replay() and _is_agent_session(). We did not identify which tool produces them.

All 348 transcripts in that folder, the headless summary sessions included, carry entrypoint: "claude-desktop" and isSidechain: false; we read only these two fields. In this environment the entrypoint field therefore cannot tell these headless runs apart from interactive sessions.

None of these sessions is filtered out:

  • _is_headless_replay() (skillopt_sleep/harvest.py, lines 206-236) only catches single-turn sessions that match SkillOpt's own replay markers, or that last under 3 s with a prompt under 200 characters.
  • _is_agent_session() only matches the built-in markers (claude-mem and the plugin's own command body, lines 186-195).

Because harvest() sorts all transcripts newest first and stops at the session cap (lines 403-433, --max-sessions), frequent summary sessions crowd real interactive sessions out, and the miner turns the summary prompts into "recurring tasks".

The extension point SKILLOPT_SLEEP_AGENT_MARKERS (line 193) solves it, as verified below. But no .md file in the repository mentions it at this commit: we searched all .md files and found 0 occurrences.

Expected behavior
  • Document SKILLOPT_SLEEP_AGENT_MARKERS, and the SKILLOPT_SLEEP_NEG_FEEDBACK / SKILLOPT_SLEEP_POS_FEEDBACK variables, in the plugin README and the harvest documentation, with an example for session-summary hooks.
  • Consider excluding by default, or at least flagging, single-turn sessions with no follow-up user turn whose prompt recurs with an identical prefix across many transcripts: that pattern is machine-generated rather than a user task.
  • When the cap is reached, report how many candidate sessions were skipped as machine-generated and how many as real, so the user can see that the cap was filled by noise.
  • Do not rely on entrypoint alone, as proposed in #62: in the desktop app the headless sessions above are recorded as "claude-desktop", like interactive ones.
Minimal synthetic reproduction

No model calls and no network. Run from the repository root, or set SKILLOPT_REPO to it. Run it twice: as is, then with SKILLOPT_SLEEP_AGENT_MARKERS="Below is a conversation log".

"""Synthetic repro: single-turn headless summary sessions are harvested as user tasks
and crowd real sessions out of the session cap (no model calls, no network).
Run once as is, then with SKILLOPT_SLEEP_AGENT_MARKERS="Below is a conversation log"."""
import json
import os
import sys
import tempfile

sys.path.insert(0, os.environ.get("SKILLOPT_REPO", "."))
from skillopt_sleep.harvest import harvest  # noqa: E402  (markers are read at import time)
from skillopt_sleep.mine import heuristic_mine  # noqa: E402

ROOT = tempfile.mkdtemp(prefix="skillopt-d20-")
PROJECT = os.path.join(ROOT, "proj")
TRANSCRIPTS = os.path.join(ROOT, "projects")
os.makedirs(PROJECT)

SUMMARY_PROMPT = ("Below is a conversation log from a coding session. Create a summary to help the next "
                  "session quickly understand the context.\n## Prioritize\n- decisions\n- open items\n\n"
                  "LOG:\nuser: please refactor module A\nassistant: done, tests pass\n")


def session(sid, turns, mtime):
    folder = os.path.join(TRANSCRIPTS, "proj")
    os.makedirs(folder, exist_ok=True)
    path = os.path.join(folder, sid + ".jsonl")
    with open(path, "w", encoding="utf-8") as f:
        for i, (role, text) in enumerate(turns):
            f.write(json.dumps({"type": role, "cwd": PROJECT, "sessionId": sid,
                                "timestamp": f"2026-01-01T10:{i:02d}:00Z",
                                "message": {"role": role, "content": text}}) + "\n")
    os.utime(path, (mtime, mtime))


# two real interactive sessions, older
session("real-1", [("user", "Fix the failing date parser test in module B."), ("assistant", "Fixed."),
                   ("user", "Thanks, now add a regression test."), ("assistant", "Added.")], 1_000_000)
session("real-2", [("user", "Add a --dry-run flag to the export command."), ("assistant", "Added.")], 1_000_100)
# three single-turn summary sessions written by a local hook via a headless CLI, newer
for n in range(3):
    session(f"summary-{n}", [("user", SUMMARY_PROMPT), ("assistant", "Summary: ...")], 2_000_000 + n)

print("SKILLOPT_SLEEP_AGENT_MARKERS =", repr(os.environ.get("SKILLOPT_SLEEP_AGENT_MARKERS", "")))
all_digests = harvest(TRANSCRIPTS, scope="invoked", invoked_project=PROJECT)
print("harvested (no cap):", sorted(d.session_id for d in all_digests))
capped = harvest(TRANSCRIPTS, scope="invoked", invoked_project=PROJECT, limit=3)
print("harvested (limit=3):", sorted(d.session_id for d in capped))
for t in heuristic_mine(capped):
    print(f"task from {t.source_sessions[0]}: intent starts {t.intent[:45]!r}")

Output at the commit above, run as is:

SKILLOPT_SLEEP_AGENT_MARKERS = ''
harvested (no cap): ['real-1', 'real-2', 'summary-0', 'summary-1', 'summary-2']
harvested (limit=3): ['summary-0', 'summary-1', 'summary-2']
task from summary-2: intent starts 'Below is a conversation log from a coding ses'
task from summary-1: intent starts 'Below is a conversation log from a coding ses'
task from summary-0: intent starts 'Below is a conversation log from a coding ses'

Output with SKILLOPT_SLEEP_AGENT_MARKERS="Below is a conversation log":

SKILLOPT_SLEEP_AGENT_MARKERS = 'Below is a conversation log'
harvested (no cap): ['real-1', 'real-2']
harvested (limit=3): ['real-1', 'real-2']
task from real-2: intent starts 'Add a --dry-run flag to the export command.'
task from real-1: intent starts 'Fix the failing date parser test in module B.'
Related
  • #62 (closed) describes the same class of problem, headless claude -p runs from scripts and hooks mined as user tasks, and proposes filtering on entrypoint == "sdk-cli".
    • At this commit harvest.py does not read entrypoint. The filter that shipped, _is_headless_replay(), only catches single-turn sessions matching SkillOpt's own replay markers or lasting under 3 s with a short prompt.
    • In the desktop app the filter proposed in #62 would not catch these sessions either, because they carry entrypoint: "claude-desktop".
  • #99 introduced _is_agent_session() and SKILLOPT_SLEEP_AGENT_MARKERS.
  • #286 / #287 (Codex) and #310 (engine replay markers) cover SkillOpt's own replay sessions, not sessions written by other local tooling.
Not verified
  • Which local tool writes the summary sessions, and why their transcripts carry entrypoint: "claude-desktop". We assume the value is inherited from the environment of the desktop session that launched them [INFERENCE].
  • The effect with real summaries through the full mine() path. The repro calls heuristic_mine() directly; mine() also runs dedup_tasks(), which may merge identical synthetic intents. Real summaries differ from each other because they embed different logs [INFERENCE].
Ngôn ngữ chính
Python
Star
18.2k
Fork
1.7k
Merge trung bình
4 ngày 1 giờ
Pull request đã merge (30 ngày)
10

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của microsoft/SkillOpt

Tất cả issue của microsoft/SkillOpt

Issue tương tự

Thêm issue về Python

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.