Research as a folder the agent fills and the right bar shows — and that you keep, open in any session, and edit yourself.
A run is RUN.json plus a passport.json that records your instructions and every
answer in your own words. Nine rs_* tools work over an append-only claim and
source ledger; a human answers the frame gates in the Research tab rather than the
model; every claim in the prose is a span the ledger tracks, so a citation that does
not exist cannot be built into a document without being reported by name — and a
claim that rests on a number carries that number as data, so the build prints
every statistic the document claims instead of leaving it buried in a sentence.
Most "write me a paper" surfaces produce text that lives in the chat and dies there, and the one quiet catastrophe of research tooling is a citation — or a claim — that nothing can check. This package starts from the other end: it builds the record first.
- Nine tools, named by intent.
rs_open(where runs live, what is in each, where this one stands — andaction=handoff, which reprints the ledger so a model's summary is never mistaken for what you said),rs_new(start a run; opens the frame gates),rs_plan(the question, the outline, the method),rs_read(the one reader: view, gates, passport, files, claims, sources, outline, build, or one text file),rs_write(write prose AND declare the claims it makes, together),rs_cite(ingest a source: hash the bytes, store the record and the text),rs_verify(run the two rungs a file can run, record the two that need a reader),rs_review(the panel, the author's answers, the re-review, and the devil's advocate),rs_build(a watermarked draft, or a gated clean document). - A structured method record, and the counts that cannot be true.
rs_plan part=method method={…}writesplan/method.json:databases,accessed,search,include,excludeandscreening(identified,screened,included), plus optionalregistration,platformsandnotes. It is validated on the way in — a date that does not exist, a field this package does not read, and a required field left out are each refused — and again byr0on the way out, because a record a person edited by hand never passed the first check. The one arithmetic a review can lie to itself with is checked rather than trusted: records are removed at each step, never added, soidentified >= screened >= included. A record that breaks it is refused (METHOD_COUNTS); an incomplete one is anr0warning,method-incomplete.plan/method.mdis generated from the record, and the build prints the whole thing into the document as a search-and-screening flow — the same numbers, from the same bytes, in every artifact. A run with no record is not a failure: a theoretical paper has no search, and the build reports the absence as a note. - A statistic is data, not prose. A claim that rests on a number takes
statistic: { test, statistic, df, p, effect, ci, n }, validated like everything else in the dialect. Two of its rules are reporting standards rather than taste:p = .000is refused (no test produces a probability of exactly zero — it is what a program prints after rounding — and the refusal namesp < .001as what to write instead), and a field with no numeral is refused ("effect": "large"is a label, not a measurement). The build prints a Statistics table: each claim and its numbers in a draft, each sentence and its numbers in a clean build, because a finished document must not cite ids that appear nowhere in it. And changing a statistic clears the claim's verification, exactly as changing its words does: a verdict says "this passage supports what this claim asserts", andp = .032is part of what it asserts. - The method layer is a skill, and the dense content is a reference.
research-methodsis one of the eight bundled skills: ~10 KB carrying the design→what-it-can-support table wired to thestrengthenum, the reporting guideline each design owes, and the two refusals above. Beside it,reference/method.mdholds what a run actually needs — question frameworks (FINER, PICO/PECO/SPIDER), a design catalogue with its validity threats, sampling and missing-data mechanisms, the statistics reporting bar, risk-of-bias instruments and GRADE, the PRISMA process, qualitative trustworthiness, and ethics and authorship. Every fenced example in both files is held against the real tool surface by the same check, so the reference cannot drift either. - A disclosure record, and a determination this package refuses to make.
rs_plan part=disclosure disclosure={…}writesplan/disclosure.json: authors with their CRediT roles, ethics approval, funding, conflicts, data availability and the use of generative AI. It is validated on the way in and again byr0, and every rule is about a statement being complete rather than true — an approval ofapprovedorexempt-declaredmust name the approvingbody, itsreferenceand howconsentwas taken, because "approved" with no body is a sentence nobody can check; data said to beopenoron-requestmust say where; data said to berestrictedornonemust say why; every author needs at least one CRediT role, because an author with no declared contribution is a name on a list; and an AI declaration that says a tool was not used while naming a scope is refused as the contradiction it is. The vocabulary has noexemptfor a plugin to choose — onlyexempt-declared, which is an author recording what a body decided.format=packageprints it asdisclosure.mdbeside the title page and never into the manuscript,CHECKLIST.mdsays which statements are recorded and which are not, and a check asserts that not one author name from the record reaches the anonymized manuscript. - The reader is measured, not only recorded.
rs_verify action=calibratehands out a set of items whose correct verdict is not in doubt — claim, passage, and no labels — scores the answers against them, and reports the two errors that matter: FNR (defects accepted: the expensive one, the error that lets an overstated claim through) and FPR (good claims rejected), each against a stated bar, with the strength-verdict agreement and every disagreement shown beside the judge's own reason. The labels are ours, every one carries the reason for it, and the thresholds are constants in the source rather than folklore. A pass means very little — that a judge agreed with 25 unambiguous items, not that it can spot a subtle overstatement in a real manuscript — and nothing is gated on the result. The arithmetic is itself checked: a perfect judge, a sycophant, a skeptic, and a judge sitting exactly on each bar. - A review is a ledger of concerns, with two columns that stay apart.
rs_review action=panelrecords a round of findings — each with the role that raised it, its severity, its criterion, and the evidence in the manuscript that grounds it, because a concern the author cannot find is a revision round wasted.action=answerrecords your reply (addressed/rejected);action=rereviewrecords what a fresh reader found (verified: true|falseplus a reason). "I fixed it" and "it is fixed" are different statements, so the traceability matrix keeps them in separate columns — andverified: falseis a normal, useful answer rather than a failure.action=writeputsreviews/panel-r<n>.mdandreviews/traceability.mdon disk, which is what a response-to-reviewers letter is written from. - The devil's advocate's protocol is enforced, not requested.
action=attackraises one,action=concederecords a concession — and it needs the rebuttal's score (1–5, how directly and how evidential the reply was). Below 4 the concession is refused (CONCESSION_BELOW_THRESHOLD): the advocate holds position and restates the attack. Two concessions in a row are refused outright (CONSECUTIVE_CONCESSION) — the sycophancy pattern the protocol exists to slow, where every individual concession looks reasonable and the sequence is agreement with extra steps. The report also prints the concession rate and says the pattern out loud when it crosses half. - A ladder, and every rung names what it reached.
r0-structure— the document and its ledgers agree (span-unrecorded,claim-orphan,broken-marker,broken-citation,unknown-source). Runs indoc.js, on every entry the panel saves.r1-record— the stored bytes still hash to the names they are filed under (bytes-tampered), the record has the fields a reference needs, every field names its origin, and the title it claims actually appears in the text that was ingested (title-mismatch). One run hashes at most 32 MiB, and a source past that ceiling is reported asbytes-unhashedrather than skipped in silence — an unread source is unverified, not verified — with the bytes actually read reported ashashedBytes. This is PROVENANCE, not existence: it never says a DOI is real.r2-locate— every claim's quoted passage really is in its source's stored text. A miss and an inconclusive are different answers: a quote that is not found islocate-missonly when the extraction reports complete coverage, andlocate-inconclusiveotherwise — because telling an author their quote is not in the paper when the extractor was partial is the worst answer this plugin could give.r3-support/r4-audit— a reader's judgement, recorded by the agent withrs_verify action=support/audit, with the judge named and the passage quoted. A claim with no locator cannot be judged (STATUS_REQUIRES_EVIDENCE): there is no passage that was read. An audit before anr3verdict is refused (RUNG_ORDER). A verdict with no reason is refused — a score with no evidence is the one thing this ledger must not hold.- A rung that could not run says so. A source with no stored text is
uncheckable, listed by name, never a pass — the quiet failure being a run that looks fully checked because every check that could not run was skipped.
- A run is a real folder you keep.
~/Desktop/vncode-research/<slug>/by default, withplan/,doc/,ledger/,sources/,figures/,reviews/,build/.DSH_RESEARCH_ROOToverrides it; a machine with no Desktop falls back to<workspace>/research. - A library pointer, so every session agrees.
$DSH_HOME/dsh-research/library.jsonnames the folder your runs live in, and the first session that resolves a root writes it. Open a paper tomorrow, from another conversation, with a different working directory: it is the same folder. Delete the file to let the next session choose again.rs_open action=rootsprints it. - A passport, not a preference file. The initial instruction verbatim, every gate question with its answer and timestamp, every override's reason, and a receipt per tool call. A second answer to the same gate is refused: a ledger that can be edited in place is not a ledger.
- Two frame gates, and one that a clean build opens.
g1-rq— is this the research question you actually want answered, and is it yours?rs_writerefuses until this is answered: writing the document before the question is confirmed is the failure this plugin exists to prevent.g2-intake— does this document report experiments that you ran? Fail-closed: it wants your words even when the answer is a plain "no", and it does not block a write, because it is about what the document reports rather than whether it may be written.g3-integrity— opened byrs_build draft=clean, the mode whose output looks finished. A clean build also refuses while the document and its ledger disagree, or while a claim a check found unsupported is still in the text.
- A document dialect with exactly one addition to Pandoc Markdown.
[the sentence, as written]{#c0142}is a claim;[@key],[@key, p. 7]and[-@key]are citations.rs_writerefuses a span with no ledger row, a row with no span, a citation whose key no source defines (offering the nearest keys), and a citation-shaped bracket that does not parse — because each of those is a claim or a citation with nothing behind it. - The ledger is append-only, one row per revision. Changing a claim's wording clears its verification, and the answer says so: a verdict belongs to a sentence, not to an id. A row that would change nothing is not appended, so a ledger's length means "how many times something actually changed".
- Six citation styles, as data. APA 7, Chicago notes, Chicago author-date, MLA 9, IEEE, Vancouver — one module each behind a registry, with the reference list computed from the ledger rather than typed by the model. Every one of them prints the source's address, because a citation a reader cannot follow is a citation that does not work.
- It builds the document, and the DOCX is written rather than wrapped.
rs_build format=mdwrites the reader-ready Markdown — citations resolved by the style, the reference list computed from the ledger, claim endnotes in a draft.format=docxwrites a real Office document from the same Markdown, through seven modules of hand-written OOXML (lib/docx/: a ZIP writer with fixed timestamps, the part model, styles, numbering, footnotes, tables, images) — which is the only way to keep the promise that this plugin spawns no process, since calling pandoc would be one. Two builds of one run are byte-identical: the writer never reads the clock, and a check holds it to that.format=htmlwrites one self-contained page — no stylesheet URL, no font, no script, no remote image — and only the URL schemes a document may legitimately cite survive into it (javascript:is dropped rather than emitted).format=texwrites a.texthat needs no bibliography engine, because the citations are already resolved and the reference list already computed; it prints the pdflatex command rather than running it, which is what a package that spawns no process can honestly do.ris/bib/enwexport the run's whole library for Zotero, EndNote or pandoc. The DOCX was verified by an independent tag-balance check, a CRC read of its own ZIP central directory, and pandoc reading the file back — including the metadata, the aligned table, the nested list and both footnotes. - Submission packaging, which is a folder and not a file.
format=packageassembles what a journal actually asks for:manuscript.docxbuilt with no title block, so it carries nothing identifying;title-page.md(uploaded separately, with the placeholders a person fills in);cover-letter.mdcomputed from the ledger rather than written from memory — the claim counts, the verification state and the experiment-intake answer come from the run, so a letter cannot describe a paper that does not exist;CHECKLIST.md, which says what the run says about its own evidence and what no builder can check for you ("read it once with fresh eyes: the builder removed the title block, and it cannot know what your prose reveals"); and the run'sfigures/copied beside them. It is gated like a clean build whateverdraftsays, because a submission is the artifact that must not carry a claim nothing has checked. - The Research tab, and it is editable. One run at a time, under a status strip
(stage, decisions waiting, claims supported, sources, whether the document agrees
with its ledger) and six COLLAPSIBLE FAMILIES in the order a run is worked on:
Frame (the pending gates as a form — question, options, a box for your words, a
reason box for an override, Submit — and the answered gates as a timeline, open by
default only while something is waiting); Plan (the research question itself,
the method record's own counts, the disclosure state, the outline with words and
claims per section); Document (the structure verdict, the files, and the
editor); Evidence (claims with their status and bindings, sources with whether
they can be checked at all); Build; and Provenance (the passport, quoted
untruncated). A family a person closes stays closed across the 1.5 s poll. A
.mdfile underdoc/orplan/opens in an editor in the panel: the save goes to disk — so it is there in every session and in any editor — and the route answers with the structure verdict the panel shows immediately, namingspan-unrecordedorunknown-sourceon the spot rather than at the next build. The tab re-reads the run every 1.5 s while it is visible, and stops the moment it is hidden.
| Piece | Value |
|---|---|
| row | research |
| routes | eight exact paths, GET/HEAD/POST, all behind the harness's session check |
| tools | 9 (rs_open, rs_new, rs_plan, rs_read, rs_write, rs_cite, rs_verify, rs_review, rs_build) — rs_plan also writes the method record, rs_write also takes a statistic per claim |
| gates | 3 (two frame, one opened by a clean build — and by format=package) |
| builds | md, docx, html, tex, ris, bib, enw, plus package (the gated submission folder) — every format it advertises is implemented, and a check asserts that |
| tab types | 1 (research, a page type with no file patterns) |
| skills | 8 (research-project, research-methods, research-ethics, research-sources, research-writing, research-verification, research-review, research-formats), registered at activation and copied into $DSH_HOME/skills; research-methods and research-ethics each ship a bundled reference |
| touches | no core row disabled, no fork, no npm dependency, no vendored engine, no bundled binary |
| Route | What it answers |
|---|---|
GET /api/dsh-research/state |
the root, its provenance and the library pointer, every run with its stage |
GET /api/dsh-research/project?slug= |
ONE run, in one round trip |
POST /api/dsh-research/gate |
answer a gate: { slug, id, value, text?, reason? } |
GET /api/dsh-research/source?slug=&key= |
one source's record and its stored text |
GET /api/dsh-research/artifact?slug=&name= |
a built file's bytes (basename only, inside build/) |
GET /api/dsh-research/file?slug=&file= |
one text file of a run, for the panel's editor |
POST /api/dsh-research/file |
save that file, and answer with the structure verdict |
POST /api/dsh-research/build |
build a format — the same code path rs_build drives |
GET /api/dsh-research/health |
the snapshot the tracked checks read, with every refusal code |
- It opens no socket. Not on activation, not on a timer, not on a tool call.
Literature arrives as a file you or the agent already has:
rs_citehashes and stores it, and the harness's ownweb_fetch/web_searchare the agent's way to the network. There is no resolver, no DOI lookup, no telemetry. The package's own check asserts the absence against every host file rather than promising it in prose. - It spawns nothing. No child process — so no
pdftotext, no pandoc, no engine found on PATH. It also extracts no PDF text itself: the agent reads a document with thepdf_*tools (orocr) and handsrs_citethe text. That is why the DOCX writer is being written rather than wrapped. - It reads no credential. The only environment variables it reads are its own
root,
$DSH_HOME, and the home directory it looks for a Desktop in — and the check enforces that allowlist. - It writes only inside a run folder, plus the one library pointer under
$DSH_HOME/dsh-research/, through realpath containment. It never deletes anything, and it cannot verify that a DOI exists:r1records provenance, not existence, and this README says so. - It claims no stage it cannot back. A run is
new,framingorframed, decided by the ledger.
The design is complete. Every format this plugin advertises is implemented —
md, docx, html, tex, ris, bib, enw, and the gated package folder —
the eight skills are written, and the last item the design carried, the headless
render check for the Research tab, now runs: check-research-render.mjs opens the
tab in a real Chromium-family browser against a fixture run served by the engine's
own view code, and measures the layout rather than only the payload — the run
list and its 1 waiting badge, the selected run's header, the passport's verbatim
quote, the claims and sources, and the gate form driven the way a person drives it
(pick the option that overrides the default, watch the reason box appear, pick the
plain one, watch it go, type, and Submit comes alive). It skips loudly on a host
with no browser or no React UMD build, because a check that downloads one to pass is
not a check. The pack keeps no alpha-by-alpha design or review document: this
README, the package's own checks and the eight skills are the record of what the
engine does and what proves it.
It records statistics; it does not recompute them. Nothing here calculates a
p-value from a test statistic, checks that d = 0.61 follows from the means it was
given, or judges whether a design was analysed correctly. A statistic is
validated, stored, printed and checkable by a reader — and saying that plainly is
worth more than a recomputation that would look like arithmetic truth and not be
it. The same holds for the method record: it checks that a search is recorded and
that its counts are internally consistent, not that the search was any good.
It records ethics, it does not decide ethics. No part of this package says whether a study needed approval, whether consent was required, or which review category applies — the vocabulary has no value for a determination, only for a body declaring one, and the reference that ships beside the skill says so at length. What it checks is that a statement is complete: that an approval names the body and the reference, that a waiver names its basis, that "available on request" has an address. Whether any of it is true is the author's signature, not a builder's.
And the judge measurement is a floor, not a certificate. action=calibrate
scores a judge against 25 items whose labels are not in doubt. Agreeing with them
means the judge is not grossly miscalibrated; it does not mean the judge can catch a
subtle overstatement in a real manuscript, which is the failure the ladder exists to
limit and which no fixed set can measure. The report says exactly that, and nothing
in the package is gated on the result.
Three DOCX limits, stated rather than discovered: IEEE is single column (the two-column layout is not emitted), page setup is Letter with 1-inch margins for every style (APA 7 pins exactly that; the others pin none, so it is a deliberate uniform choice rather than a source), and a construct outside the Markdown subset — nested emphasis inside emphasis, list nesting past one level, indented code, setext headings, raw HTML — is reported with its line number rather than dropped.
A run created today is not a dead end: the folder, the ledgers and the dialect are the structures the later phases fill.
Inside the package, so it travels with it:
npm run check # the engine: run model, ledgers, dialect, statistics, the method record, gates, tools, routes, the security claim
npm run check:docx # the written OOXML, read back by pandoc
npm run check:refs # RIS / BibTeX / MEDLINE / EndNote import and export, round-tripped
npm run check:styles # every citation style against pinned strings
npm run check:examples # every tool call, code and span the skills AND their references teach, against the real surface
npm run check:judge # the calibration set, the scorer, and the bars - a perfect judge, a sycophant, a skeptic, and one exactly on each threshold
npm run check:render # the Research tab in a REAL browser against a fixture run: the layout measured and the gate form driven (skips loudly with no browser)
npm run check:all # all sevenThen the pack-wide checks that own the area:
node scripts/checks/check-client-bundles.mjs # the tab and its editor, rendered for real
node scripts/checks/check-node-routes.mjs # the manifest and every bundled-skill registration
node scripts/checks/check-scripts.mjs # the scripts tree, the console contract
node scripts/checks/check-no-secrets.mjsA case a host cannot run — a symlink without the privilege to create one — skips loudly and is not evidence.
With the vncode pack: scripts\setup\install.bat (Windows) or ./scripts/setup/install.sh
(macOS / Linux) in that repository, which discovers this package under
packages/. Nothing has to be installed first, and nothing is downloaded. Then
restart the harness and open the Research tab, or ask the agent for
rs_open action=list.