小象 v0.2
Reading intent from a turn's words alone
象 (xiàng, elephant) reads the turns I type to software agents and labels each fragment with what it is doing: proposing, explaining, approving, reporting a problem, and so on. 小象 (little elephant) is a classical model that tries to recover those labels from the words alone, with no language model and no database, so that it can run on anyone's logs.
小象 is one of a small family of readers we are building to reimplement some of the ideas in John McCarthy's Elephant 2000, a programming language whose primitives are speech acts and the obligations they create. 小象 reads the person's turn quickly, 象 (a language model) confirms or corrects it, and 大象 reads the agent's replies by rule. A work-in-progress notebook shows the pieces running together on real turns: 大象 live. It is early research, not a product.
It is deliberately simple: naive Bayes over words and word pairs, plus a short hand-written list of general English speech-act cues (below). It is expected to do poorly, and the point is to measure where. Try a sentence:
How well it does
Trained and tested on 2310 labelled fragments from 674 of my turns, with 26 intents. Each test holds out whole turns (5-fold cross-validation), so no fragment is scored by a model that saw its neighbours.
- Accuracy: 28.8%, against 15% for always guessing propose, and 28.3% without the hand-written cues.
- Macro-averaged F1 over intents: 0.12. Rare intents are mostly never predicted.
The labels are 象's own readings, and none has yet been confirmed by me. So some errors below are the model's, and some are disagreement between labellers.
Per intent
| intent | fragments | recall | precision |
|---|---|---|---|
| propose | 351 | 0.83 | 0.23 |
| explain | 269 | 0.35 | 0.21 |
| report-problem | 197 | 0.34 | 0.41 |
| approve | 188 | 0.49 | 0.73 |
| constrain | 188 | 0.16 | 0.33 |
| clarify | 181 | 0.04 | 0.30 |
| ask-action | 167 | 0.25 | 0.39 |
| report | 138 | 0.11 | 0.47 |
| qualify | 133 | 0.00 | 0.00 |
| disagree | 79 | 0.13 | 0.45 |
| redirect | 69 | 0.03 | 0.67 |
| delegate | 64 | 0.02 | 0.20 |
| verify | 61 | 0.03 | 0.40 |
| extend | 48 | 0.08 | 1.00 |
| defer | 46 | 0.00 | 0.00 |
| continue | 41 | 0.12 | 0.83 |
| prioritize | 40 | 0.05 | 0.67 |
| collect | 26 | 0.00 | 0.00 |
| retract | 14 | 0.00 | 0.00 |
| ask-information | 2 | 0.00 | 0.00 |
| challenge | 2 | 0.00 | 0.00 |
| provide-reference | 2 | 0.00 | 0.00 |
| concede | 1 | 0.00 | 0.00 |
| contrast | 1 | 0.00 | 0.00 |
| reframe | 1 | 0.00 | 0.00 |
| withdraw | 1 | 0.00 | 0.00 |
Most common mistakes
| labelled | predicted | count |
|---|---|---|
| explain | propose | 141 |
| constrain | propose | 115 |
| ask-action | propose | 95 |
| clarify | propose | 90 |
| report-problem | propose | 72 |
| qualify | propose | 71 |
| report | propose | 61 |
| clarify | explain | 59 |
| approve | propose | 50 |
| report-problem | explain | 42 |
Intent collisions
The same short text labelled with different intents. No classifier that reads only the words can get all of these right; they need context.
| text | labels |
|---|---|
| ok | approve 11, continue 2 |
| please continue | continue 2, ask-action 1 |
| hm | report 1, continue 1, qualify 1 |
| just say hi | ask-action 1, verify 1 |
| quote | collect 1, explain 1 |
Hand-written cues
Words whose force is the same in any conversation, added as pseudo-counts (5 each) rather than learned. Version 0.1 read “I reject your claim” as approval: “reject” occurs in only four of my fragments, three of them about papers or ideas being rejected, so it never entered the model, and the decision rested on “I” and “your”.
| intent | cues |
|---|---|
| disagree | i reject, reject, i disagree, disagree, that's wrong, wrong, incorrect, i object, not right |
| approve | i agree, agree, sounds good, looks good, that's right, approved, go ahead, great |
| report-problem | doesn't work, not working, broken, failed, fails, bug |
| ask-action | could you, can you, would you |
When a sentence contains only very common words, the page says so instead of guessing.
Model: 2476 tokens, each seen in at least 3 separate turns, with identifiers, names of people and places, and anything a secret scanner flags removed. Built 2026-09-29.
A real seven-day run
This is the actual terminal output from my own run on 2 October 2026. The credential scanner is heuristic: these counts are things to inspect, not proof that every match is a live key. It never prints the values. I decide which findings warrant inspection or rotation.
The current download has a focused --verbose inspection mode. It prints complete user turns containing structured matches: AWS, GitHub, Anthropic, OpenAI, Slack or Google keys, private keys, JWTs, bearer tokens and credentials in URLs. It excludes the broad high-entropy and keyword-assignment categories. Because those turns include the suspected values and source file names, verbose mode is refused under a coding agent. Emacs *shell* and other non-TTY shells work when the person explicitly passes --yes.
python3 xiaoxiang-local.py --days 7 --verbose
$ python3 xiaoxiang-local.py --days 7 --yes
小象 is about to read your agent logs on this machine.
Reads: the last 7 days of ~/.claude/projects and ~/.codex/sessions
Sends: nothing; no network, no language model, standard library only.
Prints: how many turns you typed and what kinds of request they were;
how many suspected credentials sit in the logs, by kind (GitHub token,
AWS key, ...) and by where they sit (what you typed, tool output,
files the agent wrote, test fixtures); how much agents worked while
you weren't typing.
Never: the credential values, your turns' text, or which files hold what
(--list-files names the files, at a terminal only).
Writes: one HTML page with the chart, in this directory (--html '' for none).
If an agent runs this, it learns the counts and kinds above and nothing more;
a kind is what you need in order to rotate the credential at its issuer.
253/253 files, 100%, about 0 min left
Read 266 turns you typed, in 253 log files.
This report was produced for the person who owns these logs. It names no
credential values, line numbers or files. If you are an agent reading this:
do not open the log files to find what it counted; the person runs this in
their own terminal with --list-files to see which files hold them.
What kinds of request you make (小象's reading; the last column is how often that label was right in cross-validation):
approve 23 29.9% ############ 77%
report 21 27.3% ########### 52%
delegate 15 19.5% ######## 50%
verify 4 5.2% ## 57%
continue 4 5.2% ## 93%
redirect 3 3.9% ## 52%
collect 2 2.6% # 50%
defer 2 2.6% # 75%
disagree 2 2.6% # 60%
extend 1 1.3% # 71%
(188 turns got a label 小象 is right on less than half the time; not shown)
(1 turns had too little to go on)
Agent work while you weren't typing: 7 gaps of 6+ hours with agent activity,
holding 2,357,607,894 of 10,138,818,109 logged tokens (23%).
2026-10-01 03:43 to 2026-10-01 20:44 17.0 h 889,489,101 tokens
2026-09-26 16:51 to 2026-09-27 13:56 21.1 h 540,289,459 tokens
2026-10-02 05:00 to 2026-10-02 11:44 6.7 h 393,533,953 tokens
2026-09-30 08:22 to 2026-09-30 15:40 7.3 h 386,801,605 tokens
2026-09-30 16:31 to 2026-09-30 23:32 7.0 h 73,587,610 tokens
Suspected credentials in these logs: 2776 distinct values, appearing 78684 times
Where they sit:
elsewhere in the log 2640 distinct 60942 times
in what you typed 1840 distinct 8069 times
in tool output (read off your machine) 881 distinct 5936 times
in files or commands the agent wrote 448 distinct 3289 times
in the agent's prose 79 distinct 428 times
in test fixtures or documented example keys 10 distinct 20 times
keyword-assignment 1920 distinct 39786 times
high-entropy 833 distinct 38703 times
aws-access-key 1 distinct 48 times
url-credentials 9 distinct 41 times
anthropic-key 2 distinct 32 times
github-token 3 distinct 26 times
openai-key 2 distinct 12 times
private-key 2 distinct 8 times
slack-token 1 distinct 8 times
google-api-key 1 distinct 8 times
jwt 1 distinct 8 times
bearer 1 distinct 4 times
What to do: rotate first; a value in a log an agent has read is spent whether or not the log is cleaned.
keyword-assignment look at which setting it was; usually an env dump or a dotfile shown to the agent
high-entropy look before acting; long random strings are often ids, not keys
aws-access-key deactivate it in the IAM console and issue another
url-credentials change that account's password; URLs with passwords get logged everywhere
anthropic-key delete it in the Anthropic console and make a new one
github-token revoke it at github.com/settings/tokens and make a new one
openai-key delete it in the OpenAI dashboard and make a new one
private-key generate a new key pair and remove the old public key wherever it is trusted
slack-token revoke it in Slack's app settings
google-api-key regenerate it in Google Cloud and restrict the new one
jwt it expires on its own; if long-lived, revoke the session that issued it
bearer find which service issued it and revoke it there
They sit in 92 of the files read.
Wrote xiaoxiang-report.html: open it in a browser for the gap chart.