Unit 7 activity: Ask AI to generate the routine

Release candidate. The unit videos are added in the first release.

Required work: about 12 minutes. Read the unit page first. Optional extensions at the end add more if you want them.

You will produce one file and one table. Keep both. Unit 8 checks the file, and on the routes that include Unit 17 it goes into your evidence pack.

Do not run the generated code during this activity. Predictions written before the checker sees them are worth far more than predictions written after.

Safety. Generated code comes from a source you cannot vouch for. Read it before you ever run it, and run it in a disposable local environment.


Task 1 — Generate (about 4 minutes)

Use whichever AI system you have access to; the exercise does not depend on which you pick. Give it exactly this, and nothing else:

Write a Python function that returns Bernoulli numbers.

Do not add context, do not mention Ada Lovelace or conventions or fractions, and do not follow up. One prompt, one response.

Save the result to a file — generated_bernoulli.py is a reasonable name — exactly as it came back, without tidying the formatting, deleting comments, or fixing anything you notice on the way past. What you noticed is the interesting part, and you will record it in Task 2.

Alongside it record your provenance: the prompt word for word, the date, the kind of system in general terms (you need not name a product), and whether web search, code execution, memory or a reasoning setting was on. Some of these may be on without your choosing: one provider's documentation, as of November 2026, says its assistant may search the web without being asked and that memory can be on by default [D2, "What is Claude's memory?"], [D4, opening note]. Each changes what produced the output. That record is what makes your work checkable by somebody else.

If you do not have AI access

Take one of the files in The verification lab and self-check as your specimen instead: wrong_b1_sign.py, ada_indexing.py, float_output.py, off_by_one_range.py, wrong_b4_sign.py, correct_example.py.

Pick one. Its file name, and its docstring (the description between triple quotation marks at the top of the file), say what is wrong with it, so the practice here is prediction rather than spotting: before running anything, write down how that failure should show up — which values come out wrong, and in what way — then copy the code into your own file, triage it in Task 2, and see whether the evidence matches what you predicted. For correct_example.py, predict what a correct routine must get right that the others miss.

This route is equivalent. In one respect it is stronger: the fault in each file is known in advance, and one of the six files is correct, so if you have time for a second specimen, the folder lets you compare a failure with a correct routine. The folder does not hold every failure mode: no file in it raises an error, fails on import or lacks a bernoulli function.

On the table-based route

If you are not working in Python, ask for values instead of a program, with the same restraint: exactly this, and nothing else.

List the Bernoulli numbers.

Save the reply as it came back, in a plain text file, with the same provenance record. Without AI access, use one of the supplied files above as a table of values: its docstring (the description at the top of the file) says what it returns, so write out the values it describes next to the reference table in the Unit 6 page. You read it; you do not run it. Either way your specimen is a list of values and any prose that came with it, and Task 2 triages that.


Task 2 — Triage (about 5 minutes)

Go through your specimen — code, comments, docstrings, and any prose that came with it — and put every substantive part into one of three columns.

Trust Doubt Must test
I will rely on this now, and here is my reason. Plausible, but I cannot confirm it. And here is the specific check that would settle it.

Three rules make this worth doing:

  1. Every "trust" entry needs a reason, and "it looks right" is not one. "I derived B4 by hand in Unit 6 and this agrees with it" is one.
  2. Every "must test" entry needs a named check that somebody else could run and get your answer from. "Test the code" is not a check; "compare bernoulli(0) through bernoulli(30) against the oracle (the lab's trusted reference routine, Unit 8) as exact fractions, index by index" is.
  3. Historical and factual claims go in their own rows, separate from the code beneath them, because they need a different kind of checking.

If the tool also wrote tests, ran them, or reported that they pass, those go in the table too. A tool's own tests are its own claims about what the code should do, and "all tests pass" is one more: they are triaged like any other output, starting with what they actually test. The same goes for a displayed step-by-step explanation, which need not reflect what produced the code (page; one study showed it for multiple-choice tasks [L9, sec. 3.2]).

Six rows is enough. Use these two prompts to make sure you have covered the ground, and leave the rest for the optional extension:

On the table-based route the same two prompts apply to the values: what is given for B1, whether the values are exact fractions or decimals, and whether the odd ones above B1 are listed as zero or skipped. Skipped zeros shift every later label, which is the indexing question Unit 6 raised.


Task 3 — Predict the checker's verdict (about 1 minute)

Before Unit 8 runs anything, commit to a prediction in writing. Using the course's failure-mode codes — b1-sign, odd-terms, ada-indexing, off-by-one, not-fraction — write down two things:

  1. Which codes you expect the checker to report for your specimen, if any.
  2. The first index at which you expect it to diverge from the reference.

Being wrong here costs nothing and teaches more than being right. The prediction exists so that Unit 8 tells you something about your own judgement, not only about the code, so write it down even if you have no idea.


Task 4 — Handle the prose separately (about 2 minutes)

If your specimen contained any claim about history, mathematics or attribution — in a comment, a docstring, or the surrounding explanation — copy it out on its own.

Then answer:

  1. What kind of evidence would settle whether it is true?
  2. Is that the same kind of evidence that would settle whether the code is correct?
  3. If the code passes every test you can devise, what have you learnt about the claim?

If your specimen had no such claim, use this one, which is the checkpoint's example:

# Ada Lovelace used this same recurrence in Note G (1843).

Answer the three questions about it. Do not attempt to establish whether it is true — Unit 16 handles Note G properly, and Historical Notes is careful about which parts of that story are established and which are contested. What this task is asking is what you would need in order to know.


Before you move on

Keep these four things together:

  1. generated_bernoulli.py — or your copied example — exactly as it came.
  2. Your prompt, the date, the kind of system used, and which tools were on.
  3. Your three-column triage table.
  4. Your written prediction of the checker's verdict.

Unit 8 needs items 1 and 4. On the routes that include Unit 17, its evidence pack takes items 1 and 2; keep items 3 and 4 with them. See Learner evidence template for the format it will eventually take.

Now check yourself

This unit has one checkpoint:

Either route:

python3 selfcheck.py run cp07-trust-triage  # or in the browser

Run this from the course/lab folder (setup: The verification lab and self-check).

The browser route, at the foot of this unit's page, has the same question, the same answer and the same feedback, and needs nothing installed.

The checkpoint asks about a comment rather than about code, which catches people out. Attempt it after Task 4, while the distinction between the two kinds of claim is fresh. Tasks 1 to 4 are all you need for it.


Optional extensions

Optional. These sit outside the core study time, are not required for the checkpoint, and nothing later in the course depends on them.

Extend the triage table (about 4 minutes). Task 2 asked for six rows and two prompts. Carry on with the two that were left out, adding a row for each answer:

The mirror-image prediction (about 2 minutes). Task 3 predicted what is wrong. Now predict the opposite: if your specimen turns out to be entirely correct, what single check convinced you of that, and what would that check have missed? Add one sentence on how confident you are and what would change your mind. A check that could not have failed has told you nothing.

Triage a second specimen (about 8 minutes). Take a file from The verification lab and self-check — without reading its docstring — and run Tasks 2 and 3 on it as well. If your own generated routine looked correct, this is where you get a contrasting case.

If you write code (about 10 minutes). Without running your specimen, read it and answer:

Then write the smallest test you can that would distinguish the correct example in The verification lab and self-check from the four with named failure modes. Then ask what it would make of wrong_b4_sign.py. Note how much of your Unit 6 grounding that test relies on.

Marking guidance — open this once you have done the activity, to check your own work

Read this after attempting the activity.

Most of this activity has no single correct answer, because what comes back from a generation is not fixed. What is fixed is the standard a good triage meets, and that is what this page sets out. Where the activity asks for judgement, the guidance describes what a strong answer does rather than supplying the answer.

Checkpoint answers are not restated here. cp07-trust-triage is defined once, and the browser self-check and the terminal both read that one definition and grade against it.

Expected time: 12 minutes for the required work, Tasks 1 to 4. The optional extensions at the end of the activity add roughly 4, 2, 8 and 10 minutes, sit outside the core budget, and are not needed for the checkpoint. The guidance below marks which material belongs to them, and does not assume any of it has been done.


Task 1 — Generate

Nothing to mark. Two things to check about your own work.

Did you save it untouched? The failure to watch for at this step is quiet tidying — reformatting, deleting a comment that looked wrong, renaming a variable. Each of those edits is a judgement you made, and once folded into the file it becomes indistinguishable from what was generated. Your evidence pack in Unit 17 depends on being able to tell the two apart.

Did you record provenance? Prompt, date, the kind of system, and which tools and settings were on: search, code execution, memory, a reasoning mode. Each changes what produced the output, and some may be on without your choosing (Unit 5); "not recorded" is a weaker record than "off". This course avoids naming commercial products, and you can too: "a general-purpose conversational AI assistant, accessed via its web interface, on 4 March" is adequate provenance. What matters is that a reader could understand how the artefact came to exist.

If your generated routine turned out to be entirely correct, that is a real and unremarkable outcome — this is a well-known sequence and there is a great deal of correct Bernoulli code in the world. It does not mean you have missed the point. It means your triage in Task 2 has to justify the trust rather than merely record it, which is the harder version of the exercise. The optional "triage a second specimen" extension gives you a contrasting case from The verification lab and self-check if you want one.


Task 2 — Triage

What a strong table looks like

A strong triage is recognisable by three features, none of which is about being right.

Reasons in the "trust" column reference something outside the specimen. The strongest reason available to you at this point in the course is your own Unit 6 working: "this returns 1/6 for B2, which matches the value I derived by hand". That is evidence. "The code is clean and well-commented" is not evidence about correctness; it is an observation about style, and generated code can be well styled whether or not it works.

Checks in the "must test" column are specific enough that someone else could run them. Compare these two entries:

The difference is the difference between doubting something and being able to resolve the doubt. A "must test" entry with no named check belongs in the "doubt" column, and moving it there honestly is better work than leaving it where it flatters you.

Claims about the world are on separate rows from claims about behaviour. If one row covers "the recurrence and the historical comment above it", the triage has already blurred the distinction the unit is about.

The prompts, worked through

Task 2's two prompts cover the first three of these -- convention, return type, and the behaviour at n = 3, n = 0 and n = 1. The last two belong to the optional "extend the triage table" extension, so a required-work-only table will not cover them; they are worked through here for anyone who goes on to it.

Which convention does it use? Determinable from the code alone, without running it, in most implementations. Look for a special case at n == 1, or a sign flip applied to a single term. course/lab/examples/correct_example.py shows what an explicit answer looks like: the Akiyama–Tanigawa transform naturally yields B1 = +1/2, so the file negates it and says so in a comment. wrong_b1_sign.py uses a different method, a binomial recurrence that also yields B1 = +1/2, and has no such step. What gives it away is a branch that is not there: nothing special-cases n == 1.

If you cannot tell from reading, that is a finding in itself, and it belongs in "must test" with the check "evaluate bernoulli(1) and compare against -1/2".

What type does it return? Look at the imports first. from fractions import Fraction at the top is a strong signal; its absence in numerical code is a stronger one. Then look at the literals: 1.0 and / on floats produce floats. This is the not-fraction failure mode, and it is a real possibility rather than an exotic one, because in Python dividing one integer by another already gives a float: exact fractions have to be asked for.

Worth internalising: -0.03333333333333333 is not -1/30. It is close, it prints convincingly, and it will fail an exact equality test. To the eye nothing appears wrong. float_output.py in the examples folder exists to make this concrete -- it passes a casual eyeball check at every index, and an exact comparison catches it at B2. The size of its error grows with the values, while its digits stay good to about fourteen significant figures.

What does it do at n = 3, n = 0, n = 1? These three indices are where a default filled in for an unstated requirement is most likely to differ from what you meant. n = 3 should be exactly zero; a small non-zero value is odd-terms, a raised error is raises, and a routine that skips the odd indices and closes up the numbering shows odd-terms at the terms it moves there. n = 0 should be 1. n = 1 is where the convention shows. Three single-index checks, chosen because each one isolates a different failure mode — which is what makes them worth more than thirty arbitrary ones.

What is the largest index you would trust it at? The question is designed to be uncomfortable. An honest answer, before testing, is "the ones I have personally checked", which is a much shorter list than the routine's apparent range. That gap between apparent range and verified range is what the differential test in Unit 8 closes. Note also that a float-based routine has no clean answer to this question at all: its values are inexact from B2 on, so there is no index up to which it can be trusted as exact.

Does any comment make a claim a test could not settle? See Task 4.

The failure modes, and the missing requirement each one traces back to

This mapping is the substance of the unit, and it is what Unit 10 builds on directly.

failure mode the requirement that was never stated
b1-sign Which convention. The prompt never said B1 = -1/2.
odd-terms That every odd index above 1 must be present and exactly zero.
ada-indexing That modern indexing is required, covering the zero terms.
off-by-one Which index the sequence starts at, and what bernoulli(0) means.
not-fraction That returns must be exact fractions.Fraction, never float.

Read the right-hand column on its own. It is very nearly the precise prompt from Unit 10, arrived at from the other direction. That is the general lesson: a specification is not written in advance by a sufficiently careful person. It accumulates, mostly one clause per failure that has already happened. Most clauses in a good prompt are bugs somebody met.

Each of these five may come from a decision the prompt left open and the generated code filled in, or it may be an ordinary bug. The output alone cannot tell you which. Either way, it counts as a failure only once the requirement is written down.


Task 3 — Predicting the checker's verdict

No correct answer, and no penalty for a wrong prediction. What the task is for is calibration: after Unit 8 runs the checker, you will be able to compare what you expected against what was there, and that comparison tells you something about your own reading that no amount of further reading would.

Two patterns are worth naming in advance.

Predicting no failures because the code looked fine. An easy prediction to make, and the five named failure modes are precisely the ones that leave code looking fine. off_by_one_range.py in the examples folder is the extreme case: clean code, exact arithmetic, every returned value a genuine Bernoulli number, and every one under the wrong index. The shift is visible -- it returns _b(n + 1) -- but nothing about that line looks wrong unless you hold it against a specification, because the error exists only relative to one.

Predicting a first divergence too late. It is tempting to nominate a large index, on the reasoning that small cases are easy. The first divergence can just as easily be at B1, because that is where the convention shows. If you predicted B12 and the answer is B1, that is the most informative outcome the task can produce.

The mirror-image prediction — what convinced you a correct routine was correct, and what that check would have missed — is now an optional extension rather than required work, so do not expect to see it. It is worth raising in feedback even when it has not been attempted: a check that passes tells you only about what it tested. Checking B0 to B12 with an absolute tolerance would pass float_output.py, which is wrong, and it would also pass a correct routine. A check that cannot fail for the right reason has not told you anything.


Task 4 — Handling the prose separately

Taking the checkpoint's example:

# Ada Lovelace used this same recurrence in Note G (1843).

1. What evidence would settle it? A primary source, or a cited secondary source: the published Notes themselves, or scholarship that quotes them. It is a claim about a document that exists, so it is settled by consulting the document. Note that the claim also has parts that could be true and false separately — a date, an attribution, and an assertion that a particular recurrence was used. An answer that treats it as one atomic claim has already lost some resolution.

2. Is that the same evidence that settles the code? No, and the difference is categorical rather than one of degree. The code is settled by execution: run it against known values and observe. The claim cannot be executed at all. Different failure modes, different checks, different kinds of authority — and no amount of one substitutes for the other.

3. If the code passes every test, what have you learnt about the claim? Nothing. This is the whole point of the checkpoint and it is worth stating without softening: correct arithmetic is not weak evidence for an adjacent historical claim, it is no evidence. The two were produced by the same process, and either can be wrong while the other is right. If anything, the checkable part being checked makes the uncheckable part more dangerous, because the specimen as a whole now feels verified.

The course is not asserting that this particular comment is false. The point is that you do not know, and that nothing about the code puts you in a position to find out. Unit 16 addresses what is genuinely established about Note G; Historical Notes states which parts of that account are settled and which are contested. Both are careful in a way that a generated comment has no mechanism to be.

A note on why this happens. It is not that the historical part is generated less carefully. Generated text is a single fluent continuation throughout; the arithmetic and the history are produced the same way and are equally plausible on the surface. What differs is not the output but your access to the truth: one part you can settle in seconds by running it, and the other requires a library. Both parts can be equally fluent while differing in reliability. That asymmetry may not announce itself, which is why triage has to be a deliberate step rather than an impression.

The student page's short Gödel example applies the same triage outside code: check what the cited theorem actually establishes, then check the separate argument that transfers it to "an AI". Unit 18 supplies the formal definitions and full scope; they are not required for identifying the unsupported transfer here.


Optional extensions

None of this is required work, and none of it is needed for the checkpoint. The notes below are for anyone who takes an extension.

Algorithm identification. The examples folder contains two distinct methods, which is deliberate: reference_bernoulli.py uses the binomial recurrence and correct_example.py uses the Akiyama–Tanigawa transform. Two independent routes agreeing on exact values is much stronger evidence than one routine agreeing with a copy of itself, and that is a general principle of differential testing rather than a detail of this course.

Representation versus method. The distinction the task is after. A routine using a sound algorithm with float arithmetic is fixed by changing the type; the method was never wrong. A routine using Lovelace's indexing is not fixed by any change of type, because the arithmetic was exact all along and the labels were wrong. Diagnosing which of the two you are looking at determines whether the fix is one line or a rewrite, and generated code gives no outward sign of the difference.

Slow rather than wrong. A naive recurrence that recomputes every earlier term from scratch is correct and impractical. Correctness and efficiency are separate properties with separate checks, and the course's checker deliberately tests only the first.

Negative n. Undefined in the four-line contract (Unit 10's C4 adds it), and a generated routine may say nothing about it -- returning something meaningless, or raising an unhelpful error. The checker names this raises when it occurs inside the required range. The lesson is that "the specification does not say" and "the behaviour does not matter" are different statements, and code cannot tell them apart.

A compact distinguishing test. Four indices cover the five failure modes:

Four checks cover the five named failure modes (the zero-term check covers both odd-terms and ada-indexing), each isolating a single decision the vague prompt left open. Notice how much Unit 6 that required: you cannot design this test without knowing the convention, the zero property, and the indexing scheme. That is what "build understanding before using the tool" means in practice — not a slogan about diligence, but the reason you were able to write a four-line test instead of thirty arbitrary ones.

And notice what it misses. wrong_b4_sign.py returns 1/30 for B4 and is right everywhere else, so it passes all four checks: a test aimed at the named modes says nothing about an index it never reads. That is the case for also comparing every index against the oracle (Unit 8), and for checks that try to break the routine rather than confirm the cases you expected.


Checkpoint

python3 selfcheck.py run cp07-trust-triage  # or in the browser

Both routes ask the same question and give the same feedback. The answer and its diagnostics live in the checkpoint data below, which both routes read.

Your private activity record

Browser storage is not a permanent copy

Progress is kept only in this browser, profile and device. Private browsing, clearing site data, removing the profile, a browser reset, storage eviction or device loss can erase it. Keep important answers and contributions separately.

These notes stay in this browser unless you download a backup or activity log.