Skip to content

Commit a79bd7e

Browse files
NikitasT2003claude
andcommitted
fix(skills+memory): agent wouldn't boot — two root causes
Reported symptom: `thesis style` printed four spec-compliance warnings from deepagents then died with: Invalid YAML in ...wiki-curator/SKILL.md: mapping values are not allowed here, line 2, column 301: ... status: pending, update ... agent error: generator didn't stop after throw() Root cause 1 (real blocker): YAML frontmatter The description field in wiki-curator/SKILL.md ran on one long line and contained unquoted phrases like `status: pending` and `status: curated`. PyYAML (via deepagents' skill loader) parsed `status` as a new mapping key, blew up on the malformed document, and propagated the error out of `build_agent`. Fix: every SKILL.md frontmatter now uses a folded block scalar (`description: >`) so colons, backticks, and quotes inside the text carry no special meaning. Five files touched, content unchanged. Root cause 2 (crash on error propagation): memory.make_store `make_store` wrapped both the SqliteStore import AND the yield in one `try/except Exception`. When the first exception from the agent propagated back into the yield, the except branch caught it and tried to yield InMemoryStore() as a fallback — but the generator had already yielded once. Python's contextmanager protocol rejects this with "generator didn't stop after throw()", masking the real error. Fix: split the import attempt (pure function `_try_import_sqlite_store`) from the context manager. Only one yield path per invocation, and no exception handling around the yield point. Tests (5 new, 267 total passing) - Every SKILL.md now has a regression test via real `yaml.safe_load` (my earlier ad-hoc frontmatter parser did not catch the bug — it tolerated the unquoted colon). - Existing frontmatter-key allowlist test migrated to yaml.safe_load too, for consistency. Smoke test: `thesis style` now boots past skill-load + memory setup and reaches the provider API (fails only on the fake key, as expected). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3509903 commit a79bd7e

7 files changed

Lines changed: 93 additions & 23 deletions

File tree

skills/citation-linter/SKILL.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
---
22
name: citation-linter
3-
description: Use this skill when the user asks to lint, check, verify, audit, or review citations in thesis chapters. Scans a chapter (or all chapters) for `[src:<filename>]` markers, verifies each target exists under `research/raw/`, flags paragraphs making factual claims without any citation, and reports file+line issues in a short actionable table. Does not auto-edit unless explicitly asked. Triggers "lint citations", "check citations", "audit grounding", "verify sources", "review chapter".
3+
description: >
4+
Use this skill when the user asks to lint, check, verify, audit, or review
5+
citations in thesis chapters. Scans a chapter (or all chapters) for
6+
`[src:<filename>]` markers, verifies each target exists under `research/raw/`,
7+
flags paragraphs making factual claims without any citation, and reports file
8+
and line issues in a short actionable table. Does not auto-edit unless
9+
explicitly asked. Triggers "lint citations", "check citations",
10+
"audit grounding", "verify sources", "review chapter".
411
---
512

613
# citation-linter

skills/ingest-sources/SKILL.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
---
22
name: ingest-sources
3-
description: Use this skill when the user asks to ingest, add, index, or import new source material (PDFs, DOCX, EPUB, Markdown, URLs) into the thesis research library, or after they have dropped new files into `research/raw/`. Explains how to run the deterministic ingest CLI and follow up by triggering curation. Triggers include phrases like "ingest", "add source", "index these", "import sources", "update knowledge base", "I just dropped files in raw".
3+
description: >
4+
Use this skill when the user asks to ingest, add, index, or import new source
5+
material (PDFs, DOCX, EPUB, Markdown, URLs) into the thesis research library,
6+
or after they have dropped new files into `research/raw/`. Explains how to run
7+
the deterministic ingest CLI and follow up by triggering curation. Triggers
8+
include phrases like "ingest", "add source", "index these", "import sources",
9+
"update knowledge base", "I just dropped files in raw".
410
---
511

612
# ingest-sources

skills/style-learner/SKILL.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
---
22
name: style-learner
3-
description: Use this skill when the user asks you to learn their writing style, build/update the style guide, or when they have added new samples to `style/samples/`. Reads every file in `style/samples/`, extracts voice and mechanical patterns (sentence length, hedging, jargon density, POV, citation placement), and writes a prescriptive `style/STYLE.md` that the drafter will follow. Triggers "learn my style", "update style guide", "analyse my writing", "I added samples", "compile style".
3+
description: >
4+
Use this skill when the user asks you to learn their writing style,
5+
build/update the style guide, or when they have added new samples to
6+
`style/samples/`. Reads every file in `style/samples/`, extracts voice and
7+
mechanical patterns (sentence length, hedging, jargon density, POV, citation
8+
placement), and writes a prescriptive `style/STYLE.md` that the drafter will
9+
follow. Triggers "learn my style", "update style guide", "analyse my writing",
10+
"I added samples", "compile style".
411
---
512

613
# style-learner

skills/thesis-writer/SKILL.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
---
22
name: thesis-writer
3-
description: Use this skill when the user asks you to draft, write, or expand a thesis section, chapter, paragraph, or the whole thesis. Enforces the grounding rule (every factual claim cites an indexed source), loads the user's writing style from `style/STYLE.md`, navigates the wiki for supporting material, and writes to `thesis/chapters/<NN>.md` matching `thesis/outline.md` numbering. Triggers "draft", "write section", "expand outline", "write chapter", "flesh out 2.1", "continue writing".
3+
description: >
4+
Use this skill when the user asks you to draft, write, or expand a thesis
5+
section, chapter, paragraph, or the whole thesis. Enforces the grounding
6+
rule (every factual claim cites an indexed source), loads the user's writing
7+
style from `style/STYLE.md`, navigates the wiki for supporting material, and
8+
writes to `thesis/chapters/<NN>.md` matching `thesis/outline.md` numbering.
9+
Triggers "draft", "write section", "expand outline", "write chapter",
10+
"flesh out 2.1", "continue writing".
411
---
512

613
# thesis-writer

skills/wiki-curator/SKILL.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
---
22
name: wiki-curator
3-
description: Use this skill to build or update the research wiki from newly-ingested raw sources following the Karpathy LLM Wiki pattern, one page per source file. Read the template from AGENTS.md and produce `research/wiki/<source_stem>.md` for every entry in `research/raw/_index.json` with `status: pending`, update `research/wiki/index.md`, flag contradictions (flag only — no merging), then flip status to `curated`. Triggers include "curate", "build wiki", "compile wiki", "update the wiki", "after ingest".
3+
description: >
4+
Use this skill to build or update the research wiki from newly-ingested raw
5+
sources following the Karpathy LLM Wiki pattern, one page per source file.
6+
Read the template from AGENTS.md and produce `research/wiki/<source_stem>.md`
7+
for every entry in `research/raw/_index.json` with status "pending", update
8+
`research/wiki/index.md`, flag contradictions (flag only, no merging), then
9+
flip status to "curated". Triggers include "curate", "build wiki",
10+
"compile wiki", "update the wiki", "after ingest".
411
---
512

613
# wiki-curator

src/thesis_agent/memory.py

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,32 +39,47 @@ def make_checkpointer(paths: Paths) -> Iterator[Any]:
3939
conn.close()
4040

4141

42+
def _try_import_sqlite_store():
43+
"""Return SqliteStore class if the installed langgraph ships one, else None.
44+
45+
Isolated from the context manager so any import/attribute failure can't
46+
get mixed up with exceptions thrown into the generator at yield time —
47+
that mix produces 'generator didn't stop after throw()'.
48+
"""
49+
try:
50+
from langgraph.store.sqlite import SqliteStore # type: ignore
51+
return SqliteStore
52+
except Exception:
53+
return None
54+
55+
4256
@contextmanager
4357
def make_store(paths: Paths) -> Iterator[Any]:
4458
"""Yield a LangGraph Store for long-term cross-thread memory.
4559
4660
Prefer SqliteStore when the installed langgraph ships it; fall back to
47-
InMemoryStore so the agent still works on older installs (long-term memory
48-
just won't persist between processes until the user upgrades).
61+
InMemoryStore so the agent still works on older installs (long-term
62+
memory just won't persist between processes until the user upgrades).
4963
"""
50-
try:
51-
from langgraph.store.sqlite import SqliteStore # type: ignore
64+
SqliteStoreCls = _try_import_sqlite_store()
5265

53-
_ensure_parent(paths.store_db)
54-
conn = sqlite3.connect(str(paths.store_db), check_same_thread=False)
55-
try:
56-
store = SqliteStore(conn)
57-
try:
58-
store.setup()
59-
except Exception:
60-
pass
61-
yield store
62-
finally:
63-
conn.close()
64-
except Exception:
66+
if SqliteStoreCls is None:
6567
from langgraph.store.memory import InMemoryStore
6668

6769
yield InMemoryStore()
70+
return
71+
72+
_ensure_parent(paths.store_db)
73+
conn = sqlite3.connect(str(paths.store_db), check_same_thread=False)
74+
try:
75+
store = SqliteStoreCls(conn)
76+
try:
77+
store.setup()
78+
except Exception:
79+
pass
80+
yield store
81+
finally:
82+
conn.close()
6883

6984

7085
@contextmanager

tests/test_schema.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,23 @@ def _file(self, skill: str) -> Path:
6868
def test_file_exists(self, skill: str):
6969
assert self._file(skill).exists()
7070

71+
def test_frontmatter_parses_with_real_yaml(self, skill: str):
72+
"""Regression: an earlier description contained `status: pending`
73+
which PyYAML parsed as a new mapping key, crashing the agent at
74+
boot. Every SKILL.md must parse cleanly with yaml.safe_load."""
75+
import yaml
76+
77+
text = self._file(skill).read_text(encoding="utf-8")
78+
m = FRONTMATTER_RE.match(text)
79+
assert m, f"{skill}: no YAML frontmatter"
80+
data = yaml.safe_load(m.group(1))
81+
assert isinstance(data, dict), f"{skill}: frontmatter did not parse to a mapping"
82+
assert data.get("name") == skill
83+
assert "description" in data
84+
assert isinstance(data["description"], str)
85+
# Description must be substantive enough to be a useful trigger.
86+
assert len(data["description"]) >= 80
87+
7188
def test_has_valid_frontmatter(self, skill: str):
7289
text = self._file(skill).read_text(encoding="utf-8")
7390
fm = _parse_frontmatter(text)
@@ -79,9 +96,13 @@ def test_has_valid_frontmatter(self, skill: str):
7996

8097
def test_frontmatter_only_allowed_keys(self, skill: str):
8198
"""deepagents spec: only name/description/license/compatibility/allowed-tools/metadata."""
99+
import yaml
100+
82101
allowed = {"name", "description", "license", "compatibility", "allowed-tools", "metadata"}
83-
fm = _parse_frontmatter(self._file(skill).read_text(encoding="utf-8"))
84-
unexpected = set(fm) - allowed
102+
m = FRONTMATTER_RE.match(self._file(skill).read_text(encoding="utf-8"))
103+
assert m
104+
data = yaml.safe_load(m.group(1))
105+
unexpected = set(data) - allowed
85106
assert not unexpected, f"{skill}: unexpected frontmatter keys: {unexpected}"
86107

87108
def test_body_starts_after_frontmatter(self, skill: str):

0 commit comments

Comments
 (0)