小象 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.

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

intentfragmentsrecallprecision
propose3510.830.23
explain2690.350.21
report-problem1970.340.41
approve1880.490.73
constrain1880.160.33
clarify1810.040.30
ask-action1670.250.39
report1380.110.47
qualify1330.000.00
disagree790.130.45
redirect690.030.67
delegate640.020.20
verify610.030.40
extend480.081.00
defer460.000.00
continue410.120.83
prioritize400.050.67
collect260.000.00
retract140.000.00
ask-information20.000.00
challenge20.000.00
provide-reference20.000.00
concede10.000.00
contrast10.000.00
reframe10.000.00
withdraw10.000.00

Most common mistakes

labelledpredictedcount
explainpropose141
constrainpropose115
ask-actionpropose95
clarifypropose90
report-problempropose72
qualifypropose71
reportpropose61
clarifyexplain59
approvepropose50
report-problemexplain42

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.

textlabels
okapprove 11, continue 2
please continuecontinue 2, ask-action 1
hmreport 1, continue 1, qualify 1
just say hiask-action 1, verify 1
quotecollect 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”.

intentcues
disagreei reject, reject, i disagree, disagree, that's wrong, wrong, incorrect, i object, not right
approvei agree, agree, sounds good, looks good, that's right, approved, go ahead, great
report-problemdoesn't work, not working, broken, failed, fails, bug
ask-actioncould 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.

The HTML report from that run

Open the report on its own page.