跪拜 Guibai
← All articles
Artificial Intelligence · Agent · AI Programming

Hand-Built Agent Memory and Context Compression in 200 Lines of TypeScript

By 不一样的少年_ ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Agent memory and context management are the difference between a demo that answers one question and a tool that can finish a multi-step programming task. This implementation shows how to separate complete history from working memory, use real API token counts to trigger compression, and generate structured summaries that preserve task state—all without framework lock-in.

Summary

A hand-built TypeScript agent, previously limited to single-turn interactions, gains two new capabilities without pulling in LangChain or any other framework. A `Session` class now holds the full message history within a single process, and a terminal readline loop lets users ask follow-up questions without restarting the program. The agent remembers previous turns because the same `Session` instance persists across prompts.

When the conversation grows too long for the model's context window, a `Compactor` module steps in. It uses a hybrid token-counting strategy—real API usage snapshots plus cheap UTF-8 byte estimates for new messages—to decide when to compress. The compactor calls the same LLM to produce a structured summary of older messages while keeping roughly the last 20,000 tokens of recent conversation in their original form. The summary preserves user goals, constraints, completed work, and next steps, so the model can continue a long task without losing critical context.

The result is a terminal agent that behaves like Claude Code or Codex CLI: start it once, ask a chain of related questions, and watch it automatically fold early history into a summary when the token budget is tight. The complete history remains intact in the `Session` object; only the working memory sent to the model is shortened.

Takeaways
Session holds the full message history inside a single process; it is not permanent storage and disappears when the process exits.
A terminal readline loop keeps the program alive, letting users ask follow-up questions without restarting.
Context compression splits message history into a structured summary of older content and the original text of recent messages.
The compactor uses a hybrid token count: real `usage.total_tokens` from the API plus a UTF-8 bytes ÷ 4 estimate for new, uncounted messages.
Compression is triggered when working memory exceeds `contextWindow - reserveTokens` (default reserve: 16,384 tokens).
Approximately the last 20,000 tokens are kept as original text; the cut point always aligns with a `user` message to avoid splitting tool-call sequences.
The summary prompt enforces a structure: User Goal, Constraints & Key Decisions, Completed, Current Issues, Files Read or Modified, and Next Steps.
Truncating large tool outputs (over 8,000 characters) keeps only the head and tail, preventing a single tool result from dominating the context.
Compression never deletes the full `Session.history`; it only shortens the working memory sent to the model for the current request.
Conclusions

The design cleanly separates 'what is saved' (complete history) from 'what is sent' (working memory), which makes future persistence or different compression strategies easier to add.

Using real API token counts as a baseline and only estimating new messages is a pragmatic middle ground—more accurate than pure estimation, less complex than integrating a tokenizer for every model.

Keeping recent messages in original form while summarizing older ones aligns with the 'lost in the middle' phenomenon observed in long-context LLMs, where models attend best to the beginning and end of the context.

The compactor's cut-point logic prioritizes conversation integrity over hitting an exact token target, which avoids breaking tool-call sequences that would cause API errors or confuse the model.

Concepts & terms
Session
An in-memory object that holds the complete, ordered message history of a conversation within a single process. It provides `append()` to add messages and `getHistory()` to retrieve a deep copy, but does not survive process restarts.
Working Memory
The subset of conversation history actually sent to the model for a given request. It may consist of a structured summary of older messages plus the original text of recent messages, rather than the full history.
Context Compression
The process of replacing older conversation messages with a shorter, LLM-generated summary while keeping recent messages intact, in order to stay within the model's token limit without losing task-critical information.
Hybrid Token Counting
A strategy that uses real `usage.total_tokens` from the API for messages already sent, and a rough UTF-8 bytes ÷ 4 estimate only for new messages added since the last API call, avoiding the need for a model-specific tokenizer.
Compaction State
Metadata stored in the Session that records the latest summary text and the index in the full history up to which messages have been summarized, allowing incremental compression without re-processing already summarized content.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗