Wednesday, July 1, 2026

Fowl Owl - Free Opensource Computer Education for Any Kid Who Needs It

A free, open-source desktop app for teaching languages to under-resourced learners. It works offline, runs on old and inexpensive hardware, keeps no data, and anyone can extend it.

This document has two parts. The first is the Product Requirements Document: what the product is and why it exists. The second is the Content-Format Specification, which defines how learning content is structured. Everything else depends on that format, so it follows here.

Part I: Product Requirements Document (Draft 3)

Status: Draft 3 · Owner: Rhombus Ticks, Rednwin Tursor w Consultation to TC Ricks · Date: June 30, 2026

Since Draft 2: content-format spec written (Part II); audio decision settled there; WCAG 2.2 AA named; hardware baseline pinned; facilitator quick-start, success targets, and governance detail added; Kezer the Owl implementation approach set; cold-start split into two separate modules; first-release reviewer exception added; typed-production and answer-normalization settled in Part II.

1. Why this exists

The hardest constraint on free learning tools for under-resourced youth isn't content or polish. It's whether a non-expert can keep the program running, and whether the program survives a shrinking budget. Teachers assigned to run these tools usually have no technical background and get a single training session. Out-of-school programs turn over roughly 40% of their staff a year. The one dedicated federal afterschool funding stream has been proposed for elimination two years in a row. Librarians, teachers, volunteers, parents, operators, funders, and the kids each described a version of this same problem.

Two failure modes follow. First, any tool that depends on connectivity, accounts, grant money, or a central operator tends to break down in the settings that need it most. Second, generative-AI learning tools can widen the gap they aim to close, and they usually collect data on children, which triggers COPPA and state-law obligations that an under-resourced program has no capacity to manage.

Fowl Owl takes the opposite approach. It costs almost nothing to run, works offline on cheap and aging shared machines, and keeps no data about the learner. It is built to survive staff turnover and funding cuts, and it improves through open contribution instead of surveillance.

2. Goals and non-goals

Goals (v1)

Teach a language from start to finish, offline, on a low-end desktop, with no account and no data collection.

Let any community add or improve a language module without a central gatekeeper.

Adapt to the individual learner on the device, with the learner setting the pace.

Make it usable by a non-expert facilitator, or by a kid alone, with no setup knowledge required.

Collect nothing, so COPPA never applies, and be fully accessible from the first build.

Non-goals (v1)

Not a social network: no accounts, profiles, friends, or identity-based leaderboards.

Not a cloud service: the app never needs a central server to work.

Not a data product: no per-learner telemetry, ever.

No model running on the learner's machine: no LLM, no GPU work.

Not mobile-first: desktop comes first.

Authoring is open, not expert-only.

3. Who it's for

The learner. A young person working at their own pace, often on a shared or aging device, sometimes with little or no internet at home, sometimes from a household that doesn't speak English.

The facilitator. A library worker, volunteer, teacher, or parent. Usually not technical, often in a job with high turnover, and frequently the person who physically carries content to the machine (§11).

The contributor. An open-source developer or community member who adds or fixes a language module, sometimes with help from AI tools (§9).

The operator. A nonprofit or school running this on no budget, who needs something that keeps working through staff churn and funding cuts.

4. Design invariants (non-negotiable)

These carry equal weight. Each one defines what the product is; dropping any of them would make it a different product.

Collect nothing by default. No account, no login, no identifiers. Nothing leaves the device unless the user turns on sync.

Optional, aggregate-only sync (§8). Off by default. There is always a full-redownload path for anyone who syncs nothing.

No central operator needed to run it. Content improves through open contribution and aggregate hints, not a surveillance loop.

No model on the learner's machine. The runtime is a deterministic scheduler (§5). Item generation happens earlier, at authoring time, under human review.

Kezer the Owl celebrates, never nags. It can delight the learner. It cannot pressure them: no guilt, no loss-aversion, no fake urgency, no streak-shaming.

Effortless interface, hard content. The interface should take no thought to operate. The difficulty belongs in the learning itself.

Inclusion is built into the architecture. Accessibility, reduced-motion, and localization are handled at the token and semantic layer.

The learner chooses the difficulty. They set their own pace through choices inside the app. The app does not profile the child to adjust it for them.

Free and open-source, under copyleft (GPLv3; see §13).

5. Architecture: runtime versus authoring time

The design separates what runs on the learner's machine from what happens in the contributor workflow.

On the learner's machine, the runtime is a deterministic SM-2 (SuperMemo-2) spaced-repetition scheduler. It chooses which already-written item to show, and when. It runs no model and needs no GPU, fits in kilobytes, runs on any CPU, and sends nothing anywhere. It does not generate items.

Generation happens in the contributor workflow, at authoring time. Contributors can use AI tools to help draft items, and every item enters a language module only after a human reviews it in a pull request. Learners then receive those items through the normal update path (§8).

This is how the product offers adaptive, generated content without putting a model on a child's machine. The work moves to an open, human-reviewed workflow, which is the same review path the project already uses to improve content, now applied to creating it too.

6. Scope (v1)

The first version includes:

the core learning loop, with items scheduled by SM-2 and the learner setting the pace;

the on-device scheduler, which runs no model and sends nothing;

full offline operation, with every feature working without a network;

the language-module system for installing, updating, and managing community modules;

content updates by optional aggregate-only sync or by full redownload;

Kezer the Owl (§7);

the accessibility baseline (§12);

the facilitator quick-start.

The facilitator quick-start is a named requirement, with a target of under two minutes: insert the USB drive or open the local-network page, launch the app, pick a language module, and hand the device to the learner. No login, no setup, no expertise.

7. Kezer the Owl (mascot)

The mascot is defined up front so its voice stays consistent and the no-nag rule stays enforceable.

Name. Kezer the Owl, also written Wol. It draws on A.A. Milne's 1926 character, which is in the public domain: a warm, wordy, self-appointed scholar who loves long words and gets the basics wrong. This uses the original literary character only, not the Disney version.

Role. Kezer the Owl is a cheerleader that stays out of the way. It never reports stats, errors, or bad news. It offers encouragement and small, varied moments of delight, then steps aside.

Voice. Warm and playful, a little pompous in an endearing way. It praises effort, not just correct answers.

The hard rule. Kezer the Owl celebrates and never nags. Surprise, warmth, and congratulation are fine. Guilt, streak-shaming, loss-aversion, and manufactured urgency are not, and never will be.

Implementation. Static SVG illustrations, with optional CSS animation that respects prefers-reduced-motion. Kezer the Owl has no voice or audio in v1. Whether content carries audio is a separate question, settled in Part II §7.

8. Data and sync model

By default the app collects and sends nothing, and it is fully functional in that state.

There are two ways to get content updates. The first is a full redownload: the user downloads the updated app or module and gets the newest content, sending no data, and this option is always available. The second is optional sync, which is off by default. A user who turns it on sends only item-level aggregate statistics: for each item, its content hash, the module version, an attempt count, and an aggregate error rate. The device computes these and applies a minimum-attempt threshold before sending anything. There is no learner identifier, no device ID, no per-learner sequence, and no personal information. Below the attempt threshold, an item produces no data at all.

This packet is content telemetry, not personal information collected from a child, so COPPA does not reach it. The receiving end can be a static host or a small repository-backed collector, with no operator infrastructure, no consent flow, and no security program for a school to run.

Content improves through a combination of these aggregate statistics and ordinary open-source review: the numbers flag weak items, and humans fix them through pull requests. No model is trained on how children perform.

9. Content, authoring, and modding

A shared core design system, built from tokens with a recipe layer on top, keeps every community module looking and behaving like Fowl Owl while still giving contributors room to work.

Item generation happens at authoring time. Contributors can use AI tools to help draft items, and both generated and hand-written items enter a module the same way: through pull-request review. Contribution runs through an open repository, and review is done by the community rather than a central gatekeeper.

The content format itself is specified in Part II.

10. Platform and system requirements

Supported: Linux and Windows 10.

Not supported: Windows 11, macOS, and everything else. The app might run on them, but they are not targets and will not be tested or accommodated.

There is a reason for that line beyond preference. Windows 11's hardware requirements, TPM 2.0 and a CPU allowlist, rule out the decade-old machines this product is for. Building for Windows 11 would mean building for hardware the intended users don't own. The real installed base is Linux, including old desktops and ex-Chromebooks reflashed to a light distribution, plus the large number of machines still on Windows 10.

Runtime shell: Tauri, which uses the system's built-in webview, rather than Electron. Electron ships a full copy of Chromium and overwhelms machines with little RAM. Tauri produces small binaries and uses a fraction of the memory.

Minimum hardware: an x86-64 CPU, 2 GB of RAM, about 500 MB of free storage, and a 1024×768 display. The target is roughly decade-old shared machines, which is the environment to test against rather than a high-end fallback.

Still to verify on old hardware: that the system webview is new enough for Tauri: WebView2 on Windows 10, WebKitGTK on Linux. This is the main platform assumption that needs checking on a real machine.

11. Distribution

The primary channels are USB-drive images and local-network hosting. Many of the learners this is for never connect a device to the internet directly; content and updates reach the machine through a facilitator's occasional sync or a thumb drive carried over by hand.

The secondary channel is a standard web download, for facilitators and contributors who are online.

The consequence for design is that a person, usually the facilitator, is often how content gets onto a machine. Both the quick-start (§6) and the redownload path (§8) assume that, rather than assuming each device updates itself.

12. Accessibility and inclusion

The target standard is WCAG 2.2 AA.

The non-negotiable requirements are:

full keyboard operation;

screen-reader support for NVDA and JAWS on Windows, and Orca on Linux;

reduced-motion compliance: the app honors prefers-reduced-motion, avoids flashing, and keeps default motion to small movements, opacity, and color;

sufficient color contrast;

no information conveyed by color or audio alone.

VoiceOver is out of scope, since macOS is unsupported.

The ARIA markup for each interactive item type belongs in the technical design doc, not left to module authors. That covers multiple choice, typed input, and matching, including the harder drag-to-match case, which needs a solid keyboard and selection fallback.

The design follows Universal Design for Learning, offering several ways to engage with material, take it in, and respond. This covers learners with disabilities and multilingual learners in one framework.

Localization is built into the structure: every UI string can be translated, and the app is not locked to English. Part II §8 has the details. The work is tested with real assistive-technology users.

13. Sustainability, license, and governance

License. GPLv3, a copyleft license. Every dependency is open source as well. Copyleft keeps any derivative free and open, which stops a vendor from taking the code, wrapping it in a proprietary cloud product with tracking, and selling it back to schools. That is the outcome the project is built to prevent. AGPL would add protection against a network-service loophole, but a distributed offline desktop app doesn't have that loophole, so GPLv3 is the right fit.

Governance. The project lives in an open repository. Contributions come in under a DCO sign-off rather than a CLA: a DCO is a one-line certificate of origin and assigns no rights, whereas a CLA would assign rights upward, which runs against the point of the project. A module clears review when it has at least two approvers, passes schema validation, passes the accessibility checklist, and carries an open license.

First-release exception. The two-approver rule deadlocks the very first module in a language, because there is no community yet to supply a second reviewer. For a language's first release, the core maintainers can act as reviewers, on the condition that they recruit independent reviewers within a set window. Once a module has its own contributors, the normal two-approver rule takes over.

Why the economics hold. There is no growth number that anyone has to inflate, which is what keeps the no-dark-patterns rule from being undercut later.

14. Success metrics, without surveilling anyone

The constraint is to measure success without per-learner telemetry.

The adoption signals are downloads, the number of language modules, the count of contributors and merged pull requests, facilitator installs, and the opt-in aggregate trends. As illustrative targets: five language modules within six months of v1, and 90% of a module's items below a 15% aggregate error rate within three months of its release.

Content health shows up in those aggregate error rates moving toward the target band, and in how quickly flagged items get fixed.

What the project does not measure is per-learner progress, retention, time spent in the app, or anything else that would require identifying a child.

The qualitative picture comes from talking to facilitators and learners and from watching sessions in classrooms, all opt-in and done by people, never instrumented.

15. Risks and open questions

Open. The real open item is the initial build runway: even a tool designed to survive a funding drought needs money to build in the first place. The ways to handle that are grants, a fiscal sponsor, and a minimal first alpha of a single module to keep the upfront cost small. A few format niceties are also still open and can wait: a friendlier YAML authoring layer, and templated grammar and conjugation families. Part II §13 covers those.

Resolved. The content format is settled (Part II). Typed production and answer normalization are settled (Part II §4–§5). The license is GPLv3. The runtime footprint is settled: SM-2, no model at runtime, generation moved to authoring time. Aggregate-sync integrity turned out to be a non-issue, since the packet carries no identity or personal data, the figures are advisory and human-reviewed, and there is no auth surface to attack. The platform is Linux and Windows 10. The runtime shell is Tauri. The cold-start content is two modules, Spanish-from-English and English-from-Spanish (§16). Distribution is USB and local-network first.

To verify. The system webview version on the oldest target machines (§10).

16. Cold-start content (v1)

The first release ships two separate modules: es-from-en, Spanish for English speakers, and en-from-es, English for Spanish speakers. These are different bodies of content with different audio needs (see Part II §7), so each is its own module covering one source-and-target pair, rather than one combined bidirectional package. Between them they address the largest demographic need in North American under-resourced settings, and they make the localization and two-direction architecture prove itself from the start.

Appendix A: Technical overview

This is for engineers and contributors. The full technical design is a separate document.

There are three surfaces. The runtime is the Tauri shell with the SM-2 scheduler; it reads modules, runs no model, and uses no network. The authoring side is the open repo, with AI-assisted drafting, pull-request review, and JSON-Schema validation. The sync side is optional and aggregate-only, with a static collector.

The stack is Tauri, with a Rust core and a web UI. The SM-2 scheduler is a small, deterministic, unit-tested module. Content is declarative JSON validated against a JSON Schema.

The separate technical design doc covers SM-2 tuning, including whether SM-2 state is shared or kept separate across skill types; Tauri packaging for each target; the sync collector; and the ARIA patterns for each item type, including the drag-to-match fallback. Keeping it separate keeps this PRD readable for non-engineers.

Appendix B: Out of scope (for now)

Mobile and tablet. Windows 11, macOS, and other platforms (§10). Subjects other than language, which are post-v1 exploration at most. Multiplayer or cohort features, whether facilitator-led or peer-to-peer. And anything that would require an account, a server, a model at runtime, or per-learner data.

Part II: Content-Format Specification (Draft 2)

Status: Draft 2. This is the companion to the PRD above. It defines the language module: a self-contained, versioned, hash-addressed bundle of learning items along with their assets and metadata. It is the schema the scheduler reads, the one contributors write against, and the one the sync hash is computed from.

Since Draft 1: production items are now typed input (§4); the answer-normalization policy is written, with a per-language profile and the Spanish accent rule (§5); Piper is named as the recommended authoring-time TTS (§7); scheduler behavior across skill types and per-type ARIA are marked as technical-design-doc material (§4 and PRD Appendix A).

1. Principles

Declarative and readable. Modules are authored and reviewed as text in git, so diffs are legible.

Complete offline. A module carries everything it needs: items, audio, images, and strings. Nothing is fetched at runtime.

Hash-addressed. Each item has a stable content hash that keys both the scheduler and the aggregate-sync packet.

Accessible from the start. Accessibility metadata such as alt text and transcripts is required, not optional.

Localizable. UI and chrome strings are kept separate from content.

Easy to contribute to. One documented schema, machine-checkable with JSON Schema, with a low barrier to entry.

No model at runtime. Anything generative, including speech, is produced at authoring time and shipped as an asset. The runtime only reads.

2. Module structure

One module covers one source-and-target pair. A bidirectional pair is therefore two modules, such as es-from-en and en-from-es. A module is a directory:

text

es-from-en/

  module.json        # metadata

  items/             # item files (JSON)

    0001.json

    ...

  audio/             # optional, hash-named assets

    <hash>.opus

  images/            # optional, hash-named assets

    <hash>.svg

  strings/           # UI/chrome localization (separate from content)

    en.json

    es.json

  LICENSE

3. Module metadata (module.json)

module.json carries: schemaVersion (the content-format version it targets) · id (stable, e.g. es-from-en) · name and description · sourceLanguage and targetLanguage (BCP-47, e.g. en, es) · direction (which language the learner speaks and which they're learning) · version (semver of the content) · license (open and GPL-compatible) · contributors · audioIncluded (whether the module ships content audio) · normalization (optional; overrides the standard per-language profile chosen by targetLanguage, per §5).

4. Item schema

Each item is a JSON object with these fields:

id: stable within the module, never reused.

type: one of a small fixed set the runtime understands. recognition (see the target, give the meaning), production (see the source, produce the target), listening (hear the target; requires audio), and match (pair items up). The set grows only by adding a type the runtime knows how to handle.

prompt: the stimulus, as text; which language depends on the type.

answer: the expected response. acceptable: an array of variants that also count, judged per §5.

distractors: an array, used only by the selection-judged types.

audio: optional, { "ref": "<hash>", "transcript": "…" }, pointing at a packaged audio asset (§7).

image: optional, { "ref": "<hash>", "alt": "…" }, with alt required.

tags: skill and topic tags. An optional difficulty hint feeds the learner's own pace-setting and the initial ordering, and is not used to profile anyone.

notes: optional notes for contributors. Not shown to the learner, and left out of the hash.

Rendering and judging. A production item always renders as a typed free-text field, and is judged against answer and acceptable through the normalization steps in §5; it does not use distractors. A production item that quietly became multiple choice would be a recognition item with the prompt reversed, not actual recall, which is why production stays typed. A recognition or listening item renders as multiple choice when it has distractors, judged by selection, and otherwise renders as typed input judged per §5. A match item is judged by selection.

The scheduler is not the author's concern. How the SM-2 scheduler treats different item types, including whether the types share one state pool or keep separate state, is a runtime detail covered in the technical design doc. Authors write items; scheduling is not something they set.

5. Answer normalization (for typed answers)

This applies to any typed answer: every production item, plus any recognition or listening item shown as typed input. Selection-judged items, meaning match and multiple-choice recognition or listening, don't use it.

To judge a typed answer, the runtime runs the same steps on both the learner's input and each answer/acceptable variant, then checks for an exact match:

Trim and collapse whitespace: remove leading and trailing spaces, and reduce internal runs to a single space.

Unicode-normalize to a single form (NFKD), so that strings that look identical but use different code points compare as equal. NFKD decomposes an accented character into a base letter plus a combining mark; it does not remove the mark.

Case-fold to lowercase.

Normalize punctuation: unify curly and straight quotes and apostrophes, and drop terminal sentence punctuation (., !, ?, and the Spanish ¿ and ¡). Internal punctuation that carries meaning stays.

Compare for an exact match against each acceptable variant.

Per-language profile. What can be folded safely varies by language, so each module uses a standard profile chosen by its targetLanguage, which module.json can override:

Spanish target: accents are required. They carry meaning (está versus esta, sí versus si, él versus el, tú versus tu), so a missing accent does not earn credit, and ñ is its own letter and is never folded to n.

English target: accents barely matter, so the profile may fold the occasional one (café and cafe). Articles and contractions are handled through acceptable.

Variants stay explicit. Normalization cleans up mechanical noise like whitespace, case, and quote style. It does not decide meaning. Whether "a dog", "the dog", and "dog" all count is an authoring choice, written into the acceptable array, not something the normalizer infers. The review checklist confirms the obvious variants are present.

The principle behind all of this is to normalize how an answer is written, never what it means. A normalizer that strips Spanish accents to be forgiving will accept the wrong word, which is the kind of unfair miss the product is trying to avoid. That is why the profile is per-language and accent-stripping is never the default.

6. Hashing and versioning

The item content hash is a SHA-256 over a canonical serialization of the item's content fields, using RFC 8785 (JCS) so the keys are sorted and whitespace is normalized. The content fields are type, prompt, answer, acceptable, distractors, audio.ref, and image.ref. The hash leaves out notes and any ordering. So editing an item's content produces a new hash, and the scheduler and the aggregate stats then treat it as a new item, which is correct, since a changed item is a different thing to learn and to measure. Editing notes leaves the hash unchanged, so a typo fix doesn't churn anything.

Module versions follow semver. Adding items is a minor bump; changing content is minor or major depending on the change; removing items is a major bump. A redownload replaces the whole module, while sync pulls new or changed items by hash.

SM-2 state is per-learner, stored only on the device, and keyed by item.id. It is never part of a module and never synced. That is the zero-data rule restated at the level of the format.

7. Assets, and the audio decision

Content audio is optional, pre-recorded, and shipped with the module, referenced by hash. There is no text-to-speech at runtime, because that would need either the model the project has ruled out or a system voice that may not exist on a reflashed Linux box.

The format is Opus at a low bitrate, around 24 to 32 kbps mono, to keep the thumb-drive image small. A module declares audioIncluded, and modules that don't need audio ship none, which keeps the image lean.

The guidance is direction-aware. The en-from-es module should ship audio, because English spelling and sound diverge so widely that hearing a word is half the skill for an English learner. The es-from-en module can skip it, because Spanish is phonetically regular and text is mostly enough. Either way the field exists in the schema, so the choice is made on purpose for each module rather than buried in code.

For contributors without human recordings, the recommended authoring-time tool is Piper: it runs offline, is open source, handles English and Spanish, and produces audio that bakes cleanly to Opus. A contributor can generate assets with Piper and ship the files, and the review checklist checks pronunciation and prosody. Human recordings are still welcome wherever they exist.

Every audio asset includes a transcript, both for accessibility and so that an install without audio can still show the item as text.

Images are optional, preferably SVG since it is tiny and scales, named by hash, and they require alt text. All assets are content-hashed and bundled, and nothing is fetched at runtime.

8. Localization: chrome versus content

Content means the language data itself, the Spanish and English items. It is specific to the language pair and lives in items/.

Chrome and UI strings are the buttons, Kezer the Owl's lines, and the settings. They are translatable and keyed, PO-style, in strings/<lang>.json. This lets the interface appear in the learner's own language regardless of what they're studying, so a Spanish-speaking child learning English sees a Spanish interface.

Right-to-left languages are out of scope for v1 modules, but the string layer keeps directionality metadata so that adding them later isn't a rewrite.

9. Accessibility metadata (required)

Every image carries alt text and every audio asset carries a transcript. An item has to be answerable without depending on audio or color by itself; audio adds to the text rather than standing in for it. The review checklist enforces this. The standard is WCAG 2.2 AA (PRD §12), and the ARIA markup for each interactive item type is in the technical design doc (PRD §12 and Appendix A).

10. Sync contract (cross-reference)

The aggregate-sync packet is keyed on the item content hash and contains { itemHash, moduleVersion, attempts, errorRate }, thresholded on the device, with no identity attached. A stable, meaningful hash is part of the reason this format exists. See PRD §8.

11. Validation and review

A module is validated against a published JSON Schema before it can merge. The pull-request gates are at least two approvers, a passing schema validation, a passing accessibility checklist (alt text, transcripts, and no items that rely on audio or color alone), and an open license. AI-assisted items go through the same review as hand-written ones.

The first release of a language module is the exception, mirroring PRD §13: there is no community yet to provide a second reviewer, so the core maintainers can review it, on the condition that they recruit independent reviewers within a set window. Once the module has its own contributors, the usual two-approver gate applies.

12. Example items

A listening item from es-from-en, where the learner hears Spanish and gives the English meaning:

json

{

  "id": "0042",

  "type": "listening",

  "audio": { "ref": "a1b2c3…", "transcript": "el perro" },

  "answer": "the dog",

  "acceptable": ["a dog", "dog"],

  "distractors": ["the cat", "the house", "the water"],

  "image": { "ref": "d4e5f6…", "alt": "a brown dog" },

  "tags": ["animals", "nouns"],

  "difficulty": 1

}

A production item from es-from-en, where the learner sees English and types the Spanish. It is typed and accent-sensitive:


json

{

  "id": "0108",

  "type": "production",

  "prompt": "the dog",

  "answer": "el perro",

  "acceptable": ["el perro"],

  "tags": ["animals", "nouns"],

  "difficulty": 1

}

Here the learner has to type el perro. Under the Spanish profile in §5, an accented target like el café would be marked wrong if the accent were left off, rather than accepted anyway.

13. Open questions

Two format questions are still open, and both can wait. First, whether to offer a friendlier YAML authoring layer that compiles to JSON and validates against the same schema; JSON stays the canonical form regardless. Second, whether grammar and conjugation items stay atomic, one fact each, or gain authored families that expand at authoring time.

Two further items are not format questions and sit in the technical design doc: whether SM-2 state is shared or kept separate across skill types, and the per-item-type ARIA patterns, including the drag-to-match fallback.

No comments:

Post a Comment