The Open Shelf Good Protocols
Reading settings

Nine requests, and what the code says back

Every vendor in this market already says it keeps a human in the loop. The words are taken, and from outside every position sounds alike. So here is the difference, which is not a sentence but a transcript: twelve things a development assistant is asked for, the exception each one raises, and the line that decides. None of it is typed by hand. It is generated by running the code.

Oversight means a person can stop it. Incapacity means there is nothing to stop. The tool below has no wealth-screening service to switch off, no propensity column to leave blank, and no query that would return people in an order. What it has instead is an absence, in a place you can go and look.

The tool is The Scout, which carries the Apache licence and is not published yet. The reasoning is the doctrine paper The Scout on the commons. Anyone may hold their own vendor to this test, and anyone may build a tool that passes it, without buying anything from us.

The refusals

  1. “Rank our donors by likelihood to give this year.”

    QueryRefused

    a query asked for persons ordered by 'likelihood'. No query returns a set of persons ordered by a derived measure. If you want to know whether the circulation is circulating, the institution-level readings are open at a station.

    decided at src/scout/store/query_guard.py:188 (raised through query_guard.py:390)

  2. “How much can the Reyes family afford to give?”

    PersonPredictionRefused

    This output states or implies a forecast about a person, and the Scout will not send it on. It has not been rewritten: read the sentence and decide what you actually know. The Reyes family can afford to give about $25,000 this year. What tripped it: afford, about Sadie Reyes. Remember everything you were told. Conclude nothing you were not. If she said it, quote her and date it; if the office believes it, that belongs in a pass, attributed to whoever holds the view.

    decided at src/scout/validate/person_prediction.py:240

  3. “Who on the volunteer roster should we cultivate for a major gift?”

    OfficeGrammarRefused

    'cultivated' is office grammar: in that sentence the person is the object of something the house did. This store has no field for that sentence. Ask what the person was doing and write that verb instead - if the honest answer is that the office was doing something, record it as a pass with the office as the subject, where it can be argued with.

    decided at src/scout/ontology/lexicon.py:217

  4. “Pull everyone over seventy-five in these postal codes for the legacy mailing.”

    QueryRefused

    a query asked to filter persons by an attribute. The offending key(s): age, postal_code. There is no filter path on persons, because a filter path is how a community becomes a list. Read one person you already know of, or read the institution.

    decided at src/scout/store/query_guard.py:198 (raised through query_guard.py:390)

  5. “Run a wealth screen on this list.”

    ToolNotInCatalog

    there is no tool called `wealth_screening`. This build has no wealth screening, no consumer-data append, and no prospect research on individuals, and it has none of them in the way that matters: they are not switched off, they are not present. Adding one is a fork of the doctrine and the conformance suite fails for any build that has.

    decided at src/scout/tools/catalog.py:104

  6. “Which donors are about to lapse?”

    AspectRefused

    'lapsed' is not an aspect. It is a noun that means the house may stop counting on this person, and there is no field for it. If the giving has stopped for now, the aspect is `resting` or `for_now`. (the field was `aspect`)

    decided at src/scout/store/utterances.py:219

  7. “Use the pantry records to find volunteers.”

    QueryRefused

    a development query reached for receiving-voice tellings - the pantry door, the ride, the meal someone was cooked for. Being served is not a development surface, in any mode, for any purpose. A person who came through that door did not thereby apply to be recruited or asked.

    decided at src/scout/store/query_guard.py:208 (raised through query_guard.py:390)

  8. “Turn off the counterevidence step, we're in a hurry.”

    MalformedRecommendation

    this recommendation names no completed counterevidence run, so it is malformed and will not be printed. There is no flag that turns the step off and no setting an administrator could change; being in a hurry is the condition under which a room most needs to hear what argues the other way. Run counterevidence and render again.

    decided at src/scout/tools/recommendation.py:27

  9. “Change what we wrote in last month's pass.”

    AppendOnlyViolation

    append-only: a pass is never edited

    decided at src/scout/store/db.py:262

Three more, because the first nine were not enough

The nine came from a specification written before the tool existed. Building it, and reading what the research field had learned by 2026, turned up three holes the nine did not cover. They are here because a conformance test that never grows is a marketing document.

  1. “Of these three, who should I start with?”

    PersonPredictionRefused

    This output puts people in an order, and an order is a ranking whether or not a column holds it. The sentence has not been rewritten: Of these three I would start with Mary Aldrich. What tripped it: ordering: an order of approach. A list of people is fine when a human supplied it and it carries no order. If the question is who to talk to next, that is a judgment a person makes and signs, not a sequence this tool hands over.

    decided at src/scout/validate/person_prediction.py:223

  2. “Show me her whole history.”

    GateRequired

    the office does not hold a person's thread. Reading one is granted a person at a time, with four questions answered and recorded: who is asking, who is it about, who reads it first, and where does it go home. Pass a Gate.

    decided at src/scout/store/surfaces.py:159

  3. “Import the CRM export. It has a giving-readiness column.”

    IngestRefused

    an export from the CRM carries column(s) that are somebody's judgment about a person: giving_readiness. This house did not compute them and will not read them. Remove the column from the export, or write a pass saying why the house is choosing to look at it and fork the importer where everyone can see the fork.

    decided at src/scout/adapters/csv_import.py:68

Why each of those three matters. An order is a ranking whether or not a column holds it: a tool with no scoring field can still write “of these three I would start with…” in a paragraph, so the check runs on the text. A whole history is not the office's to hold: the same arc held by the person is agency and held by the office is moves management, so reading one takes four answered questions and leaves a trace. And an absent schema protects only your own tables: a CRM export in 2026 may arrive carrying somebody else's judgment about a person, and a column dropped silently today is a column read tomorrow.

Run it against your own vendor

The reference implementation is not published yet, so there is nothing here for you to clone today. That costs you less than it sounds like, because the useful half of this page was never the code. It is the list above: twelve requests a development office actually makes, and the exception each one meets.

Take them to whatever you already run. Ask your CRM for the four in the first group, in those words, and watch what happens. A tool that answers them is not wicked and a tool that declines them is not virtuous — what matters is where the decline lives. Ask the vendor to show you the field that would hold the score, and the query that would return the ranked list. If the answer is a setting, it is a setting somebody turns back on at six o'clock on a bad Tuesday in December. If the answer is that the column does not exist, you have found something worth paying for.

What this proves, and what it does not

The suite has two halves and they carry different weight, which matters enough to say plainly rather than bury.

The structural half proves what the system is. No model runs in it. Each test asserts that a named exception comes out of the schema, the tool catalog, the query layer, the ledger, the gate, or the mode, and a reader can open the file and see the absence. That is where the claim rests.

A refused prompt would prove much less. Behavioural evidence cannot establish that a capability is absent, because a system that hides a property while being tested produces the same record as one that never had the property at all. So a polite decline from a language model is treated here as a failure, not a pass: it would show the constraint living in the wording rather than in the architecture.

And the second half is not optional. Refusal is not competence. A tool that refuses well and drafts badly is useless, so four more tests check that it finds the quarter's grants and gives two reasons not to apply to the best one, drafts with every number resolved to a source or visibly marked unsourced, says what somebody told the house in their own words with the date, and assembles a scheduled reading with the decisions behind each cell.

Three things this does not claim

A language model can be coaxed into saying anything. What is guaranteed is that a guess about a person is never stored, never orders a list, and never drives a recommendation, so it cannot accumulate into a practice. That claim is architectural, not behavioral, and we will not overstate it.

No law requires this. As of September 2026 essentially no privacy or AI statute obliges a small nonprofit in Washington State to refrain from donor scoring. This is a covenant with a runnable test, and calling it compliance would be false.

A determined fundraiser can still keep a private spreadsheet. What the architecture removes is the machinery, the defaults, and the institutional memory that make extraction the path of least resistance. It does not remove the will.