diff --git a/README.md b/README.md index 89ff15b..76d51ff 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ First-person voice, short blog, playbook, patterns, and honest measurement — p **Topics:** `ai` · `agents` · `cursor` · `developer-tools` · `llm` · `engineering` · `documentation` · `productivity` -Site SEO includes canonical URLs, per-note Open Graph / Twitter cards, +Site SEO includes canonical URLs, per-insight Open Graph / Twitter cards, `BlogPosting` JSON-LD, RSS, `robots.txt`, `llms.txt`, and a real-URL sitemap. Contract: [`docs/NOTES-SOCIAL-SHARING.md`](docs/NOTES-SOCIAL-SHARING.md). @@ -29,7 +29,7 @@ Contract: [`docs/NOTES-SOCIAL-SHARING.md`](docs/NOTES-SOCIAL-SHARING.md). | **Explore** | Timeline, stack, capability map, patterns, glossary/FAQ, research | | **Cases** | Before/after outcomes that show the harness at work | | **Measure** | Tenure clocks, metrics that aren’t fake multipliers, weekly ritual | -| **Blog** | What broke / locked / earned a place on disk — sparks in [`blog-inbox.md`](blog-inbox.md), published at EOW | +| **Insights** | What broke / locked / earned a place on disk — sparks in [`blog-inbox.md`](blog-inbox.md), published at EOW | | **About** | Notebook blurb; contributor notes in [PRIVACY.md](PRIVACY.md) | ## Local check @@ -37,12 +37,24 @@ Contract: [`docs/NOTES-SOCIAL-SHARING.md`](docs/NOTES-SOCIAL-SHARING.md). ```bash npm install npx playwright install chromium -npm run cards # regenerate 1200×627 note cards (Pillow) +npm run cards # regenerate 1200×627 insight cards (Pillow) npm test # sync + privacy gate + Playwright (desktop + mobile) npm run serve # http://127.0.0.1:4173 ``` -`content/blog.json` is the note source of truth. `npm run sync` renders static +Start a private insight package from a rough thought or UTF-8 transcript: + +```bash +npm run insight:new -- --title "The working title" --text "The first thought" +npm run insight:new -- --title "The working title" --input ~/Downloads/transcript.txt +``` + +Drafts default to a private directory outside this public repository and are +not published automatically. Use `--output-root` to select another private +location. After publishing the associated LinkedIn post, add its HTTPS URL as +`discussionUrl` so the article invites readers to join that conversation. + +`content/blog.json` is the insight source of truth. `npm run sync` renders static `docs/notes/*.html`, `feed.xml`, `sitemap.xml` and `llms.txt`; GitHub Pages serves `docs/` from `main`. Review the PR and local Lighthouse results before merging. After production publish, run new URLs through LinkedIn Post Inspector. diff --git a/content/blog.json b/content/blog.json index fe72c24..1bec6aa 100644 --- a/content/blog.json +++ b/content/blog.json @@ -2,6 +2,65 @@ "schema": "durable-agent-harness/blog/v1", "intro": "What broke, what we locked, and what earned a place on disk \u2014 filed when the week earns it.", "posts": [ + { + "id": "when-answers-are-cheap-judgement-matters-more", + "date": "2026-09-15", + "title": "When answers are cheap, judgement matters more", + "seoTitle": "AI and education: when answers are cheap, judgement matters more", + "summary": "AI can produce answers in seconds. Education should bring critical thinking back to the centre, with the Trivium offering a useful historical lens.", + "section": "AI and education", + "tags": [ + "AI", + "education", + "critical thinking", + "Trivium", + "language", + "dialectic", + "human oversight" + ], + "customCard": true, + "imageAlt": "Six ethnically diverse sixth-form and university-age students challenge ideas around a table covered with books, notes and a laptop.", + "imageDisclosure": "The documentary-style student workshop image was generated with OpenAI under Alex Lennon's direction, then cropped and typeset as a CWCW social card.", + "body": [ + "When I started work, knowing things looked quite different. The Internet was not yet part of ordinary working life. If I needed to understand a subject, I read books. Often that meant reading a shelf of them, because that was the only way I could find the information I needed. You wouldn't believe how much slower life was back in the 90's.", + "Then search engines came along. I imagine most of you won't remember AltaVista, Ask Jeeves and all the other Google challengers. Those were heady times. Information became much easier to find, but finding it was only half the job. The useful skill was learning how to search well, compare different answers and decide which sources deserved attention.", + "This matters particularly in engineering. I am rarely looking for a solution in the abstract. I need something that will fit a real product, remain useful in the future and still be constrained enough to deliver within the time and budget available. Several answers may be technically sound. Only one may be right for the work.", + "The skills we need are not simply about accessing information. They are about judging it.", + "Over the past year, I have seen a massive change. I increasingly do not search the Internet myself. I explain what I am trying to achieve to a large language model, and it finds, compares and applies information on my behalf.", + "This change is wildly different from mere search. The agentic harness I've created uses tools across real computer systems over the Internet. It can create containers and virtual machines, configure services and DNS, and carry out the grunt work that previously consumed a substantial part of my day. I set the goals and I make the consequential decisions, but much of the mechanical effort has moved away from me.", + "That is wonderful! It frees me to spend more time on the ideas, the trade-offs and the purpose of the work. I cannot emphasise strongly enough to you how game changing this is for us.", + "This raises a difficult question for education. If AI can retrieve the information and perform the basic tasks, what should young people learn? It is tempting to say they no longer need to learn the things a machine can do. I think that is premature and potentially dangerous.", + "AI can move quickly and confidently in the wrong direction. It can misunderstand the objective, optimise the wrong thing or disappear into an impressive-looking dead end. A human still has to notice, redirect it and take responsibility for the result.", + "You cannot supervise what you cannot evaluate.", + "That is why we should bring the explicit teaching of critical thinking back to the centre of education. I do not mean occasionally telling students to be sceptical. I mean repeated practice in breaking an argument apart, identifying its assumptions, testing its evidence and explaining why a plausible answer may still be wrong.", + "The Trivium offers a useful historical lens. Ancient education was not one uniform system, and advanced learning was far from universal. But after literacy and grammar, some students moved into rhetoric, argument, literature, mathematics or law. Quintilian's account of Roman education, for example, progresses from grammar to exercises in narrative, proof and refutation.", + "By late antiquity, grammar, dialectic and rhetoric had been brought together. They later formed the medieval Trivium. In modern terms: understand language, test an argument and communicate a conclusion.", + "I am not arguing for the return of a Roman or medieval classroom. What I am suggesting, though, is that before people specialise, they need the tools to understand a claim, challenge it and explain what they think. AI's ability to produce fluent, persuasive answers makes that insight more relevant, not less.", + "This should change teaching and assessment. Give students an AI-generated answer and ask what it assumes. Which claims need evidence? Which sources would verify them? What would change their mind? Let students compare answers, defend a rejection and improve a weak argument. The finished response matters, but the reasoning matters more.", + "Foundational knowledge still matters because critical thinking cannot happen in a vacuum. Schools and universities should use AI, but operating a tool is not the same as understanding its output. Refusing to teach AI would make no more sense than refusing to teach search. Teaching it without teaching students how to challenge it would be worse.", + "Answers are becoming cheap. We should bring back the deliberate teaching of the skills needed to judge them.", + "What would it take to put critical thinking back at the centre of education?", + "(Cursor helped draft this)" + ], + "sources": [ + { + "label": "Quintilian, Institutio Oratoria", + "url": "https://penelope.uchicago.edu/Thayer/E/Roman/Texts/Quintilian/Institutio_Oratoria/home.html" + }, + { + "label": "Leiden University, Ancient Education", + "url": "https://studiegids-archive.leidenuniv.nl/en/courses/43807/tutorial-greek-ancient-education" + }, + { + "label": "Cambridge University Press, Dialectic after Plato and Aristotle", + "url": "https://assets.cambridge.org/97811084/71909/excerpt/9781108471909_excerpt.pdf" + }, + { + "label": "Cambridge University Press, Learning to Speak: The Art of Logic", + "url": "https://www.cambridge.org/core/books/education-in-twelfthcentury-art-and-architecture/learning-to-speak-the-art-of-logic/DCF28F27530372861C1C8B95BD8EFFB6" + } + ] + }, { "id": "quick-check-is-still-a-model-turn", "cardSeed": 13, diff --git a/docs/NOTES-SOCIAL-SHARING.md b/docs/NOTES-SOCIAL-SHARING.md index d004fc5..788ec7b 100644 --- a/docs/NOTES-SOCIAL-SHARING.md +++ b/docs/NOTES-SOCIAL-SHARING.md @@ -1,11 +1,11 @@ -# Notes social-sharing contract +# Insights social-sharing contract Checked: 2026-09-05 -Each published note has a stable page under -`https://chopwoodcarrywater.uk/notes/`. The page owns the title, summary, -preview image, dates and structured data used by search engines and social -networks. +Each published insight has a stable page under the legacy +`https://chopwoodcarrywater.uk/notes/` path. The URL remains stable, while the +public editorial label is **Insights**. The page owns the title, summary, +preview image, dates and structured data used by search engines and social networks. ## Required page contract @@ -19,6 +19,8 @@ networks. - one `BlogPosting` JSON-LD object with a 1200 × 627 `ImageObject` - LinkedIn share-offsite link using the canonical URL - Copy link button with an accessible live-region confirmation +- optional `discussionUrl` pointing to the published LinkedIn post; when set, + this becomes the primary "Join the discussion" action - entry in `sitemap.xml`, `feed.xml` and `llms.txt` LinkedIn's composer supplies the reader's post text. Do not prefill it. @@ -41,7 +43,7 @@ npm run sync npm test ``` -Run local mobile Lighthouse on the homepage and representative notes. A +Run local mobile Lighthouse on the homepage and representative insights. A Lighthouse SEO score of 100 confirms the audited checks, not search ranking. After production publication, inspect each new canonical in LinkedIn Post Inspector to refresh and verify its cached preview. diff --git a/docs/agents.html b/docs/agents.html index 209567e..16e29f7 100644 --- a/docs/agents.html +++ b/docs/agents.html @@ -25,7 +25,7 @@ - + @@ -58,7 +58,7 @@ diff --git a/docs/assets/notes/when-answers-are-cheap-judgement-matters-more-og.png b/docs/assets/notes/when-answers-are-cheap-judgement-matters-more-og.png new file mode 100644 index 0000000..559ff25 Binary files /dev/null and b/docs/assets/notes/when-answers-are-cheap-judgement-matters-more-og.png differ diff --git a/docs/content/blog.json b/docs/content/blog.json index fe72c24..1bec6aa 100644 --- a/docs/content/blog.json +++ b/docs/content/blog.json @@ -2,6 +2,65 @@ "schema": "durable-agent-harness/blog/v1", "intro": "What broke, what we locked, and what earned a place on disk \u2014 filed when the week earns it.", "posts": [ + { + "id": "when-answers-are-cheap-judgement-matters-more", + "date": "2026-09-15", + "title": "When answers are cheap, judgement matters more", + "seoTitle": "AI and education: when answers are cheap, judgement matters more", + "summary": "AI can produce answers in seconds. Education should bring critical thinking back to the centre, with the Trivium offering a useful historical lens.", + "section": "AI and education", + "tags": [ + "AI", + "education", + "critical thinking", + "Trivium", + "language", + "dialectic", + "human oversight" + ], + "customCard": true, + "imageAlt": "Six ethnically diverse sixth-form and university-age students challenge ideas around a table covered with books, notes and a laptop.", + "imageDisclosure": "The documentary-style student workshop image was generated with OpenAI under Alex Lennon's direction, then cropped and typeset as a CWCW social card.", + "body": [ + "When I started work, knowing things looked quite different. The Internet was not yet part of ordinary working life. If I needed to understand a subject, I read books. Often that meant reading a shelf of them, because that was the only way I could find the information I needed. You wouldn't believe how much slower life was back in the 90's.", + "Then search engines came along. I imagine most of you won't remember AltaVista, Ask Jeeves and all the other Google challengers. Those were heady times. Information became much easier to find, but finding it was only half the job. The useful skill was learning how to search well, compare different answers and decide which sources deserved attention.", + "This matters particularly in engineering. I am rarely looking for a solution in the abstract. I need something that will fit a real product, remain useful in the future and still be constrained enough to deliver within the time and budget available. Several answers may be technically sound. Only one may be right for the work.", + "The skills we need are not simply about accessing information. They are about judging it.", + "Over the past year, I have seen a massive change. I increasingly do not search the Internet myself. I explain what I am trying to achieve to a large language model, and it finds, compares and applies information on my behalf.", + "This change is wildly different from mere search. The agentic harness I've created uses tools across real computer systems over the Internet. It can create containers and virtual machines, configure services and DNS, and carry out the grunt work that previously consumed a substantial part of my day. I set the goals and I make the consequential decisions, but much of the mechanical effort has moved away from me.", + "That is wonderful! It frees me to spend more time on the ideas, the trade-offs and the purpose of the work. I cannot emphasise strongly enough to you how game changing this is for us.", + "This raises a difficult question for education. If AI can retrieve the information and perform the basic tasks, what should young people learn? It is tempting to say they no longer need to learn the things a machine can do. I think that is premature and potentially dangerous.", + "AI can move quickly and confidently in the wrong direction. It can misunderstand the objective, optimise the wrong thing or disappear into an impressive-looking dead end. A human still has to notice, redirect it and take responsibility for the result.", + "You cannot supervise what you cannot evaluate.", + "That is why we should bring the explicit teaching of critical thinking back to the centre of education. I do not mean occasionally telling students to be sceptical. I mean repeated practice in breaking an argument apart, identifying its assumptions, testing its evidence and explaining why a plausible answer may still be wrong.", + "The Trivium offers a useful historical lens. Ancient education was not one uniform system, and advanced learning was far from universal. But after literacy and grammar, some students moved into rhetoric, argument, literature, mathematics or law. Quintilian's account of Roman education, for example, progresses from grammar to exercises in narrative, proof and refutation.", + "By late antiquity, grammar, dialectic and rhetoric had been brought together. They later formed the medieval Trivium. In modern terms: understand language, test an argument and communicate a conclusion.", + "I am not arguing for the return of a Roman or medieval classroom. What I am suggesting, though, is that before people specialise, they need the tools to understand a claim, challenge it and explain what they think. AI's ability to produce fluent, persuasive answers makes that insight more relevant, not less.", + "This should change teaching and assessment. Give students an AI-generated answer and ask what it assumes. Which claims need evidence? Which sources would verify them? What would change their mind? Let students compare answers, defend a rejection and improve a weak argument. The finished response matters, but the reasoning matters more.", + "Foundational knowledge still matters because critical thinking cannot happen in a vacuum. Schools and universities should use AI, but operating a tool is not the same as understanding its output. Refusing to teach AI would make no more sense than refusing to teach search. Teaching it without teaching students how to challenge it would be worse.", + "Answers are becoming cheap. We should bring back the deliberate teaching of the skills needed to judge them.", + "What would it take to put critical thinking back at the centre of education?", + "(Cursor helped draft this)" + ], + "sources": [ + { + "label": "Quintilian, Institutio Oratoria", + "url": "https://penelope.uchicago.edu/Thayer/E/Roman/Texts/Quintilian/Institutio_Oratoria/home.html" + }, + { + "label": "Leiden University, Ancient Education", + "url": "https://studiegids-archive.leidenuniv.nl/en/courses/43807/tutorial-greek-ancient-education" + }, + { + "label": "Cambridge University Press, Dialectic after Plato and Aristotle", + "url": "https://assets.cambridge.org/97811084/71909/excerpt/9781108471909_excerpt.pdf" + }, + { + "label": "Cambridge University Press, Learning to Speak: The Art of Logic", + "url": "https://www.cambridge.org/core/books/education-in-twelfthcentury-art-and-architecture/learning-to-speak-the-art-of-logic/DCF28F27530372861C1C8B95BD8EFFB6" + } + ] + }, { "id": "quick-check-is-still-a-model-turn", "cardSeed": 13, diff --git a/docs/feed.xml b/docs/feed.xml index 8b5e3c4..1214755 100644 --- a/docs/feed.xml +++ b/docs/feed.xml @@ -1,11 +1,19 @@ - Chop Wood Carry Water notes + Chop Wood Carry Water insights https://chopwoodcarrywater.uk/ - Engineering notes on durable working relationships with AI coding agents. + Engineering insights on durable working relationships with AI coding agents. en-gb - Sat, 12 Sep 2026 00:00:00 +0000 + Tue, 15 Sep 2026 00:00:00 +0000 + + When answers are cheap, judgement matters more + https://chopwoodcarrywater.uk/notes/when-answers-are-cheap-judgement-matters-more.html + https://chopwoodcarrywater.uk/notes/when-answers-are-cheap-judgement-matters-more.html + Tue, 15 Sep 2026 00:00:00 +0000 + AI can produce answers in seconds. Education should bring critical thinking back to the centre, with the Trivium offering a useful historical lens. + When I started work, knowing things looked quite different. The Internet was not yet part of ordinary working life. If I needed to understand a subject, I read books. Often that meant reading a shelf of them, because that was the only way I could find the information I needed. You wouldn't believe how much slower life was back in the 90's.

Then search engines came along. I imagine most of you won't remember AltaVista, Ask Jeeves and all the other Google challengers. Those were heady times. Information became much easier to find, but finding it was only half the job. The useful skill was learning how to search well, compare different answers and decide which sources deserved attention.

This matters particularly in engineering. I am rarely looking for a solution in the abstract. I need something that will fit a real product, remain useful in the future and still be constrained enough to deliver within the time and budget available. Several answers may be technically sound. Only one may be right for the work.

The skills we need are not simply about accessing information. They are about judging it.

Over the past year, I have seen a massive change. I increasingly do not search the Internet myself. I explain what I am trying to achieve to a large language model, and it finds, compares and applies information on my behalf.

This change is wildly different from mere search. The agentic harness I've created uses tools across real computer systems over the Internet. It can create containers and virtual machines, configure services and DNS, and carry out the grunt work that previously consumed a substantial part of my day. I set the goals and I make the consequential decisions, but much of the mechanical effort has moved away from me.

That is wonderful! It frees me to spend more time on the ideas, the trade-offs and the purpose of the work. I cannot emphasise strongly enough to you how game changing this is for us.

This raises a difficult question for education. If AI can retrieve the information and perform the basic tasks, what should young people learn? It is tempting to say they no longer need to learn the things a machine can do. I think that is premature and potentially dangerous.

AI can move quickly and confidently in the wrong direction. It can misunderstand the objective, optimise the wrong thing or disappear into an impressive-looking dead end. A human still has to notice, redirect it and take responsibility for the result.

You cannot supervise what you cannot evaluate.

That is why we should bring the explicit teaching of critical thinking back to the centre of education. I do not mean occasionally telling students to be sceptical. I mean repeated practice in breaking an argument apart, identifying its assumptions, testing its evidence and explaining why a plausible answer may still be wrong.

The Trivium offers a useful historical lens. Ancient education was not one uniform system, and advanced learning was far from universal. But after literacy and grammar, some students moved into rhetoric, argument, literature, mathematics or law. Quintilian's account of Roman education, for example, progresses from grammar to exercises in narrative, proof and refutation.

By late antiquity, grammar, dialectic and rhetoric had been brought together. They later formed the medieval Trivium. In modern terms: understand language, test an argument and communicate a conclusion.

I am not arguing for the return of a Roman or medieval classroom. What I am suggesting, though, is that before people specialise, they need the tools to understand a claim, challenge it and explain what they think. AI's ability to produce fluent, persuasive answers makes that insight more relevant, not less.

This should change teaching and assessment. Give students an AI-generated answer and ask what it assumes. Which claims need evidence? Which sources would verify them? What would change their mind? Let students compare answers, defend a rejection and improve a weak argument. The finished response matters, but the reasoning matters more.

Foundational knowledge still matters because critical thinking cannot happen in a vacuum. Schools and universities should use AI, but operating a tool is not the same as understanding its output. Refusing to teach AI would make no more sense than refusing to teach search. Teaching it without teaching students how to challenge it would be worse.

Answers are becoming cheap. We should bring back the deliberate teaching of the skills needed to judge them.

What would it take to put critical thinking back at the centre of education?

(Cursor helped draft this)

]]>
+
A quick check is still a model turn https://chopwoodcarrywater.uk/notes/quick-check-is-still-a-model-turn.html diff --git a/docs/index.html b/docs/index.html index 7ec722e..4b924ae 100644 --- a/docs/index.html +++ b/docs/index.html @@ -40,7 +40,7 @@ - + @@ -171,7 +171,7 @@ Explore Cases Measure - Blog + Insights Starters About @@ -246,7 +246,7 @@

Durable Agent Harness

@@ -307,8 +307,8 @@

Then pick another door

Clocks and rituals — no slide-deck multipliers. - Notes - Blog + Read + Insights What broke, what locked, what earned a place on disk. @@ -515,10 +515,17 @@

Starter kit

-

Blog

+

Insights

+ + - +
@@ -563,7 +565,7 @@

Don’t backdate — pr

About

-

I’m Alex Lennon (@embedded_iot). This notebook is engineering notes in public — clocks, playbook, patterns, cases, starters, and a short blog. Steal what helps; measure your own results. AI-assisted; use at your own risk.

+

I’m Alex Lennon (@embedded_iot). This notebook is engineering practice and insights in public — clocks, playbook, patterns, cases and starters. Steal what helps; measure your own results. AI-assisted; use at your own risk.

Public baseline v1.2.0 (2026-09-12) — token-disciplined, Codex-first, Cursor-compatible — baseline diff --git a/docs/llms.txt b/docs/llms.txt index 138b6be..944513b 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -7,7 +7,8 @@ - [Agents: start here](https://chopwoodcarrywater.uk/agents.html) - [RSS feed](https://chopwoodcarrywater.uk/feed.xml) -## Notes +## Insights +- [When answers are cheap, judgement matters more](https://chopwoodcarrywater.uk/notes/when-answers-are-cheap-judgement-matters-more.html) - [A quick check is still a model turn](https://chopwoodcarrywater.uk/notes/quick-check-is-still-a-model-turn.html) - [The cockpit changed. The work did not.](https://chopwoodcarrywater.uk/notes/the-cockpit-changed-the-work-did-not.html) - [The sensor must cost less than the work](https://chopwoodcarrywater.uk/notes/sensors-cheaper-than-the-work.html) diff --git a/docs/notes/chop-wood-carry-water.html b/docs/notes/chop-wood-carry-water.html index 0aece3b..5236deb 100644 --- a/docs/notes/chop-wood-carry-water.html +++ b/docs/notes/chop-wood-carry-water.html @@ -9,7 +9,7 @@ - + @@ -23,7 +23,7 @@ - + @@ -37,7 +37,7 @@ - + + + + +

+ + + Chop Wood Carry WaterDurable Agent Harness + + +
+
+
+
+ +

AI and education · 15th September 2026

+

When answers are cheap, judgement matters more

+ +

AI can produce answers in seconds. Education should bring critical thinking back to the centre, with the Trivium offering a useful historical lens.

+ +
+
+ Six ethnically diverse sixth-form and university-age students challenge ideas around a table covered with books, notes and a laptop. +
The documentary-style student workshop image was generated with OpenAI under Alex Lennon's direction, then cropped and typeset as a CWCW social card.
+
+
+

When I started work, knowing things looked quite different. The Internet was not yet part of ordinary working life. If I needed to understand a subject, I read books. Often that meant reading a shelf of them, because that was the only way I could find the information I needed. You wouldn't believe how much slower life was back in the 90's.

+

Then search engines came along. I imagine most of you won't remember AltaVista, Ask Jeeves and all the other Google challengers. Those were heady times. Information became much easier to find, but finding it was only half the job. The useful skill was learning how to search well, compare different answers and decide which sources deserved attention.

+

This matters particularly in engineering. I am rarely looking for a solution in the abstract. I need something that will fit a real product, remain useful in the future and still be constrained enough to deliver within the time and budget available. Several answers may be technically sound. Only one may be right for the work.

+

The skills we need are not simply about accessing information. They are about judging it.

+

Over the past year, I have seen a massive change. I increasingly do not search the Internet myself. I explain what I am trying to achieve to a large language model, and it finds, compares and applies information on my behalf.

+

This change is wildly different from mere search. The agentic harness I've created uses tools across real computer systems over the Internet. It can create containers and virtual machines, configure services and DNS, and carry out the grunt work that previously consumed a substantial part of my day. I set the goals and I make the consequential decisions, but much of the mechanical effort has moved away from me.

+

That is wonderful! It frees me to spend more time on the ideas, the trade-offs and the purpose of the work. I cannot emphasise strongly enough to you how game changing this is for us.

+

This raises a difficult question for education. If AI can retrieve the information and perform the basic tasks, what should young people learn? It is tempting to say they no longer need to learn the things a machine can do. I think that is premature and potentially dangerous.

+

AI can move quickly and confidently in the wrong direction. It can misunderstand the objective, optimise the wrong thing or disappear into an impressive-looking dead end. A human still has to notice, redirect it and take responsibility for the result.

+

You cannot supervise what you cannot evaluate.

+

That is why we should bring the explicit teaching of critical thinking back to the centre of education. I do not mean occasionally telling students to be sceptical. I mean repeated practice in breaking an argument apart, identifying its assumptions, testing its evidence and explaining why a plausible answer may still be wrong.

+

The Trivium offers a useful historical lens. Ancient education was not one uniform system, and advanced learning was far from universal. But after literacy and grammar, some students moved into rhetoric, argument, literature, mathematics or law. Quintilian's account of Roman education, for example, progresses from grammar to exercises in narrative, proof and refutation.

+

By late antiquity, grammar, dialectic and rhetoric had been brought together. They later formed the medieval Trivium. In modern terms: understand language, test an argument and communicate a conclusion.

+

I am not arguing for the return of a Roman or medieval classroom. What I am suggesting, though, is that before people specialise, they need the tools to understand a claim, challenge it and explain what they think. AI's ability to produce fluent, persuasive answers makes that insight more relevant, not less.

+

This should change teaching and assessment. Give students an AI-generated answer and ask what it assumes. Which claims need evidence? Which sources would verify them? What would change their mind? Let students compare answers, defend a rejection and improve a weak argument. The finished response matters, but the reasoning matters more.

+

Foundational knowledge still matters because critical thinking cannot happen in a vacuum. Schools and universities should use AI, but operating a tool is not the same as understanding its output. Refusing to teach AI would make no more sense than refusing to teach search. Teaching it without teaching students how to challenge it would be worse.

+

Answers are becoming cheap. We should bring back the deliberate teaching of the skills needed to judge them.

+

What would it take to put critical thinking back at the centre of education?

+

(Cursor helped draft this)

+
+ +
+
+

Share this insight

+

Share the canonical article link with its Chop Wood Carry Water preview card.

+
+ Share on LinkedIn + +
+ +
+

All insights

+
+
+
+
+ + + diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 030660e..05dd4c3 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -2,7 +2,7 @@ https://chopwoodcarrywater.uk/ - 2026-09-12 + 2026-09-15 1.0 @@ -10,6 +10,11 @@ 2026-09-05 0.8 + + https://chopwoodcarrywater.uk/notes/when-answers-are-cheap-judgement-matters-more.html + 2026-09-15 + 0.9 + https://chopwoodcarrywater.uk/notes/quick-check-is-still-a-model-turn.html 2026-09-12 diff --git a/docs/styles.css b/docs/styles.css index 86127e5..3ad4a9a 100644 --- a/docs/styles.css +++ b/docs/styles.css @@ -1163,6 +1163,13 @@ section h2 { box-shadow: var(--shadow); } +.note-hero figcaption { + margin-top: 0.7rem; + color: var(--graphite); + font-family: var(--font-label); + font-size: 0.82rem; +} + .note-body { max-width: 46rem; color: #283833; @@ -1175,6 +1182,34 @@ section h2 { margin: 0 0 1.35rem; } +.note-sources { + max-width: 46rem; + margin-top: 2.5rem; + padding-top: 1.5rem; + border-top: 1px solid var(--line); +} + +.note-sources h2 { + margin: 0 0 0.8rem; + color: var(--carbon); + font-family: var(--font-display); + font-size: 1.2rem; +} + +.note-sources ul { + margin: 0; + padding-left: 1.25rem; +} + +.note-sources li { + margin-bottom: 0.55rem; +} + +.note-sources a { + color: var(--water); + text-underline-offset: 0.18em; +} + .note-footer { margin-top: 3rem; } diff --git a/package.json b/package.json index 7aaef71..3c93519 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,14 @@ "version": "1.2.0", "description": "Public notebook on building a durable AI agent harness", "scripts": { + "insight:new": "python3 scripts/new_insight.py", "sync": "bash scripts/sync-content.sh", "cards": "python3 scripts/render_home_social_card.py && python3 scripts/render_note_social_cards.py", "privacy": "bash scripts/privacy-check.sh", "serve": "npx --yes serve docs -l 4173", "test:e2e": "npx playwright test", - "test": "npm run sync && npm run privacy && npm run test:e2e", + "test:insight": "python3 tests/test_insight_tools.py", + "test": "npm run sync && npm run privacy && npm run test:insight && npm run test:e2e", "check": "npm test" }, "devDependencies": { diff --git a/scripts/new_insight.py b/scripts/new_insight.py new file mode 100644 index 0000000..f9cbafc --- /dev/null +++ b/scripts/new_insight.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +"""Create a private CWCW insight draft package from text material.""" + +from __future__ import annotations + +import argparse +import json +import re +from datetime import date +from pathlib import Path + + +DEFAULT_DRAFT_ROOT = Path.home() / "data_drive" / "dd" / "personal" / "cwcw-insights" + + +def slugify(value: str) -> str: + slug = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-") + if not slug: + raise ValueError("Title must contain at least one letter or number") + return slug + + +def create_package( + *, title: str, section: str, material: str, created: str, output_root: Path +) -> Path: + slug = slugify(title) + target = output_root.expanduser().resolve() / slug + target.mkdir(parents=True, exist_ok=False) + (target / "assets").mkdir() + (target / "output").mkdir() + + manifest = { + "status": "draft-private", + "id": slug, + "created": created, + "title": title, + "seoTitle": "", + "summary": "", + "section": section, + "tags": [], + "customCard": False, + "imageAlt": "", + "imageDisclosure": "", + "body": [], + "sources": [], + "discussionUrl": "", + } + (target / "manifest.json").write_text( + json.dumps(manifest, indent=2, ensure_ascii=False) + "\n" + ) + draft = f"""# {title} + +Status: private working draft +Created: {created} +Section: {section} + +## Working claim + +[State the single consequential claim in one sentence.] + +## Synopsis + +[Explain what this insight is about and why it matters.] + +## CWCW insight draft + +[Move from lived experience to the question, claim, evidence and practical proposal.] + +## Research and caveats + +- [Claim to verify] +- [Source] +- [Caveat that prevents a misleadingly neat story] + +## LinkedIn draft + +[Open the argument and earn the click rather than repeating the complete insight.] + +(Cursor helped draft this) + +## Image brief + +[Subject, human activity, setting, composition, diversity and negative space.] + +## Publication metadata + +- Slug: `{slug}` +- SEO title: +- Summary: +- Tags: +- Image alt: +- Image disclosure: +- LinkedIn discussion URL: [add after the post is live] + +## Raw material + +```text +{material.rstrip()} +``` + +## Publication checklist + +- [ ] Alex approved the final insight +- [ ] Facts and sources checked +- [ ] CWCW visual reviewed at 1200 x 627 +- [ ] `content/blog.json` entry prepared +- [ ] Build, privacy and browser tests passed +- [ ] Production canonical and image bytes verified +- [ ] LinkedIn preview inspected +- [ ] `discussionUrl` added after posting +""" + (target / "draft-package.md").write_text(draft) + return target + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--title", required=True) + parser.add_argument("--section", default="Working practice") + parser.add_argument("--date", default=date.today().isoformat()) + parser.add_argument("--output-root", type=Path, default=DEFAULT_DRAFT_ROOT) + source = parser.add_mutually_exclusive_group(required=True) + source.add_argument("--text", help="A thought or rough note supplied directly") + source.add_argument("--input", type=Path, help="UTF-8 text transcript or note") + return parser.parse_args() + + +def main() -> None: + args = parse_args() + if args.input: + material = args.input.expanduser().read_text() + else: + material = args.text + target = create_package( + title=args.title, + section=args.section, + material=material, + created=args.date, + output_root=args.output_root, + ) + print(target) + + +if __name__ == "__main__": + main() diff --git a/scripts/render_note_social_cards.py b/scripts/render_note_social_cards.py index b8112e4..b28a1d1 100755 --- a/scripts/render_note_social_cards.py +++ b/scripts/render_note_social_cards.py @@ -74,6 +74,16 @@ def fit_lines(draw: ImageDraw.ImageDraw, text: str, max_width: int) -> tuple[lis def draw_card(post: dict, index: int) -> None: + path = OUT / f"{post['id']}-og.png" + if post.get("customCard"): + if not path.exists(): + raise FileNotFoundError(f"Custom card is missing: {path}") + with Image.open(path) as custom: + if custom.size != (W, H): + raise ValueError(f"Custom card must be {W}x{H}: {path} is {custom.size}") + print(f"{path.relative_to(ROOT)} custom card preserved") + return + image = background(index) draw = ImageDraw.Draw(image) draw.text((64, 44), "CHOP WOOD CARRY WATER", font=font(SPACE, 23), fill=CLOUD) @@ -118,7 +128,6 @@ def draw_card(post: dict, index: int) -> None: draw.ellipse((905, 245, 1085, 425), outline=LIME, width=3) draw.rectangle((935, 275, 1055, 395), outline=TIMBER, width=6) - path = OUT / f"{post['id']}-og.png" image.save(path, optimize=True) print(f"{path.relative_to(ROOT)} {W}×{H} {path.stat().st_size} bytes") diff --git a/scripts/render_notes.py b/scripts/render_notes.py index dfeb499..e1d45e9 100755 --- a/scripts/render_notes.py +++ b/scripts/render_notes.py @@ -8,13 +8,15 @@ import json from datetime import date, datetime, timezone from pathlib import Path -from urllib.parse import quote +from urllib.parse import quote, urlparse ROOT = Path(__file__).resolve().parents[1] CONTENT = ROOT / "content" / "blog.json" DOCS = ROOT / "docs" NOTES = DOCS / "notes" ORIGIN = "https://chopwoodcarrywater.uk" +INSIGHTS_START = "" +INSIGHTS_END = "" def ordinal(day: int) -> str: @@ -38,11 +40,54 @@ def image_url(post: dict) -> str: return f"{ORIGIN}/assets/notes/{post['id']}-og.png" +def linkedin_discussion_url(post: dict) -> str | None: + value = post.get("discussionUrl", "").strip() + if not value: + return None + parsed = urlparse(value) + hostname = parsed.hostname or "" + if parsed.scheme != "https" or not ( + hostname == "linkedin.com" or hostname.endswith(".linkedin.com") + ): + raise ValueError(f"discussionUrl must be an HTTPS LinkedIn URL: {value}") + return value + + +def render_homepage_insights(posts: list[dict], limit: int = 6) -> None: + path = DOCS / "index.html" + document = path.read_text() + if document.count(INSIGHTS_START) != 1 or document.count(INSIGHTS_END) != 1: + raise ValueError("Homepage insight fallback markers are missing or duplicated") + cards = [] + for post in posts[:limit]: + post_id = html.escape(post["id"]) + title = html.escape(post["title"]) + summary = html.escape(post["summary"]) + published = html.escape(post["date"]) + cards.append(f""" """) + before, remainder = document.split(INSIGHTS_START, 1) + _, after = remainder.split(INSIGHTS_END, 1) + rendered = f"{before}{INSIGHTS_START}\n{chr(10).join(cards)}\n {INSIGHTS_END}{after}" + path.write_text(rendered) + + def render_note(post: dict) -> str: url = canonical(post) image = image_url(post) title = html.escape(post["title"]) + seo_title = html.escape(post.get("seoTitle", post["title"])) summary = html.escape(post["summary"]) + social_image_alt = html.escape( + post.get("imageAlt", f"Chop Wood Carry Water insight: {post['title']}") + ) + hero_image_alt = html.escape( + post.get("imageAlt", f"Illustrated title card for {post['title']}") + ) published = post["date"] modified = post.get("dateModified", published) tags = post.get("tags", ["AI agents", "engineering practice"]) @@ -50,6 +95,22 @@ def render_note(post: dict) -> str: f' ' for tag in tags ) paragraphs = "\n".join(f"

{html.escape(p)}

" for p in post["body"]) + sources = "" + if post.get("sources"): + source_items = "\n".join( + f'
  • {html.escape(source["label"])}
  • ' + for source in post["sources"] + ) + sources = f""" + """ + disclosure = "" + if post.get("imageDisclosure"): + disclosure = f'\n
    {html.escape(post["imageDisclosure"])}
    ' provenance = "" if post.get("sourceUrl"): provenance = f""" @@ -96,35 +157,44 @@ def render_note(post: dict) -> str: "mainEntityOfPage": url, "license": "https://creativecommons.org/licenses/by-sa/4.0/", } - linkedin = ( + linkedin_share = ( "https://www.linkedin.com/sharing/share-offsite/?url=" + quote(url, safe="") ) + discussion_url = linkedin_discussion_url(post) + if discussion_url: + share_prompt = "Continue the conversation around this insight on LinkedIn." + primary_href = html.escape(discussion_url) + primary_label = "Join the discussion on LinkedIn" + else: + share_prompt = "Share the canonical article link with its Chop Wood Carry Water preview card." + primary_href = linkedin_share + primary_label = "Share on LinkedIn" return f""" - {title} — Chop Wood Carry Water + {seo_title} — Chop Wood Carry Water - + - + - + @@ -133,10 +203,10 @@ def render_note(post: dict) -> str: - + - + @@ -148,12 +218,12 @@ def render_note(post: dict) -> str: Chop Wood Carry WaterDurable Agent Harness - +
    - +

    {html.escape(post.get("section", "Engineering practice"))} · {display_date(published)}

    {title}

    @@ -162,22 +232,22 @@ def render_note(post: dict) -> str:
    Illustrated title card for {title} + alt="{hero_image_alt}" fetchpriority="high">{disclosure}
    {paragraphs} -
    +
    {sources} @@ -231,9 +301,9 @@ def render_feed(posts: list[dict]) -> str: return f""" - Chop Wood Carry Water notes + Chop Wood Carry Water insights {ORIGIN}/ - Engineering notes on durable working relationships with AI coding agents. + Engineering insights on durable working relationships with AI coding agents. en-gb {email.utils.format_datetime(build_date)} {chr(10).join(items)} @@ -277,7 +347,7 @@ def render_llms(posts: list[dict]) -> str: - [Agents: start here]({ORIGIN}/agents.html) - [RSS feed]({ORIGIN}/feed.xml) -## Notes +## Insights {links} """ @@ -297,7 +367,8 @@ def main() -> None: (DOCS / "feed.xml").write_text(render_feed(posts)) (DOCS / "sitemap.xml").write_text(render_sitemap(posts)) (DOCS / "llms.txt").write_text(render_llms(posts)) - print(f"Rendered {len(posts)} notes, feed.xml, sitemap.xml and llms.txt") + render_homepage_insights(posts) + print(f"Rendered {len(posts)} insights, feed.xml, sitemap.xml and llms.txt") if __name__ == "__main__": diff --git a/tests/notes.spec.js b/tests/notes.spec.js index 2ed9153..951ce52 100644 --- a/tests/notes.spec.js +++ b/tests/notes.spec.js @@ -35,6 +35,8 @@ test.describe("shareable notes", () => { await expect(page.locator("main h1")).toHaveCount(1); await expect(page.locator("main h1")).toHaveText(post.title); + await expect(page.locator("nav a", { hasText: "Insights" })).toHaveCount(1); + await expect(page.getByText("Share this insight", { exact: true })).toBeVisible(); await expect(page.locator('link[rel="canonical"]')).toHaveAttribute("href", canonical); await expect(page.locator('meta[name="description"]')).toHaveAttribute("content", post.summary); expect(post.summary.length).toBeGreaterThanOrEqual(50); @@ -65,11 +67,21 @@ test.describe("shareable notes", () => { const card = page.locator(".note-hero img"); await expect(card).toHaveJSProperty("naturalWidth", 1200); await expect(card).toHaveJSProperty("naturalHeight", 627); - await expect(card).toHaveAttribute("alt", /Illustrated title card/); - await expect(page.locator('a[href*="linkedin.com/sharing/share-offsite/"]')).toHaveAttribute( - "href", - new RegExp(encodeURIComponent(canonical).replace(/[.*+?^${}()|[\]\\]/g, "\\$&")), + await expect(card).toHaveAttribute( + "alt", + post.imageAlt || `Illustrated title card for ${post.title}`, ); + if (post.discussionUrl) { + await expect(page.getByRole("link", { name: "Join the discussion on LinkedIn" })).toHaveAttribute( + "href", + post.discussionUrl, + ); + } else { + await expect(page.locator('a[href*="linkedin.com/sharing/share-offsite/"]')).toHaveAttribute( + "href", + new RegExp(encodeURIComponent(canonical).replace(/[.*+?^${}()|[\]\\]/g, "\\$&")), + ); + } const copy = page.locator(".copy-link"); await expect(copy).toHaveAttribute("data-copy-url", canonical); diff --git a/tests/smoke.spec.js b/tests/smoke.spec.js index 83901e4..6883ccd 100644 --- a/tests/smoke.spec.js +++ b/tests/smoke.spec.js @@ -102,7 +102,7 @@ test.describe("Durable Agent Harness site", () => { await expect(page.getByTestId("site-notice")).toContainText("own risk"); await expect(page.getByTestId("site-notice")).toContainText("CC BY-SA 4.0"); await expect(page.getByTestId("hour-paths")).toContainText("Playbook"); - await expect(page.getByTestId("hour-paths")).toContainText("Blog"); + await expect(page.getByTestId("hour-paths")).toContainText("Insights"); await expect(page.getByTestId("hour-paths")).toContainText("Kaizen → Toyota Way Practices"); await expect(page.getByTestId("hour-paths")).toContainText("Preloop + OpenRouter"); await expect(page.getByTestId("hour-paths")).toContainText("Channels & lean"); @@ -199,7 +199,7 @@ test.describe("Durable Agent Harness site", () => { test("blog posts render in first person", async ({ page }) => { await page.goto("/#blog"); await expect(page.locator("body")).toHaveAttribute("data-ready", "true"); - await expect(page.getByTestId("section-blog")).toContainText("Blog"); + await expect(page.getByTestId("section-blog")).toContainText("Insights"); await expect(page.getByTestId("section-blog")).toContainText("What broke"); await expect(page.getByTestId("section-blog")).not.toContainText("Not a content calendar"); await expect(page.getByTestId("blog-v1-baseline")).toContainText("v1.0"); diff --git a/tests/test_insight_tools.py b/tests/test_insight_tools.py new file mode 100644 index 0000000..5aaedd3 --- /dev/null +++ b/tests/test_insight_tools.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 + +import importlib.util +import tempfile +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def load_module(name: str, path: Path): + spec = importlib.util.spec_from_file_location(name, path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +new_insight = load_module("new_insight", ROOT / "scripts" / "new_insight.py") +render_notes = load_module("render_notes", ROOT / "scripts" / "render_notes.py") + + +class InsightToolsTest(unittest.TestCase): + def test_private_draft_package_is_complete_and_non_destructive(self): + with tempfile.TemporaryDirectory() as directory: + target = new_insight.create_package( + title="A Useful New Insight", + section="Education", + material="A rough thought from today.", + created="2026-09-15", + output_root=Path(directory), + ) + self.assertEqual(target.name, "a-useful-new-insight") + self.assertIn("## Raw material", (target / "draft-package.md").read_text()) + self.assertTrue((target / "manifest.json").exists()) + self.assertTrue((target / "assets").is_dir()) + self.assertTrue((target / "output").is_dir()) + with self.assertRaises(FileExistsError): + new_insight.create_package( + title="A Useful New Insight", + section="Education", + material="Do not overwrite the first draft.", + created="2026-09-15", + output_root=Path(directory), + ) + + def test_linkedin_discussion_becomes_primary_action(self): + post = { + "id": "example", + "date": "2026-09-15", + "title": "Example insight", + "summary": "A sufficiently descriptive summary for testing the insight renderer.", + "body": ["A useful body."], + "discussionUrl": "https://www.linkedin.com/posts/example", + } + rendered = render_notes.render_note(post) + self.assertIn("Join the discussion on LinkedIn", rendered) + self.assertIn(post["discussionUrl"], rendered) + self.assertNotIn("Share on LinkedIn", rendered) + + def test_non_linkedin_discussion_url_is_rejected(self): + with self.assertRaises(ValueError): + render_notes.linkedin_discussion_url( + {"discussionUrl": "https://example.com/not-linkedin"} + ) + + +if __name__ == "__main__": + unittest.main()