Two faithful ports of Metacat 1.2, James B. Marshall's model of analogy-making and
self-watching perception. One is in Racket, with a native
racket/gui interface. The other is in Python 3, using only the standard library, with a
tkinter interface, plus an optional Qt interface that shows every window at once in a
single window. Both reproduce the original program's runs event for event, checked
against the original itself running headless under Chez Scheme 10.
Metacat home page: https://science.slc.edu/~jmarshall/metacat/
The Racket port on Run 7 of Marshall's dissertation, abc → abd; xyz → ? with seed
3852097033. The c → d rule can't be applied to z, which has no successor. After several
snags ("All right, I've had enough of this!") Metacat sees abc and xyz as mirror images
and answers wyz: "I think this answer is great!" More screenshots are in the
gallery.
- What Metacat is
- Quick start
- Problems to try
- How the ports are checked
- Repository map
- How it was made
- Credits and license
Metacat solves letter-string analogy problems: if abc changes to abd, what does xyz
change to? It builds an interpretation of a problem out of many small, partly random
actions called codelets. Codelets notice bonds between letters, group letters, map one
string onto another, describe the change as a rule, and apply the rule to the target string.
A network of concepts, the Slipnet, decides what seems relevant. A temperature measures
how coherent the current interpretation is: high when things are a mess, low when they hang
together.
Metacat is the successor to Copycat, by Melanie Mitchell and Douglas Hofstadter. What it adds is self-watching:
- it keeps a Temporal Trace of its own processing;
- it notices when it keeps hitting the same snag, and "jumps out of the system" by clamping a pattern of concepts;
- it remembers its answers in an Episodic Memory, and can compare answers;
- it can justify an answer it is given;
- it says what it is doing in a running Commentary.
All of this is described in Marshall's dissertation,
Metacat: A Self-Watching Cognitive Architecture for Analogy-Making and High-Level
Perception (Indiana
University, 1999). A copy and its figures are in docs/reference/.
The original Metacat 1.2 (released 2020) is written in Chez Scheme, with a GUI built on the Scheme Widget Library (SWL), which is no longer maintained. Until now, the easy way to run it was a VirtualBox image.
| Racket port | Python port | |
|---|---|---|
| Needs | Racket 8.x CS with racket/gui (sudo apt install racket) |
Python 3.12, standard library + tkinter |
| GUI | racket racket/main.rkt |
cd python && python3 -m metacat.gui |
| One-window GUI | racket racket/one-window.rkt |
cd python && python3 -m metacat.qt (needs PySide6) |
| Headless | racket racket/cli.rkt abc abd xyz --seed 3852097033 |
cd python && python3 -m metacat abc abd xyz --seed 3852097033 |
| Install | bash make-dist.sh builds a standalone program in build/metacat/ |
pip install -e python installs metacat and metacat-gui; pip install -e 'python[qt]' adds PySide6 and metacat-qt |
| More | racket/README.md |
python/README.md |
Chez Scheme 10 (sudo apt install chezscheme) is needed only to run the original and the
equivalence tests.
Everything in one window. The original, and both ports' main GUIs, open about ten
windows. The Python port's Qt GUI (python/metacat/qt/)
puts them all in one window, for a screen of 1920×1080 or larger, with the same pictures,
controls and menus. The panes can be resized, hidden and shown, and the layout is
remembered:
pip install -e 'python[qt]'
metacat-qt # Run 7 is already in the box: press Enter, then Go"The answer "wyz" occurs to me. I think this answer is great!"
— Metacat, Run 7
"Aha! I see why this answer makes sense. I think it's a pretty dumb answer."
— Metacat, asked to justify
ijk → abdforabc → abd
Asked to justify the literal-minded answer abd (seed 1, codelet 1835), Metacat builds rules that turn each letter of ijk into a, b and d, sees why they work, and isn't impressed. Every pane is shown here, including the Bottom Themes and the EEG (bottom), which justify runs bring into play.
Using the GUI. Type a problem into the control panel's box and press Enter. Enter sets the problem up and waits; Go starts the run. Then:
- Go continues the run after a stop or a breakpoint;
- Step runs one step at a time (the step interval is under Options);
- Stop interrupts the run;
- Reset starts the problem again;
- the speed slider slows the run down so you can watch the structures being built.
When Metacat finds an answer it stops. Press Go, or click the Workspace, to have it look
for another. The Demos menu loads the runs from Marshall's dissertation with their
seeds; see docs/demos.md for which of them replay as documented.
The box accepts:
abc abd xyzasks whatxyzchanges to;abc abd xyz 7does the same with random seed 7, so the run can be replayed exactly;abc abd xyz wyzasks Metacat to justify the answerwyz;abc abd xyz wyz 7justifies it with a seed.
Headless, a run prints its commentary and answers:
$ racket racket/cli.rkt abc abd xyz --seed 3852097033
...
Comment: The answer "wyz" occurs to me. I think this answer is great!
Answer: wyz quality 91 codelet 2170 temperature 15
The CLI's flags (--max-codelets, --keep-going, --trace FILE, --verbose) are the same
in both ports; see their READMEs.
| Problem | What happens |
|---|---|
abc abd ijk |
the easy one: ijl, or the literal-minded ijd |
abc abd iijjkk |
letters or groups? iijjkl or iijjll |
abc abd mrrjjj |
mrrjjk, or, if Metacat sees the lengths 1-2-3, mrrjjjj |
abc abd xyz |
z has no successor: snags, then xyd, wyz, … |
eqe qeq abbbc |
a symmetry problem; seed 2302461154 finds qcccb ("really terrible") |
rst rsu xyz |
another end-of-the-alphabet problem |
Left: abc → abd; mrrjjj → ? (seed 1) answered mrrjjk, with the rr and jjj groups
mapped onto b and c. Right: asked to justify xyd, Metacat builds both rules and
concludes "Aha! I see why this answer makes sense. I think it's a pretty mediocre answer."
The aim is a faithful port, not a reinterpretation. Given the same seed, each port makes the same run as the original: the same codelets in the same order, the same structures, the same temperature, the same answers and the same commentary.
- The original is kept unchanged in
chez_scheme/original/. A gate checks its git tree hash against the import commit. - The original runs headless as an oracle.
chez_scheme/oracle/loads those 44 files, unmodified, into Chez Scheme 10 through a prelude. The prelude stubs out SWL, provides the oldextend-syntaxmacros, and instruments the program from outside. - Golden traces. The oracle recorded 109 seeded runs of 36 problems as JSON-lines
traces in
tests/golden/(tests/README.md). Every demo of the dissertation is included. - Both ports reproduce them event for event, byte for byte. So do 720 more runs with
seeds neither port was developed against, and so does the printed output. This takes
Chez's random-number generator bit for bit, Chez's order of evaluating arguments where it
matters, its
maporder, itssort, its exact arithmetic and its number printing. - Differential tests, layer by layer. Smaller Chez test batteries compare each layer with the original: utilities, coderack, slipnet, workspace, bonds and groups, bridges, rules, the SGL drawing language and the panels' graphics.
- The GUI only watches. Neither engine depends on its GUI. With every window attached, the golden runs still match.
What the ports do differently, and why, is in docs/divergences.md.
Bugs and oddities found in the original along the way, including two crashes, a copy-paste
bug and Chez's surprising evaluation order, are in
docs/anomalies_and_quirks.md.
bash tests/run-tests.sh # the Racket port and the Chez checks (about 10 min)
bash python/run-tests.sh # the Python port (about 7 min)
python3 ralph_loops/loop0001/gate.py # Racket, plus the check that the original is untouched
python3 ralph_loops/loop0002/gate.py # Python, plus the checks on the frozen referencesGUI tests run on a virtual display and need xvfb-run (Xvfb) and xwininfo (x11-utils).
Every folder has a README.
| Folder | What's there |
|---|---|
chez_scheme/ |
the original Metacat 1.2 (read-only) and the headless oracle |
racket/ |
the Racket port: engine, GUI, tests |
python/ |
the Python port: package, GUI, oracle captures, fixtures, tests |
tests/ |
shared test data: golden traces, problem list, Chez differential batteries |
docs/ |
code map, porting notes, trace format, anomalies, essays, the dissertation, the screenshot gallery |
ralph_loops/ |
the loops that built the ports: loop0001 (Racket), loop0002 (Python) |
make-dist.sh |
builds the standalone Racket program |
Both ports were built by "Ralph loops". A driver (loop.py) runs one fresh Claude Code
session per work item. After each session it runs a regression gate, allows up to three fix
attempts, then commits and pushes. The Racket port took 18 iterations
(loop0001). The Python port was a test-driven translation
that wrote every test before its code, with expected values frozen from Chez; it also took 18
iterations (loop0002). The method is in
ralph_loops/ralph_loop_guide.md.
Two essays in docs/ place Metacat beside Numbo and Ganesalingam and Gowers's theorem prover:
the similarities and
a family resemblance.
Metacat is © 1999, 2003 James B. Marshall. It is based on Copycat, originally written in Common Lisp by Melanie Mitchell, from Douglas Hofstadter's Fluid Analogies Research Group. Both ports keep Marshall's copyright headers on every translated file, with a line saying what was ported.
The original and both ports are free software under the GNU General Public License,
version 2 or later; see chez_scheme/original/LICENSE.
Metacat comes with no warranty.




