Status (2026-06-18): Design settled. See quick-fixes-plan.md for the phased implementation plan.
This document covers four related fixes: two bugs that have been reported, one small ergonomics feature, and one housekeeping improvement. They are grouped into a single document because each is small, self-contained, and can be shipped independently.
Harvey’s read_file built-in tool description explicitly
states:
PDF files (.pdf) are automatically extracted to plain text using poppler utilities — no manual conversion is needed.
This disclosure only reaches the model when tools are enabled
and the model reads tool descriptions before deciding how to
respond. Small models using prose tool calls, and any session with
--tools off, have no knowledge of Harvey’s PDF support. The
result: the model asks the user to convert a PDF to text before it can
help, even though Harvey would handle this automatically if asked via
read_file or /attach.
HARVEY.md is always injected as the system prompt. It
documents Harvey’s automatic write-proposal behavior (tagged code
blocks) and safe-mode rules, but says nothing about Harvey’s file
reading capabilities. There is no single source of truth for “what can
Harvey read without user intervention?”
Add a File reading capabilities section to
HARVEY.md. It lists all formats Harvey handles
transparently, with a single line per format. The section follows the
existing “Agent execution model” section, which already documents
automatic behaviors.
## File reading capabilities
When asked to read a file, Harvey handles these formats automatically:
- **Plain text, Markdown, source code** — returned as-is.
- **PDF (.pdf)** — text is extracted using the poppler utilities
(pdfinfo, pdftotext). No user conversion is needed. Use the optional
`pages` parameter to read a subset (e.g. `"1-10"` or `"5"`).
- **Images (.png, .jpg, .jpeg, .gif, .webp)** — injected directly when
the active model supports vision input.
Never ask the user to convert a PDF to text before reading it. Call
`read_file` with the `.pdf` path directly.The last sentence is a model instruction, not a description — it closes the loop by telling the model what to do differently.
HARVEY.md only. No code changes.builtin_tools.go (the tool description
already correct).pdf_extract.go,
helptext.go, or any test file.scanLlamafileModels() in llamafile.go
identifies binaries by suffix:
if !e.IsDir() && strings.HasSuffix(e.Name(), ".llamafile") {The llamafile project ships Windows binaries with two possible names:
- Llama-3.2-1B.llamafile.exe — the “universal” format that
works on all platforms after renaming .exe to
.llamafile on non-Windows. - Llama-3.2-1B.exe
— plain Windows executable distributed separately.
Neither matches .llamafile. Users on Windows see an
empty model picker even with valid models in ~/Models.
A second bug: llamafileModelName strips only
.llamafile, leaving .exe in the display
name:
return strings.TrimSuffix(name, ".llamafile")
// "Llama-3.2-1B.llamafile.exe" → "Llama-3.2-1B.llamafile.exe" (unchanged)scanLlamafileModels changes:
Match three patterns, in priority order: 1. .llamafile —
Linux/macOS standard form (unchanged). 2. .llamafile.exe —
Windows universal form. 3. .exe — Windows-only plain
executable. This pattern is only activated when the OS is Windows
(runtime.GOOS == "windows"), to avoid picking up unrelated
executables on other platforms.
llamafileModelName changes:
Strip suffixes in longest-first order so the double extension is
handled correctly before the single extension: 1. Strip
.llamafile.exe (if present). 2. Strip .exe (if
still present). 3. Strip .llamafile (if still present).
The helper strings.TrimSuffix already handles the “no-op
if suffix not present” case, so the chain is safe.
The .exe fallback scan is guarded by
runtime.GOOS == "windows". On Linux and macOS, only
.llamafile and .llamafile.exe are matched.
This prevents Harvey from accidentally treating non-llamafile binaries
as models on developer machines with mixed executable types in
~/Models.
--resume FlagThe most common workflow after reopening a terminal is to resume the last Harvey session. The current options are:
--continue PATH — requires finding and typing the
session file path.Both are unnecessary friction when the user simply wants to pick up exactly where they left off.
Add a --resume flag (no argument) to
cmd/harvey/main.go. When present, it resolves the most
recently modified .spmd file in
agents/sessions/ and assigns it to
cfg.ContinuePath. Harvey then enters the existing
ContinueFromFountain path without any interactive
prompt.
harvey --resume
If no .spmd files exist in
agents/sessions/, Harvey prints:
No sessions found in agents/sessions/ — starting fresh.
and continues to a clean session.
Implementation detail: --resume sets
cfg.ContinuePath before Run is called. Inside
Run, the pickSession call is skipped when
cfg.ContinuePath is already set (this logic already exists
for --continue). No changes to terminal.go are
needed.
A new helper mostRecentSession(sessDir string) string
returns the path of the .spmd file with the most recent
ModTime in sessDir, or "" if none
exist. It lives in sessions_files.go alongside the other
session file utilities.
--recordThe existing guard in terminal.go:333-338 prevents
re-recording a session that is being resumed.
--resume --record is safe: recording starts a new file
distinct from the resumed session.
bin/assay writes evaluation output to
testout/ inside the harvey/ source
repository:
harvey/testout/assay-20260618-143022/
report.md
results.json
testout/ is gitignored, but the artifacts are still
visible to file-tree tools (/file-tree,
read_dir). Language models reading the harvey source tree
interpret these as current test results, generating false alarms about
test failures and confusing the agent’s understanding of the project
state.
Change the default output directory to a workspace-level path:
$WORKSPACE/assay-results/assay-<timestamp>/
report.md
results.json
where $WORKSPACE is resolved using the same heuristic
Harvey uses: walk up from the current working directory to the nearest
directory containing agents/harvey.yaml.
If no workspace is found (e.g., assay is run outside a Harvey
workspace), fall back to assay-results/ in the current
working directory — same behaviour as today’s default, but with a
clearer directory name.
The --output PATH flag continues to override the default
entirely.
A findWorkspaceRoot(start string) string helper in
cmd/assay/main.go walks up the directory tree looking for
agents/harvey.yaml. This is a small standalone function
(~15 lines); it does not import the Harvey package’s
NewWorkspace to avoid increasing the assay binary’s
dependency surface unnecessarily.
The defaultOutputDir() function uses
findWorkspaceRoot and returns the fully qualified output
path with a timestamp suffix.
Users with scripts referencing the old testout/assay-*
path need to update to the new location or pass
--output testout/ explicitly. The --help
output documents the default location so users can find results.
termlib changes. Tracked
separately.s3-replacement-design.md.assay-llamafile-design.md.