Skip to content

Commit 188922c

Browse files
NikitasT2003claude
andcommitted
feat: initial thesis-agent scaffold
Local, sandboxed thesis-writing agent built on deepagents + Karpathy LLM Wiki pattern. Architecture - `thesis` CLI (Typer): setup, init, status, ingest, curate, chat, write, lint, style - Per-source wiki pages as the retrieval layer (no RAG, no embeddings) - Two-tier memory: SqliteSaver (threads) + SqliteStore (cross-thread), both on plain SQLite files under data/ - Per-subagent write sandbox (researcher / wiki-curator / drafter), no shell/network/code-exec tools exposed to the agent - Providers: Anthropic + OpenRouter, switchable via THESIS_PROVIDER First-run UX - `thesis setup` wizard handles retries, empty input, Ctrl-C, bad key prefix, validation failure. Non-interactive mode for CI via --non-interactive --provider --api-key Tests - 31/31 passing: ingest idempotency, sandbox scope enforcement, 11 wizard branches via mocked questionary, CLI smoke Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0 parents  commit 188922c

37 files changed

Lines changed: 3208 additions & 0 deletions

File tree

.env.example

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copy this file to .env and fill in your keys.
2+
# `thesis setup` will do this for you interactively.
3+
4+
# Which LLM provider to use: "anthropic" (default) or "openrouter".
5+
THESIS_PROVIDER=anthropic
6+
7+
# --- Anthropic (THESIS_PROVIDER=anthropic) -----------------------------------
8+
# Get a key at https://console.anthropic.com/
9+
ANTHROPIC_API_KEY=sk-ant-your-key-here
10+
11+
# --- OpenRouter (THESIS_PROVIDER=openrouter) ---------------------------------
12+
# Get a key at https://openrouter.ai/keys
13+
# Browse models at https://openrouter.ai/models
14+
# OPENROUTER_API_KEY=sk-or-v1-your-key-here
15+
# OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
16+
# OPENROUTER_SITE_URL=https://github.com/your-org/thesis-agent # optional, for attribution
17+
# OPENROUTER_SITE_NAME=thesis-agent # optional
18+
19+
# --- Model overrides (both providers) ----------------------------------------
20+
# Anthropic-format IDs (e.g. "anthropic:claude-sonnet-4-6") when provider is anthropic.
21+
# OpenRouter-format IDs (e.g. "anthropic/claude-sonnet-4-5" or "openai/gpt-4o")
22+
# when provider is openrouter.
23+
# THESIS_MODEL_DRAFTER=
24+
# THESIS_MODEL_CURATOR=
25+
# THESIS_MODEL_RESEARCHER=

.github/workflows/ci.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: ci
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
name: ${{ matrix.os }} / python ${{ matrix.python }}
12+
runs-on: ${{ matrix.os }}
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
os: [ubuntu-latest, macos-latest]
17+
python: ["3.11", "3.12"]
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- name: Install uv
22+
uses: astral-sh/setup-uv@v3
23+
with:
24+
enable-cache: true
25+
26+
- name: Set up Python
27+
run: uv python install ${{ matrix.python }}
28+
29+
- name: Install deps
30+
run: uv sync --extra dev
31+
32+
- name: Ruff
33+
run: uv run ruff check src tests
34+
35+
- name: Pytest
36+
run: uv run pytest -q
37+
38+
- name: CLI smoke
39+
run: |
40+
uv run thesis --help
41+
uv run thesis --version

.gitignore

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Python
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
*.so
6+
.Python
7+
build/
8+
dist/
9+
*.egg-info/
10+
.eggs/
11+
.venv/
12+
venv/
13+
env/
14+
15+
# uv
16+
uv.lock
17+
18+
# Environment
19+
.env
20+
.env.local
21+
.env.*.local
22+
23+
# Agent runtime data (per-user, never commit)
24+
data/
25+
*.sqlite
26+
*.db
27+
*.db-journal
28+
.thread
29+
30+
# User workspace (when repo is cloned and used as the workspace)
31+
/research/raw/*
32+
!/research/raw/.gitkeep
33+
!/research/raw/urls.txt
34+
/research/wiki/*
35+
!/research/wiki/.gitkeep
36+
/style/samples/*
37+
!/style/samples/.gitkeep
38+
/style/STYLE.md
39+
/thesis/chapters/*
40+
!/thesis/chapters/.gitkeep
41+
/thesis/outline.md
42+
43+
# Testing
44+
.pytest_cache/
45+
.coverage
46+
htmlcov/
47+
.tox/
48+
49+
# IDE
50+
.vscode/
51+
.idea/
52+
*.swp
53+
.DS_Store
54+
Thumbs.db
55+
56+
# mypy / ruff cache
57+
.mypy_cache/
58+
.ruff_cache/

AGENTS.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# AGENTS.md — Thesis Agent Schema
2+
3+
This file is loaded into the main agent's system prompt on every run. It defines how to organise knowledge, write the thesis, and stay grounded. Follow it exactly.
4+
5+
---
6+
7+
## Three layers
8+
9+
1. **`research/raw/`** — immutable source material (PDFs, DOCX, EPUB, MD, URLs). Never edit these. The `thesis ingest` CLI command normalises each file to `research/raw/<filename>.md` (same stem as the original) and records it in `research/raw/_index.json` with `status: pending`.
10+
2. **`research/wiki/`** — LLM-compiled knowledge. **One wiki page per source file**, named `research/wiki/<filename>.md` (matching the raw file's stem). Plus `research/wiki/index.md` which groups pages by topic/tag.
11+
3. **This file (`AGENTS.md`)** — the schema that turns the raw layer into the wiki layer, and the wiki layer into grounded thesis prose.
12+
13+
The wiki is the index. **There is no vector search, no embeddings, no RAG.** To find information you navigate `research/wiki/index.md` → entity pages → drill into the raw file only if the wiki page is insufficient. Use `read_file`, `glob`, and `grep` — nothing else.
14+
15+
---
16+
17+
## Wiki page template
18+
19+
Every file under `research/wiki/<name>.md` (except `index.md`) must follow this template exactly. Use the raw source's stem as the filename.
20+
21+
```markdown
22+
---
23+
source: <raw_filename_with_ext>
24+
title: <best-effort title from the source; filename if unknown>
25+
authors: [<author1>, <author2>] # empty list if unknown
26+
date: <YYYY or YYYY-MM-DD if known; "unknown" otherwise>
27+
tags: [<topic1>, <topic2>, ...] # 3-8 short lowercase tags
28+
---
29+
30+
# <title>
31+
32+
## Summary
33+
3–5 sentences capturing the source's thesis / core contribution / relevance. Every sentence must be supported by the source. Inline-cite with `[src:<raw_filename>]`.
34+
35+
## Key claims
36+
- Claim 1. [src:<raw_filename>]
37+
- Claim 2. [src:<raw_filename>]
38+
- ...
39+
(5–15 bullet points. Each bullet is a discrete factual claim quotable in the thesis.)
40+
41+
## Methods / datasets
42+
- <method or dataset name>: <one-line description>. [src:<raw_filename>]
43+
(Omit this section if the source is not empirical.)
44+
45+
## Key terms
46+
- <term>: <one-line definition as used in this source>. [src:<raw_filename>]
47+
48+
## See also
49+
- [[<other_source_stem>]] — <one-line reason for the link>
50+
(Cross-references to other wiki pages. Add reciprocally on the other page.)
51+
52+
## Conflicts
53+
- ⚠ conflicts with [[<other_source_stem>]] on <claim topic>
54+
(Flag only — do NOT quote both sides, do NOT merge. User resolves manually.)
55+
```
56+
57+
---
58+
59+
## Citation format
60+
61+
Any factual claim — in a wiki page, in a thesis chapter, in chat — must carry `[src:<raw_filename>]` where `<raw_filename>` is the name (with extension) of the file in `research/raw/`. Example: `[src:attention-is-all-you-need.pdf]`.
62+
63+
Never invent a citation. If no source supports a claim, reply: **"no grounding in indexed sources — add material or remove claim."** Do not use pretraining knowledge. Do not use web search (you have no web tools anyway).
64+
65+
---
66+
67+
## Workflows
68+
69+
### Ingest (user-initiated via `thesis ingest`)
70+
1. The CLI runs `thesis_agent.ingest` — pure Python, no agent involvement. You do not run this.
71+
2. After it finishes, new entries appear in `research/raw/_index.json` with `status: pending`.
72+
73+
### Curate (agent — triggered by `thesis curate` or user request)
74+
1. Read `research/raw/_index.json`. Collect entries where `status == "pending"`.
75+
2. For each pending entry, delegate to the `wiki-curator` subagent with the raw filename. The subagent:
76+
- Reads `research/raw/<name>.md`.
77+
- Produces `research/wiki/<name>.md` following the template above.
78+
- Updates `research/wiki/index.md` to list the new page under the right topic cluster.
79+
- Flips `status` to `curated` in `_index.json`.
80+
3. When all pending entries are processed, report a one-line summary: `curated N sources; M conflicts flagged`.
81+
82+
### Write (user-initiated via `thesis write <section>` or chat)
83+
1. Read `style/STYLE.md`. If missing, stop and tell the user to run `thesis style` first.
84+
2. Read `research/wiki/index.md`. Identify wiki pages relevant to the requested section.
85+
3. Read those wiki pages.
86+
4. For any claim you plan to make, verify it appears in a wiki page's "Key claims" or can be read out of the raw file. Drill into `research/raw/<file>.md` only when the wiki page is insufficient.
87+
5. Delegate drafting to the `drafter` subagent. The drafter writes to `thesis/chapters/<NN>.md` (matching the outline numbering in `thesis/outline.md`).
88+
6. Every paragraph must contain at least one `[src:...]` citation. Paragraphs with no citable claim (transitions, meta-prose) are allowed but must not make factual statements.
89+
90+
### Lint (agent — triggered by `thesis lint` or user request)
91+
1. Read the target chapter file.
92+
2. For every `[src:<name>]` citation, verify `research/raw/<name>` exists and `research/wiki/<name>.md` exists with `status: curated`.
93+
3. Flag paragraphs that make factual claims without any citation.
94+
4. Output a short report: file, line, issue, suggested fix. Do not auto-edit unless asked.
95+
96+
### Style learning (agent — triggered by `thesis style`)
97+
1. Read every file in `style/samples/`.
98+
2. Produce/update `style/STYLE.md` with sections: Voice, Sentence rhythm, Lexicon (jargon density, hedging words, favoured transitions), POV and tense, Citation placement habits, Structural patterns (how the user opens/closes sections).
99+
3. Be specific and prescriptive (e.g., "average sentence length 22–28 words; avoid sentences under 10 words except in conclusions"). The drafter will follow this verbatim.
100+
101+
---
102+
103+
## Write scopes (enforced by the sandbox middleware — do not attempt violations)
104+
105+
- `researcher` subagent: **read-only**. Cannot write anywhere.
106+
- `wiki-curator` subagent: may write only to `research/wiki/**` and update `research/raw/_index.json`.
107+
- `drafter` subagent: may write only to `thesis/**`.
108+
- The main agent: may write to `research/wiki/index.md`, `style/STYLE.md`, and the user's explicit targets. Never write to `research/raw/` (sources are immutable). Never write to `data/` (reserved for memory databases).
109+
- No tool can reach outside the project workspace.
110+
111+
---
112+
113+
## Hard rules (non-negotiable)
114+
115+
1. **Every factual claim** in `research/wiki/**` and `thesis/**` has a `[src:...]` citation.
116+
2. **Never fabricate** sources, quotes, author names, dates, or statistics.
117+
3. **Never write** to `research/raw/` or `data/`.
118+
4. **Always read** `style/STYLE.md` before any `thesis/**` write.
119+
5. **Contradictions are flagged, not resolved.** Add `⚠ conflicts with [[other]]` on both pages; move on.
120+
6. When the wiki has no answer, say so. Do not fill the gap from memory.

CONTRIBUTING.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Contributing
2+
3+
## Dev setup
4+
5+
```bash
6+
git clone https://github.com/your-org/thesis-agent.git
7+
cd thesis-agent
8+
uv sync --extra dev
9+
uv run pytest -q
10+
uv run ruff check src tests
11+
```
12+
13+
## Project layout
14+
15+
```
16+
src/thesis_agent/
17+
cli.py Typer CLI (thesis setup, init, ingest, ...)
18+
agent.py build_agent(): wires deepagents with our config
19+
subagents.py researcher / wiki-curator / drafter definitions
20+
memory.py SqliteSaver + SqliteStore context managers
21+
sandbox.py path-scope enforcement for agent writes
22+
config.py workspace paths + env + model defaults
23+
ingest/ deterministic source extraction (no LLM)
24+
skills/ 5 SKILL.md files (YAML frontmatter + markdown)
25+
AGENTS.md operating schema loaded into every agent run
26+
examples/ sample workspace committed for first-run demo
27+
tests/ ingest + sandbox + CLI smoke tests
28+
```
29+
30+
## Adding a skill
31+
32+
1. `mkdir skills/your-skill`
33+
2. Create `skills/your-skill/SKILL.md`:
34+
35+
```markdown
36+
---
37+
name: your-skill
38+
description: >
39+
When to use this skill. Put ALL trigger conditions here — the body is
40+
only loaded after the skill has been triggered. Be explicit about the
41+
phrases that should activate it.
42+
---
43+
44+
# your-skill
45+
46+
Step-by-step guidance here. What to read, what to write, what to avoid.
47+
```
48+
49+
3. The skill is picked up automatically on the next agent run.
50+
51+
## Swapping the model
52+
53+
Set env vars in `.env`:
54+
55+
```
56+
THESIS_MODEL_DRAFTER=anthropic:claude-sonnet-4-6
57+
THESIS_MODEL_CURATOR=anthropic:claude-sonnet-4-6
58+
THESIS_MODEL_RESEARCHER=anthropic:claude-haiku-4-5-20251001
59+
```
60+
61+
## Adjusting the sandbox
62+
63+
`src/thesis_agent/sandbox.py` defines per-scope allow/deny globs. Tests in
64+
`tests/test_sandbox.py` exercise the policy — run them if you change anything
65+
there.
66+
67+
Two hard invariants that should not be relaxed:
68+
69+
1. `tools=[]` in `build_agent(...)` — no shell, no network, no code exec.
70+
2. `research/raw/**` and `data/**` are always on the global deny list.
71+
72+
If you need the agent to do something it's currently blocked from, consider
73+
whether a deterministic Python helper (like `ingest/`) would be safer than
74+
giving the agent a new tool.
75+
76+
## Tests
77+
78+
- `tests/test_ingest.py` — extractor + manifest idempotency.
79+
- `tests/test_sandbox.py` — path traversal, per-scope writes.
80+
- `tests/test_cli_smoke.py``thesis --help`, `thesis init`, `thesis status`.
81+
82+
Add tests for any new code path before sending a PR. LLM-calling tests
83+
should be avoided in CI (no Anthropic key available); exercise those
84+
locally.
85+
86+
## Filing issues
87+
88+
- Include the full command you ran and the error.
89+
- Include your Python and `uv` versions (`uv run python --version`, `uv --version`).
90+
- Do **not** paste your API key or source material — reproduce with the
91+
committed examples where possible.

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 thesis-agent contributors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

0 commit comments

Comments
 (0)