Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

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

Aperta
#319 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 2 giorni

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
3/5
Tempo stimato
1-2 giorni
Idoneità per principianti
45/100
Tipo di issue
Bug
Chiarezza
Abbastanza chiara
Stato di attività
Attiva
Stack tecnologico
python
Ambito
devtools

Direzione di ricerca

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.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

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].
Lingua principale
Python
Stelle
18.2k
Fork
1.7k
Merge medio
4g 1h
PR unite (30g)
10

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di microsoft/SkillOpt

Tutte le issue di microsoft/SkillOpt

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.